Contents

golang-gin-简介

gin官方文档

安装

1
go get -u github.com/gin-gonic/gin

快速开始

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
package main

import (
  "net/http"

  "github.com/gin-gonic/gin"
)

func main() {
  r := gin.Default()
  r.GET("/ping", func(c *gin.Context) {
    c.JSON(http.StatusOK, gin.H{
      "message": "pong",
    })
  })
  r.Run() // listen and serve on 0.0.0.0:8080 (for windows "localhost:8080")
}

API 范例

GET、POST、PUT、PATCH、DELETE 和 OPTIONS

1
2
3
4
5
6
7
router.GET("/someGet", handlefunc)
router.POST("/somePost", handlefunc)
router.PUT("/somePut", handlefunc)
router.DELETE("/someDelete", handlefunc)
router.PATCH("/somePatch", handlefunc)
router.HEAD("/someHead", handlefunc)
router.OPTIONS("/someOptions", handlefunc)

路径中的参数

/user/:name中的name

1
name := c.Param("name")

查询字符串参数

url中"?“后的参数

1
2
3
lastname := c.Query("lastname")
//带默认值的查询
firstname := c.DefaultQuery("firstname", "Guest")

多部分/URL 编码形式

post-form类型body的参数

1
2
3
message := c.PostForm("message")
//带默认值
nick := c.DefaultPostForm("nick", "anonymous")

上传文件

单个文件

1
2
3
4
5
file, _ := c.FormFile("file")
log.Println(file.Filename)

// Upload the file to specific dst.
c.SaveUploadedFile(file, dst)

不要相信filename

多个文件

1
2
3
4
5
6
7
8
9
form, _ := c.MultipartForm()
files := form.File["upload[]"]

for _, file := range files {
  log.Println(file.Filename)

  // Upload the file to specific dst.
  c.SaveUploadedFile(file, dst)
}

分组路由

1
v1 := router.Group("/v1")

可以对分组再分组,分组具有和router一样的api

创建不使用默认中间件的路由

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

使用中间件

1
2
3
r.Use(gin.Logger())
//分组路由也可以使用中间件
v.Use(gin.Logger())

自定义恢复行为

处理过程中panic后自动调用

1
2
3
4
5
6
r.Use(gin.CustomRecovery(func(c *gin.Context, recovered interface{}) {
  if err, ok := recovered.(string); ok {
    c.String(http.StatusInternalServerError, fmt.Sprintf("error: %s", err))
  }
  c.AbortWithStatus(http.StatusInternalServerError)
}))

写日志文件

1
2
3
4
5
6
7
8
9
// 取消日志颜色
gin.DisableConsoleColor()

// 将日志写入文件重定向到目标文件
f, _ := os.Create("gin.log")
gin.DefaultWriter = io.MultiWriter(f)

// 创建路由
router := gin.Default()

控制日志输出着色

不着色日志

1
gin.DisableConsoleColor()

始终为日志着色

1
gin.ForceConsoleColor()

配置需要在创建路由之前

模型绑定和验证

将请求体绑定到某个类型,支持JSON、XML、YAML、TOML 和标准格式值 (foo=bar&boo=baz)

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

binding:“required"指定必须有的字段

  • 必须绑定:Bind, BindJSON, BindXML, BindQuery, BindYAML, BindHeader,BindTOML。绑定错误,请求将被c.AbortWithError(400, err).SetType(ErrorTypeBind)中止。
  • 应该绑定:ShouldBind, ShouldBindJSON, ShouldBindXML, ShouldBindQuery, ShouldBindYAML, ShouldBindHeader, ShouldBindTOML。存在绑定错误,则返回错误(err:=c.ShouldBind(xxx))

自定义验证器

仅绑定查询字符串

“?“后面的参数

1
2
3
4
5
6
type Person struct {
  Name    string `form:"name"`
  Address string `form:"address"`
}

err:=c.ShouldBindQuery(&person)

绑定查询字符串或Post数据

1
2
3
4
5
6
7
8
9
type Person struct {
        Name       string    `form:"name"`
        Address    string    `form:"address"`
        Birthday   time.Time `form:"birthday" time_format:"2006-01-02" time_utc:"1"`
        CreateTime time.Time `form:"createTime" time_format:"unixNano"`
        UnixTime   time.Time `form:"unixTime" time_format:"unix"`
}

err:=c.ShouldBind(&person)

绑定URI

“/:name/:id"类型的Uri

1
2
3
4
5
6
type Person struct {
  ID string `uri:"id" binding:"required,uuid"`
  Name string `uri:"name" binding:"required"`
}

err := c.ShouldBindUri(&person)

绑定Header

Header中的参数

1
2
3
4
5
6
type testHeader struct {
  Rate   int    `header:"Rate"`
  Domain string `header:"Domain"`
}

err := c.ShouldBindHeader(&h)

多部分/URL 编码绑定

多种类型绑定

1
2
3
4
5
6
type ProfileForm struct {
  Name   string                `form:"name" binding:"required"`
  Avatar *multipart.FileHeader `form:"avatar" binding:"required"`
}

c.ShouldBind(&form)

