Go的web工作原理

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。在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。當然,這裡也可以傳入自訂的路由器。

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

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

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

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

我們再順著DefaultServeMuxHandleFunc方法繼續點下去,如下:

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

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

這個HandlerFunc類型到底是個什麼呢?如下:

type HandlerFunc func(ResponseWriter, *Request)

看來和我們定義的SayHello方法的類型都差不多。但是!!!
這個HandlerFunc預設實現了ServeHTTP介面!這樣HandlerFunc對象就有了ServeHTTP方法!如下:

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

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

接下來,我們返回去繼續看muxHandle方法,也就是這段代碼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)    }    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}//muxEntrytype 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 returngo 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()


聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.