关于golang:深入浅出-Gin-生命周期

12次阅读

共计 10586 个字符,预计需要花费 27 分钟才能阅读完成。

本文首发于 深入浅出 Gin 生命周期 转载请注明出处。

Gin 是一个用 Go (Golang) 编写的 web 框架,因为杰出的性能劣势而被宽泛应用,这里咱们就来剖析下 Gin 的申请生命周期

1 Gin 目录构造

先来理解下其目录构造:

.
├── binding 根据 HTTP 申请 Accept 解析响应数据格式
│   ├── binding.go
│   ├── binding_nomsgpack.go
│   ├── default_validator.go
│   ├── form.go
│   ├── form_mapping.go
│   ├── header.go
│   ├── json.go
│   ├── msgpack.go
│   ├── multipart_form_mapping.go
│   ├── protobuf.go
│   ├── query.go
│   ├── uri.go
│   ├── xml.go
│   ├── yaml.go
├── ginS
│   └── gins.go
├── internal
│   ├── bytesconv
│   │   ├── bytesconv.go
│   └── json
│       ├── json.go
│       └── jsoniter.go
├── render 根据解析的 HTTP 申请 Accept 响应格局生成响应
│   ├── data.go
│   ├── html.go
│   ├── json.go
│   ├── msgpack.go
│   ├── protobuf.go
│   ├── reader.go
│   ├── redirect.go
│   ├── render.go
│   ├── text.go
│   ├── xml.go
│   └── yaml.go
├── auth.go
├── *context.go
├── context_appengine.go
├── debug.go
├── deprecated.go
├── errors.go
├── fs.go
├── *gin.go
├── logger.go
├── mode.go 设置 Gin 运行环境模式
├── path.go Path 解决
├── recovery.go 解决 Panic 的 Recovery 中间件
├── *response_writer.go ResponseWriter
├── *routergroup.go 路由组设置
├── tree.go 路由算法
├── utils.go helper 函数
└── version.go

其中比拟重要的模块为:context.go,gin.go,routergroup.go,以及 tree.go;别离解决 HTTP 申请及响应上下文,gin 引擎初始化,路由注册及路由查找算法实现。

binding 目录内提供基于 HTTP 申请音讯头 Context-Type 的 MIME 信息主动解析性能,绝对应的 Render 目录下提供具体数据格式渲染的实现办法。

2 Gin 申请生命周期

本文着重介绍 Gin 实现一个 Web 服务器,从申请到达到生成响应整个生命周期内的外围性能点,这将有助于咱们了解 Gin 的执行原理和当前的开发工作的开展。

2.1 简略理解下 Gin 服务执行流程

先从官网的第一个 demo example.go 登程:

package main

import "github.com/gin-gonic/gin"

func main() {
    // 创立 Gin Engine 实例
    r := gin.Default()

    // 设置申请 URI /ping 的路由及响应处理函数
    r.GET("/ping", func(c *gin.Context) {
        c.JSON(200, gin.H{"message": "pong",})
    })

    // 启动 Web 服务,监听端口, 期待 HTTP 申请到并生成响应
    r.Run() // 监听并在 0.0.0.0:8080 上启动服务}

通过执行 go run example.go 命令来运行代码,它会启动一个阻塞过程监听并期待 HTTP 申请:

# 运行 example.go 并且在浏览器中拜访 0.0.0.0:8080/ping
$ go run example.go

从代码中咱们能够看出通过 Gin 实现一个最简略的 Web 服务器,只需 3 个步骤:

1)创立 Gin 实例
2)注册路由及处理函数
3)启动 Web 服务

2.2 Gin 生命周期

2.2.1 创立 Gin 实例

Gin 实例创立通过 gin.Default() 办法实现,其定义在 gin.go#L159 文件里:

// Default returns an Engine instance with the Logger and Recovery middleware already attached.
func Default() *Engine {debugPrintWARNINGDefault()
    engine := New()
    engine.Use(Logger(), Recovery())
    return engine
}

Default() 办法实现如下性能:
1)创立 Gin 框架对象 Engine
2)配置 Gin 默认的中间件,Logger() 和 Recovery(),其实现别离位于 logger.go 和 recovery.go 文件内
3)返回 Gin 框架对象

其中 New() 办法会实例化 Gin 的 Engine 对象,gin.go#L129

