Go HTTPプログラミング(2):http.Clientの基盤となる実装の構造

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があります。指定されていない場合  、デフォルトの実装Transporthttp.DefaultTransportデフォルトで使用されます。たとえばhttp.DefaultClient 、これが当てはまりhttp.DefaultTransport 、基礎となる実装について詳しく説明し ます。 後で 

CheckRedirect 関数は、リダイレクトを処理するための戦略を定義するために使用されます。ときに HTTP要求を送信する使用Get() または Head()方法HTTPのデフォルト・クライアントによって提供さを 応答ステータスコードがある場合、  30x (例えば 301302 等)、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 と MaxConnsPerHostMaxIdleConnsPerHost デフォルト値と、によって 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クライアントリクエストをカスタマイズできます。理解してください。実際の開発では、通常、低レベルの開発とカスタマイズを行う必要がない限り、前のチュートリアルで提供されたいくつかのメソッドを呼び出すだけで済みます。それ以外の場合は、通常、これらは関与しません。

おすすめ

転載: blog.csdn.net/wxy_csdn_world/article/details/107444870