| 导语 最近考虑给SCF简单封一层web库,提供cgi的http协议处理、上下文、拦截器、html渲染等能力。很自然就想到了Gin框架,基于golang且框架比较轻量,这里简单把核心源码做个走读笔记
Gin is a HTTP web framework written in Go (Golang). It features a Martini-like API with much better performance – up to 40 times faster. If you need smashing performance, get yourself some Gin.
package main
import (
"fmt"
"net/http"
"github.com/gin-gonic/gin"
)
/* 中间件:token鉴权 */
func auth(ctx *gin.Context) {
token := ctx.Query("token")
if token == "abc" {
fmt.Println("Auth Succ: token=", token)
ctx.Next() // 鉴权通过:Next() 执行下个Handler
} else {
fmt.Println("Auth Fail: token=", token)
ctx.Abort() // 鉴权失败:Abort() 停止执行后台的Handler链
ctx.JSON(200, gin.H{"msg": "auth fail..."})
}
}
/* 业务Handler: curl "localhost:8080/ping?uid=123&name=jonny&token=abc" */
func pingHandler(ctx *gin.Context) {
url := ctx.Request.URL // 获取url
uid := ctx.Query("uid") // 获取url参数
name := ctx.DefaultQuery("name", "default") // 获取url参数
fmt.Println("Query url", url, "uid=", uid, "name=", name)
ctx.JSON(http.StatusOK, gin.H{"msg": "ping..."}) // 返回Json的Http回包
}
func main() {
r := gin.Default() // 创建和初始化默认gin.Engine
r.Use(auth) // 这行代码下面的路由都引入Auth中间件
r.GET("/ping", pingHandler) // 将/ping和pingHandler注册到路由trees
r.Run()
}
这段demo的大致流程是:
下面针对这个流程,走读一遍框架内部的核心代码
Gin最重要的数据结构就是Engine,由路由管理、上下文、以及一些参数配置组成
type Engine struct {
RouterGroup // 继承RouterGroup,实现路由管理能力
trees methodTrees // 路由树,加速路由Handler匹配
pool sync.Pool // 保存Context上下文,用Pool利于复用
...
}
可使用New()创建新的Engine实例,或者使用Default()创建初始化日志和异常捕获的Engine实例
func New() *Engine {
engine := &Engine{
RouterGroup: RouterGroup{
Handlers: nil,
basePath: "/",
root: true,
},
...
MaxMultipartMemory: defaultMultipartMemory,
trees: make(methodTrees, 0, 9),
}
engine.RouterGroup.engine = engine // 初始化路由管理
engine.pool.New = func() interface{} { // 初始化Context池
return engine.allocateContext()
}
return engine
}
func Default() *Engine {
debugPrintWARNINGDefault()
engine := New()
engine.Use(Logger(), Recovery()) // 注册日志和异常捕获中间件
return engine
}
Gin框架很重要一个概念就是中间件,其实就是注册到Engine的Handler函数链:
// 中间件函数:HandlerFunc defines the handler used by gin middleware
type HandlerFunc func(*Context)
// 中间件函数链:HandlersChain defines a HandlerFunc array.
type HandlersChain []HandlerFunc
把GET/POST等路由Handler注册到路由树中,后面ServeHTTP触发时,会在路由树中查找对应的回调Handler函数
func (engine *Engine) addRoute(method, path string, handlers HandlersChain) {
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)
}
最终路由匹配树大概结构如下:
初始化工作(日志、错误处理、中间件、路由等)完成后,调用Run()开启http监听
func (engine *Engine) Run(addr ...string) (err error) {
...
address := resolveAddress(addr)
debugPrint("Listening and serving HTTP on %s\n", address)
err = http.ListenAndServe(address, engine) // 底层就是调用http库
return
}
Gin底层处理就是调用了http库的ListenAndServe,可推理Engine实现了http的回调interface
// 1、golang内置net/http库接口定义
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
// 2、Engine的ServeHTTP实现
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
c := engine.pool.Get().(*Context) // 从Pool内获取Context实例
c.writermem.reset(w) // 初始化http应答包
c.Request = req // 初始化http请求包
c.reset() // 初始化配置等
engine.handleHTTPRequest(c) // @处理http请求逻辑
engine.pool.Put(c) // 回收Context
}
// 3、处理http请求:通过路由树匹配到Handler链并执行
func (engine *Engine) handleHTTPRequest(c *Context) {
httpMethod := c.Request.Method
path := c.Request.URL.Path
...
// 通过路由树计算要回调的Handler
t := engine.trees
for i, tl := 0, len(t); i < tl; i++ {
if t[i].method != httpMethod {
continue
}
root := t[i].root
// 获取路由节点Handler
handlers, params, tsr := root.getValue(path, c.Params, unescape)
if handlers != nil {
c.handlers = handlers
c.Params = params
c.Next() // @ 开始执行业务Handler链
c.writermem.WriteHeaderNow()
return
}
...
}
}
从Handler的定义type HandlerFunc func(*Context)可以看到,Context是每个http请求回调函数的唯一入参
type Context struct {
Request *http.Request // 请求包
writermem responseWriter // 响应包
Params Params // 请求Url路径
handlers HandlersChain // @ 这个请求注册的业务Handler执行链(中间件)
index int8 // @ 当前HandlersChain的执行索引
engine *Engine
Keys map[string]interface{} // 业务保存上下文数据
...
}
Gin通过index来控制HandlersChain的执行流,正常请求都是index+1从左到右执行,也可调用Next()来实现before和after拦截器能力。如果遇到鉴权失败等情况,可调用Abort()停止后面的handlers执行
func (c *Context) Next() {
c.index++
// 按index顺序从左到右执行
for s := int8(len(c.handlers)); c.index < s; c.index++ {
c.handlers[c.index](c)
}
}
func (c *Context) Abort() {
c.index = abortIndex
}
Gin封装了很多便捷的http参数获取函数,如Query()的源码实现:
func (c *Context) GetQueryArray(key string) ([]string, bool) {
if values, ok := c.Request.URL.Query()[key]; ok && len(values) > 0 {
return values, true
}
return []string{}, false
}
也可使用Bind()直接转化成业务数据结构,非常好用,Bind的相关实现单独放在/binding目录内,这里就不展开,看.go文件基本也了解了
json.go
query.go
xml.go
binding.go
form.go
msgpack.go
uri.go
yaml.go
form_mapping.go
protobuf.go
Context提供了数据结构Keys mapstringinterface{}来给业务保存请求的一些上下文信息,业务可通过Set/Get操作
func (c *Context) Get(key string) (value interface{}, exists bool) {
value, exists = c.Keys[key]
return
}
func (c *Context) Set(key string, value interface{}) {
if c.Keys == nil {
c.Keys = make(map[string]interface{})
}
c.Keys[key] = value
}
type RouterGroup struct {
Handlers HandlersChain // 关联的Handler执行链
basePath string
engine *Engine
root bool
}
如demo所示,Gin提供了GET、POST等多种http路由注册方法,统一定义在IRoutes接口中:
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
}
虽然Gin提供的路由注册接口很多,但这些接口最后都是调用handle()完成注册处理:
func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoutes {
// 1、计算出绝对路径
absolutePath := group.calculateAbsolutePath(relativePath)
// 2、将handlers加入HandlersChain
handlers = group.combineHandlers(handlers)
// 3、添加到Engine的路由tree
group.engine.addRoute(httpMethod, absolutePath, handlers) 中
return group.returnObj()
}
Gin的中间件便于扩展且功能强大,但其实注册执行逻辑还是比较简单,就是对HandlersChain的数组操作
func (group *RouterGroup) Use(middleware ...HandlerFunc) IRoutes {
// 添加到HandlersChain中
group.Handlers = append(group.Handlers, middleware...)
return group.returnObj()
}
func (group *RouterGroup) combineHandlers(handlers HandlersChain) HandlersChain {
// 判断Hander阈值
finalSize := len(group.Handlers) + len(handlers)
if finalSize >= int(abortIndex) {
panic("too many handlers")
}
// 就是Append入group.Handlers而已
mergedHandlers := make(HandlersChain, finalSize)
copy(mergedHandlers, group.Handlers)
copy(mergedHandlers[len(group.Handlers):], handlers)
return mergedHandlers
}
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。