golang框架-web框架之gin

mob604756e80bb7 · · 1809 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

gin介绍

gin是一个 Web应用框架,拥有良好的性能和简单明了的接口。同时支持中间件,类型绑定等实用功能。

为什么要用gin

在实际开发中,很少会直接实用http.Server。而自己搭建框架有一定成本,同时没有经过系统的校验,容易出现问题。而现有的框架中,gin拥有良好的性能,更重要的是接口清晰明了,接入成本极低。同时,其支持的功能也是多种多样,如中间件,类型绑定,日志规范。

gin 性能

以下是从官网拿到的性能对比指标表

  • (1): Total Repetitions achieved in constant time, higher means more confident result
  • (2): Single Repetition Duration (ns/op), lower is better
  • (3): Heap Memory (B/op), lower is better
  • (4): Average Allocations per Repetition (allocs/op), lower is better
Benchmark name(1)(2)(3)(4)
BenchmarkGin_GithubAll300004837500
BenchmarkAce_GithubAll1000013405913792167
BenchmarkBear_GithubAll500053444586448943
BenchmarkBeego_GithubAll300059244474705812
BenchmarkBone_GithubAll20069573086987848453

接入栗子

简单示例

接受/ping路径的Get请求,并返回message:"pong"

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 } 复制代码

支持所有的http协议

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, }) }) 复制代码

gin特性

路由组

支持以组为单位的路由,下面栗子就是以/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中来。

// 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.

// 定义一个 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原理分析

gin 可以说全是在handler上做文章。下面我们就以这三句话,一探gin。

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)。存储原理示意图如下:


757e897ef9003bff59cc7fcbce76075a.png


启动服务

看下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有如下特点:

  • 接入成本非常低,作为一个组件,这是最重要的一点
  • 拥有强大的中间件功能,用户可以自主定制需要的功能
  • 由于使用了radix树,路由的性能很高。
  • 数据绑定,让用户可以非常方便地从请求中获取想要的结构体。

gin对外接口和代码实现都非常优秀,无论是项目使用,还是源码学习,都值得推荐。



有疑问加站长微信联系(非本文作者)

本文来自:51CTO博客

感谢作者:mob604756e80bb7

查看原文:golang框架-web框架之gin

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

1809 次点击  
加入收藏 微博
1 回复  |  直到 2021-04-05 21:13:49
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传