GO web学习(一)

跟着b站https://space.bilibili.com/361469957 杨旭老师学习做的笔记

开启web服务

•http.ListenAndServer()

•第一个参数是网络地址 如果为“”,那么就是所有网络接口的 80 端口

•第二个参数是 handler 如果为 nil,那么就是 DefaultServeMux

•DefaultServeMux 是一个 multiplexer(可以看作是路由器)

•http.Server 这是一个 struct

Addr 字段表示网络地址 如果为“”,那么就是所有网络接口的 80 端口

•Handler 字段 如果为 nil,那么就是 DefaultServeMux

•ListenAndServe() 函数

因此开启服务器有两种方法

1.    •http.ListenAndServer() //http

•http.ListenAndServeTLS() //https

2.    •http.Server 可配置

•server.ListenAndServe() //http

•server.ListenAndServeTLS()    //https

//s1和s2函数等效
func s1(){
    server:= http.Server{
        Addr: "localhost:8080",
        Handler: nil,
    }
    server.ListenAndServe()
}
func s2(){
    http.ListenAndServe("localhost:8080",nil)
} 

Handle 请求

handler

•handler 是一个接口(interface)

•handler 定义了一个方法 ServeHTTP()

•http.ResponseWriter

•*http.Request 指向 Request 这个 struct 的指针

type Handler interface {

ServeHTTP(ResponseWriter,*Request)

}

DefaultServeMux

DefaultServeMux 是一个 multiplexer(可以看作是路由器)

•它也是一个 Handler

多个handler

•不指定 Server struct 里面的 Handler 字段值

•可以使用 http.Handle 将某个 Handler 附加到 DefaultServeMux

•http 包有一个 Handle 函数

•ServerMux struct 也有一个 Handle 方法

•如果你调用 http.Handle,实际上调用的是 DefaultServeMux 上的 Handle 方法

•DefaultServeMux 就是 ServerMux 的指针变量

type myHandler struct{}
type aboutHandler struct{}
func(m *myHandler) ServeHTTP(w http.ResponseWriter, r *http.Request){
    w.Write([]byte("一个美丽的日子"))
}
func (a *aboutHandler) ServeHTTP(w http.ResponseWriter,r *http.Request){
    w.Write([]byte("about!"))
}
func myselfHandle(){
    mh := myHandler{}
    server:= http.Server{
        Addr: "localhost:8080",
        Handler: &mh,
    }
    server.ListenAndServe()  
// 现在它就可以作为Handler用了
// 我们打开网址发现不管是localhost:8080/ 还是 localhost:8080/hello 等等
// 都会出现  一个美丽的日子
// 因为它直接走的是myHandler,而不是那个DefaultServeMux(下分handler,分地址注册)
// 所以多地址都对应myHandler,而DefaultServeMux,地址是分开对应的
}
func welcome(w http.ResponseWriter, r *http.Request){
    w.Write([]byte("welcome"))
}
func index(w http.ResponseWriter, r *http.Request){
    w.Write([]byte("index"))
}
func myselfHandlePro(){
    mh := myHandler{}
    ah := aboutHandler{}
    server:= http.Server{
        Addr: "localhost:8080",
        Handler: nil, //DefaultServeMux
    }
    http.Handle("/hello",&mh)
    http.Handle("/about",&ah)

    http.HandleFunc("/home",func(w http.ResponseWriter, r *http.Request) {
        w.Write([]byte("Home"))
    })
    http.Handle("/welcome",http.HandlerFunc(welcome))
    http.HandleFunc("/index",index)
    server.ListenAndServe()
}

handle请求的两种形式

1.http.Handle

•第二个参数是 Handler

2.http.HandleFunc

•第二个参数是一个 Handler 函数

•http.HandlerFunc 可以把 Handler 函数转化为 Handler

•内部调用的还是 http.Handle 函数

内置的handlers

404 not found

1.http.NotFoundHandler
func NotFoundHandler() Handler
返回一个 handler,它给每个请求的响应都是“404 page not found”

重定向

2.http.RedirectHandler
func RedirectHandler(url string, code int) Handler
返回一个 handler,它把每个请求使用给定的状态码跳转到指定的 URL。
url,要跳转到的 URL
code,跳转的状态码(3xx),常见的:StatusMovedPermanently、StatusFound 或 StatusSeeOther 等

