Golang 路由匹配浅析[1]
前言在本文中以及下篇文章中,我们会研习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","World") }) http.ListenAndServe(":8080",nil) } 我们启动这样一个程序,并在浏览器输入 注册跟随几步代码进去,会发现Golang 定义了这样一个结构 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 } 看到这里,我们可以大致猜到 // 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} } }
func main() { http.HandleFunc("/bar/",nil) } 当我们在浏览器输入 Request URL: http://127.0.0.1:8080/bar Request Method: GET Status Code: 301 Moved Permanently Remote Address: 127.0.0.1:8080 Request URL: http://localhost:8080/bar/ Request Method: GET Status Code: 200 OK (from disk cache) Remote Address: [::1]:8080 这正是server 对 匹配我们都知道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 里面的 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) } 我们看到当 http.ListenAndServe(":8080",nil) 我们在监听服务的时候,传入的handler 确实是 // 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) }
// 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 } 在 func main() { http.HandleFunc("/bar/","Hello") }) http.HandleFunc("/bar/bbb/","bbb") }) http.HandleFunc("/foo",nil) } 此时在浏览器中输入 总结至此,我们浅析了Golang的路由匹配过程,注册过程将pattern 和相应handler 注册到一个 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |