gin使用教程

文章目录

    • 1、下载并安装 gin:
    • 2、快速例子
    • 3、示例
      • 3.1、AsciiJSON
      • 3.2、HTML 渲染
      • 3.3、JSONP
      • 3.4、Multipart/Urlencoded 绑定
      • 3.5、Multipart/Urlencoded 表单
      • 3.6、PureJSON
      • 3.7、Query 和 post form
      • 3.8、SecureJSON
      • 3.9、XML/JSON/YAML/ProtoBuf 渲染
      • 3.10、上传文件
        • 3.10.1、单文件
        • 3.10.2、多文件
      • 3.11、不使用默认的中间件
      • 3.12、从 reader 读取数据
      • 3.13、使用 BasicAuth 中间件
      • 3.14、使用 HTTP 方法
      • 3.15、只绑定 url 查询字符串
      • 3.16、在中间件中使用 Goroutine
      • 3.17、如何记录日志
      • 3.18、将 request body 绑定到不同的结构体中
      • 3.19、映射查询字符串或表单参数
      • 3.20、查询字符串参数
      • 3.21、模型绑定和验证
      • 3.22、绑定 HTML 复选框
      • 3.23、绑定 Uri
      • 3.24、绑定查询字符串或表单数据
      • 3.25、绑定表单数据至自定义结构体
      • 3.26、自定义 HTTP 配置
      • 3.27、自定义中间件
      • 3.28、自定义日志文件
      • 3.29、自定义验证器
      • 3.30、设置和获取 Cookie
      • 3.31、路由参数
      • 3.32、路由组
      • 3.33、运行多个服务
      • 3.34、HTTP重定向
      • 3.35、静态文件服务
      • 3.36、静态资源嵌入

本文是来自官方Gin例子 https://gin-gonic.com/zh-cn/docs/examples/,学习Gin的时候把官方例子练习了一次和记录下来。

1、下载并安装 gin:

# 1、下载安装依赖包
$ go get -u github.com/gin-gonic/gin# 2、将gin引入到代码中:
import "github.com/gin-gonic/gin"# 3、(可选)如果使用诸如 http.StatusOK 之类的常量,则需要引入 net/http 包:
import "net/http"

2、快速例子

package mainimport ("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() // 监听并在 0.0.0.0:8080 上启动服务
}

3、示例

3.1、AsciiJSON

使用 AsciiJSON 生成具有转义的非 ASCII 字符的 ASCII-only JSON。

