Go Gin 框架简单实现
Go 版本:go version go1.18.2 darwin/amd64
Go 语言 Gin 框架主要包含七个部分,即:
- HTTP 基础库;
- Context(上下文);
- 动态路由(Tree 树);
- 分组控制;
- 中间件;
- 模版渲染;
- 错误恢复;
下面,我只对每个部分的核心代码进行解释,整体实现在这里(代码总量不超过 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 最初使用了 httprouter 的 tree 树,后来自实现了相关代码,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标准库进行渲染(包括普通渲染、列表渲染、对象渲染等)
html/template 有两个对象,分别是 *template.Template 和 template.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
}