golang原生库net/http包下有关服务器基本实现的源码阅读
在以前的文章里笔者介绍过Socket通信的一些基础知识:IO多路复用详解
基于这些知识,我们今天从应用开始,一步步深入golang原生http库
路由注册
golang通过以下两种方式实现一个http服务器
1 | //第一种注册HandleFunc函数 |
http.HandleFunc
和http.Handle
都是用于给路由规则指定处理器,http.HandleFunc
的第一个参数为路由的匹配规则(pattern)第二个参数是一个签名为func(w http.ResponseWriter, r *http.Requests)
的函数。而http.Handle
的第二个参数为实现了http.Handler
接口的类型的实例。而无论是http.HandleFunc
方法还是http.Handle
方法最终都会由DefaultServeMux
调用Handle
方法来完成路由处理器的注册
这里,我们遇到两种类型的对象:ServeMux
和Handler
Handler
Handler为实现了ServeHTTP(ResponseWriter, *Request)
的接口,这个方法用于实现通信的逻辑处理
而对于http.HandleFunc
函数,它通过调用*ServeMux.HandleFunc
方法,最终同样调用ServeMux.Handle
方法(这个方法我们稍后再说)
1 | func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) { |
ServeMux
ServeMux是存储、分配路由服务的服务复用器,它也实现了ServeHTTP()
方法,用以启动路由服务,选择匹配度最高的路由处理函数来处理请求
1 | type ServeMux struct { |
Handle方法
1 | func (mux *ServeMux) Handle(pattern string, handler Handler) { |
服务启动
观察以上实现方式我们可以发现,注册路由后,就能通过使用http.ListenAndServe
方法启动服务器开始监听指定端口过来的请求
1 | //这里的handler对应ServeMux |
这里对应socket基础中的listen->accept,Server对象的Serve方法会接收Listener中过来的连接,为每个连接创建一个goroutine,在goroutine中会用路由处理Handler对请求进行处理并构建响应
其中,Serve
函数的主要逻辑如下:
1 | func (srv *Server) Serve(l net.Listener) error { |
自此,连接建立,服务启动
处理连接
接下来我们进入conn.serve
函数,这里的conn是一个内部包含Server成员的结构体。核心逻辑如下:
1 | func (c *conn) serve(ctx context.Context) { |
关键代码为serverHandler{c.server}.ServeHTTP(w, w.req)
这一行。serverHandler是代理了Server对象的结构体类型,而这个函数最终是对ServeMux.ServeHTTP()
的包装。如果我们最初在http.ListenAndServe()
中传入的Handler参数为nil,这里就会使用默认的DefaultServeMux,最后调用其ServeHTTP()
方法匹配当前路由对应的handler方法
但请注意:如果要自定义服务复用器,就不能使用http包自带的http.handle方法,因为这个方法本身就是调用DefaultServeMux的路由分配逻辑。所以,一般当框架自定义路由时才会调用这个方法,在第二个参数传自己想要用的服务复用器(如gin中的engine),其已经实现了路由分配的功能。
如下是ServeMux调用ServeHTTP()
的逻辑,注释已经写得很详细
1 | func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) { |
所谓部分匹配是指:当不存在匹配的路由时,可以追溯最长的一部分进行路由匹配,这个请求会被导向最长路由的接口
此后,如果想要停止服务,可以使用http.ShutDown()
方法实现优雅地关闭,当然,这需要自定义http.server对象进行手动关闭
最后借用一张个人觉得很清晰的网图来总结下整个http服务流程: