這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
前言
在本文中以及下篇文章中,我們會研習Golang 的源碼來探究Golang 是如何?HTTP URL 匹配的,並對比 mux的實現。
本人水平有限,如有疏漏和不正確的地方,還請各位不吝賜教,多謝!
Golang 源碼基於1.9.2
本文
我們有這樣一個HTTP 伺服器程式:
func main() { http.HandleFunc("/bar", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, "Hello") }) http.HandleFunc("/foo", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, "World") }) http.ListenAndServe(":8080", nil)}
我們啟動這樣一個程式,並在瀏覽器輸入 http://localhost:8080/bar
,會看到頁面列印出Hello,當我們將URL 換成 http://localhost:8080/foo
時候,頁面會列印出World。正是HTTP server 根據/bar
和/foo
找到了相應的handler來server 這個request。我們跟隨Golang 的源碼來探究這個匹配的過程。
註冊
跟隨幾步代碼進去,會發現Golang 定義了這樣一個結構
type ServeMux struct { mu sync.RWMutex m map[string]muxEntry hosts bool // whether any patterns contain hostnames}
而muxEntry
是這樣定義的
type muxEntry struct { explicit bool h Handler pattern string}
看到這裡,我們可以大致猜到m
這個結構是URL 匹配的關鍵。它以URL Path作為key,而包含相應的Handler的muxEntry
作為Value。這樣,當收到一個HTTP 要求時候,將URL Path 解析出來後,只要在m
中找到對應的handler就可以server 這個request 了。下面我們具體看下handler 的註冊過程
// Handle registers the handler for the given pattern.// If a handler already exists for pattern, Handle panics.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} }}
Helpful behavior
前面的代碼顯而易見,如果這個pattern 沒有註冊,會把handler 註冊到這個pattern 上面。而 Helpful behavior
後面的代碼會做這樣的事情:假如我註冊了/bar/
這樣一個pattern,mux 會預設幫我註冊/bar
這個pattern,而/bar
的handler會將/bar
的請求redirect到/bar/
。我們修改一下我們的main 函數:
func main() { http.HandleFunc("/bar/", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, "Hello") }) http.HandleFunc("/foo", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, "World") }) http.ListenAndServe(":8080", nil)}
當我們在瀏覽器輸入http://localhost:8080/bar
時,會看到瀏覽器的URL變成了http://localhost:8080/bar/
而且頁面列印出了Hello
。實際上,這是兩個http請求:
Request URL: http://127.0.0.1:8080/barRequest Method: GETStatus Code: 301 Moved PermanentlyRemote Address: 127.0.0.1:8080
Request URL: http://localhost:8080/bar/Request Method: GETStatus Code: 200 OK (from disk cache)Remote Address: [::1]:8080
這正是server 對/bar
做了redirect請求。
註冊一個handler 到一個pattern看起來比較簡單,那麼Golang 的HTTP server 是如何serve 一個HTTP request 的呢?
匹配
我們都知道HTTP 協議是基於TCP 實現的,我們先來看一個TCP echo 伺服器
func main() { fmt.Println("Launching server...") // listen on all interfaces ln, _ := net.Listen("tcp", ":8081") for { // accept connection on port conn, _ := ln.Accept() // will listen for message to process ending in newline (\n) message, _ := bufio.NewReader(conn).ReadString('\n') // output message received fmt.Print("Message Received:", string(message)) // sample process for string received newmessage := strings.ToUpper(message) // send new string back to client conn.Write([]byte(newmessage + "\n")) }}
Golang 裡面的net.Listen
封裝了socket()
和bind()
的過程,拿到一個listener
之後,通過調用Accept()
函數阻塞等待新的串連,每次Accept()
函數返回時候,會得到一個TCP 串連。
Golang 裡面的HTTP 服務也是這麼做的:
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) 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) }}
從這也可以看到,對於每一個HTTP 要求,服務端都會起一個goroutine 來serve.
跟隨者源碼一路追溯下去,發現調用了這樣一個函數:
// parseRequestLine parses "GET /foo HTTP/1.1" into its three parts.func parseRequestLine(line string) (method, requestURI, proto string, ok bool) { s1 := strings.Index(line, " ") s2 := strings.Index(line[s1+1:], " ") if s1 < 0 || s2 < 0 { return } s2 += s1 + 1 return line[:s1], line[s1+1 : s2], line[s2+1:], true}
對串連發送的內容進行HTTP 協議解析,得到 HTTP 方法和URI。我們略過其他協議解析和驗證的部分,直接看serve request 的函數:
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)}
我們看到當handler
是nil
時候,會使用package 的預設handlerDefaultServeMux
。再回到我們的main.go:
http.ListenAndServe(":8080", nil)
我們在監聽服務的時候,傳入的handler 確實是nil
,所以使用了DefaultServeMux
,而當我們調用http.HandleFunc
時,正是向DefaultServeMux
註冊了pattern 和相應的handler。DefaultServeMux
的ServeHTTP
方法如下:
// ServeHTTP dispatches the request to the handler whose// pattern most closely matches the request URL.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)}
mux.Handler(r)
方法通過request 找到對應的handler:
// handler is the main implementation of Handler.// The path is known to be in canonical form, except for CONNECT methods.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}// Find a handler on a handler map given a path string.// Most-specific (longest) pattern wins.func (mux *ServeMux) match(path string) (h Handler, pattern string) { // Check for exact match first. v, ok := mux.m[path] if ok { return v.h, v.pattern } // Check for longest valid match. 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}// Does path match pattern?func pathMatch(pattern, path string) bool { if len(pattern) == 0 { // should not happen return false } n := len(pattern) if pattern[n-1] != '/' { return pattern == path } return len(path) >= n && path[0:n] == pattern}
在match
函數中首先檢查精確匹配,如果匹配到,直接返回相應的handler。如果沒有匹配,遍曆所有註冊path,進行pathMatch
檢查,滿足pathMatch
的最長的path勝出。舉例說明,main
函數如下:
func main() { http.HandleFunc("/bar/", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, "Hello") }) http.HandleFunc("/bar/bbb/", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, "bbb") }) http.HandleFunc("/foo", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, "World") }) http.ListenAndServe(":8080", nil)}
此時在瀏覽器中輸入http://localhost:8080/foo/aaa
,會返回404 page not found
,而輸入http://localhost:8080/bar/aaa
,會返回Hello
。輸入http://localhost:8080/bar/bbb/ccc
時,/bar/
和 /bar/bbb/
都會被匹配到,但是/bar/bbb/
這個pattern 更長,瀏覽器會列印出bbb
總結
至此,我們淺析了Golang的路由匹配過程,註冊過程將pattern 和相應handler 註冊到一個map
中,匹配時先檢查是否有pattern 和path 完全符合,如果沒有,再檢查最長相符。
整個過程看起來比較簡單,直接,但是不能支援正則的路由匹配。
下一篇文章中,將分析mux的源碼,學習它的路由匹配方式。