HttpServer源码阅读#
注册路由#
直观上看注册路由这一步,就是它要做的就是将在路由器url pattern和开发者提供的func关联起来。 很容易想到,它里面很可能是通过map实现的。
func main() { // 创建路由器 // 为路由器绑定路由规则 mux := http.NewServeMux() mux.HandleFunc("/login", doLogin) ... } func doLogin(writer http.ResponseWriter,req *http.Request){ _, err := writer.Write([]byte("do login")) if err != nil { fmt.Printf("error : %v", err) return } }
姑且将ServeMux当作是路由器。我们使用http包下的 NewServerMux 函数创建一个新的路由器对象,进而使用它的HandleFunc(pattern,func)函数完成路由的注册。
跟进NewServerMux函数,可以看到,它通过new函数返回给我们一个ServeMux结构体。
func NewServeMux() *ServeMux { return new(ServeMux) }
这个ServeMux结构体长下面这样:在这个ServeMux结构体中我们就看到了这个维护pattern和func的map
type ServeMux struct { mu sync.RWMutex m map[string]muxEntry hosts bool // whether any patterns contain hostnames }
这个muxEntry长下面这样:
type muxEntry struct { h Handler pattern string } type Handler interface { ServeHTTP(ResponseWriter, *Request) }
看到这里问题就来了,上面我们手动注册进路由器中的仅仅是一个有规定参数的方法,到这里怎么成了一个Handle了?我们也没有说去手动的实现Handler这个接口,也没有重写ServeHTTP函数啊, 在golang中实现一个接口不得像下面这样搞吗?**
type Handle interface { Serve(string, int, string) } type HandleImpl struct { } func (h HandleImpl)Serve(string, int, string){ }
带着这个疑问看下面的方法:
// 由于函数是一等公民,故我们将doLogin函数同普通变量一样当做入参传递进去。 mux.HandleFunc("/login", doLogin) func doLogin(writer http.ResponseWriter,req *http.Request){ ... }
跟进去看 HandleFunc 函数的实现:
首先:HandleFunc函数的第二个参数是接收的函数的类型和doLogin
函数的类型是一致的,所以doLogin
能正常的传递进HandleFunc中。
其次:我们的关注点应该是下面的HandlerFunc(handler)
// 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)) }
跟进这个HandlerFunc(handler)
看到下图,真相就大白于天下了。golang以一种优雅的方式悄无声息的为我们完成了一次适配。这么看来上面的HandlerFunc(handler)
并不是函数的调用,而是doLogin
转换成自定义类型。这个自定义类型去实现了Handle接口(因为它重写了ServeHTTP函数)以闭包的形式完美的将我们的doLogin适配成了Handle类型。
在往下看Handle
方法:
第一:将pattern和handler注册进map中
第二:为了保证整个过程的并发安全,使用锁保护整个过程。
// 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) } mux.m[pattern] = muxEntry{h: handler, pattern: pattern} if pattern[0] != '/' { mux.hosts = true }
启动服务#
概览图:
和java对比着看,在java一组复杂的逻辑会被封装成一个class。在golang中对应的就是一组复杂的逻辑会被封装成一个结构体。
对应HttpServer肯定也是这样,http服务器在golang的实现中有自己的结构体。它就是http包下的Server。
它有一系列描述性属性。如监听的地址、写超时时间、路由器。
server := &http.Server{ Addr: ":8081", WriteTimeout: time.Second * 2, Handler: mux, } log.Fatal(server.ListenAndServe())
我们看它启动服务的函数:server.ListenAndServe()
实现的逻辑是使用net包下的Listen函数,获取给定地址上的tcp连接。
再将这个tcp连接封装进 tcpKeepAliveListenner
结构体中。
在将这个tcpKeepAliveListenner
丢进Server的Serve函数中处理
// ListenAndServe 会监听开发者给定网络地址上的tcp连接,当有请求到来时,会调用Serve函数去处理这个连接。 // 它接收到所有连接都使用 TCP keep-alives相关的配置 // // 如果构造Server时没有指定Addr,他就会使用默认值: “:http” // // 当Server ShutDown或者是Close,ListenAndServe总是会返回一个非nil的error。 // 返回的这个Error是 ErrServerClosed func (srv *Server) ListenAndServe() error { if srv.shuttingDown() { return ErrServerClosed } addr := srv.Addr if addr == "" { addr = ":http" } // 底层借助于tcp实现 ln, err := net.Listen("tcp", addr) if err != nil { return err } return srv.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)}) } // tcpKeepAliveListener会为TCP设置一个keep-alive 超时时长。 // 它通常被 ListenAndServe 和 ListenAndServeTLS使用。 // 它保证了已经dead的TCP最终都会消失。 type tcpKeepAliveListener struct { *net.TCPListener }
接着去看看Serve
方法,上一个函数中获取到了一个基于tcp的Listener,从这个Listener中可以不断的获取出新的连接,下面的方法中使用无限for循环完成这件事。conn获取到后将连接封装进httpConn,为了保证不阻塞下一个连接到到来,开启新的goroutine处理这个http连接。
func (srv *Server) Serve(l net.Listener) error { // 如果有一个包裹了 srv 和 listener 的钩子函数,就执行它 if fn := testHookServerServe; fn != nil { fn(srv, l) // call hook with unwrapped listener } // 将tcp的Listener封装进onceCloseListener,保证连接不会被关闭多次。 l = &onceCloseListener{Listener: l} defer l.Close() // http2相关的配置 if err := srv.setupHTTP2_Serve(); err != nil { return err } if !srv.trackListener(&l, true) { return ErrServerClosed } defer srv.trackListener(&l, false) // 如果没有接收到请求睡眠多久 var tempDelay time.Duration // how long to sleep on accept failure baseCtx := context.Background() // base is always background, per Issue 16220 ctx := context.WithValue(baseCtx, ServerContextKey, srv) // 开启无限循环,尝试从Listenner中获取连接。 for { rw, e := l.Accept() // accpet过程中发生错屋 if e != nil { select { // 如果从server的doneChan中可以获取内容,返回Server关闭了 case <-srv.getDoneChan(): return ErrServerClosed default: } // 如果发生了 net.Error 并且是临时的错误就睡5毫秒,再发生错误睡眠的时间*2,上线是1s if ne, ok := e.(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", e, tempDelay) time.Sleep(tempDelay) continue } return e } // 如果没有发生错误,清空睡眠的时间 tempDelay = 0 // 将接收到连接封装进httpConn c := srv.newConn(rw) c.setState(c.rwc, StateNew) // before Serve can return // 开启一条新的协程处理这个连接 go c.serve(ctx) } }
处理请求#
在c.serve(ctx)
中就会去解析http相关的报文信息~,将http报文解析进Request结构体中。
部分代码如下:
// 将 server 包裹为 serverHandler 的实例,执行它的 ServeHTTP 方法,处理请求,返回响应。 // serverHandler 委托给 server 的 Handler 或者 DefaultServeMux(默认路由器) // 来处理 "OPTIONS *" 请求。 serverHandler{c.server}.ServeHTTP(w, w.req)
// serverHandler delegates to either the server's Handler or // DefaultServeMux and also handles "OPTIONS *" requests. type serverHandler struct { srv *Server } func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) { // 如果没有定义Handler就使用默认的 handler := sh.srv.Handler if handler == nil { handler = DefaultServeMux } if req.RequestURI == "*" && req.Method == "OPTIONS" { handler = globalOptionsHandler{} } // 处理请求,返回响应。 handler.ServeHTTP(rw, req) }
可以看到,req中包含了我们前面说的pattern,叫做RequestUri,有了它下一步就知道该回调ServeMux中的哪一个函数。