日本综合一区二区|亚洲中文天堂综合|日韩欧美自拍一区|男女精品天堂一区|欧美自拍第6页亚洲成人精品一区|亚洲黄色天堂一区二区成人|超碰91偷拍第一页|日韩av夜夜嗨中文字幕|久久蜜综合视频官网|精美人妻一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
面試官:Net/Http庫知道嗎?能說說優(yōu)缺點(diǎn)嗎?

前言

哈嘍,大家后,我是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)槲覜]有看過這部分源碼,所以一首涼涼送給我;

目前創(chuàng)新互聯(lián)建站已為上千的企業(yè)提供了網(wǎng)站建設(shè)、域名、網(wǎng)站空間、網(wǎng)站托管、服務(wù)器托管、企業(yè)網(wǎng)站設(shè)計(jì)、閔行網(wǎng)站維護(hù)等服務(wù),公司將堅(jiān)持客戶導(dǎo)向、應(yīng)用為本的策略,正道將秉承"和諧、參與、激情"的文化,與客戶和合作伙伴齊心協(xié)力一起成長,共同發(fā)展。

廢話不多說,接下來請跟著我的腳步我們一起探索net/http;

本文代碼基于:Go1.19.3

net/http庫的一個(gè)小demo

服務(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è)簡單的例子,我們可以知道客戶端我們主要使用http.Client{},服務(wù)端我們主要使用http.ListenAndServe和http.HandleFunc,所以我們就可以從這兩個(gè)包入手來分別看一看客戶端和服務(wù)端的代碼具體是怎么封裝;

客戶端實(shí)現(xiàn)

客戶端級(jí)別最高的抽象是net/http.Client{},具體結(jié)構(gòu)如下:

type Client struct {
Transport RoundTripper
CheckRedirect func(req *Request, via []*Request) error
Jar CookieJar
Timeout time.Duration
}
  • Transport:其類型是RoundTripper,RoundTrip代表一個(gè)本地事務(wù),RoundTripper接口的實(shí)現(xiàn)主要有三個(gè):Transport、http2Transport、fileTransport,其目的是支持更好的擴(kuò)展性;
  • CheckRedirect:用來做重定向
  • Jar:其類型是CookieJar,用來做cookie管理,CookieJar接口的實(shí)現(xiàn)Jar結(jié)構(gòu)體在源碼包net/http/cookiejar/jar.go;

客戶端可以直接通過net/http.DefaultClient發(fā)起HTTP請求,也可以自己構(gòu)建新的net/http.Client實(shí)現(xiàn)自定義的HTTP事務(wù),多數(shù)情況下我們使用默認(rèn)的客戶端發(fā)出的請求就可以滿足需求;

我們畫一個(gè)UML圖,如下所示:

image-20221204160448320

了解HTTP客戶端的基本結(jié)構(gòu),我們接下來就開始分析客戶端的基本實(shí)現(xiàn);

構(gòu)建Request

net/http包的Request結(jié)構(gòu)體封裝好了HTTP請求所需的必要信息:

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請求的方法、URL、協(xié)議版本、協(xié)議頭以及請求體等字段,還包括了指向響應(yīng)的引用:Response;其提供了NewRequest()、NewRequestWithContext()兩個(gè)方法用來構(gòu)建請求,這個(gè)方法可以校驗(yàn)HTTP請求的字段并根據(jù)輸入的參數(shù)拼裝成新的請求結(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)建請求過程中只有處理body的時(shí)候稍有一些復(fù)雜,我們需要根據(jù)body的類型使用不同的方法將其包裝成io.ReadCloser類型;

啟動(dòng)事務(wù)

構(gòu)建HTTP請求后,接下來我們需要開啟HTTP事務(wù)進(jìn)行請求并且等待遠(yuǎn)程響應(yīng),我們以net/http.Client.Do()方法為例子,我們看一下它的調(diào)用鏈路:

  • net/http.Client.Do()
  • net/http.Client.do()
  • net/http.Client.send()
  • net/http.Send()
  • net/http.Transport.RoundTrip()

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 {
// 用來檢測ctx退出信號(hào)
select {
case <-ctx.Done():
req.closeBody()
return nil, ctx.Err()
default:
}

// 獲取連接,這塊就是我們要看的重點(diǎn),Go語言通過連接池對資源進(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請求并在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語言使用了連接池對資源進(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 < t.MaxConnsPerHost {
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
......
}