// New returns a new blank Engine instance without any middleware attached.
// By default the configuration is:
// - RedirectTrailingSlash:  true
// - RedirectFixedPath:      false
// - HandleMethodNotAllowed: false
// - ForwardedByClientIP:    true
// - UseRawPath:             false
// - UnescapePathValues:     true
func New() *Engine {debugPrintWARNINGNew()
    engine := &Engine{
        RouterGroup: RouterGroup{
            Handlers: nil,
            basePath: "/",
            root:     true,
        },
        FuncMap:                template.FuncMap{},
        RedirectTrailingSlash:  true,
        RedirectFixedPath:      false,
        HandleMethodNotAllowed: false,
        ForwardedByClientIP:    true,
        AppEngine:              defaultAppEngine,
        UseRawPath:             false,
        RemoveExtraSlash:       false,
        UnescapePathValues:     true,
        MaxMultipartMemory:     defaultMultipartMemory,
        trees:                  make(methodTrees, 0, 9),
        delims:                 render.Delims{Left: "{{", Right: "}}"},
        secureJSONPrefix:       "while(1);",
    }
    engine.RouterGroup.engine = engine
    engine.pool.New = func() interface{} {return engine.allocateContext()
    }
    return engine
}

实例化比拟外围的性能是:
1)初始化 Engine 对象, 关键步骤是初始化路由组 RouterGroup。
2)初始化 pool, 这是外围步骤. pool 用来存储 context 上下文对象. 用来优化解决 http 申请时的性能。

前面会重点剖析 engine.pool 的实现细节。

Engine 是 Gin 框架的外围引擎 gin.go#L56,数据结构如下:

type Engine struct {
    RouterGroup // 要害:路由组

    // 设置开关
    RedirectTrailingSlash bool
    RedirectFixedPath bool
    HandleMethodNotAllowed bool
    ForwardedByClientIP    bool
    
    AppEngine bool
    UseRawPath bool
    
    UnescapePathValues bool
    MaxMultipartMemory int64
    RemoveExtraSlash bool

    // 界定符
    delims           render.Delims
    secureJSONPrefix string
    HTMLRender       render.HTMLRender
    FuncMap          template.FuncMap
    allNoRoute       HandlersChain
    allNoMethod      HandlersChain
    noRoute          HandlersChain
    noMethod         HandlersChain
    
    pool             sync.Pool // 要害:context 解决
    
    trees            methodTrees
    maxParams        uint16
}

Engine 构造体外部除一些功能性开关设置外,外围的就是 RouterRroup,pool 和 trees。Gin 的所有组件都是由 Engine 驱动。

2.2.2 路由注册

实现 Gin 的实例化之后,咱们能够通过 r.GET(“/ping”, func(c *gin.Context) {}) 定义 HTTP 路由及解决 handler 函数。

以 gin.GET 为例,开展源码如下:

// GET is a shortcut for router.Handle("GET", path, handle).
func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes {return group.handle(http.MethodGet, relativePath, handlers)
}

gin.GET 定义 HTTP GET 申请的路由及解决办法,并返回 IRoutes 对象实例。

2.2.2.1 RouterGroup 构造体
// RouterGroup is used internally to configure router, a RouterGroup is associated with
// a prefix and an array of handlers (middleware).
type RouterGroup struct {
    Handlers HandlersChain
    basePath string
    engine   *Engine
    root     bool
}

RouterGroup routergroup.go#L41 用于配置路由,其中:

  • Handlers 数组定义了 Gin 中间件调用的 handler 办法
  • engine 为 gin.go 实例化时设置的 Engine 实例对象
2.2.2.2 handle 增加路由

gin.GET 办法外部通过调用 group.handle() routergroup.go#L72 办法增加路由:

func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoutes {absolutePath := group.calculateAbsolutePath(relativePath)
    handlers = group.combineHandlers(handlers)
    
    group.engine.addRoute(httpMethod, absolutePath, handlers)// 增加路由
    return group.returnObj()}

路由就和 Engine 绑定好关系了。

2.2.2.3 IRoute 接口类型
// IRoutes defines all router handle interface.
type IRoutes interface {Use(...HandlerFunc) IRoutes

    Handle(string, string, ...HandlerFunc) IRoutes
    Any(string, ...HandlerFunc) IRoutes
    GET(string, ...HandlerFunc) IRoutes
    POST(string, ...HandlerFunc) IRoutes
    DELETE(string, ...HandlerFunc) IRoutes
    PATCH(string, ...HandlerFunc) IRoutes
    PUT(string, ...HandlerFunc) IRoutes
    OPTIONS(string, ...HandlerFunc) IRoutes
    HEAD(string, ...HandlerFunc) IRoutes

    StaticFile(string, string) IRoutes
    Static(string, string) IRoutes
    StaticFS(string, http.FileSystem) IRoutes
}

