亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

快速掌握Go 語(yǔ)言 HTTP 標(biāo)準(zhǔn)庫(kù)的實(shí)現(xiàn)方法

 更新時(shí)間:2022年07月25日 16:14:14   作者:luozhiyun  
基于HTTP構(gòu)建的服務(wù)標(biāo)準(zhǔn)模型包括兩個(gè)端,客戶端(Client)和服務(wù)端(Server),這篇文章主要介紹了Go 語(yǔ)言HTTP標(biāo)準(zhǔn)庫(kù)的實(shí)現(xiàn)方法,需要的朋友可以參考下

本篇文章來(lái)分析一下 Go 語(yǔ)言 HTTP 標(biāo)準(zhǔn)庫(kù)是如何實(shí)現(xiàn)的。

本文使用的go的源碼1.15.7

基于HTTP構(gòu)建的服務(wù)標(biāo)準(zhǔn)模型包括兩個(gè)端,客戶端(Client)和服務(wù)端(Server)。HTTP 請(qǐng)求從客戶端發(fā)出,服務(wù)端接受到請(qǐng)求后進(jìn)行處理然后將響應(yīng)返回給客戶端。所以http服務(wù)器的工作就在于如何接受來(lái)自客戶端的請(qǐng)求,并向客戶端返回響應(yīng)。

一個(gè)典型的 HTTP 服務(wù)應(yīng)該如圖所示:

HTTP client

在 Go 中可以直接通過(guò) HTTP 包的 Get 方法來(lái)發(fā)起相關(guān)請(qǐng)求數(shù)據(jù),一個(gè)簡(jiǎn)單例子:

func main() {
    resp, err := http.Get("http://httpbin.org/get?name=luozhiyun&age=27")
    if err != nil {
        fmt.Println(err)
        return
    }
    defer resp.Body.Close()
    body, _ := ioutil.ReadAll(resp.Body)
    fmt.Println(string(body))
}

我們下面通過(guò)這個(gè)例子來(lái)進(jìn)行分析。

HTTP 的 Get 方法會(huì)調(diào)用到 DefaultClient 的 Get 方法,DefaultClient 是 Client 的一個(gè)空實(shí)例,所以最后會(huì)調(diào)用到 Client 的 Get 方法:

Client 結(jié)構(gòu)體

type Client struct { 
    Transport RoundTripper 
    CheckRedirect func(req *Request, via []*Request) error 
    Jar CookieJar 
    Timeout time.Duration
}

Client 結(jié)構(gòu)體總共由四個(gè)字段組成:

Transport:表示 HTTP 事務(wù),用于處理客戶端的請(qǐng)求連接并等待服務(wù)端的響應(yīng);

CheckRedirect:用于指定處理重定向的策略;

Jar:用于管理和存儲(chǔ)請(qǐng)求中的 cookie;

Timeout:指定客戶端請(qǐng)求的最大超時(shí)時(shí)間,該超時(shí)時(shí)間包括連接、任何的重定向以及讀取相應(yīng)的時(shí)間;

初始化請(qǐng)求

func (c *Client) Get(url string) (resp *Response, err error) {
    // 根據(jù)方法名、URL 和請(qǐng)求體構(gòu)建請(qǐng)求
    req, err := NewRequest("GET", url, nil)
    if err != nil {
        return nil, err
    }
    // 執(zhí)行請(qǐng)求
    return c.Do(req)
}

我們要發(fā)起一個(gè)請(qǐng)求首先需要根據(jù)請(qǐng)求類型構(gòu)建一個(gè)完整的請(qǐng)求頭、請(qǐng)求體、請(qǐng)求參數(shù)。然后才是根據(jù)請(qǐng)求的完整結(jié)構(gòu)來(lái)執(zhí)行請(qǐng)求。

NewRequest 初始化請(qǐng)求

NewRequest 會(huì)調(diào)用到 NewRequestWithContext 函數(shù)上。這個(gè)函數(shù)會(huì)根據(jù)請(qǐng)求返回一個(gè) Request 結(jié)構(gòu)體,它里面包含了一個(gè) HTTP 請(qǐng)求所有信息。

Request

Request 結(jié)構(gòu)體有很多字段,我這里列舉幾個(gè)大家比較熟悉的字段:

NewRequestWithContext

func NewRequestWithContext(ctx context.Context, method, url string, body io.Reader) (*Request, error) {
    ...
    // parse url
    u, err := urlpkg.Parse(url)
    if err != nil {
        return nil, err
    }
    rc, ok := body.(io.ReadCloser)
    if !ok && body != nil {
        rc = ioutil.NopCloser(body)
    } 
    u.Host = removeEmptyPort(u.Host)
    req := &Request{
        ctx:        ctx,
        Method:     method,
        URL:        u,
        Proto:      "HTTP/1.1",
        ProtoMajor: 1,
        ProtoMinor: 1,
        Header:     make(Header),
        Body:       rc,
        Host:       u.Host,
    } 
    ...
    return req, nil
}

NewRequestWithContext 函數(shù)會(huì)將請(qǐng)求封裝成一個(gè) Request 結(jié)構(gòu)體并返回。

準(zhǔn)備 http 發(fā)送請(qǐng)求

如上圖所示,Client 調(diào)用 Do 方法處理發(fā)送請(qǐng)求最后會(huì)調(diào)用到 send 函數(shù)中。

func (c *Client) send(req *Request, deadline time.Time) (resp *Response, didTimeout func() bool, err error) {
    resp, didTimeout, err = send(req, c.transport(), deadline)
    if err != nil {
        return nil, didTimeout, err
    }
    ...
    return resp, nil, nil
}

Transport

Client 的 send 方法在調(diào)用 send 函數(shù)進(jìn)行下一步的處理前會(huì)先調(diào)用 transport 方法獲取 DefaultTransport 實(shí)例,該實(shí)例如下:

var DefaultTransport RoundTripper = &Transport{
    // 定義 HTTP 代理策略
    Proxy: ProxyFromEnvironment,
    DialContext: (&net.Dialer{
        Timeout:   30 * time.Second,
        KeepAlive: 30 * time.Second,
        DualStack: true,
    }).DialContext,
    ForceAttemptHTTP2:     true,
    // 最大空閑連接數(shù)
    MaxIdleConns:          100,
    // 空閑連接超時(shí)時(shí)間
    IdleConnTimeout:       90 * time.Second,
    // TLS 握手超時(shí)時(shí)間
    TLSHandshakeTimeout:   10 * time.Second,
    ExpectContinueTimeout: 1 * time.Second,
}

Transport 實(shí)現(xiàn) RoundTripper 接口,該結(jié)構(gòu)體會(huì)發(fā)送 http 請(qǐng)求并等待響應(yīng)。

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

從 RoundTripper 接口我們也可以看出,該接口定義的 RoundTrip 方法會(huì)具體的處理請(qǐng)求,處理完畢之后會(huì)響應(yīng) Response。

回到我們上面的 Client 的 send 方法中,它會(huì)調(diào)用 send 函數(shù),這個(gè)函數(shù)主要邏輯都交給 Transport 的 RoundTrip 方法來(lái)執(zhí)行。

RoundTrip 會(huì)調(diào)用到 roundTrip 方法中:

func (t *Transport) roundTrip(req *Request) (*Response, error) {
    t.nextProtoOnce.Do(t.onceSetNextProtoDefaults)
    ctx := req.Context()
    trace := httptrace.ContextClientTrace(ctx) 
    ...  
    for {
        select {
        case <-ctx.Done():
            req.closeBody()
            return nil, ctx.Err()
        default:
        }

        // 封裝請(qǐng)求
        treq := &transportRequest{Request: req, trace: trace, cancelKey: cancelKey} 
        cm, err := t.connectMethodForRequest(treq)
        if err != nil {
            req.closeBody()
            return nil, err
        } 
        // 獲取連接
        pconn, err := t.getConn(treq, cm)
        if err != nil {
            t.setReqCanceler(cancelKey, nil)
            req.closeBody()
            return nil, err
        }

        // 等待響應(yīng)結(jié)果
        var resp *Response
        if pconn.alt != nil {
            // HTTP/2 path.
            t.setReqCanceler(cancelKey, nil) // not cancelable with CancelRequest
            resp, err = pconn.alt.RoundTrip(req)
        } else {
            resp, err = pconn.roundTrip(treq)
        }
        if err == nil {
            resp.Request = origReq
            return resp, nil
        } 
        ...
    }
}

