Gin 是用Go语言编写的Web框架。它具有类似于martini的API,其性能最高可提高40倍,这要归功于httprouter。

安装

要安装Gin,你需要安装Go并且设置Go工作区。

  • 1、你需要安装Go(1.12+),然后你可以使用下面的Go命令安装Gin。
1
$ go get -u github.com/gin-gonic/gin
  • 2、导入它到你的代码。
1
import "github.com/gin-gonic/gin"
  • 3、(可选)导入net/http,例如,在使用诸如http.StatusOk之类的常量时,这是必须的。
1
import "net/http"

快速开始

首先创建一个名为example.go的文件

1
$ touch example.go

将下面的代码写入example.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package main

import (
"github.com/gin-gonic/gin"
"net/http"
)

func main() {
r := gin.Default()
r.GET("/ping", func(context *gin.Context) {
context.JSON(http.StatusOK, gin.H{
"message": "pong",
})
})
r.Run() // 默认服务地址(localhost:8080)
}

执行go run main.go命令运行代码:

1
$ go run example.go

在浏览器中访localhost:8080/ping

基准测试

Benchmark name (1) (2) (3) (4)
BenchmarkGin_GithubAll 43550 27364 ns/op 0 B/op 0 allocs/op
BenchmarkAce_GithubAll 40543 29670 ns/op 0 B/op 0 allocs/op
BenchmarkAero_GithubAll 57632 20648 ns/op 0 B/op 0 allocs/op
BenchmarkBear_GithubAll 9234 216179 ns/op 86448 B/op 943 allocs/op
BenchmarkBeego_GithubAll 7407 243496 ns/op 71456 B/op 609 allocs/op
BenchmarkBone_GithubAll 420 2922835 ns/op 720160 B/op 8620 allocs/op
BenchmarkChi_GithubAll 7620 238331 ns/op 87696 B/op 609 allocs/op
BenchmarkDenco_GithubAll 18355 64494 ns/op 20224 B/op 167 allocs/op
BenchmarkEcho_GithubAll 31251 38479 ns/op 0 B/op 0 allocs/op
BenchmarkGocraftWeb_GithubAll 4117 300062 ns/op 131656 B/op 1686 allocs/op
BenchmarkGoji_GithubAll 3274 416158 ns/op 56112 B/op 334 allocs/op
BenchmarkGojiv2_GithubAll 1402 870518 ns/op 352720 B/op 4321 allocs/op
BenchmarkGoJsonRest_GithubAll 2976 401507 ns/op 134371 B/op 2737 allocs/op
BenchmarkGoRestful_GithubAll 410 2913158 ns/op 910144 B/op 2938 allocs/op
BenchmarkGorillaMux_GithubAll 346 3384987 ns/op 251650 B/op 1994 allocs/op
BenchmarkGowwwRouter_GithubAll 10000 143025 ns/op 72144 B/op 501 allocs/op
BenchmarkHttpRouter_GithubAll 55938 21360 ns/op 0 B/op 0 allocs/op
BenchmarkHttpTreeMux_GithubAll 10000 153944 ns/op 65856 B/op 671 allocs/op
BenchmarkKocha_GithubAll 10000 106315 ns/op 23304 B/op 843 allocs/op
BenchmarkLARS_GithubAll 47779 25084 ns/op 0 B/op 0 allocs/op
BenchmarkMacaron_GithubAll 3266 371907 ns/op 149409 B/op 1624 allocs/op
BenchmarkMartini_GithubAll 331 3444706 ns/op 226551 B/op 2325 allocs/op
BenchmarkPat_GithubAll 273 4381818 ns/op 1483152 B/op 26963 allocs/op
BenchmarkPossum_GithubAll 10000 164367 ns/op 84448 B/op 609 allocs/op
BenchmarkR2router_GithubAll 10000 160220 ns/op 77328 B/op 979 allocs/op
BenchmarkRivet_GithubAll 14625 82453 ns/op 16272 B/op 167 allocs/op
BenchmarkTango_GithubAll 6255 279611 ns/op 63826 B/op 1618 allocs/op
BenchmarkTigerTonic_GithubAll 2008 687874 ns/op 193856 B/op 4474 allocs/op
BenchmarkTraffic_GithubAll 355 3478508 ns/op 820744 B/op 14114 allocs/op
BenchmarkVulcan_GithubAll 6885 193333 ns/op 19894 B/op 609 allocs/op
  • (1):在一定的时间内实现的总调用数越高越好。
  • (2):单次耗时(ns/op),越低越好。
  • (3):堆内存分配(B/op),越低越好。
  • (4):每次操作的平均内存分配次数(allocs/op),越低越好。

