GO WBE学习笔记

GoWeb学习笔记

学习的资料来自杨旭老师在B站的视频

创建第一个Web程序(网页输出HelloWorld)

在go语言中创建一个网页中输出的HelloWorld,需要先创建一个访问的函数,然后指定相应的监听和服务

HadleFunc源码

func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
   DefaultServeMux.HandleFunc(pattern, handler)
}
使用HadleFunc,并创建内置函数的形式创建访问函数
//HandleFunc一共有两个参数,第一个参数是访问路径,第二个参数是路由函数
http.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) {
   writer.Write([]byte("Hello World!"))
})
使用HadleFunc,并调用外部函数的形式创建访问函数
//自定义的外部函数
func MyHandleFunc(w http.ResponseWriter,r *http.Request){
   w.Write([]byte("MyHandleFunc"))
}

func main(){
    //调用函数
    http.HandleFunc("/myHandleFunc",MyHandleFunc)
}

创建访问监听和服务

创建访问监听和服务有两种方式,一个是调用http.ListenAndServe方法,需要配置两个参数,另一个是调用http.Server,这种方式需要自定http.Server提供的参数,相对于http.ListenAndServe,这种方式更加灵活

http.ListenAndServe源码
func ListenAndServe(addr string, handler Handler) error {
   server := &Server{Addr: addr, Handler: handler}
   return server.ListenAndServe()
}
http.Server源码(去除了源码中的注释)
type Server struct {
   Addr string
   Handler Handler 
   TLSConfig *tls.Config
   ReadTimeout time.Duration
   ReadHeaderTimeout time.Duration
   WriteTimeout time.Duration
   IdleTimeout time.Duration
   MaxHeaderBytes int
   TLSNextProto map[string]func(*Server, *tls.Conn, Handler)
   ConnState func(net.Conn, ConnState)
   ErrorLog *log.Logger
   BaseContext func(net.Listener) context.Context
   ConnContext func(ctx context.Context, c net.Conn) context.Context
   inShutdown atomicBool 
   disableKeepAlives int32     
   nextProtoOnce     sync.Once 
   nextProtoErr      error     
   mu         sync.Mutex
   listeners  map[*net.Listener]struct{}
   activeConn map[*conn]struct{}
   doneChan   chan struct{}
   onShutdown []func()
}
使用http.ListenAndServe创建监听和服务
//使用nil相当与使用了go语言内置的http.DefaultServeMux(多路复用器)
//需要传入两个参数,分别是访问的地址和使用的访问函数
//当使用localhost的时候可以写为http.ListenAndServe(":8080",nil)
http.ListenAndServe("localhost:8080",nil)
使用http.Server创建监听和服务
//等同于http.ListenAndServe,但是使用这种方式配置更加灵活,因为可以设置更多参数值
 server := http.Server{
    Addr: "localhost:8080",
    Handler: nil,
 }
 server.ListenAndServe()
整合实现
直接创建
//访问路径为localhost:8080
http.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) {
   writer.Write([]byte("Hello World!"))
})
http.ListenAndServe("localhost:8080",nil)
通过外部函数和使用http.Server实现
//为了方便,写了一个监听的函数
func Listen(){
   server := http.Server{
      Addr: "localhost:8080",
      Handler: nil,
   }
   server.ListenAndServe()
}
//自定义的访问函数
func MyHandleFunc(w http.ResponseWriter,r *http.Request){
    //因为Write()函数的源码为Write([]byte) (int, error),所以在输出string类型的时候需要转换
	w.Write([]byte("Hello World"))
}
func main(){
    http.HandleFunc("/myHandleFunc",MyHandleFunc)
    Listen()
}

http.Handle源码

func Handle(pattern string, handler Handler) { 
    DefaultServeMux.Handle(pattern, handler) 
}
使用http.Handle创建一个Hello World

有http.Handle的源码可以看出传入的第二个参数为Handler,源码如下:

type Handler interface {
   ServeHTTP(ResponseWriter, *Request)
}

Handler是一个ServeHTTP类型的Type,所以传入Handle的第二个参数也得是ServeHTTP类型的访问函数

//自定义路由
type HelloHandler struct {}

//自定义路由
type AboutHandler struct {}
//定义访问控制器
func (m *HelloHandler) ServeHTTP(w http.ResponseWriter ,r *http.Request) {
   w.Write([]byte("Hello World!"))
}
//定义访问控制器
func (m *AboutHandler) ServeHTTP(w http.ResponseWriter ,r *http.Request) {
   w.Write([]byte("About!"))
}
func MyListen(){
	mh := HelloHandler{}
	ma := AboutHandler{}

	server := http.Server{
		Addr: ":8080",
		//不指定访问路由器,从而达成通过访问不同的路由参数而访问不同的自定义路由器
		Handler: nil,
	}
	http.Handle("/hello",&mh)
	http.Handle("/about",&ma)
	server.ListenAndServe()
}
注意

