gin 教程 - Go语言中文社区

gin 教程


Gin 是一个基于 Go 语言编写的 Web 框架,与 martini 框架类似,但拥有更好的性能,借助高性能的 httprouter,速度提升了近 40 倍。如果你追求高性能和开发效率,你会爱上 Gin 框架。

开始介绍 Gin 框架的使用之前,我们先来简单看一下它的特性和优势,目前的 Gin 框架是 1.x 版本。

一、启动

1.1 初始化

gin的启动有两种方式

r := gin.Default()
//或
r := gin.New()

二、特性

快速:基于 Radix 树(一种更节省空间的 Trie 树结构)的路由,占用内存更少;没有反射;可预测的 API 性能。

内置路由器:开箱即用的路由器功能,不需要做任何配置即可使用。

支持中间件:传入的 HTTP 请求可以经由一系列中间件和最终操作来处理,例如 Logger、Authorization、GZIP 以及最终的 DB 操作。

Crash 处理:Gin 框架可以捕获一个发生在 HTTP 请求中的 panic 并 recover 它,从而保证服务器始终可用。此外,你还可以向 Sentry 报告这个 panic!

JSON 验证:Gin 框架可以解析并验证 JSON 格式的请求数据,例如检查某个必须值是否存在。

路由群组:支持通过路由群组来更好地组织路由,例如是否需要授权、设置 API 的版本等,此外,这些群组可以无限制地嵌套而不会降低性能。

API 冻结:支持 API 冻结,新版本的发布不会破坏已有的旧代码。

错误管理:Gin 框架提供了一种方便的机制来收集 HTTP 请求期间发生的所有错误,并且最终通过中间件将它们写入日志文件、数据库或者通过网络发送到其它系统。

内置渲染:Gin 框架提供了简单易上手的 API 来返回 JSON、XML 或者 HTML 格式响应。

可扩展性:我们将会在后续示例代码中看到 Gin 框架非常容易扩展。

易于测试:Gin 框架提供了完整的单元测试套件。

三、功能介绍

3.1 初始化

r := gin.Default()
//或
r := gin.New()

r.run()
//自定义 HTTP 服务器配置
http.ListenAndServe(":8080", r)
//或
s := &http.Server{
  Addr:           ":8080",
  Handler:        router,
  ReadTimeout:    10 * time.Second,
  WriteTimeout:   10 * time.Second,
  MaxHeaderBytes: 1 << 20,
}
s.ListenAndServe()

3.2 路由

参数绑定

type Student 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 student Student
    // 将路由参数绑定到结构体中
    if err := c.ShouldBindUri(&student); err != nil {
      c.JSON(400, gin.H{"msg": err})
      return
    }
    c.JSON(200, gin.H{"name": student.Name, "uuid": student.ID})
  })
  route.Run(":8088")
}

定义路由日志格式

func main() {
  r := gin.Default()

  // 默认路由输出格式
  gin.DebugPrintRouteFunc = func(httpMethod, absolutePath, handlerName string, nuHandlers int) {
    log.Printf("endpoint %v %v %v %v\n", httpMethod, absolutePath, handlerName, nuHandlers)
  }

  r.POST("/foo", func(c *gin.Context) {
    c.JSON(http.StatusOK, "foo")
  })

  r.GET("/bar", func(c *gin.Context) {
    c.JSON(http.StatusOK, "bar")
  })

  r.GET("/status", func(c *gin.Context) {
    c.JSON(http.StatusOK, "ok")
  })

  // Listen and Server in http://0.0.0.0:8080
  r.Run()
}

分组

func main() {
	router := gin.Default()
    
	// Simple group: v1
	v1 := router.Group("/v1")
	{
		v1.POST("/login", loginEndpoint)
		v1.POST("/submit", submitEndpoint)
		v1.POST("/read", readEndpoint)
	}
    
	// Simple group: v2
	v2 := router.Group("/v2")
	{
		v2.POST("/login", loginEndpoint)
		v2.POST("/submit", submitEndpoint)
		v2.POST("/read", readEndpoint)
	}
    
	router.Run(":8080")
}

3.3 中间件

自定义日志格式

func main() {
    router := gin.New()
    // LoggerWithFormatter 中间件会将日志写入 gin.DefaultWriter
    // 默认情况下 gin.DefaultWriter 是 os.Stdout
    router.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, )
    }))
    // 使用 recovery 中间件
    router.Use(gin.Recovery())
    router.GET("/ping", func(c *gin.Context) {
        c.String(200, "pong")
    })
    router.Run(":8080")
}
func Logger() gin.HandlerFunc {
	return func(c *gin.Context) {
		c.Set("example", "123456")
		c.Next()
		log.Print(time.Since(time.Now()))
		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)
		log.Println(example)
	})
	r.Run(":8085")
}

