gin路由
基本路由
API参数(param参数)
可以通过Context的Param方法获取API参数
1 2 3 4 5 6 7 8 9 10 11 12
| func main() { r := gin.Default() r.GET("/hello/:name/*action", func(c *gin.Context) { name := c.Param("name") action := c.Param("action") c.String(http.StatusOK, name+" is "+action) }) r.Run(":9090") }
|
访问:
1
| http://127.0.0.1:9090/hello/lihua/sing
|
即lihua就是name,sing就是action
URL参数(query参数)
URL参数可以通过DefaultQuery()或者Query()方法获取,参数指的是URL中?
后面携带的参数,例如:/user/search?username=lihua&address=beijing
DefaultQuery():返回默认值
1 2 3 4 5 6 7 8 9 10 11
| func main() { r := gin.Default() r.GET("/welcom", func(c *gin.Context) { name := c.DefaultQuery("name", "jack") c.String(http.StatusOK, fmt.Sprintf("hello %s", name)) }) r.Run(":9090") }
|
访问:
1
| http://127.0.0.1:9090/welcom?name=lihua
|
表单传输为post请求,http常见的传输格式为4种:
- json
- x-www-form-urlencoded
- xml
- form-data
表单参数可以通过PostForm()方法获取,或者直接使用ShouldBindWith()方法绑定结构体,该方法默认解析的是x-www-form-urlencoded或form-data格式的数据。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| func Post() { r := gin.Default()
r.POST("/form", func(c *gin.Context) {
type1 := c.DefaultPostForm("type", "alert") username := c.PostForm("username") password := c.PostForm("password") hobbys := c.PostFormArray("hobby") c.String(http.StatusOK, fmt.Sprintf("type is %s, username is %s, password is %s, hobbys is %v", type1, username, password, hobbys)) }) r.Run(":9090") }
|
前端请求界面:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>登录</title> </head> <body> <form action="http://127.0.0.1:9090/form" method="post" enctype="application/x-www-form-urlencoded"> 用户名:<input type="text" name="username"> <br> 密  码:<input type="password" name="password"> 兴  趣: <input type="checkbox" value="run" name="hobby">跑步 <input type="checkbox" value="game" name="hobby">游戏 <input type="checkbox" value="money" name="hobby">金钱 <br> <input type="submit" value="登录"> </form> </body> </html>
|
上传文件
c.FormFile("file")
方法获取上传的文件,其中"file"
是前端表单中文件字段的名称
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.MaxMultipartMemory = 8 << 20
r.POST("/upload", func(c *gin.Context) { file, err := c.FormFile("file") if err != nil { c.String(http.StatusBadRequest, fmt.Sprintf("获取上传文件失败: %s", err.Error())) return }
dst := "uploads/" + file.Filename err = c.SaveUploadedFile(file, dst) if err != nil { c.String(http.StatusInternalServerError, fmt.Sprintf("保存上传文件失败: %s", err.Error())) return }
c.String(http.StatusOK, fmt.Sprintf("文件 '%s' 上传成功!", file.Filename)) })
r.Run(":9090") }
|
路由组(routes group)
路由组是位了管理一些相同的URL
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 rout() { r := gin.Default() v1 := r.Group("/user") { v1.GET("/login", login) v1.GET("submit", submit) } v2 := r.Group("/shop") { v2.POST("/login", login) v2.POST("submit", submit) } r.Run(":9090") }
func login(c *gin.Context) { name := c.DefaultQuery("name", "jack") c.String(200, fmt.Sprintf("hello %s\n", name)) }
func submit(c *gin.Context) { name := c.DefaultQuery("name", "lily") c.String(200, fmt.Sprintf("hello %s\n", name)) }
|
请求:
1 2
| http://127.0.0.1:9090/v1/login?name=xiaozhang http://127.0.0.1:9090/v1/submit?name=xiaozhang
|
路由原理
httproter会将所有路由规则构造一颗前缀树,树方便查询。
gin数据解析与绑定
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
| type Login struct { User string `form:"username" json:"user" uri:"user" xml:"user" binding:"required"` Password string `form:"password" json:"password" uri:"password" xml:"password" binding:"required"` }
func analysisData() { r := gin.Default() r.POST("/login_json", func(c *gin.Context) { var login Login err := c.ShouldBindJSON(&login) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } if login.User != "root" || login.Password != "123456" { c.JSON(http.StatusBadRequest, gin.H{"status": "304"}) return } c.JSON(http.StatusOK, gin.H{"status": "200"}) })
r.Run(":9090") }
|
windows下的请求方法(json中需要加反斜杠):
1
| curl http://127.0.0.1:9090/login_json -H 'content-type:application/json' -d "{\"user\":\"root\", \"password\":\"123456\"}" -X POST
|
表单数据解析和绑定
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
| type Login struct { User string `form:"username" json:"user" uri:"user" xml:"user" binding:"required"` Password string `form:"password" json:"password" uri:"password" xml:"password" binding:"required"` }
func analysisData() { r := gin.Default() r.POST("/form", func(c *gin.Context) { var login_form Login err := c.Bind(&login_form) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } if login_form.User != "root" || login_form.Password != "123456" { c.JSON(http.StatusBadRequest, gin.H{"status": "304"}) return } c.JSON(http.StatusOK, gin.H{"status": "200"}) })
r.Run(":9090") }
|
前端请求界面:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>登录</title> </head> <body> <form action="http://127.0.0.1:9090/form" method="post" enctype="application/x-www-form-urlencoded"> 用户名:<input type="text" name="username"> <br> 密  码:<input type="password" name="password"> 兴  趣: <br> <input type="submit" value="登录"> </form> </body> </html>
|
URI数据解析和绑定
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
| type Login struct { User string `form:"username" json:"user" uri:"user" xml:"user" binding:"required"` Password string `form:"password" json:"password" uri:"password" xml:"password" binding:"required"` }
func analysisURI() { r := gin.Default() r.GET("/:user/:password", func(c *gin.Context) { var login_uri Login err := c.ShouldBindUri(&login_uri) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } if login_uri.User != "root" || login_uri.Password != "123456" { c.JSON(http.StatusBadRequest, gin.H{"status": "304"}) return } c.JSON(http.StatusOK, gin.H{"status": "200"}) })
r.Run(":9090") }
|
请求方法:
1
| curl http://127.0.0.1:9090/root/123456
|
gin渲染
各种数据格式的响应
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
| func resp() { r := gin.Default()
r.GET("/someJSON", func(c *gin.Context) { c.JSON(200, gin.H{"message": "someJSON", "status": 200}) })
r.GET("/someStruct", func(c *gin.Context) { var msg struct { Name string `json:"name"` Message string `json:"message"` Number int } msg.Name = "root" msg.Message = "message" msg.Number = 123 c.JSON(200, msg) })
r.GET("/someXML", func(c *gin.Context) { c.XML(200, gin.H{"message": "abc"}) })
r.GET("/someYAML", func(c *gin.Context) { c.YAML(200, gin.H{"name": "zhangsan"}) })
r.GET("someProtoBuf", func(c *gin.Context) { reps := []int64{0, 1} label := "label" data := &protoexample.Text{ Label: &label, Reps: reps, } c.ProtoBuf(200, data) }) }
|
HTML模板渲染
gin支持加载HTML模板,然后根据模板参数进行配置并返回相应的数据,本质上就是字符串替换;LoadHTMLGlob()方法可以加载模板文件
1 2 3 4 5 6 7 8 9 10 11
| func HTML_render() { r := gin.Default() r.LoadHTMLGlob("templates/*") r.GET("/index", func(c *gin.Context) { c.HTML(200, "index.tmpl", gin.H{"title": "我的标题"}) }) r.Run() }
|
html的模板文件,index.tmpl
1 2 3 4 5
| <html> <h1> {{.title}} </h1> </html>
|
重定向
1 2 3 4 5 6 7 8
| func gin_edirect() { r := gin.Default() r.GET("/redirect", func(c *gin.Context) { c.Redirect(200, "https://www.runoob.com/") }) r.Run() }
|
同步异步
goroutine机制可以方便地实现异步处理,另外,在启动新的goroutine时,不应该使用原始上下文,必须使用它的只读副本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| func async_sync() { r := gin.Default() r.GET("async", func(c *gin.Context) { copyContext := c.Copy() go func() { time.Sleep(3 * time.Second) log.Println("异步执行:" + copyContext.Request.URL.Path) }() })
r.GET("sync", func(c *gin.Context) { time.Sleep(3 * time.Second) log.Println("异步执行:" + c.Request.URL.Path) }) r.Run() }
|
gin中间件
Gin框架允许开发者在处理请求的过程中,加入用户自己的钩子(Hook)函数。这个钩子函数就叫中间件,中间件适合处理一些公共的业务逻辑,比如登录认证、权限校验、数据分页、记录日志、耗时统计等。
- gin可以构建中间件,但它只对注册过的路由函数起作用
- 对于分组路由,嵌套使用中间件,可以限定中间件的作用范围
- 中间件分为全局中间件,单个路由中间件和群组中间件
- gin中间件必须是一个gin.HandlerFunc类型
全局中间件
1 2 3 4 5 6 7 8 9 10 11 12 13
| func InitRouter() { gin.SetMode(utils.AppNode) r := gin.New() r.Use(gin.Recovery()) r.Use(middleware.Logger())
auth := r.Group("api/v1") auth.Use(middleware.JwtToken()) { auth.PUT("user/:id", v1.EditUser) } }
|
Next()方法
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.New() mid1 := func(c * gin.Context){ fmt.Println("mid1 start") c.Next() fmt.Println("mid1 end") } mid2 := func(c * gin.Context){ fmt.Println("mid2 start") c.Next() fmt.Println("mid2 end") } mid3 := func(c * gin.Context){ fmt.Println("mid3 start") c.Next() fmt.Println("mid3 end") } router.Use(mid1,mid2,mid3) router.GET("/",func(c * gin.Context){ fmt.Println("process get request") c.JSON(http.StatusOK,"hello") }) router.Run()
|
上述代码中使用了3个中间件(mid1,mid2,mid3),加上最后的路由处理即返回hello部分,共4个handles。
如果注释掉3个中间件中的c.Next(),则执行情况如下:
1 2 3 4 5 6 7
| mid1 start mid1 end mid2 start mid2 end mid3 start mid3 end process get request
|

