前言
在本文中以及下篇文章中,我们会研习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)
- }
我们启动这样一个程序,并在浏览器输入 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/",nil)
- }
当我们在浏览器输入http://localhost:8080/bar
时,会看到浏览器的URL变成了http://localhost:8080/bar/
而且页面打印出了Hello
。实际上,这是两个http请求:
- 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 对/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/","Hello")
- })
- http.HandleFunc("/bar/bbb/","bbb")
- })
- http.HandleFunc("/foo",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的源码,学习它的路由匹配方式。