golang http/transport 程式碼分析

gaorong404發表於2019-05-21

請結合原始碼閱讀,本文只是總結一下,原始碼裡有詳細的註釋。基於:go1.12.4

http.Client 表示一個http client端,用來處理HTTP相關的工作,例如cookies, redirect, timeout等工作,其內部包含一個Transport,為RountTripper interface型別。

type Client struct {
    // Transport specifies the mechanism by which individual
    // HTTP requests are made.
    // If nil, DefaultTransport is used.
    Transport RoundTripper
    ...
}

RountTripper定義了執行一次http請求時,如何根據reueqest返回response,它必須是支援併發的一個結構體,允許多個groutine同時呼叫:

type RoundTripper interface {
    RoundTrip(*Request) (*Response, error)
}

如果不給http.Client顯式指定RoundTripper則會建立一個預設的DefaultTransport。Transport是用來儲存多個請求過程中的一些狀態,用來快取tcp連線,客戶可以重用這些連線,防止每次新建,transport需要同時支援http, https, 並且需要http/1.1, http/2。DefaultTransport預設就支援http/2.0,如果需要顯式指定則呼叫ConfigureTransport

transport必須實現interface中的roundTrip方法:


// roundTrip implements a RoundTripper over HTTP.
func (t *Transport) roundTrip(req *Request) (*Response, error) {
    ...
    for {
        select {
        case <-ctx.Done():
            req.closeBody()
            return nil, ctx.Err()
        default:
        }

        // treq gets modified by roundTrip, so we need to recreate for each retry.
        treq := &transportRequest{Request: req, trace: trace}
        cm, err := t.connectMethodForRequest(treq)
        if err != nil {
            req.closeBody()
            return nil, err
        }

        // 獲取一個連線
        // Get the cached or newly-created connection to either the
        // host (for http or https), the http proxy, or the http proxy
        // pre-CONNECTed to https server. In any case, we'll be ready
        // to send it requests.
        pconn, err := t.getConn(treq, cm)
        if err != nil {
            t.setReqCanceler(req, nil)
            req.closeBody()
            return nil, err
        }

        var resp *Response
        if pconn.alt != nil {
            // HTTP/2 path.
            t.decHostConnCount(cm.key()) // don't count cached http2 conns toward conns per host
            t.setReqCanceler(req, nil)   // not cancelable with CancelRequest
            resp, err = pconn.alt.RoundTrip(req)
        } else {
            // 開始呼叫該pconn的rountTrip方法取得response
            resp, err = pconn.roundTrip(treq)
        }
        if err == nil {
            return resp, nil
        }
        if !pconn.shouldRetryRequest(req, err) {
            // Issue 16465: return underlying net.Conn.Read error from peek,
            // as we've historically done.
            if e, ok := err.(transportReadFromServerError); ok {
                err = e.err
            }
            return nil, err
        }
        testHookRoundTripRetried()

        // Rewind the body if we're able to.
        if req.GetBody != nil {
            newReq := *req
            var err error
            newReq.Body, err = req.GetBody()
            if err != nil {
                return nil, err
            }
            req = &newReq
        }
    }
}

roundTrip其實就是通過getConn用於獲取一個連線persisConn並呼叫其roundTrip方法返回repsonse。其中getConn的實現如下:

