HTTP(HyperText Transfer Protocol,超文本传输协议)是互联网上应用最为广泛的一种网络协议,定义了客户端和服务端之间请求和响应的传输标准。

Go语言标准库内建提供了net/http包,涵盖了HTTP客户端和服务端的具体实现。使用net/http包,我们可以很方便地编写HTTP客户端或服务端的程序。

HTTP客户端

net/http包的Client类提供了如下几个方法,让我们可以用最简洁的方式实现HTTP请求:
Go网络编程之HTTP编程 - 图1

下面概要介绍这几个方法。

  • http.Get()

要请求一个资源,只需调用http.Get()方法(等价于http.DefaultClient.Get())即可,示例代码如下:
Go网络编程之HTTP编程 - 图2
上面这段代码请求一个网站首页,并将其网页内容打印到标准输出流中。

  • http.Post()

要以POST的方式发送数据,也很简单,只需要调用http.Post()方法并依次传递下面的3个参数即可:

  1. 请求的目标URL

  2. 将要POST数据的资源类型(MIMEType)

  3. 数据的比特流([]byte形式)

下面的示例代码演示了如何上传一张图片:
Go网络编程之HTTP编程 - 图3

  • http.PostForm()

http.PostForm()方法实现了标准编码格式为application/x-www-form-urlencoded的表单提交。下面的示例代码模拟HTML表单提交一篇新文章:
Go网络编程之HTTP编程 - 图4

  • http.Head()

HTTP 中的 Head 请求方式表明只请求目标 URL 的头部信息,即 HTTP Header 而不返回 HTTPBody。 Go 内置的 net/http 包同样也提供了 http.Head() 方法,该方法同 http.Get() 方法一样,只需传入目标 URL 一个参数即可。下面的示例代码请求一个网站首页的 HTTP Header信息:
Go网络编程之HTTP编程 - 图5

  • (*http.Client).Do()

在多数情况下, http.Get()和http.PostForm() 就可以满足需求,但是如果我们发起的HTTP 请求需要更多的定制信息,我们希望设定一些自定义的 Http Header 字段,比如:
设定自定义的”User-Agent”,而不是默认的 “Go http package”
传递 Cookie

此时可以使用net/http包http.Client对象的Do()方法来实现:
Go网络编程之HTTP编程 - 图6
除了上面介绍的基本HTTP操作,Go语言标准库也暴露了比较底层的HTTP相关库,让开发者可以基于这些库灵活定制HTTP服务器和使用HTTP服务。

  • 自定义http.Client

前面我们使用的http.Get()、http.Post()、http.PostForm()和http.Head()方法其实都是在http.DefaultClient的基础上进行调用的,比如http.Get()等价于http.DefaultClient.Get(),依次类推。

http.DefaultClient在字面上就向我们传达了一个消息,既然存在默认的Client,那么Http Client大概是可以自定义的。实际上确实如此,在net/http包中,的确提供了Client类型。让我们来看一看http.Client类型的结构:
Go网络编程之HTTP编程 - 图7

在Go语言标准库中,http.Client类型包含了3个公开数据成员:
Go网络编程之HTTP编程 - 图8
其中Transport类型必须实现http.RoundTripper接口。Transport指定了执行一个HTTP请求的运行机制,倘若不指定具体的Transport,默认会使用http.DefaultTransport,这意味着http.Transport也是可以自定义的。net/http包中的http.Transport类型实现了http.RoundTripper接口。

CheckRedirect函数指定处理重定向的策略。当使用HTTP client的Get()或者是Head()方法发送HTTP请求时,若响应返回的状态码为30X(比如301/302/303/307),HTTP Client会在遵循跳转规则之前先调用这个CheckRedirect函数函数。

Jar可用于在HTTP Client中设定Cookie,Jar的类型必须实现了http.CookieJar接口,该接口预定义了SetCookies()和Cookies()两个方法。如果Http Client中没有设定Jar,Cookie将忽略而不会发送到客户端。实际上,我们一般都用http.SetCookie()方法来设定Cookie。