如果仅在mid1中间件中使用c.Next(),则执行流程如下:
1 2 3 4 5 6 7
| mid1 start mid2 start mid2 end mid3 start mid3 end process get request mid1 end
|

总结:
最后的get路由处理函数可以理解为最后的中间件,在不是调用c.Abort()的情况下,所有的中间件都会被执行到。当某个中间件调用了c.Next(),则整个过程会产生嵌套关系。如果某个中间件调用了c.Abort(),则此中间件结束后会直接返回,后面的中间件均不会调用。
局部中间件
1 2 3 4 5 6 7 8 9 10
| func InitRouter() { gin.SetMode(utils.AppNode) r := gin.New() r.Use(gin.Recovery()) auth := r.Group("api/v1") { auth.PUT("user/:id", middleware.Logger(), v1.EditUser) } }
|
优雅关闭
优雅关机就是服务端关机命令发出后不是立即关机,而是等待当前还在处理的请求全部处理完毕后再退出程序,是一种对客户端友好的关机方式。而执行Ctrl+C
关闭服务端时,会强制结束进程导致正在访问的请求出现问题。
Go 1.8版本之后, http.Server 内置的 Shutdown() 方法就支持优雅地关机,具体示例如下:
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
| package main
import ( "context" "log" "net/http" "os" "os/signal" "syscall" "time"
"github.com/gin-gonic/gin" )
func main() { router := gin.Default() router.GET("/", func(c *gin.Context) { time.Sleep(5 * time.Second) c.String(http.StatusOK, "Welcome Gin Server") })
srv := &http.Server{ Addr: ":8080", Handler: router, }
go func() { if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { log.Fatalf("listen: %s\n", err) } }()
quit := make(chan os.Signal, 1) signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) <-quit log.Println("Shutdown Server ...") ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() if err := srv.Shutdown(ctx); err != nil { log.Fatal("Server Shutdown: ", err) }
log.Println("Server exiting") }
|
使用gin本身代码实现优化关闭
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| func main() { r := gin.Default()
r.GET("/ping", func(context *gin.Context) { time.Sleep(5 * time.Second) context.JSON(http.StatusOK, gin.H{ "msg": "/pong", }) }) go func() { _ = r.Run(":8081") }()
quit := make(chan os.Signal) signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) <-quit fmt.Println("收到中断信号;优雅的退出...") fmt.Println("退出完成")
|