哈嘍,大家后,我是asong;這幾天看了一下Go語言標(biāo)準(zhǔn)庫net/http的源碼,所以就來分享一下我的學(xué)習(xí)心得;為什么會(huì)突然想看http標(biāo)準(zhǔn)庫呢?因?yàn)樵诿嬖嚨臅r(shí)候面試官問我你知道Go語言的net/http庫嗎?他有什么有缺點(diǎn)嗎?因?yàn)槲覜]有看過這部分源碼,所以一首涼涼送給我;
廢話不多說,接下來請(qǐng)跟著我的腳步我們一起探索net/http;
本文代碼基于:Go1.19.3
服務(wù)端:
import?(
?"fmt"
?"net/http"
)
func?getProfile(w?http.ResponseWriter,?r?*http.Request)?{
?fmt.Fprintln(w,?"asong")
}
func?main()?{
?http.HandleFunc("/profile",?getProfile)
?err?:=?http.ListenAndServe(":8080",?nil)
?if?err?!=?nil?{
??fmt.Printf("http?server?failed,?err:?%v\n",?err)
??return
?}
}
本地啟動(dòng)一個(gè)server端監(jiān)聽8080端口,并且提供路由/profile獲取個(gè)人信息;
客戶端:
import?(
?"fmt"
?"io/ioutil"
?"net/http"
)
func?main()?{
?resp,?err?:=?http.DefaultClient.Get("http://127.0.0.1:8080/profile")
?if?err?!=?nil?{
??fmt.Printf("get?failed,?err:%v\n",?err)
??return
?}
?defer?resp.Body.Close()
?body,?err?:=?ioutil.ReadAll(resp.Body)
?if?err?!=?nil?{
??fmt.Printf("read?from?resp.body?failed,?err:%v\n",?err)
??return
?}
?fmt.Println(string(body))
}
通過這樣一個(gè)簡(jiǎn)單的例子,我們可以知道客戶端我們主要使用http.Client{},服務(wù)端我們主要使用http.ListenAndServe和http.HandleFunc,所以我們就可以從這兩個(gè)包入手來分別看一看客戶端和服務(wù)端的代碼具體是怎么封裝;
客戶端級(jí)別最高的抽象是net/http.Client{},具體結(jié)構(gòu)如下:
type?Client?struct?{
????Transport?RoundTripper
????CheckRedirect?func(req?*Request,?via?[]*Request)?error
????Jar?CookieJar
????Timeout?time.Duration
}
net/http/cookiejar/jar.go;客戶端可以直接通過net/http.DefaultClient發(fā)起HTTP請(qǐng)求,也可以自己構(gòu)建新的net/http.Client實(shí)現(xiàn)自定義的HTTP事務(wù),多數(shù)情況下我們使用默認(rèn)的客戶端發(fā)出的請(qǐng)求就可以滿足需求;
我們畫一個(gè)UML圖,如下所示:

