地址:https://github.com/gin-gonic/gin
gin作为web框架,在社区中拥有较高的活跃度,通过使用前缀树结构存储,减少内存消耗,同时加快了路由查询速度,并且在基本功能的基础上提供了各种middleware,可以在访问对应的url时回调对应的handler
也给大家推荐一个go技术选型参考
组件 | 主选 | 备选 | 选型主要考虑 |
对比 |
Web框架 | Gin[GitHub - gin-gonic/gin: 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.] | Echo、Fiber | 社区活跃度/成熟度/易用性/性能 | Gin/Echo: 社区活跃度较高、性能较高 Fiber: 基于FastHttp,性能很高 |
Redis | go-redis[GitHub - go-redis/redis: Type-safe Redis client for Golang] | redigo | 社区活跃度/性能/功能 | go-redis: 社区活跃度较高、性能较高、集群支持好 redigo: 社区活跃度较高 |
SQL | gendry[GitHub - didi/gendry: a golang library for sql builder] | Gorm、Xorm、sqlx | 性能/可维护性/功能 | gendry: sql builder简单封装,易维护 Gorm/Xorm: 功能强大,社区活跃度高 sqlx: sql builder封装,社区活跃度高 |
日志 | zap[GitHub - uber-go/zap: Blazing fast, structured, leveled logging in Go.] | logrus、seelog | 性能/功能 | zap: 性能较高,结构化日志首选 logrus: 性能适中,格式定义灵活 seelog: 性能一般,简单易用 |
配置 | go-toml[GitHub - pelletier/go-toml: Go library for the TOML file format] | viper | 易用性/功能 | go-toml: toml格式处理,简单易用 viper: 支持多种格式配置、功能强大、依赖较复杂 |
import (
"fmt"
"github.com/gin-gonic/gin"
"os"
)
func main() {
//r := gin.Default()
r := gin.New()
//use must before register route
//注册gin自带中间件
r.Use(gin.Logger())
r.Use(gin.Recovery())
//注册一个路由和对应的handler
r.GET("/test", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "hello",
})
})
//在/test2的路由组中注册中间件,不影响/test
test := r.Group("/test2")
test.Use(MyLogger)
test.Use(MyLogger2)
test.GET("/hello", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "hello gin",
})
})
r.Run("127.0.0.1:8899")
}
func MyLogger(c *gin.Context) {
fmt.Fprintf(os.Stdout, "%s|%s mylogger attach", c.Request.Method, c.Request.URL)
}
func MyLogger2(c *gin.Context) {
fmt.Fprintf(os.Stdout, "%s|%s mylogger222222 attach", c.Request.Method, c.Request.URL)
}
这个demo包含了gin框架的一些常用用法,包括自己实现一个简单的middleware,启动后,通过访问localhost:8899/test和localhost:8899/test2/hello可以看到对应的返回,并且可以在控制台看到注册的middleware被调用
可以看到gin不仅提供了middleware功能,还提供了根据url path注册不同的middleware功能,middleware可以是全局的也可以只针对某个path
// Use attaches a global middleware to the router. i.e. the middleware attached through Use() will be
// included in the handlers chain for every single request. Even 404, 405, static files...
// For example, this is the right place for a logger or error management middleware.
func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes {
engine.RouterGroup.Use(middleware...)
engine.rebuild404Handlers()
engine.rebuild405Handlers()
return engine
}
// Use adds middleware to the group, see example code in GitHub.
func (group *RouterGroup) Use(middleware ...HandlerFunc) IRoutes {
group.Handlers = append(group.Handlers, middleware...)
return group.returnObj()
}
// Engine is the framework's instance, it contains the muxer, middleware and configuration settings.
// Create an instance of Engine, by using New() or Default()
type Engine struct {
RouterGroup
......
}
// RouterGroup is used internally to configure router, a RouterGroup is associated with
// a prefix and an array of handlers (middleware).
type RouterGroup struct {
Handlers HandlersChain
basePath string
engine *Engine
root bool
}
engine就是gin.New()或者gin.Default()创建出来的全局实例,其中比较重要的属性之一就是RouterGroup,路由组中包括了注册路由需要的基本信息
Use注册中间件流程:
1. 先看透本质,一个middleware其实就是一个handlerFunc,和注册到path上的api方法签名相同
2. 通过路由组进行注册,将中间件放到RouterGroup路由组的handlers切片中
3. 返回对应的RouterGroup,所以Use函数还可以采用链式调用:
test.Use(MyLogger).Use(MyLogger2)
test.Use(MyLogger, MyLogger2)
// GET is a shortcut for router.Handle("GET", path, handle).
func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes {
return group.handle(http.MethodGet, 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()
}
func (group *RouterGroup) combineHandlers(handlers HandlersChain) HandlersChain {
finalSize := len(group.Handlers) + len(handlers)
assert1(finalSize < int(abortIndex), "too many handlers")
mergedHandlers := make(HandlersChain, finalSize)
copy(mergedHandlers, group.Handlers)
copy(mergedHandlers[len(group.Handlers):], handlers)
return mergedHandlers
}
func (engine *Engine) addRoute(method, path string, handlers HandlersChain) {
......
root := engine.trees.get(method)
if root == nil {
root = new(node)
root.fullPath = "/"
engine.trees = append(engine.trees, methodTree{method: method, root: root})
}
root.addRoute(path, handlers)
......
}
从2.2中可以知道engine使用了组合的特性,将RouterGroup组合使用,所以RouterGroup实现的方法,engine也可以调用
Get、DELETE、POST等方法都调用了handle
handle注册api主要流程:
1. 通过combineHandlers方法,将之前在RouterGroup中的中间件和对应的api方法进行合并,并且api方法放到后面,关系到之后的调用顺序,可以从这里看出,先注册的中间件先调用,然后才到api的handler
2. 从前缀树中找到get对应的树,并把path和合并后的handlers放到树中(前缀树的代码会在后续的文章中为大家详解)
3. 这里也可以理解,handler最开始都是放到RouterGroup的切片中,并不会生效,只有通过handle方法,将合并后的handlers放到前缀树中才算生效,所以在Get、Post等方法后使用use是不符合预期的
// Run attaches the router to a http.Server and starts listening and serving HTTP requests.
// It is a shortcut for http.ListenAndServe(addr, router)
// Note: this method will block the calling goroutine indefinitely unless an error happens.
func (engine *Engine) Run(addr ...string) (err error) {
......
err = http.ListenAndServe(address, engine.Handler())
return
}
func (engine *Engine) Handler() http.Handler {
if !engine.UseH2C {
return engine
}
h2s := &http2.Server{}
return h2c.NewHandler(engine, h2s)
}
// ServeHTTP conforms to the http.Handler interface.
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
c := engine.pool.Get().(*Context)
c.writermem.reset(w)
c.Request = req
c.reset()
engine.handleHTTPRequest(c)
engine.pool.Put(c)
}
func (engine *Engine) handleHTTPRequest(c *Context) {
// Find route in tree
value := root.getValue(rPath, c.params, c.skippedNodes, unescape)
if value.params != nil {
c.Params = *value.params
}
if value.handlers != nil {
c.handlers = value.handlers
c.fullPath = value.fullPath
c.Next()
c.writermem.WriteHeaderNow()
return
}
}
// Next should be used only inside middleware.
// It executes the pending handlers in the chain inside the calling handler.
// See example in GitHub.
func (c *Context) Next() {
c.index++
for c.index < int8(len(c.handlers)) {
c.handlers[c.index](c)
c.index++
}
}
Run主要流程:
1. 调用golang原生http库,指定一个http.handler,开启监听
2. engine.handler是gin为了支持H2C后来支持的,原来的代码直接传入的是engine,H2C需要了解的可以自行百度,但是一般是不推荐用的,还是推荐用golang默认支持的H2
3. engine作为一个handler实现了ServeHTTP方法,当访问对应的api时,会走到该方法中
4. serveHTTP主要调用的handleHTTPRequest,其他步骤是通过sync.Pool来操作context,用于减少context的内存分配和初始化次数来增加效率减少GC
5. handleHTTPRequest通过访问的httpMethod和uri,getValue找到前缀树中对应的节点,返回的nodeValue对象就是2.3中添加到树中的节点,其中保存了中间件和api对应的handler
6. 通过Next()函数依次调用handler
还有很多功能未在本文中涉及,但是基本的思想和功能已经涵盖,如需继续拓展可以到第一章中的gin框架链接中学习