處理HTTP請求

net/http.persistConn.roundTrip()會(huì)處理HTTP請求,我們看其具體實(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請求中的內(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é)議版本、請求頭等內(nèi)容;

小結(jié)

我們簡單總結(jié)一下net/http庫中HTTP客戶端的實(shí)現(xiàn):

  • net/http.Client是級(jí)別最高的抽象,其中transport用于開啟HTTP事務(wù),jar用于處理cookie;
  • net/http.Transport中主要邏輯兩部分:
  • 從連接池中獲取持久化連接
  • 使用持久化連接處理HTTP請求

net/http庫中默認(rèn)有一個(gè)DefaultClient可以直接使用,DefaultClient有對應(yīng)DefaultTransport,可以滿足我們大多數(shù)場景,如果需要使用自己管理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請求的寫入和響應(yīng)的解析,然后使用channel進(jìn)行通信,所以要合理利用連接池,避免大量的TCP連接的建立可以優(yōu)化性能;

服務(wù)端

我們可以用net/http庫快速搭建HTTP服務(wù),HTTP服務(wù)端主要包含兩部分:

  • 注冊處理器:net/http.HandleFunc函數(shù)用于注冊處理器
  • 監(jiān)聽端口:net/http.ListenAndServe用于處理請求

注冊處理器

直接調(diào)用net/http.HandleFunc可以注冊路由和處理函數(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處理請求,DefaultServeMux本質(zhì)是ServeMux:

type ServeMux struct {
mu sync.RWMutex // 讀寫鎖,保證并發(fā)安全,注冊處理器時(shí)會(huì)加寫鎖做保護(hù)
m map[string]muxEntry // 路由規(guī)則,一個(gè)string對應(yīng)一個(gè)mux實(shí)體,這里的string就是注冊的路由表達(dá)式
es []muxEntry // slice of entries sorted from longest to shortest.
hosts bool // whether any patterns contain hostnames
}
  • mu:需要加讀寫鎖保證并發(fā)安全,注冊處理器時(shí)會(huì)加寫鎖保證寫map的數(shù)據(jù)正確性,這個(gè)map就是pattern和handler;
  • m:存儲(chǔ)路由規(guī)則,key就是pattern,value是muEntry實(shí)體,muEntry實(shí)體中包含:pattern和handler
  • es:存儲(chǔ)的也是muxEntry實(shí)體,因?yàn)槲覀兪褂胢ap存儲(chǔ)路由和handler的對應(yīng)關(guān)系,所以只能索引靜態(tài)路由,并不支持[path_param],所以這塊的作用是當(dāng)在map中沒有找到匹配的路由時(shí),會(huì)遍歷這個(gè)切片進(jìn)行前綴匹配,這個(gè)切片按照路由長度進(jìn)行排序;
  • hosts?:這個(gè)也是用來應(yīng)對特殊case,如果我們注冊的路由沒有以/開始,那么就認(rèn)為我們注冊的路由包含host,所以路由匹配時(shí)需要加上host;

我看看一下路由注冊函數(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)為注冊的路由加上了host,所以在路由匹配時(shí)使用host+path進(jìn)行匹配;
if pattern[0] != '/' {
mux.hosts = true
}
}

監(jiān)聽端口

net/http庫提供了ListenAndServe()用來監(jiān)聽TCP連接并處理請求:

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)等待客戶端的請求:

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 {
// 接收客戶端請求
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處理客戶端請求
go c.serve(connCtx)
}
}

從上述代碼我們可以到每個(gè)HTTP請求服務(wù)端都會(huì)單獨(dú)創(chuàng)建一個(gè)goroutine來處理請求,我們一下處理過程:

// 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 {
// 讀取請求,從連接中獲取HTTP請求并構(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 {
}
// 處理請求
serverHandler{c.server}.ServeHTTP(w, w.req)
}
}

我們繼續(xù)跟蹤ServeHTTP方法,ServeMux是一個(gè)HTTP請求的多路復(fù)用器,在這里可以根據(jù)請求的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)行路由匹配,獲取注冊的處理函數(shù)
h, _ := mux.Handler(r)

分享題目:面試官:Net/Http庫知道嗎?能說說優(yōu)缺點(diǎn)嗎?
網(wǎng)頁路徑:http://www.dlmjj.cn/article/dpsojji.html