gin是一个轻量化的go web框架,支持中间件、路由组等特性,与node的koa2有几分神似,上手简单,爱不释手。
Hello world
创建helloworld.go,
package main
import "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.1
Host: 127.0.0.1:8087
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7
Connection: keep-alive
Upgrade-Insecure-Requests: 1
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
test
/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)
// 获取发送的 status
status := 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)