在上面使用Handle函数的时候是使用创建好的ServeHttp,我们也可以使用HandlerFunc来将创建的没有继承ServeHTTP的 路由转化

//使用HandlerFunc将自定义的访问控制转换为一个Handler
//HandlerFunc可以将某个具有适当签名的函数f适配成为一个Handler
http.Handle("/my", http.HandlerFunc(MyHandleFunc))

func MyHandleFunc(w http.ResponseWriter, r *http.Request) {
	w.Write([]byte("MyHandleFunc"))
}

Go语言的五个内置Handler

1.NotFoundHandler

返回一个handler,每个请求的相应都是404 page not found

func NotFoundHandler() Handler { return HandlerFunc(NotFound) }

2.RedirectHandler

返回一个handler,把每一个请求按照状态码跳转到指定的URL

常见的:StatusMovedPermanently、StatusFound、StatusSeeOther

func RedirectHandler(url string, code int) Handler {
   return &redirectHandler{url, code}
}

3.StripPrefix

去前缀,返回一个handler,在指定的url中去掉前缀,然后调用另一个handler

func StripPrefix(prefix string, h Handler) Handler {
   if prefix == "" {
      return h
   }
   return HandlerFunc(func(w ResponseWriter, r *Request) {
      if p := strings.TrimPrefix(r.URL.Path, prefix); len(p) < len(r.URL.Path) {
         r2 := new(Request)
         *r2 = *r
         r2.URL = new(url.URL)
         *r2.URL = *r.URL
         r2.URL.Path = p
         h.ServeHTTP(w, r2)
      } else {
         NotFound(w, r)
      }
   })
}

4.TimeoutHandler

返回一个handler,在指定时间内运行传入的handler

func TimeoutHandler(h Handler, dt time.Duration, msg string) Handler {
   return &timeoutHandler{
      handler: h,
      body:    msg,
      dt:      dt,
   }
}

5.FileServer

返回一个handler,使用基于root的文件系统来相应请求

func FileServer(root FileSystem) Handler {
   return &fileHandler{root}
}
type FileSystem interface {
   Open(name string) (File, error)
}

在使用时需要用到操作系统的文件系统,所以一般交给下面的函数来用

type Dir string
func (d Dir) Open(name string) (File, error) {
   if filepath.Separator != '/' && strings.ContainsRune(name, filepath.Separator) {
      return nil, errors.New("http: invalid character in file path")
   }
   dir := string(d)
   if dir == "" {
      dir = "."
   }
   fullName := filepath.Join(dir, filepath.FromSlash(path.Clean("/"+name)))
   f, err := os.Open(fullName)
   if err != nil {
      return nil, mapDirOpenError(err, fullName)
   }
   return f, nil
}

HTTP消息

HTTP Request 和HTTP Response,他俩具有相同的结构,都有请求行,0个或者多个url,空行以及可选的消息体(body)

Request(请求)

在GO语言中Request是一个struct,代表了客户端发送的HTTP请求消息(既可以代表客户端的请求,也可以代表服务端的请求),可以通过Request的方法访问请求中的Cookie、URL、User Agent等信息,源码如下:

type Request struct {
   Method string
   URL *url.URL
   Proto      string // "HTTP/1.0"
   ProtoMajor int    // 1
   ProtoMinor int    // 0
   Header Header
   Body io.ReadCloser
   GetBody func() (io.ReadCloser, error)
   ContentLength int64
   TransferEncoding []string
   Close bool
   Host string
   Form url.Values
   PostForm url.Values
   MultipartForm *multipart.Form
   Trailer Header
   RemoteAddr string
   RequestURI string
   TLS *tls.ConnectionState
   Cancel <-chan struct{}
   Response *Response
   ctx context.Context
}

其中几个重要的字段

1.URL

Request的URL字段就代表了请求行(请求信息第一行)里面的部分内容,URL字段是指向url.URL类型的一个指针,url.URL是一个struct源码如下:

type URL struct {
   Scheme      string
   Opaque      string    
   User        *Userinfo 
   Host        string    
   Path        string    
   RawPath     string   
   ForceQuery  bool      
   RawQuery    string   
   Fragment    string   
   RawFragment string   
}

URL的通用格式为:

scheme://[userinfo@]host/path[?query][#fragment]

不以斜杠开头的URL被解释为:

