golang http server 源碼解析與說明

golang http server 源碼解析與說明

本文簡單的介紹golang http服務端路由的註冊解析,簡單剖析http server handler的代碼。

HTTP server–簡而言之就是一個支持http協議的服務,http是一個相對簡單的請求—響應的協議,通常是運行在TCP連接之上, 通過客戶端發送請求到服務端,獲取服務端的響應。基本模型如下:


這裏寫圖片描述

HTTP服務的簡單example

package main  
import ( 
"fmt" 
"net/http" 
) 

//③處理請求,返回結果 
func sayhelloName(w http.ResponseWriter, r *http.Request) { 
    fmt.Fprintln(w, "hello world") 
}

func main() { 
    //①路由註冊 
    http.HandleFunc("/", sayhelloName)  

    //②服務監聽   
    err := http.ListenAndServe(":8080", nil)  
    if err != nil {
    log.Fatal("ListenAndServe: ", err)
    }
}   

如上example已經實現了一個簡單的http 服務,其僅僅處理一個URL 爲IP:8080/的請求, 其返還的信息是“hello world”。


這裏寫圖片描述

源碼分析

以上代碼中有兩處至爲關鍵,http服務怎麼知道有sayhelloName函數被註冊以及其爲誰註冊,另一處則是請求怎樣找到其處理函數。

函數註冊:

從上面的代碼路由註冊開http.HandleFunc始看:

    func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
    DefaultServeMux.HandleFunc(pattern, handler)
    }

這裏使用了http.HandleFunc是使用http包自帶的DefaultServerMux來進行服務的路由註冊與管理。

    func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
    mux.Handle(pattern, HandlerFunc(handler))
    }

    func (mux *ServeMux) Handle(pattern string, handler Handler) {
    mux.mu.Lock()
    defer mux.mu.Unlock()
    //空路徑判斷
    if pattern == "" {
        panic("http: invalid pattern " + pattern)
    }
    //空handler判斷
    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)
    }
    //註冊添加到map表
    mux.m[pattern] = muxEntry{explicit: true, h: handler, pattern: pattern}
    //是否是以URL hosts開頭
    if pattern[0] != '/' {
        mux.hosts = true
    }
     // 如果URL以字符/結尾,則多註冊註冊一個redirectHandler,訪問/tree時重定向到/tree/
    // Helpful behavior:
    // If pattern is /tree/, insert an implicit permanent redirect for /tree.
    // It can be overridden by an explicit registration.
    n := len(pattern)
    if n > 0 && pattern[n-1] == '/' && !mux.m[pattern[0:n-1]].explicit {
        // If pattern contains a host name, strip it and use remaining
        // path for redirect.
        path := pattern
        if pattern[0] != '/' {
        // In pattern, at least the last character is a '/', so
        // strings.Index can't be -1.
        path = pattern[strings.Index(pattern, "/"):]
        }
        url := &url.URL{Path: path}
        mux.m[pattern[0:n-1]] = muxEntry{h: RedirectHandler(url.String(), StatusMovedPermanently), pattern: pattern}
    }
}

大部分的註冊函數到此結束, 但是有些需要重定向的則需要繼續看下面這部分:

func RedirectHandler(url string, code int) Handler {
    return &redirectHandler{url, code}
}

type redirectHandler struct {
url  string
code int
}

func (rh *redirectHandler) ServeHTTP(w ResponseWriter, r *Request) {
Redirect(w, r, rh.url, rh.code)
}

以上代碼主要是處理一些重定向的請求。

路由解析:

路由的解析過程是以上過程的反向尋找,它在http server運行的時候,等待接受到http請求一行解析 http request的頭部信息,找到request的URL,根據該URL進行查詢。這裏先簡單說這些,下面http服務運行中進行詳細分析。

