Go http 包详解

Go 语言中的 http 包提供了创建 http 服务或者访问 http 服务所需要的能力,不需要额外的依赖。在这篇文章中,我们会介绍这些功能的使用,以及看一下 http 包的设计思路。

1. http 的客户端

1.1 发送普通请求

在 Go 语言中发送请求很简单,如果不需要额外的配置,可以直接使用 http 包封装的 http Client 发送请求,比如发送 GET 请求:

1
2
resp, _ := http.Get("https://golang.org")
defer resp.Body.Close()

发送 POST ,并携带 JSON 数据的请求:

1
2
3
4
5
6
data := make(map[string]string)
dataJson, _ := json.Marshal(data)
reader := bytes.NewBuffer(dataJson)

resp, _ := http.Post("https://golang.org", "application/json;charset=utf-8", reader)
defer resp.Body.Close()

发送 POST 表单请求:

1
2
resp, _ := http.PostForm("https://golang.org", url.Values{"username":{"rayjun"}, "password":{"password"}})
defer resp.Body.Close()

在每个请求发完之后,需要手动关闭响应。

1.2 客户端配置

在实际使用的过程中,我们通常不会直接上面的方法,而是会自己做一些 Client 的配置,比如调整超时时间:

1
2
3
4
5
client := &http.Client{
Timeout: 5 * time.Second,
}
resp, _ := client.Get("https://golang.org")
defer resp.Body.Close()

另外在很多时候,我们需要使用 GET 和 POST 之外的 http 方法,那就需要下面这样的配置:

1
2
3
4
5
6
7
client := &http.Client{
Timeout: 5 * time.Second,
}

req, _ := http.NewRequest("PUT", "https://golang.org", nil)
resp, _ := client.Do(req)
defer resp.Body.Close()

比如还需要在请求的 Header 中增加一些字段:

1
2
3
4
5
6
7
client := &http.Client{
Timeout: 5 * time.Second,
}
req, _ := http.NewRequest("GET", "https://golang.org", nil)
req.Header.Add("User-Id", "userid123456")
resp, _ := client.Do(req)
defer resp.Body.Close()

或者更进一步,我们需要自定义传输层的一些配置:

1
2
3
4
5
6
7
8
tr := &http.Transport{
MaxIdleConns: 10,
IdleConnTimeout: 30 * time.Second,
DisableCompression: true,
}
client := &http.Client{Transport: tr}
resp, _ := client.Get("https://golang.org")
defer resp.Body.Close()

http 包中发送请求,提供了不同层次的配置,满足不同场景的使用。

2. http 的服务端

除了客户端,使用 http 包来创建 http 服务也很方便。

2.1 一行代码创建 http 服务

创建一个 http 服务,在 Go 代码中,只需要一行代码:

1
2
3
func main() {
http.ListenAndServe(":8080", nil)
}

在 main 方法中,写下上面那行代码,然后运行 main 方法,端口号为 8080 的 http 服务就运行起来了, 但目前还处理不了任何请求。

2.2 添加请求路径

在上面代码的基础上,需要添加一个路径,这样服务才可以开始处理请求:

1
2
3
4
5
6
7
8
9
10
11
12
13
func main() {
http.Handle("/index", &CustomerHandler{})
http.ListenAndServe(":8080", nil)
}

type CustomerHandler struct {

}

func (c *CustomerHandler) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
fmt.Println("implement http server by self")
writer.Write([]byte("server echo"))
}

添加了 /index 路径,在这种方式下,需要为每一个请求都定义一个 Handler,然后 Handler 需要实现 ServeHttp 方法。

Handler 是一个请求处理器,我们如果使用这种方式,就需要为每一个请求的 url 实现一个 Handler,这样实现很繁琐。

但我们还有另一个选择,就是使用 HandlerFunc,添加另外一个路径:

1
2
3
4
5
6
func main() {
http.HandleFunc("/index", func(writer http.ResponseWriter, request *http.Request) {
writer.Write([]byte("HandleFunc implement"))
})
http.ListenAndServe(":8080", nil)
}

使用这种方式很简洁,值需要实现 HandlerFunc 类型的一个匿名方法就可以了,HandlerFunc 是一个适配器,可以让我们把一个与 ServeHTTP 签名相同的函数作为一个处理器。

Handler 和 HandlerFunc 都是通过 DefaultServeMux 来实现的。 DefaultServeMux 才是上面服务的核心。

在上面的代码,http.ListenAndServe 的第二个参数传入的是 nil,通常情况下,这个参数都是 nil,跟进代码,发现这个参数为 nil 的时候,就是使用 DefaultServeMux 来作为服务端的实现:

1
2
3
4
5
6
7
8
9
10
11
// server.go
func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
handler := sh.srv.Handler
if handler == nil {
handler = DefaultServeMux
}
if req.RequestURI == "*" && req.Method == "OPTIONS" {
handler = globalOptionsHandler{}
}
handler.ServeHTTP(rw, req)
}

DefaultServeMux 的类型是 ServeMux,这是 Go 语言原生包中 http 服务端的默认实现。ServeMux 同样实现了ServeHttp 这个方法。

ServeHttp 方法才是整个 http 服务的核心,只要需要处理请求,就必须实现这个方法。Handler 和 HandlerFunc 只是 Go 语言提供的两种实现。

3. http 的反向代理

反向代理在开发 Web 应用,特别是开发网关类应用的时候会经常用到, Go 也提供了实现,基本上开箱即用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
func main() {
http.HandleFunc("/formawd", func(writer http.ResponseWriter, request *http.Request) {
director := func(req *http.Request) {
req.URL.Scheme = "https"
req.URL.Host = "golang.org"
req.URL.Path = "upload"
}

proxy := &httputil.ReverseProxy{Director: director}
proxy.ServeHTTP(writer, request)
})

http.ListenAndServe(":8080", nil)
}

上面的代码会把所有的请求都转发到一个地方,当然也可以通过配置,将请求转发到不同的地方。

4. 小结

Go 语言原生的包就自带了 http 包,这个包提供 http 编程所需要的基础能力,开箱即用,不需要额外的依赖。在实际项目中使用,做个简单的封装即可。而且还自带反向代理的能力,可以很方便的写出一个 API 网关。

文 / Rayjun

© 2020 Rayjun    PowerBy Hexo    京ICP备16051220号-1