roundTrip 方法會(huì)做兩件事情:

  • 調(diào)用 Transport 的 getConn 方法獲取連接;
  • 在獲取到連接后,調(diào)用 persistConn 的 roundTrip 方法等待請(qǐng)求響應(yīng)結(jié)果;獲取連接 getConn

getConn 有兩個(gè)階段:

調(diào)用 queueForIdleConn 獲取空閑 connection;調(diào)用 queueForDial 等待創(chuàng)建新的 connection;

func (t *Transport) getConn(treq *transportRequest, cm connectMethod) (pc *persistConn, err error) {
    req := treq.Request
    trace := treq.trace
    ctx := req.Context()
    if trace != nil && trace.GetConn != nil {
        trace.GetConn(cm.addr())
    }   
    // 將請(qǐng)求封裝成 wantConn 結(jié)構(gòu)體
    w := &wantConn{
        cm:         cm,
        key:        cm.key(),
        ctx:        ctx,
        ready:      make(chan struct{}, 1),
        beforeDial: testHookPrePendingDial,
        afterDial:  testHookPostPendingDial,
    }
    defer func() {
        if err != nil {
            w.cancel(t, err)
        }
    }()

    // 獲取空閑連接
    if delivered := t.queueForIdleConn(w); delivered {
        pc := w.pc
        ...
        t.setReqCanceler(treq.cancelKey, func(error) {})
        return pc, nil
    }

    // 創(chuàng)建連接
    t.queueForDial(w)

    select {
    // 獲取到連接后進(jìn)入該分支
    case <-w.ready:
        ...
        return w.pc, w.err
    ...
}

獲取空閑連接 queueForIdleConn

成功獲取到空閑 connection:

成功獲取 connection 分為如下幾步:

  • 根據(jù)當(dāng)前的請(qǐng)求的地址去空閑 connection 字典中查看存不存在空閑的 connection 列表;
  • 如果能獲取到空閑的 connection 列表,那么獲取到列表的最后一個(gè) connection;
  • 返回;

獲取不到空閑 connection:

當(dāng)獲取不到空閑 connection 時(shí):

  • 根據(jù)當(dāng)前的請(qǐng)求的地址去空閑 connection 字典中查看存不存在空閑的 connection 列表;
  • 不存在該請(qǐng)求的 connection 列表,那么將該 wantConn 加入到 等待獲取空閑 connection 字典中;

從上面的圖解應(yīng)該就很能看出這一步會(huì)怎么操作了,這里簡(jiǎn)要的分析一下代碼,讓大家更清楚里面的邏輯:

func (t *Transport) queueForIdleConn(w *wantConn) (delivered bool) {
    if t.DisableKeepAlives {
        return false
    }

    t.idleMu.Lock()
    defer t.idleMu.Unlock() 
    t.closeIdle = false

    if w == nil { 
        return false
    }

    // 計(jì)算空閑連接超時(shí)時(shí)間
    var oldTime time.Time
    if t.IdleConnTimeout > 0 {
        oldTime = time.Now().Add(-t.IdleConnTimeout)
    }
    // Look for most recently-used idle connection.
    // 找到key相同的 connection 列表
    if list, ok := t.idleConn[w.key]; ok {
        stop := false
        delivered := false
        for len(list) > 0 && !stop {
            // 找到connection列表最后一個(gè)
            pconn := list[len(list)-1] 
            // 檢查這個(gè) connection 是不是等待太久了
            tooOld := !oldTime.IsZero() && pconn.idleAt.Round(0).Before(oldTime)
            if tooOld { 
                go pconn.closeConnIfStillIdle()
            }
            // 該 connection 被標(biāo)記為 broken 或 閑置太久 continue
            if pconn.isBroken() || tooOld { 
                list = list[:len(list)-1]
                continue
            }
            // 嘗試將該 connection 寫入到 w 中
            delivered = w.tryDeliver(pconn, nil)
            if delivered {
                // 操作成功,需要將 connection 從空閑列表中移除
                if pconn.alt != nil { 
                } else { 
                    t.idleLRU.remove(pconn)
                    list = list[:len(list)-1]
                }
            }
            stop = true
        }
        if len(list) > 0 {
            t.idleConn[w.key] = list
        } else {
            // 如果該 key 對(duì)應(yīng)的空閑列表不存在,那么將該key從字典中移除
            delete(t.idleConn, w.key)
        }
        if stop {
            return delivered
        }
    } 
    // 如果找不到空閑的 connection
    if t.idleConnWait == nil {
        t.idleConnWait = make(map[connectMethodKey]wantConnQueue)
    }
  // 將該 wantConn 加入到 等待獲取空閑 connection 字典中
    q := t.idleConnWait[w.key] 
    q.cleanFront()
    q.pushBack(w)
    t.idleConnWait[w.key] = q
    return false
}

上面的注釋已經(jīng)很清楚了,我這里就不再解釋了。

建立連接 queueForDial

在獲取不到空閑連接之后,會(huì)嘗試去建立連接,從上面的圖大致可以看到,總共分為以下幾個(gè)步驟:

  • 在調(diào)用 queueForDial 方法的時(shí)候會(huì)校驗(yàn) MaxConnsPerHost 是否未設(shè)置或已達(dá)上限;
  • 檢驗(yàn)不通過(guò)則將當(dāng)前的請(qǐng)求放入到 connsPerHostWait 等待字典中;
  • 如果校驗(yàn)通過(guò)那么會(huì)異步的調(diào)用 dialConnFor 方法創(chuàng)建連接;

dialConnFor 方法首先會(huì)調(diào)用 dialConn 方法創(chuàng)建 TCP 連接,然后啟動(dòng)兩個(gè)異步線程來(lái)處理讀寫數(shù)據(jù),然后調(diào)用 tryDeliver 將連接綁定到 wantConn 上面。

下面進(jìn)行代碼分析:

func (t *Transport) queueForDial(w *wantConn) {
    w.beforeDial()
    // 小于零說(shuō)明無(wú)限制,異步建立連接
    if t.MaxConnsPerHost <= 0 {
        go t.dialConnFor(w)
        return
    }

    t.connsPerHostMu.Lock()
    defer t.connsPerHostMu.Unlock()
    // 每個(gè) host 建立的連接數(shù)沒達(dá)到上限,異步建立連接
    if n := t.connsPerHost[w.key]; n < t.MaxConnsPerHost {
        if t.connsPerHost == nil {
            t.connsPerHost = make(map[connectMethodKey]int)
        }
        t.connsPerHost[w.key] = n + 1
        go t.dialConnFor(w)
        return
    }
    //每個(gè) host 建立的連接數(shù)已達(dá)到上限,需要進(jìn)入等待隊(duì)列
    if t.connsPerHostWait == nil {
        t.connsPerHostWait = make(map[connectMethodKey]wantConnQueue)
    }
    q := t.connsPerHostWait[w.key]
    q.cleanFront()
    q.pushBack(w)
    t.connsPerHostWait[w.key] = q
}

這里主要進(jìn)行參數(shù)校驗(yàn),如果最大連接數(shù)限制為零,亦或是每個(gè) host 建立的連接數(shù)沒達(dá)到上限,那么直接異步建立連接。

dialConnFor

func (t *Transport) dialConnFor(w *wantConn) {
    defer w.afterDial()
    // 建立連接
    pc, err := t.dialConn(w.ctx, w.cm)
    // 連接綁定 wantConn
    delivered := w.tryDeliver(pc, err)
    // 建立連接成功,但是綁定 wantConn 失敗
    // 那么將該連接放置到空閑連接字典或調(diào)用 等待獲取空閑 connection 字典 中的元素執(zhí)行
    if err == nil && (!delivered || pc.alt != nil) { 
        t.putOrCloseIdleConn(pc)
    }
    if err != nil {
        t.decConnsPerHost(w.key)
    }
}

dialConnFor 會(huì)調(diào)用 dialConn 進(jìn)行 TCP 連接創(chuàng)建,創(chuàng)建完畢之后調(diào)用 tryDeliver 方法和 wantConn 進(jìn)行綁定。

dialConn

func (t *Transport) dialConn(ctx context.Context, cm connectMethod) (pconn *persistConn, err error) {
    // 創(chuàng)建連接結(jié)構(gòu)體
    pconn = &persistConn{
        t:             t,
        cacheKey:      cm.key(),
        reqch:         make(chan requestAndChan, 1),
        writech:       make(chan writeRequest, 1),
        closech:       make(chan struct{}),
        writeErrCh:    make(chan error, 1),
        writeLoopDone: make(chan struct{}),
    }
    ...
    if cm.scheme() == "https" && t.hasCustomTLSDialer() {
        ...
    } else {
        // 建立 tcp 連接
        conn, err := t.dial(ctx, "tcp", cm.addr())
        if err != nil {
            return nil, wrapErr(err)
        }
        pconn.conn = conn 
    } 
    ...

    if s := pconn.tlsState; s != nil && s.NegotiatedProtocolIsMutual && s.NegotiatedProtocol != "" {
        if next, ok := t.TLSNextProto[s.NegotiatedProtocol]; ok {
            alt := next(cm.targetAddr, pconn.conn.(*tls.Conn))
            if e, ok := alt.(http2erringRoundTripper); ok {
                // pconn.conn was closed by next (http2configureTransport.upgradeFn).
                return nil, e.err
            }
            return &persistConn{t: t, cacheKey: pconn.cacheKey, alt: alt}, nil
        }
    }

    pconn.br = bufio.NewReaderSize(pconn, t.readBufferSize())
    pconn.bw = bufio.NewWriterSize(persistConnWriter{pconn}, t.writeBufferSize())
    //為每個(gè)連接異步處理讀寫數(shù)據(jù)
    go pconn.readLoop()
    go pconn.writeLoop()
    return pconn, nil
}

這里會(huì)根據(jù) schema 的不同設(shè)置不同的連接配置,我上面顯示的是我們常用的 HTTP 連接的創(chuàng)建過(guò)程。對(duì)于 HTTP 來(lái)說(shuō)會(huì)建立 tcp 連接,然后為連接異步處理讀寫數(shù)據(jù),最后將創(chuàng)建好的連接返回。

等待響應(yīng)

這一部分的內(nèi)容會(huì)稍微復(fù)雜一些,但確實(shí)非常的有趣。

在創(chuàng)建連接的時(shí)候會(huì)初始化兩個(gè) channel :writech 負(fù)責(zé)寫入請(qǐng)求數(shù)據(jù),reqch負(fù)責(zé)讀取響應(yīng)數(shù)據(jù)。我們?cè)谏厦鎰?chuàng)建連接的時(shí)候,也提到了會(huì)為連接創(chuàng)建兩個(gè)異步循環(huán) readLoop 和 writeLoop 來(lái)負(fù)責(zé)處理讀寫數(shù)據(jù)。

