您好,登錄后才能下訂單哦!
本篇內(nèi)容介紹了“Go語言HTTP標(biāo)準(zhǔn)庫如何實(shí)現(xiàn)”的有關(guān)知識(shí),在實(shí)際案例的操作過程中,不少人都會(huì)遇到這樣的困境,接下來就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細(xì)閱讀,能夠?qū)W有所成!
本文使用的go的源碼1.15.7
基于HTTP構(gòu)建的服務(wù)標(biāo)準(zhǔn)模型包括兩個(gè)端,客戶端(Client
)和服務(wù)端(Server
)。HTTP 請求從客戶端發(fā)出,服務(wù)端接受到請求后進(jìn)行處理然后將響應(yīng)返回給客戶端。所以http服務(wù)器的工作就在于如何接受來自客戶端的請求,并向客戶端返回響應(yīng)。
一個(gè)典型的 HTTP 服務(wù)應(yīng)該如圖所示:
在 Go 中可以直接通過 HTTP 包的 Get 方法來發(fā)起相關(guān)請求數(shù)據(jù),一個(gè)簡單例子:
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)) }
我們下面通過這個(gè)例子來進(jìn)行分析。
HTTP 的 Get 方法會(huì)調(diào)用到 DefaultClient 的 Get 方法,DefaultClient 是 Client 的一個(gè)空實(shí)例,所以最后會(huì)調(diào)用到 Client 的 Get 方法:
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ù),用于處理客戶端的請求連接并等待服務(wù)端的響應(yīng);
CheckRedirect:用于指定處理重定向的策略;
Jar:用于管理和存儲(chǔ)請求中的 cookie;
Timeout:指定客戶端請求的最大超時(shí)時(shí)間,該超時(shí)時(shí)間包括連接、任何的重定向以及讀取相應(yīng)的時(shí)間;
func (c *Client) Get(url string) (resp *Response, err error) { // 根據(jù)方法名、URL 和請求體構(gòu)建請求 req, err := NewRequest("GET", url, nil) if err != nil { return nil, err } // 執(zhí)行請求 return c.Do(req) }
我們要發(fā)起一個(gè)請求首先需要根據(jù)請求類型構(gòu)建一個(gè)完整的請求頭、請求體、請求參數(shù)。然后才是根據(jù)請求的完整結(jié)構(gòu)來執(zhí)行請求。
NewRequest 會(huì)調(diào)用到 NewRequestWithContext 函數(shù)上。這個(gè)函數(shù)會(huì)根據(jù)請求返回一個(gè) Request 結(jié)構(gòu)體,它里面包含了一個(gè) HTTP 請求所有信息。
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ì)將請求封裝成一個(gè) Request 結(jié)構(gòu)體并返回。
如上圖所示,Client 調(diào)用 Do 方法處理發(fā)送請求最后會(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 }
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 請求并等待響應(yīng)。
type RoundTripper interface { RoundTrip(*Request) (*Response, error) }
從 RoundTripper 接口我們也可以看出,該接口定義的 RoundTrip 方法會(huì)具體的處理請求,處理完畢之后會(huì)響應(yīng) Response。
回到我們上面的 Client 的 send 方法中,它會(huì)調(diào)用 send 函數(shù),這個(gè)函數(shù)主要邏輯都交給 Transport 的 RoundTrip 方法來執(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: } // 封裝請求 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 方法等待請求響應(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()) } // 將請求封裝成 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 ... }
成功獲取到空閑 connection:
成功獲取 connection 分為如下幾步:
根據(jù)當(dāng)前的請求的地址去空閑 connection 字典中查看存不存在空閑的 connection 列表;
如果能獲取到空閑的 connection 列表,那么獲取到列表的最后一個(gè) connection;
返回;
獲取不到空閑 connection:
當(dāng)獲取不到空閑 connection 時(shí):
根據(jù)當(dāng)前的請求的地址去空閑 connection 字典中查看存不存在空閑的 connection 列表;
不存在該請求的 connection 列表,那么將該 wantConn 加入到 等待獲取空閑 connection 字典中;
從上面的圖解應(yīng)該就很能看出這一步會(huì)怎么操作了,這里簡要的分析一下代碼,讓大家更清楚里面的邏輯:
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 對應(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)很清楚了,我這里就不再解釋了。
在獲取不到空閑連接之后,會(huì)嘗試去建立連接,從上面的圖大致可以看到,總共分為以下幾個(gè)步驟:
在調(diào)用 queueForDial 方法的時(shí)候會(huì)校驗(yàn) MaxConnsPerHost 是否未設(shè)置或已達(dá)上限;
檢驗(yàn)不通過則將當(dāng)前的請求放入到 connsPerHostWait 等待字典中;
如果校驗(yàn)通過那么會(huì)異步的調(diào)用 dialConnFor 方法創(chuàng)建連接;
dialConnFor 方法首先會(huì)調(diào)用 dialConn 方法創(chuàng)建 TCP 連接,然后啟動(dòng)兩個(gè)異步線程來處理讀寫數(shù)據(jù),然后調(diào)用 tryDeliver 將連接綁定到 wantConn 上面。
下面進(jìn)行代碼分析:
func (t *Transport) queueForDial(w *wantConn) { w.beforeDial() // 小于零說明無限制,異步建立連接 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)建過程。對于 HTTP 來說會(huì)建立 tcp 連接,然后為連接異步處理讀寫數(shù)據(jù),最后將創(chuàng)建好的連接返回。
這一部分的內(nèi)容會(huì)稍微復(fù)雜一些,但確實(shí)非常的有趣。
在創(chuàng)建連接的時(shí)候會(huì)初始化兩個(gè) channel :writech 負(fù)責(zé)寫入請求數(shù)據(jù),reqch負(fù)責(zé)讀取響應(yīng)數(shù)據(jù)。我們在上面創(chuàng)建連接的時(shí)候,也提到了會(huì)為連接創(chuàng)建兩個(gè)異步循環(huán) readLoop 和 writeLoop 來負(fù)責(zé)處理讀寫數(shù)據(jù)。
在獲取到連接之后,會(huì)調(diào)用連接的 roundTrip 方法,它首先會(huì)將請求數(shù)據(jù)寫入到 writech 管道中,writeLoop 接收到數(shù)據(jù)之后就會(huì)處理請求。
然后 roundTrip 會(huì)將 requestAndChan 結(jié)構(gòu)體寫入到 reqch 管道中,然后 roundTrip 會(huì)循環(huán)等待。readLoop 讀取到響應(yīng)數(shù)據(jù)之后就會(huì)通過 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) // 將請求數(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ā)送請求的數(shù)據(jù),并將用于接收響應(yīng)的管道封裝成 requestAndChan 寫入到 reqch 管道中,然后循環(huán)等待接受響應(yīng)。
然后 writeLoop 會(huì)進(jìn)行請求數(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 連接中讀取到對應(yīng)的請求響應(yīng)數(shù)據(jù),通過 roundTrip 傳入的管道再回寫,然后 roundTrip 就會(huì)接受到數(shù)據(jù)并獲取的響應(yīng)數(shù)據(jù)返回。
我這里繼續(xù)以一個(gè)簡單的例子作為開頭:
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)行簡要的介紹一下:
其實(shí)我們從上面例子的方法名就可以知道一些大致的步驟:
注冊處理器到一個(gè) hash 表中,可以通過鍵值路由匹配;
注冊完之后就是開啟循環(huán)監(jiān)聽,每監(jiān)聽到一個(gè)連接就會(huì)創(chuàng)建一個(gè) Goroutine;
在創(chuàng)建好的 Goroutine 里面會(huì)循環(huán)的等待接收請求數(shù)據(jù),然后根據(jù)請求的地址去處理器路由表中匹配對應(yīng)的處理器,然后將請求交給處理器處理;注冊處理器
處理器的注冊如上面的例子所示,是通過調(diào)用 HandleFunc 函數(shù)來實(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 表的鍵來保存 muxEntry
對象,muxEntry
封裝了 pattern 和 handler。如果路由表達(dá)式以'/'
結(jié)尾,則將對應(yīng)的muxEntry
對象加入到[]muxEntry
中。
hash 表是用于路由精確匹配,[]muxEntry
用于部分匹配。
監(jiān)聽是通過調(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ò)請求 return srv.Serve(ln) }
Serve
func (srv *Server) Serve(l net.Listener) error { ... baseCtx := context.Background() ctx := context.WithValue(baseCtx, ServerContextKey, srv) for { // 接收 listener 過來的網(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)致處理不過來的情況。
處理請求是通過為每個(gè)連接創(chuàng)建 goroutine 來處理對應(yī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 { // 讀取請求 w, err := c.readRequest(ctx) ... // 根據(jù)請求路由調(diào)用處理器處理請求 serverHandler{c.server}.ServeHTTP(w, w.req) w.cancelCtx() if c.hijacked() { return } w.finishRequest() ... } }
當(dāng)一個(gè)連接建立之后,該連接中所有的請求都將在這個(gè)協(xié)程中進(jìn)行處理,直到連接被關(guān)閉。在 for 循環(huán)里面會(huì)循環(huán)調(diào)用 readRequest 讀取請求進(jìn)行處理。
請求處理是通過調(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ì)通過 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)前路由最接近的已注冊的父節(jié)點(diǎn)路由進(jìn)行匹配,否則繼續(xù)匹配下一個(gè)父節(jié)點(diǎn)路由,直到根路由/
。最后會(huì)調(diào)用對應(yīng)的處理器進(jìn)行處理。
“Go語言HTTP標(biāo)準(zhǔn)庫如何實(shí)現(xiàn)”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識(shí)可以關(guān)注億速云網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實(shí)用文章!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。