GO - HTTP
写在前面
GO关于HTTP的用法
处理请求
http.DefaultServeMux
作为一个handler
(goroutine),处理http请求
http.ListenAndServer()
- 第一个参数是网络地址
- 第二个参数是
handler
。如果为nil
,那么就使用DefaultServeMux
。
http.Server
这是一个struct,第一个字段表示网络地址,第二个字段表示Handler,有个ListenAndServe方法。
创建http Server
// 更灵活的写法
server := http.Server{
Addr: "localhost:8080",
Handler: nil,
}
server.ListenAndServe()
// 和上面功能一样
http.ListenAndServe("localhost:8080", nil)
Handler
一个接口,上面定义了ServerHTTP方法,还有一个指向Request这个Struct的指针。 Handler的struct
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
DefaultServeMux
DefaultServeMux
是一个multiplexer,可以认为是路由器。其实际上也是一个Handler。
http.Handle(pattern string, handler Handler)
如果调用http.Handle()方法,实际上调用的是DefaultServeMux上的Handle方法。DefaultServeMux就是ServerMux的指针变量。 pattern
其实是入口地址,后面是处理的方法。
http.HandleFunc(pattern string, handler func(ResponseWriter, *Request))
Handler函数
是指那些行为与handler类似的函数。接受一个http.ResponseWriter和一个指向http.Request的指针。 总结一下,就是和Handle方法差不多,区别在于给了一个和ServeHTTP函数声明一样的函数,在调用HandleFunc后会将这个函数转换为带有这个方法的Handler,然后就和Handle没什么大区别了。
五大handler
- NotFoundHandler
- RedirectHandler(url string, code int)
- StripPrefix(prefix string, h handler):用来去除指定前缀
- TimeoutHandler(h Handler, dt time.Duration, msg string)
- FileServer():等于是在指定路径开了个假的文件系统,可以通过url路径访问指定文件
HTTP Request和HTTP Response
两者具有相同的结构:
- 请求(响应)行
- 0或多个Header
- 空行
- 可选的消息体(Body)
Request是个struct,代表客户端发送的HTTP请求消息。 重要的字段有:
- URL
- Header
- Body
- Form、PostForm、MultipartForm
可以通过Request的方法获取请求中的Cookie、URL、User Agent等等。
url.URL
也是一个struct。
URL Fragment
如果是从浏览器发出的请求,由于浏览器会把fragment去掉,我们无法提取出Fragment字段的值。
Request Header
是一个map,用来表述HTTP Header里的Key-Value对。Header map的key是string类型,value是[]string,设置Key的时候会创建一个空的[]string作为Value,vallue第一个元素就是新header的值。可以使用append对key添加一个新的header值。
- 使用r.Header返回一个map
- 使用r.Header["Acccept-Encoding"]返回
[]string
类型 - 使用r.Header.Get("Acccept-Encoding")返回
string
类型
Request.Body
- Body是一个io.ReadCloser接口,有一个Reader接口和一个Closer接口
- Reader接口定义了Open方法,参数为[]byte,返回byte数量和可选的错误
- Closer接口定义了Close方法,没有参数
func (m *myHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
length := r.ContentLength
body := make([]byte, length)
r.Body.Read(body)
fmt.Fprintln(w, string(body))
}
URL Query
使用r.URL.RawQuery可以获取实际查询的原始字符串,使用r.URL.Query()会提供查询字符串对应的map[string][]string
处理表单
POST
通过POST发的数据对格式可以通过表单的Content Type来指定,也就是enctype属性。 enctype:
- 默认是application/x-www-form-urlencoded
浏览器会将表单数据编码到查询字符串里面。简单文本
- multipart/form-data
每一个name-value对都会被转换为MIME消息部分。大量数据,比如上传文件
- text/plain(H5支持)
GET
没有请求Body,所有数据都需要通过URL的name-value对来发送。
FORM字段
- Form 包括URL和Body数据
- PostForm 不包括URL数据,只包括Body数据,仅支持application/x-www-form-urlencoded
- MultiForm 支持multipart/form-data,使用ParseMultipartForm返回一个struct而不是map
multipart/form-data用来上传文件,从File字段获得FileHeader,调用其Open方法来获得文件。可以使用ioutil.ReadAll函数把文件内容读取到byte切片里面。
ResponseWriter
- 从服务器向客户端返回响应需要使用ResponseWriter
- ResponseWriter是一个接口,handler用它来返回响应
- 真正支撑ResponseWriter的幕后struct是非导出的http.response
为什么ResponseWriter传参的时候不是指针?
ResponseWriter实际上是一个interface,它代表了一个指针。
// A response represents the server side of an HTTP response.
type response struct {
conn *conn
req *Request // request for this response
reqBody io.ReadCloser
cancelCtx context.CancelFunc // when ServeHTTP exits
wroteHeader bool // a non-1xx header has been (logically) written
wroteContinue bool // 100 Continue response was written
wants10KeepAlive bool // HTTP/1.0 w/ Connection "keep-alive"
wantsClose bool // HTTP request has Connection "close"
...
如上,response指针实现了ResponseWriter接口的所有方法,因此它可以被调用进函数里面。
Write
用来写入Http的body里面,如果header里面没有设定content type,则数据的前512字节会被用来检测。
WriteHeader
接受一个整数类型作为参数,作为HTTP响应的状态码。如果该方法没有显式调用,那么在第一次调用Write方法前,会隐式调用该方法,设定为200。调用该方法后就无法再修改header里面的东西了
Header
返回headers的map,可以进行修改,修改后的headers体现在返回客户端的HTTP响应里面。写在WriteHeader之前!!!
内置的Response
- NotFound函数,包括404和错误信息
- ServeFile函数,从文件系统提供文件
- ServeContent,可以把实现了io.ReadSeeker接口的任何东西里面的内容返回给请求者。
- Redirect函数
模版引擎
感觉像快速开发前端页面。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
{{ . }}
</body>
</html>
如上,在html文件中使用该占位符。
func process(w http.ResponseWriter, r *http.Request) {
t, _ := template.ParseFiles("tmpl.html")
t.Execute(w, "Hello World!")
}
实际上,ParseFiles既是函数也是方法,除了上述写法,还可以使用template.new("tmpl.html")
先创建模版,再调用模版上的ParseFiles("tmpl.html")
方法即可。
Controller层
把不同的请求送到不同的controller中进行处理。
JSON(TAGS)
可以定义go的struct转换成json后的格式。
类型映射:
go | json |
---|---|
bool | boolean |
float64 | 数值 |
string | strings |
nil | null |
type Company struct {
ID int `json:"id"`
Name string `json:"name"`
Country string `json:"country"`
}
如果不知道具体的结构,可以使用map[string]interface{}存储任意json对象,可以用[]interface{}存储任意json数组。
中间件
请求和响应时都会经过中间键进行处理。功能:
- 记录日志
- 保证安全
- 处理请求超时问题
- 压缩响应
Request Context
func(*Request) Context() context.Context
会返回当前请求的上下文 func(*Request) WithContext(ctx context.Context) context.Context
会基于Context进行修改,实际上创建一个新的Context。
type Context interface {
Deadline() (deadline time.Time, ok bool)
Done() <-chan struct{}
Err() error //平常为nil,如果Done了则记录原因
Value(key interface{}) interface{}
}
上面的值都是用于读取,而不能进行设置,想进行更改只能创建新的context
- WithCancel(),它有一个 CancelFunc
- WithDeadline(),带有一个时间戳(time.Time)
- WithTimeout(),带有一个具体的时间段(time.Duration)
- WithValue(),在里面可以添加一些值