IRoute 是个接口类型,定义了 router 所需的 handle 接口,RouterGroup 实现了这个接口。

2.2.2.4 小结

推而广之,Gin 还反对如下等路由注册办法:

  • r.POST
  • r.DELETE
  • r.PATCH
  • r.PUT
  • r.OPTIONS
  • r.HEAD
  • 以及 r.Any

它们都定义在 routergroup.go 文件内。

2.2.3 接管申请并响应

Gin 实例化和路由设置后工作实现后,咱们进入 Gin 生命周期执行的外围功能分析,Gin 到底是如何启动 Web 服务,监听 HTTP 申请并执行 HTTP 申请处理函数生成响应的。这些工作通通从 gin.Run() 登程 gin.go#L305:

// Run attaches the router to a http.Server and starts listening and serving HTTP requests.
// It is a shortcut for http.ListenAndServe(addr, router)
// Note: this method will block the calling goroutine indefinitely unless an error happens.
func (engine *Engine) Run(addr ...string) (err error) {defer func() {debugPrintError(err) }()

    address := resolveAddress(addr)
    debugPrint("Listening and serving HTTP on %s\n", address)
    err = http.ListenAndServe(address, engine)
    return
}

gin.Run() 是 net/http 规范库 http.ListenAndServe(addr, router) 的简写,性能是将路由连贯到 http.Server 启动并监听 HTTP 申请。

由此,咱们不得不放下手头的工作,率先理解下 net/http 规范库的执行逻辑。

2.2.3.1 net/http 规范库

net/http 规范库的 ListenAndServe(addr string, handler Handler) 办法定义在 net/http/server.go#L3162 文件里。

  • 参数签名的第一个参数是监听的服务地址和端口;
  • 第二个参数接管一个 Handler 对象它是一个接口类型须要实现 ServeHTTP(ResponseWriter, *Request) 办法。
func ListenAndServe(addr string, handler Handler) error {server := &Server{Addr: addr, Handler: handler}
    return server.ListenAndServe()}

ListenAndServe(addr string, handler Handler) 外部则调用的是 Server 对象的 ListenAndServe() 办法由交由它启动监听和服务性能:

// ListenAndServe listens on the TCP network address srv.Addr and then
// calls Serve to handle requests on incoming connections.
// Accepted connections are configured to enable TCP keep-alives.
//
// If srv.Addr is blank, ":http" is used.
//
// ListenAndServe always returns a non-nil error. After Shutdown or Close,
// the returned error is ErrServerClosed.
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)// 启动服务期待连贯
}

而后,执行 srv.Serve(ln)Server.Serve(l net.Listener) server.go#L2951,在 net.Listen(“tcp”, addr) 期待连贯,创立新的 goroutine 来解决申请和生成响应的业务逻辑:

func (srv *Server) Serve(l net.Listener) error {
    ...
    for {rw, e := l.Accept()
        if e != nil {
            select {case <-srv.getDoneChan():
                return ErrServerClosed
            default:
            }
            ...
            return e
        }
        if cc := srv.ConnContext; cc != nil {ctx = cc(ctx, rw)
            if ctx == nil {panic("ConnContext returned nil")
            }
        }
        tempDelay = 0
        c := srv.newConn(rw)
        c.setState(c.rwc, StateNew) // before Serve can return
        go c.serve(ctx) // 启动 Web 服务
    }
}

最初,进入到 go c.serve(ctx) 启动 Web 服务,读取 HTTP 申请数据,生成响应 server.go#L1817:

// Serve a new connection.
func (c *conn) serve(ctx context.Context) {
    ...

    // HTTP/1.x from here on.
    for {w, err := c.readRequest(ctx)// 读取 HTTP 去申请

        ...

        // HTTP cannot have multiple simultaneous active requests.[*]
        // Until the server replies to this request, it can't read another,
        // so we might as well run the handler in this goroutine.
        // [*] Not strictly true: HTTP pipelining. We could let them all process
        // in parallel even if their responses need to be serialized.
        // But we're not going to implement HTTP pipelining because it
        // was never deployed in the wild and the answer is HTTP/2.
        serverHandler{c.server}.ServeHTTP(w, w.req)
        ...
    }
}