特点

  • 快速

    基于Readix树的路由,小内存占用。没有反射。可预测的API性能。

  • 支持中间件

    传入的http请求可以由一系列的中间件和最终操作来处理。例如:Logger,Authorization,GZIP,最终操作DB。

  • Crash处理

    Gin可以catch一个发生在HTTP请求中的panic并recover它。这样,你的服务器将始终可用。例如,你可以向Sentry报告这个panic!

  • JSON验证

    Gin可以解析并验证请求的JSON,例如检查所需值的存在。

  • 路由组

    更好的组织路由。是否需要授权,不同的API版本… 此外,这些组件可以无限制地嵌套而不会降低性能。

  • 错误管理

    Gin提供了一种方便的方法来收集HTTP请求器期间发生的所有错误。最终,中间件可以将他们写入到日志文件,并通过网络发送。

  • 内置渲染

    Gin为JSON,XML和HTML渲染提供了易于使用的API。

  • 可拓展性

    新建一个中间件非常简单。

API 示例

请求方式

使用GET,POST,PUT,PATCH,DELETE和OPTIONS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package main

import (
"github.com/gin-gonic/gin"
"net/http"
)

// GET,POST,PUT,PATCH,DELETE和OPTIONS
func main() {
router := gin.Default()
router.GET("/get", getting)
router.POST("/post", posting)
router.PUT("/put", putting)
router.PATCH("/patch", patching)
router.DELETE("/delete", deleting)
router.HEAD("/head", head)
router.OPTIONS("/options", options)
// 默认情况下,除非指定port,否则默认端口为8080
// 使用router.Run(":8000")指定端口
router.Run()
}

路径中的参数

该handler将会匹配 /user/john, 但是不会匹配/user/或/user

1
2
3
4
router.GET("/user/:name", func(context *gin.Context) {
name := context.Param("name")
context.String(http.StatusOK, "Hello %s", name)
})

然而,该handler只匹配/user/john/和/user/john/send
如果没有其他的handler匹配/user/john, 它将会重定向到/user/john/
*号截取路由后面的内容

1
2
3
4
5
6
router.GET("/user/:name/*action", func(context *gin.Context) {
name := context.Param("name")
action := context.Param("action")
message := name + "is" + action
context.String(http.StatusOK, message)
})

对于每个匹配的请求,上下文将保留路由定义

1
2
3
4
router.GET("/user/:name/*action", func(context *gin.Context) {
isSameUrl := context.FullPath() == "/user/:name/*action" // true
context.String(http.StatusOK, "%t", isSameUrl)
})

查询字符串参数

使用现有的基础请求对象来解析查询请求字符串参数。
请求响应匹配 /welcome?firstname=Jane&lastname=Doe

router.GET("/welcome", func(context *gin.Context) {
    // 当无匹配的firstname时默认值为Guest
    firstname := context.DefaultQuery("firstname", "Guest")
    lastname := context.Query("lastname")
    context.String(http.StatusOK, "Hello %s %s", firstname, lastname)
})

Multipart/Urlencoded 表单

1
2
3
4
5
6
7
8
9
router.POST("form_post", func(context *gin.Context) {
message := context.PostForm("message")
nike := context.DefaultPostForm("nike", "anonymous")
context.JSON(http.StatusOK, gin.H{
"status": "post",
message: message,
nike: nike,
})
})

其它: Query+Post

1
2
POST /post/post?id=123&page=10 HTTP/1.1
Content-Type: application/x-www-form-urlencoded
1
2
3
4
5
6
7
8
9
10
11
12
router.POST("post", func(context *gin.Context) {
id := context.Query("id")
page := context.DefaultQuery("page", "0")
name := context.PostForm("name")
gender := context.DefaultPostForm("gender", "男")
context.JSON(http.StatusOK, gin.H{
"id": id,
"page": page,
"name": name,
"gender": gender,
})
})

