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)})
}

该函数主要做了如下两步操作,

  1. 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
}
  1. srv.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)}):首先是将lnListener断言为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是把请求分发给最适配的处理函数。

这就是我们要分析的关键所在。

重点在于最后两句:

  1. 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
}

做了下面操作,

  1. 看mux中有没有指定hosts需要匹配满足的,如果需要,那么查找的时候,在路径前面添加host,这个host是请求的Host。如果找到匹配的Handler,那么返回该Handler。

  2. 如果第1步没有找到。则去掉host,查找是否有匹配的Handler,如果有,返回。

  3. 如果没有匹配的,那么返回一个特殊的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。


前面的流程串一下就是:

  1. 我们启动服务并监听一个地址,等待请求

  2. 当来了一个请求后,我们会启动一个goroutine对这个请求进行处理

  3. 处理的这个请求的方式,也即我们是怎么查找这个请求的对应的处理函数是根据Handler.ServeHTTP来规定的,这个也就是我们要找的router。

  4. 由于我们没有定义自己的router,所以我们使用了默认的DefaultServeMux

  5. 从DefaultServeMux注册的所有Handler中,找出最合适Handler进行处理(匹配且路径最长)