一、net/http
包详解
在上一篇文章中我们已经使用 net/http
(以下简称 http
) 创建了一个 Web 服务,并从源码层面分析了整个请求流转的过程,其中有两个比较核心的组件或者功能,一个是连接 Conn,另外一个是 ServeMux。
Conn
在 http
包中使用 goroutine
来处理连接的读写,这样既可以使每个请求保持独立,请求相互之间不会影响,不会阻塞,可以高效的响应网络请求,有可以实现高并发和高性能。Go 在等待客户端请求的代码如下:
c := srv.newConn(rw) c.setState(c.rwc, StateNew, runHooks) // before Serve can return go c.serve(connCtx) 复制代码
go c.serve(connCtx)
会为每一个请求创建一个 Conn,这个 Conn 中保存了该次请求的信息,然后在通过 serve 函数传递到对应的 handler,该 handler 中可以读取到相应请求的 header 信息,保证了每个请求的独立性。
ServeMux
在创建 Web 服务器的时候,我们通过 ListenAndServe
函数的第二个参数传递了一个 handler,这个 handler 为 nil,在 ServeHTTP 函数中如果 handler 为 nil,会给这个 handler 赋值一个 DefaultServeMux。
err := http.ListenAndServe(":9000", nil) 复制代码
handler := sh.srv.Handler if handler == nil { handler = DefaultServeMux } 复制代码
DefaultServeMux 就是一个 ServeMux:
var DefaultServeMux = &defaultServeMux var defaultServeMux ServeMux 复制代码
ServeMux 结构体的定义如下:
type ServeMux struct { mu sync.RWMutex // 锁,请求涉及到并发处理,这里需要一个锁的机制 m map[string]muxEntry // 路由规则,一个 string 对应一个 mux 实体,这里的 string 就是注册的路由表达方式 es []muxEntry // slice of entries sorted from longest to shortest. hosts bool // whether any patterns contain hostnames } 复制代码
muxEntry 结构体的定义如下:
type muxEntry struct { h Handler // 路由对应的 handler pattern string } 复制代码
Handler 结构体的定义如下:
type Handler interface { ServeHTTP(ResponseWriter, *Request) // 路由实现器 } 复制代码
Handler 是一个接口,该接口中定义了一个 ServeHTTP 方法,查看 HandleFun:
HandleFun 类型定义如下:
type HandlerFunc func(ResponseWriter, *Request) // ServeHTTP calls f(w, r). func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) { f(w, r) } 复制代码
我们在创建 Web 服务器的时候通过 HandleFun 函数将路由和处理函数进行了绑定:
http.HandleFunc("/", sayHelloName) 复制代码
我们定义的方法 sayHelloName 虽然没有直接实现 ServeHTTP 方法,但是 sayHelloName 方法就是 HandleFunc 调用后的结果,这个类型默认实现了 ServeHTTP 方法,即调用了 HandleFunc(f) 强制类型转换 f 为 HandleFunc 类型,这样就拥有了 ServeHTTP 方法。
路由器里面存储好了相应的路由映射规则后,通过调用 mux.handler(r).ServeHTTP(w,r)
来分发请求,mux.handler(r)
方法的源代码如下:
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 } 复制代码
handler 函数是根据用户请求的 URL 和路由器里面存储的 map 匹配获取存储的 handler,获取之后就可以调用 handler 的 ServeHTTP 方法执行相应的函数了。
Go 支持外部实现路由器,也就是 ListenerAndServe 方法的第二个参数,它是一个 Handler 接口,也就是实现 Handler 接口就可以实现自定义路由器。
在自定义的 Web 服务器的 main.go 文件中增加自定义路由器的代码:
//noinspection ALL func main(){ //http.HandleFunc("/", sayHelloName) alphaMux := &AlphaMux{} err := http.ListenAndServe(":9000", alphaMux) if err != nil { log.Fatal("ListenAndServer: ", err) } } func sayHelloName(w http.ResponseWriter, r *http.Request){ fmt.Fprint(w, "Hello, AlphaMux") } // 自定义路由器结果体 type AlphaMux struct { } // 自定义路由规则 func (p *AlphaMux) ServeHTTP(w http.ResponseWriter, r *http.Request) { if r.URL.Path == "/" { sayHelloName(w, r) return } http.NotFound(w, r) return } 复制代码
执行 main.go 文件,在浏览器中输入 localhost:9000,浏览器显示内容如下:
自定义的路由器生效。
二、Go 代码的执行流程
在使用默认的 DefaultServeMux 作为路由器的代码中,来看一下代码的执行流程:
Go 首先会调用 http.HandleFunc
,这一行代码内部执行了以下几步:
第一步:调用了 DefaultServeMux 的 HandleFunc 方法;
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) { DefaultServeMux.HandleFunc(pattern, handler) } 复制代码
第二步:调用了 DefaultServeMux 的 Handle 方法:
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) { if handler == nil { panic("http: nil handler") } mux.Handle(pattern, HandlerFunc(handler)) } 复制代码
第三步:通过 Handler 方法,往 DefaultServeMux 的 map[string]muxEntry 中增加对应的 handler 和路由规划:
接着调用了 http.ListenAndServe("9000", nil)
,这行代码内部执行了以下几件事:
第一:实例化一个 Server,并调用 Server 的 ListenAndServe() 方法
func ListenAndServe(addr string, handler Handler) error { server := &Server{Addr: addr, Handler: handler} return server.ListenAndServe() } 复制代码
第二:ListenAndServe() 方法中通过 net.Listen 监听传递的端口,在 ListenAndServe() 方法最后调用了 Serve() 方法:
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,并开启一个新的 goroutine 为这个请求进行服务 go serve(connCtx)
:
func (srv *Server) Serve(l net.Listener) error { // 其他代码省略 for { rw, err := l.Accept() // 其他代码省略 c := srv.newConn(rw) c.setState(c.rwc, StateNew, runHooks) // before Serve can return go c.serve(connCtx) } } 复制代码
第四:serve()
函数中通过 for
循环读取请求,并给每个请求分发一个 handler,分发 handler 是通过 ServeHTTP() 方法完成的:
// Serve a new connection. func (c *conn) serve(ctx context.Context) { // 其余代码省略 // 通过 for 循环读取请求 for { w, err := c.readRequest(ctx) // 其余代码省略 // 获取 handler serverHandler{c.server}.ServeHTTP(w, w.req) inFlightResponse = nil // 其余代码省略 } } 复制代码
第五:ServeHTTP 方法会分配 handler,我们在 main.go 文件中的 ListenAndServe 方法传递的第二个参数为 nil
,这里就分配了一个 DefaultServeMux,并在最后调用了 DefaultServeMux 的 ServeHTTP 方法:
func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) { handler := sh.srv.Handler if handler == nil { handler = DefaultServeMux } // 其余代码省略 handler.ServeHTTP(rw, req) } 复制代码
第六:DefaultServeMux 其实就是一个 ServeMux, ServeMux 的 ServeHTTP 方法会根据请求匹配相应的 handler
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() 方法的源码,是通过 mux.handler() 方法来选择 handler,handler 的选择会有三种情况:
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 } 复制代码
第八:最后调用 handler 的 ServeHTTP 方法就实现了响应客户端: