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。
另外,如有转载,请注明出处。
一只爱代码的猫————皮卡丘

WechatIMG5.jpeg

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349

推荐阅读更多精彩内容