// getConn dials and creates a new persistConn to the target as
// specified in the connectMethod. This includes doing a proxy CONNECT
// and/or setting up TLS.  If this doesn't return an error, the persistConn
// is ready to write requests to.
func (t *Transport) getConn(treq *transportRequest, cm connectMethod) (*persistConn, error) {
    req := treq.Request
    trace := treq.trace
    ctx := req.Context()
    if trace != nil && trace.GetConn != nil {
        trace.GetConn(cm.addr())
    }
    // 首先從idleConn空閒連線池中嘗試獲取閒置的連線
    if pc, idleSince := t.getIdleConn(cm); pc != nil {
        if trace != nil && trace.GotConn != nil {
            trace.GotConn(pc.gotIdleConnTrace(idleSince))
        }
        // set request canceler to some non-nil function so we
        // can detect whether it was cleared between now and when
        // we enter roundTrip
        t.setReqCanceler(req, func(error) {})
        return pc, nil
    }

    type dialRes struct {
        pc  *persistConn
        err error
    }
    dialc := make(chan dialRes)     // 連線建立完成之後會從該管道非同步通知
    cmKey := cm.key()               // 標識一個連線的key

    // Copy these hooks so we don't race on the postPendingDial in
    // the goroutine we launch. Issue 11136.
    testHookPrePendingDial := testHookPrePendingDial
    testHookPostPendingDial := testHookPostPendingDial

    handlePendingDial := func() {
        testHookPrePendingDial()
        go func() {
            if v := <-dialc; v.err == nil {
                t.putOrCloseIdleConn(v.pc)
            } else {
                t.decHostConnCount(cmKey)
            }
            testHookPostPendingDial()
        }()
    }

    cancelc := make(chan error, 1)
    t.setReqCanceler(req, func(err error) { cancelc <- err })

    // 一邊增加記錄的連線數,一邊嘗試獲取連線,一邊監聽取消事件
    if t.MaxConnsPerHost > 0 {
        select {
        case <-t.incHostConnCount(cmKey):
            // count below conn per host limit; proceed
        case pc := <-t.getIdleConnCh(cm):
            if trace != nil && trace.GotConn != nil {
                trace.GotConn(httptrace.GotConnInfo{Conn: pc.conn, Reused: pc.isReused()})
            }
            return pc, nil
        case <-req.Cancel:
            return nil, errRequestCanceledConn
        case <-req.Context().Done():
            return nil, req.Context().Err()
        case err := <-cancelc:
            if err == errRequestCanceled {
                err = errRequestCanceledConn
            }
            return nil, err
        }
    }

    // 非同步發起連線操作
    go func() {
        pc, err := t.dialConn(ctx, cm)
        dialc <- dialRes{pc, err}
    }()

    // 監聽多個事件來源
    // 1. 新建立成功
    // 2. 其它連線結束,閒置連線池中有連線可以複用
    // 3. 連線被取消
    // 第一種情況和第二種情況誰先成功就直接返回
    // 除了新建連線成功,其它所有情況都需要處理呼叫`handlePendingDial`,該函式決定新建連線返回後該如何處理
    idleConnCh := t.getIdleConnCh(cm)
    select {
    case v := <-dialc:  // 如果新建連線結束後會從該channel傳送過來
        // Our dial finished.
        if v.pc != nil {
            if trace != nil && trace.GotConn != nil && v.pc.alt == nil {
                trace.GotConn(httptrace.GotConnInfo{Conn: v.pc.conn})
            }
            return v.pc, nil
        }
        // Our dial failed. See why to return a nicer error
        // value.
        t.decHostConnCount(cmKey)
        select {
        case <-req.Cancel:
            // It was an error due to cancelation, so prioritize that
            // error value. (Issue 16049)
            return nil, errRequestCanceledConn
        case <-req.Context().Done():
            return nil, req.Context().Err()
        case err := <-cancelc:
            if err == errRequestCanceled {
                err = errRequestCanceledConn
            }
            return nil, err
        default:
            // It wasn't an error due to cancelation, so
            // return the original error message:
            return nil, v.err
        }
    case pc := <-idleConnCh:        // 如果從空閒連線池中有了可用的連線,直接返回
        // Another request finished first and its net.Conn
        // became available before our dial. Or somebody
        // else's dial that they didn't use.
        // But our dial is still going, so give it away
        // when it finishes:
        handlePendingDial()
        if trace != nil && trace.GotConn != nil {
            trace.GotConn(httptrace.GotConnInfo{Conn: pc.conn, Reused: pc.isReused()})
        }
        return pc, nil
    case <-req.Cancel:
        handlePendingDial()
        return nil, errRequestCanceledConn
    case <-req.Context().Done():
        handlePendingDial()
        return nil, req.Context().Err()
    case err := <-cancelc:
        handlePendingDial()
        if err == errRequestCanceled {
            err = errRequestCanceledConn
        }
        return nil, err
    }
}

