Gin 是 Go语言写的一个 web 框架,它具有运行速度快,分组的路由器,良好的崩溃捕获和错误处理,非常好的支持中间件和 json,在go web领域使用广泛。【本文来自凡蜕博客】
快速开始
go get -u github.com/gin-gonic/gin
package main
import "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(":8080") // 可以用 http.ListenAndServe(":8080", router) 替代
}
自定义http服务器配置:
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()
}
路由
func main() {
r := gin.Default() //返回已添加Logger和Recovery中间件的Engine实例。
r.GET("/", func(c *gin.Context) { //r.GET 是 router.Handle("GET", path, handle) 的快捷方式。
c.String(http.StatusOK, "hello word")
})
r.GET("/someGet", getting)
r.POST("/somePost", posting)
r.PUT("/somePut", putting)
r.DELETE("/someDelete", deleting)
r.PATCH("/somePatch", patching)
r.HEAD("/someHead", head)
r.OPTIONS("/someOptions", options)
r.Run(":8000")
}
路由组
func main() {
router := gin.Default()
v1 := router.Group("/v1") //前缀都是v1
{
v1.GET("/login", loginEndpoint)
v1.GET("/submit", submitEndpoint)
v1.POST("/read", readEndpoint)
}
v2 := router.Group("/v2")
{
v2.POST("/login", loginEndpoint)
v2.POST("/submit", submitEndpoint)
v2.POST("/read", readEndpoint)
}
router.Run(":8080")
}
func loginEndpoint(c *gin.Context) {
name := c.DefaultQuery("name", "Guest") //可设置默认值
c.String(http.StatusOK, fmt.Sprintf("Hello %s \n", name))
}
func submitEndpoint(c *gin.Context) {
name := c.DefaultQuery("name", "Guest") //可设置默认值
c.String(http.StatusOK, fmt.Sprintf("Hello %s \n", name))
}
func readEndpoint(c *gin.Context) {
name := c.DefaultQuery("name", "Guest") //可设置默认值
c.String(http.StatusOK, fmt.Sprintf("Hello %s \n", name))
}
路由拆分
获取参数
用于需要单独获取参数来操作。
获取API参数
-
冒号
:
加上一个参数名组成路由参数,可以使用c.Params的方法读取其值 -
除了
:
,gin还提供了*
号处理参数,*
号能匹配的规则就更多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) })
获取URL参数
-
URL参数可以通过
DefaultQuery
()或Query
()方法获取 -
DefaultQuery()若参数不存在,返回指定的默认值;Query()若不存在,返回空字符串串""
func main() { r := gin.Default() r.GET("/user", func(c *gin.Context) { name := c.DefaultQuery("name", "枯藤") c.String(http.StatusOK, fmt.Sprintf("hello %s", name)) }) r.Run() }
获取表单参数
-
表单传输为post请求,http常见的传输格式为四种:
- application/
json
- application/
x-www-form-urlencoded
,form无非就是把query string的内容,放到了body体里,同样也需要urlencode - application/
xml
- multipart/
form-data
,用于文件上传
- application/
-
表单参数可以通过
PostForm
()方法获取(DefaultPostForm
()能指定默认值),默认情况下,c.PostFROM解析的是x-www-form-urlencoded
或from-data
的参数。func main() { r := gin.Default() r.POST("/form", func(c *gin.Context) { types := c.DefaultPostForm("type", "post") // 可设置默认值 username := c.PostForm("username") password := c.PostForm("password") c.String(http.StatusOK, fmt.Sprintf("username:%s,password:%s,type:%s", username, password, types)) }) r.Run() }
绑定数据
直接将前端的数据绑定到结构体。
【本文来自凡蜕博客】
Gin提供了两套绑定方法:
-
Should bind(推荐)
-
方法:
ShouldBind
,ShouldBindJSON
,ShouldBindXML
,ShouldBindQuery
,ShouldBindYAML
-
行为:这些方法使用ShouldBindWith。如果存在绑定错误,则返回错误,开发人员有责任适当地处理请求和错误。
-
-
Must bind(不推荐,别看)
- 方法:
Bind
,BindJSON
,BindXML
,BindQuery
,BindYAML
- 行为:这些方法使用MustBindWith。如果存在绑定错误,则用c终止请求,使用
c.AbortWithError (400) .SetType (ErrorTypeBind)
即可。将响应状态代码设置为400,Content-Type header设置为text/plain;charset = utf - 8
。请注意,如果在此之后设置响应代码,将会受到警告:[GIN-debug][WARNING] Headers were already written. Wanted to override status code 400 with 422
将导致已经编写了警告[GIN-debug][warning]标头。如果想更好地控制行为,可以考虑使用ShouldBind等效方法。
- 方法:
注意,使用绑定方法时,Gin 会根据请求头中 Content-Type 来自动判断需要解析的类型。如果你明确绑定的类型,你可以不用自动推断,而用 BindWith 方法。 你也可以指定某字段是必需的。如果一个字段被 binding:"required"
修饰而值却是空的,请求会失败并返回错误。
JSON绑定
将前端post过来的json数据绑定到结构体里。Content-Type是 application/json
type Login struct {
User string `form:"username" json:"user" uri:"user" xml:"user" binding:"required"`
Password string `form:"password" json:"password" uri:"password" xml:"password" binding:"required"`
}
func main() {
router := gin.Default()
// 示例 JSON ({"user": "hanru", "password": "hanru123"})
router.POST("/loginJSON", func(c *gin.Context) {
var login Login
// 其实就是将request中的Body中的数据按照JSON格式解析到json变量中
if err := c.ShouldBindJSON(&login); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if login.User != "hanru" || login.Password != "hanru123" {
c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
return
}
c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})
})
router.Run(":8080")
}
对于嵌套json的实现,嵌套gin.H即可
Form绑定
将前端post过来的form数据绑定到结构体里
type Login struct {
User string `form:"username" json:"user" uri:"user" xml:"user" binding:"required"`
Password string `form:"password" json:"password" uri:"password" xml:"password" binding:"required"`
}
router.POST("/loginForm", func(c *gin.Context) {
var form Login
// 方法一:对于FORM数据直接使用Bind函数, 默认使用使用form格式解析,if c.Bind(&form) == nil
// 根据请求头中 content-type 自动推断.
if err := c.Bind(&form); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if form.User != "hanru" || form.Password != "hanru123" {
c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
return
}
c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})
//方法二: 使用BindWith函数,如果你明确知道数据的类型
// 你可以显式声明来绑定多媒体表单:
// c.BindWith(&form, binding.Form)
// 或者使用自动推断:
if c.BindWith(&form, binding.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"})
}
}
})
URL绑定
将url里的参数绑定到结构体里
// 定义接收数据的结构体
type Login struct {
// binding:"required"修饰的字段,若接收为空值,则报错,是必须字段
User string `form:"username" json:"user" uri:"user" xml:"user" binding:"required"`
Pssword string `form:"password" json:"password" uri:"password" xml:"password" binding:"required"`
}
func main() {
// 1.创建路由
// 默认使用了2个中间件Logger(), Recovery()
r := gin.Default()
// JSON绑定
r.GET("/:user/:password", func(c *gin.Context) {
// 声明接收的变量
var login Login
// Bind()默认解析并绑定form格式
// 根据请求头中content-type自动推断
if err := c.ShouldBindUri(&login); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
// 判断用户名密码是否正确
if login.User != "root" || login.Pssword != "admin" {
c.JSON(http.StatusBadRequest, gin.H{"status": "304"})
return
}
c.JSON(http.StatusOK, gin.H{"status": "200"})
})
r.Run(":8000")
}
响应
通常响应会有html,text,plain,json和xml等
json、结构体响应、XML、YAML响应、protobuf格式响应:
// 多种响应方式
func main() {
// 1.创建路由
// 默认使用了2个中间件Logger(), Recovery()
r := gin.Default()
// 1.json
r.GET("/someJSON", func(c *gin.Context) {
c.JSON(200, gin.H{"message": "someJSON", "status": 200})
})
// 2. 结构体响应
r.GET("/someStruct", func(c *gin.Context) {
var msg struct {
Name string
Message string
Number int
}
msg.Name = "root"
msg.Message = "message"
msg.Number = 123
c.JSON(200, msg)
})
// 3.XML
r.GET("/someXML", func(c *gin.Context) {
c.XML(200, gin.H{"message": "abc"})
})
// 4.YAML响应
r.GET("/someYAML", func(c *gin.Context) {
c.YAML(200, gin.H{"name": "zhangsan"})
})
// 5.protobuf格式,谷歌开发的高效存储读取的工具
// 数组?切片?如果自己构建一个传输格式,应该是什么格式?
r.GET("/someProtoBuf", func(c *gin.Context) {
reps := []int64{int64(1), int64(2)}
// 定义数据
label := "label"
// 传protobuf格式数据
data := &protoexample.Test{
Label: &label,
Reps: reps,
}
c.ProtoBuf(200, data)
})
r.Run(":8080")
}
模板渲染
func main() {
router := gin.Default()
//加载模板。先要使用 LoadHTMLGlob() 或者 LoadHTMLFiles()方法
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")
}
不同文件夹下模板名字可以相同,此时需要 LoadHTMLGlob() 加载两层模板路径。
router.LoadHTMLGlob("templates/**/*")
router.GET("/posts/index", func(c *gin.Context) {
c.HTML(http.StatusOK, "posts/index.tmpl", gin.H{
"title": "Posts",
})
c.HTML(http.StatusOK, "users/index.tmpl", gin.H{
"title": "Users",
})
})
文件响应
func main() {
router := gin.Default()
// 下面测试静态文件服务
// 显示当前文件夹下的所有文件/或者指定文件
router.StaticFS("/showDir", http.Dir("."))
router.StaticFS("/files", http.Dir("/bin"))
//Static提供给定文件系统根目录中的文件。
//router.Static("/files", "/bin")
router.StaticFile("/image", "./assets/miao.jpg")
router.Run(":8080")
}
重定向
func main() {
r := gin.Default()
r.GET("/redirect", func(c *gin.Context) {
//支持内部和外部的重定向
c.Redirect(http.StatusMovedPermanently, "http://www.baidu.com/")
})
r.Run(":8080")
}
同步异步
- goroutine机制可以方便地实现异步处理
- 在启动新的goroutine时,不应该使用原始上下文,必须使用它的只读副本
func main() {
// 1.创建路由
// 默认使用了2个中间件Logger(), Recovery()
r := gin.Default()
// 1.异步
r.GET("/long_async", func(c *gin.Context) {
// 需要搞一个副本
copyContext := c.Copy()
// 异步处理
go func() {
time.Sleep(3 * time.Second)
log.Println("异步执行:" + copyContext.Request.URL.Path)
}()
})
// 2.同步
r.GET("/long_sync", func(c *gin.Context) {
time.Sleep(3 * time.Second)
log.Println("同步执行:" + c.Request.URL.Path)
})
r.Run(":8000")
}