示例
1. AsciiJSON
使用 AsciiJSON 对非Ascii码的字符转义为Unicode
func main() {
r := gin.Default()
r.GET("/someJSON", func(c *gin.Context) {
data := map[string]interface{}{
"lang语言": "GO语言",
"tag1212": "<br>",
}
// 在这里会对中文以及特殊符号处理 -- 处理为Unicode编码形式
c.AsciiJSON(http.StatusOK, data)
})
r.Run(":8080")
}
http返回结果为:
{"lang\u8bed\u8a00":"GO\u8bed\u8a00","tag1212":"\u003cbr\u003e"}
2. 绑定表单数据到自定义结构体
Bind form-data request with custom struct
数据结构
type StructA struct {
FieldA string `form:"field_a"`
}
type StructB struct {
NestedStruct StructA
FieldB string `form:"field_b"`
}
type StructC struct {
NestedStructPointer *StructA
FieldC string `form:"field_c"`
}
type StructD struct {
NestedAnonyStruct 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.NestedStruct,
"b": b.FieldB,
})
}
func GetDataC(c *gin.Context) {
var b StructC
c.Bind(&b)
c.JSON(200, gin.H{
"a": b.NestedStructPointer,
"c": b.FieldC,
})
}
func GetDataD(c *gin.Context) {
var b StructD
c.Bind(&b)
c.JSON(200, gin.H{
"x": b.NestedAnonyStruct,
"d": b.FieldD,
})
}
示例1
http://localhost:8080/getb?field_a=hello&field_b=world
在getb中绑定的是结构体B (字段分别为结构体A(field_a)和field_b)
返回结果:
{"a":{"FieldA":"hello"},"b":"world"}
示例2
http://localhost:8080/getc?field_a=hello&field_c=world
在getb中绑定的是结构体C(字段分别为结构体A(field_a)和field_c)
返回结果:
{"a":{"FieldA":"hello"},"c":"world"}
示例3
http://localhost:8080/getd?field_x=hello&field_d=world
在getd中绑定的是结构体D(字段分别为新定义的结构体A(field_a)和field_d)
返回结果:
{"d":"world","x":{"FieldX":"hello"}}
同理只要把相应的方法改为Post 参数以form-data的形式在表单传入效果相同
3. 绑定查询字符串或表单数据
Bind query string or post data
数据结构
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() {
route := gin.Default()
route.GET("/testingg", startPage)
route.POST("/testingp", startPage)
route.Run(":8080")
}
func startPage(c *gin.Context) {
var person Person
if c.ShouldBind(&person) == nil {
log.Println(person.Name)
log.Println(person.Address)
log.Println(person.Birthday)
}
c.JSON(200, gin.H{
"name": person.Name,
"address": person.Address,
"birthday": person.Birthday,
})
}
Get
直接在路由上加参数 如 http://localhost:8080/testingg?name=appleboy&address=xyz&birthday=1992-03-15
返回数据:
{"address":"xyz","birthday":"1992-03-15T00:00:00Z","name":"appleboy"}
需注意的是 参数名称必须和结构体字段的Tag一致
如路由为http://localhost:8080/testingg?Name=appleboy&address=xyz&birthday=1992-03-15
返回数据为:
{"address":"xyz","birthday":"1992-03-15T00:00:00Z","name":""}
4. 绑定URI
数据结构:
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() {
route := gin.Default()
route.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})
})
route.Run(":8080")
}
对参数进行格式校验,以及是否必须进行校验 ,只要是路径不匹配,绑定都会失败
5. 自定义Http配置
直接使用ListenAndServer
router := gin.Default()
router.GET("/get", func(c *gin.Context) {
c.JSON(200, "Success")
})
http.ListenAndServe(":8080", router)
或自定义进行配置
router := gin.Default()
router.GET("/get", func(c *gin.Context) {
c.JSON(200, "Success")
})
s := &http.Server{
Addr: ":8080",
Handler: router,
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
MaxHeaderBytes: 1 << 20,
}
s.ListenAndServe()
6. Html渲染
数据直接返回到指定的模板文件
func main() {
router := gin.Default()
router.LoadHTMLGlob("./templates/*")
router.GET("/index", func(c *gin.Context) {
c.HTML(http.StatusOK, "index.tmpl", gin.H{
"title": "Main website",
})
})
router.Run(":8080")
}
<html>
<h1>
{{ .title }}
</h1>
</html>
7. 记录日志
func main() {
gin.DisableConsoleColor()
f, _ := os.Create("gin.log")
gin.DefaultWriter = io.MultiWriter(f)
router := gin.Default()
router.GET("/ping", func(c *gin.Context) {
c.String(200, "pong")
})
router.Run(":8080")
}
在请求时可以查看到指定路径的日志文件里面写入的日志
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.
[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
- using env: export GIN_MODE=release
- using code: gin.SetMode(gin.ReleaseMode)
[GIN-debug] GET /ping --> main.main.func1 (3 handlers)
[GIN-debug] Listening and serving HTTP on :8080
[GIN] xxxxxxx | 200 | 1.0036ms | ::1 | GET "/ping"
[GIN] xxxxxxx | 200 | 0s | ::1 | GET "/ping"
8. XML/JSON/YAML/ProtoBuf 渲染
Json渲染
使用简单数据
func main() {
r := gin.Default()
// gin.H 是一个类型为 map[string]interface{}的数据格式
r.GET("/json", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK})
})
r.Run(":8080")
}
返回数据为
{"message":"hey","status":200}
使用结构体
func main() {
r := gin.Default()
r.GET("/sjson", func(c *gin.Context) {
// 使用结构体 并返回Json
var msg struct {
Name string `json:"user"`
Message string
Number int
}
msg.Name = "Lena"
msg.Message = "hey"
msg.Number = 123
// 最终返回的键都是已Tag为标准
c.JSON(http.StatusOK, msg)
})
r.Run(":8080")
}
返回结果为:
{"user":"Lena","Message":"hey","Number":123}
XML渲染
func main() {
r := gin.Default()
r.GET("/xml", func(c *gin.Context) {
c.XML(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK})
})
r.Run(":8080")
}
返回结果为:
<map>
<message>hey</message
><status>200</status>
</map>
Yaml
func main() {
r := gin.Default()
r.GET("/yaml", func(c *gin.Context) {
c.YAML(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK})
})
r.Run(":8080")
}
ProtoBuf
9. 中间件的使用
日志中间件使用示例
func main() {
// 新建一个没有任何默认中间件的路由
r := gin.New()
// LLogger 中间件将日志写入 gin.DefaultWriter,即使你将 GIN_MODE 设置为 release。
gin.DefaultWriter = os.Stdout // 控制台输出
r.Use(gin.Logger())
r.GET("/ping", func(c *gin.Context) {
c.String(200, "pong")
})
r.Run(":8080")
}
使用中间件后控制台可以看到输出情况
[GIN-debug] Listening and serving HTTP on :8080
[GIN] xxxxx - 17:53:15 | 200 | 2.1258ms | ::1 | GET "/ping"
[GIN] xxxxx - 17:53:16 | 200 | 0s | ::1 | GET "/ping"
对指定的路由使用指定的中间件
func main() {
// 新建一个没有任何默认中间件的路由
r := gin.New()
// 对于特定的路由可以指定多个中间件
r.GET("/benchmark",gin.Logger())
r.GET("/ping",gin.Logger(),func(c *gin.Context) {
c.String(200, "pong")
})
r.Run(":8080")
}
使用 BasicAuth 中间件
// 简单的用户信息
var secrets = gin.H{
"foo": gin.H{"email": "foo@bar.com", "phone": "123433"},
"austin": gin.H{"email": "austin@example.com", "phone": "666"},
"lena": gin.H{"email": "lena@guapa.com", "phone": "523443"},
}
func main() {
r := gin.Default()
// 路由组使用BasicAuth中间件
authorized := r.Group("/admin", gin.BasicAuth(gin.Accounts{
"foo": "bar",
"austin": "1234",
"lena": "hello2",
"manu": "4321",
}))
authorized.GET("/secrets", func(c *gin.Context) {
user := c.MustGet(gin.AuthUserKey).(string)
fmt.Println(user, "-------")
if secret, ok := secrets[user]; ok {
c.JSON(http.StatusOK, gin.H{"user": user, "secret": secret})
} else {
c.JSON(http.StatusOK, gin.H{"user": user, "secret": "NO SECRET :("})
}
})
r.Run(":8080")
}
说明:
当请求头中没有Authorization或者是Authorization值无效时,需要登陆
10. 请求参数映射为Map
Map as querystring or postform parameters
func main() {
router := gin.Default()
router.POST("/post", func(c *gin.Context) {
// 把在路由中ids的键作为map的键
ids := c.QueryMap("ids") // 从url中传入
names := c.PostFormMap("names") // 从form-data中传入
fmt.Println("---------------")
fmt.Printf("ids: %v; names: %v\n", ids, names)
fmt.Println("---------------")
})
router.Run(":8080")
}
输出结果为: ids: map[a:1234 b:hello]; names: map[first:thinkerou second:tianou]
简单来说该功能就是允许参数有不同的取值并且在后台可以映射为Map,方便其他操作
11. 获取字符串参数
Query string parameters
func main() {
router := gin.Default()
router.GET("/welcome", func(c *gin.Context) {
firstname := c.DefaultQuery("firstname", "Guest") // 如果没有会有默认值
lastname := c.Query("lastname") // 没有默认值
c.String(http.StatusOK, "Hello %s %s", firstname, lastname)
})
router.Run(":8080")
}
请求地址为 http://localhost:8080/welcome?firstname=454&lastname=89898
返回结果为:
Hello 454 89898
12. 路由组
Grouping routes
func main() {
router := gin.Default()
v1 := router.Group("/v1")
{
v1.POST("/login", func(c *gin.Context) {
c.String(http.StatusOK, "Hello login1")
})
v1.POST("/submit", func(c *gin.Context) {
c.String(http.StatusOK, "Hello submit1")
})
v1.POST("/read", func(c *gin.Context) {
c.String(http.StatusOK, "Hello read1")
})
}
v2 := router.Group("/v2")
{
v2.POST("/login", func(c *gin.Context) {
c.String(http.StatusOK, "Hello login2")
})
v2.POST("/submit", func(c *gin.Context) {
c.String(http.StatusOK, "Hello submit2")
})
v2.POST("/read", func(c *gin.Context) {
c.String(http.StatusOK, "Hello read2")
})
}
router.Run(":8080")
}
这样组织路由,就可以对路由尽心分组/或者按版本进行管理了
http://localhost:8080/v2/read/
http://localhost:8080/v2/read/
13. 路由参数
Parameters in path — 匹配路由中的参数(动态路由)
func main() {
router := gin.Default()
// /user/:name/ 只能匹配 /user/{parms} 或 /user/{param}/ 其他无法匹配
// /user/:name 只能匹配 /user/{parms} 或 /user/{param}/ 其他无法匹配
// router.GET("/user/:name", func(c *gin.Context) {
// name := c.Param("name")
// fmt.Println("name--", name)
// c.String(http.StatusOK, "Hello %s", name)
// })
// 会匹配到 /user/{param}/ 但是不会匹配到/user/{param}
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")
}
14. 重定向
Redirects
func main() {
router := gin.Default()
// 重定向外部路由
router.GET("/test", func(c *gin.Context) {
c.Redirect(http.StatusMovedPermanently, "http://www.baidu.com/")
})
// 重定向内部路由 访问test1 执行test2
router.GET("/test1", func(c *gin.Context) {
c.Request.URL.Path = "/test2"
router.HandleContext(c)
})
router.GET("/test2", func(c *gin.Context) {
c.JSON(200, gin.H{"hello": "world"})
})
router.Run(":8080")
}
15. PureJSON
通常,JSON用它们的unicode实体替换特殊的HTML字符,例如<变成\u003c。如果需要原样输出,则可以使用PureJSON。此功能在Go 1.6及更低版本中不可用。
func main() {
r := gin.Default()
// 提供 unicode 实体
r.GET("/json", func(c *gin.Context) {
c.JSON(200, gin.H{
"html": "<b>Hello, world!</b>",
})
})
// 提供字面字符
r.GET("/purejson", func(c *gin.Context) {
c.PureJSON(200, gin.H{
"html": "<b>Hello, world!</b>",
})
})
// 监听并在 0.0.0.0:8080 上启动服务
r.Run(":8080")
}
访问json返回结果:
{"html":"\u003cb\u003eHello, world!\u003c/b\u003e"}
访问purejson返回结果:
{"html":"<b>Hello, world!</b>"}
16. SecureJSON
使用 SecureJSON 防止 json 劫持。 因为JSON数组默认为是可执行的JS。如果给定的结构是数组值,则默认预置 "while(1),"
到响应体。
func main() {
r := gin.Default()
// 你也可以使用自己的 SecureJSON 前缀
r.SecureJsonPrefix("demo',\n")
r.GET("/someJSON", func(c *gin.Context) {
names := []string{"lena", "austin", "foo"}
// 将输出:demo;["lena","austin","foo"]
c.SecureJSON(http.StatusOK, names)
})
r.Run(":8080")
}
返回数据:
demo',
[
"lena",
"austin",
"foo"
]
17. JSONP
使用 JSONP 向不同域的服务器请求数据。如果查询参数存在回调,则将回调添加到响应体中(达到跨域的目)
func main() {
r := gin.Default()
r.GET("/jsonp", func(c *gin.Context) {
data := map[string]interface{}{
"foo": "bar",
}
// callback 是 x
c.JSONP(http.StatusOK, data)
})
r.Run(":8080")
}
返回结果为:
x({"foo":"bar"});
// 服务端返回对应的JS类型的字符串,被浏览器执行,达到跨域的目的。
18. 从Reader中保存数据
Serving data from reader
func main() {
router := gin.Default()
router.GET("/someDataFromReader", func(c *gin.Context) {
response, err := http.Get("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1594809745162&di=458d6704506ab2b9fa7bbbee9fa5fcda&imgtype=0&src=http%3A%2F%2Fa3.att.hudong.com%2F14%2F75%2F01300000164186121366756803686.jpg")
if err != nil || response.StatusCode != http.StatusOK {
c.Status(http.StatusServiceUnavailable)
return
}
reader := response.Body
contentLength := response.ContentLength
contentType := response.Header.Get("Content-Type")
extraHeaders := map[string]string{
"Content-Disposition": `attachment; filename="gopher.png"`,
}
fmt.Println("contentLength----", contentLength, "contentType---", contentType, "reader---", reader, "extraHeaders----", extraHeaders)
c.DataFromReader(http.StatusOK, contentLength, contentType, reader, extraHeaders)
})
router.Run(":8080")
}
19. 使用 HTTP 方法
func main() {
router := gin.Default()
router.GET("/get", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"method": "get", "status": http.StatusOK})
})
router.POST("/post", func(c *gin.Context) {
c.JSON(http.StatusCreated, gin.H{"method": "post", "status": http.StatusCreated})
})
router.PUT("/put", func(c *gin.Context) {
c.JSON(http.StatusResetContent, gin.H{"method": "put", "status": http.StatusResetContent})
})
router.DELETE("/delete", func(c *gin.Context) {
c.JSON(http.StatusNoContent, gin.H{"method": "delete", "status": http.StatusNoContent})
})
router.Run()
}
20. 文件上传
单文件
func main() {
router := gin.Default()
router.POST("/upload", func(c *gin.Context) {
// 单文件
file, _ := c.FormFile("file")
log.Println(file.Filename)
// 上传文件至指定目录
c.SaveUploadedFile(file, "demo_pic.png")
c.String(http.StatusOK, fmt.Sprintf("'%s' uploaded!", file.Filename))
})
router.Run(":8080")
}
多文件
func main() {
router := gin.Default()
router.POST("/upload", func(c *gin.Context) {
// Multipart form
form, _ := c.MultipartForm()
files := form.File["upload"]
for _, file := range files {
log.Println(file.Filename)
// 上传文件至指定目录
c.SaveUploadedFile(file, "./"+file.Filename)
}
c.String(http.StatusOK, fmt.Sprintf("%d files uploaded!", len(files)))
})
router.Run(":8080")
}
21. 中间件中使用协程
func main() {
r := gin.Default()
// r.GET("/long_async", func(c *gin.Context) {
// // 创建在 goroutine 中使用的副本
// cCp := c.Copy()
// go func() {
// // 用 time.Sleep() 模拟一个长任务。
// time.Sleep(5 * time.Second)
// // 请注意您使用的是复制的上下文 "cCp",这一点很重要
// log.Println("Done! in path " + cCp.Request.URL.Path)
// }()
// })
r.GET("/long_sync", func(c *gin.Context) {
// 用 time.Sleep() 模拟一个长任务。
time.Sleep(5 * time.Second)
// 因为没有使用 goroutine,不需要拷贝上下文
log.Println("Done! in path " + c.Request.URL.Path)
})
// 监听并在 0.0.0.0:8080 上启动服务
r.Run(":8080")
}
22. 自定义中间件
func Logger() gin.HandlerFunc {
return func(c *gin.Context) {
t := time.Now()
// 设置 example 变量
c.Set("example", "12345")
// 请求前
c.Next()
// 请求后
latency := time.Since(t)
log.Print(latency)
// 获取发送的 status
status := c.Writer.Status()
log.Println(status)
}
}
func main() {
r := gin.New()
r.Use(Logger())
r.GET("/test", func(c *gin.Context) {
example := c.MustGet("example").(string)
// 打印:"12345"
log.Println(example)
})
r.Run(":8080")
}