func main() {r := gin.Default()r.GET("/someJSON", func(c *gin.Context) {data := map[string]interface{}{"lang": "GO语言","tag":  "<br>",}// 输出 : {"lang":"GO\u8bed\u8a00","tag":"\u003cbr\u003e"}c.AsciiJSON(http.StatusOK, data)})// 监听并在 0.0.0.0:8080 上启动服务r.Run(":8080")
}

3.2、HTML 渲染

使用 LoadHTMLGlob() 或者 LoadHTMLFiles()

func main() {router := gin.Default()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")
}

templates/index.tmpl

<html><h1>{{ .title }}</h1>
</html>

使用不同目录下名称相同的模板

func main() {router := gin.Default()router.LoadHTMLGlob("templates/**/*")router.GET("/posts/index", func(c *gin.Context) {c.HTML(http.StatusOK, "posts/index.tmpl", gin.H{"title": "Posts",})})router.GET("/users/index", func(c *gin.Context) {c.HTML(http.StatusOK, "users/index.tmpl", gin.H{"title": "Users",})})router.Run(":8080")
}

templates/posts/index.tmpl

{{ define "posts/index.tmpl" }}
<html><h1>{{ .title }}
</h1>
<p>Using posts/index.tmpl</p>
</html>
{{ end }}

templates/users/index.tmpl

{{ define "users/index.tmpl" }}
<html><h1>{{ .title }}
</h1>
<p>Using users/index.tmpl</p>
</html>
{{ end }}

自定义模板渲染器
你可以使用自定义的 html 模板渲染

import "html/template"func main() {router := gin.Default()html := template.Must(template.ParseFiles("file1", "file2"))router.SetHTMLTemplate(html)router.Run(":8080")
}

自定义分隔符
你可以使用自定义分隔

	r := gin.Default()r.Delims("{[{", "}]}")r.LoadHTMLGlob("/path/to/templates")

自定义模板功能
查看详细示例代码。

main.go

import ("fmt""html/template""net/http""time""github.com/gin-gonic/gin"
)func formatAsDate(t time.Time) string {year, month, day := t.Date()return fmt.Sprintf("%d/%02d/%02d", year, month, day)
}func main() {router := gin.Default()router.Delims("{[{", "}]}")router.SetFuncMap(template.FuncMap{"formatAsDate": formatAsDate,})router.LoadHTMLFiles("./testdata/template/raw.tmpl")router.GET("/raw", func(c *gin.Context) {c.HTML(http.StatusOK, "raw.tmpl", map[string]interface{}{"now": time.Date(2017, 07, 01, 0, 0, 0, 0, time.UTC),})})router.Run(":8080")
}

raw.tmpl

Date: {[{.now | formatAsDate}]}

结果:

Date: 2017/07/01

3.3、JSONP

使用 JSONP 向不同域的服务器请求数据。如果查询参数存在回调,则将回调添加到响应体中。

func main() {
r := gin.Default()r.GET("/JSONP", func(c *gin.Context) {data := map[string]interface{}{"foo": "bar",}// /JSONP?callback=x// 将输出:x({\"foo\":\"bar\"})c.JSONP(http.StatusOK, data)})// 监听并在 0.0.0.0:8080 上启动服务r.Run(":8080")
}

3.4、Multipart/Urlencoded 绑定

package mainimport ("github.com/gin-gonic/gin"
)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) {// 你可以使用显式绑定声明绑定 multipart form:// c.ShouldBindWith(&form, binding.Form)// 或者简单地使用 ShouldBind 方法自动绑定:var form LoginForm// 在这种情况下,将自动选择合适的绑定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 -v --form user=user --form password=password http://localhost:8080/login

3.5、Multipart/Urlencoded 表单

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")
}

3.6、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")
}

3.7、Query 和 post form

POST /post?id=1234&page=1 HTTP/1.1
Content-Type: application/x-www-form-urlencoded
name=manu&message=this_is_great
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")
}
id: 1234; page: 1; name: manu; message: this_is_great

3.8、SecureJSON

使用 SecureJSON 防止 json 劫持。如果给定的结构是数组值,则默认预置 “while(1),” 到响应体。

func main() {
r := gin.Default()// 你也可以使用自己的 SecureJSON 前缀// r.SecureJsonPrefix(")]}',\n")r.GET("/someJSON", func(c *gin.Context) {names := []string{"lena", "austin", "foo"}// 将输出:while(1);["lena","austin","foo"]c.SecureJSON(http.StatusOK, names)})// 监听并在 0.0.0.0:8080 上启动服务r.Run(":8080")
}

3.9、XML/JSON/YAML/ProtoBuf 渲染

func main() {
r := gin.Default()// gin.H 是 map[string]interface{} 的一种快捷方式r.GET("/someJSON", func(c *gin.Context) {c.JSON(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK})})r.GET("/moreJSON", func(c *gin.Context) {// 你也可以使用一个结构体var msg struct {Name    string `json:"user"`Message stringNumber  int}msg.Name = "Lena"msg.Message = "hey"msg.Number = 123// 注意 msg.Name 在 JSON 中变成了 "user"// 将输出:{"user": "Lena", "Message": "hey", "Number": 123}c.JSON(http.StatusOK, msg)})r.GET("/someXML", func(c *gin.Context) {c.XML(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK})})r.GET("/someYAML", func(c *gin.Context) {c.YAML(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK})})r.GET("/someProtoBuf", func(c *gin.Context) {reps := []int64{int64(1), int64(2)}label := "test"// protobuf 的具体定义写在 testdata/protoexample 文件中。data := &protoexample.Test{Label: &label,Reps:  reps,}// 请注意,数据在响应中变为二进制数据// 将输出被 protoexample.Test protobuf 序列化了的数据c.ProtoBuf(http.StatusOK, data)})// 监听并在 0.0.0.0:8080 上启动服务r.Run(":8080")
}

3.10、上传文件

3.10.1、单文件
func main() {
router := gin.Default()
// 为 multipart forms 设置较低的内存限制 (默认是 32 MiB)
router.MaxMultipartMemory = 8 << 20  // 8 MiB
router.POST("/upload", func(c *gin.Context) {
// 单文件
file, _ := c.FormFile("file")
log.Println(file.Filename)dst := "./" + file.Filename// 上传文件至指定的完整文件路径c.SaveUploadedFile(file, dst)c.String(http.StatusOK, fmt.Sprintf("'%s' uploaded!", file.Filename))})router.Run(":8080")
}

如何使用 curl:

curl -X POST http://localhost:8080/upload \
-F "file=@/Users/appleboy/test.zip" \
-H "Content-Type: multipart/form-data"
3.10.2、多文件
func main() {
router := gin.Default()
// 为 multipart forms 设置较低的内存限制 (默认是 32 MiB)
router.MaxMultipartMemory = 8 << 20  // 8 MiB
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, dst)}c.String(http.StatusOK, fmt.Sprintf("%d files uploaded!", len(files)))})router.Run(":8080")
}

如何使用 curl:

curl -X POST http://localhost:8080/upload \
-F "upload[]=@/Users/appleboy/test1.zip" \
-F "upload[]=@/Users/appleboy/test2.zip" \
-H "Content-Type: multipart/form-data"

3.11、不使用默认的中间件

使用

r := gin.New()

代替

// Default 使用 Logger 和 Recovery 中间件
r := gin.Default()

3.12、从 reader 读取数据

func main() {
router := gin.Default()
router.GET("/someDataFromReader", func(c *gin.Context) {
response, err := http.Get("https://raw.githubusercontent.com/gin-gonic/logo/master/color.png")
if err != nil || response.StatusCode != http.StatusOK {
c.Status(http.StatusServiceUnavailable)
return
}reader := response.BodycontentLength := response.ContentLengthcontentType := response.Header.Get("Content-Type")extraHeaders := map[string]string{"Content-Disposition": `attachment; filename="gopher.png"`,}c.DataFromReader(http.StatusOK, contentLength, contentType, reader, extraHeaders)})router.Run(":8080")
}

3.13、使用 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()// 路由组使用 gin.BasicAuth() 中间件// gin.Accounts 是 map[string]string 的一种快捷方式authorized := r.Group("/admin", gin.BasicAuth(gin.Accounts{"foo":    "bar","austin": "1234","lena":   "hello2","manu":   "4321",}))// /admin/secrets 端点// 触发 "localhost:8080/admin/secretsauthorized.GET("/secrets", func(c *gin.Context) {// 获取用户,它是由 BasicAuth 中间件设置的user := c.MustGet(gin.AuthUserKey).(string)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 :("})}})// 监听并在 0.0.0.0:8080 上启动服务r.Run(":8080")
}

3.14、使用 HTTP 方法

func main() {
// 禁用控制台颜色
// gin.DisableConsoleColor()// 使用默认中间件(logger 和 recovery 中间件)创建 gin 路由router := gin.Default()router.GET("/someGet", getting)router.POST("/somePost", posting)router.PUT("/somePut", putting)router.DELETE("/someDelete", deleting)router.PATCH("/somePatch", patching)router.HEAD("/someHead", head)router.OPTIONS("/someOptions", options)// 默认在 8080 端口启动服务,除非定义了一个 PORT 的环境变量。router.Run()// router.Run(":3000") hardcode 端口号
}

3.15、只绑定 url 查询字符串

ShouldBindQuery 函数只绑定 url 查询参数而忽略 post 数据。参阅详细信息.

package mainimport (
"log""github.com/gin-gonic/gin"
)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 Personif c.ShouldBindQuery(&person) == nil {log.Println("====== Only Bind By Query String ======")log.Println(person.Name)log.Println(person.Address)}c.String(200, "Success")
}

3.16、在中间件中使用 Goroutine

当在中间件或 handler 中启动新的 Goroutine 时,不能使用原始的上下文,必须使用只读副本。

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")
}

3.17、如何记录日志

func main() {// 禁用控制台颜色,将日志写入文件时不需要控制台颜色。gin.DisableConsoleColor()// 记录到文件。f, _ := os.Create("gin.log")gin.DefaultWriter = io.MultiWriter(f)// 如果需要同时将日志写入文件和控制台,请使用以下代码。// gin.DefaultWriter = io.MultiWriter(f, os.Stdout)router := gin.Default()router.GET("/ping", func(c *gin.Context) {c.String(200, "pong")})router.Run(":8080")
}

3.18、将 request body 绑定到不同的结构体中