在獲取到連接之后,會(huì)調(diào)用連接的 roundTrip 方法,它首先會(huì)將請(qǐng)求數(shù)據(jù)寫入到 writech 管道中,writeLoop 接收到數(shù)據(jù)之后就會(huì)處理請(qǐng)求。

然后 roundTrip 會(huì)將 requestAndChan 結(jié)構(gòu)體寫入到 reqch 管道中,然后 roundTrip 會(huì)循環(huán)等待。readLoop 讀取到響應(yīng)數(shù)據(jù)之后就會(huì)通過(guò) requestAndChan 結(jié)構(gòu)體中保存的管道將數(shù)據(jù)封裝成 responseAndError 結(jié)構(gòu)體回寫,這樣 roundTrip 就可以接受到響應(yīng)數(shù)據(jù)結(jié)束循環(huán)等待并返回。

roundTrip

func (pc *persistConn) roundTrip(req *transportRequest) (resp *Response, err error) {
    ...
    writeErrCh := make(chan error, 1)
    // 將請(qǐng)求數(shù)據(jù)寫入到 writech 管道中
    pc.writech <- writeRequest{req, writeErrCh, continueCh}

    // 用于接收響應(yīng)的管道
    resc := make(chan responseAndError)
    // 將用于接收響應(yīng)的管道封裝成 requestAndChan 寫入到 reqch 管道中
    pc.reqch <- requestAndChan{
        req:        req.Request,
        cancelKey:  req.cancelKey,
        ch:         resc,
        ...
    }
    ...
    for {
        testHookWaitResLoop()
        select { 
        // 接收到響應(yīng)數(shù)據(jù)
        case re := <-resc:
            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)
            }
            // 返回響應(yīng)數(shù)據(jù)
            return re.res, nil
        ...
    }
}

