Go Web框架Gin学习
Go Web框架Gin学习
一、Gin入门
1. 介绍
Gin号称是运行速度最快,特性最全的Goweb框架。
- Gin是一个golang的微框架,封装比较优雅,API友好,源码注释比较明确,具有快速灵活,容错方便等特点
- 对于golang而言,web框架的依赖要远比Python,Java之类的要小。自身的net/http足够简单,性能也非常不错
- 借助框架开发,不仅可以省去很多常用的封装带来的时间,也有助于团队的编码风格和形成规范
2. 特性
Gin v1 稳定的特性:
- 零分配路由。
- 仍然是最快的 http 路由器和框架。
- 完整的单元测试支持。
- 实战考验。
- API 冻结,新版本的发布不会破坏你的代码。
3. 安装
go get -u github.com/
gin-gonic/gin
4. helloworld示例
1package main
2
3import (
4 "net/http"
5
6 "github.com/gin-gonic/gin"
7)
8
9// gin的helloWorld
10
11func main() {
12 r := gin.Default()
13 r.GET("/", func(c *gin.Context) {
14 c.String(http.StatusOK, "hello World!")
15 })
16 r.Run()
17}
运行结果
1curl http://localhost:8080
2hello World!
二、Gin路由
1. 基本路由
-
gin 框架中采用的路由库是基于httprouter做的
-
地址为:https://github.com/julienschmidt/httprouter
1package main
2
3import (
4 "net/http"
5
6 "github.com/gin-gonic/gin"
7)
8
9func main() {
10 // Disable Console Color
11 // gin.DisableConsoleColor()
12
13 // 使用默认中间件创建一个gin路由器
14 // logger and recovery (crash-free) 中间件
15 router := gin.Default()
16
17 router.GET("/someGet", getting)
18 router.POST("/somePost", posting)
19 router.PUT("/somePut", putting)
20 router.DELETE("/someDelete", deleting)
21 router.PATCH("/somePatch", patching)
22 router.HEAD("/someHead", head)
23 router.OPTIONS("/someOptions", options)
24
25 // 默认启动的是 8080端口,也可以自己定义启动端口
26 router.Run()
27 // router.Run(":3000") for a hard coded port
28}
2. Restful风格的API
-
gin支持Restful风格的API
-
即Representational State Transfer的缩写。直接翻译的意思是"表现层状态转化",是一种互联网应用程序的API设计理念:URL定位资源,用HTTP描述操作
1.获取文章 /blog/getXxx Get blog/Xxx
2.添加 /blog/addXxx POST blog/Xxx
3.修改 /blog/updateXxx PUT blog/Xxx
4.删除 /blog/delXxxx DELETE blog/Xxx
3. API参数
-
可以通过Context的Param方法来获取API参数
-
localhost:8000/xxx/zhangsan
1package main
2
3import (
4 "net/http"
5
6 "github.com/gin-gonic/gin"
7)
8
9func main() {
10 router := gin.Default()
11
12 // 此规则能够匹配/user/john这种格式,但不能匹配/user/ 或 /user这种格式
13 router.GET("/user/:name", func(c *gin.Context) {
14 name := c.Param("name")
15 c.String(http.StatusOK, "Hello %s", name)
16 })
17
18 // 但是,这个规则既能匹配/user/john/格式也能匹配/user/john/send这种格式
19 // 如果没有其他路由器匹配/user/john,它将重定向到/user/john/
20 router.GET("/user/:name/*action", func(c *gin.Context) {
21 name := c.Param("name")
22 action := c.Param("action")
23 message := name + " is " + action
24 c.String(http.StatusOK, message)
25 })
26
27 router.Run(":8080")
28}
输出结果
4. 获取GET参数
-
URL参数可以通过DefaultQuery()或Query()方法获取
-
DefaultQuery()若参数不村则,返回默认值,Query()若不存在,返回空串
-
API ?name=zs
1package main
2
3import(
4 "net/http"
5 "github.com/gin-gonic/gin"
6)
7
8func main() {
9 router := gin.Default()
10
11 // 匹配的url格式: /welcome?firstname=Jane&lastname=Doe
12 router.GET("/welcome", func(c *gin.Context) {
13 firstname := c.DefaultQuery("firstname", "Guest")
14 lastname := c.Query("lastname") // 是 c.Request.URL.Query().Get("lastname") 的简写
15
16 c.String(http.StatusOK, "Hello %s %s", firstname, lastname)
17 })
18 router.Run(":8080")
19}
运行结果
5. 获取POST参数
- 表单传输为post请求,http常见的传输格式为四种:
- application/json
- application/x-www-form-urlencoded
- application/xml
- multipart/form-data
- 表单参数可以通过PostForm()方法获取,该方法默认解析的是x-www-form-urlencoded或from-data格式的参数
1package main
2
3//
4import (
5 "github.com/gin-gonic/gin"
6)
7
8func main() {
9 router := gin.Default()
10
11 router.POST("/form_post", func(c *gin.Context) {
12 message := c.PostForm("message")
13 nick := c.DefaultPostForm("nick", "anonymous") // 此方法可以设置默认值
14
15 c.JSON(200, gin.H{
16 "status": "posted",
17 "message": message,
18 "nick": nick,
19 })
20 })
21 router.Run(":8080")
22}
POST指定参数
POST不带参数
6. 路由分组
路由分组通常是为了管理一些相同的URL的不同版本
1func main() {
2 router := gin.Default()
3
4 // Simple group: v1
5 v1 := router.Group("/v1")
6 {
7 v1.POST("/login", loginEndpoint)
8 v1.POST("/submit", submitEndpoint)
9 }
10
11 // Simple group: v2
12 v2 := router.Group("/v2")
13 {
14 v2.POST("/login", loginEndpoint)
15 v2.POST("/submit", submitEndpoint)
16 }
17
18 router.Run(":8080")
19}
20
21func login(c *gin.Context) {
22 name := c.DefaultQuery("name", "jack")
23 c.String(200, fmt.Sprintf("hello %s\n", name))
24}
25
26func submit(c *gin.Context) {
27 name := c.DefaultQuery("name", "lily")
28 c.String(200, fmt.Sprintf("hello %s\n", name))
29}
curl
测试
1curl -X POST http://localhost:8080/v1/login\?name\=tom
2hello tom
3curl -X POST http://localhost:8080/v1/login
4hello jerry
5curl -X POST http://localhost:8080/v1/submit
6hello tom
7curl -X POST http://localhost:8080/v1/submit\?name\=jerry
8hello jerry
三、文件上传
1. 上传单个文件
1package main
2
3import (
4 "fmt"
5 "net/http"
6 "log"
7 "github.com/gin-gonic/gin"
8)
9
10func main() {
11 router := gin.Default()
12 // 给表单限制上传大小 (默认 32 MiB)
13 // router.MaxMultipartMemory = 8 << 20 // 8 MiB
14 router.POST("/upload", func(c *gin.Context) {
15 // 单文件
16 file, _ := c.FormFile("file")
17 log.Println(file.Filename)
18
19 // 上传文件到指定的路径
20 // c.SaveUploadedFile(file, dst)
21
22 c.String(http.StatusOK, fmt.Sprintf("'%s' uploaded!", file.Filename))
23 })
24 router.Run(":8080")
25}
curl测试
1curl -X POST http://localhost:8080/upload \
2 -F "file=@/Users/jerry/test.zip" \
3 -H "Content-Type: multipart/form-data"
2. 上传多个文件
1package main
2
3import (
4 "fmt"
5 "net/http"
6 "log"
7 "github.com/gin-gonic/gin"
8)
9
10func main() {
11 router := gin.Default()
12 // 给表单限制上传大小 (默认 32 MiB)
13 // router.MaxMultipartMemory = 8 << 20 // 8 MiB
14 router.POST("/upload", func(c *gin.Context) {
15 // 多文件
16 form, _ := c.MultipartForm()
17 files := form.File["upload[]"]
18
19 for _, file := range files {
20 log.Println(file.Filename)
21
22 // 上传文件到指定的路径
23 // c.SaveUploadedFile(file, dst)
24 }
25 c.String(http.StatusOK, fmt.Sprintf("%d files uploaded!", len(files)))
26 })
27 router.Run(":8080")
28}
curl
测试
1curl -X POST http://localhost:8080/upload \
2 -F "upload[]=@/Users/jerry/test1.zip" \
3 -F "upload[]=@/Users/jerry/test2.zip" \
4 -H "Content-Type: multipart/form-data"
四、Gin中间件
-
gin可以构建中间件,但它只对注册过的路由函数起作用
-
对于分组路由,嵌套使用中间件,可以限定中间件的作用范围
-
中间件分为全局中间件,单个路由中间件和群组中间件
-
gin中间件必须是一个 gin.HandlerFunc 类型
1. 不使用中间件
所有请求都经过此中间件
默认启动方式,包含 Logger、Recovery 中间件
不使用中间件
1r := gin.New()
2. 自定义中间件
可通过authorized限制中间件的使用范围
1package main
2
3import (
4 "log"
5 "time"
6
7 "github.com/gin-gonic/gin"
8)
9
10func Logger() gin.HandlerFunc {
11 return func(c *gin.Context) {
12 t := time.Now()
13
14 // Set example variable
15 c.Set("example", "12345")
16
17 // before request
18
19 c.Next()
20
21 // after request
22 latency := time.Since(t)
23 log.Print(latency)
24
25 // access the status we are sending
26 status := c.Writer.Status()
27 log.Println(status)
28 }
29}
30
31func main() {
32 r := gin.New()
33 r.Use(Logger())
34
35 r.GET("/test", func(c *gin.Context) {
36 example := c.MustGet("example").(string)
37
38 // it would print: "12345"
39 log.Println(example)
40 })
41
42 // Listen and serve on 0.0.0.0:8080
43 r.Run(":8080")
44}
运行应用程序日志输出如下
1[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
2 - using env: export GIN_MODE=release
3 - using code: gin.SetMode(gin.ReleaseMode)
4
5[GIN-debug] GET /test --> main.main.func1 (2 handlers)
6[GIN-debug] Listening and serving HTTP on :8080
72020/03/09 11:55:44 12345
82020/03/09 11:55:44 296.204µs
92020/03/09 11:55:44 200
102020/03/09 11:55:55 12345
112020/03/09 11:55:55 20.116µs
122020/03/09 11:55:55 200
3. 使用BasicAuth()验证中间件
1package main
2
3import (
4 "github.com/gin-gonic/gin"
5 "net/http"
6)
7
8// simulate some private data
9var secrets = gin.H{
10 "foo": gin.H{"email": "foo@bar.com", "phone": "123433"},
11 "austin": gin.H{"email": "austin@example.com", "phone": "666"},
12 "lena": gin.H{"email": "lena@guapa.com", "phone": "523443"},
13}
14
15func main() {
16 r := gin.Default()
17
18 // Group using gin.BasicAuth() middleware
19 // gin.Accounts is a shortcut for map[string]string
20 authorized := r.Group("/admin", gin.BasicAuth(gin.Accounts{
21 "foo": "bar",
22 "admin": "admin",
23 "lena": "hello2",
24 "jerry": "123456",
25 }))
26
27 // /admin/secrets endpoint
28 // hit "localhost:8080/admin/secrets
29 authorized.GET("/secrets", func(c *gin.Context) {
30 // get user, it was set by the BasicAuth middleware
31 user := c.MustGet(gin.AuthUserKey).(string)
32 if secret, ok := secrets[user]; ok {
33 c.JSON(http.StatusOK, gin.H{"user": user, "secret": secret})
34 } else {
35 c.JSON(http.StatusOK, gin.H{"user": user, "secret": "NO SECRET :("})
36 }
37 })
38
39 // Listen and serve on 0.0.0.0:8080
40 r.Run(":8080")
41}
4. 中间件使用Goroutines
在中间件或处理程序中启动新的Goroutines时,你不应该使用其中的原始上下文,你必须使用只读副本(c.Copy()
)
1func main() {
2 r := gin.Default()
3
4 r.GET("/long_async", func(c *gin.Context) {
5 // 创建要在goroutine中使用的副本
6 cCp := c.Copy()
7 go func() {
8 // simulate a long task with time.Sleep(). 5 seconds
9 time.Sleep(5 * time.Second)
10
11 // 这里使用你创建的副本
12 log.Println("Done! in path " + cCp.Request.URL.Path)
13 }()
14 })
15
16 r.GET("/long_sync", func(c *gin.Context) {
17 // simulate a long task with time.Sleep(). 5 seconds
18 time.Sleep(5 * time.Second)
19
20 // 这里没有使用goroutine,所以不用使用副本
21 log.Println("Done! in path " + c.Request.URL.Path)
22 })
23
24 // Listen and serve on 0.0.0.0:8080
25 r.Run(":8080")
26}
五、日志
1. 写日志文件
默认情况下,日志输出到控制台中
1package main
2
3import(
4 "os"
5 "io"
6
7 "github.com/gin-gonic/gin"
8)
9
10func main() {
11 // 禁用控制台颜色
12 gin.DisableConsoleColor()
13
14 // 创建记录日志的文件
15 f, _ := os.Create("gin.log")
16 gin.DefaultWriter = io.MultiWriter(f)
17
18 // 如果需要将日志同时写入文件和控制台,请使用以下代码
19 // gin.DefaultWriter = io.MultiWriter(f, os.Stdout)
20
21 router := gin.Default()
22 router.GET("/ping", func(c *gin.Context) {
23 c.String(200, "pong")
24 })
25
26 router.Run(":8080")
27}
程序运行起来之后,将日志全部写入到文件
1cat gin.log
2[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.
3
4[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
5 - using env: export GIN_MODE=release
6 - using code: gin.SetMode(gin.ReleaseMode)
7
8[GIN-debug] GET /ping --> main.main.func1 (3 handlers)
9[GIN-debug] Listening and serving HTTP on :8080
2. 自定义日志格式
有的时候为了方便应用程序日志的收集,比如用EFK进行日志收集,通常需要统一日志文件格式。
1package main
2
3import(
4 "fmt"
5 "time"
6 "github.com/gin-gonic/gin"
7)
8
9func main() {
10 router := gin.New()
11
12 // LoggerWithFormatter 中间件会将日志写入 gin.DefaultWriter
13 // By default gin.DefaultWriter = os.Stdout
14 router.Use(gin.LoggerWithFormatter(func(param gin.LogFormatterParams) string {
15
16 // 你的自定义格式
17 return fmt.Sprintf("%s - [%s] \"%s %s %s %d %s \"%s\" %s\"\n",
18 param.ClientIP,
19 param.TimeStamp.Format(time.RFC1123),
20 param.Method,
21 param.Path,
22 param.Request.Proto,
23 param.StatusCode,
24 param.Latency,
25 param.Request.UserAgent(),
26 param.ErrorMessage,
27 )
28 }))
29 router.Use(gin.Recovery())
30
31 router.GET("/ping", func(c *gin.Context) {
32 c.String(200, "pong")
33 })
34
35 router.Run(":8080")
36}
curl
测试
1curl http://localhost:8080/ping
2pong
应用程序日志
1::1 - [Mon, 09 Mar 2020 10:57:54 CST] "GET /ping HTTP/1.1 200 122.376µs "curl/7.64.1" "
六、模型绑定和验证
若要将请求主体绑定到结构体中,请使用模型绑定,目前支持JSON、XML、YAML和标准表单值(foo=bar&boo=baz)的绑定。
Gin使用 go-playground/validator.v8 验证参数,查看完整文档。
需要在绑定的字段上设置tag,比如,绑定格式为json,需要这样设置 json:"fieldname"
。
此外,Gin还提供了两套绑定方法:
- 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-debug] [WARNING] Headers were already written. Wanted to override status code 400 with 422
,如果你希望更好地控制行为,请使用ShouldBind
相关的方法
- Methods -
- Should bind
- Methods -
ShouldBind
,ShouldBindJSON
,ShouldBindXML
,ShouldBindQuery
,ShouldBindYAML
- Behavior - 这些方法底层使用
ShouldBindWith
,如果存在绑定错误,则返回错误,开发人员可以正确处理请求和错误。
- Methods -
当我们使用绑定方法时,Gin会根据Content-Type推断出使用哪种绑定器,如果你确定你绑定的是什么,你可以使用MustBindWith
或者BindingWith
。
你还可以给字段指定特定规则的修饰符,如果一个字段用binding:"required"
修饰,并且在绑定时该字段的值为空,那么将返回一个错误。
1. JSON绑定
1package main
2
3import(
4 "net/http"
5 "github.com/gin-gonic/gin"
6)
7
8// 绑定为json
9
10type Login struct {
11 User string `form:"user" json:"user" binding:"required"`
12 Password string `form:"password" json:"password" binding:"required"`
13}
14
15func main() {
16 router := gin.Default()
17
18 // Example for binding JSON ({"user": "jerry", "password": "123456"})
19 router.POST("/loginJSON", func(c *gin.Context) {
20 var json Login
21 if err := c.ShouldBindJSON(&json); err != nil {
22 c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
23 return
24 }
25
26 if json.User != "jerry" || json.Password != "123456" {
27 c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
28 return
29 }
30
31 c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})
32 })
33 router.Run(":8080")
34}
curl
测试
1# 验证通过示例
2curl -X POST http://localhost:8080/loginJSON -H "Content-Type: application/json" -d '{"user": "jerry", "password": "123456"}'
3{"status":"you are logged in"}
4# 验证失败示例
5curl -X POST http://localhost:8080/loginJSON -H "Content-Type: application/json" -d '{"user": "tom", "password": "123"}'
6{"status":"unauthorized"}
2. 表单绑定
1package main
2
3import (
4 "github.com/gin-gonic/gin"
5 "net/http"
6)
7
8// 绑定为json
9
10type Login struct {
11 User string `form:"user" json:"user" binding:"required"`
12 Password string `form:"password" json:"password" binding:"required"`
13}
14
15func main() {
16 router := gin.Default()
17 // Example for binding a HTML form (user=jerry&password=123456)
18 router.POST("/loginForm", func(c *gin.Context) {
19 var form Login
20 // This will infer what binder to use depending on the content-type header.
21 if err := c.ShouldBind(&form); err != nil {
22 c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
23 return
24 }
25
26 if form.User != "jerry" || form.Password != "123456" {
27 c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
28 return
29 }
30
31 c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})
32 })
33
34 // Listen and serve on 0.0.0.0:8080
35 router.Run(":8080")
36}
curl
测试
1# 验证通过示例
2curl -X POST http://localhost:8080/loginForm\?user\=jerry\&\password\=123456
3{"status":"you are logged in"}
4# 验证失败示例
5curl -X POST http://localhost:8080/loginForm\?user\=tom\&\password\=123
6{"status":"unauthorized"}
3. URI绑定
1package main
2
3import (
4 "github.com/gin-gonic/gin"
5 "net/http"
6)
7
8// 绑定为json
9
10type Login struct {
11 User string `form:"user" json:"user" uri:"user" binding:"required"`
12 Password string `form:"password" json:"password" uri:"password" binding:"required"`
13}
14
15func main() {
16 router := gin.Default()
17 // Example for binding a HTML URI (/jerry/123456)
18 router.GET("/:user/:password", func(c *gin.Context) {
19 // 声明接收变量
20 var login Login
21 // Bind()默认解析并绑定form格式
22 // 根据请求头中content-type自动推断
23 if err := c.ShouldBindUri(&login); err != nil {
24 c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
25 return
26 }
27
28 if login.User != "jerry" || login.Password != "123456" {
29 c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
30 return
31 }
32
33 c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})
34 })
35
36 // Listen and serve on 0.0.0.0:8080
37 router.Run(":8080")
38}
curl
测试
1# 验证通过示例
2curl http://localhost:8080/jerry/123456
3{"status":"you are logged in"}
4# 验证失败示例
5curl http://localhost:8080/tom/123
6{"status":"unauthorized"}
4. 只绑定GET参数
ShouldBindQuery
函数只绑定Get参数,不绑定post数据
1package main
2
3import (
4 "log"
5
6 "github.com/gin-gonic/gin"
7)
8
9type Person struct {
10 Name string `form:"name"`
11 Address string `form:"address"`
12}
13
14func main() {
15 route := gin.Default()
16 route.Any("/testing", startPage)
17 route.Run(":8080")
18}
19
20func startPage(c *gin.Context) {
21 var person Person
22 if c.ShouldBindQuery(&person) == nil {
23 log.Println("====== Only Bind By Query String ======")
24 log.Println(person.Name)
25 log.Println(person.Address)
26 }
27 c.String(200, "Success")
28}
curl
测试
1curl http://localhost:8080/testing\?name\=jerry\&address\=shenzhen
应用程序日志
12020/03/09 11:34:09 ====== Only Bind By Query String ======
22020/03/09 11:34:09 jerry
32020/03/09 11:34:09 shenzhen
4[GIN] 2020/03/09 - 11:34:09 | 200 | 140.48µs | ::1 | GET /testing?name=jerry&address=shenzhen
5. 绑定GET参数或者POST参数
1package main
2
3import (
4 "log"
5 "time"
6
7 "github.com/gin-gonic/gin"
8)
9
10type Person struct {
11 Name string `form:"name"`
12 Address string `form:"address"`
13 Birthday time.Time `form:"birthday" time_format:"2006-01-02" time_utc:"1"`
14}
15
16func main() {
17 route := gin.Default()
18 route.GET("/testing", startPage)
19 route.Run(":8080")
20}
21
22func startPage(c *gin.Context) {
23 var person Person
24 // If `GET`, only `Form` binding engine (`query`) used.
25 // 如果是Get,那么接收不到请求中的Post的数据??
26 // 如果是Post, 首先判断 `content-type` 的类型 `JSON` or `XML`, 然后使用对应的绑定器获取数据.
27 // See more at https://github.com/gin-gonic/gin/blob/master/binding/binding.go#L48
28 if c.ShouldBind(&person) == nil {
29 log.Println(person.Name)
30 log.Println(person.Address)
31 log.Println(person.Birthday)
32 }
33
34 c.String(200, "Success")
35}
6. 绑定复选框
main.go
1...
2
3type myForm struct {
4 Colors []string `form:"colors[]"`
5}
6
7...
8
9func formHandler(c *gin.Context) {
10 var fakeForm myForm
11 c.ShouldBind(&fakeForm)
12 c.JSON(200, gin.H{"color": fakeForm.Colors})
13}
14
15...
form.html
1<form action="/" method="POST">
2 <p>Check some colors</p>
3 <label for="red">Red</label>
4 <input type="checkbox" name="colors[]" value="red" id="red">
5 <label for="green">Green</label>
6 <input type="checkbox" name="colors[]" value="green" id="green">
7 <label for="blue">Blue</label>
8 <input type="checkbox" name="colors[]" value="blue" id="blue">
9 <input type="submit">
10</form>
result:
1{"color":["red","green","blue"]}
七、Gin渲染
1. 各种数据格式的响应
1package main
2
3import (
4 "net/http"
5
6 "github.com/gin-gonic/gin"
7 "github.com/gin-gonic/gin/testdata/protoexample"
8)
9
10func main() {
11 r := gin.Default()
12
13 // gin.H is a shortcut for map[string]interface{}
14 r.GET("/someJSON", func(c *gin.Context) {
15 c.JSON(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK})
16 })
17
18 r.GET("/moreJSON", func(c *gin.Context) {
19 // You also can use a struct
20 var msg struct {
21 Name string `json:"user"`
22 Message string
23 Number int
24 }
25 msg.Name = "Lena"
26 msg.Message = "hey"
27 msg.Number = 123
28 // Note that msg.Name becomes "user" in the JSON
29 // Will output : {"user": "Lena", "Message": "hey", "Number": 123}
30 c.JSON(http.StatusOK, msg)
31 })
32
33 r.GET("/someXML", func(c *gin.Context) {
34 c.XML(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK})
35 })
36
37 r.GET("/someYAML", func(c *gin.Context) {
38 c.YAML(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK})
39 })
40
41 r.GET("/someProtoBuf", func(c *gin.Context) {
42 reps := []int64{int64(1), int64(2)}
43 label := "test"
44 // The specific definition of protobuf is written in the testdata/protoexample file.
45 data := &protoexample.Test{
46 Label: &label,
47 Reps: reps,
48 }
49 // Note that data becomes binary data in the response
50 // Will output protoexample.Test protobuf serialized data
51 c.ProtoBuf(http.StatusOK, data)
52 })
53
54 // Listen and serve on 0.0.0.0:8080
55 r.Run(":8080")
56}
SecureJSON
使用SecureJSON可以防止json劫持,如果返回的数据是数组,则会默认在返回值前加上"while(1)"
1func main() {
2 r := gin.Default()
3
4 // 可以自定义返回的json数据前缀
5 // r.SecureJsonPrefix(")]}',\n")
6
7 r.GET("/someJSON", func(c *gin.Context) {
8 names := []string{"lena", "austin", "foo"}
9
10 // 将会输出: while(1);["lena","austin","foo"]
11 c.SecureJSON(http.StatusOK, names)
12 })
13
14 // Listen and serve on 0.0.0.0:8080
15 r.Run(":8080")
16}
JSONP
使用JSONP可以跨域传输,如果参数中存在回调参数,那么返回的参数将是回调函数的形式
1func main() {
2 r := gin.Default()
3
4 r.GET("/JSONP", func(c *gin.Context) {
5 data := map[string]interface{}{
6 "foo": "bar",
7 }
8
9 // 访问 http://localhost:8080/JSONP?callback=call
10 // 将会输出: call({foo:"bar"})
11 c.JSONP(http.StatusOK, data)
12 })
13
14 // Listen and serve on 0.0.0.0:8080
15 r.Run(":8080")
16}
AsciiJSON
使用AsciiJSON将使特殊字符编码
1func main() {
2 r := gin.Default()
3
4 r.GET("/someJSON", func(c *gin.Context) {
5 data := map[string]interface{}{
6 "lang": "GO语言",
7 "tag": "<br>",
8 }
9
10 // 将输出: {"lang":"GO\u8bed\u8a00","tag":"\u003cbr\u003e"}
11 c.AsciiJSON(http.StatusOK, data)
12 })
13
14 // Listen and serve on 0.0.0.0:8080
15 r.Run(":8080")
16}
PureJSON
通常情况下,JSON会将特殊的HTML字符替换为对应的unicode字符,比如<
替换为\u003c
,如果想原样输出html,则使用PureJSON,这个特性在Go 1.6及以下版本中无法使用。
1func main() {
2 r := gin.Default()
3
4 // Serves unicode entities
5 r.GET("/json", func(c *gin.Context) {
6 c.JSON(200, gin.H{
7 "html": "<b>Hello, world!</b>",
8 })
9 })
10
11 // Serves literal characters
12 r.GET("/purejson", func(c *gin.Context) {
13 c.PureJSON(200, gin.H{
14 "html": "<b>Hello, world!</b>",
15 })
16 })
17
18 // listen and serve on 0.0.0.0:8080
19 r.Run(":8080")
20}
2. 设置静态文件路径
访问静态文件需要先设置路径
1func main() {
2 router := gin.Default()
3 router.Static("/assets", "./assets")
4 router.StaticFS("/more_static", http.Dir("my_file_system"))
5 router.StaticFile("/favicon.ico", "./resources/favicon.ico")
6
7 // Listen and serve on 0.0.0.0:8080
8 router.Run(":8080")
9}
3. 返回第三方获取的数据
1func main() {
2 router := gin.Default()
3 router.GET("/someDataFromReader", func(c *gin.Context) {
4 response, err := http.Get("https://raw.githubusercontent.com/gin-gonic/logo/master/color.png")
5 if err != nil || response.StatusCode != http.StatusOK {
6 c.Status(http.StatusServiceUnavailable)
7 return
8 }
9
10 reader := response.Body
11 contentLength := response.ContentLength
12 contentType := response.Header.Get("Content-Type")
13
14 extraHeaders := map[string]string{
15 "Content-Disposition": `attachment; filename="gopher.png"`,
16 }
17
18 c.DataFromReader(http.StatusOK, contentLength, contentType, reader, extraHeaders)
19 })
20 router.Run(":8080")
21}
4. HTML模版渲染
使用LoadHTMLGlob()
或者 LoadHTMLFiles()
1func main() {
2 router := gin.Default()
3 router.LoadHTMLGlob("templates/*")
4 //router.LoadHTMLFiles("templates/template1.html", "templates/template2.html")
5 router.GET("/index", func(c *gin.Context) {
6 c.HTML(http.StatusOK, "index.tmpl", gin.H{
7 "title": "Main website",
8 })
9 })
10 router.Run(":8080")
11}
templates/index.tmpl
1<html>
2 <h1>
3 {{ .title }}
4 </h1>
5</html>
在不同目录中使用具有相同名称的模板
1func main() {
2 router := gin.Default()
3 router.LoadHTMLGlob("templates/**/*")
4 router.GET("/posts/index", func(c *gin.Context) {
5 c.HTML(http.StatusOK, "posts/index.tmpl", gin.H{
6 "title": "Posts",
7 })
8 })
9 router.GET("/users/index", func(c *gin.Context) {
10 c.HTML(http.StatusOK, "users/index.tmpl", gin.H{
11 "title": "Users",
12 })
13 })
14 router.Run(":8080")
15}
templates/posts/index.tmpl
1{{ define "posts/index.tmpl" }}
2<html><h1>
3 {{ .title }}
4</h1>
5<p>Using posts/index.tmpl</p>
6</html>
7{{ end }}
templates/users/index.tmpl
1{{ define "users/index.tmpl" }}
2<html><h1>
3 {{ .title }}
4</h1>
5<p>Using users/index.tmpl</p>
6</html>
7{{ end }}
自定义模板渲染器
1import "html/template"
2
3func main() {
4 router := gin.Default()
5 html := template.Must(template.ParseFiles("file1", "file2"))
6 router.SetHTMLTemplate(html)
7 router.Run(":8080")
8}
自定义渲染分隔符
1r := gin.Default()
2 r.Delims("{[{", "}]}")
3 r.LoadHTMLGlob("/path/to/templates")
自定义模板函数
main.go
1import (
2 "fmt"
3 "html/template"
4 "net/http"
5 "time"
6
7 "github.com/gin-gonic/gin"
8)
9
10func formatAsDate(t time.Time) string {
11 year, month, day := t.Date()
12 return fmt.Sprintf("%d%02d/%02d", year, month, day)
13}
14
15func main() {
16 router := gin.Default()
17 router.Delims("{[{", "}]}")
18 router.SetFuncMap(template.FuncMap{
19 "formatAsDate": formatAsDate,
20 })
21 router.LoadHTMLFiles("./testdata/template/raw.tmpl")
22
23 router.GET("/raw", func(c *gin.Context) {
24 c.HTML(http.StatusOK, "raw.tmpl", map[string]interface{}{
25 "now": time.Date(2020, 03, 09, 0, 0, 0, 0, time.UTC),
26 })
27 })
28
29 router.Run(":8080")
30}
raw.tmpl
然后就可以在html中直接使用formatAsDate函数了
1Date: {[{.now | formatAsDate}]}
Result:
1Date: 2020/03/09
5. 多个模版文件
Gin默认情况下只允许使用一个html模板文件(即一次可以加载多个模板文件)
1package main
2
3import (
4 "github.com/gin-contrib/multitemplate"
5 "github.com/gin-gonic/gin"
6)
7
8func createMyRender() multitemplate.Renderer {
9 r := multitemplate.NewRenderer()
10 r.AddFromFiles("index", "templates/base.html", "templates/index.html")
11 r.AddFromFiles("article", "templates/base.html", "templates/index.html", "templates/article.html")
12 return r
13}
14
15func main() {
16 router := gin.Default()
17 router.HTMLRender = createMyRender()
18 router.GET("/", func(c *gin.Context) {
19 c.HTML(200, "index", gin.H{
20 "title": "Html5 Template Engine",
21 })
22 })
23 router.GET("/article", func(c *gin.Context) {
24 c.HTML(200, "article", gin.H{
25 "title": "Html5 Article Engine",
26 })
27 })
28 router.Run(":8080")
29}
6. 重定向
发布HTTP重定向很容易,支持内部和外部链接
1r.GET("/test", func(c *gin.Context) {
2 c.Redirect(http.StatusMovedPermanently, "https://huangzhongde.cn/")
3})
Gin路由重定向,使用如下的HandleContext
1r.GET("/test", func(c *gin.Context) {
2 c.Request.URL.Path = "/test2"
3 r.HandleContext(c)
4})
5r.GET("/test2", func(c *gin.Context) {
6 c.JSON(200, gin.H{"hello": "world"})
7})
八、会话控制
1. Cookie是什么
-
HTTP是无状态协议,服务器不能记录浏览器的访问状态,也就是说服务器不能区分两次请求是否由同一个客户端发出
-
Cookie就是解决HTTP协议无状态的方案之一,中文是小甜饼的意思
-
Cookie实际上就是服务器保存在浏览器上的一段信息。浏览器有了Cookie之后,每次向服务器发送请求时都会同时将该信息发送给服务器,服务器收到请求后,就可以根据该信息处理请求
-
Cookie由服务器创建,并发送给浏览器,最终由浏览器保存
2. Cookie的用途
-
保持用户登录状态
-
京东购物车
3. Cookie的使用
测试服务端发送cookie给客户端,客户端请求时携带cookie
1package main
2
3import (
4 "fmt"
5
6 "github.com/gin-gonic/gin"
7)
8
9func main() {
10
11 router := gin.Default()
12
13 router.GET("/cookie", func(c *gin.Context) {
14
15 cookie, err := c.Cookie("gin_cookie")
16
17 if err != nil {
18 cookie = "NotSet"
19 c.SetCookie("gin_cookie", "test", 3600, "/", "localhost", false, true)
20 }
21
22 fmt.Printf("Cookie value: %s \n", cookie)
23 })
24
25 router.Run()
26}
打开浏览器进行测试,第一次是NotSet,刷新之后就有了
1Cookie value: NotSet
2[GIN] 2020/03/09 - 12:07:19 | 200 | 104.121µs | ::1 | GET /cookie
3Cookie value: test
4[GIN] 2020/03/09 - 12:07:22 | 200 | 95.51µs | ::1 | GET /cookie
4. Cookie的缺点
-
不安全,明文
-
增加带宽消耗
-
可以被禁用
-
cookie有上限
5. Session是什么
Session可以弥补Cookie的不足,Session必须依赖于Cookie才能使用,生成一个SessionId放在Cookie里传给客户端就可以。
- 原文作者:黄忠德
- 原文链接:https://huangzhongde.cn/post/Golang/Gin_study_guide/
- 版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议进行许可,非商业转载请注明出处(作者,原文链接),商业转载请联系作者获得授权。