链接:

  • Gin Web Framework
  • github.com/gin-gonic/gin
  • https://pkg.go.dev/github.com/julienschmidt/httprouter

    Gin是一个用Go (Golang)编写的web框架。它有一个类似martini-like的API,性能要好得多,多亏了httprouter,速度快了40倍。如果你需要表现和良好的生产力,你会喜欢Gin。
    在本节中,我们将介绍什么是Gin,它解决了什么问题,以及它如何帮助您的项目。
    或者,如果您准备在项目中使用Gin,请访问快速入门。

  1. Features
  2. Fast:基于路由,速度快,内存占用小。没有反射。可预测的API的性能。
  3. Radix tree based routing, small memory foot print. No reflection. Predictable API performance.
  4. Middleware support:中间件支持,传入的HTTP请求可以由一系列中间件和最终操作来处理。
  5. An incoming HTTP request can be handled by a chain of middlewares and the final action. For example: Logger, Authorization, GZIP and finally post a message in the DB.
  6. Crash-free:绝对无故障,Gin可以捕获在HTTP请求期间发生的panic并恢复它。
  7. Gin can catch a panic occurred during a HTTP request and recover it. This way, your server will be always available. As an example - its also possible to report this panic to Sentry!
  8. JSON validationJSON验证,Gin可以解析并验证请求的JSON——例如,检查是否存在所需的值。
  9. Gin can parse and validate the JSON of a request - for example,checking the existence of required values.
  10. Routes grouping:组织路由
  11. Organize your routes better. Authorization required vs non required, different API versions In addition, the groups can be nested unlimitedly without degrading performance.
  12. Error management:错误管理,Gin提供了一种方便的方法来收集HTTP请求期间发生的所有错误。最终,中间件可以将它们写入日志文件、数据库并通过网络发送。
  13. Gin provides a convenient way to collect all the errors occurred during a HTTP request. Eventually, a middleware can write them to a log file, to a database and send them through the network.
  14. Rendering built-in:呈现内置,GinJSONXMLHTML渲染提供了一个易于使用的API
  15. Gin provides an easy to use API for JSON, XML and HTML rendering.
  16. Extendable:可扩展,创建一个新的中间件是如此简单,只需查看示例代码。
  17. Creating a new middleware is so easy, just check out the sample codes.

一、快速入门

  1. 代码中导入包

    1. import (
    2. "github.com/gin-gonic/gin"
    3. "net/http"
    4. )
  2. 创建文件

    1. touch example.go
  3. 编写代码 ```go package main

import “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() // listen and serve on 0.0.0.0:8080 }

  1. <a name="qRFJe"></a>
  2. # 二、 日志
  3. <a name="MZ1XC"></a>
  4. ## 2.1 、 如何记录日志
  5. ```go
  6. func main() {
  7. // 禁用控制台颜色,将日志写入文件时不需要控制台颜色。
  8. gin.DisableConsoleColor()
  9. // 记录到文件。
  10. f, _ := os.Create("gin.log")
  11. gin.DefaultWriter = io.MultiWriter(f)
  12. // 如果需要同时将日志写入文件和控制台,请使用以下代码。
  13. // gin.DefaultWriter = io.MultiWriter(f, os.Stdout)
  14. router := gin.Default()
  15. router.GET("/ping", func(c *gin.Context) {
  16. c.String(200, "pong")
  17. })
  18. router.Run(":8080")
  19. }


2.2 、 自定义日志文件

  1. func main() {
  2. router := gin.New()
  3. // LoggerWithFormatter middleware will write the logs to gin.DefaultWriter
  4. // By default gin.DefaultWriter = os.Stdout
  5. router.Use(gin.LoggerWithFormatter(func(param gin.LogFormatterParams) string {
  6. // your custom format
  7. return fmt.Sprintf("%s - [%s] \"%s %s %s %d %s \"%s\" %s\"\n",
  8. param.ClientIP,
  9. param.TimeStamp.Format(time.RFC1123),
  10. param.Method,
  11. param.Path,
  12. param.Request.Proto,
  13. param.StatusCode,
  14. param.Latency,
  15. param.Request.UserAgent(),
  16. param.ErrorMessage,
  17. )
  18. }))
  19. router.Use(gin.Recovery())
  20. router.GET("/ping", func(c *gin.Context) {
  21. c.String(200, "pong")
  22. })
  23. router.Run(":8080")
  24. }