XML、JSON、YAML、TOML 和 ProtoBuf 渲染

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
c.JSON(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK})

var msg struct {
  Name    string `json:"user"`
  Message string
  Number  int
}
msg.Name = "Lena"
msg.Message = "hey"
msg.Number = 123
// Note that msg.Name becomes "user" in the JSON
// Will output  :   {"user": "Lena", "Message": "hey", "Number": 123}
c.JSON(http.StatusOK, msg)
1
2
3
c.XML(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK})
c.YAML(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK})
c.TOML(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK})
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
reps := []int64{int64(1), int64(2)}
label := "test"
// The specific definition of protobuf is written in the testdata/protoexample file.
data := &protoexample.Test{
  Label: &label,
  Reps:  reps,
}
// Note that data becomes binary data in the response
// Will output protoexample.Test protobuf serialized data
c.ProtoBuf(http.StatusOK, data)

安全JSON

防止 json 劫持

1
c.SecureJSON(http.StatusOK, names)

JSONP

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

1
c.JSONP(http.StatusOK, data)

提供静态文件

1
2
3
4
5
6
7
//设置静态资源路径
r.Static("/assets", "./assets")
//设置FS,目录下不存 index.html 文件时,会列出该目录下的所有文件
r.StaticFS("/more_static", http.Dir("my_file_system"))
//指定某个具体的文件作为静态资源访问
r.StaticFile("/favicon.ico", "./resources/favicon.ico")
r.StaticFileFS("/more_favicon.ico", "more_favicon.ico", http.Dir("my_file_system"))

从文件提供数据

1
2
3
4
5
6
//从默认静态资源寻找文件
c.File("local/file.go")

//从某个FS找文件
var fs http.FileSystem = // ...
c.FileFromFS("fs/file.go", fs)

重定向

HTTP 重定向支持内部和外部位置

1
2
3
4
//外部位置
c.Redirect(http.StatusMovedPermanently, "http://www.google.com/")
//内部位置
c.Redirect(http.StatusFound, "/foo")

路由器重定向

1
2
c.Request.URL.Path = "/test2"
r.HandleContext(c)

自定义中间件

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
func Logger() gin.HandlerFunc {
  return func(c *gin.Context) {
    t := time.Now()

    // Set example variable
    c.Set("example", "12345")

    // before request

    c.Next()

    // after request
    latency := time.Since(t)
    log.Print(latency)

    // access the status we are sending
    status := c.Writer.Status()
    log.Println(status)
  }
}

r.Use(Logger())

中间件内的协程

中间件或处理程序中启动新的 Goroutine 时,你不应该使用其中的原始上下文,你必须使用只读副本

1
2
3
4
5
6
7
8
cCp := c.Copy()
go func() {
  // simulate a long task with time.Sleep(). 5 seconds
  time.Sleep(5 * time.Second)

  // note that you are using the copied context "cCp", IMPORTANT
  log.Println("Done! in path " + cCp.Request.URL.Path)
}()

自定义 HTTP 配置

直接使用http.ListenAndServe()使用默认构造的Server对象

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

自己新建一个Server对象

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
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()
}

HTTPS

单行配置

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
func main() {
  r := gin.Default()

  // Ping handler
  r.GET("/ping", func(c *gin.Context) {
    c.String(http.StatusOK, "pong")
  })

  log.Fatal(autotls.Run(r, "example1.com", "example2.com"))
}

自定义自动证书管理器

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
func main() {
  r := gin.Default()

  // Ping handler
  r.GET("/ping", func(c *gin.Context) {
    c.String(http.StatusOK, "pong")
  })

  m := autocert.Manager{
    Prompt:     autocert.AcceptTOS,
    HostPolicy: autocert.HostWhitelist("example1.com", "example2.com"),
    Cache:      autocert.DirCache("/var/www/.cache"),
  }

  log.Fatal(autotls.RunWithManager(r, &m))
}

使用 Gin 运行多个服务

gin定义的Router(*gin.Engine)其实就是标准net/http包的ServeMux,所以要启动多个服务就需要通过gin定义多个Router,然后使用协程启动定义多个Server对象来启动多个服务。

使用自定义结构绑定表单数据请求

gin会自动查询内嵌结构体的字段来绑定,不需要一定是匿名结构体

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
type StructA struct {
    FieldA string `form:"field_a"`
}

type StructB struct {
    NestedStruct StructA
    FieldB string `form:"field_b"`
}

c.Bind(&b)

不要相信所有代理

对于代理过的请求,我们可以设置信任的代理服务器来丢弃不信任的代理过的请求

1
router.SetTrustedProxies([]string{"192.168.1.2"})

测试

net/http/httptest包

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
func setupRouter() *gin.Engine {
  r := gin.Default()
  r.GET("/ping", func(c *gin.Context) {
    c.String(http.StatusOK, "pong")
  })
  return r
}

func TestPingRoute(t *testing.T) {
  router := setupRouter()

  w := httptest.NewRecorder()
  req, _ := http.NewRequest(http.MethodGet, "/ping", nil)
  router.ServeHTTP(w, req)

  assert.Equal(t, http.StatusOK, w.Code)
  assert.Equal(t, "pong", w.Body.String())
}
 |