Gin 框架源码学习(二) -- 服务启动

本篇主要介绍gin服务启动过程的源码
Run() 启动入口 我们的程序都是通过调用Run函数来启动gin的实例,下面来看一下Run的源码:

func (engine *Engine) Run(addr ...string) (err error) { defer func() { debugPrintError(err) }() // 解析服务地址 address := resolveAddress(addr) debugPrint("Listening and serving HTTP on %s\n", address) // 此处会block err = http.ListenAndServe(address, engine) return }

【Gin 框架源码学习(二) -- 服务启动】该方法其实是对http.ListenAndServe的简单封装,以下逻辑就进入net/http包中了,继续往下看:
func ListenAndServe(addr string, handler Handler) error { // 配置tcp的监听地址和监听到来后的处理函数 server := &Server{Addr: addr, Handler: handler} // 继续调用。。。 return server.ListenAndServe() }// Handler定义 type Handler interface { // 请求到来后的处理逻辑,gin框架会自己实现 ServeHTTP(ResponseWriter, *Request) }func (srv *Server) ListenAndServe() error { // 异常处理 if srv.shuttingDown() { return ErrServerClosed } addr := srv.Addr if addr == "" { addr = ":http" } // 监听配置 ln, err := net.Listen("tcp", addr) if err != nil { return err } return srv.Serve(ln) }func (srv *Server) Serve(l net.Listener) error { if fn := testHookServerServe; fn != nil { fn(srv, l) // call hook with unwrapped listener }origListener := l l = &onceCloseListener{Listener: l} defer l.Close()if err := srv.setupHTTP2_Serve(); err != nil { return err }if !srv.trackListener(&l, true) { return ErrServerClosed } defer srv.trackListener(&l, false)baseCtx := context.Background() if srv.BaseContext != nil { baseCtx = srv.BaseContext(origListener) if baseCtx == nil { panic("BaseContext returned a nil context") } }var tempDelay time.Duration // how long to sleep on accept failure// 带值的上下文 ctx := context.WithValue(baseCtx, ServerContextKey, srv) // 死循环,监听&处理请求 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 // 创建一个新连接,一个conn对象,代表服务端的http连接,里边包含该次请求的数据 c := srv.newConn(rw) c.setState(c.rwc, StateNew) // before Serve can return // 开启一个goroutine处理请求,将上下文传递进去,高并发的保障 go c.serve(connCtx) } }// serve函数只摘取最重要的一行 func (c *conn) serve(ctx context.Context) {... // 这里就会调用gin框架实现的ServeHTTP逻辑 serverHandler{c.server}.ServeHTTP(w, w.req)... }

ServeHTTP 逻辑
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) { // 从对象池子里获取一个Context对象,池子里有可以直接拿来用,没有则创建一个新的对象返回 c := engine.pool.Get().(*Context) // 把请求的相关数据都写入Context c.writermem.reset(w) c.Request = req c.reset()// 请求处理逻辑 engine.handleHTTPRequest(c) // 使用完毕,放回池子,复用 engine.pool.Put(c) }func (engine *Engine) handleHTTPRequest(c *Context) { httpMethod := c.Request.Method rPath := c.Request.URL.Path unescape := false if engine.UseRawPath && len(c.Request.URL.RawPath) > 0 { rPath = c.Request.URL.RawPath unescape = engine.UnescapePathValues } rPath = cleanPath(rPath)// Find root of the tree for the given HTTP method t := engine.trees // 遍历路由数组,根据请求方法找出对应的那棵树 for i, tl := 0, len(t); i < tl; i++ { if t[i].method != httpMethod { continue } root := t[i].root // Find route in tree // 从树中找到路由对应的函数处理链和参数 handlers, params, tsr := root.getValue(rPath, c.Params, unescape) if handlers != nil { c.handlers = handlers c.Params = params // 按顺序执行handler方法,可以配合在中间件中使用 c.Next() // handlers中的所有函数都处理完毕后,该返回了 c.writermem.WriteHeaderNow() return } if httpMethod != "CONNECT" && rPath != "/" { if tsr && engine.RedirectTrailingSlash { redirectTrailingSlash(c) return } if engine.RedirectFixedPath && redirectFixedPath(c, root, engine.RedirectFixedPath) { return } } break }if engine.HandleMethodNotAllowed { for _, tree := range engine.trees { if tree.method == httpMethod { continue } if handlers, _, _ := tree.root.getValue(rPath, nil, unescape); handlers != nil { c.handlers = engine.allNoMethod serveError(c, http.StatusMethodNotAllowed, default405Body) return } } } c.handlers = engine.allNoRoute serveError(c, http.StatusNotFound, default404Body) }// 看一下Next方法,很简单 func (c *Context) Next() { c.index++ for c.index < int8(len(c.handlers)) { c.handlers[c.index](c) c.index++ } }

总结
  1. 启动流程很清晰,通过net/http包监听请求,核心逻辑就是一个死循环,无限等待,当有请求到达指定端口时,启动一个goroutine异步处理该请求。
  2. 请求的处理逻辑使用的是gin框架自己实现的ServeHTTP函数,gin对Conext的封装和复用,也是一大亮点。
  3. context的Next方法在中间件中的使用,可以实现后置中间件。

    推荐阅读