最终,调用 r.Run() 办法传入的 Engine 来执行 serverHandler{c.server}.ServeHTTP(w, w.req) 解决接管到的 HTTP 申请和生成响应,这里将响应解决的控制权交回给 Gin Engine。

小结

Go 规范库 net/http 提供了丰盛的 Web 编程接口反对,感兴趣的敌人能够深入研究下 net/http 规范库源码,理解其实现细节。

2.2.3.2 Engine.ServeHTTP 解决 HTTP 申请

Engine.ServeHTTP 是 Gin 框架外围中的外围,咱们来看下它是如何解决申请和响应的:

// ServeHTTP conforms to the http.Handler interface.
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {c := engine.pool.Get().(*Context) // 从长期对象池 pool 获取 context 上下文对象
    c.writermem.reset(w)
    c.Request = req
    c.reset()

    engine.handleHTTPRequest(c) // 解决 HTTP 申请

    engine.pool.Put(c) // 应用完 context 对象, 归还给 pool 
}

ServeHTTP 会先获取 Gin Context 上下文信息,接着将 Context 注入到 engine.handleHTTPRequest(c) 办法内来解决 HTTP 申请:

func (engine *Engine) handleHTTPRequest(c *Context) {
    httpMethod := c.Request.Method
    rPath := c.Request.URL.Path

    ...

    // Find root of the tree for the given HTTP method
    t := engine.trees
    for i, tl := 0, len(t); i < tl; i++ {

        ...

        root := t[i].root
        // Find route in tree
        value := root.getValue(rPath, c.params, unescape)
        
        ...
        
        if value.handlers != nil {
            c.handlers = value.handlers
            c.fullPath = value.fullPath
            c.Next() // 具体执行响应解决
            c.writermem.WriteHeaderNow()
            return
        }
        if httpMethod != "CONNECT" && rPath != "/" {...}
        break
    }

    ...
}

handleHTTPRequest 实现 路由 回调 办法的查找,执行 Gin.Context.Next() 调用解决响应。

2.2.3.3 Gin.Context.Next() 在外部中间件执行 handler 办法

Gin.Context.Next() 仅有数行代码:

// Next should be used only inside middleware.
// It executes the pending handlers in the chain inside the calling handler.
// See example in GitHub.
func (c *Context) Next() {
    c.index++
    for c.index < int8(len(c.handlers)) {c.handlers[c.index](c)
        c.index++
    }
}

性能是在 Gin 外部中间件中执行 handler 调用,即 r.GET() 中传入的

func(c *gin.Context) {
    c.JSON(200, gin.H{"message": "pong",})
}

办法生成 HTTP 响应。

到这里咱们实现了 Gin 的申请和响应的残缺流程的源码走读,然而咱们有必要对 Gin.Context 有多一些的理解。

2.2.3.4 Gin.Context 上下文解决

Gin 的 Context 实现了对 request 和 response 的封装是 Gin 的外围实现之一,其数据结构如下:

// Context is the most important part of gin. It allows us to pass variables between middleware,
// manage the flow, validate the JSON of a request and render a JSON response for example.
type Context struct {
    writermem responseWriter

    Request   *http.Request    // HTTP 申请
    Writer    ResponseWriter   // HTTP 响应

    Params   Params
    handlers HandlersChain  // 要害: 数组: 内蕴含办法汇合
    index    int8

    engine *Engine  // 要害: 引擎

    // Keys is a key/value pair exclusively for the context of each request.
    Keys map[string]interface{}

    // Errors is a list of errors attached to all the handlers/middlewares who used this context.
    Errors errorMsgs

    // Accepted defines a list of manually accepted formats for content negotiation.
    Accepted []string}

其蕴含了 Gin 申请及响应的上下文信息和 Engine 指针数据

  • Request *http.Request : HTTP 申请
  • Writer ResponseWriter : HTTP 响应
  • handlers HandlersChain : 是 type HandlerFunc func(*Context) 办法集即路由设置的回调函数
  • engine *Engine : gin 框架对象

Gin 官网文档 简直所有的示例都是在解说 Context 的应用办法,可用说钻研 Context 源码对用好 Gin 框架会起到只管重要的作用。

感兴趣的敌人能够自行浏览 Context.go

3 参考资料

  • https://golang.org/pkg/net/http/
  • https://www.haohongfan.com/po…
  • https://github.com/hhstore/bl…
  • https://draveness.me/golang/d…

正文完
 0