gin是一个轻量化的go web框架,支持中间件、路由组等特性,与node的koa2有几分神似,上手简单,爱不释手。
Hello world
创建helloworld.go,
package mainimport "github.com/gin-gonic/gin"func main() {r := gin.Default()r.GET("/ping", func(c *gin.Context) {c.JSON(200, gin.H{"message": "pong",})})r.Run() // 监听并在 0.0.0.0:8080 上启动服务}
然后执行go run helloworld.go,就可以在浏览器访问127.0.0.1:8080/ping,得到如下相应:
{"message": "pong"}
深入Hello World
虽然只有短短的13行代码,但里面还是有点门道的。
首先看main函数的第一行
r:=gin.Default()
go函数的调用
这里就是简单的调用一个gin的Default函数。
gin就是指代gin这个包,go中函数的调用就是包名+函数名。
注意Default()函数,它是以大写字母开头。
在go中规定:
- 包内小写字母开头的函数是内部函数,只能在该包内调用,类似java中的private修饰的函数
- 包内大写字母开头的函数,包外也能调用,类似java中的public修饰的函数
gin的默认中间件
首先看一下gin.Default()做了什么吧
gin.Default()源码// Default returns an Engine instance with the Logger and Recovery middleware already attached.func Default() *Engine {// 输出些警告信息,忽略debugPrintWARNINGDefault()// 调用New()函数,生成一个gin框架的实例:*Engine// 关于gin框架实例细节,就先挖个坑,日后再聊。engine := New()// 使用Logger,Recovery两个中间件engine.Use(Logger(), Recovery())// 返回gin框架实例return engine}
实际上,gin.Default()就是实例化一个gin实例,然后使用了默认的Logger和Recovery中间件。
Logger中间件
Logger中间件:记录每次请求
[GIN] 2019/08/03 - 17:29:44 | 200 | 13.293µs | 127.0.0.1 | GET /ping
Recory中间件
Recovery中间件:如果程序遇到错误(panic),就会恢复程序(Recovery),避免面程序中断,并且记录错误的栈,方便查找问题。
// 请求有异常的接口[GIN] 2019/08/03 - 17:29:40 | 500 | 2.405564ms | 127.0.0.1 | GET /panic// Recovery中间件起作用,恢复程序,并记录错误栈2019/08/03 17:29:40 [Recovery] 2019/08/03 - 17:29:40 panic recovered:GET /panic HTTP/1.1Host: 127.0.0.1:8087Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3Accept-Encoding: gzip, deflate, brAccept-Language: en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7Connection: keep-aliveUpgrade-Insecure-Requests: 1User-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.36test/code/go/workspace/src/go-gin/main.go:14 (0x158d018)main.func2: panic("test")/code/go/workspace/pkg/mod/github.com/gin-gonic/gin@v1.4.0/context.go:124 (0x1577d09)(*Context).Next: c.handlers[c.index](c)/code/go/workspace/pkg/mod/github.com/gin-gonic/gin@v1.4.0/recovery.go:83 (0x158afd9)RecoveryWithWriter.func1: c.Next()/code/go/workspace/pkg/mod/github.com/gin-gonic/gin@v1.4.0/context.go:124 (0x1577d09)(*Context).Next: c.handlers[c.index](c)/code/go/workspace/pkg/mod/github.com/gin-gonic/gin@v1.4.0/logger.go:240 (0x158a080)LoggerWithConfig.func1: c.Next()/code/go/workspace/pkg/mod/github.com/gin-gonic/gin@v1.4.0/context.go:124 (0x1577d09)(*Context).Next: c.handlers[c.index](c)/code/go/workspace/pkg/mod/github.com/gin-gonic/gin@v1.4.0/gin.go:389 (0x1581521)(*Engine).handleHTTPRequest: c.Next()/code/go/workspace/pkg/mod/github.com/gin-gonic/gin@v1.4.0/gin.go:351 (0x1580d53)(*Engine).ServeHTTP: engine.handleHTTPRequest(c)/usr/local/Cellar/go/1.12.7/libexec/src/net/http/server.go:2774 (0x12b4097)serverHandler.ServeHTTP: handler.ServeHTTP(rw, req)/usr/local/Cellar/go/1.12.7/libexec/src/net/http/server.go:1878 (0x12afc80)(*conn).serve: serverHandler{c.server}.ServeHTTP(w, w.req)/usr/local/Cellar/go/1.12.7/libexec/src/runtime/asm_amd64.s:1337 (0x1059c50)goexit: BYTE $0x90 // NOP//程序还能继续访问[GIN] 2019/08/03 - 17:29:44 | 200 | 13.293µs | 127.0.0.1 | GET /ping
不使用默认中间件
默认的Logger中间件,常常不能满足需求,需要自定义Log中间件,(Recory中间件还是挺好用的)。
此时就可以使用gin.New()来创建一个没有默认中间件的gin实例。
r := gin.New()
自定义中间件
现在简单说说自定义中间件,先看官方的自定义中间件的🌰:
func Logger() gin.HandlerFunc {return func(c *gin.Context) {t := time.Now()// 设置 example 变量c.Set("example", "12345")// 请求前c.Next()// 请求后latency := time.Since(t)log.Print(latency)// 获取发送的 statusstatus := c.Writer.Status()log.Println(status)}}func main() {r := gin.New()r.Use(Logger())r.GET("/test", func(c *gin.Context) {example := c.MustGet("example").(string)// 打印:"12345"log.Println(example)})// 监听并在 0.0.0.0:8080 上启动服务r.Run(":8080")}
我们可以看到gin的中间件与Koa2的中间件设计的多么相似。
- 首先,中间件函数仅仅只是返回一个gin.HandlerFunc的函数
- 这个函数接受一个指向gin.Context的指针,即接受gin的上下文
- 上下文是gin最重要的部分。它允许我们在中间件之间传递变量,例如,管理流、验证请求的JSON并呈现JSON响应。类似Koa2中的ctx。
- 函数体内部可以在处理请求前做一些事情,上面的🌰中,记录了处理请求前的时间,
- 使用c.Next()交出控制权,不继续执行下面的函数体,
- 处理完请求后,又会进入该函数体,即从c.Next()之后继续执行,上🌰中,打印请求耗时时间,打印返回的状态。
洋葱圈模型
gin的中间件可以用Koa2中的洋葱圈模型来解释
g.Use(gin.Recovery())g.Use(middleware.NoCache)g.Use(middleware.Options)g.Use(middleware.Secure)
上面的代码执行如下
Recovery->NoCache->Options->Secure->逻辑处理->Options->NoCache->Recovery
所以g.Use的顺序也得注意,不一样的顺序,结果可能就不一样。
路由
r.GET("/ping", func(c *gin.Context) {c.JSON(200, gin.H{"message": "pong",})})
使用r.GET为gin创建一个路由。
r.GET接受2个参数,
第一个是路由的路径,本例中是“/ping”,即访问http://127.0.0.1:8080/ping即可访问到该路由。
第二个是路由的处理函数,本例中是返回一个json。
JSON
c.JSON(200, gin.H{"message": "pong",})
现在的开发基本上都是使用前后端分离的架构,而前后端之间传输数据基本上都是JSON格式。所以gin为我们提供了一个方法:c.JSON,方便我们返回数据。
c.JSON的源码:
// JSON serializes the given struct as JSON into the response body.// It also sets the Content-Type as "application/json".func (c *Context) JSON(code int, obj interface{}) {c.Render(code, render.JSON{Data: obj})}
该方法接受两个参数:一个int类型的code,一个interface。code是返回的http的状态码,interface就是要返回的json字符串。
gin.H{"message": "pong",}
gin.H{}就是一个map类型,是map[string]interface{}的简写,方便我们书写map类型的返回值
跑起来
r.Run() // 监听并在 0.0.0.0:8080 上启动服务
使用gin的实例的Run方法让服务跑起来。
让我们看看Run的源码
// 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) {defer func() { debugPrintError(err) }()// 默认为8080 如果addr有值,就改为addr的端口address := resolveAddress(addr)debugPrint("Listening and serving HTTP on %s\n", address)err = http.ListenAndServe(address, engine)return}
Run方法内部也是调用go原生库http的ListenAndServe,所以也可以不用r.Run(),而是直接调用http.ListenAndServe(address, engine)也是一样的。
如果需要https,则可以调用r.RunTLS或者http.ListenAndServeTLS(addr, certFile, keyFile, engine)
