gin是一个轻量化的go web框架,支持中间件、路由组等特性,与node的koa2有几分神似,上手简单,爱不释手。

Hello world

创建helloworld.go,

  1. package main
  2. import "github.com/gin-gonic/gin"
  3. func main() {
  4. r := gin.Default()
  5. r.GET("/ping", func(c *gin.Context) {
  6. c.JSON(200, gin.H{
  7. "message": "pong",
  8. })
  9. })
  10. r.Run() // 监听并在 0.0.0.0:8080 上启动服务
  11. }

然后执行go run helloworld.go,就可以在浏览器访问127.0.0.1:8080/ping,得到如下相应:

  1. {
  2. "message": "pong"
  3. }

深入Hello World

虽然只有短短的13行代码,但里面还是有点门道的。
首先看main函数的第一行

  1. r:=gin.Default()

go函数的调用

这里就是简单的调用一个gin的Default函数。
gin就是指代gin这个包,go中函数的调用就是包名+函数名。
注意Default()函数,它是以大写字母开头。
在go中规定:

  • 包内小写字母开头的函数是内部函数,只能在该包内调用,类似java中的private修饰的函数
  • 包内大写字母开头的函数,包外也能调用,类似java中的public修饰的函数

gin的默认中间件

首先看一下gin.Default()做了什么吧

  1. gin.Default()源码
  2. // Default returns an Engine instance with the Logger and Recovery middleware already attached.
  3. func Default() *Engine {
  4. // 输出些警告信息,忽略
  5. debugPrintWARNINGDefault()
  6. // 调用New()函数,生成一个gin框架的实例:*Engine
  7. // 关于gin框架实例细节,就先挖个坑,日后再聊。
  8. engine := New()
  9. // 使用Logger,Recovery两个中间件
  10. engine.Use(Logger(), Recovery())
  11. // 返回gin框架实例
  12. return engine
  13. }

实际上,gin.Default()就是实例化一个gin实例,然后使用了默认的Logger和Recovery中间件。

Logger中间件

Logger中间件:记录每次请求

  1. [GIN] 2019/08/03 - 17:29:44 | 200 | 13.293µs | 127.0.0.1 | GET /ping

Recory中间件

Recovery中间件:如果程序遇到错误(panic),就会恢复程序(Recovery),避免面程序中断,并且记录错误的栈,方便查找问题。

  1. // 请求有异常的接口
  2. [GIN] 2019/08/03 - 17:29:40 | 500 | 2.405564ms | 127.0.0.1 | GET /panic
  3. // Recovery中间件起作用,恢复程序,并记录错误栈
  4. 2019/08/03 17:29:40 [Recovery] 2019/08/03 - 17:29:40 panic recovered:
  5. GET /panic HTTP/1.1
  6. Host: 127.0.0.1:8087
  7. Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
  8. Accept-Encoding: gzip, deflate, br
  9. Accept-Language: en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7
  10. Connection: keep-alive
  11. Upgrade-Insecure-Requests: 1
  12. User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36
  13. test
  14. /code/go/workspace/src/go-gin/main.go:14 (0x158d018)
  15. main.func2: panic("test")
  16. /code/go/workspace/pkg/mod/github.com/gin-gonic/gin@v1.4.0/context.go:124 (0x1577d09)
  17. (*Context).Next: c.handlers[c.index](c)
  18. /code/go/workspace/pkg/mod/github.com/gin-gonic/gin@v1.4.0/recovery.go:83 (0x158afd9)
  19. RecoveryWithWriter.func1: c.Next()
  20. /code/go/workspace/pkg/mod/github.com/gin-gonic/gin@v1.4.0/context.go:124 (0x1577d09)
  21. (*Context).Next: c.handlers[c.index](c)
  22. /code/go/workspace/pkg/mod/github.com/gin-gonic/gin@v1.4.0/logger.go:240 (0x158a080)
  23. LoggerWithConfig.func1: c.Next()
  24. /code/go/workspace/pkg/mod/github.com/gin-gonic/gin@v1.4.0/context.go:124 (0x1577d09)
  25. (*Context).Next: c.handlers[c.index](c)
  26. /code/go/workspace/pkg/mod/github.com/gin-gonic/gin@v1.4.0/gin.go:389 (0x1581521)
  27. (*Engine).handleHTTPRequest: c.Next()
  28. /code/go/workspace/pkg/mod/github.com/gin-gonic/gin@v1.4.0/gin.go:351 (0x1580d53)
  29. (*Engine).ServeHTTP: engine.handleHTTPRequest(c)
  30. /usr/local/Cellar/go/1.12.7/libexec/src/net/http/server.go:2774 (0x12b4097)
  31. serverHandler.ServeHTTP: handler.ServeHTTP(rw, req)
  32. /usr/local/Cellar/go/1.12.7/libexec/src/net/http/server.go:1878 (0x12afc80)
  33. (*conn).serve: serverHandler{c.server}.ServeHTTP(w, w.req)
  34. /usr/local/Cellar/go/1.12.7/libexec/src/runtime/asm_amd64.s:1337 (0x1059c50)
  35. goexit: BYTE $0x90 // NOP
  36. //程序还能继续访问
  37. [GIN] 2019/08/03 - 17:29:44 | 200 | 13.293µs | 127.0.0.1 | GET /ping