一般通过调用 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{}// c.ShouldBind 使用了 c.Request.Body,不可重用。if errA := c.ShouldBind(&objA); errA == nil {c.String(http.StatusOK, `the body should be formA`)// 因为现在 c.Request.Body 是 EOF,所以这里会报错。} 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{}// 读取 c.Request.Body 并将结果存入上下文。if errA := c.ShouldBindBodyWith(&objA, binding.JSON); errA == nil {c.String(http.StatusOK, `the body should be formA`)// 这时, 复用存储在上下文中的 body。} else if errB := c.ShouldBindBodyWith(&objB, binding.JSON); errB == nil {c.String(http.StatusOK, `the body should be formB JSON`)// 可以接受其他格式} 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() 而不会造成任任何性能损失 (详见 #1341)。

3.19、映射查询字符串或表单参数

POST /post?ids[a]=1234&ids[b]=hello HTTP/1.1
Content-Type: application/x-www-form-urlencodednames[first]=thinkerou&names[second]=tianou
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")
}
ids: map[b:hello a:1234], names: map[second:tianou first:thinkerou]

3.20、查询字符串参数

func main() {
router := gin.Default()// 使用现有的基础请求对象解析查询字符串参数。// 示例 URL: /welcome?firstname=Jane&lastname=Doerouter.GET("/welcome", func(c *gin.Context) {firstname := c.DefaultQuery("firstname", "Guest")lastname := c.Query("lastname") // c.Request.URL.Query().Get("lastname") 的一种快捷方式c.String(http.StatusOK, "Hello %s %s", firstname, lastname)})router.Run(":8080")
}

3.21、模型绑定和验证

要将请求体绑定到结构体中,使用模型绑定。 Gin目前支持JSON、XML、YAML和标准表单值的绑定(foo=bar&boo=baz)。

Gin使用 go-playground/validator/v10 进行验证。 查看标签用法的全部文档.

使用时,需要在要绑定的所有字段上,设置相应的tag。 例如,使用 JSON 绑定时,设置字段标签为 json:“fieldname”。

Gin提供了两类绑定方法:

  • Type - 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会输出日志 [GIN-debug] [WARNING] Headers were already written. Wanted to override status code 400 with 422。 如果您希望更好地控制绑定,考虑使用 ShouldBind 等效方法。
  • Type - Should bind
    • Methods - ShouldBind, ShouldBindJSON, ShouldBindXML, ShouldBindQuery, ShouldBindYAML
    • Behavior - 这些方法属于 ShouldBindWith 的具体调用。 如果发生绑定错误,Gin 会返回错误并由开发者处理错误和请求。
      使用 Bind 方法时,Gin 会尝试根据 Content-Type 推断如何绑定。 如果你明确知道要绑定什么,可以使用 MustBindWith 或 ShouldBindWith。

你也可以指定必须绑定的字段。 如果一个字段的 tag 加上了 binding:“required”,但绑定时是空值, Gin 会报错。

// 绑定 JSON
type Login struct {
User     string `form:"user" json:"user" xml:"user"  binding:"required"`
Password string `form:"password" json:"password" xml:"password" binding:"required"`
}func main() {
router := gin.Default()// 绑定 JSON ({"user": "manu", "password": "123"})router.POST("/loginJSON", func(c *gin.Context) {var json Loginif err := c.ShouldBindJSON(&json); err != nil {c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})return}if json.User != "manu" || json.Password != "123" {c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})return} c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})})// 绑定 XML (//	<?xml version="1.0" encoding="UTF-8"?>//	<root>//		<user>manu</user>//		<password>123</password>//	</root>)router.POST("/loginXML", func(c *gin.Context) {var xml Loginif err := c.ShouldBindXML(&xml); err != nil {c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})return}if xml.User != "manu" || xml.Password != "123" {c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})return} c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})})// 绑定 HTML 表单 (user=manu&password=123)router.POST("/loginForm", func(c *gin.Context) {var form Login// 根据 Content-Type Header 推断使用哪个绑定器。if err := c.ShouldBind(&form); err != nil {c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})return}if form.User != "manu" || form.Password != "123" {c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})return} c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})})// 监听并在 0.0.0.0:8080 上启动服务router.Run(":8080")
}

3.22、绑定 HTML 复选框

main.go

...type myForm struct {
Colors []string `form:"colors[]"`
}...func formHandler(c *gin.Context) {
var fakeForm myForm
c.ShouldBind(&fakeForm)
c.JSON(200, gin.H{"color": fakeForm.Colors})
}...

form.html

<form action="/" method="POST"><p>Check some colors</p><label for="red">Red</label><input type="checkbox" name="colors[]" value="red" id="red"><label for="green">Green</label><input type="checkbox" name="colors[]" value="green" id="green"><label for="blue">Blue</label><input type="checkbox" name="colors[]" value="blue" id="blue"><input type="submit">
</form>

结果:

{"color":["red","green","blue"]}

3.23、绑定 Uri

package mainimport "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 Personif err := c.ShouldBindUri(&person); err != nil {c.JSON(400, gin.H{"msg": err.Error()})return}c.JSON(200, gin.H{"name": person.Name, "uuid": person.ID})})route.Run(":8088")
}

测试:

$ curl -v localhost:8088/thinkerou/987fbc97-4bed-5078-9f07-9141ba07c9f3
$ curl -v localhost:8088/thinkerou/not-uuid

3.24、绑定查询字符串或表单数据

package mainimport ("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("/testing", startPage)route.Run(":8085")
}func startPage(c *gin.Context) {var person Person// 如果是 `GET` 请求,只使用 `Form` 绑定引擎(`query`)。// 如果是 `POST` 请求,首先检查 `content-type` 是否为 `JSON` 或 `XML`,然后再使用 `Form`(`form-data`)。// 查看更多:https://github.com/gin-gonic/gin/blob/master/binding/binding.go#L88if c.ShouldBind(&person) == nil {log.Println(person.Name)log.Println(person.Address)log.Println(person.Birthday)}c.String(200, "Success")
}

测试:

$ curl -X GET "localhost:8085/testing?name=appleboy&address=xyz&birthday=1992-03-15"

3.25、绑定表单数据至自定义结构体

以下示例使用自定义结构体:

type StructA struct {FieldA string `form:"field_a"`
}type StructB struct {NestedStruct StructAFieldB string `form:"field_b"`
}type StructC struct {NestedStructPointer *StructAFieldC 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 StructBc.Bind(&b)c.JSON(200, gin.H{"a": b.NestedStruct,"b": b.FieldB,})
}func GetDataC(c *gin.Context) {var b StructCc.Bind(&b)c.JSON(200, gin.H{"a": b.NestedStructPointer,"c": b.FieldC,})
}func GetDataD(c *gin.Context) {var b StructDc.Bind(&b)c.JSON(200, gin.H{"x": b.NestedAnonyStruct,"d": b.FieldD,})
}func main() {r := gin.Default()r.GET("/getb", GetDataB)r.GET("/getc", GetDataC)r.GET("/getd", GetDataD)r.Run()
}

使用 curl 命令结果:

$ 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"}}

3.26、自定义 HTTP 配置

直接使用 http.ListenAndServe(),如下所示:

func main() {router := gin.Default()http.ListenAndServe(":8080", router)
}

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()
}

3.27、自定义中间件

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)// 获取发送的 statusstatus := 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)})// 监听并在 0.0.0.0:8080 上启动服务r.Run(":8080")
}

3.28、自定义日志文件

func main() {router := gin.New()// LoggerWithFormatter 中间件会写入日志到 gin.DefaultWriter// 默认 gin.DefaultWriter = os.Stdoutrouter.Use(gin.LoggerWithFormatter(func(param gin.LogFormatterParams) string {// 你的自定义格式return fmt.Sprintf("%s - [%s] \"%s %s %s %d %s \"%s\" %s\"\n",param.ClientIP,param.TimeStamp.Format(time.RFC1123),param.Method,param.Path,param.Request.Proto,param.StatusCode,param.Latency,param.Request.UserAgent(),param.ErrorMessage,)}))router.Use(gin.Recovery())router.GET("/ping", func(c *gin.Context) {c.String(200, "pong")})router.Run(":8080")
}

3.29、自定义验证器

注册自定义验证器,查看示例代码.

package main
import ("net/http""reflect""time""github.com/gin-gonic/gin""github.com/gin-gonic/gin/binding""github.com/go-playground/validator/v10"
)
// Booking 包含绑定和验证的数据。
type Booking struct {CheckIn  time.Time `form:"check_in" binding:"required,bookabledate" time_format:"2006-01-02"`CheckOut time.Time `form:"check_out" binding:"required,gtfield=CheckIn,bookabledate" time_format:"2006-01-02"`
}var bookableDate validator.Func = func(fl validator.FieldLevel) bool {date, ok := fl.Field().Interface().(time.Time)if ok {today := time.Now()if today.After(date) {return false}}return true
}func main() {route := gin.Default()if v, ok := binding.Validator.Engine().(*validator.Validate); ok {v.RegisterValidation("bookabledate", bookableDate)}route.GET("/bookable", getBookable)route.Run(":8085")
}func getBookable(c *gin.Context) {var b Bookingif err := c.ShouldBindWith(&b, binding.Query); err == nil {c.JSON(http.StatusOK, gin.H{"message": "Booking dates are valid!"})} else {c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})}
}