使用自定义的http.Client及其Do()方法,我们可以非常灵活的控制HTTP请求,比如发送自定义HTTP Header或是改写重定向策略等。创建自定义的HTTP Client非常简单,具体代码如下:
Go网络编程之HTTP编程 - 图9

  • 自定义 http.Transport

在http.Client 类型的结构定义中,我们看到的第一个数据成员就是一个 http.Transport对象,该对象指定执行一个 HTTP 请求时的运行规则。下面我们来看看 http.Transport 类型的具体结构:
Go网络编程之HTTP编程 - 图10

在上面的代码中,我们定义了 http.Transport 类型中的公开数据成员,下面详细说明其中的各行代码。

  1. Proxy func(*Request) (*url.URL, error)

Proxy 指定了一个代理方法,该方法接受一个 Request 类型的请求实例作为参数并返回一个最终的 HTTP 代理。如果 Proxy 未指定或者返回的 URL 为零值,将不会有代理被启用。

  1. Dial func(net, addr string) (c net.Conn, err error)

Dial 指定具体的dial()方法来创建 TCP 连接。如果不指定,默认将使用 net.Dial() 方法。

  1. TLSClientConfig *tls.Config

SSL连接专用, TLSClientConfig 指定 tls.Client 所用的 TLS 配置信息,如果不指定,也会使用默认的配置。

  1. DisableKeepAlives bool

是否取消长连接,默认值为 false,即启用长连接。

  1. DisableCompression bool

是否取消压缩(GZip),默认值为 false,即启用压缩。

  1. MaxIdleConnsPerHost int

指定与每个请求的目标主机之间的最大非活跃连接(keep-alive)数量。如果不指定,默认使用 DefaultMaxIdleConnsPerHost 的常量值。

除了 http.Transport 类型中定义的公开数据成员以外,它同时还提供了几个公开的成员方法。

  • func(t *Transport) CloseIdleConnections()。该方法用于关闭所有非活跃的连接。

  • func(t *Transport) RegisterProtocol(scheme string, rt RoundTripper)。该方法可用于注册并启用一个新的传输协议,比如 WebSocket 的传输协议标准(ws),或者 FTP、 File 协议等。

  • func(t Transport) RoundTrip(req Request) (resp *Response, err error)。用于实现 http.RoundTripper 接口。

自定义http.Transport也很简单,如下列代码所示:

  1. tr := &http.Transport{
  2. TLSClientConfig: &tls.Config{RootCAs: pool},
  3. DisableCompression: true,
  4. }
  5. client := &http.Client{Transport: tr}
  6. resp, err := client.Get("https://example.com")

Client和Transport在执行多个 goroutine 的并发过程中都是安全的,但出于性能考虑,应当创建一次后反复使用。

  • 灵活的http.RoundTripper接口

http.Client 定义的第一个公开成员就是一个http.Transport 类型的实例,且该成员所对应的类型必须实现http.RoundTripper接口。下面我们来看看 http.RoundTripper接口的具体定义:
Go网络编程之HTTP编程 - 图11
从上述代码中可以看到, http.RoundTripper接口很简单,只定义了一个名为RoundTrip的方法。任何实现了 RoundTrip() 方法的类型即可实现http.RoundTripper接口。前面我们看到的http.Transport类型正是实现了 RoundTrip() 方法继而实现了该接口。

http.RoundTripper 接口定义的 RoundTrip() 方法用于执行一个独立的 HTTP 事务,接受传入的 *Request 请求值作为参数并返回对应的 *Response 响应值,以及一个 error 值。在实现具体的 RoundTrip() 方法时,不应该试图在该函数里边解析 HTTP 响应信息。若响应成功,error 的值必须为nil,而与返回的 HTTP 状态码无关。若不能成功得到服务端的响应,error必须为非零值。类似地,也不应该试图在 RoundTrip() 中处理协议层面的相关细节,比如重定向、认证或是 cookie 等。

