探究go源码之http server

golang标准库提供了很好用的http库,比如想要编写一个服务器程序,只需要简单几行代码:

http.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) {
    writer.Write([]byte("hello"))
})
http.ListenAndServe(":8000", nil)

下面深入标准库来看下go到底做了哪些事情。

首先http.HandleFunc函数的定义如下:

func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
	DefaultServeMux.HandleFunc(pattern, handler)
}

这个函数里使用了DefaultServeMux这个标准库内置的路由结构变量,其定义如下:

type ServeMux struct {
	mu    sync.RWMutex
	m     map[string]muxEntry
	hosts bool // whether any patterns contain hostnames
}

这个结构使用了map来存储url路径与handler的映射关系,并使用mutex来加锁。

然后http.ListenAndServe的定义如下:

func ListenAndServe(addr string, handler Handler) error {
	server := &Server{Addr: addr, Handler: handler}
	return server.ListenAndServe()
}

Server结构定义了服务器程序的参数,其中Handler成员变量负责处理url路由,默认是http.DefaultServeMux这个内置变量,如下所示:

Handler   Handler     // handler to invoke, http.DefaultServeMux if nil

Handler是一种接口,定义如下:

type Handler interface {
	ServeHTTP(ResponseWriter, *Request)
}

所以,任何实现了ServeHTTP的对象都可以作为url路由,这种机制允许使用者灵活的替换标准库内置的路由结构,大大提高了标准库的灵活性和可扩展性。

接下来就是服务端主要的处理流程

程序调用ln, err := net.Listen("tcp", addr)获取到一个tcp链接,然后就是一段服务端开发比较常见的代码模式了,主干代码如下:

for {
		rw, e := l.Accept()
		c := srv.newConn(rw)
		go c.serve(ctx)
	}

在死循环里,accept新的链接,然后启动新的routine来处理这个链接。在新的routine里,调用readRequest来读取并解析http协议报文,然后调用serverHandler{c.server}.ServeHTTP(w, w.req)来处理请求。

看到这里有没有眼熟,没错,这里调用了ServeHTTP,深入看下去会发现,其实最终会调用我们注册的handler。但我们做的只是把url路径和handler存储到map里,哪来的ServeHTTP方法?这就是go实现的比较巧妙的地方,上面提到了DefaultServeMux.HandleFunc(pattern, handler)函数,进一步看下去是这样的

func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
	mux.Handle(pattern, HandlerFunc(handler))
}

这里做了一次类型转换,把我们的handler转换成了HandlerFunc,而其定义如下:

type HandlerFunc func(ResponseWriter, *Request)

// ServeHTTP calls f(w, r).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
	f(w, r)
}

看到这里就会发现,我们的handler的ServeHTTP方法就是自己。怎么样,函数也是变量,是对象,有点函数式编程的意思吧。

在代码里发现一个有趣的注释,http协议本来不能同时处理多个请求的,但HTTP pipelining技术可以做到这点,然而go并不打算实现这一技术,因为HTTP pipelining在现实世界里从来没有被实施过,而且更好的解决方案是http2。就像血统纯正的unix被后起之秀linux打败一样,这就是现实。