Tips

Go Gin 框架简单实现

2022-06-01  本文已影响0人  Sun东辉

Go 版本:go version go1.18.2 darwin/amd64

Go 语言 Gin 框架主要包含七个部分,即:

下面,我只对每个部分的核心代码进行解释,整体实现在这里(代码总量不超过 500 行)。

HTTP 基础库

这部分其实就是 net/http 标准库的标准实现,包含路由注册、路径解析、服务监听和处理。

Context 上下文

context 的作用是为了将外部的请求、请求的处理和响应解耦,将扩展性和复杂性保留在内部,从而简化了外部调用的复杂度。

type Context struct {
    // HTTP 基础库的参数
    Writer http.ResponseWriter
    Req    *http.Request
    // 请求相关值
    Path   string
    Method string
    Params map[string]string
    // 响应信息
    StatusCode int
    // 中间件
    handlers []HandlerFunc
    index    int
    engine   *Engine
}

动态路由

Gin 最初使用了 httproutertree 树,后来自实现了相关代码,Tree 树的主要功能是基于特定的规则,匹配一个或多个路由值,如 /static/* 可以匹配到 /static/fav.ico,也可以匹配到 static/js/jQuery.js

type node struct {
    pattern  string  // 待匹配路由
    part     string  // 路由中的一部分
    children []*node // 子节点
    isWild   bool    // 是否精确匹配
}

分组控制

分组控制是 Web 框架提供的基础功能,大部分情况下,以相同的前缀进行区分,对路由进行分组,并支持分组的嵌套。

type RouterGroup struct {
    prefix      string
    middlewares []HandlerFunc
    parent      *RouterGroup
    engine      *Engine
}

type Engine struct {
    *RouterGroup
    router        *router
    groups        []*RouterGroup     // store all groups
    htmlTemplates *template.Template // for html render
    funcMap       template.FuncMap   // for html render
}

这里,我们将 Engine 作为最顶层的分组,也就是说 Engine 拥有 RouterGroup 所有的能力,这样,就可以将和路由有关的函数,都交给 RouterGroup 实现了。

中间件

中间件的作用,主要是为了支持用户自定义的非业务的技术类组件。

中间件的调用顺序是根据 index 记录当前执行到第几个中间件,当在中间件中调用 Next 方法时,控制权交给下一个中间件,直到调用最后一个中间件,然后再从后往前,调用每个中间件在 Next 方法之后定义的部分。

func (c *Context) Next() {
    c.index++
    s := len(c.handlers)
    for ; c.index < s; c.index++ {
        c.handlers[c.index](c)
    }
}

func (r *router) handle(c *Context) {
    n, params := r.getRoute(c.Method, c.Path)
    if n != nil {
        key := c.Method + "-" + n.pattern
        c.Params = params
        c.handlers = append(c.handlers, r.handlers[key])
    } else {
        c.handlers = append(c.handlers, func(c *Context) {
            c.String(http.StatusNotFound, "404 NOT FOUND: %s\\n", c.Path)
        })
    }
    c.Next()
}

模版渲染

这部分代码有两个作用

html/template 有两个对象,分别是 *template.Templatetemplate.FuncMap ,前者将所有模版加载进内存,后者是所有的自定义模版的渲染函数。

func (engine *Engine) SetFuncMap(funcMap template.FuncMap) {
    engine.funcMap = funcMap
}

func (engine *Engine) LoadHtmlGlob(pattern string) {
    engine.htmlTemplates = template.Must(template.New("").Funcs(engine.funcMap).ParseGlob(pattern))
}

错误恢复

错误恢复的主要作用就是在程式码发生了意料之外的错误时,在内部对错误进行处理,同时将程序恢复正常,继续往下执行直到结束。

func Recovery() HandlerFunc {
    return func(c *Context) {
        defer func() {
            if err := recover(); err != nil {
                message := fmt.Sprintf("%s", err)
                log.Printf("%s\\n\\n", trace(message))
                c.Fail(http.StatusInternalServerError, "Internal Server Error")
            }
        }()
        c.Next()
    }
}

func trace(message string) string {
    var pcs [32]uintptr
    _, _, n, _ := runtime.Caller(3) // 跳过前三个 caller,即 Callers 本身、上一层的 trace 和 defer func

    var str strings.Builder
    str.WriteString(message + "\\n Traceback:")
    for _, pc := range pcs[:n] {
        fn := runtime.FuncForPC(pc)
        file, line := fn.FileLine(pc)
        str.WriteString(fmt.Sprintf("\\n\\t%s:%d", file, line))
    }
    return str.String()
}

func Default() *Engine {
    engine := New()
    engine.Use(Logger(), Recovery())
    return engine
}

参考文章:7天用Go从零实现Web框架Gee教程

上一篇 下一篇

猜你喜欢

热点阅读