getConn首先從空閒連線池中獲取連線,如果沒有,則新建連線。在新建過程中,如果連線池中有空閒連線則也複用空閒連線。
繼續看一下dialConn是如何建立連線的:

func (t *Transport) dialConn(ctx context.Context, cm connectMethod) (*persistConn, error) {
    // 注意這裡初始化的各種channle
    pconn := &persistConn{
        t:             t,
        cacheKey:      cm.key(),
        reqch:         make(chan requestAndChan, 1),    // 用於給readLoop傳送request
        writech:       make(chan writeRequest, 1),      // 用於給writeLoop傳送request
        closech:       make(chan struct{}),             // 當連線關閉是用於傳遞資訊
        writeErrCh:    make(chan error, 1),             // 由writeLoop返回給roundTrip錯誤資訊
        writeLoopDone: make(chan struct{}),             // 當writeLoop結束的時候會關閉該channel
    }
    trace := httptrace.ContextClientTrace(ctx)
    wrapErr := func(err error) error {
        if cm.proxyURL != nil {
            // Return a typed error, per Issue 16997
            return &net.OpError{Op: "proxyconnect", Net: "tcp", Err: err}
        }
        return err
    }

    conn, err := t.dial(ctx, "tcp", cm.addr())
    if err != nil {
        return nil, wrapErr(err)
    }
    pconn.conn = conn


    // 包裝一個請求成另一個結構體,方便後續處理
    if t.MaxConnsPerHost > 0 {
        pconn.conn = &connCloseListener{Conn: pconn.conn, t: t, cmKey: pconn.cacheKey}
    }
    // 包裝讀寫conn並開啟讀取和寫入groutine
    pconn.br = bufio.NewReader(pconn)   
    pconn.bw = bufio.NewWriter(persistConnWriter{pconn})
    go pconn.readLoop()
    go pconn.writeLoop()
    return pconn, nil
}

可以看到首先呼叫dial函式,獲取一個conn物件,然後封裝為pconn的, 啟動readLoop和wirteLoop後將該pconn返回。
以readLoop為例,看看是如何從一個pc中讀取response的:

