天天看點

Go語言HTTP Server源碼分析

go語言中http server:

http server,顧名思義,支援http協定的伺服器,http是一個簡單的請求-響應協定,通常運作在tcp之上。通過用戶端發送請求給伺服器得到對應的響應。

Go語言HTTP Server源碼分析

http服務簡單實作

package main  

import ( 

    "fmt" 

    "net/http" 

)  

//③處理請求,傳回結果 

func hello(w http.responsewriter, r *http.request) { 

    fmt.fprintln(w, "hello world") 

}  

func main() { 

    //①路由注冊 

    http.handlefunc("/", hello)  

    //②服務監聽 

    http.listenandserve(":8080", nil) 

Go語言HTTP Server源碼分析

你以為這樣就結束了嗎,不才剛剛開始。

源碼分析

①路由注冊

func handlefunc(pattern string, handler func(responsewriter, *request)) { 

    defaultservemux.handlefunc(pattern, handler) 

defaultservemux是什麼?

defaultservemux是servemux的一個執行個體。

servemux又是什麼?

// defaultservemux is the default servemux used by serve. 

var defaultservemux = &defaultservemux  

var defaultservemux servemux  

type servemux struct { 

    mu    sync.rwmutex 

    m     map[string]muxentry 

    hosts bool  

type muxentry struct { 

    explicit bool 

    h        handler 

    pattern  string 

servemux主要通過map[string]muxentry,來存儲了具體的url模式和handler(此handler是實作handler接口的類型)。通過實作handler的servehttp方法,來比對路由(這一點下面源碼會講到)

很多地方都涉及到了handler,那麼handler是什麼?

type handler interface { 

    servehttp(responsewriter, *request) 

此接口可以算是http server一個樞紐

func (mux *servemux) handlefunc(pattern string, handler func(responsewriter, *request)) { 

    mux.handle(pattern, handlerfunc(handler)) 

type handlerfunc func(responsewriter, *request)  

func (f handlerfunc) servehttp(w responsewriter, r *request) { 

    f(w, r) 

從代碼中可以看出handlerfunc是一個函數類型,并實作了handler接口。當通過調用handlefunc(),把hello強轉為handlerfunc類型時,就意味着 hello函數也實作servehttp方法。

servemux的handle方法:

func (mux *servemux) handle(pattern string, handler handler) { 

    mux.mu.lock() 

    defer mux.mu.unlock()  

    if pattern == "" { 

        panic("http: invalid pattern " + pattern) 

    } 

    if handler == nil { 

        panic("http: nil handler") 

    if mux.m[pattern].explicit { 

        panic("http: multiple registrations for " + pattern) 

    }  

    if mux.m == nil { 

        mux.m = make(map[string]muxentry) 

    //把handler和pattern模式綁定到 

    //map[string]muxentry的map上 

    mux.m[pattern] = muxentry{explicit: true, h: handler, pattern: pattern} 

    if pattern[0] != '/' { 

        mux.hosts = true 

   //這裡是綁定靜态目錄,不作為本片重點。 

    n := len(pattern) 

    if n > 0 && pattern[n-1] == '/' && !mux.m[pattern[0:n-1]].explicit { 

        path := pattern 

        if pattern[0] != '/' { 

            path = pattern[strings.index(pattern, "/"):] 

        } 

        url := &url.url{path: path} 

        mux.m[pattern[0:n-1]] = muxentry{h: redirecthandler(url.string(), statusmovedpermanently), pattern: pattern} 

上面的流程就完成了路由注冊。

②服務監聽

type server struct { 

    addr         string         

    handler      handler        

    readtimeout  time.duration  

    writetimeout time.duration  

    tlsconfig    *tls.config    

    maxheaderbytes int  

    tlsnextproto map[string]func(*server, *tls.conn, handler)  

    connstate func(net.conn, connstate) 

    errorlog *log.logger 

    disablekeepalives int32        nextprotoonce     sync.once  

    nextprotoerr      error      

func listenandserve(addr string, handler handler) error { 

    server := &server{addr: addr, handler: handler} 

    return server.listenandserve() 

//初始化監聽位址addr,同時調用listen方法設定監聽。 

//最後将監聽的tcp對象傳入serve方法: 

func (srv *server) listenandserve() error { 

        addr := srv.addr 

        if addr == "" { 

            addr = ":http" 

        ln, err := net.listen("tcp", addr) 

        if err != nil { 

            return err 

        return srv.serve(tcpkeepalivelistener{ln.(*net.tcplistener)}) 

serve(l net.listener)為每個請求開啟goroutine的設計,保證了go的高并發。

func (srv *server) serve(l net.listener) error { 

    defer l.close() 

    if fn := testhookserverserve; fn != nil { 

        fn(srv, l) 

    var tempdelay time.duration // how long to sleep on accept failure  

    if err := srv.setuphttp2_serve(); err != nil { 

        return err 

    srv.tracklistener(l, true) 

    defer srv.tracklistener(l, false)  

    basectx := context.background() // base is always background, per issue 16220 

    ctx := context.withvalue(basectx, servercontextkey, srv) 

    ctx = context.withvalue(ctx, localaddrcontextkey, l.addr()) 

    //開啟循環進行監聽 

    for { 

       //通過listener的accept方法用來擷取連接配接資料 

        rw, e := l.accept() 

        if e != nil { 

            select { 

            case <-srv.getdonechan(): 

                return errserverclosed 

            default: 

            } 

            if ne, ok := e.(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", e, tempdelay) 

                time.sleep(tempdelay) 

                continue 

            return e 

        tempdelay = 0 

        //通過獲得的連接配接資料,建立newconn連接配接對象 

        c := srv.newconn(rw) 

                c.setstate(c.rwc, statenew) // before serve can return 

       //開啟goroutine發送連接配接請求 

        go c.serve(ctx) 

serve()為核心,讀取對應的連接配接資料進行配置設定

func (c *conn) serve(ctx context.context) { 

    c.remoteaddr = c.rwc.remoteaddr().string() 

        //連接配接關閉相關的處理 

    defer func() { 

        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) 

        if !c.hijacked() { 

            c.close() 

            c.setstate(c.rwc, stateclosed) 

    }()  

    .....  

    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)  

        //讀取用戶端的請求 

        w, err := c.readrequest(ctx) 

        if c.r.remain != c.server.initialreadlimitsize() { 

            // if we read any bytes off the wire, we're active. 

            c.setstate(c.rwc, stateactive) 

        }

                ................. 

        //處理網絡資料的狀态 

        // expect 100 continue support 

        req := w.req 

        if req.expectscontinue() { 

            if req.protoatleast(1, 1) && req.contentlength != 0 { 

                // wrap the body reader with one that replies on the connection 

                req.body = &expectcontinuereader{readcloser: req.body, resp: w} 

        } else if req.header.get("expect") != "" { 

            w.sendexpectationfailed() 

            return 

        }  

        c.curreq.store(w) 

        if requestbodyremains(req.body) { 

            registeronhiteof(req.body, w.conn.r.startbackgroundread) 

        } else { 

            if w.conn.bufr.buffered() > 0 { 

                w.conn.r.closenotifyfrompipelinedrequest() 

            w.conn.r.startbackgroundread() 

        //調用serverhandler{c.server}.servehttp(w, w.req) 

        //方法處理請求 

        serverhandler{c.server}.servehttp(w, w.req) 

        w.cancelctx() 

        if c.hijacked() { 

        w.finishrequest() 

        if !w.shouldreuseconnection() { 

            if w.requestbodylimithit || w.closedrequestbodyearly() { 

                c.closewriteandwait() 

        c.setstate(c.rwc, stateidle) 

        c.curreq.store((*response)(nil)) 

        if !w.conn.server.dokeepalives() { 

        if d := c.server.idletimeout(); d != 0 { 

            c.rwc.setreaddeadline(time.now().add(d)) 

            if _, err := c.bufr.peek(4); err != nil { 

                return 

        c.rwc.setreaddeadline(time.time{}) 

③處理請求,傳回結果

serverhandler 主要初始化路由多路複用器。如果server對象沒有指定handler,則使用預設的defaultservemux作為路由多路複用器。并調用初始化handler的servehttp方法。

type serverhandler struct { 

    srv *server 

func (sh serverhandler) servehttp(rw responsewriter, req *request) { 

    handler := sh.srv.handler 

        handler = defaultservemux 

    if req.requesturi == "*" && req.method == "options" { 

        handler = globaloptionshandler{} 

    handler.servehttp(rw, req) 

這裡就是之前提到的比對路由的具體代碼

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 

    //比對注冊到路由上的handler函數 

    h, _ := mux.handler(r) 

    //調用handler函數的servehttp方法 

    //即hello函數,然後把資料寫到http.responsewriter 

    //對象中傳回給用戶端。 

    h.servehttp(w, r) 

}

func (mux *servemux) handler(r *request) (h handler, pattern string) { 

    if r.method != "connect" { 

        if p := cleanpath(r.url.path); p != r.url.path { 

            _, pattern = mux.handler(r.host, p) 

            url := *r.url 

            url.path = p 

            return redirecthandler(url.string(), statusmovedpermanently), pattern 

    return mux.handler(r.host, r.url.path) 

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 { 

        //如 127.0.0.1/hello 

        h, pattern = mux.match(host + path) 

    if h == nil { 

        // 如  /hello 

        h, pattern = mux.match(path) 

        h, pattern = notfoundhandler(), "" 

    return 

func (mux *servemux) match(path string) (h handler, pattern string) { 

    var n = 0 

    for k, v := range mux.m { 

        if !pathmatch(k, path) { 

            continue 

      //通過疊代m尋找出注冊路由的patten模式 

      //與實際url比對的handler函數并傳回。 

        if h == nil || len(k) > n { 

            n = len(k) 

            h = v.h 

            pattern = v.pattern 

func pathmatch(pattern, path string) bool { 

    if len(pattern) == 0 { 

        // should not happen 

        return false 

        //如果注冊模式與請求uri一樣傳回true,否則false 

    if pattern[n-1] != '/' { 

        return pattern == path 

        //靜态檔案比對 

    return len(path) >= n && path[0:n] == pattern 

将資料寫給用戶端

//主要代碼,通過層層封裝才走到這一步 

func (w checkconnerrorwriter) write(p []byte) (n int, err error) { 

    n, err = w.c.rwc.write(p) 

    if err != nil && w.c.werr == nil { 

        w.c.werr = err 

        w.c.cancelctx() 

serverhandler{c.server}.servehttp(w, w.req)當請求結束後,就開始執行連接配接斷開的相關邏輯。

總結

go語言通過一個servemux實作了的路由多路複用器來管理路由。同時提供一個handler接口提供servehttp方法,實作handler接口的函數,可以處理實際request并傳回response。

servemux和handler函數的連接配接橋梁就是handler接口。servemux的servehttp方法實作了尋找注冊路由的handler的函數,并調用該handler的servehttp方法。

是以說handler接口是一個重要樞紐。

簡單梳理下整個請求響應過程,如下圖

Go語言HTTP Server源碼分析

作者:佚名

來源:51cto