Dive_into_http_router_02
Day 2
http注册一个http处理函数的过程为:
http.HandleFunc("/foo", fooHandler)
查看源码为,
// HandleFunc registers the handler function for the given pattern
// in the DefaultServeMux.
// The documentation for ServeMux explains how patterns are matched.
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
DefaultServeMux.HandleFunc(pattern, handler)
}
HandleFunc函数的作用就是把一个处理函数注册按照特定的模式注册给默认的DefaultServeMux。
处理函数(HandlerFunc)的定义为:
handler func(ResponseWriter, *Request)
在函数里面展示了如何把一个制定的HandlerFunc绑定到DefaultServeMux上。
// HandleFunc registers the handler function for the given pattern.
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
// ...
mux.Handle(pattern, HandlerFunc(handler))
}
继续进入mux.Handle分析,
// 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.m[pattern] = muxEntry{h: handler, pattern: pattern}
// ...
}
Handle的作用就是把一种模式注册给http handler。
其实就是把pattern对应的路径规则保存在ServeMux的map表(map[string]muxEntry)中,其中muxEntry的定义如下,
type muxEntry struct {
h Handler
pattern string
}
TODO(zouying): - 为什么map中保存了pattern,这边里面还需要再保存一次? 猜测的是估计按照某种规则匹配到对应的http handler以后,还需要再按照某种规则处理一下。
从这里我们了解如何注册一个http handler到对应的pattern
了,
下一步我们需要揭开的谜题就是:当来了一个请求后,找到对应的http handler的过程。
以default_router/main.go为例,
发起http request后,
curl -X GET http://localhost:8080/foo
,
如何找到对应的fooHandler
。
思路分析
当client端按照上面的例子发出一个http request的时候,
我们在服务器端需要建立一个http server,这个http server应该绑定socket地址为localhost:8080
上,协议为http。
那么在go里面,如何建立这样的http server呢?
通过内部的net/http
包中提供的方法http.ListenAndServe(":8080", nil)
即可进行监听提供服务。
http.ListenAndServe方法
func ListenAndServe(addr string, handler Handler) error {
server := &Server{Addr: addr, Handler: handler}
return server.ListenAndServe()
}
前面已经分析过该函数,这个函数就是创建一个Server,监听(listen)入参地址addr,等待连接。
当接收到client端的请求连接时,按照配置的Mux进行处理。其中如果入参Handler为空时,那么使用默认的DefaultServeMux。
在demo中,addr为”:8080”,Handler为nil,也意味着我们使用了默认的DefaultServeMux。
接着分析里面的Server.ListenAndServe()
函数,
首先是分析Server这个结构体,
// A Server defines parameters for running an HTTP server.
// The zero value for Server is a valid configuration.
type Server struct {
Addr string // TCP address to listen on, ":http" if empty
Handler Handler // handler to invoke, http.DefaultServeMux if nil
// ...
}
Server这个结构体就是定义了HTTP Server的一系列参数,比如监听的地址Addr、注册的处理函数Handler、各种超时参数等等。
其中的Handler是一个重点。
// A Handler responds to an HTTP request.
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
Handler是一个interface,约定了处理HTTP request的接口。
也就是说,如果我们希望定一个函数来响应或者处理接收到的http request请求,那么我们就要实现Handler这个接口约定的ServeHTTP(ResponseWriter, *Request)
,
其中ResponseWriter是我们相应给client端的headers和data,
其中Request是我们接收到的请求的各种数据。
我们在demo中Server的定义为:Server{Addr: addr, Handler: handler}
,其他的都是默认值。
我们调用Server.ListenAndServe()进行监听及处理request,所以接着往下追,查看该函数的定义。
// ListenAndServe listens on the TCP network address srv.Addr and then
// calls Serve to handle requests on incoming connections.
// ...
func (srv *Server) ListenAndServe() error {
ln, err := net.Listen("tcp", addr)
// ...
return srv.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)})
}
该函数主要做了如下两步操作,
ln, err := net.Listen("tcp", addr)
:使用tcp在地址addr上进行了监听。其中”tcp”表示即监听了ipv4,也监听了ipv6,如果只需要监听ipv4的话,则使用tcp4。addr为main()中的”:8080”。
ln是一个interface。
// A Listener is a generic network listener for stream-oriented protocols.
//
// Multiple goroutines may invoke methods on a Listener simultaneously.
type Listener interface {
// Accept waits for and returns the next connection to the listener.
Accept() (Conn, error)
// Close closes the listener.
// Any blocked Accept operations will be unblocked and return errors.
Close() error
// Addr returns the listener's network address.
Addr() Addr
}
srv.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)})
:首先是将ln
由Listener
断言为TCPListener
,具体作用现在还不确定,但这可以理解,因为我们是使用TCP进行监听。另外使用tcpKeepAliveListener
封装了一下,具体作用先不深入调查,从命名看猜测可能是加入了一些Keep-Alive相关的参数设置。
下面重点重点分析svr.Server()
函数的实现。
// Serve accepts incoming connections on the Listener l, creating a
// new service goroutine for each. The service goroutines read requests and
// then call srv.Handler to reply to them.
// ...
func (srv *Server) Serve(l net.Listener) error {
// ...
for {
rw, e := l.Accept()
// ...
c := srv.newConn(rw)
c.setState(c.rwc, StateNew) // before Serve can return
go c.serve(ctx)
}
}
Serve的工作就是从监听的Listener得到一个连接,然后创建一个goroutine来处理这个请求。该goroutine会读取这个request,然后调用Server中的Handler对进来的(incoming)请求进行处理。
其中Listener在我们的示例中就是tcp在”:8080” socket地址上的监听。
分析上面源码,删除非重点代码,得到精华部分。
在一个for循环中,使用Accept()等待一个连接Conn。其中Conn为interface,是一个通用的数据流的网络连接interface。我们可以从Conn进行Read和Write。
// Conn is a generic stream-oriented network connection.
type Conn interface {
Read(b []byte) (n int, err error)
Write(b []byte) (n int, err error)
// ...
}
Serve中将监听到的Conn进一步使用conn封装了一个新的连接,该连接是一个server side的HTTP连接。该server端的连接应该很重要,做一个TODO,后面详细分析为什么需要建立这个server端的连接。
当建立这个server端的连接后,开启一个goroutine对这个连接进行serve()
操作。
// Serve a new connection.
func (c *conn) serve(ctx context.Context) {
c.remoteAddr = c.rwc.RemoteAddr().String()
ctx = context.WithValue(ctx, LocalAddrContextKey, c.rwc.LocalAddr())
defer func() {
// 异常panic处理
}()
// ... tls相关的处理
// HTTP/1.x from here on.
ctx, cancelCtx := context.WithCancel(ctx)
c.cancelCtx = cancelCtx
defer cancelCtx()
c.r = &connReader{conn: c}
c.bufr = newBufioReader(c.r)
c.bufw = newBufioWriterSize(checkConnErrorWriter{c}, 4<<10)
for {
w, err := c.readRequest(ctx)
// ...
// HTTP cannot have multiple simultaneous active requests.[*]
// Until the server replies to this request, it can't read another,
// so we might as well run the handler in this goroutine.
// [*] Not strictly true: HTTP pipelining. We could let them all process
// in parallel even if their responses need to be serialized.
// But we're not going to implement HTTP pipelining because it
// was never deployed in the wild and the answer is HTTP/2.
serverHandler{c.server}.ServeHTTP(w, w.req)
// ...
}
}
首先conn对于对于c.r
进行封装成connReader
,这个就是一个io.Reader的一个封装实现。connReader
在Read的时候,做了一些特殊的处理。这里暂时跳过。
c.bufr就是一个带缓存的Reader,下面其实就是用了bufio.Reader来封装。另外这里的缓存使用了sync.Pool来保存cache,这里可以看到底层的源码使用了很多技巧。sync.Pool我在百度工作的时候,因为需要处理的业务量较大,集群200w QPS,单台4-5w QPS,当时也在很多地方用来sync.Pool和bytes.Buffer做性能优化。
另外需要在这里说一些自己的感悟,如果我自己之前没有做过对性能优化相关的工作的话,那么我自己看这段源码可能也是一扫而过,知道在这里用了sync.Pool,但是sync.Pool到底起到什么作用(避免频繁分配内存导致的性能下降),可能从根本上还是没有那么透彻,看来技能还是得多多打磨。
接着分析是,使用for循环一直从conn中读出请求,然后进行处理。从代码猜的是,看来请求并不是一次都能读取完毕,可能会多次读取。
在这里出现了把我们最初定义的server封装成一个serverHandler
的类型,该类型其实就是一个server的Handler或者DefaultServeMux,在serverHandler的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)
}
可以在这里面看到,如果我们最开始的时候,server中的Handler如果是nil的话,就使用默认的DefaultServeMux。
我们在demo中,使用http.ListenAndServe(":8080", nil)
启动服务时,第二个参数就是nil,所以我们在demo中,用到的就是默认的DefaultServeMux。
那么最后当调用handler.ServeHTTP(rw, req)
时,DefaultServeMux.ServeHTTP会如何处理这些请求呢?
我们可以看到DefaultServeMux的定义是:
// DefaultServeMux is the default ServeMux used by Serve.
var DefaultServeMux = &defaultServeMux
var defaultServeMux ServeMux
其中ServeMux
的定义是:
type ServeMux struct {
mu sync.RWMutex
m map[string]muxEntry
hosts bool // whether any patterns contain hostnames
}
我们这样就找到之前所解释的ServeMux
的调用的地方了,也就是我们终于找到了我们想要分析的路由router的入口点了。
那么我们继续分析ServeMux.ServeHTTP都是如何做的。
// ServeHTTP dispatches the request to the handler whose
// pattern most closely matches the request URL.
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
// ...
h, _ := mux.Handler(r)
h.ServeHTTP(w, r)
}
啊哈,起码从注释中,我们可以看到ServeHTTP是把请求分发给最适配的处理函数。
这就是我们要分析的关键所在。
重点在于最后两句:
h, _ := mux.Handler(r)
:返回一个Handler(Handler的定义是:A Handler responds to an HTTP request.),Handler就是对HTTP请求进行处理并作出返回的函数。
// Handler returns the handler to use for the given request,
// consulting r.Method, r.Host, and r.URL.Path. It always returns
// a non-nil handler.
func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) {
// ...
return mux.handler(host, r.URL.Path)
}
我们暂时不考虑HTTP CONNECT METHOD,剩下的主要的是mux.handler(host, r.URL.Path)
。
// 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
}
做了下面操作,
看mux中有没有指定hosts需要匹配满足的,如果需要,那么查找的时候,在路径前面添加host,这个host是请求的Host。如果找到匹配的Handler,那么返回该Handler。
如果第1步没有找到。则去掉host,查找是否有匹配的Handler,如果有,返回。
如果没有匹配的,那么返回一个特殊的Handler——NotFoundHandler(),该Handler的定义如下,也就是往Response中写入404。
// NotFound replies to the request with an HTTP 404 not found error.
func NotFound(w ResponseWriter, r *Request) { Error(w, "404 page not found", StatusNotFound) }
// NotFoundHandler returns a simple request handler
// that replies to each request with a ``404 page not found'' reply.
func NotFoundHandler() Handler { return HandlerFunc(NotFound) }
我们往下分析path匹配的函数,h, pattern = mux.match(path)
// 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
}
从mux中,也即默认的DefaultServeMux,注册Handler map表中搜索,根据一个符合的返回。如果有多个handler都符合,那么返回path匹配最长路径的handler。
在这里我们可以看到http默认的mux是不区分http方法的,所以只要路径满足,不管request method是get、post、delete,都是会进入到同一个Handler中。
我们回顾到上面调用处理函数的地方,
// ServeHTTP dispatches the request to the handler whose
// pattern most closely matches the request URL.
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
// ...
h, _ := mux.Handler(r)
h.ServeHTTP(w, r)
}
我们从mux中获取到了一个Handler h,我们是使用h.ServeHTTP(w, r)
来调用我们定义的请求处理函数。
我们定义的请求处理函数原型是:func fooHandler(w http.ResponseWriter, r *http.Request)
并且Handler interface的定义也是下面这样,
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
也要求Handler,也即是我们的处理函数需要满足:ServeHTTP(ResponseWriter, *Request),然而我们的函数并没有定义ServeHTTP函数,这是怎么一回事?
奇妙之处就在于注册的时候,
当我们用http.HandleFunc("/foo", fooHandler)
注册时,
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
DefaultServeMux.HandleFunc(pattern, handler)
}
// HandleFunc registers the handler function for the given pattern.
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
// ...
mux.Handle(pattern, HandlerFunc(handler))
}
在HandleFunc中,会把我们自定义的handler(也就是fooHandler)强制类型转换为HandlerFunc(handler)
。
而HandlerFunc的定义如下,
// The HandlerFunc type is an adapter to allow the use of
// ordinary functions as HTTP handlers. If f is a function
// with the appropriate signature, HandlerFunc(f) is a
// Handler that calls f.
type HandlerFunc func(ResponseWriter, *Request)
// ServeHTTP calls f(w, r).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}
HandlerFunc就是为了适配自定义的函数为标准的Handler。
前面的流程串一下就是:
我们启动服务并监听一个地址,等待请求
当来了一个请求后,我们会启动一个goroutine对这个请求进行处理
处理的这个请求的方式,也即我们是怎么查找这个请求的对应的处理函数是根据Handler.ServeHTTP来规定的,这个也就是我们要找的router。
由于我们没有定义自己的router,所以我们使用了默认的DefaultServeMux
从DefaultServeMux注册的所有Handler中,找出最合适Handler进行处理(匹配且路径最长)