這里會(huì)封裝好 writeRequest 作為發(fā)送請(qǐng)求的數(shù)據(jù),并將用于接收響應(yīng)的管道封裝成 requestAndChan 寫入到 reqch 管道中,然后循環(huán)等待接受響應(yīng)。

然后 writeLoop 會(huì)進(jìn)行請(qǐng)求數(shù)據(jù) writeRequest :

func (pc *persistConn) writeLoop() {
    defer close(pc.writeLoopDone)
    for {
        select {
        case wr := <-pc.writech:
            startBytesWritten := pc.nwrite
            // 向 TCP 連接中寫入數(shù)據(jù),并發(fā)送至目標(biāo)服務(wù)器
            err := wr.req.Request.write(pc.bw, pc.isProxy, wr.req.extra, pc.waitForContinue(wr.continueCh))
            ...
        case <-pc.closech:
            return
        }
    }
}

這里會(huì)將從 writech 管道中獲取到的數(shù)據(jù)寫入到 TCP 連接中,并發(fā)送至目標(biāo)服務(wù)器。
readLoop

func (pc *persistConn) readLoop() {
    closeErr := errReadLoopExiting // default value, if not changed below
    defer func() {
        pc.close(closeErr)
        pc.t.removeIdleConn(pc)
    }()
    ... 
    alive := true
    for alive {
        pc.readLimit = pc.maxHeaderResponseSize()
        // 獲取 roundTrip 發(fā)送的結(jié)構(gòu)體
        rc := <-pc.reqch
        trace := httptrace.ContextClientTrace(rc.req.Context())

        var resp *Response
        if err == nil {
            // 讀取數(shù)據(jù)
            resp, err = pc.readResponse(rc, trace)
        } else {
            err = transportReadFromServerError{err}
            closeErr = err
        }

        ...  
        // 將響應(yīng)數(shù)據(jù)寫回到管道中
        select {
        case rc.ch <- responseAndError{res: resp}:
        case <-rc.callerGone:
            return
        }
        ...
    }
}

這里是從 TCP 連接中讀取到對(duì)應(yīng)的請(qǐng)求響應(yīng)數(shù)據(jù),通過(guò) roundTrip 傳入的管道再回寫,然后 roundTrip 就會(huì)接受到數(shù)據(jù)并獲取的響應(yīng)數(shù)據(jù)返回。

http server

我這里繼續(xù)以一個(gè)簡(jiǎn)單的例子作為開頭:

func HelloHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Hello World")
}
func main () {
    http.HandleFunc("/", HelloHandler)
    http.ListenAndServe(":8000", nil)
}

在實(shí)現(xiàn)上面我先用一張圖進(jìn)行簡(jiǎn)要的介紹一下:

其實(shí)我們從上面例子的方法名就可以知道一些大致的步驟:

  • 注冊(cè)處理器到一個(gè) hash 表中,可以通過(guò)鍵值路由匹配;
  • 注冊(cè)完之后就是開啟循環(huán)監(jiān)聽,每監(jiān)聽到一個(gè)連接就會(huì)創(chuàng)建一個(gè) Goroutine;
  • 在創(chuàng)建好的 Goroutine 里面會(huì)循環(huán)的等待接收請(qǐng)求數(shù)據(jù),然后根據(jù)請(qǐng)求的地址去處理器路由表中匹配對(duì)應(yīng)的處理器,然后將請(qǐng)求交給處理器處理;注冊(cè)處理器

處理器的注冊(cè)如上面的例子所示,是通過(guò)調(diào)用 HandleFunc 函數(shù)來(lái)實(shí)現(xiàn)的。

HandleFunc 函數(shù)會(huì)一直調(diào)用到 ServeMux 的 Handle 方法中。

func (mux *ServeMux) Handle(pattern string, handler Handler) {
    mux.mu.Lock()
    defer mux.mu.Unlock()
    ...
    e := muxEntry{h: handler, pattern: pattern}
    mux.m[pattern] = e
    if pattern[len(pattern)-1] == '/' {
        mux.es = appendSorted(mux.es, e)
    }

    if pattern[0] != '/' {
        mux.hosts = true
    }
}

Handle 會(huì)根據(jù)路由作為 hash 表的鍵來(lái)保存 muxEntry 對(duì)象,muxEntry封裝了 pattern 和 handler。如果路由表達(dá)式以'/'結(jié)尾,則將對(duì)應(yīng)的muxEntry對(duì)象加入到[]muxEntry中。

hash 表是用于路由精確匹配,[]muxEntry用于部分匹配。

監(jiān)聽

監(jiān)聽是通過(guò)調(diào)用 ListenAndServe 函數(shù),里面會(huì)調(diào)用 server 的 ListenAndServe 方法:

