文章目录
- 1、下载并安装 gin:
- 2、快速例子
- 3、示例
- 3.1、AsciiJSON
- 3.2、HTML 渲染
- 3.3、JSONP
- 3.4、Multipart/Urlencoded 绑定
- 3.5、Multipart/Urlencoded 表单
- 3.6、PureJSON
- 3.7、Query 和 post form
- 3.8、SecureJSON
- 3.9、XML/JSON/YAML/ProtoBuf 渲染
- 3.10、上传文件
- 3.10.1、单文件
- 3.10.2、多文件
- 3.11、不使用默认的中间件
- 3.12、从 reader 读取数据
- 3.13、使用 BasicAuth 中间件
- 3.14、使用 HTTP 方法
- 3.15、只绑定 url 查询字符串
- 3.16、在中间件中使用 Goroutine
- 3.17、如何记录日志
- 3.18、将 request body 绑定到不同的结构体中
- 3.19、映射查询字符串或表单参数
- 3.20、查询字符串参数
- 3.21、模型绑定和验证
- 3.22、绑定 HTML 复选框
- 3.23、绑定 Uri
- 3.24、绑定查询字符串或表单数据
- 3.25、绑定表单数据至自定义结构体
- 3.26、自定义 HTTP 配置
- 3.27、自定义中间件
- 3.28、自定义日志文件
- 3.29、自定义验证器
- 3.30、设置和获取 Cookie
- 3.31、路由参数
- 3.32、路由组
- 3.33、运行多个服务
- 3.34、HTTP重定向
- 3.35、静态文件服务
- 3.36、静态资源嵌入
本文是来自官方Gin例子 https://gin-gonic.com/zh-cn/docs/examples/,学习Gin的时候把官方例子练习了一次和记录下来。
1、下载并安装 gin:
# 1、下载安装依赖包
$ go get -u github.com/gin-gonic/gin# 2、将gin引入到代码中:
import "github.com/gin-gonic/gin"# 3、(可选)如果使用诸如 http.StatusOK 之类的常量,则需要引入 net/http 包:
import "net/http"
2、快速例子
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 上启动服务
}
3、示例
3.1、AsciiJSON
使用 AsciiJSON 生成具有转义的非 ASCII 字符的 ASCII-only JSON。
func main() {r := gin.Default()r.GET("/someJSON", func(c *gin.Context) {data := map[string]interface{}{"lang": "GO语言","tag": "<br>",}// 输出 : {"lang":"GO\u8bed\u8a00","tag":"\u003cbr\u003e"}c.AsciiJSON(http.StatusOK, data)})// 监听并在 0.0.0.0:8080 上启动服务r.Run(":8080")
}
3.2、HTML 渲染
使用 LoadHTMLGlob() 或者 LoadHTMLFiles()
func main() {router := gin.Default()router.LoadHTMLGlob("templates/*")//router.LoadHTMLFiles("templates/template1.html", "templates/template2.html")router.GET("/index", func(c *gin.Context) {c.HTML(http.StatusOK, "index.tmpl", gin.H{"title": "Main website",})})router.Run(":8080")
}
templates/index.tmpl
<html><h1>{{ .title }}</h1>
</html>
使用不同目录下名称相同的模板
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")
}
templates/posts/index.tmpl
{{ define "posts/index.tmpl" }}
<html><h1>{{ .title }}
</h1>
<p>Using posts/index.tmpl</p>
</html>
{{ end }}
templates/users/index.tmpl
{{ define "users/index.tmpl" }}
<html><h1>{{ .title }}
</h1>
<p>Using users/index.tmpl</p>
</html>
{{ end }}
自定义模板渲染器
你可以使用自定义的 html 模板渲染
import "html/template"func main() {router := gin.Default()html := template.Must(template.ParseFiles("file1", "file2"))router.SetHTMLTemplate(html)router.Run(":8080")
}
自定义分隔符
你可以使用自定义分隔
r := gin.Default()r.Delims("{[{", "}]}")r.LoadHTMLGlob("/path/to/templates")
自定义模板功能
查看详细示例代码。
main.go
import ("fmt""html/template""net/http""time""github.com/gin-gonic/gin"
)func formatAsDate(t time.Time) string {year, month, day := t.Date()return fmt.Sprintf("%d/%02d/%02d", year, month, day)
}func main() {router := gin.Default()router.Delims("{[{", "}]}")router.SetFuncMap(template.FuncMap{"formatAsDate": formatAsDate,})router.LoadHTMLFiles("./testdata/template/raw.tmpl")router.GET("/raw", func(c *gin.Context) {c.HTML(http.StatusOK, "raw.tmpl", map[string]interface{}{"now": time.Date(2017, 07, 01, 0, 0, 0, 0, time.UTC),})})router.Run(":8080")
}
raw.tmpl
Date: {[{.now | formatAsDate}]}
结果:
Date: 2017/07/01
3.3、JSONP
使用 JSONP 向不同域的服务器请求数据。如果查询参数存在回调,则将回调添加到响应体中。
func main() {
r := gin.Default()r.GET("/JSONP", func(c *gin.Context) {data := map[string]interface{}{"foo": "bar",}// /JSONP?callback=x// 将输出:x({\"foo\":\"bar\"})c.JSONP(http.StatusOK, data)})// 监听并在 0.0.0.0:8080 上启动服务r.Run(":8080")
}
3.4、Multipart/Urlencoded 绑定
package mainimport ("github.com/gin-gonic/gin"
)type LoginForm struct {User string `form:"user" binding:"required"`Password string `form:"password" binding:"required"`
}func main() {router := gin.Default()router.POST("/login", func(c *gin.Context) {// 你可以使用显式绑定声明绑定 multipart form:// c.ShouldBindWith(&form, binding.Form)// 或者简单地使用 ShouldBind 方法自动绑定:var form LoginForm// 在这种情况下,将自动选择合适的绑定if c.ShouldBind(&form) == nil {if form.User == "user" && form.Password == "password" {c.JSON(200, gin.H{"status": "you are logged in"})} else {c.JSON(401, gin.H{"status": "unauthorized"})}}})router.Run(":8080")
}
测试:
$ curl -v --form user=user --form password=password http://localhost:8080/login
3.5、Multipart/Urlencoded 表单
func main() {router := gin.Default()router.POST("/form_post", func(c *gin.Context) {message := c.PostForm("message")nick := c.DefaultPostForm("nick", "anonymous")c.JSON(200, gin.H{"status": "posted","message": message,"nick": nick,})})router.Run(":8080")
}
3.6、PureJSON
通常,JSON 使用 unicode 替换特殊 HTML 字符,例如 < 变为 \ u003c。如果要按字面对这些字符进行编码,则可以使用 PureJSON。Go 1.6 及更低版本无法使用此功能。
func main() {
r := gin.Default()// 提供 unicode 实体r.GET("/json", func(c *gin.Context) {c.JSON(200, gin.H{"html": "<b>Hello, world!</b>",})})// 提供字面字符r.GET("/purejson", func(c *gin.Context) {c.PureJSON(200, gin.H{"html": "<b>Hello, world!</b>",})})// 监听并在 0.0.0.0:8080 上启动服务r.Run(":8080")
}
3.7、Query 和 post form
POST /post?id=1234&page=1 HTTP/1.1
Content-Type: application/x-www-form-urlencoded
name=manu&message=this_is_great
func main() {
router := gin.Default()router.POST("/post", func(c *gin.Context) {id := c.Query("id")page := c.DefaultQuery("page", "0")name := c.PostForm("name")message := c.PostForm("message")fmt.Printf("id: %s; page: %s; name: %s; message: %s", id, page, name, message)})router.Run(":8080")
}
id: 1234; page: 1; name: manu; message: this_is_great
3.8、SecureJSON
使用 SecureJSON 防止 json 劫持。如果给定的结构是数组值,则默认预置 “while(1),” 到响应体。
func main() {
r := gin.Default()// 你也可以使用自己的 SecureJSON 前缀// r.SecureJsonPrefix(")]}',\n")r.GET("/someJSON", func(c *gin.Context) {names := []string{"lena", "austin", "foo"}// 将输出:while(1);["lena","austin","foo"]c.SecureJSON(http.StatusOK, names)})// 监听并在 0.0.0.0:8080 上启动服务r.Run(":8080")
}
3.9、XML/JSON/YAML/ProtoBuf 渲染
func main() {
r := gin.Default()// gin.H 是 map[string]interface{} 的一种快捷方式r.GET("/someJSON", func(c *gin.Context) {c.JSON(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK})})r.GET("/moreJSON", func(c *gin.Context) {// 你也可以使用一个结构体var msg struct {Name string `json:"user"`Message stringNumber int}msg.Name = "Lena"msg.Message = "hey"msg.Number = 123// 注意 msg.Name 在 JSON 中变成了 "user"// 将输出:{"user": "Lena", "Message": "hey", "Number": 123}c.JSON(http.StatusOK, msg)})r.GET("/someXML", func(c *gin.Context) {c.XML(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK})})r.GET("/someYAML", func(c *gin.Context) {c.YAML(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK})})r.GET("/someProtoBuf", func(c *gin.Context) {reps := []int64{int64(1), int64(2)}label := "test"// protobuf 的具体定义写在 testdata/protoexample 文件中。data := &protoexample.Test{Label: &label,Reps: reps,}// 请注意,数据在响应中变为二进制数据// 将输出被 protoexample.Test protobuf 序列化了的数据c.ProtoBuf(http.StatusOK, data)})// 监听并在 0.0.0.0:8080 上启动服务r.Run(":8080")
}
3.10、上传文件
3.10.1、单文件
func main() {
router := gin.Default()
// 为 multipart forms 设置较低的内存限制 (默认是 32 MiB)
router.MaxMultipartMemory = 8 << 20 // 8 MiB
router.POST("/upload", func(c *gin.Context) {
// 单文件
file, _ := c.FormFile("file")
log.Println(file.Filename)dst := "./" + file.Filename// 上传文件至指定的完整文件路径c.SaveUploadedFile(file, dst)c.String(http.StatusOK, fmt.Sprintf("'%s' uploaded!", file.Filename))})router.Run(":8080")
}
如何使用 curl:
curl -X POST http://localhost:8080/upload \
-F "file=@/Users/appleboy/test.zip" \
-H "Content-Type: multipart/form-data"
3.10.2、多文件
func main() {
router := gin.Default()
// 为 multipart forms 设置较低的内存限制 (默认是 32 MiB)
router.MaxMultipartMemory = 8 << 20 // 8 MiB
router.POST("/upload", func(c *gin.Context) {
// Multipart form
form, _ := c.MultipartForm()
files := form.File["upload[]"]for _, file := range files {log.Println(file.Filename)// 上传文件至指定目录c.SaveUploadedFile(file, dst)}c.String(http.StatusOK, fmt.Sprintf("%d files uploaded!", len(files)))})router.Run(":8080")
}
如何使用 curl:
curl -X POST http://localhost:8080/upload \
-F "upload[]=@/Users/appleboy/test1.zip" \
-F "upload[]=@/Users/appleboy/test2.zip" \
-H "Content-Type: multipart/form-data"
3.11、不使用默认的中间件
使用
r := gin.New()
代替
// Default 使用 Logger 和 Recovery 中间件
r := gin.Default()
3.12、从 reader 读取数据
func main() {
router := gin.Default()
router.GET("/someDataFromReader", func(c *gin.Context) {
response, err := http.Get("https://raw.githubusercontent.com/gin-gonic/logo/master/color.png")
if err != nil || response.StatusCode != http.StatusOK {
c.Status(http.StatusServiceUnavailable)
return
}reader := response.BodycontentLength := response.ContentLengthcontentType := response.Header.Get("Content-Type")extraHeaders := map[string]string{"Content-Disposition": `attachment; filename="gopher.png"`,}c.DataFromReader(http.StatusOK, contentLength, contentType, reader, extraHeaders)})router.Run(":8080")
}
3.13、使用 BasicAuth 中间件
// 模拟一些私人数据
var secrets = gin.H{"foo": gin.H{"email": "foo@bar.com", "phone": "123433"},"austin": gin.H{"email": "austin@example.com", "phone": "666"},"lena": gin.H{"email": "lena@guapa.com", "phone": "523443"},
}func main() {
r := gin.Default()// 路由组使用 gin.BasicAuth() 中间件// gin.Accounts 是 map[string]string 的一种快捷方式authorized := r.Group("/admin", gin.BasicAuth(gin.Accounts{"foo": "bar","austin": "1234","lena": "hello2","manu": "4321",}))// /admin/secrets 端点// 触发 "localhost:8080/admin/secretsauthorized.GET("/secrets", func(c *gin.Context) {// 获取用户,它是由 BasicAuth 中间件设置的user := c.MustGet(gin.AuthUserKey).(string)if secret, ok := secrets[user]; ok {c.JSON(http.StatusOK, gin.H{"user": user, "secret": secret})} else {c.JSON(http.StatusOK, gin.H{"user": user, "secret": "NO SECRET :("})}})// 监听并在 0.0.0.0:8080 上启动服务r.Run(":8080")
}
3.14、使用 HTTP 方法
func main() {
// 禁用控制台颜色
// gin.DisableConsoleColor()// 使用默认中间件(logger 和 recovery 中间件)创建 gin 路由router := gin.Default()router.GET("/someGet", getting)router.POST("/somePost", posting)router.PUT("/somePut", putting)router.DELETE("/someDelete", deleting)router.PATCH("/somePatch", patching)router.HEAD("/someHead", head)router.OPTIONS("/someOptions", options)// 默认在 8080 端口启动服务,除非定义了一个 PORT 的环境变量。router.Run()// router.Run(":3000") hardcode 端口号
}
3.15、只绑定 url 查询字符串
ShouldBindQuery 函数只绑定 url 查询参数而忽略 post 数据。参阅详细信息.
package mainimport (
"log""github.com/gin-gonic/gin"
)type Person struct {Name string `form:"name"`Address string `form:"address"`
}func main() {route := gin.Default()route.Any("/testing", startPage)route.Run(":8085")
}func startPage(c *gin.Context) {var person Personif c.ShouldBindQuery(&person) == nil {log.Println("====== Only Bind By Query String ======")log.Println(person.Name)log.Println(person.Address)}c.String(200, "Success")
}
3.16、在中间件中使用 Goroutine
当在中间件或 handler 中启动新的 Goroutine 时,不能使用原始的上下文,必须使用只读副本。
func main() {r := gin.Default()r.GET("/long_async", func(c *gin.Context) {// 创建在 goroutine 中使用的副本cCp := c.Copy()go func() {// 用 time.Sleep() 模拟一个长任务。time.Sleep(5 * time.Second)// 请注意您使用的是复制的上下文 "cCp",这一点很重要log.Println("Done! in path " + cCp.Request.URL.Path)}()})r.GET("/long_sync", func(c *gin.Context) {// 用 time.Sleep() 模拟一个长任务。time.Sleep(5 * time.Second)// 因为没有使用 goroutine,不需要拷贝上下文log.Println("Done! in path " + c.Request.URL.Path)})// 监听并在 0.0.0.0:8080 上启动服务r.Run(":8080")
}
3.17、如何记录日志
func main() {// 禁用控制台颜色,将日志写入文件时不需要控制台颜色。gin.DisableConsoleColor()// 记录到文件。f, _ := os.Create("gin.log")gin.DefaultWriter = io.MultiWriter(f)// 如果需要同时将日志写入文件和控制台,请使用以下代码。// gin.DefaultWriter = io.MultiWriter(f, os.Stdout)router := gin.Default()router.GET("/ping", func(c *gin.Context) {c.String(200, "pong")})router.Run(":8080")
}
3.18、将 request body 绑定到不同的结构体中
一般通过调用 c.Request.Body 方法绑定数据,但不能多次调用这个方法。
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{}// c.ShouldBind 使用了 c.Request.Body,不可重用。if errA := c.ShouldBind(&objA); errA == nil {c.String(http.StatusOK, `the body should be formA`)// 因为现在 c.Request.Body 是 EOF,所以这里会报错。} else if errB := c.ShouldBind(&objB); errB == nil {c.String(http.StatusOK, `the body should be formB`)} else {...}
}
要想多次绑定,可以使用 c.ShouldBindBodyWith.
func SomeHandler(c *gin.Context) {objA := formA{}objB := formB{}// 读取 c.Request.Body 并将结果存入上下文。if errA := c.ShouldBindBodyWith(&objA, binding.JSON); errA == nil {c.String(http.StatusOK, `the body should be formA`)// 这时, 复用存储在上下文中的 body。} 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() 而不会造成任任何性能损失 (详见 #1341)。
3.19、映射查询字符串或表单参数
POST /post?ids[a]=1234&ids[b]=hello HTTP/1.1
Content-Type: application/x-www-form-urlencodednames[first]=thinkerou&names[second]=tianou
func main() {router := gin.Default()router.POST("/post", func(c *gin.Context) {ids := c.QueryMap("ids")names := c.PostFormMap("names")fmt.Printf("ids: %v; names: %v", ids, names)})router.Run(":8080")
}
ids: map[b:hello a:1234], names: map[second:tianou first:thinkerou]
3.20、查询字符串参数
func main() {
router := gin.Default()// 使用现有的基础请求对象解析查询字符串参数。// 示例 URL: /welcome?firstname=Jane&lastname=Doerouter.GET("/welcome", func(c *gin.Context) {firstname := c.DefaultQuery("firstname", "Guest")lastname := c.Query("lastname") // c.Request.URL.Query().Get("lastname") 的一种快捷方式c.String(http.StatusOK, "Hello %s %s", firstname, lastname)})router.Run(":8080")
}
3.21、模型绑定和验证
要将请求体绑定到结构体中,使用模型绑定。 Gin目前支持JSON、XML、YAML和标准表单值的绑定(foo=bar&boo=baz)。
Gin使用 go-playground/validator/v10 进行验证。 查看标签用法的全部文档.
使用时,需要在要绑定的所有字段上,设置相应的tag。 例如,使用 JSON 绑定时,设置字段标签为 json:“fieldname”。
Gin提供了两类绑定方法:
- Type - 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 等效方法。
- Type - Should bind
- Methods - ShouldBind, ShouldBindJSON, ShouldBindXML, ShouldBindQuery, ShouldBindYAML
- Behavior - 这些方法属于 ShouldBindWith 的具体调用。 如果发生绑定错误,Gin 会返回错误并由开发者处理错误和请求。
使用 Bind 方法时,Gin 会尝试根据 Content-Type 推断如何绑定。 如果你明确知道要绑定什么,可以使用 MustBindWith 或 ShouldBindWith。
你也可以指定必须绑定的字段。 如果一个字段的 tag 加上了 binding:“required”,但绑定时是空值, Gin 会报错。
// 绑定 JSON
type Login struct {
User string `form:"user" json:"user" xml:"user" binding:"required"`
Password string `form:"password" json:"password" xml:"password" binding:"required"`
}func main() {
router := gin.Default()// 绑定 JSON ({"user": "manu", "password": "123"})router.POST("/loginJSON", func(c *gin.Context) {var json Loginif err := c.ShouldBindJSON(&json); err != nil {c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})return}if json.User != "manu" || json.Password != "123" {c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})return} c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})})// 绑定 XML (// <?xml version="1.0" encoding="UTF-8"?>// <root>// <user>manu</user>// <password>123</password>// </root>)router.POST("/loginXML", func(c *gin.Context) {var xml Loginif err := c.ShouldBindXML(&xml); err != nil {c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})return}if xml.User != "manu" || xml.Password != "123" {c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})return} c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})})// 绑定 HTML 表单 (user=manu&password=123)router.POST("/loginForm", func(c *gin.Context) {var form Login// 根据 Content-Type Header 推断使用哪个绑定器。if err := c.ShouldBind(&form); err != nil {c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})return}if form.User != "manu" || form.Password != "123" {c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})return} c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})})// 监听并在 0.0.0.0:8080 上启动服务router.Run(":8080")
}
3.22、绑定 HTML 复选框
main.go
...type myForm struct {
Colors []string `form:"colors[]"`
}...func formHandler(c *gin.Context) {
var fakeForm myForm
c.ShouldBind(&fakeForm)
c.JSON(200, gin.H{"color": fakeForm.Colors})
}...
form.html
<form action="/" method="POST"><p>Check some colors</p><label for="red">Red</label><input type="checkbox" name="colors[]" value="red" id="red"><label for="green">Green</label><input type="checkbox" name="colors[]" value="green" id="green"><label for="blue">Blue</label><input type="checkbox" name="colors[]" value="blue" id="blue"><input type="submit">
</form>
结果:
{"color":["red","green","blue"]}
3.23、绑定 Uri
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 Personif 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")
}
测试:
$ curl -v localhost:8088/thinkerou/987fbc97-4bed-5078-9f07-9141ba07c9f3
$ curl -v localhost:8088/thinkerou/not-uuid
3.24、绑定查询字符串或表单数据
package mainimport ("log""time""github.com/gin-gonic/gin"
)type Person struct {Name string `form:"name"`Address string `form:"address"`Birthday time.Time `form:"birthday" time_format:"2006-01-02" time_utc:"1"`
}func main() {route := gin.Default()route.GET("/testing", startPage)route.Run(":8085")
}func startPage(c *gin.Context) {var person Person// 如果是 `GET` 请求,只使用 `Form` 绑定引擎(`query`)。// 如果是 `POST` 请求,首先检查 `content-type` 是否为 `JSON` 或 `XML`,然后再使用 `Form`(`form-data`)。// 查看更多:https://github.com/gin-gonic/gin/blob/master/binding/binding.go#L88if c.ShouldBind(&person) == nil {log.Println(person.Name)log.Println(person.Address)log.Println(person.Birthday)}c.String(200, "Success")
}
测试:
$ curl -X GET "localhost:8085/testing?name=appleboy&address=xyz&birthday=1992-03-15"
3.25、绑定表单数据至自定义结构体
以下示例使用自定义结构体:
type StructA struct {FieldA string `form:"field_a"`
}type StructB struct {NestedStruct StructAFieldB string `form:"field_b"`
}type StructC struct {NestedStructPointer *StructAFieldC string `form:"field_c"`
}type StructD struct {NestedAnonyStruct struct {FieldX string `form:"field_x"`}FieldD string `form:"field_d"`
}func GetDataB(c *gin.Context) {var b StructBc.Bind(&b)c.JSON(200, gin.H{"a": b.NestedStruct,"b": b.FieldB,})
}func GetDataC(c *gin.Context) {var b StructCc.Bind(&b)c.JSON(200, gin.H{"a": b.NestedStructPointer,"c": b.FieldC,})
}func GetDataD(c *gin.Context) {var b StructDc.Bind(&b)c.JSON(200, gin.H{"x": b.NestedAnonyStruct,"d": b.FieldD,})
}func main() {r := gin.Default()r.GET("/getb", GetDataB)r.GET("/getc", GetDataC)r.GET("/getd", GetDataD)r.Run()
}
使用 curl 命令结果:
$ curl "http://localhost:8080/getb?field_a=hello&field_b=world"
{"a":{"FieldA":"hello"},"b":"world"}
$ curl "http://localhost:8080/getc?field_a=hello&field_c=world"
{"a":{"FieldA":"hello"},"c":"world"}
$ curl "http://localhost:8080/getd?field_x=hello&field_d=world"
{"d":"world","x":{"FieldX":"hello"}}
3.26、自定义 HTTP 配置
直接使用 http.ListenAndServe(),如下所示:
func main() {router := gin.Default()http.ListenAndServe(":8080", router)
}
或
func main() {router := gin.Default()s := &http.Server{Addr: ":8080",Handler: router,ReadTimeout: 10 * time.Second,WriteTimeout: 10 * time.Second,MaxHeaderBytes: 1 << 20,}s.ListenAndServe()
}
3.27、自定义中间件
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")
}
3.28、自定义日志文件
func main() {router := gin.New()// LoggerWithFormatter 中间件会写入日志到 gin.DefaultWriter// 默认 gin.DefaultWriter = os.Stdoutrouter.Use(gin.LoggerWithFormatter(func(param gin.LogFormatterParams) string {// 你的自定义格式return fmt.Sprintf("%s - [%s] \"%s %s %s %d %s \"%s\" %s\"\n",param.ClientIP,param.TimeStamp.Format(time.RFC1123),param.Method,param.Path,param.Request.Proto,param.StatusCode,param.Latency,param.Request.UserAgent(),param.ErrorMessage,)}))router.Use(gin.Recovery())router.GET("/ping", func(c *gin.Context) {c.String(200, "pong")})router.Run(":8080")
}
3.29、自定义验证器
注册自定义验证器,查看示例代码.
package main
import ("net/http""reflect""time""github.com/gin-gonic/gin""github.com/gin-gonic/gin/binding""github.com/go-playground/validator/v10"
)
// Booking 包含绑定和验证的数据。
type Booking struct {CheckIn time.Time `form:"check_in" binding:"required,bookabledate" time_format:"2006-01-02"`CheckOut time.Time `form:"check_out" binding:"required,gtfield=CheckIn,bookabledate" time_format:"2006-01-02"`
}var bookableDate validator.Func = func(fl validator.FieldLevel) bool {date, ok := fl.Field().Interface().(time.Time)if ok {today := time.Now()if today.After(date) {return false}}return true
}func main() {route := gin.Default()if v, ok := binding.Validator.Engine().(*validator.Validate); ok {v.RegisterValidation("bookabledate", bookableDate)}route.GET("/bookable", getBookable)route.Run(":8085")
}func getBookable(c *gin.Context) {var b Bookingif err := c.ShouldBindWith(&b, binding.Query); err == nil {c.JSON(http.StatusOK, gin.H{"message": "Booking dates are valid!"})} else {c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})}
}
3.30、设置和获取 Cookie
import ("fmt""github.com/gin-gonic/gin"
)func main() {router := gin.Default()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)})router.Run()
}
3.31、路由参数
func main() {
router := gin.Default()// 此 handler 将匹配 /user/john 但不会匹配 /user/ 或者 /userrouter.GET("/user/:name", func(c *gin.Context) {name := c.Param("name")c.String(http.StatusOK, "Hello %s", name)})// 此 handler 将匹配 /user/john/ 和 /user/john/send// 如果没有其他路由匹配 /user/john,它将重定向到 /user/john/router.GET("/user/:name/*action", func(c *gin.Context) {name := c.Param("name")action := c.Param("action")message := name + " is " + actionc.String(http.StatusOK, message)})router.Run(":8080")
}
3.32、路由组
func main() {router := gin.Default()// 简单的路由组: v1v1 := router.Group("/v1"){v1.POST("/login", loginEndpoint)v1.POST("/submit", submitEndpoint)v1.POST("/read", readEndpoint)}// 简单的路由组: v2v2 := router.Group("/v2"){v2.POST("/login", loginEndpoint)v2.POST("/submit", submitEndpoint)v2.POST("/read", readEndpoint)}router.Run(":8080")
}
3.33、运行多个服务
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)}
}
3.34、HTTP重定向
HTTP 重定向很容易。 内部、外部重定向均支持。
r.GET("/test", func(c *gin.Context) {c.Redirect(http.StatusMovedPermanently, "http://www.google.com/")
})
通过 POST 方法进行 HTTP 重定向。请参考 issue:#444
r.POST("/test", func(c *gin.Context) {c.Redirect(http.StatusFound, "/foo")
})
路由重定向,使用 HandleContext:
r.GET("/test", func(c *gin.Context) {c.Request.URL.Path = "/test2"r.HandleContext(c)
})
r.GET("/test2", func(c *gin.Context) {c.JSON(200, gin.H{"hello": "world"})
})
3.35、静态文件服务
func main() {router := gin.Default()router.Static("/assets", "./assets")router.StaticFS("/more_static", http.Dir("my_file_system"))router.StaticFile("/favicon.ico", "./resources/favicon.ico")// 监听并在 0.0.0.0:8080 上启动服务router.Run(":8080")
}
3.36、静态资源嵌入
你可以使用 go-assets 将静态资源打包到可执行文件中。
go get github.com/jessevdk/go-assets-builder
func main() {r := gin.New()t, err := loadTemplate()if err != nil {panic(err)}r.SetHTMLTemplate(t)r.GET("/", func(c *gin.Context) {c.HTML(http.StatusOK, "/html/index.tmpl", nil)})r.Run(":8080")
}// loadTemplate 加载由 go-assets-builder 嵌入的模板
func loadTemplate() (*template.Template, error) {t := template.New("")for name, file := range Assets.Files {if file.IsDir() || !strings.HasSuffix(name, ".tmpl") {continue}h, err := ioutil.ReadAll(file)if err != nil {return nil, err}t, err = t.New(name).Parse(string(h))if err != nil {return nil, err}}return t, nil
}