了解HTTP客戶端的基本結(jié)構(gòu),我們接下來就開始分析客戶端的基本實(shí)現(xiàn);
net/http包的Request結(jié)構(gòu)體封裝好了HTTP請(qǐng)求所需的必要信息:
type?Request?struct?{
?Method?string
?URL?*url.URL
?Proto??????string?//?"HTTP/1.0"
?ProtoMajor?int????//?1
?ProtoMinor?int????//?0
?Header?Header
?Body?io.ReadCloser
?GetBody?func()?(io.ReadCloser,?error)
?ContentLength?int64
?removed?as?necessary?when?sending?and
?TransferEncoding?[]string
?Close?bool
?Host?string
?Form?url.Values
?PostForm?url.Values
?MultipartForm?*multipart.Form
?Trailer?Header
?RemoteAddr?string
?RequestURI?string
?TLS?*tls.ConnectionState
?Cancel?<-chan?struct{}
?Response?*Response
?ctx?context.Context
}
其中包含了HTTP請(qǐng)求的方法、URL、協(xié)議版本、協(xié)議頭以及請(qǐng)求體等字段,還包括了指向響應(yīng)的引用:Response;其提供了NewRequest()、NewRequestWithContext()兩個(gè)方法用來構(gòu)建請(qǐng)求,這個(gè)方法可以校驗(yàn)HTTP請(qǐng)求的字段并根據(jù)輸入的參數(shù)拼裝成新的請(qǐng)求結(jié)構(gòu)體,NewRequest()方法內(nèi)部也是調(diào)用的NewRequestWithContext,區(qū)別就是是否使用context來做goroutine上下文傳遞;接下來我們看一下NewRequestWithContext方法的具體實(shí)現(xiàn):
func?NewRequestWithContext(ctx?context.Context,?method,?url?string,?body?io.Reader)?(*Request,?error)?{
????//?默認(rèn)使用GET方法
?if?method?==?""?{
??method?=?"GET"
?}
????//?校驗(yàn)方法是否有效,也就是是否是GET、POST、PUT等
?if?!validMethod(method)?{
??return?nil,?fmt.Errorf("net/http:?invalid?method?%q",?method)
?}
????//?ctx必須要傳遞,NewRequest方法調(diào)用時(shí)會(huì)傳遞context.Background()
?if?ctx?==?nil?{
??return?nil,?errors.New("net/http:?nil?Context")
?}
????//?解析URL,解析Scheme、Host、Path等信息
?u,?err?:=?urlpkg.Parse(url)
?if?err?!=?nil?{
??return?nil,?err
?}
????//?body在下面會(huì)根據(jù)其類型包裝成io.ReadCloser類型
?rc,?ok?:=?body.(io.ReadCloser)
?if?!ok?&&?body?!=?nil?{
??rc?=?io.NopCloser(body)
?}
?//?The?host's?colon:port?should?be?normalized.?See?Issue?14836.
?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,
?}
?if?body?!=?nil?{
??switch?v?:=?body.(type)?{
??case?*bytes.Buffer:
???req.ContentLength?=?int64(v.Len())
???buf?:=?v.Bytes()
???req.GetBody?=?func()?(io.ReadCloser,?error)?{
????r?:=?bytes.NewReader(buf)
????return?io.NopCloser(r),?nil
???}
??case?*bytes.Reader:
???req.ContentLength?=?int64(v.Len())
???snapshot?:=?*v
???req.GetBody?=?func()?(io.ReadCloser,?error)?{
????r?:=?snapshot
????return?io.NopCloser(&r),?nil
???}
??case?*strings.Reader:
???req.ContentLength?=?int64(v.Len())
???snapshot?:=?*v
???req.GetBody?=?func()?(io.ReadCloser,?error)?{
????r?:=?snapshot
????return?io.NopCloser(&r),?nil
???}
??default:
??if?req.GetBody?!=?nil?&&?req.ContentLength?==?0?{
???req.Body?=?NoBody
???req.GetBody?=?func()?(io.ReadCloser,?error)?{?return?NoBody,?nil?}
??}
?}
?return?req,?nil
}
整個(gè)構(gòu)建請(qǐng)求過程中只有處理body的時(shí)候稍有一些復(fù)雜,我們需要根據(jù)body的類型使用不同的方法將其包裝成io.ReadCloser類型;
構(gòu)建HTTP請(qǐng)求后,接下來我們需要開啟HTTP事務(wù)進(jìn)行請(qǐng)求并且等待遠(yuǎn)程響應(yīng),我們以net/http.Client.Do()方法為例子,我們看一下它的調(diào)用鏈路:
RoundTrip()是RoundTripper類型中的一個(gè)的方法,net/http.Transport是其中的一個(gè)實(shí)現(xiàn),在net/http/transport.go文件中我們可以找到這個(gè)方法:
//?roundTrip?implements?a?RoundTripper?over?HTTP.
func?(t?*Transport)?roundTrip(req?*Request)?(*Response,?error)?{
?t.nextProtoOnce.Do(t.onceSetNextProtoDefaults)
?ctx?:=?req.Context()
?trace?:=?httptrace.ContextClientTrace(ctx)
????//?省略前置檢查部分
????.....
?for?{
????????//?用來檢測(cè)ctx退出信號(hào)
??select?{
??case?<-ctx.Done():
???req.closeBody()
???return?nil,?ctx.Err()
??default:
??}
??//?獲取連接,這塊就是我們要看的重點(diǎn),Go語言通過連接池對(duì)資源進(jìn)行了復(fù)用;
??pconn,?err?:=?t.getConn(treq,?cm)
??if?err?!=?nil?{
???t.setReqCanceler(cancelKey,?nil)
???req.closeBody()
???return?nil,?err
??}
??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?{
????????????//?開始處理響應(yīng)
???resp,?err?=?pconn.roundTrip(treq)
??}
??if?err?==?nil?{
???resp.Request?=?origReq
???return?resp,?nil
??}
??//?Rewind?the?body?if?we're?able?to.
??req,?err?=?rewindBody(req)
??if?err?!=?nil?{
???return?nil,?err
??}
?}
}
代碼一大堆,我們只要重點(diǎn)看兩部分即可:
net/http.Transport.getConn()獲取連接net/http.persistConn.roundTrip()處理寫入HTTP請(qǐng)求并在select中等待響應(yīng)的返回;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())
?}
?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)
??}
?}()
?//?在隊(duì)列中有閑置連接,直接返回
?if?delivered?:=?t.queueForIdleConn(w);?delivered?{
??pc?:=?w.pc
??return?pc,?nil
?}
?cancelc?:=?make(chan?error,?1)
?t.setReqCanceler(treq.cancelKey,?func(err?error)?{?cancelc?<-?err?})
?//?放到隊(duì)列中等待建立新的連接
?t.queueForDial(w)
?//?阻塞等待連接
?select?{
?case?<-w.ready:
??return?w.pc,?w.err
?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
?}
}
因?yàn)檫B接的建議會(huì)消耗比較多的時(shí)間,帶來較大的開下,所以Go語言使用了連接池對(duì)資源進(jìn)行分配和復(fù)用,先調(diào)用net/http.Transport.queueForIdleConn()獲取等待閑置的連接,如果沒有獲取到在調(diào)用net/http.Transport.queueForDial在隊(duì)列中等待建立新的連接,通過select監(jiān)聽連接是否建立完畢,超時(shí)未獲取到連接會(huì)上剖錯(cuò)誤,我們繼續(xù)在queueForDial追蹤TCP連接的建立:
func?(t?*Transport)?queueForDial(w?*wantConn)?{
?w.beforeDial()
?if?t.MaxConnsPerHost?<=?0?{
??go?t.dialConnFor(w)
??return
?}
?t.connsPerHostMu.Lock()
?defer?t.connsPerHostMu.Unlock()
?if?n?:=?t.connsPerHost[w.key];?n???if?t.connsPerHost?==?nil?{
???t.connsPerHost?=?make(map[connectMethodKey]int)
??}
??t.connsPerHost[w.key]?=?n?+?1
??go?t.dialConnFor(w)
??return
?}
????....
}
我們會(huì)啟動(dòng)一個(gè)goroutine做tcp的建連,最終調(diào)用dialConn方法,在這個(gè)方法內(nèi)做持久化連接,調(diào)用net庫的dial方法進(jìn)行TCP連接:
func?(t?*Transport)?dialConn(ctx?context.Context,?cm?connectMethod)?(pconn?*persistConn,?err?error)?{
?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{}),
?}
?conn,?err?:=?t.dial(ctx,?"tcp",?cm.addr())
?if?err?!=?nil?{
??return?nil,?err
?}
?pconn.conn?=?conn
?pconn.br?=?bufio.NewReaderSize(pconn,?t.readBufferSize())
?pconn.bw?=?bufio.NewWriterSize(persistConnWriter{pconn},?t.writeBufferSize())
?go?pconn.readLoop()
?go?pconn.writeLoop()
?return?pconn,?nil
}
在連接建立后,代碼中我們我們還看到分別啟動(dòng)了兩個(gè)goroutine,readLoop用于從tcp連接中讀取數(shù)據(jù),writeLoop用于從tcp連接中寫入數(shù)據(jù);
我們看一下writeLoop方法:
func?(pc?*persistConn)?writeLoop()?{
?defer?close(pc.writeLoopDone)
?for?{
??select?{
??case?wr?:=?<-pc.writech:
???startBytesWritten?:=?pc.nwrite
???err?:=?wr.req.Request.write(pc.bw,?pc.isProxy,?wr.req.extra,?pc.waitForContinue(wr.continueCh))
???if?bre,?ok?:=?err.(requestBodyReadError);?ok?{
????err?=?bre.error
????wr.req.setError(err)
???}
???if?err?==?nil?{
????err?=?pc.bw.Flush()
???}
???if?err?!=?nil?{
????if?pc.nwrite?==?startBytesWritten?{
?????err?=?nothingWrittenError{err}
????}
???}
???pc.writeErrCh?<-?err?//?to?the?body?reader,?which?might?recycle?us
???wr.ch?<-?err?????????//?to?the?roundTrip?function
???if?err?!=?nil?{
????pc.close(err)
????return
???}
??case?<-pc.closech:
???return
??}
?}
}
監(jiān)聽writech通道,所以的數(shù)據(jù)發(fā)送都是在這個(gè)循環(huán)中寫入的;
net/http.Transport{}中提供了連接池配置參數(shù),開發(fā)者可以自行定義:
type?Transport?struct?{
?......
????MaxIdleConns?int
????MaxIdleConnsPerHost?int
????MaxConnsPerHost?int
????IdleConnTimeout?time.Duration
????......
}
net/http.persistConn.roundTrip()會(huì)處理HTTP請(qǐng)求,我們看其具體實(shí)現(xiàn):
func?(pc?*persistConn)?roundTrip(req?*transportRequest)?(resp?*Response,?err?error)?{
????......
?startBytesWritten?:=?pc.nwrite
?writeErrCh?:=?make(chan?error,?1)
????//writeLoop循環(huán)寫入
?pc.writech?<-?writeRequest{req,?writeErrCh,?continueCh}
????
????//?等待響應(yīng)數(shù)據(jù)
????resc?:=?make(chan?responseAndError)
????//?readLoop循環(huán)等待響應(yīng)
?pc.reqch?<-?requestAndChan{
??req:????????req.Request,
??cancelKey:??req.cancelKey,
??ch:?????????resc,
??addedGzip:??requestedGzip,
??continueCh:?continueCh,
??callerGone:?gone,
?}
????
?for?{
??select?{
??case?err?:=?<-writeErrCh:
??case?<-pcClosed:
???pcClosed?=?nil
???if?canceled?||?pc.t.replaceReqCanceler(req.cancelKey,?nil)?{
????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:
???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:
???canceled?=?pc.t.cancelRequest(req.cancelKey,?errRequestCanceled)
???cancelChan?=?nil
??case?<-ctxDoneChan:
???canceled?=?pc.t.cancelRequest(req.cancelKey,?req.Context().Err())
???cancelChan?=?nil
???ctxDoneChan?=?nil
??}
?}
}
我們重點(diǎn)關(guān)注這兩個(gè)通道:
pc.writech :其類型是chan writeRequest ,writeLoop協(xié)程會(huì)循環(huán)寫入數(shù)據(jù),net/http.Request.write會(huì)根據(jù)net/http.Request結(jié)構(gòu)中的字段按照HTTP協(xié)議組成TCP數(shù)據(jù)段,TCP協(xié)議棧會(huì)負(fù)責(zé)將HTTP請(qǐng)求中的內(nèi)容發(fā)送到目標(biāo)服務(wù)器上;pc.reqch:其類型是chan requestAndChan,readLoop協(xié)程會(huì)循環(huán)讀取響應(yīng)數(shù)據(jù)并且調(diào)用net/http.ReadResponse進(jìn)行協(xié)議解析,其中包含狀態(tài)碼、協(xié)議版本、請(qǐng)求頭等內(nèi)容;我們簡(jiǎn)單總結(jié)一下net/http庫中HTTP客戶端的實(shí)現(xiàn):
transport用于開啟HTTP事務(wù),jar用于處理cookie;net/http庫中默認(rèn)有一個(gè)DefaultClient可以直接使用,DefaultClient有對(duì)應(yīng)DefaultTransport,可以滿足我們大多數(shù)場(chǎng)景,如果需要使用自己管理HTTP客戶端的頭域、重定向等策略,那么可以自定義Client,如果需要管理代理、TLS配置、連接池、壓縮等設(shè)置,可以自定義Transport;
因?yàn)镠TTP協(xié)議的版本是不斷變化的,所以為了可擴(kuò)展性,transport是一個(gè)接口類型,具體的是實(shí)現(xiàn)是Transport、http2Transport、fileTransport,這樣實(shí)現(xiàn)擴(kuò)展性變高,值得我們學(xué)習(xí);
HTTP在建立連接時(shí)會(huì)耗費(fèi)大量的資源,需要開辟一個(gè)goroutine去創(chuàng)建TCP連接,連接建立后會(huì)在創(chuàng)建兩個(gè)goroutine用于HTTP請(qǐng)求的寫入和響應(yīng)的解析,然后使用channel進(jìn)行通信,所以要合理利用連接池,避免大量的TCP連接的建立可以優(yōu)化性能;
我們可以用net/http庫快速搭建HTTP服務(wù),HTTP服務(wù)端主要包含兩部分:
net/http.ListenAndServe用于處理請(qǐng)求直接調(diào)用net/http.HandleFunc可以注冊(cè)路由和處理函數(shù):
func?HandleFunc(pattern?string,?handler?func(ResponseWriter,?*Request))?{
?DefaultServeMux.HandleFunc(pattern,?handler)
}
我們可以看到處理函數(shù)是一個(gè)統(tǒng)一的格式:
handler?func(ResponseWriter,?*Request)
默認(rèn)調(diào)用HTTP服務(wù)起的DefaultServeMux處理請(qǐng)求,DefaultServeMux本質(zhì)是ServeMux:
type?ServeMux?struct?{
?mu????sync.RWMutex??//?讀寫鎖,保證并發(fā)安全,注冊(cè)處理器時(shí)會(huì)加寫鎖做保護(hù)
?m?????map[string]muxEntry?//?路由規(guī)則,一個(gè)string對(duì)應(yīng)一個(gè)mux實(shí)體,這里的string就是注冊(cè)的路由表達(dá)式
?es????[]muxEntry?//?slice?of?entries?sorted?from?longest?to?shortest.
?hosts?bool???????//?whether?any?patterns?contain?hostnames
}
mu:需要加讀寫鎖保證并發(fā)安全,注冊(cè)處理器時(shí)會(huì)加寫鎖保證寫map的數(shù)據(jù)正確性,這個(gè)map就是pattern和handler;m:存儲(chǔ)路由規(guī)則,key就是pattern,value是muEntry實(shí)體,muEntry實(shí)體中包含:pattern和handleres:存儲(chǔ)的也是muxEntry實(shí)體,因?yàn)槲覀兪褂胢ap存儲(chǔ)路由和handler的對(duì)應(yīng)關(guān)系,所以只能索引靜態(tài)路由,并不支持[path_param],所以這塊的作用是當(dāng)在map中沒有找到匹配的路由時(shí),會(huì)遍歷這個(gè)切片進(jìn)行前綴匹配,這個(gè)切片按照路由長度進(jìn)行排序;hosts:這個(gè)也是用來應(yīng)對(duì)特殊case,如果我們注冊(cè)的路由沒有以/開始,那么就認(rèn)為我們注冊(cè)的路由包含host,所以路由匹配時(shí)需要加上host;我看看一下路由注冊(cè)函數(shù):
func?(mux?*ServeMux)?Handle(pattern?string,?handler?Handler)?{
????//?加鎖,保證并發(fā)安全
?mux.mu.Lock()
?defer?mux.mu.Unlock()
?if?pattern?==?""?{
??panic("http:?invalid?pattern")
?}
?if?handler?==?nil?{
??panic("http:?nil?handler")
?}
?if?_,?exist?:=?mux.m[pattern];?exist?{
??panic("http:?multiple?registrations?for?"?+?pattern)
?}
?if?mux.m?==?nil?{
??mux.m?=?make(map[string]muxEntry)
?}
?e?:=?muxEntry{h:?handler,?pattern:?pattern}
????//?map存儲(chǔ)路由和處理函數(shù)的映射
?mux.m[pattern]?=?e
????//?如果路由最后加了`/`放入到切片后在路由匹配時(shí)做前綴匹配
?if?pattern[len(pattern)-1]?==?'/'?{
??mux.es?=?appendSorted(mux.es,?e)
?}
?//?如果路由第一位不是/,則認(rèn)為注冊(cè)的路由加上了host,所以在路由匹配時(shí)使用host+path進(jìn)行匹配;
?if?pattern[0]?!=?'/'?{
??mux.hosts?=?true
?}
}
net/http庫提供了ListenAndServe()用來監(jiān)聽TCP連接并處理請(qǐng)求:
func?ListenAndServe(addr?string,?handler?Handler)?error?{
?server?:=?&Server{Addr:?addr,?Handler:?handler}
?return?server.ListenAndServe()
}
在這里初始化Server結(jié)構(gòu),然后調(diào)用ListenAndServe:
func?(srv?*Server)?ListenAndServe()?error?{
?if?srv.shuttingDown()?{
??return?ErrServerClosed
?}
?addr?:=?srv.Addr
?if?addr?==?""?{
??addr?=?":http"
?}
????//?調(diào)用net進(jìn)行tcp連接
?ln,?err?:=?net.Listen("tcp",?addr)
?if?err?!=?nil?{
??return?err
?}
?return?srv.Serve(ln)
}
我們調(diào)用net網(wǎng)絡(luò)庫進(jìn)行tcp連接,這里包含了創(chuàng)建socket、bind綁定socket與地址,listen端口的操作,最后調(diào)用Serve方法循環(huán)等待客戶端的請(qǐng)求:
func?(srv?*Server)?Serve(l?net.Listener)?error?{
?origListener?:=?l
?l?=?&onceCloseListener{Listener:?l}
?defer?l.Close()
?if?err?:=?srv.setupHTTP2_Serve();?err?!=?nil?{
??return?err
?}
?if?!srv.trackListener(&l,?true)?{
??return?ErrServerClosed
?}
?defer?srv.trackListener(&l,?false)
?baseCtx?:=?context.Background()
?if?srv.BaseContext?!=?nil?{
??baseCtx?=?srv.BaseContext(origListener)
??if?baseCtx?==?nil?{
???panic("BaseContext?returned?a?nil?context")
??}
?}
?var?tempDelay?time.Duration?//?how?long?to?sleep?on?accept?failure
?ctx?:=?context.WithValue(baseCtx,?ServerContextKey,?srv)
?for?{
????????//?接收客戶端請(qǐng)求
??rw,?err?:=?l.Accept()
??if?err?!=?nil?{
???select?{
???case?<-srv.getDoneChan():
????return?ErrServerClosed
???default:
???}
????????????//?網(wǎng)絡(luò)錯(cuò)誤進(jìn)行延時(shí)等待
???if?ne,?ok?:=?err.(net.Error);?ok?&&?ne.Temporary()?{
????if?tempDelay?==?0?{
?????tempDelay?=?5?*?time.Millisecond
????}?else?{
?????tempDelay?*=?2
????}
????if?max?:=?1?*?time.Second;?tempDelay?>?max?{
?????tempDelay?=?max
????}
????srv.logf("http:?Accept?error:?%v;?retrying?in?%v",?err,?tempDelay)
????time.Sleep(tempDelay)
????continue
???}
???return?err
??}
??connCtx?:=?ctx
??if?cc?:=?srv.ConnContext;?cc?!=?nil?{
???connCtx?=?cc(connCtx,?rw)
???if?connCtx?==?nil?{
????panic("ConnContext?returned?nil")
???}
??}
??tempDelay?=?0
????????//?創(chuàng)建一個(gè)新的連接
??c?:=?srv.newConn(rw)
??c.setState(c.rwc,?StateNew,?runHooks)?//?before?Serve?can?return
????????//?讀取起一個(gè)goroutine處理客戶端請(qǐng)求
??go?c.serve(connCtx)
?}
}
從上述代碼我們可以到每個(gè)HTTP請(qǐng)求服務(wù)端都會(huì)單獨(dú)創(chuàng)建一個(gè)goroutine來處理請(qǐng)求,我們一下處理過程:
//?Serve?a?new?connection.
func?(c?*conn)?serve(ctx?context.Context)?{
?c.remoteAddr?=?c.rwc.RemoteAddr().String()
?ctx?=?context.WithValue(ctx,?LocalAddrContextKey,?c.rwc.LocalAddr())
?var?inFlightResponse?*response
?defer?func()?{
????????//?添加recover函數(shù)防止panic引發(fā)主程序掛掉;
??if?err?:=?recover();?err?!=?nil?&&?err?!=?ErrAbortHandler?{
???const?size?=?64?<10
???buf?:=?make([]byte,?size)
???buf?=?buf[:runtime.Stack(buf,?false)]
???c.server.logf("http:?panic?serving?%v:?%v\n%s",?c.remoteAddr,?err,?buf)
??}
?}()
?//?HTTP/1.x?from?here?on.
?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)求,從連接中獲取HTTP請(qǐng)求并構(gòu)建一個(gè)實(shí)現(xiàn)了`net/http.Conn.ResponseWriter`接口的變量`net/http.response`
??w,?err?:=?c.readRequest(ctx)
??if?c.r.remain?!=?c.server.initialReadLimitSize()?{
???c.setState(c.rwc,?StateActive,?runHooks)
??}
??if?err?!=?nil?{
??}
??//?處理請(qǐng)求
??serverHandler{c.server}.ServeHTTP(w,?w.req)
?}
}
我們繼續(xù)跟蹤ServeHTTP方法,ServeMux是一個(gè)HTTP請(qǐng)求的多路復(fù)用器,在這里可以根據(jù)請(qǐng)求的URL匹配合適的處理器,我們看代碼:
func?(mux?*ServeMux)?ServeHTTP(w?ResponseWriter,?r?*Request)?{
?if?r.RequestURI?==?"*"?{
??if?r.ProtoAtLeast(1,?1)?{
???w.Header().Set("Connection",?"close")
??}
??w.WriteHeader(StatusBadRequest)
??return
?}
????//?進(jìn)行路由匹配,獲取注冊(cè)的處理函數(shù)
?h,?_?:=?mux.Handler(r)
????//?這塊就是執(zhí)行我們注冊(cè)的handler,也就是例子中的getProfile()
?h.ServeHTTP(w,?r)
}
在mux.Handler()中我們就看到了路由匹配的代碼:
func?(mux?*ServeMux)?handler(host,?path?string)?(h?Handler,?pattern?string)?{
?mux.mu.RLock()
?defer?mux.mu.RUnlock()
?//?Host-specific?pattern?takes?precedence?over?generic?ones
?if?mux.hosts?{
??h,?pattern?=?mux.match(host?+?path)
?}
?if?h?==?nil?{
??h,?pattern?=?mux.match(path)
?}
?if?h?==?nil?{
??h,?pattern?=?NotFoundHandler(),?""
?}
?return
}
func?(mux?*ServeMux)?match(path?string)?(h?Handler,?pattern?string)?{
?//?先從map中查找
?v,?ok?:=?mux.m[path]
?if?ok?{
????????//?找打了返回注冊(cè)的函數(shù)
??return?v.h,?v.pattern
?}
?//?從切片中進(jìn)行前綴匹配
?for?_,?e?:=?range?mux.es?{
??if?strings.HasPrefix(path,?e.pattern)?{
???return?e.h,?e.pattern
??}
?}
?return?nil,?""
}
服務(wù)端的代碼看主邏輯主要是看兩部分,一個(gè)是注冊(cè)處理器,標(biāo)準(zhǔn)庫使用map進(jìn)行存儲(chǔ),本質(zhì)是一個(gè)靜態(tài)索引,同時(shí)維護(hù)了一個(gè)切片,用來做前綴匹配,只要以/結(jié)尾的,都會(huì)在切片中存儲(chǔ);服務(wù)端監(jiān)聽端口本質(zhì)也是使用net網(wǎng)絡(luò)庫進(jìn)行TCP連接,然后監(jiān)聽對(duì)應(yīng)的TCP連接,每一個(gè)HTTP請(qǐng)求都會(huì)開一個(gè)goroutine去處理請(qǐng)求,所以如果有海量請(qǐng)求,會(huì)在一瞬間創(chuàng)建大量的goroutine,這個(gè)可能是一個(gè)性能瓶頸點(diǎn),所以小伙伴要注意下這塊的性能問題;
net/HTTP的總體代碼行數(shù)是比較多的,我們只需要看主要邏輯是怎么實(shí)現(xiàn)的就可以了,別人問你原理能打出來個(gè)所以然就行,不必要扣細(xì)節(jié),當(dāng)出現(xiàn)問題或者想具體了解某部分協(xié)議的時(shí)候在細(xì)看源碼對(duì)應(yīng)部分即可;
像我們過了一遍源碼后我們就知道當(dāng)前net/http的一些優(yōu)缺點(diǎn),比如優(yōu)點(diǎn)是HTTP客戶端使用了連接池,避免頻繁建立帶來的大開銷,缺點(diǎn)是HTTP服務(wù)端的路由只是一個(gè)靜態(tài)索引匹配,對(duì)于動(dòng)態(tài)路由匹配支持的不好,并且每一個(gè)請(qǐng)求都會(huì)創(chuàng)建一個(gè)gouroutine進(jìn)行處理,海量請(qǐng)求到來時(shí)需要考慮這塊的性能瓶頸;
net/http標(biāo)準(zhǔn)庫可以讓我們很快的就實(shí)現(xiàn)一個(gè)HTTP服務(wù)器,并且也有很多我們值得借鑒學(xué)習(xí)的地方,所以源碼的學(xué)習(xí)還是很必要的;
好啦,今天的文章就到這里了,我是asong,我們下期見~