scheme:opaque[?query][#fragment]

URL Query

  • RawQuery提供实际查询的字符串
  • 通过Request的Form字段

r.URL.Query()

该方法会提供查询字符串对应的map[string] [] string

URL Fragment

就是URL格式中的#后面的部分

当请求从浏览器发出时,无法获取到Fragment的值,因为在浏览器发送请求的时候会把Fragment去掉

部分客户端工具发出的请求可以获取到Fragment的值,例如HTTP客户端包

2.Handler

请求和相应的headers是通过Header类型来描述的,它是一个map类型,用来描述HTTP header里的Key-Value对。

Header map的key是string类型,value是[]string

设置key时会创建一个空的[]string作为value,value里面第一个元素就是新的header的值

如果是为指定的key添加一个新的header值的话,执行append操作即可

3.Body

请求和相应的bodies都是使用Body字段来表示的

Body是一个io.ReadCloser接口,一个Reader接口和一个Closer接口

Reader接口定义了一个Open方法,参数[]byte,返回byte的数量、可选的错误

Closer接口定义了一个Close方法:没有参数,返回可选的错误

读取body的内容,调用Body的Read方法

func getQuery(){
   http.HandleFunc("/query", func(writer http.ResponseWriter, request *http.Request) {
      //获取URL
      url := request.URL
      //调用query方法
      query := url.Query()
      //根据传入的key值查询相应的数据,返回全部值
      id := query["id"]
      //以日志的形式打印在控制台
      log.Println(id)
      //根据传入的key值返回第一个值
      name := query.Get("name")
      log.Println(name)
   })
}

4.Form、PostForm、MultipartForm

通过表单发送post请求

<form action="/index" method="post">
	用户名<input type="text" name="name" />
	密码<input type="password" name="password" />
	<input type="submit" />
</form>

action是发送请求对应的服务器路径,method是发送请求的方式,有post和get两种,html表单里的数据会以name-value对的方式通过post请求发送出去。

name-value

通过psot发送的name-value数据对的格式通过表单的Content Type来指定,也就是表单里面的enctype属性,在form表单中enctype的默认属性为application/x-www-form-urlencoded。

1.application/x-www-form-urlencoded

在这个属性下,浏览器将会将表单数据编码到查询字符串里面,简单的文本格式使用这种方式

2.multipart/form-data

在这种属性下每一个name-value对都会被转化为一个mime消息部分

每一个部分都有自己的Content Type和Content Disposition,在上传文件的时候选用这个方式

3.text/plain

POST & GET

表单的method属性可以设置post和get两种属性

1.GET

get请求没有body,所有的数据都通过URL的name-value对来发送

2.POST

FORM字段

Resquest上的函数允许我们通过url或/和body中提取数据,form里面的数据是key-value对

通常的做法是先调用ParseForm或ParseMultipartForm来解析Request,然后相应的访问Form、PostForm或MultipartForm字段

PostForm

PostForm只支持application/x-www-form-urlencoded,

func getForm(){
   http.HandleFunc("/process", func(writer http.ResponseWriter, request *http.Request) {
      request.ParseForm()
       //输出到页面
      fmt.Fprintln(writer,request.Form)
       //以日志的形式打印到控制台
      log.Println(request.Form)
   })
}

前端代码:

<form action="http://localhost:8080/process" method="post" >
	用户名<input type="text" name="name" />
	密码<input type="password" name="password" />
	<input type="submit" />
</form>

MultipartForm

使用MultipartForm的时候需要先调用ParseMultipartForm,ParseMultipartForm会在必要的时候调用ParseForm,里面需要传入一个参数(读取的数据长度,单位为字节),MultipartForm只包含表单的key-value对,返回类型是一个struct而不是map,这个struct里面包含两个map,一个是你表单里面的数据,另一个是文件

func getMultipart(){
   http.HandleFunc("/process", func(writer http.ResponseWriter, request *http.Request) {
      //参数为字节,是上传数据的长度
      request.ParseMultipartForm(1024)
      fmt.Fprintln(writer,request.MultipartForm)
      log.Println(request.MultipartForm)
   })
}

FormValue&PostFormValue

FormValue方法会返回form字段指定key对应的第一个value,无需调用ParseForm和ParseMultipartForm

PostFormValue方法只能读取PostForm

这两种该方法都会调用ParseMultipartForm

当你表单的enctype设置为multipart/form-data的时候,上面两种方法无法获取到表单的数据

上传文件

首先form里面的enctype类型要设置为multipart/form-data

在GO语言中处理上传文件的时候:

1.调用ParseMultiparForm方法

2.从file字段获得FileHeadler,调用Open方法来获得文件

3.可以使用ioutil.ReadAll函数将文件内容读取到byte切片里

func getFile(){
	http.HandleFunc("/process", func(writer http.ResponseWriter, request *http.Request) {
		request.ParseMultipartForm(1024)
		//因为file是一个map,也就是允许多个文件上传,这里可以指定获取那个文件,0代表第一个
		fileHeader := request.MultipartForm.File["uploaded"][0]
		file,err := fileHeader.Open()
		if err == nil {
			data,err := ioutil.ReadAll(file)
			if err != nil {
				fmt.Println(err)
			}
			fmt.Fprintln(writer,string(data))
		}
	})
}

方法2

func getFile2(){
   http.HandleFunc("/process", func(writer http.ResponseWriter, request *http.Request) {
       //返回第一个文件,当只上传一个文件的时候,这种方式更快
      file,_,err := request.FormFile("uploaded")
      if err == nil {
         data,err := ioutil.ReadAll(file)
         if err != nil {
            fmt.Println(err)
         }
         fmt.Fprintln(writer,string(data))
      }
   })
}

MultipartReader()

源码如下:

func (r *Request) MultipartReader() (*multipart.Reader, error) {
   if r.MultipartForm == multipartByReader {
      return nil, errors.New("http: MultipartReader called twice")
   }
   if r.MultipartForm != nil {
      return nil, errors.New("http: multipart handled by ParseMultipartForm")
   }
   r.MultipartForm = multipartByReader
   

如果是multipart/form-data或multipart混合的POST请求,MultipartReader会返回一个MIME multipart reader,否则这返回一个error和nil

在使用中可以使用MultipartReader代替ParseMultipartForm来把请求的body作为stream进行处理,它在处理的时候不是一次性处理整个表单数据,而是检查来自表单的值,然后每次处理一个

POST请求-JSON BODY

不是所有的post请求都来自form

在不同的客户端框架下会以不同的方式对post请求编码

例如jQuery通常使用application/x-www-form-urlencoded

Augular则是application/json,但是ParseForm方法无法处理application/json

ResponseWriter

从服务器向客户端返回相应需要使用ResponseWriter

ResponseWriter是一个接口,handler用它来返回相应,真正支撑ResponseWriter的幕后struct是一个非导出的http.response

写入到ResponseWriter

ResponseWriter在底层实现的时候其实也是实现了一个指针

在ResponseWriter中,write方法接收一个byte切片作为参数,然后把他写入到HTTP相应的Body里面。

如果Write方法被调用时,header里面没有设定content type,那么数据的前512字节就会被用来监测content type

func writeExample(w http.ResponseWriter,r *http.Request) {

   str := `<html><head><title>Go Web</title></head>
<body><h1>Hello World</h1></body>
   </html>`
   w.Write([]byte(str))
}

WriteHeader

WriteHeader方法接收一个整数类型(HTTP状态码)作为参数,并把它作为HTTP响应的状态码返回,如果这个方法没有被显示的调用,那么在第一次调用Write方法前会隐式的调用

当WriteHeader被调用完后,仍然可以写入到ResponseWriter,但是不能再修改header

func writeHeader(w http.ResponseWriter,r *http.Request) {
   w.WriteHeader(501)
   fmt.Fprintln(w,"66666666666666")
}

Header

Header方法返回Headers的map。可以进行修改,修改后的headers将会体现再返回给客户端的HTTP响应里

func headerExample(w http.ResponseWriter,r * http.Request) {
   //重定向访问的请求
   w.Header().Set("location","http://www.baidu.com")
   //访问请求的状态码
   w.WriteHeader(302)
}

传入json数据

type Post struct {
   User string
   Threads []string
}

func jsonExample(w http.ResponseWriter , r *http.Request) {
   w.Header().Set("Content-Type","application/json")
   post := &Post{
      User: "张三",
      Threads: []string{"666","777","888"},
   }
   json,_:= json2.Marshal(post)
   w.Write(json)
}
内置的Response

1.NotFound函数,包装一个404状态码和一个额外的信息

2.ServeFile函数,从文件系统提供文件,返回给请求者

3.ServeContent函数,它可以把实现了io.ReadSeeker接口的任何东西里面的内容返回给请求者,同时它还可以处理Range请求(范围请求),如果只请求了资源的一部分内容,那么ServeContent就可以如此响应。而ServeFile或io.Copy则不行

4.Redirect函数,告诉客户端重定向到另一个URL
tp://www.baidu.com")
//访问请求的状态码
w.WriteHeader(302)
}


传入json数据

```go
type Post struct {
   User string
   Threads []string
}

func jsonExample(w http.ResponseWriter , r *http.Request) {
   w.Header().Set("Content-Type","application/json")
   post := &Post{
      User: "张三",
      Threads: []string{"666","777","888"},
   }
   json,_:= json2.Marshal(post)
   w.Write(json)
}
内置的Response

1.NotFound函数,包装一个404状态码和一个额外的信息

2.ServeFile函数,从文件系统提供文件,返回给请求者

3.ServeContent函数,它可以把实现了io.ReadSeeker接口的任何东西里面的内容返回给请求者,同时它还可以处理Range请求(范围请求),如果只请求了资源的一部分内容,那么ServeContent就可以如此响应。而ServeFile或io.Copy则不行

4.Redirect函数,告诉客户端重定向到另一个URL

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值