Gin渲染 HTML渲染 首先编写模板文件index.tmpl
:
1 2 3 4 5 6 7 8 9 10 11 12 13 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <meta name ="viewport" content ="width=device-width, initial-scale=1.0" > <meta http-equiv ="X-UA-Compatible" content ="ie=edge" > <title > posts/index</title > </head > <body > {{.title}} </body > </html >
Gin中使用LoadHTMLGlob()
或者LoadHTMLFiles()
进行模板渲染:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package mainimport ( "github.com/gin-gonic/gin" "net/http" ) func main () { r := gin.Default() r.LoadHTMLFiles("templates/index.tmpl" ) r.GET("/index" , func (c *gin.Context) { c.HTML(http.StatusOK, "index.tmpl" , gin.H{ "title" : "Dawn ZH" , }) }) r.Run(":9090" ) }
使用不同目录下的名称相同模板: 可以指定模板的父文件夹 和文件名 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 func main () { router := gin.Default() router.LoadHTMLGlob("templates/**/*" ) router.GET("/posts/index" , func (c *gin.Context) { c.HTML(http.StatusOK, "posts/index.tmpl" , gin.H{ "title" : "Posts" , }) }) router.GET("/users/index" , func (c *gin.Context) { c.HTML(http.StatusOK, "users/index.tmpl" , gin.H{ "title" : "Users" , }) }) router.Run(":8080" ) }
自定义模板函数 定义一个safe
函数,不转义HTML标签:
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 package mainimport ( "github.com/gin-gonic/gin" "html/template" "net/http" ) func main () { r := gin.Default() r.SetFuncMap(template.FuncMap{ "safe" : func (str string ) template .HTML { return template.HTML(str) }, }) r.LoadHTMLFiles("index.tmpl" ) r.GET("/index" , func (c *gin.Context) { c.HTML(http.StatusOK, "index.tmpl" , gin.H{ "title" : "Hello, DawnZH" , "href" : "<a href='http://dawnzzz.github.io'>Dawn的博客地址</a>" , }) }) r.Run(":9090" ) }
在模板中使用自定义函数safe
:
1 2 3 4 5 6 7 8 9 10 11 12 13 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <meta name ="viewport" content ="width=device-width, initial-scale=1.0" > <meta http-equiv ="X-UA-Compatible" content ="ie=edge" > <title > posts/index</title > </head > <body > {{.title}} {{.href | safe}} </body > </html >
也可以自定义 HTML 模板渲染器:
1 2 3 4 5 6 7 8 import "html/template" func main () { router := gin.Default() html := template.Must(template.ParseFiles("file1" , "file2" )) router.SetHTMLTemplate(html) router.Run(":8080" ) }
自定义分隔符:
1 2 3 r := gin.Default() r.Delims("{[{" , "}]}" ) r.LoadHTMLGlob("/path/to/templates" )
静态文件处理 当渲染的HTML文件中使用了静态文件时,只需要在模板解析前调用Static
方法即可:
1 2 3 4 5 6 r := gin.Default() r.Static("/xxx" , "./static" ) r.LoadHTMLFiles("index.tmpl" )
在模板中引用静态文件:
1 <link rel ="stylesheet" href ="xxx/index.css" >
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 33 34 35 36 37 package mainimport ( "github.com/gin-gonic/gin" "net/http" ) func main () { r := gin.Default() r.GET("/json1" , func (c *gin.Context) { data := gin.H{ "name" : "Dawn" , "content" : "Hello world" , "age" : 18 , } c.JSON(http.StatusOK, data) }) r.GET("/json2" , func (c *gin.Context) { var data struct { Name string Content string Age int } data.Name = "DawnZH" data.Content = "Hello Dawn" data.Age = 22 c.JSON(http.StatusOK, data) }) r.Run(":9090" ) }
通常,JSON 使用 unicode 替换特殊 HTML 字符,例如 < 变为 \u003c
。如果要按字面对这些字符进行编码 ,则可以使用 PureJSON。
使用 SecureJSON 防止 json 劫持。 如果给定的结构是数组值,则默认预置 "while(1),"
到响应体。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 func main () { r := gin.Default() r.GET("/someJSON" , func (c *gin.Context) { names := []string {"lena" , "austin" , "foo" } c.SecureJSON(http.StatusOK, names) }) r.Run(":8080" ) }
获取参数 获取querystring querystring指URL中?
后面携带的参数,获取querystring的方法,可以使用Query
方法、DefaultQuery
和GetQuery
方法。
DefaultQuery
在没有获取到参数时会设置默认值,GetQuery
可以判断是否取到:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 r.GET("/index" , func (c *gin.Context) { data, ok := c.GetQuery("query" ) if !ok { } c.JSON(http.StatusOK, gin.H{ "data" : data, }) })
获取POST请求参数时,与获取GET请求参数相类似,也有三个方法:PostForm
,DefaultPostForm
以及GetPostForm
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 () { r := gin.Default() r.LoadHTMLFiles("./form.html" , "./index.html" ) r.GET("/login" , func (c *gin.Context) { c.HTML(http.StatusOK, "form.html" , nil ) }) r.POST("/login" , func (c *gin.Context) { user := c.PostForm("username" ) pwd := c.PostForm("password" ) c.HTML(http.StatusOK, "index.html" , gin.H{ "username" : user, "password" : pwd, }) }) r.Run(":9090" ) }
获取path参数(URI) 获取请求URL路径中的参数的方式如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 func main () { router := gin.Default() router.GET("/user/:name" , func (c *gin.Context) { name := c.Param("name" ) c.String(http.StatusOK, "Hello %s" , name) }) router.GET("/user/:name/*action" , func (c *gin.Context) { name := c.Param("name" ) action := c.Param("action" ) message := name + " is " + action c.String(http.StatusOK, message) }) router.Run(":8080" ) }
参数绑定 基于请求的Content-type
失败请求中的类型并自动提取请求中的querystring
、form
、json
、uri
等参数到结构体中。ShouldBind
能够自动提取json
、form
和querystring
类型的数据并放入结构体中。
Gin使用 go-playground/validator/v10 进行验证。 查看标签用法的全部文档 。
使用时,需要在要绑定的所有字段上,设置相应的tag 。 例如,使用 JSON 绑定时,设置字段标签为 json:"fieldname"
。
Gin提供了两类绑定方法:
Must bind:
Methods - Bind
, BindJSON
, BindXML
, BindQuery
, BindYAML
。
Behavior - 这些方法属于 MustBindWith
的具体调用。 如果发生绑定错误,则请求终止 ,并触发 c.AbortWithError(400, err).SetType(ErrorTypeBind)
。响应状态码被设置为 400 并且 Content-Type
被设置为 text/plain; charset=utf-8
。 如果在此之后尝试设置响应状态码,Gin会输出日志 [GIN-debug] [WARNING] Headers were already written. Wanted to override status code 400 with 422
。 如果希望更好地控制绑定 ,考虑使用 ShouldBind
等效方法。
Should bind:
Methods - ShouldBind
, ShouldBindJSON
, ShouldBindXML
, ShouldBindQuery
, ShouldBindYAML
Behavior - 这些方法属于 ShouldBindWith
的具体调用。 如果发生绑定错误,Gin 会返回错误并由开发者处理错误和请求 。
使用 Bind 方法时,Gin 会尝试根据 Content-Type 推断如何绑定 。 如果明确知道要绑定什么 ,可以使用 MustBindWith
或 ShouldBindWith
。
也可以指定必须绑定的字段。 如果一个字段的 tag 加上了 binding:"required"
,但绑定时是空值,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 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 package mainimport ( "github.com/gin-gonic/gin" "net/http" ) type userInfo struct { Username string `form:"username" json:"username"` Password string `form:"password" json:"password"` } func main () { r := gin.Default() r.GET("/query" , func (c *gin.Context) { var u userInfo err := c.ShouldBind(&u) if err != nil { c.JSON(http.StatusBadRequest, gin.H{ "status" : "Bad" , }) } else { c.JSON(http.StatusOK, gin.H{ "status" : "OK" , }) } }) r.POST("/form" , func (c *gin.Context) { var u userInfo err := c.ShouldBind(&u) if err != nil { c.JSON(http.StatusBadRequest, gin.H{ "status" : "Bad" , }) } else { c.JSON(http.StatusOK, gin.H{ "status" : "OK" , }) } }) r.POST("/json" , func (c *gin.Context) { var u userInfo err := c.ShouldBind(&u) if err != nil { c.JSON(http.StatusBadRequest, gin.H{ "status" : "Bad" , }) } else { c.JSON(http.StatusOK, gin.H{ "status" : "OK" , }) } }) r.Run(":9090" ) }
将 request body 绑定到不同结构体中 一般通过调用 c.Request.Body
方法绑定数据,但不能多次调用这个方法,在第二次调用时 因为 c.Request.Body
是 EOF,所以会报错。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 type formA struct { Foo string `json:"foo" xml:"foo" binding:"required"` } type formB struct { Bar string `json:"bar" xml:"bar" binding:"required"` } func SomeHandler (c *gin.Context) { objA := formA{} objB := formB{} if errA := c.ShouldBind(&objA); errA == nil { c.String(http.StatusOK, `the body should be formA` ) } else if errB := c.ShouldBind(&objB); errB == nil { c.String(http.StatusOK, `the body should be formB` ) } else { ... } }
要想多次绑定 ,可以使用 c.ShouldBindBodyWith
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 objA := formA{} objB := formB{} if errA := c.ShouldBindBodyWith(&objA, binding.JSON); errA == nil { c.String(http.StatusOK, `the body should be formA` ) } else if errB := c.ShouldBindBodyWith(&objB, binding.JSON); errB == nil { c.String(http.StatusOK, `the body should be formB JSON` ) } else if errB2 := c.ShouldBindBodyWith(&objB, binding.XML); errB2 == nil { c.String(http.StatusOK, `the body should be formB XML` ) } else { ... }
c.ShouldBindBodyWith
会在绑定之前将 body 存储到上下文中 。 这会对性能造成轻微影响 ,如果调用一次就能完成绑定的话,那就不要用这个方法。
只有某些格式需要此功能,如 JSON
, XML
, MsgPack
, ProtoBuf
。 对于其他格式,如 Query
, Form
, FormPost
, FormMultipart
可以多次调用 c.ShouldBind()
而不会造成任任何性能损失(因为它们使用 req.Form 或 url.Values,而不是 req.Body )。
绑定 uri 也可以将 URI 中的变量绑定到结构体中 ,使用 uri tag。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package mainimport "github.com/gin-gonic/gin" type Person struct { ID string `uri:"id" binding:"required,uuid"` Name string `uri:"name" binding:"required"` } func main () { route := gin.Default() route.GET("/:name/:id" , func (c *gin.Context) { var person Person if err := c.ShouldBindUri(&person); err != nil { c.JSON(400 , gin.H{"msg" : err.Error()}) return } c.JSON(200 , gin.H{"name" : person.Name, "uuid" : person.ID}) }) route.Run(":8088" ) }
设置和获取Cookie 使用 Cookie
函数可以获取 Cookie,使用 setCookie
函数可以设置 Cookie:
1 2 3 4 5 6 7 8 9 10 11 router.GET("/cookie" , func (c *gin.Context) { cookie, err := c.Cookie("gin_cookie" ) if err != nil { cookie = "NotSet" c.SetCookie("gin_cookie" , "test" , 3600 , "/" , "localhost" , false , true ) } fmt.Printf("Cookie value: %s \n" , cookie) })