路径处理

3.http.StripPrefix

func StripPrefix(prefix string, h handler) Handler
返回一个 handler,它从请求 URL 中去掉指定的前缀,然后再调用另一个 handler。
如果请求的 URL 与提供的前缀不符,那么 404
略像中间件
prefix,URL 将要被移除的字符串前缀
h,是一个 handler,在移除字符串前缀之后,这个 handler 将会接收到请求
修饰了另一个 Handler

超时处理

4.http.TimeoutHandler
func TimeoutHandler(h Handler, dt time.Duration, msg string) Handler
返回一个 handler,它用来在指定时间内运行传入的 h。
也相当于是一个修饰器
h,将要被修饰的 handler
dt,第一个 handler 允许的处理时间
msg,如果超时,那么就把 msg 返回给请求,表示响应时间过长

文件服务

5.http.FileServer
func FileServer(root FileSystem) Handler
返回一个 handler,使用基于 root 的文件系统来响应请求
type FileSystem interface {
Open(name string) (File, error)
}
使用时需要用到操作系统的文件系统,所以还需要委托给:
type Dir string
func (d Dir) Open(name string) (File, error)

http.ListenAndServe(":8080",http.FileServer(http.Dir("wwwroot")))
http.HandleFunc("/",func(w http.ResponseWriter, r *http.Request) {
    http.ServeFile(w,r,"wwwroot"+r.URL.Path)
    })
http.ListenAndServe(":8080",nil)

这两个都是打开wwwroot目录下的文件,而且路径不需要加wwwroot/

Request 请求

http消息

•HTTP Request 和 HTTPResponse(请求和响应)

•它们具有相同的结构:

•请求(响应)行

•0 个或多个 Header

•空行

•可选的消息体(Body)

•例子:

GET
/Protocols/rfc2616/rfc2616.html HTTP/1.1
Host:www.w3.org
User-Agent:Mozilla/5.0
(空行)

•net/http 包提供了用于表示 HTTP 消息的结构

请求(Request)

Request

•Reqeust(是个 struct),代表了客户端发送的 HTTP 请求消息

•重要的字段:

•URL

•Header

•Body

•Form、PostForm、MultipartForm

•也可以通过 Request 的方法访问请求中的 Cookie、URL、UserAgent 等信息

•Request 即可代表发送到服务器的请求,又可代表客户端发出的请求

请求的 URL

•Request 的 URL 字段就代表了请求行(请求信息第一行)里面的部分内容

•URL 字段是指向 url.URL 类型的一个指针,url.URL 是一个 struct:

type URL struct {
    Scheme   string
    Opaque   string
    User     *Userinfo
    Host     string
    Path     string
    RawQuery string
    Fragment string
}
url的通用形式

•通用格式是:scheme://[userinfo@]host/path[?query][#fragment]

•不可以斜杠开头的 URL 被解释为:scheme:opaque[?query][#fragment]

URL Query

•RawQuery 会提供实际查询的字符串。

•例如: http://www.example.com/post?id=123&thread_id=456

•它的 RawQuery 的值就是 id=123&thread_id=456

•r.URL.Query(),会提供查询字符串对应的 map[string][]string

•还有一个简便方法可以得到 Key-Value 对:通过 Request 的 Form 字段(以后再说)

// r.URL.RawQuery 会提供实际查询的原始字符串
// r.URL.Query() 会提供查询字符串对应的map[string][]string
// query = r.URL.Query()
// query[""]  返回[]string
// query.Get("")  返回 string 且只能获得第一个

http.HandleFunc("/query",func(w http.ResponseWriter, r *http.Request) {
		query := r.URL.Query()
		id := query["id"]
		log.Println(id)
		name:= query.Get("name")
		log.Println(name)
	})
	http.ListenAndServe("localhost:8080",nil)
	// http://localhost:8080/query?id=123&name=456
	// 2023/04/18 20:57:01 [123]
     // 2023/04/18 20:57:01 456

	//  http://localhost:8080/query?id=123&name=hello&id=456&name=zcy
	// 2023/04/18 20:58:25 [123 456]
    // 2023/04/18 20:58:25 hello   看这里,只返回了第一个值 hello
url fragment

•如果从浏览器发出的请求,那么你无法提取出 Fragment 字段的值

•浏览器在发送请求时会把 fragment 部分去掉

// http://localhost:8080/url#hello  什么也没有输出
// 浏览器自动过滤了Fragment的部分

server:= http.Server{
		Addr: "localhost:8080",
	}
	http.HandleFunc("/url",func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintf(w,r.URL.Fragment)
	})
	server.ListenAndServe()

