http.Clientのデータ構造
我々はすでに、導入しておりhttp.Get()
、http.Post()
、http.PostForm()
および http.Head()
方法は、実際にされている http.DefaultClient
コールに基づいて行います。
http.DefaultClient
これは、 net / http パッケージによって提供されるHTTPクライアントのデフォルトの実装です。
// DefaultClient is the default Client and is used by Get, Head, and Post.
var DefaultClient = &Client{}
実際、http.Client
カスタムHTTPクライアントに基づいて実装することもでき ます。その前Client
に、次のタイプのデータ構造を見てみましょう 。
type Client struct {
// Transport 用于指定单次 HTTP 请求响应的完整流程
// 默认值是 DefaultTransport
Transport RoundTripper
// CheckRedirect 用于定义重定向处理策略
// 它是一个函数类型,接收 req 和 via 两个参数,分别表示即将发起的请求和已经发起的所有请求,最早的已发起请求在最前面
// 如果不为空,客户端将在跟踪 HTTP 重定向前调用该函数
// 如果返回错误,客户端将直接返回错误,不会再发起该请求
// 如果为空,Client 将采用一种确认策略,会在 10 个连续请求后终止
CheckRedirect func(req *Request, via []*Request) error
// Jar 用于指定请求和响应头中的 Cookie
// 如果该字段为空,则只有在请求中显式设置的 Cookie 才会被发送
Jar CookieJar
// 指定单次 HTTP 请求响应事务的超时时间
// 未设置的话使用 Transport 的默认设置,为零的话表示不设置超时时间
Timeout time.Duration
}
この Transport
フィールド は、HTTPトランザクション(要求応答)の完全なフローを指定して、http.RoundTripper
インターフェースを実装する必要 Transport
があります。指定されていない場合 、デフォルトの実装Transport
がhttp.DefaultTransport
デフォルトで使用されます。たとえばhttp.DefaultClient
、これが当てはまりhttp.DefaultTransport
、基礎となる実装について詳しく説明し ます。 後で 。
CheckRedirect
関数は、リダイレクトを処理するための戦略を定義するために使用されます。ときに HTTP要求を送信する使用Get()
または Head()
方法HTTPのデフォルト・クライアントによって提供さを 応答ステータスコードがある場合、 30x
(例えば 301
、302
等)、HTTPクライアントがこの呼び出すCheckRedirect
関数をリダイレクトルールを次の前 。
Jar
HTTPクライアントにクッキーを設定するために使用することができます。Jar
タイプが実装しなければなら http.CookieJar
事前に定義しているインタフェース、 SetCookies()
および Cookies()
2つのメソッドを。HTTPクライアントに設定がない場合 Jar
、Cookieは無視され、クライアントに送信されません。実際、私たちは通常、http.SetCookie()
Cookieを設定するためのメソッドを使用し ます。
Timeout
このフィールドはTransport
、タイムアウト期間を指定するために使用されます。指定されて いない場合はTransport
、カスタム設定が使用され ます。
http.Transportの基本的な実装
以下ではhttp.DefaultTransport
、実装を使用 して焦点を当て ます。フィールドがhttp.Transport
明示的に設定されていないTransport
場合に 使用されます DefaultTransport
。
func (c *Client) transport() RoundTripper {
if c.Transport != nil {
return c.Transport
}
return DefaultTransport
}
DefaultTransport
はい Transport
、デフォルトの実装です。対応する初期化コードは次のとおりです。
var DefaultTransport RoundTripper = &Transport{
Proxy: ProxyFromEnvironment,
DialContext: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
DualStack: true,
}).DialContext,
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
}
ここでTransport
は、属性の一部のみが設定されて おりTransport
、タイプの完全なデータ構造は次のとおりです。
type Transport struct {
...
// 定义 HTTP 代理策略
Proxy func(*Request) (*url.URL, error)
// 用于指定创建未加密 TCP 连接的上下文参数(通过 net.Dial()创建连接时使用)
DialContext func(ctx context.Context, network, addr string) (net.Conn, error)
// 已废弃,使用 DialContext 替代
Dial func(network, addr string) (net.Conn, error)
// 创建加密 TCP 连接
DialTLS func(network, addr string) (net.Conn, error)
// 指定 tls.Client 所使用的 TLS 配置
TLSClientConfig *tls.Config
// TLS 握手超时时间
TLSHandshakeTimeout time.Duration
// 是否禁用 HTTP 长连接
DisableKeepAlives bool
// 是否对 HTTP 报文进行压缩传输(gzip)
DisableCompression bool
// 最大空闲连接数(支持长连接时有效)
MaxIdleConns int
// 单个服务(域名)最大空闲连接数
MaxIdleConnsPerHost int
// 单个服务(域名)最大连接数
MaxConnsPerHost int
// 空闲连接超时时间
IdleConnTimeout time.Duration
// 从客户端把请求完全提交给服务器到从服务器接收到响应报文头的超时时间
ResponseHeaderTimeout time.Duration
// 包含 "Expect: 100-continue" 请求头的情况下从客户端把请求完全提交给服务器到从服务器接收到响应报文头的超时时间
ExpectContinueTimeout time.Duration
...
}
Transport
データ構造と併せDefaultTransport
て設定を確認して ください 。
net.Dialer
ダイヤルコンテキスト設定を初期化し、デフォルトのタイムアウト時間は30秒に設定されています。- 介して
MaxIdleConns
接続100のアイドル、明示的に設定されていないの最大数を指定するMaxIdleConnsPerHost
とMaxConnsPerHost
、MaxIdleConnsPerHost
デフォルト値と、によってhttp.DefaultMaxIdleConnsPerHost
対応するデフォルト値を設定することは2です。 - することにより
IdleConnTimeout
、アイドル状態の接続が90秒以上のために再利用されていない場合、それは破壊されます。アイドル状態の接続が必要であること、90秒として最大アイドル接続時間を指定DisableKeepAlives
するためにfalse
利用可能である、つまり、それはHTTP長い有効です接続状態(HTTP / 1.1以降)リクエストヘッダーに対応する長い接続をサポートしConnection:keep-alive
ます); TLSHandshakeTimeout
TLSプロトコルに基づく安全なTCP接続が確立されるように指定することにより 、ハンドシェイクフェーズのタイムアウト期間は10秒になります。ExpectContinueTimeout
クライアントがPOSTリクエストを使用して大きなメッセージ本文をサーバーに送信することを指定することにより 、クライアントは最初Expect: 100-continue
に、含まれるリクエストヘッダーを送信することにより、 この大きなメッセージ本文に対応するタイムアウト期間を受信するかどうかをサーバーに尋ねます。デフォルトここでの設定は1秒です。
さらに、 メソッドの実装がTransport
含まれている RoundTrip
ため、RoundTripper
インターフェイスが実装され ます。 メソッドTransport
の RoundTrip
実装を見てみましょう 。
Transport.RoundTrip()メソッドの実装
まず、http.RoundTripper
インターフェースの具体的な定義を見てみましょう 。
type RoundTripper interface {
RoundTrip(*Request) (*Response, error)
}
上記のコードからわかるように、http.RoundTripper
インターフェースは非常に単純で、という名前のRoundTrip
メソッドのみを定義し ます。RoundTrip()
このメソッドは、独立したHTTPトランザクションを実行し、着信*Request
要求値をパラメーターとして受け入れ 、対応する*Response
応答値と値を返すために 使用され error
ます。
特定のRoundTrip()
メソッドを実装する場合 、この関数でHTTP応答情報を解析しようとしないでください。応答が成功した場合、返されるHTTPステータスコードに関係なくerror
、値はである必要があります nil
。サーバーからの応答を正常に取得できない場合は、error
ゼロ以外の値である必要があります。同様に、RoundTrip()
リダイレクト、認証、Cookieなどのプロトコルレベルの詳細を処理しようとしないでください 。
必要がない場合は、RoundTrip()
受信リクエストオブジェクト(*Request
)をメソッドで書き換えないでください 。リクエストのコンテンツ(URLやヘッダーなど)は、に渡されるRoundTrip()
前に整理して初期化する必要が あります。
RoundTrip()
メソッドを実装するすべてのタイプは 、http.RoundTripper
インターフェースを実装し ます。http.Transport
これは、RoundTrip()
メソッドを実装してからインターフェースを実装する メソッドです。最下層では、GoはWHATWG FetchAPIを介して 単一のHTTP要求応答トランザクションを 実装します。
func (t *Transport) RoundTrip(req *Request) (*Response, error) {
if useFakeNetwork() {
return t.roundTrip(req)
}
ac := js.Global().Get("AbortController")
if ac != js.Undefined() {
// Some browsers that support WASM don't necessarily support
// the AbortController. See
// https://developer.mozilla.org/en-US/docs/Web/API/AbortController#Browser_compatibility.
ac = ac.New()
}
opt := js.Global().Get("Object").New()
// See https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch
// for options available.
opt.Set("method", req.Method)
opt.Set("credentials", "same-origin")
if h := req.Header.Get(jsFetchCreds); h != "" {
opt.Set("credentials", h)
req.Header.Del(jsFetchCreds)
}
if h := req.Header.Get(jsFetchMode); h != "" {
opt.Set("mode", h)
req.Header.Del(jsFetchMode)
}
if ac != js.Undefined() {
opt.Set("signal", ac.Get("signal"))
}
headers := js.Global().Get("Headers").New()
for key, values := range req.Header {
for _, value := range values {
headers.Call("append", key, value)
}
}
opt.Set("headers", headers)
if req.Body != nil {
// TODO(johanbrandhorst): Stream request body when possible.
// See https://bugs.chromium.org/p/chromium/issues/detail?id=688906 for Blink issue.
// See https://bugzilla.mozilla.org/show_bug.cgi?id=1387483 for Firefox issue.
// See https://github.com/web-platform-tests/wpt/issues/7693 for WHATWG tests issue.
// See https://developer.mozilla.org/en-US/docs/Web/API/Streams_API for more details on the Streams API
// and browser support.
body, err := ioutil.ReadAll(req.Body)
if err != nil {
req.Body.Close() // RoundTrip must always close the body, including on errors.
return nil, err
}
req.Body.Close()
a := js.TypedArrayOf(body)
defer a.Release()
opt.Set("body", a)
}
respPromise := js.Global().Call("fetch", req.URL.String(), opt)
var (
respCh = make(chan *Response, 1)
errCh = make(chan error, 1)
)
success := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
result := args[0]
header := Header{}
// https://developer.mozilla.org/en-US/docs/Web/API/Headers/entries
headersIt := result.Get("headers").Call("entries")
for {
n := headersIt.Call("next")
if n.Get("done").Bool() {
break
}
pair := n.Get("value")
key, value := pair.Index(0).String(), pair.Index(1).String()
ck := CanonicalHeaderKey(key)
header[ck] = append(header[ck], value)
}
contentLength := int64(0)
if cl, err := strconv.ParseInt(header.Get("Content-Length"), 10, 64); err == nil {
contentLength = cl
}
b := result.Get("body")
var body io.ReadCloser
// The body is undefined when the browser does not support streaming response bodies (Firefox),
// and null in certain error cases, i.e. when the request is blocked because of CORS settings.
if b != js.Undefined() && b != js.Null() {
body = &streamReader{stream: b.Call("getReader")}
} else {
// Fall back to using ArrayBuffer
// https://developer.mozilla.org/en-US/docs/Web/API/Body/arrayBuffer
body = &arrayReader{arrayPromise: result.Call("arrayBuffer")}
}
select {
case respCh <- &Response{
Status: result.Get("status").String() + " " + StatusText(result.Get("status").Int()),
StatusCode: result.Get("status").Int(),
Header: header,
ContentLength: contentLength,
Body: body,
Request: req,
}:
case <-req.Context().Done():
}
return nil
})
defer success.Release()
failure := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
err := fmt.Errorf("net/http: fetch() failed: %s", args[0].String())
select {
case errCh <- err:
case <-req.Context().Done():
}
return nil
})
defer failure.Release()
respPromise.Call("then", success, failure)
select {
case <-req.Context().Done():
if ac != js.Undefined() {
// Abort the Fetch request
ac.Call("abort")
}
return nil, req.Context().Err()
case resp := <-respCh:
return resp, nil
case err := <-errCh:
return nil, err
}
}
http.RoundTripper
インターフェイスを実装 するコードは通常、複数のゴルーチンで同時に実行する必要があるため、実装コードのスレッドセーフを確保する必要があります。
上記は、http.Client
基盤となる実装とそのデフォルトの実装の コアコンポーネントです。焦点は http.Transport
です。これは、HTTPトランザクションの完全なプロセスを定義します。カスタマイズ Transport
を通じてHTTPクライアントリクエストをカスタマイズできます。理解してください。実際の開発では、通常、低レベルの開発とカスタマイズを行う必要がない限り、前のチュートリアルで提供されたいくつかのメソッドを呼び出すだけで済みます。それ以外の場合は、通常、これらは関与しません。