在中间件或处理器中开启新的协程时,不应该在其中使用原生的上下文对象,而应该使用它的只读副本:

func main() {
  r := gin.Default()

  r.GET("/long_async", func(c *gin.Context) {
    // 在协程中使用上下文对象c的只读副本
    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)

    // 这里没有使用协程,所以可以直接使用上下文对象 c
    log.Println("Done! in path " + c.Request.URL.Path)
  })

  r.Run(":8080")
}

3.4 验证器

// 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" time_format:"2006-01-02"`
}

var bookableDate validator.Func = func(fl validator.FieldLevel) bool {
  date, ok := fl.Field().Interface().(time.Time)
  if ok {
    if time.Now().After(date) {
      return false
    }
  }
  return true
}

func getBookable(c *gin.Context) {
  var b Booking
  if err := c.ShouldBindWith(&b, binding.Query); err == nil {
    c.JSON(http.StatusOK, gin.H{"message": "预定日期有效!"})
  } else {
    c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
  }
}

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

数据绑定

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

func main() {
	r := gin.Default()
	r.GET("/getb", GetDataB) //{"a":{"FieldA":""},"b":""}
	r.GET("/getc", GetDataC) //{"a":null,"c":""}
	r.GET("/getd", GetDataD) //{"d":"","x":{"FieldX":""}}
	r.Run(":8080")
}

//localhost:8080/getb?field_a=a&field_b=b => {"a":{"FieldA":"a"},"b":"b"}

绑定复选框

type myForm struct {
  Colors []string `form:"colors[]"`
}

func main()  {
  r := gin.Default()
  r.POST("/colors", func(c *gin.Context) {
    var fakeForm myForm
    // ShouldBind 和 Bind 类似,不过会在出错时退出而不是返回400状态码
    c.ShouldBind(&fakeForm)
    c.JSON(200, gin.H{"color": fakeForm.Colors})
  })
  r.Run()
}

查询绑定

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 startPage(c *gin.Context) {
  var person Person
  // If `GET`, only `Form` binding engine (`query`) used.
  // If `POST`, first checks the `content-type` for `JSON` or `XML`, then uses `Form` (`form-data`).
  // See more at https://github.com/gin-gonic/gin/blob/master/binding/binding.go#L48
  if c.ShouldBind(&person) == nil {
    log.Println(person.Name)
    log.Println(person.Address)
    log.Println(person.Birthday)
  }
  c.String(200, "Success") //可以直接返回给客户端
}

func main() {
  route := gin.Default()
  route.GET("/testing", startPage)   // GET 请求
  route.POST("/testing", startPage)   // POST 请求
  route.Run(":8085")
}

视图

模版解析

func main()  {
  r := gin.Default()
  r.LoadHTMLGlob("templates/**/*")
  r.GET("/colors", func(c *gin.Context) {
    c.HTML(http.StatusOK, "checkbox/color.tmpl", gin.H{
      "title": "Select Colors",
    })
  })
  r.Run()
}

模版渲染

func main() {
  router := gin.Default()
  //router.LoadHTMLGlob("templates/**/*") 多目录渲染
  router.LoadHTMLGlob("templates/*.tmpl")
  //如果用 LoadHTMLFiles(需列举所有文件,不如LoadHTMLGlob方便):
  // 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")
}
#视图 index.tmpl
{{ define "index.tmpl" }}
<html>
	<h1>
		{{ .title }}
	</h1>
</html>
{{ end }}

自定义模板引擎

import "html/template"

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,
  })
  
  html := template.Must(template.ParseFiles("file1", "file2"))
  router.SetHTMLTemplate(html)
  router.Run(":8080")
}

模板转二进制 6

json

使用 AsciiJSON 生成只包含 ASCII 字符的 JSON 数据,其他字符会被转义:

  r.GET("/asciiJSON", func(c *gin.Context) {
    data := map[string]interface{}{
      "lang": "Gin框架",
      "tag":  "<br>",
    }
    // 输出: {"lang":"Gin\u6846\u67b6","tag":"\u003cbr\u003e"}
    c.AsciiJSON(http.StatusOK, data)
  })

日志log

控制日志输出颜色

func main() {
  // 禁止日志颜色
  gin.DisableConsoleColor()
  // 使用默认中间件(logger 和 recovery)创建 gin 路由器
  router := gin.Default()
  router.GET("/ping", func(c *gin.Context) {
    c.String(200, "pong")
  })
  router.Run(":8080")
}

将日志信息写文件

func main() 
                            
                            版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/qq_29677867/article/details/108838356
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