但不是所有的请求都是从浏览器发出的(例如从 HTTP 客户端包)

request header

•请求和响应(Request、Response)的 headers 是通过 Header 类型来描述的,它是一个 map,用来表述 HTTP Header 里的 Key-Value 对。

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

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

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

// Request Header例子
// Header 是个map[string][]string
// r.Header 返回map
// r.Header[""]   返回[]string 类型
// r.Header.Get("") 返回string类型,把东西拼一起了。
http.HandleFunc("/header",func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintln(w,r.Header)
		fmt.Fprintln(w,r.Header["Accept-Encoding"])
		fmt.Fprintln(w,r.Header.Get("Accept-Encoding"))
	}) 

request body

请求和响应的 bodies 都是使用 Body 字段来表示的
Body 是一个 io.ReadCloser 接口
一个 Reader 接口
一个 Closer 接口
Reader 接口定义了一个 Open 方法:
参数:[]byte
返回:byte 的数量、可选的错误
Closer 接口定义了一个 Close 方法:没有参数,返回可选的错误

想要读取请求 body 的内容,可以调用 Body 的 Read 方法

http.HandleFunc("/post",func(w http.ResponseWriter, r *http.Request) {
	length:= r.ContentLength
	body := make([]byte,length)
	r.Body.Read(body)
	fmt.Fprintln(w,string(body))
})

表单form

通过表单发送请求

HTML 表单里面的数据会以 name-value 对的形式,通过 POST/GET请求发送出去。
它的数据内容会放在 POST 请求的 Body 里面
但 name-value 对在 Body 里面的格式是什么样的?
通过 POST 发送的 name-value 数据对的格式可以通过表单的 Content Type 来指定,也就是 enctype 属性

表单的enctype 属性

默认值是:application/x-www-form-urlencoded
浏览器被要求至少要支持: application/x-www-form-urlencoded 、multipart/form-data
HTML 5 的话,还需要支持 text/plain

  • 如果 enctype 是 application/x-www-form-urlencoded,那么浏览器会将表单数据编码到查询字符串里面。

    • 例如:
      first_name=sau%20sheong&last_name=chang
  • 如果 enctype 是 multipart/form-data,那么每一个 name-value 对都会被转换为一个MIME消息部分。每一个部分都有自己的 Content Type 和 Content Disposition

------WebKitFormBoundaryMPNjkpe09LiocMw
Content-Disposition:form-data;name="first_name"

sau sheong
------WebKitFormBoundaryMPNjkpe09LiocMw
Content-Disposition:form-data;name="last_name"

chang
------WebKitFormBoundaryMPNjkpe09LiocMw --
  • 如何选择
  • 简单文本:表单 URL 编码
    大量数据,例如上传文件:multipart-MIME
    甚至可以把二进制数据通过选择 Base64 编码,来当作文本进行发送
表单的GET

通过表单的 method 属性,可以设置 POST 还是 GET。GET 请求没有 Body,所有的数据都通过 URL 的 name-value 对来发送

Form 字段
  • Request 上的函数允许我们从 URL 或/和 Body 中提取数据,通过这些字段:
    • Form
    • PostForm
    • MultipartForm
  • Form 里面的数据是 key-value 对。
    通常的做法是:
    • 先调用 ParseForm 或ParseMultipartForm 来解析 Request
    • 然后相应的访问 Form、PostForm 或 MultipartForm 字段
r.ParseForm()
// Form 取表单和url里面的
// 只支持 application/x-www-form-urlencoded
fmt.Fprintln(w,r.Form)
PostForm 字段
  • 如果表单和 URL 里有同样的 Key,那么它们都会放在一个 slice 里:表单里的值靠前,URL 的值靠后
  • 如果只想要表单的 key-value 对,不要 URL 的,可以使用PostForm 字段。
  • PostForm 只支持 application/x-www-form-urlencoded
  • 想要得到 multipart key-value 对,必须使用 MultipartForm 字段