func (pc *persistConn) readLoop() {
    // 預設是失敗,如果失敗則進行處理,移除該連線,使用defer語句表示在程式退出的時候執行,也就是說該groutine在正常情況下不會退出,是個死迴圈,通過channel與其它groutine通訊,處理請求
    closeErr := errReadLoopExiting // default value, if not changed below
    defer func() {
        pc.close(closeErr)
        pc.t.removeIdleConn(pc)
    }()

    // 嘗試將該連線重新返回閒置連線池
    tryPutIdleConn := func(trace *httptrace.ClientTrace) bool {
        if err := pc.t.tryPutIdleConn(pc); err != nil {
            closeErr = err
            if trace != nil && trace.PutIdleConn != nil && err != errKeepAlivesDisabled {
                trace.PutIdleConn(err)
            }
            return false
        }
        if trace != nil && trace.PutIdleConn != nil {
            trace.PutIdleConn(nil)
        }
        return true
    }

    // 用來保證先後次序,先歸還連線再讀取response.Body
    // eofc is used to block caller goroutines reading from Response.Body
    // at EOF until this goroutines has (potentially) added the connection
    // back to the idle pool.
    eofc := make(chan struct{})
    defer close(eofc) // unblock reader on errors

    // Read this once, before loop starts. (to avoid races in tests)
    testHookMu.Lock()
    testHookReadLoopBeforeNextRead := testHookReadLoopBeforeNextRead
    testHookMu.Unlock()

    alive := true
    for alive {
        pc.readLimit = pc.maxHeaderResponseSize()
        _, err := pc.br.Peek(1)

        pc.mu.Lock()
        if pc.numExpectedResponses == 0 {
            pc.readLoopPeekFailLocked(err)
            pc.mu.Unlock()
            return
        }
        pc.mu.Unlock()

        // 獲取一個新連線來處理
        rc := <-pc.reqch
        trace := httptrace.ContextClientTrace(rc.req.Context())

        var resp *Response
        if err == nil {
            // 讀取返回結果
            resp, err = pc.readResponse(rc, trace)
        } else {
            err = transportReadFromServerError{err}
            closeErr = err
        }

        if err != nil {
            if pc.readLimit <= 0 {
                err = fmt.Errorf("net/http: server response headers exceeded %d bytes; aborted", pc.maxHeaderResponseSize())
            }

            select {
            case rc.ch <- responseAndError{err: err}:
            case <-rc.callerGone:
                return
            }
            return
        }
        pc.readLimit = maxInt64 // effictively no limit for response bodies

        pc.mu.Lock()
        pc.numExpectedResponses--
        pc.mu.Unlock()

        bodyWritable := resp.bodyIsWritable()
        hasBody := rc.req.Method != "HEAD" && resp.ContentLength != 0

        if resp.Close || rc.req.Close || resp.StatusCode <= 199 || bodyWritable {
            // Don't do keep-alive on error if either party requested a close
            // or we get an unexpected informational (1xx) response.
            // StatusCode 100 is already handled above.
            alive = false
        }

        if !hasBody || bodyWritable {
            pc.t.setReqCanceler(rc.req, nil)

            // Put the idle conn back into the pool before we send the response
            // so if they process it quickly and make another request, they'll
            // get this same conn. But we use the unbuffered channel 'rc'
            // to guarantee that persistConn.roundTrip got out of its select
            // potentially waiting for this persistConn to close.
            // but after
            alive = alive &&
                !pc.sawEOF &&
                pc.wroteRequest() &&
                tryPutIdleConn(trace)

            if bodyWritable {
                closeErr = errCallerOwnsConn
            }

            select {
            case rc.ch <- responseAndError{res: resp}:
            case <-rc.callerGone:
                return
            }

            // Now that they've read from the unbuffered channel, they're safely
            // out of the select that also waits on this goroutine to die, so
            // we're allowed to exit now if needed (if alive is false)
            testHookReadLoopBeforeNextRead()
            continue
        }

        // bodyEOFSignal實現了io.ReadCloser interface, 保證讀取的時候,該response已經收到了eof
        waitForBodyRead := make(chan bool, 2)
        body := &bodyEOFSignal{
            body: resp.Body,
            earlyCloseFn: func() error {
                waitForBodyRead <- false
                <-eofc // will be closed by deferred call at the end of the function
                return nil

            },
            fn: func(err error) error {
                isEOF := err == io.EOF
                waitForBodyRead <- isEOF
                if isEOF {
                    <-eofc // see comment above eofc declaration
                } else if err != nil {
                    if cerr := pc.canceled(); cerr != nil {
                        return cerr
                    }
                }
                return err
            },
        }

        resp.Body = body
        if rc.addedGzip && strings.EqualFold(resp.Header.Get("Content-Encoding"), "gzip") {
            resp.Body = &gzipReader{body: body}
            resp.Header.Del("Content-Encoding")
            resp.Header.Del("Content-Length")
            resp.ContentLength = -1
            resp.Uncompressed = true
        }

        select {
        // 將分裝好的repsponse傳送回去
        case rc.ch <- responseAndError{res: resp}:
        case <-rc.callerGone:
            return
        }

        // Before looping back to the top of this function and peeking on
        // the bufio.Reader, wait for the caller goroutine to finish
        // reading the response body. (or for cancelation or death)
        select {
        case bodyEOF := <-waitForBodyRead:
            pc.t.setReqCanceler(rc.req, nil) // before pc might return to idle pool
            alive = alive &&
                bodyEOF &&
                !pc.sawEOF &&
                pc.wroteRequest() &&
                tryPutIdleConn(trace)
            if bodyEOF {
                eofc <- struct{}{}      //前面所有檢查完畢,通知對端開始讀取
            }
        case <-rc.req.Cancel:
            alive = false
            pc.t.CancelRequest(rc.req)
        case <-rc.req.Context().Done():
            alive = false
            pc.t.cancelRequest(rc.req, rc.req.Context().Err())
        case <-pc.closech:
            alive = false
        }

        testHookReadLoopBeforeNextRead()
    }
}

