前言
我正在學習酷酷的 Golang,可點此查看帖子Golang學習筆記彙總。
1 庫的介紹
Go 內置的 net/http 包提供了最簡潔的 HTTP 客戶端實現,我們無需藉助第三方網絡通信庫(比如 libcurl)就可以直接使用 HTTP 中用得最多的 GET 和 POST 方式請求數據。
2 基本方法
net/http包的 Client 類型提供瞭如下幾個方法,讓我們可以用最簡潔的方式實現 HTTP 請求:
func (c *Client) Get(url string) (r *Response, err error)
func (c *Client) Post(url string, bodyType string, body io.Reader) (r *Response, err error)
func (c *Client) PostForm(url string, data url.Values) (r *Response, err error)
func (c *Client) Head(url string) (r *Response, err error)
func (c *Client) Do(req *Request) (resp *Response, err error)
- http.Get()
要請求一個資源,只需調用http.Get()方法(等價於http.DefaultClient.Get())即可,示例代碼如下:
resp, err := http.Get("http://example.com/")
if err != nil {
// 處理錯誤 ...
return
}
defer resp.Body.close()
io.Copy(os.Stdout, resp.Body)
上面這段代碼請求一個網站首頁,並將其網頁內容打印到標準輸出流中。
- http.Post()
要以POST的方式發送數據,也很簡單,只需調用http.Post()方法並依次傳遞下面的3個參數即可:請求的目標 URL、將要 POST 數據的資源類型(MIMEType)、數據的比特流([]byte形式)
下面的示例代碼演示瞭如何上傳一張圖片:
resp, err := http.Post("http://example.com/upload", "image/jpeg", &imageDataBuf)
if err != nil {
// 處理錯誤
return
}
if resp.StatusCode != http.StatusOK {
// 處理錯誤
return
}
// ...
3 高級封裝
除了基本HTTP操作, Go語言標準庫也暴露了比較底層的HTTP相關庫,讓開發者可以基於這些庫靈活定製HTTP服務器和使用HTTP服務。
自定義 http.Client
前面我們使用的http.Get()、 http.Post()方法其實都是在http.DefaultClient的基礎上進行調用的,比如http.Get()等價於http.DefaultClient.Get(),依次類推。
在net/http包中,的確提供了Client類型。讓我們來看一看http.Client類型的結構:
type Client struct {
// Transport specifies the mechanism by which individual
// HTTP requests are made.
// If nil, DefaultTransport is used.
Transport RoundTripper
// CheckRedirect specifies the policy for handling redirects.
// If CheckRedirect is not nil, the client calls it before
// following an HTTP redirect. The arguments req and via are
// the upcoming request and the requests made already, oldest
// first. If CheckRedirect returns an error, the Client's Get
// method returns both the previous Response (with its Body
// closed) and CheckRedirect's error (wrapped in a url.Error)
// instead of issuing the Request req.
// As a special case, if CheckRedirect returns ErrUseLastResponse,
// then the most recent response is returned with its body
// unclosed, along with a nil error.
//
// If CheckRedirect is nil, the Client uses its default policy,
// which is to stop after 10 consecutive requests.
CheckRedirect func(req *Request, via []*Request) error
// Jar specifies the cookie jar.
//
// The Jar is used to insert relevant cookies into every
// outbound Request and is updated with the cookie values
// of every inbound Response. The Jar is consulted for every
// redirect that the Client follows.
//
// If Jar is nil, cookies are only sent if they are explicitly
// set on the Request.
Jar CookieJar
// Timeout specifies a time limit for requests made by this
// Client. The timeout includes connection time, any
// redirects, and reading the response body. The timer remains
// running after Get, Head, Post, or Do return and will
// interrupt reading of the Response.Body.
//
// A Timeout of zero means no timeout.
//
// The Client cancels requests to the underlying Transport
// as if the Request's Context ended.
//
// For compatibility, the Client will also use the deprecated
// CancelRequest method on Transport if found. New
// RoundTripper implementations should use the Request's Context
// for cancelation instead of implementing CancelRequest.
Timeout time.Duration
}
示例:
client := &http.Client {
CheckRedirect: redirectPolicyFunc,
}
resp, err := client.Get("http://example.com")
// ...
req, err := http.NewRequest("GET", "http://example.com", nil)
// ...
req.Header.Add("User-Agent", "Our Custom User-Agent")
req.Header.Add("If-None-Match", `W/"TheFileEtag"`)
resp, err := client.Do(req)
// ...
自定義 http.Transport
在http.Client 類型的結構定義中,我們看到的第一個數據成員就是一個 http.Transport 對象,該對象指定執行一個 HTTP 請求時的運行規則。
type Transport struct {
idleMu sync.Mutex
wantIdle bool // user has requested to close all idle conns
idleConn map[connectMethodKey][]*persistConn // most recently used at end
idleConnCh map[connectMethodKey]chan *persistConn
idleLRU connLRU
reqMu sync.Mutex
reqCanceler map[*Request]func(error)
altMu sync.Mutex // guards changing altProto only
altProto atomic.Value // of nil or map[string]RoundTripper, key is URI scheme
connCountMu sync.Mutex
connPerHostCount map[connectMethodKey]int
connPerHostAvailable map[connectMethodKey]chan struct{}
// Proxy specifies a function to return a proxy for a given
// Request. If the function returns a non-nil error, the
// request is aborted with the provided error.
//
// The proxy type is determined by the URL scheme. "http",
// "https", and "socks5" are supported. If the scheme is empty,
// "http" is assumed.
//
// If Proxy is nil or returns a nil *URL, no proxy is used.
Proxy func(*Request) (*url.URL, error)
// DialContext specifies the dial function for creating unencrypted TCP connections.
// If DialContext is nil (and the deprecated Dial below is also nil),
// then the transport dials using package net.
//
// DialContext runs concurrently with calls to RoundTrip.
// A RoundTrip call that initiates a dial may end up using
// a connection dialed previously when the earlier connection
// becomes idle before the later DialContext completes.
DialContext func(ctx context.Context, network, addr string) (net.Conn, error)
// Dial specifies the dial function for creating unencrypted TCP connections.
//
// Dial runs concurrently with calls to RoundTrip.
// A RoundTrip call that initiates a dial may end up using
// a connection dialed previously when the earlier connection
// becomes idle before the later Dial completes.
//
// Deprecated: Use DialContext instead, which allows the transport
// to cancel dials as soon as they are no longer needed.
// If both are set, DialContext takes priority.
Dial func(network, addr string) (net.Conn, error)
// DialTLS specifies an optional dial function for creating
// TLS connections for non-proxied HTTPS requests.
//
// If DialTLS is nil, Dial and TLSClientConfig are used.
//
// If DialTLS is set, the Dial hook is not used for HTTPS
// requests and the TLSClientConfig and TLSHandshakeTimeout
// are ignored. The returned net.Conn is assumed to already be
// past the TLS handshake.
DialTLS func(network, addr string) (net.Conn, error)
// TLSClientConfig specifies the TLS configuration to use with
// tls.Client.
// If nil, the default configuration is used.
// If non-nil, HTTP/2 support may not be enabled by default.
TLSClientConfig *tls.Config
// TLSHandshakeTimeout specifies the maximum amount of time waiting to
// wait for a TLS handshake. Zero means no timeout.
TLSHandshakeTimeout time.Duration
// DisableKeepAlives, if true, disables HTTP keep-alives and
// will only use the connection to the server for a single
// HTTP request.
//
// This is unrelated to the similarly named TCP keep-alives.
DisableKeepAlives bool
// DisableCompression, if true, prevents the Transport from
// requesting compression with an "Accept-Encoding: gzip"
// request header when the Request contains no existing
// Accept-Encoding value. If the Transport requests gzip on
// its own and gets a gzipped response, it's transparently
// decoded in the Response.Body. However, if the user
// explicitly requested gzip it is not automatically
// uncompressed.
DisableCompression bool
// MaxIdleConns controls the maximum number of idle (keep-alive)
// connections across all hosts. Zero means no limit.
MaxIdleConns int
// MaxIdleConnsPerHost, if non-zero, controls the maximum idle
// (keep-alive) connections to keep per-host. If zero,
// DefaultMaxIdleConnsPerHost is used.
MaxIdleConnsPerHost int
// MaxConnsPerHost optionally limits the total number of
// connections per host, including connections in the dialing,
// active, and idle states. On limit violation, dials will block.
//
// Zero means no limit.
//
// For HTTP/2, this currently only controls the number of new
// connections being created at a time, instead of the total
// number. In practice, hosts using HTTP/2 only have about one
// idle connection, though.
MaxConnsPerHost int
// IdleConnTimeout is the maximum amount of time an idle
// (keep-alive) connection will remain idle before closing
// itself.
// Zero means no limit.
IdleConnTimeout time.Duration
// ResponseHeaderTimeout, if non-zero, specifies the amount of
// time to wait for a server's response headers after fully
// writing the request (including its body, if any). This
// time does not include the time to read the response body.
ResponseHeaderTimeout time.Duration
// ExpectContinueTimeout, if non-zero, specifies the amount of
// time to wait for a server's first response headers after fully
// writing the request headers if the request has an
// "Expect: 100-continue" header. Zero means no timeout and
// causes the body to be sent immediately, without
// waiting for the server to approve.
// This time does not include the time to send the request header.
ExpectContinueTimeout time.Duration
// TLSNextProto specifies how the Transport switches to an
// alternate protocol (such as HTTP/2) after a TLS NPN/ALPN
// protocol negotiation. If Transport dials an TLS connection
// with a non-empty protocol name and TLSNextProto contains a
// map entry for that key (such as "h2"), then the func is
// called with the request's authority (such as "example.com"
// or "example.com:1234") and the TLS connection. The function
// must return a RoundTripper that then handles the request.
// If TLSNextProto is not nil, HTTP/2 support is not enabled
// automatically.
TLSNextProto map[string]func(authority string, c *tls.Conn) RoundTripper
// ProxyConnectHeader optionally specifies headers to send to
// proxies during CONNECT requests.
ProxyConnectHeader Header
// MaxResponseHeaderBytes specifies a limit on how many
// response bytes are allowed in the server's response
// header.
//
// Zero means to use a default limit.
MaxResponseHeaderBytes int64
// nextProtoOnce guards initialization of TLSNextProto and
// h2transport (via onceSetNextProtoDefaults)
nextProtoOnce sync.Once
h2transport h2Transport // non-nil if http2 wired up
}
示例:
tr := &http.Transport{
TLSClientConfig: &tls.Config{RootCAs: pool},
DisableCompression: true,
}
client := &http.Client{Transport: tr}
resp, err := client.Get("https://example.com")
4 示例 - 短連接 POST 請求
DisableKeepAlives 默認是 false,表示開了長連接,如果我們不需要的話,可以改爲 true,表示短連接。
如下是一個短連接的 POST 方法。
client := http.Client{
Timeout: timeout,
Transport: &http.Transport{DisableKeepAlives: true},
}
resp, err := client.Post(url, "application/json", buffer)
5 小結
綜上示例講解可以看到, Go語言標準庫提供的 HTTP Client 是相當優雅的。一方面提供了極其簡單的使用方式,另一方面又具備極大的靈活性。
Go語言標準庫提供的HTTP Client 被設計成上下兩層結構。
一層是上述提到的 http.Client 類及其封裝的基礎方法,我們不妨將其稱爲“業務層”。之所以稱爲業務層,是因爲調用方通常只需要關心請求的業務邏輯本身,而無需關心非業務相關的技術細節,這些細節包括:
- HTTP 底層傳輸細節
- HTTP 代理
- gzip 壓縮
- 連接池及其管理
- 認證(SSL或其他認證方式)
之所以 HTTP Client 可以做到這麼好的封裝性,是因爲 HTTP Client 在底層抽象了 http.RoundTripper 接口,而 http.Transport 實現了該接口,從而能夠處理更多的細節,我們不妨將其稱爲“傳輸層”。 HTTP Client 在業務層初始化 HTTP Method、目標URL、請求參數、請求內容等重要信息後,經過“傳輸層”,“傳輸層”在業務層處理的基礎上補充其他細節,然後再發起 HTTP 請求,接收服務端返回的 HTTP 響應。
一句話:Go語言標準庫提供的 HTTP 客戶端相當優雅,一方面可以極其簡單的使用 Get、Post 方法,另一方面又具備極大的靈活性(可以詳細設置業務層和傳輸層的細節)。