在 Gin
框架中下列方法可以用处理 URLQuery
参数:
// 返回指定名字参数的值,c.Params.ByName(key) 简写,
// 如: "/user/:id",则返回 id := c.Param("id") id == "john"
func (c *Context) Param(key string) string
// Query 返回 query 中指定参数的值,如不存在则返回""。
// c.Request.URL.Query().Get(key) 的简写,
// 如 GET /path?id=1234&name=Manu&value=,则 c.Query("id") == "1234"
func (c *Context) Query(key string) string
// DefaultQuery 返回 query 中指定参数的值,如不存在则返回指定的值 defaultValue。
// GET /?name=Manu&lastname=
// c.DefaultQuery("name", "unknown") == "Manu"
// c.DefaultQuery("id", "none") == "none"
// c.DefaultQuery("lastname", "none") == ""
func (c *Context) DefaultQuery(key, defaultValue string) string
// GetQuery 类似 Query() , 返回 query 中指定参数的值,如参数存在(即使值为"")
// 则返回 (value, true),不存在的参数则返回指定的值 ("", false)。
// c.Request.URL.Query().Get(key) 的简写
// GET /?name=Manu&lastname=
// ("Manu", true) == c.GetQuery("name")
// ("", false) == c.GetQuery("id")
// ("", true) == c.GetQuery("lastname")
func (c *Context) GetQuery(key string) (string, bool)
// 返回 URL 指定名字参数的字符串切片,切片的长度与指定参数的值多少有关
func (c *Context) QueryArray(key string) []string
// 返回 URL 指定名字参数的字符串切片与布尔值,值存在则为 true
func (c *Context) GetQueryArray(key string) ([]string, bool)
// 返回 URL 指定名字参数的字符串字典
func (c *Context) QueryMap(key string) map[string]string
// 返回 URL 指定名字参数的字符串字典与布尔值,值存在则为 true
func (c *Context) GetQueryMap(key string) (map[string]string, bool)
对于类似 /welcome?firstname=Jane&lastname=Doe
这样的 URL
, ?
后面为 Query
查询字符串参数,在 Gin
框架中有专门方法来处理这些参数,例如:
func main() {
router := gin.Default()
// 使用现有的基础请求对象解析查询字符串参数。
// 示例 URL: /welcome?firstname=Jane&lastname=Doe
router.GET("/welcome", func(c *gin.Context) {
firstname := c.DefaultQuery("firstname", "Guest")
lastname := c.Query("lastname") // c.Request.URL.Query().Get("lastname") 的快捷方式
name, _ := c.GetQuery("lastname")
c.String(http.StatusOK, "Hello %s %s %s", firstname, lastname, name)
})
router.Run(":8080")
}
程序运行在 Debug 模式时,通过浏览器访问
http://localhost:8080/welcome?firstname=Jane&lastname=Doe
上面是通过 Query
方式传递参数,在 Gin
框架中可以通过 Query()
、DefaultQuery()
、GetQuery()
等方法得到指定参数的值。
Query()
读取 URL
中的地址参数,例如
// GET /path?id=1234&name=Manu&value=
c.Query("id") == "1234"
c.Query("name") == "Manu"
c.Query("value") == ""
c.Query("wtf") == ""
DefaultQuery()
:类似 Query()
,但是如果 key
不存在,会返回默认值
//GET /?name=Manu&lastname=
c.DefaultQuery("name", "unknown") == "Manu"
c.DefaultQuery("id", "none") == "none"
c.DefaultQuery("lastname", "none") == ""
输出结果:
$ curl -X GET http://localhost:8080/welcome?firstname=wohu\&lastname='1104'
Hello wohu 1104
$ curl -X GET "http://localhost:8080/welcome?firstname=wohu&lastname=1104"
Hello wohu 1104
对于类似 /user/:firstname/:lastname
,:lastname
是 Gin
框架中路由参数的一种写法,表示 lastname
为任意的字符串,访问时使用具体值。
func main() {
router := gin.Default()
router.GET("/user/:firstname/:lastname", func(c *gin.Context) {
fname := c.Param("firstname")
lname := c.Param("lastname")
c.String(http.StatusOK, "Hello %s %s ", fname, lname)
})
router.Run(":8080")
}
Param()
方法能快速返回路由 URI
指定名字参数的值,它是 c.Params.ByName(key)
方法的简写。如路由定义为: "/user/:id"
,则返回 id := c.Param("id")
。
程序运行在 Debug 模式时,通过浏览器访问
http://localhost:8080/user/wohu/1104
输出:
hello wohu 1104
func main() {
router := gin.Default()
// This handler will match /user/john but will not match /user/ or /user
router.GET("/user/:name", func(c *gin.Context) {
name := c.Param("name") // name == "john"
c.String(http.StatusOK, "Hello %s", name)
})
// However, this one will match /user/john/ and also /user/john/send
// If no other routers match /user/john, it will redirect to /user/john/
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")
}
上面代码路由路径中带参数的方式有 :
和 *
两种,不同符号代表不同含义,通过 Param()
方法取得对应的字符串值。
:
表示参数值不为空,且不以 /
结尾;*
表示参数可为空,可为任意字符包括 /
;Param()
方法能快速返回路由 URI
指定名字参数的值,它是 c.Params.ByName(key)
方法的简写。如路由定义为: “/user/:id”,则返回 id := c.Param("id")
。
在 Gin
框架中 PostFormMap()
, QueryMap()
等方法在某些情况下非常有用,下面对参数映射到字典做了简单说明,
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")
}
查询
POST /post?ids[a]=1234&ids[b]=hello HTTP/1.1
Content-Type: application/x-www-form-urlencoded
names[first]=thinkerou&names[second]=tianou
curl -H "Content-Type:application/x-www-form-urlencoded" -X POST -d "names[first]=thinkerou&names[second]=tianou" -g "http://localhost:8080/post?ids[a]=1234&ids[b]=hello"
输出:
ids: map[b:hello a:1234], names: map[second:tianou first:thinkerou]
Bind()
:将消息体作为指定的格式解析到 Go struct
变量中。而绑定(Binding
)是通过一系列方法可以将请求体中参数自动绑定到自定义的结构体中,从而可以简单快速地得到对应的参数值。
package main
import "github.com/gin-gonic/gin"
type Person struct {
ID string `uri:"id" binding:"required,uuid"`
Name string `uri:"name" binding:"required"`
}
func main() {
r := gin.Default()
r.GET("/:name/:id", func(c *gin.Context) {
var person Person
if err := c.ShouldBindUri(&person); err != nil {
c.JSON(400, gin.H{"msg": err})
return
}
c.JSON(200, gin.H{"name": person.Name, "uuid": person.ID})
})
r.Run(":8080")
}
gin.H
可当做为字典类型,在 utils.go
文件中定义如下:
type H map[string]interface{}
输出结果:
$ curl localhost:8080/wohu/987fbc97-4bed-5078-9f07-9141ba07c9f3
{"name":"wohu","uuid":"987fbc97-4bed-5078-9f07-9141ba07c9f3"}
$ curl localhost:8080/wohu/uuid
{"msg":[{}]}
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 Person
if c.ShouldBindQuery(&person) == nil {
log.Println("====== Only Bind By Query String ======")
log.Println(person.Name)
log.Println(person.Address)
}
c.String(200, "Success")
}
package main
import (
"github.com/gin-gonic/gin"
)
type StructA struct {
FieldA string `form:"field_a"`
}
type StructB struct {
StructAValue StructA
FieldB string `form:"field_b"`
}
type StructC struct {
StructAPointer *StructA
FieldC string `form:"field_c"`
}
type StructD struct {
AnonyStruct struct {
FieldX string `form:"field_x"`
}
FieldD string `form:"field_d"`
}
func GetDataB(c *gin.Context) {
var b StructB
c.Bind(&b)
c.JSON(200, gin.H{
"a": b.StructAValue,
"b": b.FieldB,
})
}
func GetDataC(c *gin.Context) {
var cStruct StructC
c.Bind(&cStruct)
c.JSON(200, gin.H{
"a": cStruct.StructAPointer,
"c": cStruct.FieldC,
})
}
func GetDataD(c *gin.Context) {
var d StructD
c.Bind(&d)
c.JSON(200, gin.H{
"x": d.AnonyStruct,
"d": d.FieldD,
})
}
func main() {
r := gin.Default()
r.GET("/getb", GetDataB)
r.GET("/getc", GetDataC)
r.GET("/getd", GetDataD)
r.Run()
}
输出结果:
$ 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"}}
package main
import (
"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() {
r := gin.Default()
r.GET("/testing", startPage)
r.Run()
}
func startPage(c *gin.Context) {
var person Person
// If `GET`, only `Form` binding engine (`query`) used.
// If `POST`, first checks the `content-type` for `JSON` or `XML`, then uses `Form` (`form-data`).
// See more at https://github.com/gin-gonic/gin/blob/master/binding/binding.go#L48
if c.ShouldBind(&person) == nil {
log.Println(person.Name)
log.Println(person.Address)
log.Println(person.Birthday)
}
c.String(200, "Success")
}
输出结果:
$ curl -X GET "localhost:8080/testing?name=wohu&address=city&birthday=1992-03-15"
Success
绑定请求体的使用方法 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{}
// This c.ShouldBind consumes c.Request.Body and it cannot be reused.
if errA := c.ShouldBind(&objA); errA == nil {
c.String(http.StatusOK, `the body should be formA`)
// Always an error is occurred by this because c.Request.Body is EOF now.
} 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{}
// This reads c.Request.Body and stores the result into the context.
if errA := c.ShouldBindBodyWith(&objA, binding.JSON); errA == nil {
c.String(http.StatusOK, `the body should be formA`)
// At this time, it reuses body stored in the context.
} else if errB := c.ShouldBindBodyWith(&objB, binding.JSON); errB == nil {
c.String(http.StatusOK, `the body should be formB JSON`)
// And it can accepts other formats
} 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()
多次调用而不会对性能造成任何损害。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) {
// you can bind multipart form with explicit binding declaration:
// c.ShouldBindWith(&form, binding.Form)
// or you can simply use autobinding with ShouldBind method:
var form LoginForm
// in this case proper binding will be automatically selected
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 --form user=user --form password=password http://localhost:8080/login
{"status":"you are logged in"}
表单提交方法为 POST
时,enctype
属性为 application/x-www-form-urlencoded
或 multipart/form-data
的差异:
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")
}
输出结果:
可以看到在简单的键值对传递时,属性为 application/x-www-form-urlencoded
或 multipart/form-data
基本不存在差异。都能正常返回 JSON
:
curl -H "Content-Type:multipart/form-data" -X POST -d "nick=manu&message=this_is_great" "http://localhost:8080/form_post"
curl -H "Content-Type:application/x-www-form-urlencoded" -X POST -d "nick=manu&message=this_is_great" "http://localhost:8080/form_post"
$ curl -X POST --form message=message --form nick=nick http://localhost:8080/form_post
{"message":"message","nick":"nick","status":"posted"}
在 <form>
中,enctype
属性规定当表单数据提交到服务器时如何编码(仅适用于 method="post"
的表单)。formenctype
属性是 HTML5
中的新属性,formenctype
属性覆盖 <form>
元素的 enctype
属性。
常用有两种:application/x-www-form-urlencoded
和 multipart/form-data
,默认为 application/x-www-form-urlencoded
。
当表单提交方法为 GET
时,浏览器用 x-www-form-urlencoded
的编码方式把表单数据转换成一个字串(name1=value1&name2=value2...
),然后把这个字串追加到 URL
后面。
当表单提交方法为 POST
时,浏览器把表单数据封装到请求体中,然后发送到服务端。如果此时 enctype
属性为 application/x-www-form-urlencoded
,则请求体是简单的键值对连接,格式如下:k1=v1&k2=v2&k3=v3
。而如果此时 enctype
属性为 multipart/form-data
,则请求体则是添加了分隔符、参数描述信息等内容。
enctype
属性表
属性值 | 说明 |
---|---|
application/x-www-form-urlencoded | 数据被编码为名称/值对,这是默认的编码格式 |
multipart/form-data | 数据被编码为一条消息,每个控件对应消息中的一个部分 |
text/plain | 数据以纯文本形式进行编码,其中不含任何控件或格式字符 |
在 Gin
框架中下列方法可以用处理表单数据:
// PostForm 从特定的 urlencoded 表单或 multipart 表单返回特定参数的值,不存在则为空""
func (c *Context) PostForm(key string) string
// DefaultPostForm 从特定的 urlencoded 表单或 multipart 表单返回特定参数的值,
// 不存在则返回指定的值
func (c *Context) DefaultPostForm(key, defaultValue string) string
// GetPostForm 类似 PostForm(key).从特定的 urlencoded 表单或 multipart 表单返回特定参数的值,
// 如参数存在(即使值为"")则返回 (value, true),不存在的参数则返回指定的值 ("", false)。
// 例如:
// email=mail@example.com --> ("mail@example.com", true) := GetPostForm("email")
// email 为 "mail@example.com"
// email= --> ("", true) := GetPostForm("email") // email 值为 ""
// --> ("", false) := GetPostForm("email") // email 不存在
func (c *Context) GetPostForm(key string) (string, bool)
// 从特定的 urlencoded 表单或 multipart 表单返回特定参数的字符串切片,
// 切片的长度与指定参数的值多少有关
func (c *Context) PostFormArray(key string) []string
//
func (c *Context) getFormCache()
// 从特定的 urlencoded 表单或 multipart 表单返回特定参数的字符串切片,
// 至少一个值存在则布尔值为true
func (c *Context) GetPostFormArray(key string) ([]string, bool)
// 从特定的 urlencoded 表单或 multipart 表单返回特定参数的字符串字典
func (c *Context) PostFormMap(key string) map[string]string
// 从特定的 urlencoded 表单或 multipart 表单返回特定参数的字符串字典,
// 至少一个值存在则布尔值为true
func (c *Context) GetPostFormMap(key string) (map[string]string, bool)
// 返回表单指定参数的第一个文件
func (c *Context) FormFile(name string) (*multipart.FileHeader, error)
// 分析multipart表单,包括文件上传
func (c *Context) MultipartForm() (*multipart.Form, error)
// 将表单文件上传到特定dst
func (c *Context) SaveUploadedFile(file *multipart.FileHeader, dst string) error
参数和表单混合处理
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")
}
下面是请求 Request
的头信息,分为四部分,请求行,请求头,空行,请求体:输入输出
POST /post?id=1234&page=1 HTTP/1.1
Content-Type: application/x-www-form-urlencoded
name=manu&message=this_is_great
curl -H "Content-Type:application/x-www-form-urlencoded" -X POST -d "name=manu&message=this_is_great" "http://localhost:8080/post?id=1234&page=1"
id: 1234; page: 1; name: manu; message: this_is_great
参考:
https://github.com/gin-gonic/gin
https://github.com/gin-gonic/examples
https://gin-gonic.com/docs/introduction/
https://www.jianshu.com/p/a31e4ee25305
https://blog.csdn.net/u014361775/article/details/80582910
https://learnku.com/docs/gin-gonic/1.7/examples-ascii-json/11362