3.30、设置和获取 Cookie

import ("fmt""github.com/gin-gonic/gin"
)func main() {router := gin.Default()router.GET("/cookie", func(c *gin.Context) {cookie, err := c.Cookie("gin_cookie")if err != nil {cookie = "NotSet"c.SetCookie("gin_cookie", "test", 3600, "/", "localhost", false, true)}fmt.Printf("Cookie value: %s \n", cookie)})router.Run()
}

3.31、路由参数

func main() {
router := gin.Default()// 此 handler 将匹配 /user/john 但不会匹配 /user/ 或者 /userrouter.GET("/user/:name", func(c *gin.Context) {name := c.Param("name")c.String(http.StatusOK, "Hello %s", name)})// 此 handler 将匹配 /user/john/ 和 /user/john/send// 如果没有其他路由匹配 /user/john,它将重定向到 /user/john/router.GET("/user/:name/*action", func(c *gin.Context) {name := c.Param("name")action := c.Param("action")message := name + " is " + actionc.String(http.StatusOK, message)})router.Run(":8080")
}

3.32、路由组

func main() {router := gin.Default()// 简单的路由组: v1v1 := router.Group("/v1"){v1.POST("/login", loginEndpoint)v1.POST("/submit", submitEndpoint)v1.POST("/read", readEndpoint)}// 简单的路由组: v2v2 := router.Group("/v2"){v2.POST("/login", loginEndpoint)v2.POST("/submit", submitEndpoint)v2.POST("/read", readEndpoint)}router.Run(":8080")
}

3.33、运行多个服务

package main
import ("log""net/http""time""github.com/gin-gonic/gin""golang.org/x/sync/errgroup"
)
var (g errgroup.Group
)func router01() http.Handler {e := gin.New()e.Use(gin.Recovery())e.GET("/", func(c *gin.Context) {c.JSON(http.StatusOK,gin.H{"code":  http.StatusOK,"error": "Welcome server 01",},)})return e
}func router02() http.Handler {e := gin.New()e.Use(gin.Recovery())e.GET("/", func(c *gin.Context) {c.JSON(http.StatusOK,gin.H{"code":  http.StatusOK,"error": "Welcome server 02",},)})return e
}func main() {server01 := &http.Server{Addr:         ":8080",Handler:      router01(),ReadTimeout:  5 * time.Second,WriteTimeout: 10 * time.Second,}server02 := &http.Server{Addr:         ":8081",Handler:      router02(),ReadTimeout:  5 * time.Second,WriteTimeout: 10 * time.Second,}g.Go(func() error {return server01.ListenAndServe()})g.Go(func() error {return server02.ListenAndServe()})if err := g.Wait(); err != nil {log.Fatal(err)}
}

3.34、HTTP重定向

HTTP 重定向很容易。 内部、外部重定向均支持。

r.GET("/test", func(c *gin.Context) {c.Redirect(http.StatusMovedPermanently, "http://www.google.com/")
})

通过 POST 方法进行 HTTP 重定向。请参考 issue:#444

r.POST("/test", func(c *gin.Context) {c.Redirect(http.StatusFound, "/foo")
})

路由重定向,使用 HandleContext:

r.GET("/test", func(c *gin.Context) {c.Request.URL.Path = "/test2"r.HandleContext(c)
})
r.GET("/test2", func(c *gin.Context) {c.JSON(200, gin.H{"hello": "world"})
})

3.35、静态文件服务

func main() {router := gin.Default()router.Static("/assets", "./assets")router.StaticFS("/more_static", http.Dir("my_file_system"))router.StaticFile("/favicon.ico", "./resources/favicon.ico")// 监听并在 0.0.0.0:8080 上启动服务router.Run(":8080")
}

3.36、静态资源嵌入

你可以使用 go-assets 将静态资源打包到可执行文件中。