样本输出:

  1. ::1 - [Fri, 07 Dec 2018 17:04:38 JST] "GET /ping HTTP/1.1 200 122.767µs "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.80 Safari/537.36" "

2.3 、 控制日志输出颜色

路由的默认日志是:

  1. [GIN-debug] POST /foo --> main.main.func1 (3 handlers)
  2. [GIN-debug] GET /bar --> main.main.func2 (3 handlers)
  3. [GIN-debug] GET /status --> main.main.func3 (3 handlers)

如果要以给定格式(例如 JSON、键值或其他格式)记录此信息,则可以使用
gin.DebugPrintRouteFunc 。在下面的示例中,我们使用标准日志包记录所有路由,但您可以使用其他适合您需求的日志工具。

  1. import (
  2. "log"
  3. "net/http"
  4. "github.com/gin-gonic/gin"
  5. )
  6. func main() {
  7. r := gin.Default()
  8. gin.DebugPrintRouteFunc = func(httpMethod, absolutePath, handlerName string, nuHandlers int) {
  9. log.Printf("endpoint %v %v %v %v\n", httpMethod, absolutePath, handlerName, nuHandlers)
  10. }
  11. r.POST("/foo", func(c *gin.Context) {
  12. c.JSON(http.StatusOK, "foo")
  13. })
  14. r.GET("/bar", func(c *gin.Context) {
  15. c.JSON(http.StatusOK, "bar")
  16. })
  17. r.GET("/status", func(c *gin.Context) {
  18. c.JSON(http.StatusOK, "ok")
  19. })
  20. // Listen and Server in http://0.0.0.0:8080
  21. r.Run()
  22. }


三、 中间件

3.1 、 不使用默认的中间件

使用

  1. r := gin.New()

代替

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

3.2 、 使用中间件

  1. func main() {
  2. // 新建一个没有任何默认中间件的路由
  3. r := gin.New()
  4. // 全局中间件
  5. // Logger 中间件将日志写入 gin.DefaultWriter,即使你将 GIN_MODE 设置为 release。
  6. // By default gin.DefaultWriter = os.Stdout
  7. r.Use(gin.Logger())
  8. // Recovery 中间件会 recover 任何 panic。如果有 panic 的话,会写入 500。
  9. r.Use(gin.Recovery())
  10. // 你可以为每个路由添加任意数量的中间件。
  11. r.GET("/benchmark", MyBenchLogger(), benchEndpoint)
  12. // 认证路由组
  13. // authorized := r.Group("/", AuthRequired())
  14. // 和使用以下两行代码的效果完全一样:
  15. authorized := r.Group("/")
  16. // 路由组中间件! 在此例中,我们在 "authorized" 路由组中使用自定义创建的
  17. // AuthRequired() 中间件
  18. authorized.Use(AuthRequired())
  19. {
  20. authorized.POST("/login", loginEndpoint)
  21. authorized.POST("/submit", submitEndpoint)
  22. authorized.POST("/read", readEndpoint)
  23. // 嵌套路由组
  24. testing := authorized.Group("testing")
  25. testing.GET("/analytics", analyticsEndpoint)
  26. }
  27. // 监听并在 0.0.0.0:8080 上启动服务
  28. r.Run(":8080")
  29. }

3.3 、 自定义中间件

  1. func Logger() gin.HandlerFunc {
  2. return func(c *gin.Context) {
  3. t := time.Now()
  4. // Set example variable
  5. c.Set("example", "12345")
  6. // before request
  7. c.Next()
  8. // after request
  9. latency := time.Since(t)
  10. log.Print(latency)
  11. // access the status we are sending
  12. status := c.Writer.Status()
  13. log.Println(status)
  14. }
  15. }
  16. func main() {
  17. r := gin.New()
  18. r.Use(Logger())
  19. r.GET("/test", func(c *gin.Context) {
  20. example := c.MustGet("example").(string)
  21. // it would print: "12345"
  22. log.Println(example)
  23. })
  24. // Listen and serve on 0.0.0.0:8080
  25. r.Run(":8080")
  26. }

3.4 、 中间件中的 Goroutines