Map作为查询字符串或Post表单参数

1
2
3
4
POST /post?ids[a]=123&ids[b]=abc HTTP/1.1

Content-Type: application/x-www-form-urlencoded
names[a]=张三&names[b]=翠花
1
2
3
4
5
6
7
8
router.POST("post", func(context *gin.Context) {
ids := context.QueryMap("ids")
names := context.PostFormMap("names")
context.JSON(http.StatusOK, gin.H{
"ids": fmt.Sprintf("%v", ids), //"ids": "map[a:123 b:abc]",
"names": fmt.Sprintf("%v", names), //"names": "map[a:张三 b:翠花]"
})
})

文件上传

单文件上传

上传文件名可以由用户自定义,可能包含非法字符串,为了安全起见,应该由服务端统一文件名规则。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 设置Multipart Forms设置一个较低的内存限制(默认 32MiB)
router.MaxMultipartMemory = 8 << 20 //8 MiB
router.POST("upload", func(context *gin.Context) {
// 单个文件
file, _ := context.FormFile("file")
log.Println(file.Filename)

// 上传文件到指定目录, 含文件名
err := context.SaveUploadedFile(file, "/home/ysliu/test.jpg")
if err != nil {
log.Println(err)
}

context.String(http.StatusOK, fmt.Sprintf("%s upload!", file.Filename))
})

使用curl上传文件

1
2
3
curl -X POST http://localhost:8080/upload \
-F 'file=@小青.jpg' \
-H "Content-Type: multipart/form-data"

@用于加载文件,&用于指定多个键/值。

多文件上传

1
2
3
4
5
6
7
8
9
10
11
12
router.MaxMultipartMemory = 8 << 20 //8MiB
router.POST("/upload", func(context *gin.Context) {
multipartForm, _ := context.MultipartForm()
files := multipartForm.File["upload[]"]

for _, file := range files {
log.Println(file.Filename)

context.SaveUploadedFile(file, "/home/ysliu/upload/"+file.Filename)
}
context.String(http.StatusOK, fmt.Sprintf("%d file upload\n", len(files)))
})

使用curl上传多文件

1
2
3
4
curl -X POST http://localhost:8080/upload \
-F 'upload[]=@/home/ysliu/Pictures/images/1.jpg' \
-F 'upload[]=@/home/ysliu/Pictures/images/2.jpg' \
-H "Content-Type: multipart/form-data"

路由组

1
2
3
4
5
6
7
8
9
10
11
12
13
v1 := router.Group("/v1")
{
v1.POST("/login", loginEndpoint)
v1.POST("submit", submitEndpoint)
v1.POST("/read", readEndpoint)
}

v2 := router.Group("/v2")
{
v2.POST("/login", loginEndpoint)
v2.POST("submit", submitEndpoint)
v2.POST("/read", readEndpoint)
}

默认没有中间件的 gin

不包含任何中间件

1
router := gin.New()

包含Logger,Recovery中间件

1
router := gin.Default()

使用中间件

默认创建的路由没有任何中间件

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
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
package main

import (
"fmt"
"github.com/gin-gonic/gin"
"log"
"net/http"
"time"
)

func main() {
// 创建没有任何中间件的路由
router := gin.New()

// 全局中间件
// 即使你使用GIN_MODE=release进行设置,日志中间件也会将日志写入gin.DefaultWriter
// 默认情况gin.DefaultWriter = os.Stdio
router.Use(gin.Logger())

// Recovery中间件可以从任何panic中恢复,在遇到panic时,时将会返回500错误
router.Use(gin.Recovery())

// 可以添加任意个中间件,当遇到context.Next()执行后面的中间件
router.GET("/benchmark", MyBenchLogger(), benchEndpoint)

// 创建权限路由组
authorized := router.Group("/")
// 为权限路由组添加中间件
authorized.Use(Authorized())
{
// 执行权限路由组中间件,不会执行嵌套路由的中间件
authorized.POST("/login", loginEndpoint)
authorized.POST("/submit", submitEndpoint)
authorized.POST("/read", readEndpoint)

// 嵌套组,同样执行包含父路由中间件,也包含自己组的路由中间件
testing := authorized.Group("testing")
testing.Use(testingMiddleware)
testing.GET("/analytics", analyticsEndpoint)
}

router.Run()
}

