GO服务框架Gin源码解读<一>

最近在捣鼓GO服务开发,接触到Gin这个犀利而又神奇的框架。So,对这款框架源码进行了研究学习。下面就展开第一段分享。

从最简单的几行代码开始

func main() {
    router := gin.Default()
    router.GET("/test", func(context *gin.Context) {
        context.JSON(http.StatusOK, gin.H{
            "code":1024,
            "result":"helloworld",
        })
    })
    router.Run()
}

这是最简单的服务器监听http Get请求的实现。
看到这里,想必都能看出来3个步骤

  1. 创建router对象,默认初始化参数
  2. 通过router创建一个GET方式的监听,并设置监听回调
  3. 让这个router Run起来

是不是看起来觉得so easy,那么,接下来就针对这3步,我们具体看下源码当中究竟做了什么

一、创建router对象过程

也就是 gin.Default()过程

// 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
}

这里返回的是一个Engine指针,也就是上面说的"router"对象,后面再分析Engine具体是什么。

1. debugPrintWARNINGDefault()

第一行也就没有啥,就是debug模式下打印几行日志,表示进入了此方法。
啥也不多说,上代码

func debugPrintWARNINGDefault() {
    debugPrint(`[WARNING] Now Gin requires Go 1.6 or later and Go 1.7 will be required soon.

`)
    debugPrint(`[WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.

`)
}

func debugPrint(format string, values ...interface{}) {
    if IsDebugging() {
        log.Printf("[GIN-debug] "+format, values...)
    }
}
2. engine := New()

看New方法之前,首先来了解下Engine这个结构体

type Engine struct {
    RouterGroup

    RedirectTrailingSlash bool

    RedirectFixedPath bool
    HandleMethodNotAllowed bool
    ForwardedByClientIP    bool
    AppEngine bool
    UseRawPath bool
    UnescapePathValues bool
    MaxMultipartMemory int64

    delims           render.Delims
    secureJsonPrefix string
    HTMLRender       render.HTMLRender
    FuncMap          template.FuncMap
    allNoRoute       HandlersChain
    allNoMethod      HandlersChain
    noRoute          HandlersChain
    noMethod         HandlersChain
    pool             sync.Pool
    trees            methodTrees
}

RouterGroup 描述的是路由的一个父节点,里面包含了父节点的一些属性

type RouterGroup struct {
    Handlers HandlersChain //父节点路由的监听器,实际上最后也是一个带有上下文指针的回调
    basePath string  //路由路径,相对于子路由的上级路径
    engine   *Engine //父节点路由的Engine实体
    root     bool   //是否为根节点路由
}

接下来就是几个bool类型的变量,主要是对重定向、转发等一些属性的控制
MaxMultipartMemory 从http.Request当中解析处理的最大内存上限
还有一个比较重要的参数就是pool变量

type Pool struct {
    noCopy noCopy

    local     unsafe.Pointer // local fixed-size per-P pool, actual type is [P]poolLocal
    localSize uintptr        // size of the local array

    // New optionally specifies a function to generate
    // a value when Get would otherwise return nil.
    // It may not be changed concurrently with calls to Get.
    New func() interface{}
}

Pool实际上是Gin框架里面对于服务器处理请求定义的一个线程池模型,里面包含了线程池的最大上限,以及每个线程的同步异步处理。详情请细读pool源码,这里就不作展开了。
接下来看gin的New方法

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,
        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
}

第一步,构造了Engine对象,并传入了所需参数。
第二步,将engine自身的父节点指向了自己,因为这里并没有对路由进行分组。
第三步,将pool的New变量指向了一个匿名函数,并返回了包含有engine的Context。
那么,请看engine.allocateContext()的代码

func (engine *Engine) allocateContext() *Context {
    return &Context{engine: engine}
}

以及Context结构体代码

type Context struct {
    writermem responseWriter
    Request   *http.Request
    Writer    ResponseWriter

    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
}

里面包含了请求的一系列参数

3. engine.Use(Logger(), Recovery())

这里实际上是传入了默认的中间件,日志和基本异常处理。当然也可以自定义,具体可以参照Gin手册中的使用方法。
接下来简单看下Logger和Recovery的源码

func Logger() HandlerFunc {
    return LoggerWithWriter(DefaultWriter)
}