http重要數據結構

  • ServeMux
    Http ServerMux 其實是一個http的多路轉發器(路由器),負責接受http handler的註冊和路由解析。在服務開始對外服務之前接受handler的註冊, 將所有的路徑與處理函數成對的存放在一個map表中, 當接收到URL請求的時候根據URL請求所帶的路徑到map表中進行查詢,查到對應的handler函數對該請求進行處理或者說成是服務。
    type ServeMux struct {
    mu sync.RWMutex
    m map[string]muxEntry
    hosts bool // whether any patterns contain hostnames
    }

    type muxEntry struct {
        explicit bool
        h        Handler
        pattern  string
    }
    
    type Handler interface {
        ServeHTTP(ResponseWriter, *Request)
    }
    
  • ServeHTTP

    // ServeHTTP calls f(w, r).    
    func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {  
        f(w, r)  
    }  
    
  • HandlerFunc

    type HandlerFunc func(ResponseWriter, *Request) 
    
  • Server
    http Server 是一個http服務對象, 是一個http服務的總體數據結構。
    type Server struct {
    Addr string // TCP address to listen on, “:http” if empty
    Handler Handler // handler to invoke, http.DefaultServeMux if nil
    ReadTimeout time.Duration // maximum duration before timing out read of the request
    WriteTimeout time.Duration // maximum duration before timing out write of the response
    TLSConfig *tls.Config // optional TLS config, used by ListenAndServeTLS

        // MaxHeaderBytes controls the maximum number of bytes the
        // server will read parsing the request header's keys and
        // values, including the request line. It does not limit the
        // size of the request body.
        // If zero, DefaultMaxHeaderBytes is used.
        MaxHeaderBytes int
    
        // TLSNextProto optionally specifies a function to take over
        // ownership of the provided TLS connection when an NPN/ALPN
        // protocol upgrade has occurred. The map key is the protocol
        // name negotiated. The Handler argument should be used to
        // handle HTTP requests and will initialize the Request's TLS
        // and RemoteAddr if not already set. The connection is
        // automatically closed when the function returns.
        // If TLSNextProto is nil, HTTP/2 support is enabled automatically.
        TLSNextProto map[string]func(*Server, *tls.Conn, Handler)
    
        // ConnState specifies an optional callback function that is
        // called when a client connection changes state. See the
        // ConnState type and associated constants for details.
        ConnState func(net.Conn, ConnState)
    
        // ErrorLog specifies an optional logger for errors accepting
        // connections and unexpected behavior from handlers.
        // If nil, logging goes to os.Stderr via the log package's
        // standard logger.
        ErrorLog *log.Logger
    
        disableKeepAlives int32     // accessed atomically.
        nextProtoOnce     sync.Once // guards setupHTTP2_* init
        nextProtoErr      error     // result of http2.ConfigureServer if used
    }
    

http服務創建的三種模式

服務創建的三種模式是說http服務三種代碼風格,以及http ListenAndServer時候的對象配置模式。其實更準確的說法爲路由服務註冊的三種方式以及對應的服務創建模式。

  1. 模式一
    模式一也是最簡單的模式,就是類似於例子中那種寫法,直接ListenAndServe(:port, nil), 這裏寫的nil實際上是使用了http包自帶的http.DefaultServeMux來進行路由的註冊於解析。
    這種方法適合非常簡單和沒有太多路由請求的簡單httpserver, 比如說這個http server僅僅支持單一的IP:Port/talk 這樣的路由,或者簡單的幾個路由。對應稍微重一點服務來說都是不好用的。

    package main  
    import ( 
    "fmt" 
    "net/http" 
    ) 
    
    //③處理請求,返回結果 
    func sayhelloName(w http.ResponseWriter, r *http.Request) { 
    fmt.Fprintln(w, "hello world") 
    }
    
    func main() { 
    //①路由註冊 
    http.HandleFunc("/", sayhelloName)  
    
    //②服務監聽   
    err := http.ListenAndServe(":8080", nil)  
    if err != nil {
    log.Fatal("ListenAndServe: ", err)
    }
    }   
    
  2. 模式二
    第二種模式就是自定義一個ServeMux, 然後自己在自定義的ServeMux註冊路由於路由處理函數。最後ListenAndServe自定義的ServeMux。

    package main  
    import ( 
    "fmt" 
    "net/http" 
    ) 
    
    //③處理請求,返回結果 
    func sayhelloName(w http.ResponseWriter, r *http.Request) { 
        fmt.Fprintln(w, "hello world") 
    }
    
    func main() { 
        mux := http.NewServeMux()
        //①路由註冊 
        mux.Handle("/", sayhelloName)
        //mux.Handlefunc("/", sayhelloName)
    
        //②服務監聽   
        err := http.ListenAndServe(":8080", mux)  
        if err != nil {
        log.Fatal("ListenAndServe: ", err)
        }
    }     
    
  3. 模式三
    第三種模式是自定義server, 然後使用自定義的server的ListenAndServe()。這種模式完全的掌握和管理服務端的事物,一般的服務都採用這種模式。這種寫法也比較符合對象化的概念。

    package main  
    import ( 
    "fmt" 
    "net/http" 
    ) 
    
    //③處理請求,返回結果 
    func sayhelloName(w http.ResponseWriter, r *http.Request) { 
        fmt.Fprintln(w, "hello world") 
    }
    
    func main() { 
        mux := http.NewServeMux()
        //①路由註冊 
        mux.Handle("/", sayhelloName)
        //mux.Handlefunc("/", sayhelloName)
    
    s := &http.Server{
        Addr: ":8080:,
        Handler: mux, //指定路由或處理器,不指定時爲nil,表示使用默認的路由DefaultServeMux
        ReadTimeout: 20 * time.Second,
        WriteTimeout: 20 * time.Second,
        MaxHeaderBytes: 1 << 20,
        ConnState: //指定連接conn的狀態改變時的處理函數
        ...
        }
    
    
    
        //②服務監聽   
        err := s.ListenAndServe()  
        if err != nil {
        log.Fatal("ListenAndServe: ", err)
        }
    }
    

http服務的執行過程

在前面我們分析了http的路由註冊,在路由註冊完成以準備開始運行服務並提供訪問,通常的的起始點是ListenAndServer方法。
這裏我們依然先使用例子中的默認方式來進行分析:

http.ListenAndServe(":12345", nil)-->

func ListenAndServe(addr string, handler Handler) error {
    // 構造一個http的server,其實我們上面所說的第三種方式就是是自定義了一個server
    server := &Server{Addr: addr, Handler: handler}
    // server 進行監聽和提供訪問
    return server.ListenAndServe()
    }

上面的代碼和註釋中以及完成了服務的構造,以及監聽並對外服務。
我們先來看一下服務的構造, 很簡單:
server := &Server{Addr: addr, Handler: handler} 就像我們模式三種的那樣給new 一個server,然後給server的addr 和handler進行賦值。
我們再來看一下Server的ListenAndServe():

func (srv *Server) ListenAndServe() error {
    addr := srv.Addr
    if addr == "" {
        addr = ":http"
    }
    //這裏創建一個tcplisten, 監聽tcp連接
    ln, err := net.Listen("tcp", addr)
    if err != nil {
        return err
    }
    return srv.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)})
}

func Listen(net, laddr string) (Listener, error) {
    addrs, err := resolveAddrList(context.Background(), "listen", net, laddr, nil)
    if err != nil {
        return nil, &OpError{Op: "listen", Net: net, Source: nil, Addr: nil, Err: err}
    }
    var l Listener
    switch la := addrs.first(isIPv4).(type) {
    case *TCPAddr:
        //ListenTCP-->listenTCP-->fd=internetSocket return &TCPListener{fd}
        l, err = ListenTCP(net, la) 
    case *UnixAddr:
        l, err = ListenUnix(net, la)
    default:
        return nil, &OpError{Op: "listen", Net: net, Source: nil, Addr: la, Err: &AddrError{Err: "unexpected address type", Addr: laddr}}
    }
    if err != nil {
        return nil, err // l is non-nil interface containing nil pointer
    }
    return l, nil
}

從代碼來看先創建了一個tcp的監聽,實際上是一個的TCP連接,然後將該TCP連接的fd轉換成爲TCPListener,再講該TCPListener轉換爲tcpKeepAliveListener, 主要是tcpKeepAliveLitener的Accept方法帶有定時發送心跳的設置,以實現連接保持。

func (ln tcpKeepAliveListener) Accept() (c net.Conn, err error) {
    tc, err := ln.AcceptTCP()
    if err != nil {
        return
    }
    tc.SetKeepAlive(true) //設置發送心跳
    tc.SetKeepAlivePeriod(3 * time.Minute) //發送心跳週期
    return tc, nil
}

真正的開始服務現在纔開始:

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
    }

    // TODO: allow changing base context? can't imagine concrete
    // use cases yet.
    baseCtx := context.Background()
    ctx := context.WithValue(baseCtx, ServerContextKey, srv)
    ctx = context.WithValue(ctx, LocalAddrContextKey, l.Addr())
    for {
        rw, e := l.Accept()
        if e != nil {
            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
        c := srv.newConn(rw)
        c.setState(c.rwc, StateNew) // before Serve can return
        go c.serve(ctx)
    }
}

上面主要的部分是 for{}裏面, Accept會獲取到連接請求,然後調用srv.newConn()進行連接,設置連接狀態,然後有獨立的go 線程進行Serve該連接。這就是是http服務提供服務。接下來,c.serve()中通常的request請求會調用到 serverHandler{c.server}.ServeHTTP(w, w.req), 準備開始路由解析過程。

http路由解析過程

接着上面http服務serverHandler{c.server}.ServeHTTP(w, w.req):

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)
}  

從這段代碼上可以看出上面創建http.ListenAndServe(“:8080”, nil) 時填寫的nil爲啥能夠正確提供服務了。繼續分析代碼最後一行handler實際上使用mux,因此繼續如下:

 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
    }
    h, _ := mux.Handler(r)
    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)
}

最終調用到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
}

這裏開始調用match方法,從字面上來講就行對比請求的URL,簡單猜測就是查表,再繼續往下看:

func (mux *ServeMux) match(path string) (h Handler, pattern string) {
    var n = 0
    for k, v := range mux.m {
        if !pathMatch(k, path) {
            continue
        }
        if h == nil || len(k) > n {
            n = len(k)
            h = v.h
            pattern = v.pattern
        }
    }
    return
}   

果然,一個key Value的表查詢。這樣該請求的handler方法就找到了,因此http請求的路由解析或者說根據請求查詢處理函數其實就是一個mux的handler表查詢。
查找到handler後執行h.serverHttp(w,r)方法,

func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
    f(w, r)
}

將handler轉換爲ServeHTTP的藉口。至此路由解析與http服務執行的分析接近結束,剩下的是自己的handler處理業務邏輯。

http常用方法的定義與使用

最常用的http方法用俗語表示爲增刪改查————>Post, Delete, Put, Get。處理這四種最常用的方法外http還有比較常用的Head,Patch,Trace,Options 方法,這裏簡單的說Head跟Get非常類似,Patch跟Put非常類似。
http 方法在request請求的Hearder裏面有設置,在發送http請求的時候其僅僅只是http包的一個屬性。在server接受到http request的時候其實是不太care這個屬性的,方法一直被寫到知道路由信息解析完成到達這層路由的處理函數中,在該函數在紅檢測http request的方法,在進行Dispatch, 這種處理方式相當於把 http request方法當做是http請求的一個路徑,因此處理的時候可以當做二次路由進行處理。
比較好的服務端或者大型http服務的服務段在這種情況下通常是自定義server,在server中的handler定義一個對象,對象有Post、Put、Delete和Get方法, 把request的方法當做對象的方法進行調用。

http重定向

http包自帶有部分的錯誤處理或者說重定向函數:FileServer,NotFoundHandler、StripPrefix、TimeoutHandler,除了這些,http還自帶RedirectHandler函數是用來專門提供重定向的:它返回一個請求處理器,該處理器會對每個請求都使用狀態碼code重定向到其他URL上。

httpTLS的處理

TLS 是一種加密的協議, https服務即爲http TLS加密的結構,在需要http加密的時候,其服務端和客戶端都有相應的修改。
HTTP服務段在建立服務的時候使用如下模式:

http.ListenAndServeTLS(":8081", "server.crt",
                       "server.key", nil)

以上方法既可以給HTTP服務段進行加密,只是server.crt跟server.key 需要生成或購買。

HTTP客戶端則如下:

tr := &http.Transport{
    TLSClientConfig:    &tls.Config{****}, //****這裏是TLS的相關設置
    }
    client := &http.Client{Transport: tr}

根據詳細的內容參見:https://studygolang.com/articles/2946

Golang中http錯誤的處理

這裏主要是關於golang語言自注冊的http的handler在處理請求是遇到錯誤的話很多時候直接進行abort或者panic的處理。 這個跟golang語言的語法有關係,golang中的recover會補貨http request handler裏面遇到錯誤時給出的panic,從而關閉連接,整體進程不會受到影響。

http 客戶端的簡單模型

一般來講,http服務端的程序相對簡單,代碼模型如下:

  1. 第一種模型–使用DefaultClient:

    resq, err = http.Get(URL)
    resq, err = http.Post(URL, BodyType, BodyBuffer)
    resq, err = http.PostForm(URL, url.Values{key:value,key:value...})
    
    // 設置關閉response主體
    defer resp.Body.Close()
    
    if err!= nil {
    // TODO 錯誤處理
    }
    
    if resp.StatusCode != ?? {
    //這裏主要是處理一些異常的狀態值
    }
    //讀取response的Body信息
    bodyText, err := ioutil.ReadAll(resp.Body)
    if err != nil {
    //TODO 錯誤信息
    }
    

    這種格式寫法簡單,但是無法設置頭部信息。

  2. 第二種模型:

    //初始化一個Client
    client := &http.Client{}
    //創建request 
    req, err := http.NewRequest("GET", URL, nil)
    //設置Header 
    req.Header.Set("string of Key", "string of Value")
    // 發送請求並等待response
    resp, err := client.Do(req)
    // 設置關閉response主體
    defer resp.Body.Close()
    
    if err != nil {
        //TODO 錯誤處理
    }
    //檢查response 狀態值並處理
    if resp.StatusCode != ?? {
        //這裏主要是處理一些異常的狀態值
    }
    //讀取response的Body信息
    bodyText, err := ioutil.ReadAll(resp.Body)
    if err != nil {
    //TODO 錯誤信息
    }  
    

    這種寫法自己定義client, 自創request,可以設置request頭信息。
    由此,如果遇到https這種請求,則只能使用第二種模式。如下:

    tr := &http.Transport{
    TLSClientConfig:    &tls.Config{****}, //****這裏是TLS的相關設置
    }
    client := &http.Client{Transport: tr}
    

常見的http statu 信息

  1. 2XX 通常表示請求成功完成。具體其信息,請參見http.status
    200 200OK, 表示請求被正確處理
    201 針對POST, 表示成功創建請求的對象

  2. 3XX 通常表示請求成功完成。具體其信息,請參見http.status

  3. 4XX 通常表示用戶信息錯誤–客戶端配置的信息出錯。具體其信息,請參見http.status
    400 表示bad request
    401 表示用戶認證錯誤
    403 表示禁止訪問
    404 表示訪問的資源未發現

  4. 5XX 通常表示請求成功完成。具體其信息,請參見http.status
    500 內部錯誤

參考文獻:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章