func testingMiddleware(context *gin.Context) {
start := time.Now()
fmt.Println("我是嵌套路由组中间件---开始")
// 在gin上下文中定义一个变量
context.Set("example", "testingMiddleware")
// 执行请求
context.Next()
fmt.Println("我是嵌套路由组中间件---结束")
since := time.Since(start)
log.Println(since)
}

func Authorized() gin.HandlerFunc {
return func(context *gin.Context) {
start := time.Now()
fmt.Println("我是自定义权限校验中间件---开始")
// 在gin上下文中定义一个变量
context.Set("example", "Authorized")
// 执行请求
context.Next()
fmt.Println("我是自定义权限校验中间件---结束")
since := time.Since(start)
log.Println(since)
}
}

func MyBenchLogger() gin.HandlerFunc {
return func(context *gin.Context){
start := time.Now()
fmt.Println("我是自定义中间件的第一种定义方式---请求前")
// 在gin上下文中定义一个变量
context.Set("example", "CustomRouterMiddleware1")
// 执行请求
context.Next()
fmt.Println("我是自定义中间件的第一种定义方式---请求后")
since := time.Since(start)
log.Println(since)
}
}

func benchEndpoint(context *gin.Context) {
start := time.Now()
fmt.Println("我是自定义中间件的第二种定义方式---请求前")
// 在gin上下文中定义变量
context.Set("example", "CustomRouterMiddleware2")
// 执行请求
context.Next()
fmt.Println("我是自定义中间件的第二种定义方式---请求后")
since := time.Since(start)
log.Println(since)
}

func analyticsEndpoint(context *gin.Context) {
context.String(http.StatusOK, "full path: %s", context.FullPath())
}

func readEndpoint(context *gin.Context) {
log.Println(context.FullPath())
context.String(http.StatusOK, "hello read path: %s", context.FullPath())
}

func submitEndpoint(context *gin.Context) {
log.Println(context.FullPath())
context.String(http.StatusOK, "hello submit path: %s", context.FullPath())
}

func loginEndpoint(context *gin.Context) {
log.Println(context.FullPath())
context.String(http.StatusOK, "hello login path: %s", context.FullPath())
}

自定义Recovery行为

自定义中间件可以将错误信息保存到数据库或将其报告给用户

1
2
3
4
5
6
7
8
9
10
11
// gin v1.6.3 无该方法CustomRecovery
router.Use(gin.CustomRecovery(func(context *gin.Context, recovered interface{}) {
if err, ok := recovered.(string); ok {
context.String(http.StatusInternalServerError, fmt.Sprintf("error: %s", err))
}
context.AbortWithStatus(http.StatusInternalServerError)
}))

router.GET("/panic", func(c *gin.Context) {
panic("foo")
})

如何写日志文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 禁用控制台颜色,将日志些如文件时不需要控制台颜色
gin.DisableConsoleColor()

// 记录到文件
file, _ := os.Create("gin.log")
gin.DefaultWriter = io.MultiWriter(file)

// 如果你需要同时将日志写入到文件和控制台
//gin.DefaultWriter = io.MultiWriter(file, os.Stdout)

router := gin.Default()

router.GET("/ping", func(context *gin.Context) {
context.String(http.StatusOK, "pong")
})

router.Run()

自定义日志格式

LoggerWithFormatter 中间件会将日志写入到gin.DefaultWrite,默认gin.DefaultWrite = os.Stdout

1
2
3
4
5
6
7
8
9
10
11
12
13
router.Use(gin.LoggerWithFormatter(func(params gin.LogFormatterParams) string {
return fmt.Sprintf("%s - [%s] \"%s %s %s %d %s \"%s\" %s\"\n",
params.ClientIP,
params.TimeStamp.Format(time.RFC1123),
params.Method,
params.Path,
params.Request.Proto,
params.StatusCode,
params.Latency,
params.Request.UserAgent(),
params.ErrorMessage,
)
}))

Sample Output

1
::1 - [Mon, 05 Apr 2021 09:43:57 CST] "GET /get HTTP/1.1 200 32.729µs "PostmanRuntime/7.26.10" "

控制日志输出颜色

默认,控制台上输出的日志应根据检测到的TTY进行着色。

1
2
// 禁用控制台颜色
gin.DisableConsoleColor()
1
2
// 强制使用日志颜色。
gin.ForceConsoleColor()

模型绑定和验证

要将请求主体绑定到结构体,请使用模型绑定。目前支持JSON,XML和标准Form表单(foo=bar&boo=baz)

注意,你需要在绑定的所有字段上设置相应的绑定标签,例如:

1
`form:"fieldname" json:"fieldname" xml:"fieldname"`

此外,Gin提供了两种绑定的方法:

  • 类型 - Must bind
    • 方法 - BindBindJSONBindXMLBindQueryBindYAMLBindHeader
    • 行为 - 这些方法底层使用MustBindWith,如果存在绑定错误,请求将会被以下指令终止context.AbortWithError(400, err).SetType(ErrorTypeBind),响应状态码会被设置为400,请求头Content-Type被设置为text/plain; charset=utf-8。注意,如果你试图在此之后设置响应状态码,将会发出一个警告[GIN-debug] [WARNING] Headers were already written. Wanted to override status code 400 with 422。如果你希望更好的控制行为,请使用ShouldBind相关的方法。
  • 类型 - Should bind
    • 方法 - ShouldBindShouldBindJSONShouldBindXMLShouldBindQueryShouldBindYAMLShouldBindHeader
    • 行为 - 这些方法底层使用ShouldBindWith,如果存在绑定错误,则会返回错误,开发人员可以正确处理请求和错误。当我们使用绑定方法时,Gin会根据Content-Type推断使用哪种绑定器,如果你确定绑定的是什么,你可以使用MustBindWith或者BindWith

你可以给字段指定特定的规则修饰符,如果一个字段用binding:"required"修饰,并且绑定时该字段为空,那么将会返回一个错误。

Gin使用go-playground/validator/v10进行验证,查看完整validator文档

1
2
3
4
type Login struct {
User string `form:"user" json:"user" xml:"user" binding:"required"`
Password string `form:"password" json:"password" xml:"password" binding:"required"`
}

跳过校验: 使用binding:"-"将不会对其进行校验。

JSON:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
router.POST("/loginJSON", func(context *gin.Context) {
var login Login
if err := context.ShouldBindJSON(&login); err != nil{
context.JSON(http.StatusBadRequest, gin.H{
"error": err.Error(),
})
return
}
if login.User != "manu" || login.Password != "123" {
context.JSON(http.StatusUnauthorized, gin.H{
"status": "Unauthorized",
})
return
}
context.JSON(http.StatusOK, gin.H{
"message": "JSON login success",
})
})

请求示例:

1
2
3
4
{
"user": "man1u",
"password": "123"
}

XML:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
router.POST("/loginXML", func(context *gin.Context) {
var login Login
if err := context.ShouldBindXML(&login); err != nil {
context.XML(http.StatusBadRequest, gin.H{
"error": err.Error(),
})
return
}
if login.User != "manu" || login.Password != "123" {
context.XML(http.StatusUnauthorized, gin.H{
"status": "Unauthorized",
})
return
}
context.XML(http.StatusOK, gin.H{
"message": "XML login success",
})
})

请求示例:

1
2
3
4
5
<?xml version="1.0" encoding="UTF-8"?>
<root>
<user>manu</user>
<password>123</password>
</root>)

FORM表单:

1
2
3
4
5
6
7
8
9
10
11
12
router.POST("/loginFORM", func(context *gin.Context) {
var login Login
if err := context.ShouldBind(&login); err != nil {
context.String(http.StatusBadRequest, "error: %s", err.Error())
return
}
if login.User != "manu" || login.Password != "123" {
context.String(http.StatusUnauthorized, "status: Unauthorized")
return
}
context.String(http.StatusOK, "FORM login success")
})

请求示例:

1
?user=manu&password=123

自定义校验器

可以注册自定义校验器。请参阅示例代码

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
package main

import (
"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/binding"
"github.com/go-playground/validator/v10"
"net/http"
"time"
)

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,bookableDate" time_format:"2006-01-02"`
}

var bookableDate validator.Func = func(fl validator.FieldLevel) bool {
date,ok := fl.Field().Interface().(time.Time)
if ok {
if time.Now().After(date) {
return false
}
}
return true
}

func main() {
router := gin.Default()

validate, ok := binding.Validator.Engine().(*validator.Validate)
if ok {
validate.RegisterValidation("bookableDate", bookableDate)
}

router.GET("/booking", func(context *gin.Context) {
var booking Booking
err := context.ShouldBindWith(&booking, binding.Query)
if err != nil {
context.String(http.StatusBadRequest, "error: %s", err.Error())
return
}
context.String(http.StatusOK, "book success")
})

router.Run()
}

仅绑定查询字符串

ShouldBindQuery仅绑定查询参数,而不绑定post数据。查看 详细信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
type Person struct {
Name string `form:"name"`
Address string `form:"address"`
}

router.Any("/testing", func(context *gin.Context) {
var person Person
if context.ShouldBindQuery(&person) == nil {
log.Println("Only Bind By Query String...")
log.Println(person.Name)
log.Println(person.Address)
}
context.String(http.StatusOK, "Success")
})

绑定查询字符串或Post数据

ShouldBind 查看 详细信息

如果是GET请求,则仅使用Form绑定引擎query

如果是Post,请先检查content-type是否为JSONXML然后使用Form

1
2
3
4
5
6
7
type Person struct {
Name string `form:"name"`
Address string `form:"address"`
Birthday string `form:"birthday" time_format:"2006-01-02" time_utc:"1"`
CreateTime time.Time `form:"createTime" time_format:"unixNano"`
UnixTime time.Time `form:"unixTime" time_format:"unix"`
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
func main() {
router := gin.Default()

router.POST("/testing", func(context *gin.Context) {
var person Person
if context.ShouldBind(&person) == nil {
log.Println(person.Name)
log.Println(person.Address)
log.Println(person.Birthday)
log.Println(person.CreateTime)
log.Println(person.UnixTime)
}
context.String(http.StatusOK, "Success")
})

router.Run()
}

只绑定Uri

ShouldBindUri 查看详细信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
type Person struct {
Id string `uri:"id" binding:"required,uuid"`
Name string `uri:"name" binding:"required"`
}

router.GET("/:name/:id", func(context *gin.Context) {
var person Person

if err := context.ShouldBindUri(&person); err != nil {
context.JSON(http.StatusBadRequest, gin.H{
"message": err,
})
return
}
context.JSON(http.StatusOK, gin.H{"Id": person.Id, "Name": person.Name})
})

绑定Header

1
2
3
4
5
6
7
8
9
10
11
12
13
type testHeader struct {
Rate string `header:"rate"`
Domain string `header:"domain"`
}

router.GET("/", func(context *gin.Context) {
var header testHeader
if err := context.ShouldBindHeader(&header); err != nil {
context.JSON(http.StatusOK, err)
}
fmt.Printf("%v/n", header)
context.JSON(http.StatusOK, gin.H{"Rate": header.Rate, "Domain": header.Domain})
})
1
{"Domain":"music","Rate":300}

绑定HTML

1
2
3
4
5
6
7
8
9
10
11
12
type mYForm struct {
Colors []string `form:"colors[]"`
}

router.POST("/", func(context *gin.Context) {
var form mYForm
if err := context.ShouldBind(&form); err != nil{
context.JSON(http.StatusOK, gin.H{"message": err})
return
}
context.JSON(http.StatusOK, gin.H{"Colors": fmt.Sprintf("%v", form.Colors)})
})

示例:

1
2
3
4
5
6
7
8
9
10
<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>

Multipart/Urlencoded binding

ShouldBindWith(obj interface{}, b binding.Binding) 可以显式声明来绑定Multipart Form。

或者使用ShouldBind 进行自动绑定,他会自动选择适当的绑定方式进行绑定。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
router.POST("/upload", func(context *gin.Context) {
var profile profileForm
err := context.ShouldBind(&profile)
if err != nil {
context.String(http.StatusBadRequest, "bad Request")
return
}
err = context.SaveUploadedFile(profile.Avatar, profile.Name)
if err != nil {
context.String(http.StatusInternalServerError, "%v", err)
return
}
context.String(http.StatusOK, "ok")
})

请求示例:

1
curl -X POST -v --form name=user --form "avatar=@./avatar.png" http://localhost:8080/profile

安全的JSON

使用SecureJSON 避免JSON劫持。如果给定的结构是数组值,则默认在相应主体前添加while(1),