func (srv *Server) ListenAndServe() error {
    if srv.shuttingDown() {
        return ErrServerClosed
    }
    addr := srv.Addr
    if addr == "" {
        addr = ":http"
    }
    // 監(jiān)聽端口
    ln, err := net.Listen("tcp", addr)
    if err != nil {
        return err
    }
    // 循環(huán)接收監(jiān)聽到的網(wǎng)絡(luò)請(qǐng)求
    return srv.Serve(ln)
}

Serve

func (srv *Server) Serve(l net.Listener) error { 
    ...
    baseCtx := context.Background()  
    ctx := context.WithValue(baseCtx, ServerContextKey, srv)
    for {
        // 接收 listener 過(guò)來(lái)的網(wǎng)絡(luò)連接
        rw, err := l.Accept()
        ... 
        tempDelay = 0
        c := srv.newConn(rw)
        c.setState(c.rwc, StateNew) 
        // 創(chuàng)建協(xié)程處理連接
        go c.serve(connCtx)
    }
}

Serve 這個(gè)方法里面會(huì)用一個(gè)循環(huán)去接收監(jiān)聽到的網(wǎng)絡(luò)連接,然后創(chuàng)建協(xié)程處理連接。所以難免就會(huì)有一個(gè)問題,如果并發(fā)很高的話,可能會(huì)一次性創(chuàng)建太多協(xié)程,導(dǎo)致處理不過(guò)來(lái)的情況。

處理請(qǐng)求

處理請(qǐng)求是通過(guò)為每個(gè)連接創(chuàng)建 goroutine 來(lái)處理對(duì)應(yīng)的請(qǐng)求:

func (c *conn) serve(ctx context.Context) {
    c.remoteAddr = c.rwc.RemoteAddr().String()
    ctx = context.WithValue(ctx, LocalAddrContextKey, c.rwc.LocalAddr()) 
    ... 
    ctx, cancelCtx := context.WithCancel(ctx)
    c.cancelCtx = cancelCtx
    defer cancelCtx() 
    c.r = &connReader{conn: c}
    c.bufr = newBufioReader(c.r)
    c.bufw = newBufioWriterSize(checkConnErrorWriter{c}, 4<<10)  
    for {
        // 讀取請(qǐng)求
        w, err := c.readRequest(ctx) 
        ... 
        // 根據(jù)請(qǐng)求路由調(diào)用處理器處理請(qǐng)求
        serverHandler{c.server}.ServeHTTP(w, w.req)
        w.cancelCtx()
        if c.hijacked() {
            return
        }
        w.finishRequest() 
        ...
    }
}

當(dāng)一個(gè)連接建立之后,該連接中所有的請(qǐng)求都將在這個(gè)協(xié)程中進(jìn)行處理,直到連接被關(guān)閉。在 for 循環(huán)里面會(huì)循環(huán)調(diào)用 readRequest 讀取請(qǐng)求進(jìn)行處理。

請(qǐng)求處理是通過(guò)調(diào)用 ServeHTTP 進(jìn)行的:

type serverHandler struct {
   srv *Server
}

func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
    handler := sh.srv.Handler
    if handler == nil {
        handler = DefaultServeMux
    }
    if req.RequestURI == "*" && req.Method == "OPTIONS" {
        handler = globalOptionsHandler{}
    }
    handler.ServeHTTP(rw, req)
}

serverHandler 其實(shí)就是 Server 包裝了一層。這里的 sh.srv.Handler參數(shù)實(shí)際上是傳入的 ServeMux 實(shí)例,所以這里最后會(huì)調(diào)用到 ServeMux 的 ServeHTTP 方法。

最終會(huì)通過(guò) handler 調(diào)用到 match 方法進(jìn)行路由匹配:

func (mux *ServeMux) match(path string) (h Handler, pattern string) {
    v, ok := mux.m[path]
    if ok {
        return v.h, v.pattern
    }

    for _, e := range mux.es {
        if strings.HasPrefix(path, e.pattern) {
            return e.h, e.pattern
        }
    }
    return nil, ""
}

