gin:gin是一个用golang编写的web框架,它采用了类Martini的api,有更好的性能,速度快40倍,如果你需要非常好的性能,就使用gin吧
功能列表:
- 鉴权(auth)
- 上下文(context)、参数绑定(binding)、渲染(render)
- 中间件(middleware)
- 路由(routergroup、tree)
- gin engine(Engine)
核心文件及目录结构:
gin/
auth.go # 鉴权
binding/ # 包含参数绑定的各种实现,比如form表单、header头、query参数,json、xml、toml、yaml、protobuf等格式的body数据解析格式
context.go # gin自定义的context实现,
gin.go # gin引擎的核心文件
routergroup.go # 路由组的实现,不再使用httprouter中的router,自己实现了routergroup
tree.go # 基于radix tree的数据结构,用于保存路由信息,基于httprouter的实现(https://github.com/julienschmidt/httprouter)
render/ # 数据渲染相关,包含html、json、text、xml、yaml、protobuf等格式的数据渲染
其他文件及目录
gin/
deprecated.go # 不推荐使用的内容放到这个文件下
ginS # gin engine的单例实现,一般不用于线上,在编写脚本时可能用得上
internal # 内部的一些实现,包含多个json库的封装,如jsoniter、sonic json等,bytes转string、string转bytes的函数封装。比较简单
debug.go # debug相关
errors.go # errors相关定义
logger.go # 日志输出实现
mode.go # 多种运行模式
recover.go # 异常恢复的捕捉
response_writer.go # 封装http ResponseWriter
utils.go # 工具包
gin架构设计
image.png
接下来分别从RouterGroup、Context、Engine来分析gin的核心源码实现
RouterGroup.go
// 路由组、实现了IRouter接口
type RouterGroup struct {
// Handlers 存储了调用Use函数的中间件
Handlers HandlersChain
// 存储路由组的url前缀
basePath string
// 存储了engine的地址,在路由组中可以直接调用engine的addRoute方法和noRoute、noMethod HandlerChain
engine *Engine
root bool
}
type IRouter interface {
IRoutes
Group(string, ...HandlerFunc) *RouterGroup
}
// IRoutes defines all router handle interface.
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
Match([]string, string, ...HandlerFunc) IRoutes
StaticFile(string, string) IRoutes
StaticFileFS(string, string, http.FileSystem) IRoutes
Static(string, string) IRoutes
StaticFS(string, http.FileSystem) IRoutes
}
// 当我们调用Group返回一个子路由组时,子路由组的Handlers继承了父路由组的所有handlers,basePath 也添加了父路由组的路径。所以父路由组的handlers都会作用于所有的子路由组中
func (group *RouterGroup) Group(relativePath string, handlers ...HandlerFunc) *RouterGroup {
return &RouterGroup{
Handlers: group.combineHandlers(handlers),
basePath: group.calculateAbsolutePath(relativePath),
engine: group.engine,
}
}
// 将middleware 追加到group的handlers中
func (group *RouterGroup) Use(middleware ...HandlerFunc) IRoutes {
group.Handlers = append(group.Handlers, middleware...)
return group.returnObj()
}
// 通用方法、下面的Handle、GET、POST等都会调用handle
func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoutes {
// 从group中计算basePath + relativePath,返回根路径
absolutePath := group.calculateAbsolutePath(relativePath)
// 从group中返回所有middleware和传进来的handlers
handlers = group.combineHandlers(handlers)
// 调用engine的addRoute方法,将url和handlers写入httprouter的前缀树中
group.engine.addRoute(httpMethod, absolutePath, handlers)
return group.returnObj()
}
Context.go
type Context struct {
// 对ResponseWriter的实现
writermem responseWriter
// 保留ServeHTTP的Request
Request *http.Request
// 自定义的ResponseWriter接口、组合了http.ResponseWriter、http.Hijacker、http.Flusher接口,
Writer ResponseWriter
// 存储url的restful风格的参数:/index/:name/:id
Params Params
// 存储tree中当前url的handlers链(包含middlewares和业务处理handler)
handlers HandlersChain
// 记录Next函数处理Handlers链的位置
index int8
// url的全路径
fullPath string
// 保存engine的指针
engine *Engine
// 保存从tree中通过getValue返回的url参数,params的值会赋值给上面的Params
params *Params
skippedNodes *[]skippedNode
mu sync.RWMutex
// 存储context上下文数据
Keys map[string]any
Errors errorMsgs
Accepted []string
// 缓存c.Request.URL.Query()中的值
queryCache url.Values
// 缓存c.Request.PostForm中的数据,包含POST、PATCH、PUT方法的body参数
formCache url.Values
sameSite http.SameSite
}
从sync.Pool中取出context后,做一些初始化的工作
func (c *Context) reset() {
c.Writer = &c.writermem
c.Params = c.Params[:0]
c.handlers = nil
c.index = -1
c.fullPath = ""
c.Keys = nil
c.Errors = c.Errors[:0]
c.Accepted = nil
c.queryCache = nil
c.formCache = nil
c.sameSite = 0
*c.params = (*c.params)[:0]
*c.skippedNodes = (*c.skippedNodes)[:0]
}
由于context不是线程安全的,如果要在多个协程中使用context,可以调用封装好的Copy函数
func (c *Context) Copy() *Context {}
Next函数用于递归调用Middleware,先执行所有Middleware函数调用Next()之前的部分,再执行业务逻辑的Handler,最后采用先进后出的方式,依次执行所有Middleware函数调用Next()之后的内容。
func (c *Context) Next() {
c.index++
for c.index < int8(len(c.handlers)) {
c.handlers[c.index](c)
c.index++ // 这儿的index++,主要是为了解决,如果在Middleware中没有调用Next()函数,能够保证程序能够继续执行,而不是死循环
}
}
// 将index设置为abortIndex,终止后续middleware和业务handler的执行
func (c *Context) Abort() {
c.index = abortIndex
}
Key相关的函数
// 设置key,keys用于存储上下文数据。如果调用ShouldBindBodyWith方法,也会把body整个内容缓存为一个key/value存储到keys中
func (c *Context) Set(key string, value any) {}
func (c *Context) Get(key string) (value any, exists bool) {}
// 从Key中获取对应的string类型的值,其他类型的实现类似,不额外介绍
func (c *Context) GetString(key string) (s string) {
if val, ok := c.Get(key); ok && val != nil {
s, _ = val.(string)
}
return
}
func (c *Context) GetBool(key string) (b bool) {}
func (c *Context) GetInt(key string) (i int) {}
func (c *Context) GetInt64(key string) (i64 int64) {}
func (c *Context) GetUint(key string) (ui uint) {}
func (c *Context) GetUint64(key string) (ui64 uint64) {}
func (c *Context) GetFloat64(key string) (f64 float64) {}
func (c *Context) GetTime(key string) (t time.Time) {}
func (c *Context) GetDuration(key string) (d time.Duration) {}
func (c *Context) GetStringSlice(key string) (ss []string) {}
func (c *Context) GetStringMap(key string) (sm map[string]any) {}
func (c *Context) GetStringMapString(key string) (sms map[string]string) {}
func (c *Context) GetStringMapStringSlice(key string) (smss map[string][]string) {}
Param 相关函数
// 获取Param 参数,Param参数从路由tree中解析出来的,如果路由参数中有多个相同key的数据,只会返回第一个值
func (c *Context) Param(key string) string {
return c.Params.ByName(key)
}
// 也可以手动设置param参数的值
func (c *Context) AddParam(key, value string) {
c.Params = append(c.Params, Param{Key: key, Value: value})
}
Query参数相关函数
// 如果存在,从url query中返回值,否则返回"",是c.Request.URL.Query().Get(key)的简写
//
// GET /path?id=1234&name=Manu&value=
// c.Query("id") == "1234"
// c.Query("name") == "Manu"
// c.Query("value") == ""
// c.Query("wtf") == ""
func (c *Context) Query(key string) (value string) {
value, _ = c.GetQuery(key)
return
}
// 带默认值的Query
func (c *Context) DefaultQuery(key, defaultValue string) string {
if value, ok := c.GetQuery(key); ok {
return value
}
return defaultValue
}
// GetQuery 和 Query()类似, 多一个bool返回值,是Query()函数的底层调用
//
// GET /?name=Manu&lastname=
// ("Manu", true) == c.GetQuery("name")
// ("", false) == c.GetQuery("id")
// ("", true) == c.GetQuery("lastname")
func (c *Context) GetQuery(key string) (string, bool) {
if values, ok := c.GetQueryArray(key); ok {
return values[0], ok
}
return "", false
}
// QueryArray 返回[]string
func (c *Context) QueryArray(key string) (values []string) {
values, _ = c.GetQueryArray(key)
return
}
// 初始化query缓存
func (c *Context) initQueryCache() {
if c.queryCache == nil {
if c.Request != nil {
c.queryCache = c.Request.URL.Query()
} else {
c.queryCache = url.Values{}
}
}
}
// GetQueryArray 返回值带[]string和bool
func (c *Context) GetQueryArray(key string) (values []string, ok bool) {
c.initQueryCache() // 初始化query cache
values, ok = c.queryCache[key]
return
}
// QueryMap 返回字典值
func (c *Context) QueryMap(key string) (dicts map[string]string) {
dicts, _ = c.GetQueryMap(key)
return
}
// GetQueryMap 返回bool和字段值
func (c *Context) GetQueryMap(key string) (map[string]string, bool) {
c.initQueryCache() // 初始化query cache
return c.get(c.queryCache, key) // 返回map格式数据
}
// PostForm 从post中返回key
func (c *Context) PostForm(key string) (value string) {
value, _ = c.GetPostForm(key)
return
}
// DefaultPostForm 返回带默认值的key
func (c *Context) DefaultPostForm(key, defaultValue string) string {
if value, ok := c.GetPostForm(key); ok {
return value
}
return defaultValue
}
// GetPostForm 返回key值和bool
func (c *Context) GetPostForm(key string) (string, bool) {
if values, ok := c.GetPostFormArray(key); ok {
return values[0], ok
}
return "", false
}
// PostFormArray 返回切片类型的值
func (c *Context) PostFormArray(key string) (values []string) {
values, _ = c.GetPostFormArray(key)
return
}
// 初始化form 缓存
func (c *Context) initFormCache() {
if c.formCache == nil {
c.formCache = make(url.Values)
req := c.Request
if err := req.ParseMultipartForm(c.engine.MaxMultipartMemory); err != nil {
if !errors.Is(err, http.ErrNotMultipart) {
debugPrint("error on parse multipart form array: %v", err)
}
}
c.formCache = req.PostForm
}
}
// GetPostFormArray 返回切片类型值和bool
func (c *Context) GetPostFormArray(key string) (values []string, ok bool) {
c.initFormCache()
values, ok = c.formCache[key]
return
}
// PostFormMap 返回字典类型的值
func (c *Context) PostFormMap(key string) (dicts map[string]string) {
dicts, _ = c.GetPostFormMap(key)
return
}
// GetPostFormMap 返回字典类型的值和bool
func (c *Context) GetPostFormMap(key string) (map[string]string, bool) {
c.initFormCache()
return c.get(c.formCache, key)
}
//
func (c *Context) get(m map[string][]string, key string) (map[string]string, bool) {
dicts := make(map[string]string)
exist := false
for k, v := range m {
if i := strings.IndexByte(k, '['); i >= 1 && k[0:i] == key {
if j := strings.IndexByte(k[i+1:], ']'); j >= 1 {
exist = true
dicts[k[i+1:][:j]] = v[0]
}
}
}
return dicts, exist
}
Query参数Binding
MustBind在绑定出错时返回400错误
Bind在绑定出错时不会返回错误
// Bind 会通过请求方法和content-type来判断使用哪个类型的bind
func (c *Context) Bind(obj any) error {
b := binding.Default(c.Request.Method, c.ContentType())
return c.MustBindWith(obj, b)
}
// BindJSON 使用json解析body数据
func (c *Context) BindJSON(obj any) error {
return c.MustBindWith(obj, binding.JSON)
}
// 同上
func (c *Context) BindXML(obj any) error {}
func (c *Context) BindQuery(obj any) error {}
func (c *Context) BindYAML(obj any) error {}
func (c *Context) BindTOML(obj any) error {}
// BindHeader 绑定header数据到obj对象
func (c *Context) BindHeader(obj any) error {
return c.MustBindWith(obj, binding.Header)
}
// BindUri 将uri参数绑定到obj
func (c *Context) BindUri(obj any) error {
if err := c.ShouldBindUri(obj); err != nil {
c.AbortWithError(http.StatusBadRequest, err).SetType(ErrorTypeBind) //nolint: errcheck
return err
}
return nil
}
// MustBindWith 使用提供的binding将数据绑定到obj,如果绑定出错,返回400错误
func (c *Context) MustBindWith(obj any, b binding.Binding) error {
if err := c.ShouldBindWith(obj, b); err != nil {
c.AbortWithError(http.StatusBadRequest, err).SetType(ErrorTypeBind) //nolint: errcheck
return err
}
return nil
}
// ShouldBind
func (c *Context) ShouldBind(obj any) error {
b := binding.Default(c.Request.Method, c.ContentType())
return c.ShouldBindWith(obj, b)
}
// ShouldBindJSON
func (c *Context) ShouldBindJSON(obj any) error {
return c.ShouldBindWith(obj, binding.JSON)
}
func (c *Context) ShouldBindXML(obj any) error {}
func (c *Context) ShouldBindQuery(obj any) error {}
func (c *Context) ShouldBindYAML(obj any) error {}
func (c *Context) ShouldBindTOML(obj any) error {}
func (c *Context) ShouldBindHeader(obj any) error {}
func (c *Context) ShouldBindUri(obj any) error {}
// ShouldBindWith 使用提供的binding将request数据绑定到obj,出现错误不会返回400
func (c *Context) ShouldBindWith(obj any, b binding.Binding) error {
return b.Bind(c.Request, obj)
}
// ShouldBindBodyWith 与 ShouldBindWith 类似, 但会body中的数据缓存到context的Keys中,供下次调用重用
// 注意: 该方法在绑定之前读取body数据。所以如果只调用一次的话,使用ShouldBindWith会有更好的性能,
func (c *Context) ShouldBindBodyWith(obj any, bb binding.BindingBody) (err error) {
var body []byte
if cb, ok := c.Get(BodyBytesKey); ok {
if cbb, ok := cb.([]byte); ok {
body = cbb
}
}
if body == nil {
body, err = io.ReadAll(c.Request.Body)
if err != nil {
return err
}
c.Set(BodyBytesKey, body)
}
return bb.BindBody(body, obj)
}
Cookie相关函数
// 读写Cookie
func (c *Context) SetCookie(name, value string, maxAge int, path, domain string, secure, httpOnly bool) {}
func (c *Context) Cookie(name string) (string, error) {}
Render渲染相关函数
// Render 写入响应头和调用Render来渲染数据
func (c *Context) Render(code int, r render.Render) {
c.Status(code)
if !bodyAllowedForStatus(code) {
r.WriteContentType(c.Writer)
c.Writer.WriteHeaderNow()
return
}
if err := r.Render(c.Writer); err != nil {
// Pushing error to c.Errors
_ = c.Error(err)
c.Abort()
}
}
// 使用http模板渲染数据,内部统一调用Render函数来渲染数据
func (c *Context) HTML(code int, name string, obj any) {}
func (c *Context) IndentedJSON(code int, obj any) {} // 使用智能json
func (c *Context) SecureJSON(code int, obj any) {}
func (c *Context) JSONP(code int, obj any) {}
func (c *Context) JSON(code int, obj any) {}
func (c *Context) AsciiJSON(code int, obj any) {}
func (c *Context) PureJSON(code int, obj any) {}
func (c *Context) XML(code int, obj any) {}
func (c *Context) YAML(code int, obj any) {}
func (c *Context) TOML(code int, obj any) {}
func (c *Context) ProtoBuf(code int, obj any) {}
func (c *Context) String(code int, format string, values ...any) {}
// 重定向
func (c *Context) Redirect(code int, location string) {}
Negotiate
Negotiate主要用于根据客户端提供的格式,协商返回对应格式的数据
Engine.go
// HandlerFunc 定义了中间件和业务handler的函数
type HandlerFunc func(*Context)
// HandlersChain Handlers链,底层为handler的切片
type HandlersChain []HandlerFunc
// gin Engine
type Engine struct {
// 组合了RouterGroup,所以可在engine中可以直接调用Group()、GET()、POST()等方法
RouterGroup
// 启用自动重定向,如果当前路由无法匹配,但存在带(不带)尾部斜杠的路径处理程序。
// 例如,如果请求 /foo/ 但仅存在 /foo 的路由,则对于 GET 请求,客户端将重定向到 /foo,HTTP 状态代码为 301
// 和 307 用于所有其他请求方法。
RedirectTrailingSlash bool
// 启用路径修复,如果没有已为其注册的handler,则路由器尝试修复当前请求路径。
// 首先删除多余的路径元素,例如 ../ 或 // 。
// 然后路由器对清理后的路径进行不区分大小写的查找。
// 如果可以找到该路由的句柄,则路由器进行重定向到正确的路径,
// GET 请求的状态代码为 301, 所有其他请求方法返回的状态代码为 307 。
// 例如 /FOO 和 /..//Foo 可以重定向到 /foo。
// RedirectTrailingSlash 与此选项没有关系。
RedirectFixedPath bool
// 如果启用,如果当前请求无法路由,路由器将检查是否允许使用其他方法
// 如果是这种情况,则请求将得到“不允许的方法”的响应和 HTTP 405 状态码。
// 如果没有其他方法可路由,则返回 NotFound Handler
HandleMethodNotAllowed bool
UseRawPath bool
UnescapePathValues bool
// 移出额外的斜杠
RemoveExtraSlash bool
// 限制http.Request的 ParseMultipartForm 的maxMemory参数值
MaxMultipartMemory int64
// UseH2C 开启http2协议,使用http2client.
UseH2C bool
delims render.Delims
secureJSONPrefix string
HTMLRender render.HTMLRender
FuncMap template.FuncMap
allNoRoute HandlersChain
allNoMethod HandlersChain
noRoute HandlersChain
noMethod HandlersChain
// 存储context的pool
pool sync.Pool
// 使用压缩前缀树(radix tree) 存储url和handler的对应关系
trees methodTrees
maxParams uint16
maxSections uint16
trustedProxies []string
trustedCIDRs []*net.IPNet
}
// 验证Engine实现了IRouter
var _ IRouter = (*Engine)(nil)
func New() *Engine {
debugPrintWARNINGNew()
engine := &Engine{
RouterGroup: RouterGroup{
Handlers: nil,
basePath: "/",
root: true,
},
// ...
}
engine.RouterGroup.engine = engine
// 配置pool 生成临时对象的New函数
engine.pool.New = func() any {
return engine.allocateContext(engine.maxParams)
}
return engine
}
// Default 调用New函数初始化Engine,添加了Logger和Recover中间件
func Default() *Engine {
debugPrintWARNINGDefault()
engine := New()
engine.Use(Logger(), Recovery())
return engine
}
// context临时对象的生成方法,在New函数中调用
func (engine *Engine) allocateContext(maxParams uint16) *Context {
v := make(Params, 0, maxParams)
skippedNodes := make([]skippedNode, 0, engine.maxSections)
return &Context{engine: engine, params: &v, skippedNodes: &skippedNodes}
}
// 将中间件添加到RouterGroup的Handler链中
func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes {
engine.RouterGroup.Use(middleware...)
engine.rebuild404Handlers()
engine.rebuild405Handlers()
return engine
}
// 通过method、path和handler,将路由信息添加到tree中
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)
// root为空,就生成新的root节点
if root == nil {
root = new(node)
root.fullPath = "/"
engine.trees = append(engine.trees, methodTree{method: method, root: root})
}
// 添加路由到tree中、tree中保存了多个压缩前缀树结构(每个请求方法一个)
root.addRoute(path, handlers)
// Update maxParams
if paramsCount := countParams(path); paramsCount > engine.maxParams {
engine.maxParams = paramsCount
}
if sectionsCount := countSections(path); sectionsCount > engine.maxSections {
engine.maxSections = sectionsCount
}
}
// 调用http.ListenAndServe 启动服务,使用不同的模式启动服务
func (engine *Engine) Run(addr ...string) (err error) {}
func (engine *Engine) RunTLS(addr, certFile, keyFile string) (err error) {}
func (engine *Engine) RunUnix(file string) (err error) {}
func (engine *Engine) RunFd(fd int) (err error) {}
func (engine *Engine) RunListener(listener net.Listener) (err error) {}
// 实现了http.Handler接口,请求到达服务端后,会将请求交给ServeHTTP函数来处理
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
// 从pool中获取一个context
c := engine.pool.Get().(*Context)
// 初始化ResponseWriter、Requet
c.writermem.reset(w)
c.Request = req
c.reset()
// handleHTTPRequest处理实际的请求
engine.handleHTTPRequest(c)
// 将context放回pool
engine.pool.Put(c)
}
// 处理具体的请求
func (engine *Engine) handleHTTPRequest(c *Context) {
httpMethod := c.Request.Method
rPath := c.Request.URL.Path
unescape := false
if engine.UseRawPath && len(c.Request.URL.RawPath) > 0 {
rPath = c.Request.URL.RawPath
unescape = engine.UnescapePathValues
}
if engine.RemoveExtraSlash {
rPath = cleanPath(rPath)
}
// Find root of the tree for the given HTTP method
t := engine.trees
// 从trees中根据请求方法类型获取对应的tree
for i, tl := 0, len(t); i < tl; i++ {
if t[i].method != httpMethod {
continue
}
root := t[i].root
// 从tree中获取handlers,并返回handlers和params
value := root.getValue(rPath, c.params, c.skippedNodes, unescape)
if value.params != nil {
c.Params = *value.params
}
// handlers不为空,调用Next()方法递归调用middleware和业处理handler
if value.handlers != nil {
c.handlers = value.handlers
c.fullPath = value.fullPath
c.Next()
c.writermem.WriteHeaderNow()
return
}
if httpMethod != http.MethodConnect && rPath != "/" {
if value.tsr && engine.RedirectTrailingSlash {
redirectTrailingSlash(c)
return
}
if engine.RedirectFixedPath && redirectFixedPath(c, root, engine.RedirectFixedPath) {
return
}
}
break
}
// 如果开启了HandleMethodNotAllowed,就继续遍历tree,在其他tree中去匹配路由,如果匹配到后,就返回405错误
if engine.HandleMethodNotAllowed {
for _, tree := range engine.trees {
if tree.method == httpMethod {
continue
}
if value := tree.root.getValue(rPath, nil, c.skippedNodes, unescape); value.handlers != nil {
c.handlers = engine.allNoMethod
serveError(c, http.StatusMethodNotAllowed, default405Body)
return
}
}
}
// 返回404错误
c.handlers = engine.allNoRoute
serveError(c, http.StatusNotFound, default404Body)
}
到这里,gin的核心模块context、routergroup、engine的实现就分析完了,radix tree的实现可以参考httprouter。