上面readLoop中從一個channel中讀取出來需要處理的request, 然後讀取readResponse並通過管道返回回去。那接受到的request是從哪個地方傳送過來的吶?
回到最開始的Transport.roundTrip函式裡,它先呼叫getConn返回一個pconn後然後呼叫pconn.roundTrip方法,就是在這裡面傳送的,我們看看:

func (pc *persistConn) roundTrip(req *transportRequest) (resp *Response, err error) {
    testHookEnterRoundTrip()
    if !pc.t.replaceReqCanceler(req.Request, pc.cancelRequest) {
        pc.t.putOrCloseIdleConn(pc)
        return nil, errRequestCanceled
    }
    pc.mu.Lock()
    pc.numExpectedResponses++
    headerFn := pc.mutateHeaderFunc
    pc.mu.Unlock()

    if headerFn != nil {
        headerFn(req.extraHeaders())
    }

    ...

    var continueCh chan struct{}
    if req.ProtoAtLeast(1, 1) && req.Body != nil && req.expectsContinue() {
        continueCh = make(chan struct{}, 1)
    }

    if pc.t.DisableKeepAlives && !req.wantsClose() {
        req.extraHeaders().Set("Connection", "close")
    }

    gone := make(chan struct{})
    defer close(gone)

    defer func() {
        if err != nil {
            pc.t.setReqCanceler(req.Request, nil)
        }
    }()

    const debugRoundTrip = false

    // 通過writech傳送該請求
    // Write the request concurrently with waiting for a response,
    // in case the server decides to reply before reading our full
    // request body.
    startBytesWritten := pc.nwrite
    writeErrCh := make(chan error, 1)
    pc.writech <- writeRequest{req, writeErrCh, continueCh}

    resc := make(chan responseAndError)
    // 傳送當前正在處理的請求給readLoop,readLoop中從channle中讀取出該請求,進行readResponse
    // 其中的requestAndChan.ch是response返回的channel
    pc.reqch <- requestAndChan{
        req:        req.Request,
        ch:         resc,
        addedGzip:  requestedGzip,
        continueCh: continueCh,
        callerGone: gone,
    }

    var respHeaderTimer <-chan time.Time
    cancelChan := req.Request.Cancel
    ctxDoneChan := req.Context().Done()
    for {
        testHookWaitResLoop()
        select {
        case err := <-writeErrCh:   // writeLoop出現錯誤
            if debugRoundTrip {
                req.logf("writeErrCh resv: %T/%#v", err, err)
            }
            if err != nil {
                pc.close(fmt.Errorf("write error: %v", err))
                return nil, pc.mapRoundTripError(req, startBytesWritten, err)
            }
            if d := pc.t.ResponseHeaderTimeout; d > 0 {
                if debugRoundTrip {
                    req.logf("starting timer for %v", d)
                }
                timer := time.NewTimer(d)
                defer timer.Stop() // prevent leaks
                respHeaderTimer = timer.C
            }
        case <-pc.closech:
            if debugRoundTrip {
                req.logf("closech recv: %T %#v", pc.closed, pc.closed)
            }
            return nil, pc.mapRoundTripError(req, startBytesWritten, pc.closed)
        case <-respHeaderTimer:
            if debugRoundTrip {
                req.logf("timeout waiting for response headers.")
            }
            pc.close(errTimeout)
            return nil, errTimeout
        case re := <-resc:          // readLoop會將讀取的結果通過resc channel傳送回來
            if (re.res == nil) == (re.err == nil) {
                panic(fmt.Sprintf("internal error: exactly one of res or err should be set; nil=%v", re.res == nil))
            }
            if debugRoundTrip {
                req.logf("resc recv: %p, %T/%#v", re.res, re.err, re.err)
            }
            if re.err != nil {
                return nil, pc.mapRoundTripError(req, startBytesWritten, re.err)
            }
            return re.res, nil
        case <-cancelChan:
            pc.t.CancelRequest(req.Request)
            cancelChan = nil
        case <-ctxDoneChan:
            pc.t.cancelRequest(req.Request, req.Context().Err())
            cancelChan = nil
            ctxDoneChan = nil
        }
    }
}