在中间件或处理程序中启动新的 Goroutines 时,不应使用其中的原始上下文,而必须使用只读副本。
(什么意思??)

  1. func main() {
  2. r := gin.Default()
  3. r.GET("/long_async", func(c *gin.Context) {
  4. // create copy to be used inside the goroutine
  5. cCp := c.Copy()
  6. go func() {
  7. // simulate a long task with time.Sleep(). 5 seconds
  8. time.Sleep(5 * time.Second)
  9. // note that you are using the copied context "cCp", IMPORTANT
  10. log.Println("Done! in path " + cCp.Request.URL.Path)
  11. }()
  12. })
  13. r.GET("/long_sync", func(c *gin.Context) {
  14. // simulate a long task with time.Sleep(). 5 seconds
  15. time.Sleep(5 * time.Second)
  16. // since we are NOT using a goroutine, we do not have to copy the context
  17. log.Println("Done! in path " + c.Request.URL.Path)
  18. })
  19. // Listen and serve on 0.0.0.0:8080
  20. r.Run(":8080")
  21. }

3.5 、 使用BasicAuth中间件

  1. // 模拟一些私人数据
  2. var secrets = gin.H{
  3. "foo": gin.H{"email": "foo@bar.com", "phone": "123433"},
  4. "austin": gin.H{"email": "austin@example.com", "phone": "666"},
  5. "lena": gin.H{"email": "lena@guapa.com", "phone": "523443"},
  6. }
  7. func main() {
  8. r := gin.Default()
  9. // 路由组使用 gin.BasicAuth() 中间件
  10. // gin.Accounts 是 map[string]string 的一种快捷方式
  11. authorized := r.Group("/admin", gin.BasicAuth(gin.Accounts{
  12. "foo": "bar",
  13. "austin": "1234",
  14. "lena": "hello2",
  15. "manu": "4321",
  16. }))
  17. // /admin/secrets 端点
  18. // 触发 "localhost:8080/admin/secrets
  19. authorized.GET("/secrets", func(c *gin.Context) {
  20. // 获取用户,它是由 BasicAuth 中间件设置的
  21. user := c.MustGet(gin.AuthUserKey).(string)
  22. if secret, ok := secrets[user]; ok {
  23. c.JSON(http.StatusOK, gin.H{"user": user, "secret": secret})
  24. } else {
  25. c.JSON(http.StatusOK, gin.H{"user": user, "secret": "NO SECRET :("})
  26. }
  27. })
  28. // 监听并在 0.0.0.0:8080 上启动服务
  29. r.Run(":8080")
  30. }

3.5 、 Example

https://github.com/gin-gonic/examples/blob/master/new_relic/main.go
https://gitee.com/critsun/gin-examples/blob/master/new_relic/main.go

  1. package main
  2. import (
  3. "log"
  4. "net/http"
  5. "os"
  6. "github.com/gin-gonic/gin"
  7. "github.com/newrelic/go-agent"
  8. )
  9. const (
  10. // NewRelicTxnKey is the key used to retrieve the NewRelic Transaction from the context
  11. NewRelicTxnKey = "NewRelicTxnKey"
  12. )
  13. // NewRelicMonitoring is a middleware that starts a newrelic transaction, stores it in the context, then calls the next handler
  14. func NewRelicMonitoring(app newrelic.Application) gin.HandlerFunc {
  15. return func(ctx *gin.Context) {
  16. txn := app.StartTransaction(ctx.Request.URL.Path, ctx.Writer, ctx.Request)
  17. defer txn.End()
  18. ctx.Set(NewRelicTxnKey, txn)
  19. ctx.Next()
  20. }
  21. }
  22. func main() {
  23. router := gin.Default()
  24. cfg := newrelic.NewConfig(os.Getenv("APP_NAME"), os.Getenv("NEW_RELIC_API_KEY"))
  25. app, err := newrelic.NewApplication(cfg)
  26. if err != nil {
  27. log.Printf("failed to make new_relic app: %v", err)
  28. } else {
  29. router.Use(NewRelicMonitoring(app))
  30. }
  31. router.GET("/", func(c *gin.Context) {
  32. c.String(http.StatusOK, "Hello World!\n")
  33. })
  34. router.Run()
  35. }

