ServerMux ServeMux详解 - 掘金 (juejin.cn)
03.4. Go 的 http 包详解 | 第三章. Web 基础 |《Go Web 编程》
以下实现了一个http服务。编译运行后访问http://127.0.0.1:8080/。
1 2 3 4 5 6 7 8 9 10 func main () { http.HandleFunc("/" , handler) log.Fatal(http.ListenAndServe("localhost:8080" , nil )) } func handler (w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "hello world" ) }
http包中,Handler是一个接口,规定了作为处理器需要有ServeHTTP方法 。
HandlerFunc是一个类型,实现了ServeHTTP方法。通过把一个func(ResponseWriter, *Request)
类型的函数值到HandlerFunc进行类型强转 ,实现了包装 。
1 2 3 4 5 6 7 8 9 type Handler interface { ServeHTTP(ResponseWriter, *Request) } type HandlerFunc func (ResponseWriter, *Request) func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) { f(w, r) }
Golang中,ServeMux类型实现了路由功能(将HTTP请求映射到对应的处理器)。
1 2 3 4 5 6 7 8 9 10 type ServeMux struct { mu sync.RWMutex m map [string ]muxEntry hosts bool } type muxEntry struct { h Handler pattern string }
ServeMux的Handle
方法通过将传入的路由和处理函数存入 ServeMux 的映射表 m
中来实现 “路由注册(register)”。
ServeMux还有一个方便的HandleFunc
方法,接收一个具体的处理函数将其包装后再传给Handle
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 func (mux *ServeMux) Handle(pattern string , handler Handler) { mux.mu.Lock() defer mux.mu.Unlock() if pattern == "" { panic ("http: invalid pattern" ) } if handler == nil { panic ("http: nil handler" ) } if _, exist := mux.m[pattern]; exist { panic ("http: multiple registrations for " + pattern) } if mux.m == nil { mux.m = make (map [string ]muxEntry) } e := muxEntry{h: handler, pattern: pattern} mux.m[pattern] = e if pattern[len (pattern)-1 ] == '/' { mux.es = appendSorted(mux.es, e) } if pattern[0 ] != '/' { mux.hosts = true } }
在一个ServerMux中,可以注册多个处理程序(Handler),每个处理程序对应一个URL路径。当一个HTTP请求到达ServerMux时,ServerMux会根据请求的URL路径选择对应的处理程序,并将请求传递给该处理程序进行处理。
1 2 3 4 5 6 7 8 9 mux := http.NewServeMux() mux.HandleFunc("/" , func (w http.ResponseWriter, r *http.Request) { fmt.Fprint(w, "Welcome to my website!" ) }) mux.HandleFunc("/hello" , func (w http.ResponseWriter, r *http.Request) { fmt.Fprint(w, "Hello, world!" ) })
路由器里面存储好了相应的路由规则之后,那么具体的请求又是怎么分发的?见下面的源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) { if r.RequestURI == "*" { w.Header().Set("Connection" , "close" ) w.WriteHeader(StatusBadRequest) return } h, _ := mux.Handler(r) h.ServeHTTP(w, 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) return RedirectHandler(p, 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() if mux.hosts { h, pattern = mux.match(host + path) } if h == nil { h, pattern = mux.match(path) } if h == nil { h, pattern = NotFoundHandler(), "" } return }
本质上根据用户请求的 URL 和路由器里面存储的 map 去匹配的,当匹配到之后返回存储的 handler,调用这个 handler 的 ServeHTTP 接口就可以执行到相应的函数了。
在源码中,ServeMux类型实现了Handler 接口(ServeHTTP方法) ,我们可以将它传给http.ListenAndServe
:
1 2 3 4 5 6 7 8 func main () { router := http.NewServeMux() router.HandleFunc("/" , homeHandler) http.ListenAndServe(":8080" , router) }
可以嵌套使用ServerMux,即一个ServerMux可以作为另一个ServerMux的处理程序,这种方式可以让我们更灵活地组织和管理HTTP请求的处理程序——
1 2 3 4 5 6 7 8 9 10 11 rootMux := http.NewServeMux() rootMux.HandleFunc("/" , func (w http.ResponseWriter, r *http.Request) { fmt.Fprint(w, "Welcome to my website!" ) }) helloMux := http.NewServeMux() helloMux.HandleFunc("/" , func (w http.ResponseWriter, r *http.Request) { fmt.Fprint(w, "Hello, world!" ) }) rootMux.Handle("/hello" , helloMux)
回到最初。http.HandleFunc("/", handler)
的实现如下:
1 2 3 4 func HandleFunc (pattern string , handler func (ResponseWriter, *Request) ) { DefaultServeMux.HandleFunc(pattern, handler) }
在一个应用程序的多个文件中定义HTTP handler也是非常典型的,如果它们必须全部都显式地注册到这个应用的ServeMux实例上会比较麻烦。所以为了方便,net/http包提供了一个全局的ServeMux实例DefaultServerMux 和包级别的http.Handle和http.HandleFunc函数。
http.ListenAndServe做了什么工作?它创建了Server对象,调用ListenAndServe方法启动端口监听和请求接收。
1 2 3 4 5 6 7 8 9 10 11 12 func ListenAndServe (addr string , handler Handler) error { server := &Server{Addr: addr, Handler: handler} return server.ListenAndServe() } type Server struct { Addr string Handler Handler }
Server对象使用 net.Listen 来启动监听端口。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 func (srv *Server) ListenAndServe() error { if srv.shuttingDown() { return ErrServerClosed } addr := srv.Addr if addr == "" { addr = ":http" } ln, err := net.Listen("tcp" , addr) if err != nil { return err } return srv.Serve(ln) }
在 Serve 方法中,有一个永久for循环。接收到请之后,会为每一个请求创建一个 conn 实例,conn 表示服务端的一个 HTTP 连接,并启动一个新的 goroutine 来处理这个请求。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 func (srv *Server) Serve(l net.Listener) error { defer l.Close() var tempDelay time.Duration for { rw, e := l.Accept() if e != nil { ... return e } tempDelay = 0 c, err := srv.newConn(rw) if err != nil { continue } go c.serve() } }
在 conn 的 serve 方法中,其实就只做了两件事,一件事读取请求中的数据,然后是调用 ServeHTTP 方法进行处理。