Hello Gin
中文文档
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| package main
import ( "github.com/gin-gonic/gin" )
func main() { r := gin.Default() r.GET("/hello", func(c *gin.Context) { c.JSON(200, gin.H{ "message": "Hello world!", }) }) r.Run() }
|
先剖析一波:
1 2 3 4 5 6 7 8
| r.GET("/hello", func(c *gin.Context){ c.JSON(200, gin.H{ "message": "Hello world!", }) })
|
Restful Api
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| func main() { r := gin.Default() r.GET("/book", func(c *gin.Context) { c.JSON(200, gin.H{ "message": "GET", }) })
r.POST("/book", func(c *gin.Context) { c.JSON(200, gin.H{ "message": "POST", }) })
r.PUT("/book", func(c *gin.Context) { c.JSON(200, gin.H{ "message": "PUT", }) })
r.DELETE("/book", func(c *gin.Context) { c.JSON(200, gin.H{ "message": "DELETE", }) }) }
|
JSON渲染
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| func main() { r := gin.Default() r.GET("/someJSON2", func(c *gin.Context) { data:=map[string] interface{}{ "name":"小王子", } c.JSON(http.StatusOK, data) }) r.GET("/someJSON", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"message": "Hello world!"}) }) r.GET("/moreJSON", func(c *gin.Context) { var msg struct { Name string `json:"user"` Message string Age int } msg.Name = "小王子" msg.Message = "Hello world!" msg.Age = 18 c.JSON(http.StatusOK, msg) }) r.Run(":8080") }
|
获取参数
获取querystring参数
querystring
指的是URL中?
后面携带的参数,例如:/user/search?username=小王子&address=沙河
。 获取请求的querystring参数的方法如下:
hl:DefaultQuery1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| func main() { r := gin.Default() r.GET("/user/search", func(c *gin.Context) { username := c.DefaultQuery("username", "小王子") address := c.Query("address") c.JSON(http.StatusOK, gin.H{ "message": "ok", "username": username, "address": address, }) }) r.Run() }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| func main() { r := gin.Default() r.POST("/user/search", func(c *gin.Context) { username := c.PostForm("username") address := c.PostForm("address") c.JSON(http.StatusOK, gin.H{ "message": "ok", "username": username, "address": address, }) }) r.Run(":8080") }
|
获取JSON参数
1 2 3 4 5 6 7 8 9 10 11
| r.POST("/json", func(c *gin.Context) { b, _ := c.GetRawData() var m map[string]interface{} _ = json.Unmarshal(b, &m)
c.JSON(http.StatusOK, m) })
|
获取path参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| func main() { r := gin.Default() r.GET("/user/search/:username/:address", func(c *gin.Context) { username := c.Param("username") address := c.Param("address") c.JSON(http.StatusOK, gin.H{ "message": "ok", "username": username, "address": address, }) })
r.Run(":8080") }
|
参数绑定(最便捷)
为了能够更方便的获取请求相关参数,提高开发效率,我们可以基于请求的Content-Type
识别请求数据类型并利用反射机制自动提取请求中QueryString
、form表单
、JSON
、XML
等参数到结构体中。 下面的示例代码演示了.ShouldBind()
强大的功能,它能够基于请求自动提取JSON
、form表单
和QueryString
类型的数据,并把值绑定到指定的结构体对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
| type Login struct { User string `form:"user" json:"user" binding:"required"` Password string `form:"password" json:"password" binding:"required"` }
func main() { router := gin.Default()
router.POST("/loginJSON", func(c *gin.Context) { var login Login
if err := c.ShouldBind(&login); err == nil { fmt.Printf("login info:%#v\n", login) c.JSON(http.StatusOK, gin.H{ "user": login.User, "password": login.Password, }) } else { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) } })
router.POST("/loginForm", func(c *gin.Context) { var login Login if err := c.ShouldBind(&login); err == nil { c.JSON(http.StatusOK, gin.H{ "user": login.User, "password": login.Password, }) } else { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) } })
router.GET("/loginForm", func(c *gin.Context) { var login Login if err := c.ShouldBind(&login); err == nil { c.JSON(http.StatusOK, gin.H{ "user": login.User, "password": login.Password, }) } else { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) } })
router.Run(":8080") }
|
ShouldBind
会按照下面的顺序解析请求中的数据完成绑定:
- 如果是
GET
请求,只使用 Form
绑定引擎(query
)。
- 如果是
POST
请求,首先检查 content-type
是否为 JSON
或 XML
,然后再使用 Form
(form-data
)。
文件上传
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| func main() { router := gin.Default() router.POST("/upload", func(c *gin.Context) { file, err := c.FormFile("f1") if err != nil { c.JSON(http.StatusInternalServerError, gin.H{ "message": err.Error(), }) return }
log.Println(file.Filename) dst := fmt.Sprintf("C:/tmp/%s", file.Filename) c.SaveUploadedFile(file, dst) c.JSON(http.StatusOK, gin.H{ "message": fmt.Sprintf("'%s' uploaded!", file.Filename), }) }) router.Run() }
|
当多文件上传时,用for
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| func main() { router := gin.Default() router.POST("/upload", func(c *gin.Context) { form, _ := c.MultipartForm() files := form.File["file"]
for index, file := range files { log.Println(file.Filename) dst := fmt.Sprintf("C:/tmp/%s_%d", file.Filename, index) c.SaveUploadedFile(file, dst) } c.JSON(http.StatusOK, gin.H{ "message": fmt.Sprintf("%d files uploaded!", len(files)), }) }) router.Run() }
|
路由
重定向
http重定向(浏览器地址变化)
1 2 3 4
| r.GET("/test", func(c *gin.Context) { c.Redirect(http.StatusMovedPermanently, "http://www.sogo.com/") })
|
路由重定向(浏览器地址不变化)
1 2 3 4 5 6 7 8 9 10
| r.GET("/test", func(c *gin.Context) { c.Request.URL.Path = "/test2" r.HandleContext(c) }) r.GET("/test2", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"hello": "world"}) })
|
接收任意方法 Any 与错误的路由
1 2 3 4 5 6 7 8
| r.Any("/user",func(c *gin.Context){ switch c.Request.Method case "GET" C.JSON(http.StatusoK,gin.H{"method":"GET"}) case http.MethodPost: C.JSONChttp.StatusoK,gin.H{"method":"POST"})
|
NoRoute表示没有匹配到走这里
1 2 3 4
| r.NoRoute(func(c *gin.Context) { c.HTML(http.StatusNotFound, "views/404.html", nil) })
|
路由组 BaseUrl
为了区分,加一些公共前缀,如/user/account
,/user/logout
的user
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| func main() { r := gin.Default() userGroup := r.Group("/user") { userGroup.GET("/index", func(c *gin.Context) {...}) userGroup.GET("/login", func(c *gin.Context) {...}) userGroup.POST("/login", func(c *gin.Context) {...})
} shopGroup := r.Group("/shop") { shopGroup.GET("/index", func(c *gin.Context) {...}) shopGroup.GET("/cart", func(c *gin.Context) {...}) shopGroup.POST("/checkout", func(c *gin.Context) {...}) } r.Run() }
|
路由组也是支持嵌套的,例如:
1 2 3 4 5 6 7 8 9 10 11
| shopGroup := r.Group("/shop") { shopGroup.GET("/index", func(c *gin.Context) {...}) shopGroup.GET("/cart", func(c *gin.Context) {...}) shopGroup.POST("/checkout", func(c *gin.Context) {...}) xx := shopGroup.Group("xx") xx.GET("/oo", func(c *gin.Context) {...}) }
|
Gin 中间件
Gin框架允许开发者在处理请求的过程中,加入用户自己的钩子(Hook) 函数。这个钩子函数就叫中间件,中间件适合处理一些公共的业务逻辑,比如登录认证、权限校验、数据分页、记录日志、耗时统计等
定义中间件
Gin中的中间件必须是一个gin.HandlerFunc
类型。
例子:记录接口耗时的中间件
例如我们像下面的代码一样定义一个统计请求耗时的中间件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| func StatCost() gin.HandlerFunc { return func(c *gin.Context) { start := time.Now() c.Set("name", "小王子") c.Next() cost := time.Since(start) log.Println(cost) } }
|
Next就类比于深度递归DFS
注册中间件
如何注册,直接加载相应里面就行
1
| r.GET("/test2", handleFuc....)
|
handleFuc就是中间件,其实我们写的处理响应的函数就是一个中间件。可以传递多个handleFuc,会按照顺序执行多个handleFuc

全局注册
如果需要全局注册中间件,其会第一个执行
路由组注册
注册StatCost()
写法1:
1 2 3 4 5 6
| shopGroup := r.Group("/shop", StatCost()) { shopGroup.GET("/index", func(c *gin.Context) {...}) ... }
|
写法2:
1 2 3 4 5 6 7
| shopGroup := r.Group("/shop") shopGroup.Use(StatCost()) { shopGroup.GET("/index", func(c *gin.Context) {...}) ... }
|
获取中间件上下文值
在中间件中,我们可以用下面的函数传递值
1
| c.Set("name222", "小王子222")
|
用下面的函数接收中间件的传递
1 2 3 4 5 6 7 8
| r.GET("/test", func(c *gin.Context) { name := c.MustGet("name222").(string) log.Println(name) c.JSON(http.StatusOK, gin.H{ "message": "Hello world!", }) })
|
注意事项
默认中间件
gin.Default()
默认使用了Logger
和Recovery
中间件,其中:
Logger
中间件将日志写入gin.DefaultWriter
,即使配置了GIN_MODE=release
。
Recovery
中间件会recover任何panic
。如果有panic的话,会写入500响应码。
如果不想使用上面两个默认的中间件,可以使用gin.New()
新建一个没有任何默认中间件的路由。
gin中间件中使用goroutine
当在中间件或handler
中启动新的goroutine
时,不能使用原始的上下文(c *gin.Context)
,必须使用其只读副本(c.Copy()
)。
即,例如中间件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| func StatCost() gin.HandlerFunc { return func(c *gin.Context) { start := time.Now() c.Set("name", "小王子")
go func XX(c.Copy())
c.Next() cost := time.Since(start) log.Println(cost) } }
|
运行多个服务
我们可以在多个端口启动服务,例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75
| package main
import ( "log" "net/http" "time"
"github.com/gin-gonic/gin" "golang.org/x/sync/errgroup" )
var ( g errgroup.Group )
func router01() http.Handler { e := gin.New() e.Use(gin.Recovery()) e.GET("/", func(c *gin.Context) { c.JSON( http.StatusOK, gin.H{ "code": http.StatusOK, "error": "Welcome server 01", }, ) })
return e }
func router02() http.Handler { e := gin.New() e.Use(gin.Recovery()) e.GET("/", func(c *gin.Context) { c.JSON( http.StatusOK, gin.H{ "code": http.StatusOK, "error": "Welcome server 02", }, ) })
return e }
func main() { server01 := &http.Server{ Addr: ":8080", Handler: router01(), ReadTimeout: 5 * time.Second, WriteTimeout: 10 * time.Second, }
server02 := &http.Server{ Addr: ":8081", Handler: router02(), ReadTimeout: 5 * time.Second, WriteTimeout: 10 * time.Second, } g.Go(func() error { return server01.ListenAndServe() })
g.Go(func() error { return server02.ListenAndServe() })
if err := g.Wait(); err != nil { log.Fatal(err) } }
|