HTTP(HyperText Transfer Protocol,超文本传输协议)是互联网上应用最为广泛的一种网络协议,定义了客户端和服务端之间请求和响应的传输标准。
Go语言标准库内建提供了net/http包,涵盖了HTTP客户端和服务端的具体实现。使用net/http包,我们可以很方便地编写HTTP客户端或服务端的程序。
HTTP客户端
net/http包的Client类提供了如下几个方法,让我们可以用最简洁的方式实现HTTP请求:
下面概要介绍这几个方法。
- http.Get()
要请求一个资源,只需调用http.Get()方法(等价于http.DefaultClient.Get())即可,示例代码如下:
上面这段代码请求一个网站首页,并将其网页内容打印到标准输出流中。
- http.Post()
要以POST的方式发送数据,也很简单,只需要调用http.Post()方法并依次传递下面的3个参数即可:
请求的目标URL
将要POST数据的资源类型(MIMEType)
数据的比特流([]byte形式)
下面的示例代码演示了如何上传一张图片:
- http.PostForm()
http.PostForm()方法实现了标准编码格式为application/x-www-form-urlencoded的表单提交。下面的示例代码模拟HTML表单提交一篇新文章:
- http.Head()
HTTP 中的 Head 请求方式表明只请求目标 URL 的头部信息,即 HTTP Header 而不返回 HTTPBody。 Go 内置的 net/http 包同样也提供了 http.Head() 方法,该方法同 http.Get() 方法一样,只需传入目标 URL 一个参数即可。下面的示例代码请求一个网站首页的 HTTP Header信息:
- (*http.Client).Do()
在多数情况下, http.Get()和http.PostForm() 就可以满足需求,但是如果我们发起的HTTP 请求需要更多的定制信息,我们希望设定一些自定义的 Http Header 字段,比如:
设定自定义的”User-Agent”,而不是默认的 “Go http package”
传递 Cookie
此时可以使用net/http包http.Client对象的Do()方法来实现:
除了上面介绍的基本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.Client类型包含了3个公开数据成员:
其中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非常简单,具体代码如下:
- 自定义 http.Transport
在http.Client 类型的结构定义中,我们看到的第一个数据成员就是一个 http.Transport对象,该对象指定执行一个 HTTP 请求时的运行规则。下面我们来看看 http.Transport 类型的具体结构:
在上面的代码中,我们定义了 http.Transport 类型中的公开数据成员,下面详细说明其中的各行代码。
Proxy func(*Request) (*url.URL, error)
Proxy 指定了一个代理方法,该方法接受一个 Request 类型的请求实例作为参数并返回一个最终的 HTTP 代理。如果 Proxy 未指定或者返回的 URL 为零值,将不会有代理被启用。
Dial func(net, addr string) (c net.Conn, err error)
Dial 指定具体的dial()方法来创建 TCP 连接。如果不指定,默认将使用 net.Dial() 方法。
TLSClientConfig *tls.Config
SSL连接专用, TLSClientConfig 指定 tls.Client 所用的 TLS 配置信息,如果不指定,也会使用默认的配置。
DisableKeepAlives bool
是否取消长连接,默认值为 false,即启用长连接。
DisableCompression bool
是否取消压缩(GZip),默认值为 false,即启用压缩。
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也很简单,如下列代码所示:
tr := &http.Transport{
TLSClientConfig: &tls.Config{RootCAs: pool},
DisableCompression: true,
}
client := &http.Client{Transport: tr}
resp, err := client.Get("https://example.com")
Client和Transport在执行多个 goroutine 的并发过程中都是安全的,但出于性能考虑,应当创建一次后反复使用。
- 灵活的http.RoundTripper接口
http.Client 定义的第一个公开成员就是一个http.Transport 类型的实例,且该成员所对应的类型必须实现http.RoundTripper接口。下面我们来看看 http.RoundTripper接口的具体定义:
从上述代码中可以看到, 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()方法,如以下代码所示:
package main
import(
"net/http"
)
type OurCustomTransport struct {
Transport http.RoundTripper
}
func (t *OurCustomTransport) transport() http.RoundTripper {
if t.Transport != nil {
return t.Transport
}
return http.DefaultTransport
}
func (t *OurCustomTransport) RoundTrip(req *http.Request) (*http.Response, error) {
// 处理一些事情 ...
// 发起HTTP请求
// 添加一些域到req.Header中
return t.transport().RoundTrip(req)
}
func (t *OurCustomTransport) Client() *http.Client {
return &http.Client{Transport: t}
}
func main() {
t := &OurCustomTransport{
//...
}
c := t.Client()
resp, err := c.Get("http://example.com")
// ...
}
因为实现了http.RoundTripper 接口的代码通常需要在多个 goroutine中并发执行,因此我们必须确保实现代码的线程安全性。
HTTP服务端
处理HTTP请求
使用 net/http 包提供的 http.ListenAndServe() 方法,可以在指定的地址进行监听,开启一个HTTP,服务端该方法的原型如下:
func ListenAndServe(addr string, handler Handler) error
该方法用于在指定的 TCP 网络地址 addr 进行监听,然后调用服务端处理程序来处理传入的连接请求。该方法有两个参数:第一个参数 addr 即监听地址;第二个参数表示服务端处理程序,通常为空,这意味着服务端调用 http.DefaultServeMux 进行处理,而服务端编写的业务逻辑处理程序 http.Handle() 或 http.HandleFunc() 默认注入 http.DefaultServeMux 中,具体代码如下:
http.Handle("/foo", fooHandler)
http.HandleFunc("/bar", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, %q", html.EscapeString(r.URL.Path))
})
log.Fatal(http.ListenAndServe(":8080", nil))
如果想更多地控制服务端的行为,可以自定义 http.Server,代码如下:
s := &http.Server{
Addr: ":8080",
Handler: myHandler,
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
MaxHeaderBytes: 1 << 20,
}
log.Fatal(s.ListenAndServe())
处理HTTPS请求
net/http 包还提供 http.ListenAndServeTLS() 方法,用于处理 HTTPS 连接请求:
func ListenAndServeTLS(addr string, certFile string, keyFile string, handler Handler) error
ListenAndServeTLS() 和 ListenAndServe()的行为一致,区别在于只处理HTTPS请求。此外,服务器上必须存在包含证书和与之匹配的私钥的相关文件,比如certFile对应SSL证书文件存放路径, keyFile对应证书私钥文件路径。如果证书是由证书颁发机构签署的, certFile参数指定的路径必须是存放在服务器上的经由CA认证过的SSL证书。
开启 SSL 监听服务也很简单,如下列代码所示:
http.Handle("/foo", fooHandler)
http.HandleFunc("/bar", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, %q", html.EscapeString(r.URL.Path))
})
log.Fatal(http.ListenAndServeTLS(":10443", "cert.pem", "key.pem", nil))
或者是:
ss := &http.Server{
Addr: ":10443",
Handler: myHandler,
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
MaxHeaderBytes: 1 << 20,
}
log.Fatal(ss.ListenAndServeTLS("cert.pem", "key.pem"))