Golang筆記 6.1.1 HTTP 客戶端

前言

我正在學習酷酷的 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 方法,另一方面又具備極大的靈活性(可以詳細設置業務層和傳輸層的細節)。

END


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章