go get github.com/jessevdk/go-assets-builder
func main() {r := gin.New()t, err := loadTemplate()if err != nil {panic(err)}r.SetHTMLTemplate(t)r.GET("/", func(c *gin.Context) {c.HTML(http.StatusOK, "/html/index.tmpl", nil)})r.Run(":8080")
}// loadTemplate 加载由 go-assets-builder 嵌入的模板
func loadTemplate() (*template.Template, error) {t := template.New("")for name, file := range Assets.Files {if file.IsDir() || !strings.HasSuffix(name, ".tmpl") {continue}h, err := ioutil.ReadAll(file)if err != nil {return nil, err}t, err = t.New(name).Parse(string(h))if err != nil {return nil, err}}return t, nil
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/625359.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

sqli-labs关卡23(基于get提交的过滤注释符的联合注入)

文章目录 前言一、回顾前几关知识点二、靶场第二十三关通关思路1、判断注入点2、爆数据库名3、爆数据库表4、爆数据库列5、爆数据库关键信息 总结 前言 此文章只用于学习和反思巩固sql注入知识&#xff0c;禁止用于做非法攻击。注意靶场是可以练习的平台&#xff0c;不能随意去…

给 Linux 主机添加 SSH 双因子认证

GitHub&#xff1a;https://github.com/google/google-authenticator-android 在信息时代&#xff0c;服务器安全愈发成为首要任务。Linux 主机通过 ssh 方式连接&#xff0c;当存在弱密码的情况下&#xff0c;通过暴力破解的方式会很容易就被攻破了&#xff0c;本文将向你展示…

国科大软件安全原理期末复习笔记

1 软件安全总论 1.软件的三大特性&#xff1a;复杂性、互连性、可扩展性&#xff1b; 2.基本概念&#xff1a;缺陷、漏洞、风险 缺陷&#xff08;bug&#xff09;&#xff1a;软件在设计和实现上的错误&#xff1b;漏洞&#xff08;vulnerability&#xff09;&#xff1a;漏洞…

解决虚拟机字体太小的问题

在win11中&#xff0c;安装VMWare软件后&#xff0c;创建好虚拟机&#xff0c;打开终端后&#xff0c;发现终端里显示的字体太小&#xff0c;不方便使用&#xff0c;因此需要修改。 1、打开终端 2、输入"gsettings set org.gnome.desktop.interface text-scaling-factor…

代码随想录算法训练营第五十二天|300.最长递增子序列、674. 最长连续递增序列、718. 最长重复子数组

代码随想录算法训练营第五十二天|300.最长递增子序列、674. 最长连续递增序列、718. 最长重复子数组 最长递增子序列 300.最长递增子序列 文章讲解&#xff1a;https://programmercarl.com/0300.%E6%9C%80%E9%95%BF%E4%B8%8A%E5%8D%87%E5%AD%90%E5%BA%8F%E5%88%97.html 题目链…

Android 13.0仿ios的hotseat效果修改hotseat样式

1.概述 在13.0系统产品rom定制化开发中,在项目需求的需要,系统原生Launcher的布局样式很一般,所以需要重新设计ui对布局样式做调整,产品在看到 ios的hotseat效果觉得特别美观,所以要仿ios一样不需要横屏铺满的效果 居中显示就行了,所以就要看hotseat的具体布局显示了 效…

【MATLAB】CEEMD+FFT+HHT组合算法

代码原理 CEEMD&#xff08;集合经验模态分解&#xff09;FFT&#xff08;快速傅里叶变换&#xff09;HHT&#xff08;希尔伯特-黄变换&#xff09;组合算法也是一种常见的信号处理和分析方法。这种组合算法结合了CEEMD、FFT和HHT三个步骤&#xff0c;可以处理非线性和非平稳信…

react中概念性总结(三)

目录 React中事件绑定函数的方法有哪些? 函数如何传递参数&#xff1f; 什么是重绘与回流&#xff0c;触发条件&#xff0c;如何减少回流和重绘 React 生命周期中 getDerivedStateFromProps怎么使用&#xff0c;怎么理解&#xff1f; React中路由单页面应用的优缺点都有哪些…

Vue3+Vite项目搭建

为什么选择vite而不是vue-cli&#xff1a; vite下一代前端开发与构建工具 vite创建的项目默认vue3 优势&#xff1a; 开发环境中&#xff0c;无需打包&#xff0c;可快速的冷启动 轻量快速的热重载&#xff08;HMR&#xff09; 真正的按需编译&#xff0c;不在等待整个应用…

Android中两种选择联系人方式

1.在选择联系人方式网上也有很多案例 有的说是使用ContactsContract.CommonDataKinds.Phone.CONTENT_URI也有的说是使用ContactsContract.Contacts.CONTENT_URI其实这两种方式都可以使用 只不过ContactsContract.Contacts.CONTENT_URI这种方式需要多查询一遍 一、使用Contacts…

23. 合并 K 个升序链表(递归分治)

这是我的第一个自己ak的分治题目&#xff01;&#xff01;&#xff01;好耶&#xff01;&#xff01;&#xff08;骄傲脸 思路参考&#xff1a;148. 排序链表&#xff08;归并排序&#xff09; /*** Definition for singly-linked list.* public class ListNode {* int v…

龙芯系统部署Elasticsearch

1.配置JDK环境 #查看是否安装jdk java -version#搜索java&#xff0c;结果&#xff1a;/usr/bin/java whereis java#&#xff08;1&#xff09;配置环境变量(临时有效) export JAVA_HOME/usr export PATH$PATH:$JAVA_HOME/bin#&#xff08;2&#xff09;配置环境变量(永久有效…

DA14531-高级应用篇-用户如何开启OTA服务

文章目录 1. OTA相关文件2.OTA宏定义列表3.OTA主要函数接口4.OTA具体实施步骤5.总结1. OTA相关文件 1)app_suotar_task.c和app_suotar_task.h 2)app_suotar.c和app_suotar.h 2.OTA宏定义列表 宏定义注解CFG_PRF_SUOTAR用户开启SOTA功能BLE_SUOTA_RECEIVERSOTA功能服务CFG_S…

