Go的web工做原理

在Go中使用及其簡單的代碼便可開啓一個web服務。以下:
//開啓web服務
func test(){
    http.HandleFunc("/", sayHello)
    err := http.ListenAndServe(":9090",nil)
    if err!=nil {
        log.Fatal("ListenAndServer:",err)
    }
}

func sayHello(w http.ResponseWriter, r *http.Request){
    r.ParseForm()
    fmt.Println("path",r.URL.Path)
    fmt.Println("scheme",r.URL.Scheme)

    fmt.Fprintf(w, "Hello Guest!")
}複製代碼

在使用ListenAndServe這個方法時,系統就會給咱們指派一個路由器,DefaultServeMux是系統默認使用的路由器,若是ListenAndServe這個方法的第2個參數傳入nil,系統就會默認使用DefaultServeMux。固然,這裏也能夠傳入自定義的路由器。web

先來看http.HandleFunc("/", sayHello),從HandleFunc方法點進去,以下:瀏覽器

func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
    DefaultServeMux.HandleFunc(pattern, handler)
}複製代碼

在這裏調用了DefaultServeMuxHandleFunc方法,這個方法有兩個參數,pattern是匹配的路由規則,handler表示這個路由規則對應的處理方法,而且這個處理方法有兩個參數。bash

在咱們書寫的代碼示例中,pattern對應/handler對應sayHello,當咱們在瀏覽器中輸入http://localhost:9090時,就會觸發sayHello方法。併發

咱們再順着DefaultServeMuxHandleFunc方法繼續點下去,以下:tcp

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

在這個方法中,路由器又調用了Handle方法,注意這個Handle方法的第2個參數,將以前傳入的handler這個響應方法強制轉換成了HandlerFunc類型。高併發

這個HandlerFunc類型究竟是個什麼呢?以下:ui

type HandlerFunc func(ResponseWriter, *Request)
複製代碼

看來和咱們定義的SayHello方法的類型都差很少。可是!!!
這個HandlerFunc默認實現了ServeHTTP接口!這樣HandlerFunc對象就有了ServeHTTP方法!以下:url

// ServeHTTP calls f(w, r).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
    f(w, r)
}
複製代碼

這個細節是十分重要的,由於這一步關乎到當路由規則匹配時,相應的響應方法是否會被調用的問題!這個方法的調用時機會在下一小節中講到。spa

接下來,咱們返回去繼續看muxHandle方法,也就是這段代碼mux.Handle(pattern, HandlerFunc(handler))。這段代碼作了哪些事呢?源碼以下:code

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)
    }
    mux.m[pattern] = muxEntry{explicit: true, h: handler, pattern: pattern}

    if pattern[0] != '/' {
        mux.hosts = true
    }

    // 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} } } 複製代碼

代碼挺多,其實主要就作了一件事,向DefaultServeMuxmap[string]muxEntry中增長對應的路由規則和handler

map[string]muxEntry是個什麼鬼?

map是一個字典對象,它保存的是key-value
[string]表示這個字典的keystring類型的,這個key值會保存咱們的路由規則。
muxEntry是一個實例對象,這個對象內保存了路由規則對應的處理方法。

找到相應代碼,以下:

//路由器
type ServeMux struct {
    mu    sync.RWMutex
    m     map[string]muxEntry //路由規則,一個string對應一個mux實例對象,map的key就是註冊的路由表達式(string類型的)
    hosts bool // whether any patterns contain hostnames
}

//muxEntry
type muxEntry struct {
    explicit bool
    h        Handler //這個路由表達式對應哪一個handler
    pattern  string
}

//路由響應方法
type Handler interface {
    ServeHTTP(ResponseWriter, *Request)  //handler的路由實現器
}
複製代碼

ServeMux就是這個系統默認的路由器。

最後,總結一下這個部分:
1.調用http.HandleFunc("/", sayHello)
2.調用DefaultServeMuxHandleFunc(),把咱們定義的sayHello()包裝成HandlerFunc類型
3.繼續調用DefaultServeMuxHandle(),向DefaultServeMuxmap[string]muxEntry中增長路由規則和對應的handler

OK,這部分代碼作的事就這麼多,第一部分結束。

第二部分主要就是研究這句代碼err := http.ListenAndServe(":9090",nil),也就是ListenAndServe這個方法。從這個方法點進去,以下:

func ListenAndServe(addr string, handler Handler) error {
    server := &Server{Addr: addr, Handler: handler}
    return server.ListenAndServe()
}
複製代碼

在這個方法中,初始化了一個server對象,而後調用這個server對象的ListenAndServe方法,在這個方法中,以下:

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)})
}
複製代碼

在這個方法中,調用了net.Listen("tcp", addr),也就是底層用TCP協議搭建了一個服務,而後監控咱們設置的端口。

代碼的最後,調用了srvServe方法,以下:

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 {
        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
        c := srv.newConn(rw)
        c.setState(c.rwc, StateNew) // before Serve can return
        go c.serve(ctx)
    }
}
複製代碼

最後3段代碼比較重要,也是Go語言支持高併發的體現,以下:

c := srv.newConn(rw)
c.setState(c.rwc, StateNew) // before Serve can return
go c.serve(ctx)
複製代碼

上面那一大坨代碼,整體意思是進入方法後,首先開了一個for循環,在for循環內時刻Accept請求,請求來了以後,會爲每一個請求建立一個Conn,而後單獨開啓一個goroutine,把這個請求的數據當作參數扔給這個Conn去服務:go c.serve()。用戶的每一次請求都是在一個新的goroutine去服務,每一個請求間相互不影響。

connserve方法中,有一句代碼很重要,以下:

serverHandler{c.server}.ServeHTTP(w, w.req)
複製代碼

表示serverHandler

實現了ServeHTTP接口,ServeHTTP方法實現以下:

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)
}
複製代碼

在這裏若是handler爲空(這個handler就能夠理解爲是咱們自定義的路由器),就會使用系統默認的DefaultServeMux,代碼的最後調用了DefaultServeMuxServeHTTP()

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是Handler接口對象
    h.ServeHTTP(w, r)       //調用Handler接口對象的ServeHTTP方法實際上就調用了咱們定義的sayHello方法
}
複製代碼

路由器接收到請求以後,若是是*那麼關閉連接,若是不是*就調用mux.Handler(r)返回該路由對應的處理Handler,而後執行該handlerServeHTTP方法,也就是這句代碼h.ServeHTTP(w, r)mux.Handler(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 {
        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) {
    var n = 0
    for k, v := range mux.m {  //mux.m就是系統默認路由的map
        if !pathMatch(k, path) {
            continue
        }
        if h == nil || len(k) > n {
            n = len(k)
            h = v.h
            pattern = v.pattern
        }
    }
    return
}
複製代碼

它會根據用戶請求的URL到路由器裏面存儲的map中匹配,匹配成功就會返回存儲的handler,調用這個handlerServeHTTP()就能夠執行到相應的處理方法了,這個處理方法實際上就是咱們剛開始定義的sayHello(),只不過這個sayHello()HandlerFunc又包了一層,由於HandlerFunc實現了ServeHTTP接口,因此在調用HandlerFunc對象的ServeHTTP()時,實際上在ServeHTTP ()的內部調用了咱們的sayHello()

總結一下:
1.調用http.ListenAndServe(":9090",nil)
2.實例化server
3.調用serverListenAndServe()
4.調用serverServe方法,開啓for循環,在循環中Accept請求
5.對每個請求實例化一個Conn,而且開啓一個goroutine爲這個請求進行服務go c.serve()
6.讀取每一個請求的內容c.readRequest()
7.調用serverHandlerServeHTTP(),若是handler爲空,就把handler設置爲系統默認的路由器DefaultServeMux
8.調用handlerServeHTTP() =>其實是調用了DefaultServeMuxServeHTTP()
9.在ServeHTTP()中會調用路由對應處理handler
10.在路由對應處理handler中會執行sayHello()

有一個須要注意的點:
DefaultServeMux和路由對應的處理方法handler都實現了ServeHTTP接口,他們倆都有ServeHTTP方法,可是方法要達到的目的不一樣,在DefaultServeMuxServeHttp()裏會執行路由對應的處理handlerServeHttp()

相關文章
相關標籤/搜索