golang http路由分发机制

package main

import (  
    "fmt"
    "net/http"
)

func main() {  
    fmt.Println("process running  on 8123")
    http.Handle("/", http.FileServer(http.Dir("./")))
    http.HandleFunc("/halo", func(w http.ResponseWriter, r *http.Request) {
        w.Write([]byte("halo"))
    })
    fmt.Println(http.ListenAndServe(":8123", nil))
}

这是一段最简单的http web服务器了,这里面包含了静态文件服务器:

http.Handle("/", http.FileServer(http.Dir("./")))  

http的请求路由:

http.HandleFunc("/halo", func(w http.ResponseWriter, r *http.Request) {  
        w.Write([]byte("halo"))
    })

在封装自己的go-tool工具包时,关于http的请求总会出现404 not found 于是花了几乎3,4个小时一步一步调试golang源码,慢慢理解了golang http路由分发的机制。

众所周知启动web server都会调用 http.ListenAndServe(":8123", nil)(忽略带ssl的情况),一层一层的跳下去会跳到

func (srv *Server) Serve(l net.Listener) error {  
    ...
    go c.serve(ctx)
}

然后再进入

func (c *conn) serve(ctx context.Context) {  
    ...
    serverHandler{c.server}.ServeHTTP(w, w.req) w.cancelCtx()
        if c.hijacked() {
            return
        }
    ...
}

最终进入

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
    fmt.Println("handler start match hosts ", mux.hosts)
    if mux.hosts {
        h, pattern = mux.match(host + path)
    }
    fmt.Println("handler start match path ", path, h == nil)
    if h == nil {
        h, pattern = mux.match(path)
    }
    fmt.Println("handler start match nil ", h == nil)
    if h == nil {
        h, pattern = NotFoundHandler(), ""
    }
    fmt.Println("h is not nil", h)
    return
}

ps:这个地方的fmt.Println("...")就是调试的过程,不然我怎么知道程序跑到哪里去了?你说用断点?好吧,个人所爱。

在这里就可以看到golang对请求来的路由在这个位置进行mux.match(path),而mux.match的具体实现就是一个字符串匹配的过程,会匹配出最匹配的字符串,并将匹配出来的handler返回出去,然后执行handler.ServeHTTP(w, r),实现最终的http请求。匹配的路由则是注册的所有路由和请求的路由经行比较。

总结: golang的路由分发其实就是把客户端请求来的所有路由同服务器注册的路由进行最匹配比较,找不到则404 not found.在封装自定义的golang http工具包时,如过滤器,钩子等等时,就可以很明确的处理各个请求。