有请求就要有响应,一来一回。这篇文章来看看response的细节。
1. 从处理器Handler开始
对于Request,在前一篇文章中已经知道了整个的流程。Go会构造一个Request
结构体,将接收到的请求中的信息解析到这个结构体中。对于一些信息(比如三个Form)需要手动调用一些函数来填充。这样在之后的处理过程中就可以通过这个Request
结构来获取具体的请求信息了。
那么对于Response,是不是也是这个过程呢?创建一个Response结构,将数据保存在这个结构中,然后返回给客户端?
不是的,服务端返回响应的时候,使用的是ResponseWriter
接口。这一点可以从处理器Handler的定义来看:
ServeHTTP(ResponseWriter, *Request)
两个参数分别对应响应和请求。
net/http
包内部使用response
结构来处理响应,由于是非导出类型,不能直接操作,所以通过这个接口,来操作具体的响应。
而在通过ResponseWriter
接口操作response
时,传递的也是指向response
结构的指针,这也解释了为什么处理器Handler的两个参数一个是值一个是引用,因为底层实际上都是引用。
2. 使用ResponseWriter进行写入
在具体看看Go是怎么处理response的之前,先看看怎么使用ResponseWriter
进行写入。
ResponseWriter
接口有三个方法:
Write
:将指定的数据写入对应的链接作为响应结果;WriteHeader
:用来写入一个HTTP响应码(比如200 OK);Header
:返回Header,可以用来设置值。
而response
结构实现了这几个方法。
2.1 Sending Header
可以直接通过WriteHeader()
方法来写入HTTP响应码:
w.Header().Set("Server", "A Test Server")
w.WriteHeader(200)
这里首先通过Header()
方法返回Header然后设置一个值,接下来通过WriteHeader()
方法设置HTTP响应码为200。
通过curl可以看到下面的结果:
HTTP/1.1 200 OK
Server: A Test Server
Date: Thu, 02 Jan 2020 08:18:24 GMT
Content-Length: 0
其实,对于HTTP响应码200来说,不用显示写入,默认就会写入这个响应码。对于WriteHeader
方法来说,最常见的应用是写入HTTP的错误码。
比如,下面的代码实现了一次HTTP重定向:
w.Header().Set("Location", "http://google.com")
w.WriteHeader(302)
这时启动浏览器之后就会自动跳到Google的页面了。
2.2 Rendering Plain Text
在之前的文章中,都是通过fmt.Fprintf(w, "hello")
这种方式写入响应的。原理都是一样,也可以使用Write()
方法来写入结果:
w.Write([]byte("I'm a Test Server"))
不同在于,这里的参数是一个[]byte
。结果:
HTTP/1.1 200 OK
Date: Thu, 02 Jan 2020 08:27:49 GMT
Content-Length: 17
Content-Type: text/plain; charset=utf-8
I'm a Test Server
可以看出:
- 对于200响应码不用手动写入;
Content-Length
字段标识了响应的长度。
2.3 Rendering JSON
上面写入的响应是一个纯文本。同样可以对响应写入JSON格式的数据:
type User struct {
Name string `json:"name"`
Age int `json:"age"`
}
data, _ := json.Marshal(User{"Valineliu", 25})
w.Header().Set("Content-Type", "application/json")
w.Write(data)
注意需要通过Header()
方法将内容类型设置成application/json
,不然就是纯文本了。结果:
HTTP/1.1 200 OK
Content-Type: application/json
Date: Thu, 02 Jan 2020 08:32:49 GMT
Content-Length: 29
{"name":"Valineliu","age":25}
当然除了JSON格式外,还可以写入XML等格式。
2.4 Serving a File
还可以发送一个文件:
http.ServeFile(w, r, "luffy.jpg")
通过这个函数就可以了。打开浏览器就可以看到结果。使用curl结果:
HTTP/1.1 200 OK
Accept-Ranges: bytes
Content-Length: 54146
Content-Type: image/jpeg
Last-Modified: Wed, 06 Nov 2019 01:50:35 GMT
Date: Thu, 02 Jan 2020 08:39:48 GMT
3. 看看具体发生了什么
除了上面的那些类型的内容外,还可以返回一个HTML模板,这里就不详细说了。
接下来看看net/http
包是怎么通过ResponseHeader
操作response
的。
response
定义如下:
type response struct {
conn *conn
req *Request // request for this response
reqBody io.ReadCloser
w *bufio.Writer
...
}
这里只是一部分的字段,原来这个response
结构竟然还有请求的字段。
当链接收到一个请求后,会调用server()
方法进行处理,简略的过程如下:
// Serve a new connection.
func (c *conn) serve(ctx context.Context) {
...
w, err := c.readRequest(ctx)
...
req := w.req
...
serverHandler{c.server}.ServeHTTP(w, w.req)
...
}
先通过readRequest()
方法获得response
,这个时候里面的Resquest
已经构造好了。之后就可以使用w
和w.req
作为参数调用ServeHTTP
了。
而在readRequest()
方法中:
// Read next request from connection.
func (c *conn) readRequest(ctx context.Context) (w *response, err error) {
...
req, err := readRequest(c.bufr, keepHostHeader)
...
w = &response{
conn: c,
cancelCtx: cancelCtx,
req: req,
reqBody: req.Body,
handlerHeader: make(Header),
contentLength: -1,
closeNotifyCh: make(chan bool, 1),
// We populate these ahead of time so we're not
// reading from req.Header after their Handler starts
// and maybe mutates it (Issue 14940)
wants10KeepAlive: req.wantsHttp10KeepAlive(),
wantsClose: req.wantsClose(),
}
if isH2Upgrade {
w.closeAfterReply = true
}
w.cw.res = w
w.w = newBufioWriterSize(&w.cw, bufferBeforeChunkingSize)
return w, nil
}
可以看到这个response
的整个构造过程。这样,后序的处理就可以通过ResponseWriter
接口来处理了。
To Be Continued…