不使用默认中间件

默认的Logger中间件,常常不能满足需求,需要自定义Log中间件,(Recory中间件还是挺好用的)。
此时就可以使用gin.New()来创建一个没有默认中间件的gin实例。

  1. r := gin.New()

自定义中间件

现在简单说说自定义中间件,先看官方的自定义中间件的🌰:

  1. func Logger() gin.HandlerFunc {
  2. return func(c *gin.Context) {
  3. t := time.Now()
  4. // 设置 example 变量
  5. c.Set("example", "12345")
  6. // 请求前
  7. c.Next()
  8. // 请求后
  9. latency := time.Since(t)
  10. log.Print(latency)
  11. // 获取发送的 status
  12. status := c.Writer.Status()
  13. log.Println(status)
  14. }
  15. }
  16. func main() {
  17. r := gin.New()
  18. r.Use(Logger())
  19. r.GET("/test", func(c *gin.Context) {
  20. example := c.MustGet("example").(string)
  21. // 打印:"12345"
  22. log.Println(example)
  23. })
  24. // 监听并在 0.0.0.0:8080 上启动服务
  25. r.Run(":8080")
  26. }

我们可以看到gin的中间件与Koa2的中间件设计的多么相似。

  • 首先,中间件函数仅仅只是返回一个gin.HandlerFunc的函数
  • 这个函数接受一个指向gin.Context的指针,即接受gin的上下文
  • 上下文是gin最重要的部分。它允许我们在中间件之间传递变量,例如,管理流、验证请求的JSON并呈现JSON响应。类似Koa2中的ctx。
  • 函数体内部可以在处理请求前做一些事情,上面的🌰中,记录了处理请求前的时间,
  • 使用c.Next()交出控制权,不继续执行下面的函数体,
  • 处理完请求后,又会进入该函数体,即从c.Next()之后继续执行,上🌰中,打印请求耗时时间,打印返回的状态。

洋葱圈模型

gin的中间件可以用Koa2中的洋葱圈模型来解释
洋葱圈模型.png

  1. g.Use(gin.Recovery())
  2. g.Use(middleware.NoCache)
  3. g.Use(middleware.Options)
  4. g.Use(middleware.Secure)

上面的代码执行如下
Recovery->NoCache->Options->Secure->逻辑处理->Options->NoCache->Recovery
所以g.Use的顺序也得注意,不一样的顺序,结果可能就不一样。


路由

  1. r.GET("/ping", func(c *gin.Context) {
  2. c.JSON(200, gin.H{
  3. "message": "pong",
  4. })
  5. })

使用r.GET为gin创建一个路由。
r.GET接受2个参数,
第一个是路由的路径,本例中是“/ping”,即访问http://127.0.0.1:8080/ping即可访问到该路由。
第二个是路由的处理函数,本例中是返回一个json。


JSON

  1. c.JSON(200, gin.H{
  2. "message": "pong",
  3. })

现在的开发基本上都是使用前后端分离的架构,而前后端之间传输数据基本上都是JSON格式。所以gin为我们提供了一个方法:c.JSON,方便我们返回数据。
c.JSON的源码:

  1. // JSON serializes the given struct as JSON into the response body.
  2. // It also sets the Content-Type as "application/json".
  3. func (c *Context) JSON(code int, obj interface{}) {
  4. c.Render(code, render.JSON{Data: obj})
  5. }

该方法接受两个参数:一个int类型的code,一个interface。code是返回的http的状态码,interface就是要返回的json字符串。

  1. gin.H{
  2. "message": "pong",
  3. }

gin.H{}就是一个map类型,是map[string]interface{}的简写,方便我们书写map类型的返回值

跑起来

  1. r.Run() // 监听并在 0.0.0.0:8080 上启动服务

使用gin的实例的Run方法让服务跑起来。
让我们看看Run的源码

  1. // Run attaches the router to a http.Server and starts listening and serving HTTP requests.
  2. // It is a shortcut for http.ListenAndServe(addr, router)
  3. // Note: this method will block the calling goroutine indefinitely unless an error happens.
  4. func (engine *Engine) Run(addr ...string) (err error) {
  5. defer func() { debugPrintError(err) }()
  6. // 默认为8080 如果addr有值,就改为addr的端口
  7. address := resolveAddress(addr)
  8. debugPrint("Listening and serving HTTP on %s\n", address)
  9. err = http.ListenAndServe(address, engine)
  10. return
  11. }

Run方法内部也是调用go原生库http的ListenAndServe,所以也可以不用r.Run(),而是直接调用http.ListenAndServe(address, engine)也是一样的。

如果需要https,则可以调用r.RunTLS或者http.ListenAndServeTLS(addr, certFile, keyFile, engine)