gin是一个 Web应用框架,拥有良好的性能和简单明了的接口。同时支持中间件,类型绑定等实用功能。node
在实际开发中,不多会直接实用http.Server。而本身搭建框架有必定成本,同时没有通过系统的校验,容易出现问题。而现有的框架中,gin拥有良好的性能,更重要的是接口清晰明了,接入成本极低。同时,其支持的功能也是多种多样,如中间件,类型绑定,日志规范。git
如下是从官网拿到的性能对比指标表github
Benchmark name | (1) | (2) | (3) | (4) |
---|---|---|---|---|
BenchmarkGin_GithubAll | 30000 | 48375 | 0 | 0 |
BenchmarkAce_GithubAll | 10000 | 134059 | 13792 | 167 |
BenchmarkBear_GithubAll | 5000 | 534445 | 86448 | 943 |
BenchmarkBeego_GithubAll | 3000 | 592444 | 74705 | 812 |
BenchmarkBone_GithubAll | 200 | 6957308 | 698784 | 8453 |
接受/ping路径的Get请求,并返回message:"pong"bash
package main
import "github.com/gin-gonic/gin"
func main() {
router := gin.Default()
router.GET("/ping", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "pong",
})
})
router.Run() // listen and serve on 0.0.0.0:8080
}
复制代码
router := gin.Default()
router.GET("/someGet", getting)
router.POST("/somePost", posting)
router.PUT("/somePut", putting)
router.DELETE("/someDelete", deleting)
router.PATCH("/somePatch", patching)
router.HEAD("/someHead", head)
router.OPTIONS("/someOptions", options)
复制代码
router := gin.Default()
// The request responds to a url matching: /welcome?firstname=Jane&lastname=Doe
/**** example1: 解析Querystring ****/
router.GET("/welcome", func(c *gin.Context) {
firstname := c.DefaultQuery("firstname", "Guest")
// shortcut for c.Request.URL.Query().Get("lastname")
lastname := c.Query("lastname")
c.String(http.StatusOK, "Hello %s %s", firstname, lastname)
})
/**** example2: 解析表单 ****/
router.POST("/form_post", func(c *gin.Context) {
message := c.PostForm("message")
nick := c.DefaultPostForm("nick", "anonymous")
c.JSON(200, gin.H{
"status": "posted",
"message": message,
"nick": nick,
})
})
复制代码
支持以组为单位的路由,下面栗子就是以/v1开头,以/v2开头的两组配置。路由组能够共享一样的配置,好比路由组v1能够使用中间件a。而v2能够使用另外一个中间件,互不影响。数据结构
router := gin.Default()
// Simple group: v1
v1 := router.Group("/v1")
{
v1.POST("/login", loginEndpoint)
v1.POST("/submit", submitEndpoint)
v1.POST("/read", readEndpoint)
}
// Simple group: v2
v2 := router.Group("/v2")
{
v2.POST("/login", loginEndpoint)
v2.POST("/submit", submitEndpoint)
v2.POST("/read", readEndpoint)
}
router.Run(":8080")
复制代码
中间件是对框架能力一个很是好的抽象。以组件的形式,为路由或路由组提供插件式功能。也能够本身实现中间件,加入到Use中来。app
// Creates a router without any middleware by default
r := gin.New()
// Global middleware
r.Use(gin.Logger())
r.Use(gin.Recovery())
// Listen and serve on 0.0.0.0:8080
r.Run(":8080")
复制代码
中间件能够很是方便的定义日志格式框架
router.Use(gin.LoggerWithFormatter(func(param gin.LogFormatterParams) string {
// your custom format
return fmt.Sprintf("%s - [%s] \"%s %s %s %d %s \"%s\" %s\"\n",
param.ClientIP,
param.TimeStamp.Format(time.RFC1123),
param.Method,
param.Path,
param.Request.Proto,
param.StatusCode,
param.Latency,
param.Request.UserAgent(),
param.ErrorMessage,
)
}))
复制代码
使用 c.ShouldBind方法,能够将参数自动绑定到 struct.该方法是会检查 Url 查询字符串和 POST 的数据,并且会根据 content-type类型,优先匹配JSON或者 XML,以后才是 Form.ide
// 定义一个 Person 结构体,用来绑定数据
type Person struct {
Name string `form:"name"`
Address string `form:"address"`
Birthday time.Time `form:"birthday" time_format:"2006-01-02" time_utc:"1"`
}
func main() {
route := gin.Default()
route.GET("/testing", startPage)
route.Run(":8085")
}
func startPage(c *gin.Context) {
var person Person
// 绑定到 person
if c.ShouldBind(&person) == nil {
log.Println(person.Name)
log.Println(person.Address)
log.Println(person.Birthday)
}
c.String(200, "Success")
}
复制代码
gin 能够说全是在handler上作文章。下面咱们就以这三句话,一探gin。post
func main() {
r := gin.Default()
r.GET("/getb", GetDataB)
r.Run()
}
复制代码
r := gin.Default() 的定义以下性能
func Default() *Engine {
debugPrintWARNINGDefault()
engine := New()
engine.Use(Logger(), Recovery())
return engine
}
复制代码
Engine是gin中的一个很重要的概念。等下面对.r.Run分析时候,咱们会发现他的本质就是http.Server里面的handler实例!
这里看到engine.Use(Logger(), Recovery()) 直观上就很像以前提早的http中间件的某种实现 Logger()是日志中间件, Recovery()是针对panic的中间件(否则每一个handler都得写个panic处理逻辑)
来分析一下Recover中间件
func Recovery() HandlerFunc {
return RecoveryWithWriter(DefaultErrorWriter)
}
func RecoveryWithWriter(out io.Writer) HandlerFunc {
var logger *log.Logger
if out != nil {
logger = log.New(out, "\n\n\x1b[31m", log.LstdFlags)
}
return func(c *Context) {
defer func() {
if err := recover(); err != nil {
// 省略非关键代码
}
}()
c.Next()
}
}
复制代码
c.Next()能将多个中间件串联起来调用
// Next should be used only inside middleware.
// It executes the pending handlers in the chain inside the calling handler.
func (c *Context) Next() {
c.index++
for c.index < int8(len(c.handlers)) {
c.handlers[c.index](c)
c.index++
}
}
复制代码
c.handlersc.index即当前索引位置对应的handler的调用
type HandlersChain []HandlerFunc
type HandlerFunc func(*Context)
复制代码
因为处理逻辑是放在了c.Next前面,因此中间件的处理顺序是先入后出。中间件自己应该互相独立。但若是由于特殊缘由,有先后依赖,就要注意这点。
r.GET("/getb", GetDataB) 实现以下
// GET is a shortcut for router.Handle("GET", path, handle).
func (group *RouterGroup) GET(relativePath string,
handlers ...HandlerFunc) IRoutes {
return group.handle("GET", 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 (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)
if root == nil {
root = new(node)
root.fullPath = "/"
engine.trees = append(engine.trees,
methodTree{method: method, root: root})
}
root.addRoute(path, handlers)
}
复制代码
能够看到,路由映射是加入了一颗树中。这里使用的是radix树,Radix树,即基数树,也称压缩前缀树,是一种提供key-value存储查找的数据结构。与Trie不一样的是,它对Trie树进行了空间优化,只有一个子节点的中间节点将被压缩。一样的,Radix树的插入、查询、删除操做的时间复杂度都为O(k)。存储原理示意图以下:
看下r.Run的实现
func (engine *Engine) Run(addr ...string) (err error) {
defer func() { debugPrintError(err) }()
address := resolveAddress(addr)
debugPrint("Listening and serving HTTP on %s\n", address)
err = http.ListenAndServe(address, engine)
return
}
复制代码
能够看到,实际调用的,仍是http.ListenAndServe这个方法,engine做为handler参数传入。http.ListenAndServe的原理,在前文httpServer有过阐述,这里不作过多分析。
gin有以下特色:
gin对外接口和代码实现都很是优秀,不管是项目使用,仍是源码学习,都值得推荐。