非必要情况下,不应该在 RoundTrip() 中改写传入的请求体(*Request),请求体的内容(比如 URL 和 Header 等)必须在传入RoundTrip()之前就已组织好并完成初始化。通常,我们可以在默认的 http.Transport之包一层Transport并实现RoundTrip()方法,如以下代码所示:

  1. package main
  2. import(
  3. "net/http"
  4. )
  5. type OurCustomTransport struct {
  6. Transport http.RoundTripper
  7. }
  8. func (t *OurCustomTransport) transport() http.RoundTripper {
  9. if t.Transport != nil {
  10. return t.Transport
  11. }
  12. return http.DefaultTransport
  13. }
  14. func (t *OurCustomTransport) RoundTrip(req *http.Request) (*http.Response, error) {
  15. // 处理一些事情 ...
  16. // 发起HTTP请求
  17. // 添加一些域到req.Header中
  18. return t.transport().RoundTrip(req)
  19. }
  20. func (t *OurCustomTransport) Client() *http.Client {
  21. return &http.Client{Transport: t}
  22. }
  23. func main() {
  24. t := &OurCustomTransport{
  25. //...
  26. }
  27. c := t.Client()
  28. resp, err := c.Get("http://example.com")
  29. // ...
  30. }

因为实现了http.RoundTripper 接口的代码通常需要在多个 goroutine中并发执行,因此我们必须确保实现代码的线程安全性。

HTTP服务端

处理HTTP请求
使用 net/http 包提供的 http.ListenAndServe() 方法,可以在指定的地址进行监听,开启一个HTTP,服务端该方法的原型如下:

  1. func ListenAndServe(addr string, handler Handler) error
  1. 该方法用于在指定的 TCP 网络地址 addr 进行监听,然后调用服务端处理程序来处理传入的连接请求。该方法有两个参数:第一个参数 addr 即监听地址;第二个参数表示服务端处理程序,通常为空,这意味着服务端调用 http.DefaultServeMux 进行处理,而服务端编写的业务逻辑处理程序 http.Handle() http.HandleFunc() 默认注入 http.DefaultServeMux 中,具体代码如下:
  1. http.Handle("/foo", fooHandler)
  2. http.HandleFunc("/bar", func(w http.ResponseWriter, r *http.Request) {
  3. fmt.Fprintf(w, "Hello, %q", html.EscapeString(r.URL.Path))
  4. })
  5. log.Fatal(http.ListenAndServe(":8080", nil))

如果想更多地控制服务端的行为,可以自定义 http.Server,代码如下:

  1. s := &http.Server{
  2. Addr: ":8080",
  3. Handler: myHandler,
  4. ReadTimeout: 10 * time.Second,
  5. WriteTimeout: 10 * time.Second,
  6. MaxHeaderBytes: 1 << 20,
  7. }
  8. log.Fatal(s.ListenAndServe())

处理HTTPS请求
net/http 包还提供 http.ListenAndServeTLS() 方法,用于处理 HTTPS 连接请求:

  1. func ListenAndServeTLS(addr string, certFile string, keyFile string, handler Handler) error
  1. ListenAndServeTLS() ListenAndServe()的行为一致,区别在于只处理HTTPS请求。此外,服务器上必须存在包含证书和与之匹配的私钥的相关文件,比如certFile对应SSL证书文件存放路径, keyFile对应证书私钥文件路径。如果证书是由证书颁发机构签署的, certFile参数指定的路径必须是存放在服务器上的经由CA认证过的SSL证书。

开启 SSL 监听服务也很简单,如下列代码所示:

  1. http.Handle("/foo", fooHandler)
  2. http.HandleFunc("/bar", func(w http.ResponseWriter, r *http.Request) {
  3. fmt.Fprintf(w, "Hello, %q", html.EscapeString(r.URL.Path))
  4. })
  5. log.Fatal(http.ListenAndServeTLS(":10443", "cert.pem", "key.pem", nil))

或者是:

  1. ss := &http.Server{
  2. Addr: ":10443",
  3. Handler: myHandler,
  4. ReadTimeout: 10 * time.Second,
  5. WriteTimeout: 10 * time.Second,
  6. MaxHeaderBytes: 1 << 20,
  7. }
  8. log.Fatal(ss.ListenAndServeTLS("cert.pem", "key.pem"))

Go网络编程之HTTP编程 - 图12