把树状数组在页面显示成‘/‘/‘形式,并搜索想要的值

大概思路 在Vue中&#xff0c;若要将树状数组以类似于文件路径的形式&#xff08;即“/”分隔&#xff09;显示在页面上&#xff0c;可以按照以下步骤操作&#xff1a; 首先&#xff0c;假设您有一个树状数组&#xff0c;其结构可能如下所示&#xff1a; const treeData [{…

浅谈专项测试之弱网络测试

一&#xff0e;弱网络测试背景 移动端产品的使用并非完全都是在流畅的wifi环境&#xff0c;大部分用户主要使用4G,3G,2G等网络&#xff0c;另外因为移动端产品使用的场景多变&#xff0c;如进公交&#xff0c;上地铁&#xff0c;坐电梯&#xff0c;使得弱网测试显得尤为重要。考…

QT-JSON相关API/QJsonDocument/QJsonObject

QJsonObject类的相关操作 格式化排版创建JSON对象&#xff0c;使用字符串创建JSON对象&#xff0c;使用标准JSON对象获取JSON对象中的值&#xff0c;非数组获取JSON对象中的值&#xff0c;数组 格式化排版 下面的代码将一个符合JSON格式的字符串&#xff0c;格式化成具有缩进格…

HCIA基础知识

IP地址、静态路由、动态路由、交换机 OSPF RIP DHCP VLAN ACL NAT OSI TCP/IP UDP TCP 三次握手&#xff0c;四次挥手&#xff0c;报头 什么是网络&#xff1f; 由网络连接设备通过传输介质将网络终端设备连接起来&#xff0c;进行资源共享、信息传递的平台。 OSI七…

【C】函数指针 int (*addPtr)(int, int);

目录 函数指针1&#xff09;定义2&#xff09;声明和赋值3&#xff09;通过函数指针调用函数4&#xff09;用途&#xff1a;函数指针作为函数参数5&#xff09;函数名和函数指针6&#xff09;复杂一点的例子 函数指针 1&#xff09;定义 在C语言中&#xff0c;函数指针是指向…

智慧校园云桌面解决方案应用场景

​​​​​​教师办公解决方案 教师办公桌面现状 大多数学校一位教师配置一台个人计算机,实际上每位教师平时会使用到的计算机资源不超过15-20%,因此多数时间个人计算机都是处在闲置状态。教师常用的办公用机一般都使用台式机,所有的数据都存放在本地(“信息孤点”),根…

如何购买腾讯云服务器?图文教程超详细

腾讯云服务器购买流程很简单&#xff0c;有两种购买方式&#xff0c;直接在官方活动上购买比较划算&#xff0c;在云服务器CVM或轻量应用服务器页面自定义购买价格比较贵&#xff0c;但是自定义购买云服务器CPU内存带宽配置选择范围广&#xff0c;活动上购买只能选择固定的活动…