net/http
每种编程语言都自带标准库,但随着时间的推移对标准库包含内容的预期在发生变化。作为一个21世纪10年代发布的语言,Go标准库中包含了一些其它语言认为应由第三方库负责的部分:生产级的HTTP/2客户端和服务端。
客户端
net/http
包定义了一个Client
类型,发送HTTP请求及接收HTTP响应。net/http
包中有一个默认客户端实例(恰到好处地命名为DefaultClient
),但应当避免在生产应用中使用它,因为它默认不带超时。请实例化自己的客户端。在整个应用中只需要创建一个http.Client
,因为它处理好了跨协程的多并发请求:
client := &http.Client{
Timeout: 30 * time.Second,
}
在希望发送请求时,通过http.NewRequestWithContext
函数实例化一个新的*http.Request
实例,将上下文、方法和希望连接的URL发送给它。如果为PUT
、POST
或PATCH
请求,最后一个参数使用io.Reader
类型指定请求体。如果没有请求体,使用nil
:
req, err := http.NewRequestWithContext(context.Background(),
http.MethodGet, "https://jsonplaceholder.typicode.com/todos/1", nil)
if err != nil {
panic(err)
}
注:我们会在上下文一章中讨论上下文。
有了*http.Request
实例,就可通过实例的Headers
字段设置请求头。用http.Request
对http.Client
调用Do
方法,结果在http.Response
中返回。
req.Header.Add("X-My-Client", "Learning Go")
res, err := client.Do(req)
if err != nil {
panic(err)
}
响应中有多个包含请求相应信息的字段。响应状态的数字码位于StatusCode
字段,响应码的文本位于Status
字段,响应头位于Header
字段,返回的内容都位于io.ReadCloser
类型的Body
字段中。这样我们就可以使用json.Decoder
来处理REST API响应了:
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
panic(fmt.Sprintf("unexpected status: got %v", res.Status))
}
fmt.Println(res.Header.Get("Content-Type"))
var data struct {
UserID int `json:"userId"`
ID int `json:"id"`
Title string `json:"title"`
Completed bool `json:"completed"`
}
err = json.NewDecoder(res.Body).Decode(&data)
if err != nil {
panic(err)
}
fmt.Printf("%+v\n", data)
可在GitHub仓库的sample_code/client目录中查看代码。
警告:
net/http
包中有方法处理GET
、HEAD
和POST
调用。避免使用这些函数,因为它们使用默认客户端,因此没有设置请求超时。
服务端
HTTP服务端是以http.Server
的概念和http.Handler
接口进行构建的。就像http.Client
是发送HTTP请求的,http.Server
负责监听HTTP的请求。它是一个支持TLS的高性能HTTP/2服务端。
对服务端的请求由赋值给Handler
字段的http.Handler
接口实现来处理。接口中定义了一个方法:
type Handler interface {
ServeHTTP(http.ResponseWriter, *http.Request)
}
*http.Request
很眼熟,它和向HTTP服务端发送请求使用的同一种类型。http.ResponseWriter
是一个带有三个方法的接口:
type ResponseWriter interface {
Header() http.Header
Write([]byte) (int, error)
WriteHeader(statusCode int)
}
这些方法必须按指定顺序调用。首先,调用Header
来获取一个http.Header
实例并设置所需响应头。如果无需设置头部,就不用调用它。接着, 使用响应的HTTP状态码调用WriteHeader
。(所有的状态码在net/http
包中以常量进行定义。这会是定义自定义类型的好地方,但并不完全,所有的状态码常量都是无类型整数。)如果想要发送状态码为200的响应,可以跳过WriteHeader
。最后,调用Write
方法来设置响应体。以下是小型handler的示例:
type HelloHandler struct{}
func (hh HelloHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello!