Go的web工作原理
在使用ListenAndServe這個方法時,系統就會給我們指派一個路由器,DefaultServeMux是系統默認使用的路由器,如果ListenAndServe這個方法的第2個參數傳入nil,系統就會默認使用DefaultServeMux。當然,這里也可以傳入自定義的路由器。
先來看http.HandleFunc("/", sayHello),從HandleFunc方法點進去,如下:
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {DefaultServeMux.HandleFunc(pattern, handler) }復制代碼在這里調用了DefaultServeMux的HandleFunc方法,這個方法有兩個參數,pattern是匹配的路由規則,handler表示這個路由規則對應的處理方法,并且這個處理方法有兩個參數。
在我們書寫的代碼示例中,pattern對應/,handler對應sayHello,當我們在瀏覽器中輸入http://localhost:9090時,就會觸發sayHello方法。
我們再順著DefaultServeMux的HandleFunc方法繼續點下去,如下:
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方法!如下:
這個細節是十分重要的,因為這一步關乎到當路由規則匹配時,相應的響應方法是否會被調用的問題!這個方法的調用時機會在下一小節中講到。
接下來,我們返回去繼續看mux的Handle方法,也就是這段代碼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 := patternif 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}} } 復制代碼代碼挺多,其實主要就做了一件事,向DefaultServeMux的map[string]muxEntry中增加對應的路由規則和handler。
map[string]muxEntry是個什么鬼?
map是一個字典對象,它保存的是key-value。
[string]表示這個字典的key是string類型的,這個key值會保存我們的路由規則。
muxEntry是一個實例對象,這個對象內保存了路由規則對應的處理方法。
找到相應代碼,如下:
//路由器 type ServeMux struct {mu sync.RWMutexm map[string]muxEntry //路由規則,一個string對應一個mux實例對象,map的key就是注冊的路由表達式(string類型的)hosts bool // whether any patterns contain hostnames }//muxEntry type muxEntry struct {explicit boolh Handler //這個路由表達式對應哪個handlerpattern string }//路由響應方法 type Handler interface {ServeHTTP(ResponseWriter, *Request) //handler的路由實現器 } 復制代碼ServeMux就是這個系統默認的路由器。
最后,總結一下這個部分:
1.調用http.HandleFunc("/", sayHello)
2.調用DefaultServeMux的HandleFunc(),把我們定義的sayHello()包裝成HandlerFunc類型
3.繼續調用DefaultServeMux的Handle(),向DefaultServeMux的map[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.Addrif 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協議搭建了一個服務,然后監控我們設置的端口。
代碼的最后,調用了srv的Serve方法,如下:
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 failureif 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 16220ctx := 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 ErrServerCloseddefault:}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 = 0c := srv.newConn(rw)c.setState(c.rwc, StateNew) // before Serve can returngo 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去服務,每個請求間相互不影響。
在conn的serve方法中,有一句代碼很重要,如下:
serverHandler{c.server}.ServeHTTP(w, w.req) 復制代碼表示serverHandler
也實現了ServeHTTP接口,ServeHTTP方法實現如下:
func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {handler := sh.srv.Handlerif handler == nil {handler = DefaultServeMux}if req.RequestURI == "*" && req.Method == "OPTIONS" {handler = globalOptionsHandler{}}handler.ServeHTTP(rw, req) } 復制代碼在這里如果handler為空(這個handler就可以理解為是我們自定義的路由器),就會使用系統默認的DefaultServeMux,代碼的最后調用了DefaultServeMux的ServeHTTP()
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,然后執行該handler的ServeHTTP方法,也就是這句代碼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.URLurl.Path = preturn 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 onesif 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 = 0for k, v := range mux.m { //mux.m就是系統默認路由的mapif !pathMatch(k, path) {continue}if h == nil || len(k) > n {n = len(k)h = v.hpattern = v.pattern}}return } 復制代碼它會根據用戶請求的URL到路由器里面存儲的map中匹配,匹配成功就會返回存儲的handler,調用這個handler的ServeHTTP()就可以執行到相應的處理方法了,這個處理方法實際上就是我們剛開始定義的sayHello(),只不過這個sayHello()被HandlerFunc又包了一層,因為HandlerFunc實現了ServeHTTP接口,所以在調用HandlerFunc對象的ServeHTTP()時,實際上在ServeHTTP ()的內部調用了我們的sayHello()。
總結一下:
1.調用http.ListenAndServe(":9090",nil)
2.實例化server
3.調用server的ListenAndServe()
4.調用server的Serve方法,開啟for循環,在循環中Accept請求
5.對每一個請求實例化一個Conn,并且開啟一個goroutine為這個請求進行服務go c.serve()
6.讀取每個請求的內容c.readRequest()
7.調用serverHandler的ServeHTTP(),如果handler為空,就把handler設置為系統默認的路由器DefaultServeMux
8.調用handler的ServeHTTP() =>實際上是調用了DefaultServeMux的ServeHTTP()
9.在ServeHTTP()中會調用路由對應處理handler
10.在路由對應處理handler中會執行sayHello()
有一個需要注意的點:
DefaultServeMux和路由對應的處理方法handler都實現了ServeHTTP接口,他們倆都有ServeHTTP方法,但是方法要達到的目的不同,在DefaultServeMux的ServeHttp()里會執行路由對應的處理handler的ServeHttp()。
總結
以上是生活随笔為你收集整理的Go的web工作原理的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 12.1 LNMP架构介绍 12.2 M
- 下一篇: Less代码规范