介绍 golang net/http 源码
文章目录
1、源代码阅读
- 阅读源代码是学习 golang 绕不开的任务,下文笔者将以 net/http 库 web 工作原理阅读为例,原理图如下:
- 它的处理流程总体分为:注册路由;开始监听;处理请求;返回响应,流程图如下
2、阅读源码的注意事项
- 我们从入口函数 ListenAndServe 开始阅读分析代码:
- 关注函数、方法参数中的 接口 和 函数 参数,是接口一定要了解接口的定义。
- 随时查阅 API 文档,了解相关类型的属性与方法
- 忽视任何错误处理、分支处理。尽管其中有许多有趣的东西,也要放弃
- 其中特别注意闭包、匿名函数、匿名类型这些编程技巧
- 特别注意接口断言语法 var.(type)
- 线程要注意上下文对象(context)的构建
3、源代码分析
① 注册路由
1)DefaultServeMux & ServeMux
- Mux,即 multiplexer 多路选择器。在多路数据传送过程,能根据需要将其中一路选出
- 查看上文默认路由器的定义 DefaultServeMux,DefaultServeMux 是 ServeMux 的一个实例。
- 在 ServeMux 中,m 是一个 map,存储路由和 handler 的关系,es 是一个切片 slice,将路由按长度从长到短排序存储。
var DefaultServeMux = &defaultServeMux
var defaultServeMux ServeMux
type ServeMux struct {
mu sync.RWMutex
m map[string]muxEntry
es []muxEntry // slice of entries sorted from longest to shortest.
hosts bool // whether any patterns contain hostnames
}
2)HandleFunc & Handle
-
HandlerFunc 是一个函数类型,它实现了 Handler 接口的 ServeHTTP 方法
-
查看 HandleFunc 源码,它调用 DefaultServeMux.HandleFunc() 注册路由
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
DefaultServeMux.HandleFunc(pattern, handler)
}
- 将传入的 handle function 和相应的 pattern 匹配
// HandleFunc registers the handler function for the given pattern.
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
if handler == nil {
panic("http: nil handler")
}
mux.Handle(pattern, HandlerFunc(handler))
}
- 查看上文 Handle() 的源码,它对传入的路径进行解析,路由匹配,然后设置 ServeMux.m 和 ServeMux.es,存放路由的匹配规则
// 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.mu.Lock()
defer mux.mu.Unlock()
if pattern == "" {
panic("http: invalid pattern")
}
if handler == nil {
panic("http: nil handler")
}
if _, exist := mux.m[pattern]; exist {
panic("http: multiple registrations for " + pattern)
}
if mux.m == nil {
mux.m = make(map[string]muxEntry)
}
e := muxEntry{h: handler, pattern: pattern}
mux.m[pattern] = e
if pattern[len(pattern)-1] == '/' {
mux.es = appendSorted(mux.es, e)
}
if pattern[0] != '/' {
mux.hosts = true
}
}
- 至此,pattern 和 handler 的路由注册完成
② 开启监听
1)ListenAndServe
- 因为我们是通过 http.ListenAndServe() 绑定地址端口的,所以从它开始分析
- ListenAndServe() 先实例化 Serve,然后调用 Server.ListenAndServe()
func ListenAndServe(addr string, handler Handler) error {
server := &Server{Addr: addr, Handler: handler}
return server.ListenAndServe()
}
- 在 Server.ListenAndServe(),使用 TCP 协议创建了一个服务器,监听端口
func (srv *Server) ListenAndServe() error {
if srv.shuttingDown() {
return ErrServerClosed
}
// 取地址
addr := srv.Addr
if addr == "" {
addr = ":http"
}
// net.Listen 创建 socket 和绑定端口
ln, err := net.Listen("tcp", addr)
// 错误处理
if err != nil {
return err
}
// Server 再 Serve 链路上的请求
return srv.Serve(ln)
}
③ 处理请求
1)Serve
- 查看上文 srv.Serve(ln) 的源码
- 它的功能为在 for 循环里持续监听 request,通过 accept 接受 request,并为每个 request 创建一个 goroutine 来处理。各个 request 之间是相互不影响,并发处理的
func (srv *Server) Serve(l net.Listener) error {
……
// 循环监听
for {
rw, err := l.Accept()
// 错误处理
if err != nil {
select {
case <-srv.getDoneChan():
return ErrServerClosed
default:
}
if ne, ok := err.(net.Error); ok && ne.Temporary() {
if tempDelay == 0 {
tempDelay = 5 * time.Millisecond
} else {
tempDelay *= 2
}
if max := 1 * time.Second; tempDelay > max {
tempDelay = max
}
srv.logf("http: Accept error: %v; retrying in %v", err, tempDelay)
time.Sleep(tempDelay)
continue
}
return err
}
// 复制服务线程
connCtx := ctx
// 为一个新的连接修改上下文
if cc := srv.ConnContext; cc != nil {
connCtx = cc(connCtx, rw)
if connCtx == nil {
panic("ConnContext returned nil")
}
}
tempDelay = 0
// 创建连接,并初始化
c := srv.newConn(rw)
c.setState(c.rwc, StateNew)
// go 协程处理连接
go c.serve(connCtx)
}
}
2)serve
- 查看上文 c.serve (connCtx) 的源码
- 它用于处理一个连接,用 readRequest 读取数据,解析 request 中的 Header 和 Body。接着通过 serverHandler 处理 request 和 response
func (c *conn) serve(ctx context.Context) {
c.remoteAddr = c.rwc.RemoteAddr().String()
ctx = context.WithValue(ctx, LocalAddrContextKey, c.rwc.LocalAddr())
// 连接断开后处理
// ……
if tlsConn, ok := c.rwc.(*tls.Conn); ok {
// 设置 server 维持时间 和 tls handshake 失败处理
// ···
c.tlsState = new(tls.ConnectionState)
*c.tlsState = tlsConn.ConnectionState()
if proto := c.tlsState.NegotiatedProtocol; validNextProto(proto) {
if fn := c.server.TLSNextProto[proto]; fn != nil {
h := initALPNRequest{ctx, tlsConn, serverHandler{c.server}}
fn(c.server, tlsConn, h)
}
return
}
}
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 request 错误处理
// ……
req := w.req
if req.expectsContinue() {
if req.ProtoAtLeast(1, 1) && req.ContentLength != 0 {
// Wrap the Body reader with one that replies on the connection
req.Body = &expectContinueReader{readCloser: req.Body, resp: w}
w.canWriteContinue.setTrue()
}
} else if req.Header.get("Expect") != "" {
w.sendExpectationFailed()
return
}
c.curReq.Store(w)
if requestBodyRemains(req.Body) {
registerOnHitEOF(req.Body, w.conn.r.startBackgroundRead)
} else {
w.conn.r.startBackgroundRead()
}
serverHandler{c.server}.ServeHTTP(w, w.req)
w.cancelCtx()
// ……
w.finishRequest()
// ……
c.setState(c.rwc, StateIdle)
c.curReq.Store((*response)(nil))
// 判断连接存活
if !w.conn.server.doKeepAlives() {
return
}
if d := c.server.idleTimeout(); d != 0 {
c.rwc.SetReadDeadline(time.Now().Add(d))
if _, err := c.bufr.Peek(4); err != nil {
return
}
}
c.rwc.SetReadDeadline(time.Time{})
}
}
④ 返回响应
1)serverHandler & ServeHTTP
- 查看上文 serverHandler 的源码,它用于实现服务和处理的隔离
- 在 ServerHTTP 中,设置 handler,如果 handler 为 nil,默认使用 DefaultServeMux
- Handler 是一个服务处理回调接口
- 实现 ServeHTTP(ResponseWriter, *Request) 方法的对象都可以与客户端会话
type serverHandler struct {
srv *Server
}
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)
}
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}
type HandlerFunc func(ResponseWriter, *Request)
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
2)match
- match 函数实现了,根据路由找到对应的 handler。m 表存储了 pattern 和 handler 处理器函数的 map[string]muxEntry。优先查找 m 表,如果找不到,则在 es 表中进行匹配,路径长的优先匹配。
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. mux.es contains all patterns
// that end in / sorted from longest to shortest.
for _, e := range mux.es {
if strings.HasPrefix(path, e.pattern) {
return e.h, e.pattern
}
}
return nil, ""
}
4、总结
- 服务器的工作流程其实就是:创建 ServerSocket,绑定并 listen,accept 连接,创建 go 线程服务连接
- Go 通过 ServeMux 实现了路由 multiplexer 来管理路由,并且设计一个 Handler 接口提供 ServeHTTP 实现 handler ,用于处理 request 并 response。