1、一个简单的Gin示例
下载并安装Gin:
go get -u github.com/gin-gonic/gin
1.1 一个简单的例子
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
// 创建一个默认的路由引擎
r := gin.Default()
// 当客户端以GET方式访问 /hello 时,会执行后面的匿名函数,返回Hello World
r.GET("/hello", func(ctx *gin.Context) {
ctx.String(http.StatusOK, "Hello World")
})
r.GET("/json", func(ctx *gin.Context) {
ctx.JSON(http.StatusOK, gin.H{
"message": "Hello World",
})
})
// 在8080端口监听
r.Run(":8080")
}
使用浏览器打开127.0.0.1:8080/hello就能看到一串字符串,打开127.0.0.1:8080/json就能看到一串JSON字符串。
1.2 RESTful API
Gin 如何处理 GET, POST, PUT, PATCH, DELETE 和 OPTIONS
请求
Gin 处理各种 HTTP 请求是非常简单的,只要使用 router.XXX
方法即可注册处理器。其中 XXX 是 HTTP 请求方法的大写模式。
也就是说,Gin框架支持开发RESTful API的开发。
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
// 创建一个默认的路由引擎
r := gin.Default()
//
r.GET("/book", func(ctx *gin.Context) {
ctx.JSON(http.StatusOK, gin.H{
"message": "GET",
})
})
r.POST("/book", func(ctx *gin.Context) {
ctx.JSON(http.StatusOK, gin.H{
"message": "POST",
})
})
r.PUT("/book", func(ctx *gin.Context) {
ctx.JSON(http.StatusOK, gin.H{
"message": "PUT",
})
})
r.DELETE("/book", func(ctx *gin.Context) {
ctx.JSON(http.StatusOK, gin.H{
"message": "DELETE",
})
})
// 在8080端口监听
r.Run(":8080")
}
开发RESTful API的时候我们通常使用Postman来作为客户端的测试工具。
2、获取参数
2.1、如何获取 GET 查询字符串参数?
查询字符串参数就是 URL 中 ?
后面 #
之前的参数,比如下面的 URL,
https://www.xxxxx.cn/user/search?username=金克斯&address=艾欧尼亚#reply0
查询字符串参数特指 username=金克斯&address=艾欧尼亚
Gin 的 Handler 提供了 以下几种方式
方法 | 说明 |
---|---|
ctx.Query() | 获取查询参数,如果参数不存在或值为空则返回空字符串 “” |
ctx.DefaultQuery() | 获取查询参数,如果参数不存在或值为空则返回第二个参数做为值 |
ctx.GetQuery() | 类似于 c.Query(),但同时返回第二个 bool 参数用于判断该参数到底存不存在 |
- ctx.Query()
ctx.Query("username") // 返回"金克斯"
ctx.Query("address") // 返回"艾欧尼亚"
ctx.Query("gender") // 返回""
- ctx.DefaultQuery()
ctx.DefaultQuery("username", "none") // 返回"金克斯"
ctx.DefaultQuery("address", "none") // 返回"艾欧尼亚"
ctx.DefaultQuery("gender", "none") // 返回"none"
- ctx.GetQuery()
ctx.GetQuery("username") // 返回("金克斯", true)
ctx.GetQuery("address") // 返回("艾欧尼亚", true)
ctx.GetQuery("gender") // 返回("", false)
例子
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
// 创建一个默认的路由引擎
router := gin.Default()
// 解析/user/search?username=金克斯&address=艾欧尼亚#reply0
router.GET("/user/search", func(ctx *gin.Context) {
username := ctx.Query("username")
address, _ := ctx.GetQuery("address")
gender := ctx.DefaultQuery("gender", "none")
ctx.JSON(http.StatusOK, gin.H{
"username": username,
"address": address,
"gender": gender,
})
})
// 在8080端口监听
router.Run(":8080")
}
浏览器返回http://127.0.0.1:8080/user/search?username=金克斯&address=艾欧尼亚#reply0
,返回
{"address":"艾欧尼亚","gender":"none","username":"金克斯"}
2.2、如何获取 POST 表单form数据
Gin 提供了三个类似的方法用于获取 POST 请求提交的参数
方法 | 说明 |
---|---|
ctx.PostForm() | 获取 POST 表单参数,如果参数不存在或值为空则返回空字符串 “” |
ctx.DefaultPostForm() | 获取 POST 表单参数,如果参数不存在或值为空则返回第二个参数做为值 |
ctx.GetPostForm() | 类似于 c.PostForm(),但同时返回第二个 bool 参数用于判断该参数到底存不存在 |
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
// 创建一个默认的路由引擎
router := gin.Default()
// 解析/user/search?username=金克斯&address=艾欧尼亚#reply0
router.POST("/user/search", func(ctx *gin.Context) {
username := ctx.PostForm("username")
address, _ := ctx.GetPostForm("address")
gender := ctx.DefaultPostForm("gender", "none")
ctx.JSON(http.StatusOK, gin.H{
"username": username,
"address": address,
"gender": gender,
})
})
// 在8080端口监听
router.Run(":8080")
}
使用postman发送表单请求
2.3、如何同时获取 GET 数据和 POST 数据
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
router := gin.Default()
router.POST("user/search", func(ctx *gin.Context) {
query_name := ctx.Query("name")
query_address := ctx.Query("address")
post_name := ctx.PostForm("name")
post_address := ctx.PostForm("address")
ctx.JSON(http.StatusOK, gin.H{
"query_name": query_name,
"query_address": query_address,
"post_name": post_name,
"post_address": post_address,
})
})
router.Run(":8080")
}
2.4、如何获取路由参数
在没有路由参数之前,如果我们想获取/user/1
和/user/2
的用户数据,我们需要注册多个路由器
router.GET("/user/1", func(c *gin.Context) {})
router.GET("/user/2", func(c *gin.Context) {})
如果用户量大了,这个注册显然不靠谱。
Gin 允许我们使用 :[参数名]
或 *[参数名]
来注册一个路由参数,比如上面的用户详情就可以注册为 /user/:user_id
。然后我们就可以在路由 Handler 中通过 c.Param("user_id")
获取到这个参数。
:[参数名]
:路由参数能够匹配任何字符串,除了路径分隔符/
。 也就是说/user/:user_id
可以匹配/user/1
但不能匹配/user/1/message
router.GET("/user/:name", func(ctx *gin.Context) {
name := ctx.Param("name")
ctx.JSON(http.StatusOK, gin.H{
"message": name,
})
})
/*
127.0.0.1:8080/user/金克斯, 返回 {"message":"金克斯"}
127.0.0.1:8080/user/金克斯/艾欧尼亚, 返回 404 page not found
*/
*[参数名]
:路由参数可以匹配任意字符,包括/
,也就是说/user/:user_id
可以匹配/user/1
也可以能匹配/user/1/message
,还能匹配/user/1/message/a/b/c/d/e
router.GET("/user/:name/*address", func(ctx *gin.Context) {
name := ctx.Param("name")
address := ctx.Param("address")
ctx.JSON(http.StatusOK, gin.H{
"message": name,
"address": address,
})
})
/*
127.0.0.1:8080/user/金克斯, 返回 {"address":"/","message":"金克斯"}
127.0.0.1:8080/user/金克斯/艾欧尼亚, 返回 {"address":"/艾欧尼亚","message":"金克斯"}
*/
Gin 的路由定义遵循几个规范
- 和访问路径一样的路由定义(精确路由) 将会被优先匹配。 比如
router.GET("/user/:name", func(ctx *gin.Context) {
name := ctx.Param("name")
ctx.JSON(http.StatusOK, gin.H{
"message": name,
})
})
// 精确路由(相对于有路由参数的路由)会被优先匹配,而无论他们在哪里定义
router.GET("/user/groups", func(c *gin.Context) {
c.String(http.StatusOK, "The available groups are [...]")
})
- 默认情况下,如果先定义的路由匹配了,那么后续定义的路由就不会被匹配。
2.5、如何接受表单中的字典(Map)参数
Gin 提供了 ctx.QueryMap()
用于获取字典形式的查询字符串参数,提供了ctx.PostFormMap()
用于获取字典形式的 POST 表单参数。
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
router := gin.Default()
router.POST("/post", func(ctx *gin.Context) {
ids := ctx.QueryMap("ids")
names := ctx.PostFormMap("names")
ctx.JSON(http.StatusOK, gin.H{
"ids": ids,
"names": names,
})
})
router.Run(":8080")
}
2.6、如何对请求参数模型绑定和验证
为了能够更方便的获取请求相关参数,提高开发效率,我们可以基于请求的Content-Type识别请求数据类型并利用反射机制自动提取请求中QueryString
、form表单
、JSON
、XML
等参数到结构体中。
原理都是一样的: 需要在要绑定的所有字段上,设置相应的 tag。例如,使用 JSON 绑定时,设置字段标签为 json:"fieldname"
Gin 框架提供了两类绑定方法:
-
第一类: 必须绑定 (Must Bind),BindXXX() 方法,如果绑定出错则会直接抛出 400 错误:
方法有: Bind(), BindJSON(), BindXML(), BindQuery(), BindYAML(), BindHeader(), BindTOML()。 -
第二类: 应该绑定 (Should bind),ShouldBindXXX() 等方法,如果绑定出错则会抛出异常
方法有: ShouldBind(), ShouldBindJSON(), ShouldBindXML(), ShouldBindQuery(), ShouldBindYAML(), ShouldBindHeader(), ShouldBindTOML(),
这些方法具体的实现调用了 ShouldBindWith()。 如果发生绑定错误,Gin 会返回错误并由开发者处理错误和请求。
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
type Login struct {
Username string `form:"username" json:"username" binding:"required"`
Password string `form:"password" json:"password" binding:"required"`
}
func main() {
router := gin.Default()
// 绑定JSON的示例 ({"username": "admin", "password": "123456"})
router.POST("/loginJson", func(ctx *gin.Context) {
var login Login
// ShouldBind()会根据请求的Content-Type自行选择绑定器
if err := ctx.ShouldBind(&login); err != nil {
ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if login.Username == "admin" && login.Password == "123456" {
ctx.JSON(http.StatusOK, gin.H{
"status": "you are logged in",
"username": login.Username,
"password": login.Password,
})
} else {
ctx.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
}
})
// 绑定form表单示例
router.POST("/loginForm", func(ctx *gin.Context) {
var login Login
// ShouldBind()会根据请求的Content-Type自行选择绑定器
if err := ctx.ShouldBind(&login); err != nil {
ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if login.Username == "admin" && login.Password == "123456" {
ctx.JSON(http.StatusOK, gin.H{
"status": "you are logged in",
"username": login.Username,
"password": login.Password,
})
} else {
ctx.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
}
})
// 绑定QueryString示例
router.GET("loginQuery", func(ctx *gin.Context) {
var login Login
// ShouldBind()会根据请求的Content-Type自行选择绑定器
if err := ctx.ShouldBind(&login); err != nil {
ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if login.Username == "admin" && login.Password == "123456" {
ctx.JSON(http.StatusOK, gin.H{
"status": "you are logged in",
"username": login.Username,
"password": login.Password,
})
} else {
ctx.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
}
})
router.Run(":8080")
}
绑定JSON
绑定form表单
绑定QueryString
需要注意的是,如果没有输入password,是会报错的。是因为tag中的binding:"required"
进行了限制。如果将 Password 改为 binding:"-"
, 再次运行上面的示例就不会返回错误。
Password string `form:"password" json:"password" binding:"required"`
参考资料
李文周的博客
Gin 框架中文文档