首先创建一个Response
结构,接着将数据存储到这个结构里面,最后将这个结构返回给客户端——如果你认为服务器是通过这种方式向客户端返回响应的,那么你就想多了:服务器在向客户端返回响应的时候,真正需要用到的是ResponseWriter
接口。
ResponseWriter
是一个接口,处理器可以通过这个接口创建HTTP响应。ResponseWriter
在创建响应时会用到http.response
结构,因为该结构是一个非导出(nonexported)的结构,所以用户只能通过ResponseWriter
来使用这个结构,而不能直接使用它。
为什么要以传值的方式将
ResponseWriter
传递给ServeHTTP
在阅读了本章前面的内容之后,有的读者可能会感到疑惑——
ServeHTTP
为什么要接受ResponseWriter
接口和一个指向Request
结构的指针作为参数呢?接受
Request
结构指针的原因很简单:为了让服务器能够察觉到处理器对Request
结构的修改,我们必须以传引用(pass by reference)而不是传值(pass by value)的方式传递Request
结构。但是另一方面,为什么
ServeHTTP
却是以传值的方式接受ResponseWriter
呢?难道服务器不需要知道处理器对ResponseWriter
所做的修改吗?对于这个问题,如果我们深入探究
net/http
库的源码,就会发现ResponseWriter
实际上就是response
这个非导出结构的接口,而ResponseWriter
在使用response
结构时,传递的也是指向response
结构的指针,这也就是说,ResponseWriter
是以传引用而不是传值的方式在使用response
结构。换句话说,实际上
ServeHTTP
函数的两个参数传递的都是引用而不是值——虽然ResponseWriter
看上去像是一个值,但它实际上却是一个带有结构指针的接口。
ResponseWriter
接口拥有以下3个方法:
Write
WriteHeader
Header
对ResponseWriter
进行写入
Write
方法接受一个字节数组作为参数,并将数组中的字节写入HTTP响应的主体中。如果用户在使用Write
方法执行写入操作的时候,没有为首部设置相应的内容类型,那么响应的内容类型将通过检测被写入的前512字节决定。代码清单4-8展示了Write
方法的用法。
代码清单4-8 使用Write
方法向客户端发送响应
package main
import (
"net/http"
)
func writeExample(w http.ResponseWriter, r *http.Request) {
str := `<html><head><title>Go Web Programming</title></head>
<body><h1>Hello World</h1></body></html>`
w.Write([]byte(str))
}
func main() {
server := http.Server{
Addr: "127.0.0.1:8080",
}
http.HandleFunc("/write", writeExample)
server.ListenAndServe()
}
这段代码通过调用Write
方法将一段HTML字符串写入了HTTP响应的主体中。通过向服务器发送以下命令:
curl -i 127.0.0.1:8080/write
我们可以得到以下响应:
HTTP/1.1 200 OK
Date: Tue, 13 Jan 2015 16:16:13 GMT
Content-Length: 95
Content-Type: text/html; charset=utf-8
<html>
<head><title>GoWebProgramming</title></head>
<body><h1>Hello World</h1></body>
</html>
注意,尽管我们没有亲自为响应设置内容类型,但程序还是通过检测自动设置了正确的内容类型。
WriteHeader
方法的名字带有一点儿误导性质,它并不能用于设置响应的首部(Header
方法才是做这件事的):WriteHeader
方法接受一个代表HTTP响应状态码的整数作为参数,并将这个整数用作HTTP响应的返回状态码;在调用这个方法之后,用户可以继续对ResponseWriter
进行写入,但是不能对响应的首部做任何写入操作。如果用户在调用Write
方法之前没有执行过WriteHeader
方法,那么程序默认会使用200 OK
作为响应的状态码。
WriteHeader
方法在返回错误状态码时特别有用:如果你定义了一个API,但是尚未为其编写具体的实现,那么当客户端访问这个API的时候,你可能会希望这个API返回一个501NotImplemented
,状态码,代码清单4-9通过添加新的处理器实现了这一需求。顺带一提,千万别忘了使用HandleFunc
方法将新处理器绑定到DefaultServeMux
多路复用器里面!
代码清单4-9 通过WriteHeader
方法将状态码写入到响应当中
package main
import (
"fmt"
"net/http"
)
func writeExample(w http.ResponseWriter, r *http.Request) {
str := `<html><head><title>Go Web Programming</title></head><body><h1>Hello World</h1></body></html>`
w.Write([]byte(str))
}
func writeHeaderExample(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(501)
fmt.Fprintln(w, "No such service, try next door")
}
func main() {
server := http.Server{
Addr: "127.0.0.1:8080",
}
http.HandleFunc("/write", writeExample)
http.HandleFunc("/writeheader", writeHeaderExample)
server.ListenAndServe()
}
通过curl
访问刚刚添加的新处理器:
curl -i 127.0.0.1:8080/writeheader
我们将得到以下响应:
HTTP/1.1 501 Not Implemented
Date: Tue, 13 Jan 2015 16:20:29 GMT
Content-Length: 31
Content-Type: text/plain; charset=utf-8
No such service, try next door
最后,通过调用Header
方法可以取得一个由首部组成的映射(关于首部的具体细节在4.1.3节曾经讲过),修改这个映射就可以俢改首部,修改后的首部将被包含在HTTP响应里面,并随着响应一同发送至客户端。
代码清单4-10 通过编写首部实现客户端重定向
package main
import (
"fmt"
"net/http"
)
func writeExample(w http.ResponseWriter, r *http.Request) {
str := `<html><head><title>Go Web Programming</title></head><body><h1>Hello World</h1></body></html>`
w.Write([]byte(str))
}
func writeHeaderExample(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(501)
fmt.Fprintln(w, "No such service, try next door")
}
func headerExample(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Location", "http://google.com")
w.WriteHeader(302)
}
func main() {
server := http.Server{
Addr: "127.0.0.1:8080",
}
http.HandleFunc("/write", writeExample)
http.HandleFunc("/writeheader", writeHeaderExample)
http.HandleFunc("/redirect", headerExample)
server.ListenAndServe()
}
代码清单4-10向我们展示了如何实现一次HTTP重定向:除了将状态码设置成了302
之外,它还给响应添加了一个名为Location
的首部,并将这个首部的值设置成了重定向的目的地。需要注意的是,因为WriteHeader
方法在执行完毕之后就不允许再对首部进行写入了,所以用户必须先写入Location
首部,然后再写入状态码。现在,如果我们在浏览器里面访问这个处理器,那么浏览器将被重定向到Google。
另一方面,如果我们使用curl访问这个处理器:
curl -i 127.0.0.1:8080/redirect
那么curl将获得以下响应:
HTTP/1.1 302 Found
Location: http://google.com
Date: Tue, 13 Jan 2015 16:22:16 GMT
Content-Length: 0
Content-Type: text/plain; charset=utf-8
最后,让我们来学习一下通过ResponseWriter
直接向客户端返回JSON数据的方法。代码清单4-11展示了如何以JSON格式将一个名为Post的结构返回给客户端。
代码清单4-11 编写JSON输出
package main
import (
"fmt"
"encoding/json"
"net/http"
)
type Post struct {
User string
Threads []string
}
func writeExample(w http.ResponseWriter, r *http.Request) {
str := `<html><head><title>Go Web Programming</title></head><body><h1>Hello World</h1></body></html>`
w.Write([]byte(str))
}
func writeHeaderExample(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(501)
fmt.Fprintln(w, "No such service, try next door")
}
func headerExample(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Location", "http://google.com")
w.WriteHeader(302)
}
func jsonExample(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
post := &Post{
User: "Sau Sheong",
Threads: []string{"first", "second", "third"},
}
json, _ := json.Marshal(post)
w.Write(json)
}
func main() {
server := http.Server{
Addr: "127.0.0.1:8080",
}
http.HandleFunc("/write", writeExample)
http.HandleFunc("/writeheader", writeHeaderExample)
http.HandleFunc("/redirect", headerExample)
http.HandleFunc("/json", jsonExample)
server.ListenAndServe()
}
这段代码中的jsonExample
处理器就是这次的主角。因为本书将在第7章进一步介绍JSON格式,所以不了解JSON格式的读者也不必过于担心,目前来说,你只需要知道变量json是一个由Post结构序列化而成的JSON字符串就可以了。
这段程序首先使用Header
方法将内容类型设置成application/json
,然后调用Write
方法将JSON字符串写入ResponseWriter
中。现在,如果我们执行curl
命令:
curl -i 127.0.0.1:8080/json
那么它将返回以下响应:
HTTP/1.1 200 OK
Content-Type: application/json
Date: Tue, 13 Jan 2015 16:27:01 GMT
Content-Length: 58
{"User":"Sau Sheong","Threads":["first","second","third"]}