四、 自定义验证器

  1. package main
  2. import (
  3. "net/http"
  4. "time"
  5. "github.com/gin-gonic/gin"
  6. "github.com/gin-gonic/gin/binding"
  7. "github.com/go-playground/validator/v10"
  8. )
  9. // Booking contains binded and validated data.
  10. type Booking struct {
  11. CheckIn time.Time `form:"check_in" binding:"required,bookabledate" time_format:"2006-01-02"`
  12. CheckOut time.Time `form:"check_out" binding:"required,gtfield=CheckIn,bookabledate" time_format:"2006-01-02"`
  13. }
  14. var bookableDate validator.Func = func(fl validator.FieldLevel) bool {
  15. date, ok := fl.Field().Interface().(time.Time)
  16. if ok {
  17. today := time.Now()
  18. if today.After(date) {
  19. return false
  20. }
  21. }
  22. return true
  23. }
  24. func main() {
  25. route := gin.Default()
  26. if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
  27. v.RegisterValidation("bookabledate", bookableDate)
  28. }
  29. route.GET("/bookable", getBookable)
  30. route.Run(":8085")
  31. }
  32. func getBookable(c *gin.Context) {
  33. var b Booking
  34. if err := c.ShouldBindWith(&b, binding.Query); err == nil {
  35. c.JSON(http.StatusOK, gin.H{"message": "Booking dates are valid!"})
  36. } else {
  37. c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
  38. }
  39. }

测试:

  1. $ curl "localhost:8085/bookable?check_in=2118-04-16&check_out=2118-04-17"
  2. {"message":"Booking dates are valid!"}
  3. $ curl "localhost:8085/bookable?check_in=2118-03-10&check_out=2118-03-09"
  4. {"error":"Key: 'Booking.CheckOut' Error:Field validation for 'CheckOut' failed on the 'gtfield' tag"}
  • tips:

如果参数校验规则很多,可以将其设置成 map [tag] validator.Func
统一注册之后在主函数调用

五、 参数绑定

5.1 、 模型绑定和验证

要将请求体绑定到结构体中,使用模型绑定。 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 会报错。

  1. // 绑定 JSON
  2. type Login struct {
  3. User string `form:"user" json:"user" xml:"user" binding:"required"`
  4. Password string `form:"password" json:"password" xml:"password" binding:"required"`
  5. }
  6. func main() {
  7. router := gin.Default()
  8. // 绑定 JSON ({"user": "manu", "password": "123"})
  9. router.POST("/loginJSON", func(c *gin.Context) {
  10. var json Login
  11. if err := c.ShouldBindJSON(&json); err != nil {
  12. c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
  13. return
  14. }
  15. if json.User != "manu" || json.Password != "123" {
  16. c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
  17. return
  18. }
  19. c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})
  20. })
  21. // 绑定 XML (
  22. // <?xml version="1.0" encoding="UTF-8"?>
  23. // <root>
  24. // <user>manu</user>
  25. // <password>123</password>
  26. // </root>)
  27. router.POST("/loginXML", func(c *gin.Context) {
  28. var xml Login
  29. if err := c.ShouldBindXML(&xml); err != nil {
  30. c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
  31. return
  32. }
  33. if xml.User != "manu" || xml.Password != "123" {
  34. c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
  35. return
  36. }
  37. c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})
  38. })
  39. // 绑定 HTML 表单 (user=manu&password=123)
  40. router.POST("/loginForm", func(c *gin.Context) {
  41. var form Login
  42. // 根据 Content-Type Header 推断使用哪个绑定器。
  43. if err := c.ShouldBind(&form); err != nil {
  44. c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
  45. return
  46. }
  47. if form.User != "manu" || form.Password != "123" {
  48. c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
  49. return
  50. }
  51. c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})
  52. })
  53. // 监听并在 0.0.0.0:8080 上启动服务
  54. router.Run(":8080")
  55. }