r.ParseForm()
// PostForm 只取表单里面的
// 只支持 application/x-www-form-urlencoded
fmt.Fprintln(w,r.PostForm)
MultipartForm 字段
  • 想要使用 MultipartForm 这个字段的话,首先需要调用 ParseMultipartForm 这个方法
    • 该方法会在必要时调用 ParseForm 方法
    • 参数是需要读取数据的长度
  • MultipartForm 只包含表单的 key-value 对。返回类型是一个 struct 而不是 map。这个 struct 里有两个 map:
    • key 是 string,value 是 []string
    • key 是 string,value 是文件
r.ParseMultipartForm(1024)
//需要传入数据长度,字节数
fmt.Fprintln(w,r.MultipartForm)
// 返回的是个结构体,里面有两个map
// 第一个map[string][]string
// 第二个map[string]文件
FormValue & PostFormValue 方法
  • FormValue 方法会返回 Form 字段中指定 key 对应的第一个 value
    • 无需调用 ParseForm 或 ParseMultipartForm
  • PostFormValue 方法也一样,但只能读取 PostForm
  • FormValue 和 PostFormValue 在需要时都会调用ParseMultipartForm 方法
  • 但如果表单的 enctype 设为 multipart/form-data,那么即使你调用ParseMultipartForm 方法,也无法通过 FormValue 获得想要的值。
  • 只支持 application/x-www-form-urlencoded
// FormValue PostFormValue可以直接使用,无需解析,
// 且只返回一个值
// 注意!如果是 application/x-www-form-urlencoded
// FormValue可以带上url里的,但如果form和url的重名
// 会显示form的,因为它靠前
// PostFormValue 不能带上url里的
// 如果不小心用了multipart/form-data 应该是解析不了form 只能显示url里的。
multipart/form-data
  • multipart/form-data 最常见的应用场景就是上传文件
    • 首先调用 ParseMultipartForm 方法
    • 从 File 字段获得 FileHeader,调用其 Open 方法来获得文件
    • 可以使用 ioutil.ReadAll 函数把文件内容读取到 byte 切片里
r.ParseMultipartForm(1024)
fileHeader  := r.MultipartForm.File["uploaded"][0]
//"uploaded" 是 file 的input标签的名字
file,err := fileHeader.Open()
if err == nil{
	data,err := ioutil.ReadAll(file)
	if err ==nil{
		fmt.Fprintln(w,string(data))
	}
}。
FormFile 方法

-- 上传文件还有一个简便方法:FormFile(例子)
-- 无需调用 ParseMultipartForm 方法
-- 返回指定 key 对应的第一个 value
-- 同时返回 File 和 FileHeader,以及错误信息
-- 如果只上传一个文件,那么这种方式会快一些


file,_,err := r.FormFile("uploaded")
if err == nil{
	data,err := ioutil.ReadAll(file)
	if err ==nil{
		fmt.Fprintln(w,string(data))
	}
}
POST JSON

-- 不是所有的 POST 请求都来自 Form
-- 客户端框架(例如 Angular 等)会以不同的方式对 POST 请求编码:
-- jQuery 通常使用 application/x-www-form-urlencoded
-- Angular 是 application/json
-- ParseForm 方法无法处理 application/json

MultipartReader()

func (r Request) MultipartReader() (multipart.Reader, error)

  • 如果是 multipart/form-data 或 multipart 混合的 POST 请求:
    • MultipartReader 返回一个 MIME multipart reader
    • 否则返回 nil 和一个错误
  • 可以使用该函数代替 ParseMultipartForm 来把请求的 body 作为 stream 进行处理
    • 不是把表单作为一个对象来处理的,不是一次性获得整个 map
    • 逐个检查来自表单的值,然后每次处理一个
小结

支持url编码的:
Form PostForm FormValue PostFormValue
支持 Multipart编码的:
MutipartForm

他们都支持表单键值对,但是只有Form 和FormValue 可以显示URL键值对

posted @ 2023-05-18 21:00  id_shiguang  阅读(47)  评论(0编辑  收藏  举报