該函式中會將request進行封裝,然後分別通過channel傳送給readLoop和writeLoop,並監聽各個channel的事件,分別進行不同的處理。

整體流程走完之後,我們回顧一下兩個比較重要的結構體:persistConnTransport的成員

// persistConn wraps a connection, usually a persistent one
// (but may be used for non-keep-alive requests as well)
type persistConn struct {
    // alt optionally specifies the TLS NextProto RoundTripper.
    // This is used for HTTP/2 today and future protocols later.
    // If it's non-nil, the rest of the fields are unused.
    alt RoundTripper

    t         *Transport
    cacheKey  connectMethodKey        // 當前連線對應的key, 也是idleConns map中的key
    conn      net.Conn                  // 被封裝的conn物件
    tlsState  *tls.ConnectionState  
    br        *bufio.Reader       // from conn      // bufio.Reader 物件,封裝conn
    bw        *bufio.Writer       // to conn        // bufio.Writer 物件,封裝conn
    nwrite    int64               // bytes written  // 記錄寫入的長度
    reqch     chan requestAndChan // written by roundTrip; read by readLoop // rountTrip在建立一個請求的時候會講請求通過該chenel傳送給readLoop,  readLoop後面解釋
    writech   chan writeRequest   // written by roundTrip; read by writeLoop    // writeTrop 從中讀取寫入請求並執行寫入
    closech   chan struct{}       // closed when conn closed                    // 連線關閉的時候從該channle通訊
    isProxy   bool
    sawEOF    bool  // whether we've seen EOF from conn; owned by readLoop
    readLimit int64 // bytes allowed to be read; owned by readLoop
    // writeErrCh passes the request write error (usually nil)
    // from the writeLoop goroutine to the readLoop which passes
    // it off to the res.Body reader, which then uses it to decide
    // whether or not a connection can be reused. Issue 7569.
    writeErrCh chan error                                       // 

    writeLoopDone chan struct{} // closed when write loop ends

    // Both guarded by Transport.idleMu:
    idleAt    time.Time   // time it last become idle
    idleTimer *time.Timer // holding an AfterFunc to close it

    mu                   sync.Mutex // guards following fields
    numExpectedResponses int            //表示當期期望的返回response數目
    closed               error // set non-nil when conn is closed, before closech is closed
    canceledErr          error // set non-nil if conn is canceled
    broken               bool  // an error has happened on this connection; marked broken so it's not reused.
    reused               bool  // whether conn has had successful request/response and is being reused.
    // mutateHeaderFunc is an optional func to modify extra
    // headers on each outbound request before it's written. (the
    // original Request given to RoundTrip is not modified)
    mutateHeaderFunc func(Header)
}
type Transport struct {
    idleMu     sync.Mutex       // 互斥鎖,用於保護下面空閒連線池
    wantIdle   bool                                // user has requested to close all idle conns// 標識是否idle
    idleConn   map[connectMethodKey][]*persistConn // most recently used at end                 // 空閒連線池
    idleConnCh map[connectMethodKey]chan *persistConn    // 用於在groutine中間傳遞空閒的連線,一般用於當連線池中沒有連線,但是還有請求需要處理,當連線池中出現空閒連線時通過該channel通知
    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
}

如此便是整個流程,其實還是很清晰的,最後總結一下:
tranport用來建立一個連線,其中維護了一個空閒連線池idleConn map[connectMethodKey][]*persistConn,其中的每個成員都是一個persistConn物件,persistConn是個具體的連線例項,包含了連線的上下文,會啟動兩個groutine分別執行readLoopwriteLoop, 每當transport呼叫roundTrip的時候,就會從連線池中選擇一個空閒的persistConn,然後呼叫其roundTrip方法,將讀寫請求通過channel分別傳送到readLoopwriteLoop中,然後會進行select各個channel的資訊,包括連線關閉,請求超時,writeLoop出錯, readLoop返回讀取結果等。在writeLoop中傳送請求,在readLoop中獲取response並通過channe返回給roundTrip函式中,並再次將自己加入到idleConn中,等待下次請求到來。

相關文章