這個(gè)方法里首先會(huì)利用進(jìn)行精確匹配,如果匹配成功那么直接返回;匹配不成功,那么會(huì)根據(jù) []muxEntry中保存的和當(dāng)前路由最接近的已注冊(cè)的父節(jié)點(diǎn)路由進(jìn)行匹配,否則繼續(xù)匹配下一個(gè)父節(jié)點(diǎn)路由,直到根路由/。最后會(huì)調(diào)用對(duì)應(yīng)的處理器進(jìn)行處理。

Reference

https://cloud.tencent.com/developer/article/1515297

https://duyanghao.github.io/http-transport

https://draveness.me/golang/docs/part4-advanced/ch09-stdlib/golang-net-http

https://laravelacademy.org/post/21003

https://segmentfault.com/a/1190000021653550

到此這篇關(guān)于快速掌握Go 語(yǔ)言 HTTP 標(biāo)準(zhǔn)庫(kù)的實(shí)現(xiàn)方法的文章就介紹到這了,更多相關(guān)Go 語(yǔ)言 HTTP 標(biāo)準(zhǔn)庫(kù)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Golang 刪除文件并遞歸刪除空目錄的操作

    Golang 刪除文件并遞歸刪除空目錄的操作

    這篇文章主要介紹了Golang 刪除文件并遞歸刪除空目錄的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2021-04-04
  • Go語(yǔ)言zip文件的讀寫操作

    Go語(yǔ)言zip文件的讀寫操作

    本文主要介紹了Go語(yǔ)言zip文件的讀寫操作,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2023-04-04
  • go語(yǔ)言通過(guò)zlib壓縮數(shù)據(jù)的方法

    go語(yǔ)言通過(guò)zlib壓縮數(shù)據(jù)的方法

    這篇文章主要介紹了go語(yǔ)言通過(guò)zlib壓縮數(shù)據(jù)的方法,實(shí)例分析了Go語(yǔ)言中zlib的使用技巧,需要的朋友可以參考下
    2015-03-03
  • 解決golang http重定向失效的問題

    解決golang http重定向失效的問題

    這篇文章主要介紹了解決golang http重定向失效的問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2020-12-12
  • go語(yǔ)言標(biāo)準(zhǔn)庫(kù)fmt包的一鍵入門

    go語(yǔ)言標(biāo)準(zhǔn)庫(kù)fmt包的一鍵入門

    這篇文章主要為大家介紹了go語(yǔ)言標(biāo)準(zhǔn)庫(kù)fmt包的一鍵入門使用示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-08-08
  • 在Mac OS上安裝Go語(yǔ)言編譯器的方法

    在Mac OS上安裝Go語(yǔ)言編譯器的方法

    這篇文章主要介紹了在Mac OS上安裝Go語(yǔ)言編譯器的方法,Docker的興起使得Go近來(lái)人氣大幅攀升,需要的朋友可以參考下
    2015-10-10
  • Go?語(yǔ)言使用goroutine運(yùn)行閉包踩坑分析

    Go?語(yǔ)言使用goroutine運(yùn)行閉包踩坑分析

    這篇文章主要介紹了Go?語(yǔ)言使用goroutine運(yùn)行閉包踩坑解決分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-11-11
  • go語(yǔ)言使用Casbin實(shí)現(xiàn)角色的權(quán)限控制

    go語(yǔ)言使用Casbin實(shí)現(xiàn)角色的權(quán)限控制

    Casbin是用于Golang項(xiàng)目的功能強(qiáng)大且高效的開源訪問控制庫(kù)。本文主要介紹了go語(yǔ)言使用Casbin實(shí)現(xiàn)角色的權(quán)限控制,感興趣的可以了解下
    2021-06-06
  • Go語(yǔ)言LeetCode題解1046最后一塊石頭的重量

    Go語(yǔ)言LeetCode題解1046最后一塊石頭的重量

    這篇文章主要為大家介紹了Go語(yǔ)言LeetCode題解1046最后一塊石頭的重量,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-12-12
  • 再次探討go實(shí)現(xiàn)無(wú)限 buffer 的 channel方法

    再次探討go實(shí)現(xiàn)無(wú)限 buffer 的 channel方法

    我們知道go語(yǔ)言內(nèi)置的channel緩沖大小是有上限的,那么我們自己如何實(shí)現(xiàn)一個(gè)無(wú)限 buffer 的 channel呢?今天通過(guò)本文給大家分享go實(shí)現(xiàn)無(wú)限 buffer 的 channel方法,感興趣的朋友一起看看吧
    2021-06-06

最新評(píng)論