请求示例:

  1. $ curl -v -X POST \
  2. http://localhost:8080/loginJSON \
  3. -H 'content-type: application/json' \
  4. -d '{ "user": "manu" }'
  5. > POST /loginJSON HTTP/1.1
  6. > Host: localhost:8080
  7. > User-Agent: curl/7.51.0
  8. > Accept: */*
  9. > content-type: application/json
  10. > Content-Length: 18
  11. >
  12. * upload completely sent off: 18 out of 18 bytes
  13. < HTTP/1.1 400 Bad Request
  14. < Content-Type: application/json; charset=utf-8
  15. < Date: Fri, 04 Aug 2017 03:51:31 GMT
  16. < Content-Length: 100
  17. <
  18. {"error":"Key: 'Login.Password' Error:Field validation for 'Password' failed on the 'required' tag"}

忽略验证:
使用上述的 curl 命令运行上面的示例时会返回错误。 因为示例中 Password 使用了 binding:"required"。 如果 Password 使用binding:"-", 再次运行上面的示例就不会返回错误。

5.2 、 上传文件

5.2.1 单文件

https://github.com/gin-gonic/examples/tree/master/upload-file/single

  1. func main() {
  2. router := gin.Default()
  3. // 为 multipart forms 设置较低的内存限制 (默认是 32 MiB)
  4. router.MaxMultipartMemory = 8 << 20 // 8 MiB
  5. router.POST("/upload", func(c *gin.Context) {
  6. // 单文件
  7. file, _ := c.FormFile("file")
  8. log.Println(file.Filename)
  9. // 上传文件至指定目录
  10. c.SaveUploadedFile(file, dst)
  11. c.String(http.StatusOK, fmt.Sprintf("'%s' uploaded!", file.Filename))
  12. })
  13. router.Run(":8080")
  14. }
  15. curl
  16. curl -X POST http://localhost:8080/upload \
  17. -F "file=@/Users/appleboy/test.zip" \
  18. -H "Content-Type: multipart/form-data"

5.2.2 多文件

https://github.com/gin-gonic/examples/tree/master/upload-file/multiple

  1. func main() {
  2. router := gin.Default()
  3. // 为 multipart forms 设置较低的内存限制 (默认是 32 MiB)
  4. router.MaxMultipartMemory = 8 << 20 // 8 MiB
  5. router.POST("/upload", func(c *gin.Context) {
  6. // Multipart form
  7. form, _ := c.MultipartForm()
  8. files := form.File["upload[]"]
  9. for _, file := range files {
  10. log.Println(file.Filename)
  11. // 上传文件至指定目录
  12. c.SaveUploadedFile(file, dst)
  13. }
  14. c.String(http.StatusOK, fmt.Sprintf("%d files uploaded!", len(files)))
  15. })
  16. router.Run(":8080")
  17. }
  18. curl
  19. curl -X POST http://localhost:8080/upload \
  20. -F "upload[]=@/Users/appleboy/test1.zip" \
  21. -F "upload[]=@/Users/appleboy/test2.zip" \
  22. -H "Content-Type: multipart/form-data"

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

使用 c**.**ShouldBindBodyWith

  1. type formA struct {
  2. Foo string `json:"foo" xml:"foo" binding:"required"`
  3. }
  4. type formB struct {
  5. Bar string `json:"bar" xml:"bar" binding:"required"`
  6. }
  7. func SomeHandler(c *gin.Context) {
  8. objA := formA{}
  9. objB := formB{}
  10. // 读取 c.Request.Body 并将结果存入上下文。
  11. if errA := c.ShouldBindBodyWith(&objA, binding.JSON); errA == nil {
  12. c.String(http.StatusOK, `the body should be formA`)
  13. // 这时, 复用存储在上下文中的 body。
  14. } else if errB := c.ShouldBindBodyWith(&objB, binding.JSON); errB == nil {
  15. c.String(http.StatusOK, `the body should be formB JSON`)
  16. // 可以接受其他格式
  17. } else if errB2 := c.ShouldBindBodyWith(&objB, binding.XML); errB2 == nil {
  18. c.String(http.StatusOK, `the body should be formB XML`)
  19. } else {
  20. ...
  21. }
  22. }
  • c.ShouldBindBodyWith会在绑定之前将 body 存储到上下文中。 这会对性能造成轻微影响,如果调用一次就能完成绑定的话,那就不要用这个方法。
  • 只有某些格式需要此功能,如 JSON, XML, MsgPack, ProtoBuf。 对于其他格式, 如 Query, Form, FormPost, FormMultipart 可以多次调用 c.ShouldBind() 而不会造成任任何性能损失 (详见 #1341)。

    六、 参数获取

    6.1 、 查询字符串参数

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

    6.2 、 设置和获取Cookie

    ```go import ( “fmt”

    “github.com/gin-gonic/gin” )

func main() {

  1. router := gin.Default()
  2. router.GET("/cookie", func(c *gin.Context) {
  3. cookie, err := c.Cookie("gin_cookie")
  4. if err != nil {
  5. cookie = "NotSet"
  6. c.SetCookie("gin_cookie", "test", 3600, "/", "localhost", false, true)
  7. }
  8. fmt.Printf("Cookie value: %s \n", cookie)
  9. })
  10. router.Run()

}

  1. <a name="nof1j"></a>
  2. ## 6.3 、 从reader读取数据
  3. ```go
  4. func main() {
  5. router := gin.Default()
  6. router.GET("/someDataFromReader", func(c *gin.Context) {
  7. response, err := http.Get("https://raw.githubusercontent.com/gin-gonic/logo/master/color.png")
  8. if err != nil || response.StatusCode != http.StatusOK {
  9. c.Status(http.StatusServiceUnavailable)
  10. return
  11. }
  12. reader := response.Body
  13. contentLength := response.ContentLength
  14. contentType := response.Header.Get("Content-Type")
  15. extraHeaders := map[string]string{
  16. "Content-Disposition": `attachment; filename="gopher.png"`,
  17. }
  18. c.DataFromReader(http.StatusOK, contentLength, contentType, reader, extraHeaders)
  19. })
  20. router.Run(":8080")
  21. }

七、 路由

7.1 、 路由参数

  1. func main() {
  2. router := gin.Default()
  3. // 此 handler 将匹配 /user/john 但不会匹配 /user/ 或者 /user
  4. router.GET("/user/:name", func(c *gin.Context) {
  5. name := c.Param("name")
  6. c.String(http.StatusOK, "Hello %s", name)
  7. })
  8. // 此 handler 将匹配 /user/john/ 和 /user/john/send
  9. // 如果没有其他路由匹配 /user/john,它将重定向到 /user/john/
  10. router.GET("/user/:name/*action", func(c *gin.Context) {
  11. name := c.Param("name")
  12. action := c.Param("action")
  13. message := name + " is " + action
  14. c.String(http.StatusOK, message)
  15. })
  16. router.Run(":8080")
  17. }

7.2 、 使用HTTP方法

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

八、 优雅地重启和停止

  1. 可以使用 fvbock/endless 来替换默认的 ListenAndServe。更多详细信息,请参阅 issue #296

    1. router := gin.Default()
    2. router.GET("/", handler)
    3. // [...]
    4. endless.ListenAndServe(":4242", router)
  2. 替代方案:

  • manners:可以优雅关机的 Go Http 服务器。
  • graceful:Graceful 是一个 Go 扩展包,可以优雅地关闭 http.Handler 服务器。
  • grace:Go 服务器平滑重启和零停机时间部署。

如果你使用的是 Go 1.8,可以不需要这些库!考虑使用 http.Server 内置的 Shutdown() 方法优雅地关机. 请参阅 gin 完整的 graceful-shutdown 示例。

  1. // +build go1.8
  2. package main
  3. import (
  4. "context"
  5. "log"
  6. "net/http"
  7. "os"
  8. "os/signal"
  9. "time"
  10. "github.com/gin-gonic/gin"
  11. )
  12. func main() {
  13. router := gin.Default()
  14. router.GET("/", func(c *gin.Context) {
  15. time.Sleep(5 * time.Second)
  16. c.String(http.StatusOK, "Welcome Gin Server")
  17. })
  18. srv := &http.Server{
  19. Addr: ":8080",
  20. Handler: router,
  21. }
  22. go func() {
  23. // 服务连接
  24. if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
  25. log.Fatalf("listen: %s\n", err)
  26. }
  27. }()
  28. // 等待中断信号以优雅地关闭服务器(设置 5 秒的超时时间)
  29. quit := make(chan os.Signal)
  30. signal.Notify(quit, os.Interrupt)
  31. <-quit
  32. log.Println("Shutdown Server ...")
  33. ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
  34. defer cancel()
  35. if err := srv.Shutdown(ctx); err != nil {
  36. log.Fatal("Server Shutdown:", err)
  37. }
  38. log.Println("Server exiting")
  39. }

参考:https://gin-gonic.com/docs/examples/custom-middleware/
待续….

问题、一个bug

  1. 在url使用path传参时,若参数不正确,会出现找不到url的问题,1.7.7修复了这个问题https://github.com/gin-gonic/gin/pull/2924

image.png