func LoggerWithWriter(out io.Writer, notlogged ...string) HandlerFunc {
    isTerm := true

    if w, ok := out.(*os.File); !ok ||
        (os.Getenv("TERM") == "dumb" || (!isatty.IsTerminal(w.Fd()) && !isatty.IsCygwinTerminal(w.Fd()))) ||
        disableColor {
        isTerm = false
    }

    var skip map[string]struct{}

    if length := len(notlogged); length > 0 {
        skip = make(map[string]struct{}, length)

        for _, path := range notlogged {
            skip[path] = struct{}{}
        }
    }

    return func(c *Context) {
        // Start timer
        start := time.Now()
        path := c.Request.URL.Path
        raw := c.Request.URL.RawQuery

        // Process request
        c.Next()

        // Log only when path is not being skipped
        if _, ok := skip[path]; !ok {
            // Stop timer
            end := time.Now()
            latency := end.Sub(start)

            clientIP := c.ClientIP()
            method := c.Request.Method
            statusCode := c.Writer.Status()
            var statusColor, methodColor, resetColor string
            if isTerm {
                statusColor = colorForStatus(statusCode)
                methodColor = colorForMethod(method)
                resetColor = reset
            }
            comment := c.Errors.ByType(ErrorTypePrivate).String()

            if raw != "" {
                path = path + "?" + raw
            }

            fmt.Fprintf(out, "[GIN] %v |%s %3d %s| %13v | %15s |%s %-7s %s %s\n%s",
                end.Format("2006/01/02 - 15:04:05"),
                statusColor, statusCode, resetColor,
                latency,
                clientIP,
                methodColor, method, resetColor,
                path,
                comment,
            )
        }
    }
}

主要是对请求参数的打印

func Recovery() HandlerFunc {
    return RecoveryWithWriter(DefaultErrorWriter)
}
func RecoveryWithWriter(out io.Writer) HandlerFunc {
    var logger *log.Logger
    if out != nil {
        logger = log.New(out, "\n\n\x1b[31m", log.LstdFlags)
    }
    return func(c *Context) {
        defer func() {
            if err := recover(); err != nil {
                if logger != nil {
                    stack := stack(3)
                    httprequest, _ := httputil.DumpRequest(c.Request, false)
                    logger.Printf("[Recovery] %s panic recovered:\n%s\n%s\n%s%s", timeFormat(time.Now()), string(httprequest), err, stack, reset)
                }
                c.AbortWithStatus(http.StatusInternalServerError)
            }
        }()
        c.Next()
    }
}

最终也实际上将异常信息输出到日志中

二、请求监听器设置

这里是以GET请求为例,做的简单分析

func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes {
    return group.handle("GET", relativePath, handlers)
}

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模型有关。

  1. 取得监听器所监听路由的绝对完整路径
func (group *RouterGroup) calculateAbsolutePath(relativePath string) string {
    return joinPaths(group.basePath, relativePath)
}
func joinPaths(absolutePath, relativePath string) string {
    if relativePath == "" {
        return absolutePath
    }

    finalPath := path.Join(absolutePath, relativePath)
    appendSlash := lastChar(relativePath) == '/' && lastChar(finalPath) != '/'
    if appendSlash {
        return finalPath + "/"
    }
    return finalPath
}
  1. 将group,也就是父节点下所有的监听器合并
func (group *RouterGroup) combineHandlers(handlers HandlersChain) HandlersChain {
    finalSize := len(group.Handlers) + len(handlers)
    if finalSize >= int(abortIndex) {
        panic("too many handlers")
    }
    mergedHandlers := make(HandlersChain, finalSize)
    copy(mergedHandlers, group.Handlers)
    copy(mergedHandlers[len(group.Handlers):], handlers)
    return mergedHandlers
}
  1. 将所得到的信息添加到父节点中,并返回
func (engine *Engine) addRoute(method, path string, handlers HandlersChain) {
    assert1(path[0] == '/', "path must begin with '/'")
    assert1(method != "", "HTTP method can not be empty")
    assert1(len(handlers) > 0, "there must be at least one handler")

    debugPrintRoute(method, path, handlers)
    root := engine.trees.get(method)
    if root == nil {
        root = new(node)
        engine.trees = append(engine.trees, methodTree{method: method, root: root})
    }
    root.addRoute(path, handlers)
}

三、让router 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)
    err = http.ListenAndServe(address, engine)
    return
}

func resolveAddress(addr []string) string {
    switch len(addr) {
    case 0:
        if port := os.Getenv("PORT"); port != "" {
            debugPrint("Environment variable PORT=\"%s\"", port)
            return ":" + port
        }
        debugPrint("Environment variable PORT is undefined. Using port :8080 by default")
        return ":8080"
    case 1:
        return addr[0]
    default:
        panic("too much parameters")
    }
}

func ListenAndServe(addr string, handler Handler) error {
    server := &Server{Addr: addr, Handler: handler}
    return server.ListenAndServe()
}

此步骤代码不多,无非两件事

  1. 得到配置的地址以及端口
  2. 开启监听模式,传入地址端口以及engine指针

总结

总结来说要关注几个重点

  1. Engine模型有哪些内容,RouterGroup是做什么的
  2. pool在Engine中的作用
  3. 上下文Context,在整个请求过程中是如何串联起来的

今天是程序员节,在GO学习当中的一些心得分享。
祝大家1024 happy。
另外,如有转载,请注明出处。
一只爱代码的猫————皮卡丘

GO服务框架Gin源码解读<一>_第1张图片
WechatIMG5.jpeg

你可能感兴趣的:(GO服务框架Gin源码解读<一>)