• Gin 基础


    官网
    github
    middleware
    Gin是一个用Go (Golang)编写的HTTP web框架。它的特点是一个类似Martini-like API,由于使用了httprouter,它的性能提高了40倍。如果您需要性能和良好的生产力,您将爱上Gin。

    Martini 是 Go 生态中的一个 Web 框架:
    Martini-like API 是指 Gin 框架内部的 API 命名风格、传参形式跟 Martini 类似。比如定义一个路由分组:

    // Martini
    m.Group("/users", func(r martini.Router) {
        r.Post("/", CreateUserEndpoint)
        r.Get("/:id", GetUserEndpoint)
        r.Put("/:id", UpdateUserEndpoint)
        r.Delete("/:id", DeleteUserEndpoint)
    })
    
    // Gin
    r := engine.Group("/users") {
        r.POST("/", CreateUserEndpoint)
        r.GET("/:id", GetUserEndpoint)
        r.PUT("/:id", UpdateUserEndpoint)
        r.DELETE("/:id", DeleteUserEndpoint)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    因为 Martini 诞生的比较早(2013 年),所以作为 2015 年才出现的“后辈” Gin 来说保持一个跟当时比较流行的框架一样的 API,比较容易吸引人们去学习和理解、也降低了开发者们现有项目的迁移成本。

    一、Gin v1. stable

    1.1 特点:

    1、零配置路由器
    2、仍然是最快的http路由器和框架。从路由到写入。
    3、完整的单元测试套件。
    4、Battle tested
    5、API被冻结,新版本不会破坏你的代码。

    1.2 Build with jsoniter

    jsoniter

    一个对“encoding/json” 100%兼容的高性能插拔的替换

    Gin使用 encoding/json 作为默认的json包,但你可以通过从其他标签构建来更改为 jsoniter

    go build -tags=jsoniter .
    
    • 1

    go-json

    go build -tags=go_json .
    
    • 1

    sonic

    you have to ensure that your cpu support avx instruction

    go build -tags="sonic avx" .
    
    • 1

    构建时不使用MsgPack渲染功能

    Gin默认启用MsgPack渲染功能。但是可以通过指定nomsgpack构建标记禁用此功能。

    go build -tags=nomsgpack .
    
    • 1

    这有助于减少可执行文件的二进制大小

    1.4 部署

    Gin项目可以轻松地部署在任何云提供商上。

    1.5 测试

    如何为Gin写测试用例?
    net/http/httptest是HTTP测试的首选方法。

    // example.go
    package main
    
    import "github.com/gin-gonic/gin"
    
    func setupRouter() *gin.Engine {
    	r := gin.Default()
    	r.GET("/ping", func(c *gin.Context) {
    		c.String(200, "pong")
    	})
    	return r
    }
    
    func main() {
    	r := setupRouter()
    	r.Run(":8080")
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    测试上面的代码示例:

    // example_test.go
    package main
    
    import (
    	"net/http"
    	"net/http/httptest"
    	"testing"
    	"github.com/stretchr/testify/assert"
    )
    
    func TestPingRoute(t *testing.T) {
    	router := setupRouter()
    
    	w := httptest.NewRecorder()
    	req, _ := http.NewRequest("GET", "/ping", nil)
    	router.ServeHTTP(w, req)
    
    	assert.Equal(t, 200, w.Code)
    	assert.Equal(t, "pong", w.Body.String())
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    二、API

    gin 包

    1) SetMode()

    func SetMode(value string)
    SetMode根据输入字符串设置gin模式

    const (
    	// DebugMode indicates gin mode is debug.
    	DebugMode = "debug"
    	// ReleaseMode indicates gin mode is release.
    	ReleaseMode = "release"
    	// TestMode indicates gin mode is test.
    	TestMode = "test"
    )
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    gin.SetMode(gin.ReleaseMode)
    
    • 1

    2) Engine

    type Engine struct {
    }
    
    • 1
    • 2

    gin.Default() 返回一个已经附加了 LoggerRecovery 中间件的引擎实例(Engine)。

    // ServeHTTP遵循 http.Handler 接口
    func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request)
    
    • 1
    • 2

    3) Context

    type Context struct {
    	writermem responseWriter
    	Request   *http.Request
    	Writer    ResponseWriter
    
    	Params   Params
    	handlers HandlersChain
    	index    int8
    	fullPath string
    
    	engine       *Engine
    	params       *Params
    	skippedNodes *[]skippedNode
    
    	// This mutex protects Keys map.
    	mu sync.RWMutex
    
    	// Keys is a key/value pair exclusively for the context of each request.
    	Keys map[string]any
    
    	// Errors is a list of errors attached to all the handlers/middlewares who used this context.
    	Errors errorMsgs
    
    	// Accepted defines a list of manually accepted formats for content negotiation.
    	Accepted []string
    
    	// queryCache caches the query result from c.Request.URL.Query().
    	queryCache url.Values
    
    	// formCache caches c.Request.PostForm, which contains the parsed form data from POST, PATCH,
    	// or PUT body parameters.
    	formCache url.Values
    
    	// SameSite allows a server to define a cookie attribute making it impossible for
    	// the browser to send this cookie along with cross-site requests.
    	sameSite http.SameSite
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    1、String()

    String 将给定的字符串写入响应体
    func (c *Context) String(code int, format string, values ...any)

    2、AbortWithStatus()
    func (c *Context) AbortWithStatus(code int)
    
    • 1

    AbortWithStatus调用’ Abort() '并使用指定的状态代码写入 header。
    例如,验证请求的失败尝试可以用:context.AbortWithStatus(401)

    3、 ShouldBindJSON()
    func (c *Context) ShouldBindJSON(obj any) error
    
    • 1

    ShouldBindJSON is a shortcut for c.ShouldBindWith(obj, binding.JSON).
    ShouldBindQuery 函数只绑定查询参数,而不绑定post数据

    ShouldBind

    func (c *Context) ShouldBind(obj any) error
    
    • 1

    ShouldBind函数即可绑定查询参数,也可绑定post数据

    4、JSON()
    func (c *Context) JSON(code int, obj any)
    
    • 1

    JSON将给定的结构作为JSON序列化到响应体中。它还将Content-Type设置为“application/json”

    5、 ShouldBindWith()
    func (c *Context) ShouldBindWith(obj any, b binding.Binding) error
    
    • 1

    ShouldBindWith 使用指定的绑定引擎绑定传递的结构指针

    6、SaveUploadedFile()
    func (c *Context) SaveUploadedFile(file *multipart.FileHeader, dst string) error
    
    • 1

    SaveUploadedFile 将表单文件上传到特定的 dst。

    7、Static()
    func (group *RouterGroup) Static(relativePath, root string) IRoutes
    
    • 1

    Static为来自给定文件系统 root 的文件提供服务。
    内部使用是http.FileServer,因此使用http.NotFound被用来代替Router的NotFound处理程序。

    StaticFS

    func (group *RouterGroup) StaticFS(relativePath string, fs http.FileSystem) IRoutes
    
    • 1

    StaticFile

    func (group *RouterGroup) StaticFile(relativePath, filepath string) IRoutes
    
    func (group *RouterGroup) StaticFileFS(relativePath, filepath string, fs http.FileSystem) IRoutes
    
    • 1
    • 2
    • 3
    8、Set()
    func (c *Context) Set(key string, value any)
    
    • 1

    Set用于专门为该上下文中存储一个新的键/值对。如果之前没有使用过 c.Keys,它也会惰性初始化它。

    Get

    func (c *Context) Get(key string) (value any, exists bool) 
    
    • 1

    Get返回给定键的值,即:(value, true)。
    如果值不存在,则返回(nil, false)

    MustGet

    func (c *Context) MustGet(key string) any
    
    • 1
    9、Copy()
    func (c *Context) Copy() *Context
    
    • 1

    Copy返回当前上下文的副本,可以安全地在请求的作用域之外使用。
    当必须将上下文传递给goroutine时,必须使用此方法。

    4) LoggerWithFormatter

    2.1 Using GET, POST, PUT, PATCH, DELETE and OPTIONS

    func main() {
      // Creates a gin router with default middleware:
      // logger and recovery (crash-free) middleware
      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)
    
      // By default it serves on :8080 unless a
      // PORT environment variable was defined.
      router.Run()
      // router.Run(":3000") for a hard coded port
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    RouterGroup

    func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes
    
    // HandlerFunc defines the handler used by gin middleware as return value.
    type HandlerFunc func(*Context)
    
    • 1
    • 2
    • 3
    • 4

    2.2 参数

    2.2.1 路径参数

    1、func (c *Context) Param(key string) string
    Param 返回URL参数的值。
    It is a shortcut for c.Params.ByName(key)

    2、func (c *Context) FullPath() string
    FullPath返回匹配的路由全路径。没有找到路由则返回一个空字符串。

    func main() {
      router := gin.Default()
    
      // This handler will match /user/john but will not match /user/ or /user
      router.GET("/user/:name", func(c *gin.Context) {
        name := c.Param("name")
        c.String(http.StatusOK, "Hello %s", name)
      })
    
      // However, this one will match /user/john/ and also /user/john/send
      // If no other routers match /user/john, it will redirect to /user/john/
      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)
      })
    
      // For each matched request Context will hold the route definition
      router.POST("/user/:name/*action", func(c *gin.Context) {
        b := c.FullPath() == "/user/:name/*action" // true
        c.String(http.StatusOK, "%t", b)
      })
    
      // This handler will add a new router for /user/groups.
      // Exact routes are resolved before param routes, regardless of the order they were defined.
      // Routes starting with /user/groups are never interpreted as /user/:name/... routes
      router.GET("/user/groups", func(c *gin.Context) {
        c.String(http.StatusOK, "The available groups are [...]")
      })
    
      router.Run(":8080")
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    2.2.2 Querystring parameters

    1、func (c *Context) Query(key string) (value string)
    Query 则返回url查询值,如果不存在返回空字符串("")
    它是c.Request.URL.Query().Get(key)的快捷方式

    GET /path?id=1234&name=Manu&value=
    c.Query(“id”) == “1234”
    c.Query(“name”) == “Manu”
    c.Query(“value”) == “”
    c.Query(“wtf”) == “”

    2、func (c *Context) DefaultQuery(key, defaultValue string) string
    DefaultQuery将返回 url查询值,如果不存在,将返回指定的字符串

    3、func (c *Context) GetQuery(key string) (string, bool)
    GetQuery类似于Query(),它返回键控的url查询值
    如果它存在 (value, true) (即使值是空字符串),
    否则返回("",false)

    It is shortcut for `c.Request.URL.Query().Get(key)`
        GET /?name=Manu&lastname=
        ("Manu", true) == c.GetQuery("name")
        ("", false) == c.GetQuery("id")
        ("", true) == c.GetQuery("lastname")
    
    • 1
    • 2
    • 3
    • 4
    • 5
    func main() {
      router := gin.Default()
    
      // Query string parameters are parsed using the existing underlying request object.
      // The request responds to a url matching:  /welcome?firstname=Jane&lastname=Doe
      router.GET("/welcome", func(c *gin.Context) {
        firstname := c.DefaultQuery("firstname", "Guest")
        lastname := c.Query("lastname") // shortcut for c.Request.URL.Query().Get("lastname")
    
        c.String(http.StatusOK, "Hello %s %s", firstname, lastname)
      })
      router.Run(":8080")
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    2.2.3 query + post form

    查询参数和POST格式可以同时存在

    POST /post?id=1234&page=1 HTTP/1.1
    Content-Type: application/x-www-form-urlencoded
    
    name=manu&message=this_is_great
    
    • 1
    • 2
    • 3
    • 4

    2.2.4 Map为查询字符串或postform参数

    POST /post?ids[a]=1234&ids[b]=hello HTTP/1.1
    Content-Type: application/x-www-form-urlencoded
    
    names[first]=thinkerou&names[second]=tianou
    
    • 1
    • 2
    • 3
    • 4
    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]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    2.3 Multipart/Urlencoded Form

    接收post表单提交的数据

    1、func (c *Context) PostForm(key string) (value string)
    PostForm从POST urlenencoded或multipart form 表单返回指定的键,如果它存在,则返回一个空字符串’ (“”)’
    只能获取 TEXT 类型

    2、func (c *Context) DefaultPostForm(key, defaultValue string) string

    3、func (c *Context) GetPostForm(key string) (string, bool)

    func main() {
      router := gin.Default()
    
      router.POST("/form_post", func(c *gin.Context) {
        message := c.PostForm("message")
        nick := c.DefaultPostForm("nick", "anonymous")
    
        c.JSON(http.StatusOK, gin.H{
          "status":  "posted",
          "message": message,
          "nick":    nick,
        })
      })
      router.Run(":8080")
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    表单数据处理

    1)(enctype)

    表单的默认编码方式 application/x-www-form-urlencoded

    上传文件的编码方式 multipart/form-data

    互联网应用常用编码 application/json

    传统企业级服务编码 application/xml

    2)urlencoded 格式
    Key1= value1&key2=value2&abc=123…

    只能将字符串发送服务端
    3) 上传文件

    4) JSON

    2.4 上传文件

    2.4.1 Single file

    file.Filename SHOULD NOT be trusted.

    1) FormFile

    func (c *Context) FormFile(name string) (*multipart.FileHeader, error)
    FormFile 返回提供的表单键的第一个文件。

    func main() {
      router := gin.Default()
      // Set a lower memory limit for multipart forms (default is 32 MiB)
      router.MaxMultipartMemory = 8 << 20  // 8 MiB
      router.POST("/upload", func(context *gin.Context) {
    		file, _ := context.FormFile("file")
    		log.Println(file.Filename)
    
    		fileName := filepath.Base(file.Filename)
    		if err := context.SaveUploadedFile(file, fileName); err != nil {
    			context.String(http.StatusBadRequest, "upload file err: %s", err.Error())
    			return
    		}
    
    		context.String(http.StatusOK, "File %s uploaded successfully.", file.Filename)
    	})
    	
      router.Run(":8080")
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    curl -X POST http://localhost:8080/upload \
      -F "file=@/Users/appleboy/test.zip" \
      -H "Content-Type: multipart/form-data"
    
    • 1
    • 2
    • 3

    2.4.2 Multiple files

    	r.POST("/multi-upload", func(context *gin.Context) {
    		form, _ := context.MultipartForm()
    		files := form.File["upload[]"]
    		for _, file := range files {
    
    			//file, _ := context.FormFile("file")
    			log.Println(file.Filename)
    
    			fileName := filepath.Base(file.Filename)
    			if err := context.SaveUploadedFile(file, fileName); err != nil {
    				context.String(http.StatusBadRequest, "upload file err: %s", err.Error())
    				return
    			}
    
    		}
    
    		context.String(http.StatusOK, "%d files uploaded!", len(files))
    	})
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    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"
    
    • 1
    • 2
    • 3
    • 4

    2.5 分组路由

    2.5.1 Engine.Group()

    func (group *RouterGroup) Group(relativePath string, handlers ...HandlerFunc) *RouterGroup

    Group创建一个新的路由器组。您应该添加所有具有公共中间件或相同路径前缀的路由。
    例如,所有使用公共中间件进行授权的路由都可以分组。

      router := gin.Default()
    
      // Simple group: v1
      v1 := router.Group("/v1")
      {
        v1.POST("/login", loginEndpoint)
        v1.POST("/submit", submitEndpoint)
        v1.POST("/read", readEndpoint)
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    2.6 默认没有中间件的空白Gin

    2.6.1 func New() *Engine

    New返回一个新的空白引擎实例,不附加任何中间件。

    By default, the configuration is:
     - RedirectTrailingSlash:  true
     - RedirectFixedPath:      false
     - HandleMethodNotAllowed: false
     - ForwardedByClientIP:    true
     - UseRawPath:             false
     - UnescapePathValues:     true
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    Use

    r := gin.New()
    
    • 1

    instead of

    // Default With the Logger and Recovery middleware already attached
    r := gin.Default()
    
    • 1
    • 2

    2.7 使用中间件

    2.7.1 Engine.Use()

    func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes
    Use将全局中间件附加到路由器。例如,通过Use()附加的中间件将包含在每个请求的处理程序链中。甚至是404,405,静态文件…
    例如,这是logger 或 error管理中间件的合适位置。

    func main() {
      // Creates a router without any middleware by default
      r := gin.New()
    
      // Global middleware
      // Logger middleware will write the logs to gin.DefaultWriter even if you set with GIN_MODE=release.
      // By default gin.DefaultWriter = os.Stdout
      r.Use(gin.Logger())
    
      // Recovery middleware recovers from any panics and writes a 500 if there was one.
      r.Use(gin.Recovery())
    
      // Per route middleware, you can add as many as you desire.
      r.GET("/benchmark", MyBenchLogger(), benchEndpoint)
    
      // Authorization group
      // authorized := r.Group("/", AuthRequired())
      // exactly the same as:
      authorized := r.Group("/")
      // per group middleware! in this case we use the custom created
      // AuthRequired() middleware just in the "authorized" group.
      authorized.Use(AuthRequired())
      {
        authorized.POST("/login", loginEndpoint)
        authorized.POST("/submit", submitEndpoint)
        authorized.POST("/read", readEndpoint)
    
        // nested group
        testing := authorized.Group("testing")
        // visit 0.0.0.0:8080/testing/analytics
        testing.GET("/analytics", analyticsEndpoint)
      }
    
      // Listen and serve on 0.0.0.0:8080
      r.Run(":8080")
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    2.8 自定义恢复行为

      // Recovery middleware recovers from any panics and writes a 500 if there was one.
      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)
      }))
    
    	engine.GET("/panic", func(c *gin.Context) {
    		// panic with a string -- the custom middleware could save this to a database or report it to the user
    		panic("foo")
    	})
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    Gin源码分析 - 中间件(5)- Recovery
    Recovery中间件,能够捕获HTTP请求处理过程中产生的所有panic,并返回500错误。

    没有使用recovery中间件,没有返回任何数据,浏览器展示如下:
    在这里插入图片描述

    2.9 日志

    2.9.1 如何写日志文件

     To support coloring in Windows use:
     		import "github.com/mattn/go-colorable"
     		gin.DefaultWriter = colorable.NewColorableStdout()
    
    • 1
    • 2
    • 3
    func main() {
        // Disable Console Color, you don't need console color when writing the logs to file.
        gin.DisableConsoleColor()
    
        // Logging to a file.
        f, _ := os.Create("gin.log")
        gin.DefaultWriter = io.MultiWriter(f)
    
        // Use the following code if you need to write the logs to file and console at the same time.
        // gin.DefaultWriter = io.MultiWriter(f, os.Stdout)
    
        router := gin.Default()
        router.GET("/ping", func(c *gin.Context) {
            c.String(http.StatusOK, "pong")
        })
    
        router.Run(":8080")
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    2.9.2 自定义日志格式

    func main() {
      router := gin.New()
    
      // LoggerWithFormatter middleware will write the logs to gin.DefaultWriter
      // By default gin.DefaultWriter = os.Stdout
      router.Use(gin.LoggerWithFormatter(func(param gin.LogFormatterParams) string {
    
        // your custom format
        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(http.StatusOK, "pong")
      })
    
      router.Run(":8080")
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    ::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" "
    
    • 1

    2.10 模型绑定和验证

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

    Gin使用go-playground/validator/v10进行验证。查看tag使用

    注意,需要在想要绑定的所有字段上设置相应的绑定标记。例如,当从JSON绑定时,设置JSON:"fieldname"

    此外,Gin还提供了两组绑定方法:

    • Type - Must bind
      Methods - Bind, BindJSON, BindXML, BindQuery, BindYAML, BindHeader, BindTOML
      Behavior 这些方法在底层使用MustBindWith。如果存在绑定错误,则使用c.AbortWithError(400, err).SetType(ErrorTypeBind)终止请求。这将把响应状态码设置为400,并将Content-Type报头设置为text/plain; charset=utf-8。注意,如果您尝试在此之后设置响应代码,它将导致一个警告[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, ShouldBindHeader, ShouldBindTOML,
      Behavior 这些方法在底层使用ShouldBindWith。如果存在绑定错误,则返回错误,开发人员有责任适当地处理请求和错误。
    // Binding from 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()
    
      // Example for binding JSON ({"user": "manu", "password": "123"})
      router.POST("/loginJSON", func(c *gin.Context) {
        var json Login
        if 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"})
      })
    
      // Example for binding XML (
      //  
      //  
      //    manu
      //    123
      //  )
      router.POST("/loginXML", func(c *gin.Context) {
        var xml Login
        if 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"})
      })
    
      // Example for binding a HTML form (user=manu&password=123)
      router.POST("/loginForm", func(c *gin.Context) {
        var form Login
        // This will infer what binder to use depending on the 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"})
      })
    
      // Listen and serve on 0.0.0.0:8080
      router.Run(":8080")
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    $ curl -v -X POST \
      http://localhost:8080/loginJSON \
      -H 'content-type: application/json' \
      -d '{ "user": "manu" }
    //返回
    {"error":"Key: 'Login.Password' Error:Field validation for 'Password' failed on the 'required' tag"}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    If use binding:"-" for Password, then it will not return error when running the above example again.

    2.10.1 自定义的校验器

    // Booking contains binded and validated data.
    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 {
    		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 Booking
    	if 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()})
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    2.10.2 只绑定查询字符串

    ShouldBindQuery 函数只绑定查询参数,而不绑定post数据

    type Person struct {
      Name    string `form:"name"`
      Address string `form:"address"`
    }
    
    func main() {
      route := gin.Default()
      route.Any("/testing", startPage)
      route.Run(":8080")
    }
    
    func startPage(c *gin.Context) {
      var person Person
      if c.ShouldBindQuery(&person) == nil {
        log.Println("====== Only Bind By Query String ======")
        log.Println(person.Name)
        log.Println(person.Address)
      }
      c.String(http.StatusOK, "Success")
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    2.10.3 Bind Query String or Post Data

    ShouldBind函数即可绑定查询参数,也可绑定post数据

    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"`
    }
    
    func main() {
      route := gin.Default()
      route.GET("/testing", startPage)
      route.Run(":8085")
    }
    
    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#L88
            if c.ShouldBind(&person) == nil {
                    log.Println(person.Name)
                    log.Println(person.Address)
                    log.Println(person.Birthday)
                    log.Println(person.CreateTime)
                    log.Println(person.UnixTime)
            }
    
      c.String(http.StatusOK, "Success")
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    2.10.4 Bind Uri(绑定路径参数)

    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(http.StatusBadRequest, gin.H{"msg": err.Error()})
          return
        }
        c.JSON(http.StatusOK, gin.H{"name": person.Name, "uuid": person.ID})
      })
      route.Run(":8088")
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    2.10.5 Bind Header

    ShouldBindHeader

    type testHeader struct {
      Rate   int    `header:"Rate"`
      Domain string `header:"Domain"`
    }
    
    func main() {
      r := gin.Default()
      r.GET("/", func(c *gin.Context) {
        h := testHeader{}
    
        if err := c.ShouldBindHeader(&h); err != nil {
          c.JSON(http.StatusOK, err)
        }
    
        fmt.Printf("%#v\n", h)
        c.JSON(http.StatusOK, gin.H{"Rate": h.Rate, "Domain": h.Domain})
      })
    
      r.Run()
    
    // client
    // curl -H "rate:300" -H "domain:music" 127.0.0.1:8080/
    // output
    // {"Domain":"music","Rate":300}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    2.10.6 Bind HTML checkboxes

    type myForm struct {
        Colors []string `form:"colors[]"`
    }
    
    ...
    
    func formHandler(c *gin.Context) {
        var fakeForm myForm
        c.ShouldBind(&fakeForm)
        c.JSON(http.StatusOK, gin.H{"color": fakeForm.Colors})
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    form.html

    <form action="/" method="POST">
        <p>Check some colorsp>
        <label for="red">Redlabel>
        <input type="checkbox" name="colors[]" value="red" id="red">
        <label for="green">Greenlabel>
        <input type="checkbox" name="colors[]" value="green" id="green">
        <label for="blue">Bluelabel>
        <input type="checkbox" name="colors[]" value="blue" id="blue">
        <input type="submit">
    form>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    2.10.7 Multipart/Urlencoded binding

    type ProfileForm struct {
      Name   string                `form:"name" binding:"required"`
      Avatar *multipart.FileHeader `form:"avatar" binding:"required"`
    
      // or for multiple files
      // Avatars []*multipart.FileHeader `form:"avatar" binding:"required"`
    }
    
    func main() {
      router := gin.Default()
      router.POST("/profile", func(c *gin.Context) {
        // you can bind multipart form with explicit binding declaration:
        // c.ShouldBindWith(&form, binding.Form)
        // or you can simply use autobinding with ShouldBind method:
        var form ProfileForm
        // in this case proper binding will be automatically selected
        if err := c.ShouldBind(&form); err != nil {
          c.String(http.StatusBadRequest, "bad request")
          return
        }
    
        err := c.SaveUploadedFile(form.Avatar, form.Avatar.Filename)
        if err != nil {
          c.String(http.StatusInternalServerError, "unknown error")
          return
        }
    
        // db.Save(&form)
    
        c.String(http.StatusOK, "ok")
      })
      router.Run(":8080")
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    test:

    curl -X POST -v --form name=user --form "avatar=@./avatar.png" http://localhost:8080/profile
    
    • 1

    2.10.8 XML, JSON, YAML和ProtoBuf渲染

    func main() {
      r := gin.Default()
    
      // gin.H is a shortcut for 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) {
        // You also can use a struct
        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)
      })
    
      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"
        // 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)
      })
    
      // Listen and serve on 0.0.0.0:8080
      r.Run(":8080")
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    SecureJSON

    使用SecureJSON防止json被劫持。如果给定的结构是数组值,Default会在响应体前加上"while(1) "。

    JSONP

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

    func main() {
      r := gin.Default()
    
      r.GET("/JSONP", func(c *gin.Context) {
        data := gin.H{
          "foo": "bar",
        }
    
        //callback is x
        // Will output  :   x({\"foo\":\"bar\"})
        c.JSONP(http.StatusOK, data)
      })
    
      // Listen and serve on 0.0.0.0:8080
      r.Run(":8080")
    
            // client
            // curl http://127.0.0.1:8080/JSONP?callback=x
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    JSONP 说明
    JSONP(JSON with Padding)是JSON的一种“使用模式”,可用于解决主流浏览器的跨域数据访问的问题。

    AsciiJSON

    使用AsciiJSON生成仅有Ascii JSON,并且转义非ascii字符的。

    func main() {
      r := gin.Default()
    
      r.GET("/someJSON", func(c *gin.Context) {
        data := gin.H{
          "lang": "GO语言",
          "tag":  "
    "
    , } // will output : {"lang":"GO\u8bed\u8a00","tag":"\u003cbr\u003e"} c.AsciiJSON(http.StatusOK, data) }) // Listen and serve on 0.0.0.0:8080 r.Run(":8080") }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    PureJSON

    通常,JSON用它们的unicode实体替换特殊的HTML字符,例如<变成\u003c。如果您想按字面意思编码这样的字符,您可以使用PureJSON代替。该特性在Go 1.6及以下版本中不可用。

    func main() {
      r := gin.Default()
    
      // Serves unicode entities
      r.GET("/json", func(c *gin.Context) {
        c.JSON(http.StatusOK, gin.H{
          "html": "Hello, world!",
        })
      })
    
      // Serves literal characters
      r.GET("/purejson", func(c *gin.Context) {
        c.PureJSON(http.StatusOK, gin.H{
          "html": "Hello, world!",
        })
      })
    
      // listen and serve on 0.0.0.0:8080
      r.Run(":8080")
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    2.11 提供静态文件

    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")
      router.StaticFileFS("/more_favicon.ico", "more_favicon.ico", http.Dir("my_file_system"))
      
      // Listen and serve on 0.0.0.0:8080
      router.Run(":8080")
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    2.12 从文件中提供数据

    func (c *Context) File(filepath string)
    
    • 1

    File以高效的方式将指定的文件写入正文流。

    func main() {
      router := gin.Default()
    
      router.GET("/local/file", func(c *gin.Context) {
        c.File("local/file.go")
      })
    
      var fs http.FileSystem = // ...
      router.GET("/fs/file", func(c *gin.Context) {
        c.FileFromFS("fs/file.go", fs)
      })
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    2.13 从 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.Body
         defer reader.Close()
        contentLength := response.ContentLength
        contentType := 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")
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    2.14 HTML渲染

    Using LoadHTMLGlob() or 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")
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    templates/index.tmpl

    <html>
      <h1>
        {{ .title }}
      h1>
    html>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    在不同目录中使用相同名称的模板:

    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")
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    templates/posts/index.tmpl

    {{ define "posts/index.tmpl" }}
    <html><h1>
      {{ .title }}
    h1>
    <p>Using posts/index.tmplp>
    html>
    {{ end }}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    templates/users/index.tmpl

    {{ define "users/index.tmpl" }}
    <html><h1>
      {{ .title }}
    h1>
    <p>Using users/index.tmplp>
    html>
    {{ end }}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    2.14.1 自定义模板渲染器

    你也可以使用你自己的html模板渲染

    import "html/template"
    
    func main() {
      router := gin.Default()
      html := template.Must(template.ParseFiles("file1", "file2"))
      router.SetHTMLTemplate(html)
      router.Run(":8080")
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    2.14.1 自定义分隔符

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

    2.14.2 自定义模板函数

    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", gin.H{
                "now": time.Date(2017, 07, 01, 0, 0, 0, 0, time.UTC),
            })
        })
    
        router.Run(":8080")
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    raw.tmpl

    Date: {[{.now | formatAsDate}]}
    
    • 1
    Multitemplate

    Gin默认允许只使用一个html.Templatea multitemplate render

    2.15 重定向

    发出HTTP重定向很容易。支持内部和外部位置。

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

    从POST发出HTTP重定向

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

    发出一个路由器重定向,使用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(http.StatusOK, gin.H{"hello": "world"})
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    2.16 定制中间件

    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)
      }
    }
    
    func main() {
      r := gin.New()
      r.Use(Logger())
    
      r.GET("/test", func(c *gin.Context) {
        example := c.MustGet("example").(string)
    
        // it would print: "12345"
        log.Println(example)
      })
    
      // Listen and serve on 0.0.0.0:8080
      r.Run(":8080")
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    2.17 使用BasicAuth()中间件

    // simulate some private data
    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()
    
      // Group using gin.BasicAuth() middleware
      // gin.Accounts is a shortcut for map[string]string
      authorized := r.Group("/admin", gin.BasicAuth(gin.Accounts{
        "foo":    "bar",
        "austin": "1234",
        "lena":   "hello2",
        "manu":   "4321",
      }))
    
      // /admin/secrets endpoint
      // hit "localhost:8080/admin/secrets
      authorized.GET("/secrets", func(c *gin.Context) {
        // get user, it was set by the BasicAuth middleware
        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 :("})
        }
      })
    
      // Listen and serve on 0.0.0.0:8080
      r.Run(":8080")
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    2.18 中间件中的goroutine

    当在中间件或处理程序( middleware or handler)中启动新的 Goroutine 时,不应该使用其中的原始上下文(context ),必须使用只读副本

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

    2.19 自定义HTTP配置

    直接使用http.ListenAndServe(),像这样:

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

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

    2.20 支持加密

    支持 HTTPS服务器 , 使用let’s encrypt。

    package main
    
    import (
      "log"
      "net/http"
    
      "github.com/gin-gonic/autotls"
      "github.com/gin-gonic/gin"
    )
    
    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
    • 17
    • 18
    • 19
    • 20

    自定义自动证书管理器示例:

    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))
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    2.21 使用Gin运行多个服务

    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 {
        err := server01.ListenAndServe()
        if err != nil && err != http.ErrServerClosed {
          log.Fatal(err)
        }
        return err
      })
    
      g.Go(func() error {
        err := server02.ListenAndServe()
        if err != nil && err != http.ErrServerClosed {
          log.Fatal(err)
        }
        return err
      })
    
      if err := g.Wait(); err != nil {
        log.Fatal(err)
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71

    2.22 优雅关机或重新启动

    有几种方法可以用于执行优雅的关闭或重新启动。您可以使用专门为此而构建的第三方包,也可以使用内置包中的函数和方法手动执行相同的操作。

    第三方包

    我们可以使用fvbock/endless来替换默认的ListenAndServe。

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

    Alternatives:
    grace: Graceful restart & zero downtime deploy for Go servers.
    graceful: Graceful is a Go package enabling graceful shutdown of an http.Handler server.
    manners: A polite Go HTTP server that shuts down gracefully.

    手动

    如果您使用的是Go 1.8或更高版本,您可能不需要使用这些库。考虑使用http.Server内置的Shutdown()方法用于正常关机。下面的例子描述了它的用法,我们在这里有更多使用gin的例子。

    func main() {
      router := gin.Default()
      router.GET("/", func(c *gin.Context) {
        time.Sleep(5 * time.Second)
        c.String(http.StatusOK, "Welcome Gin Server")
      })
    
      srv := &http.Server{
        Addr:    ":8080",
        Handler: router,
      }
    
      // Initializing the server in a goroutine so that
      // it won't block the graceful shutdown handling below
      go func() {
        if err := srv.ListenAndServe(); err != nil && errors.Is(err, http.ErrServerClosed) {
          log.Printf("listen: %s\n", err)
        }
      }()
    
      // Wait for interrupt signal to gracefully shutdown the server with
      // a timeout of 5 seconds.
      quit := make(chan os.Signal)
      // kill (no param) default send syscall.SIGTERM
      // kill -2 is syscall.SIGINT
      // kill -9 is syscall.SIGKILL but can't be caught, so don't need to add it
      signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
      <-quit
      log.Println("Shutting down server...")
    
      // The context is used to inform the server it has 5 seconds to finish
      // the request it is currently handling
      ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
      defer cancel()
    
      if err := srv.Shutdown(ctx); err != nil {
        log.Fatal("Server forced to shutdown:", err)
      }
    
      log.Println("Server exiting")
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    2.23 用模板构建一个二进制文件

    可以使用embed 包将服务器构建到包含模板的单个二进制文件中。

    //go:embed assets/* templates/*
    var f embed.FS
    
    func main() {
      router := gin.Default()
      templ := template.Must(template.New("").ParseFS(f, "templates/*.tmpl", "templates/foo/*.tmpl"))
      router.SetHTMLTemplate(templ)
    
      // example: /public/assets/images/example.png
      router.StaticFS("/public", http.FS(f))
    
      router.GET("/", func(c *gin.Context) {
        c.HTML(http.StatusOK, "index.tmpl", gin.H{
          "title": "Main website",
        })
      })
    
      router.GET("/foo", func(c *gin.Context) {
        c.HTML(http.StatusOK, "bar.tmpl", gin.H{
          "title": "Foo website",
        })
      })
    
      router.GET("favicon.ico", func(c *gin.Context) {
        file, _ := f.ReadFile("assets/favicon.ico")
        c.Data(
          http.StatusOK,
          "image/x-icon",
          file,
        )
      })
    
      router.Run(":8080")
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
  • 相关阅读:
    前端技能树,面试复习第 51 天—— Vue 项目性能优化方案
    JS,BOM试题,在实践中应用,非常详细!!
    Power BI 傻瓜入门 14. 深入挖掘DAX
    Java 两个整数int类型相除总是得0的原因及解决方法
    于 STM32 的 IAP 总结
    PS快捷键
    Vue项目流程8,导航守卫的使用,图片懒加载,利用vee-validate实现表单验证,路由懒加载,打包并处理map文件
    台式电脑连不上wifi怎么办
    【机器学习】面试题:LSTM长短期记忆网络的理解?LSTM是怎么解决梯度消失的问题的?还有哪些其它的解决梯度消失或梯度爆炸的方法?
    如何成为一名高级数字 IC 设计工程师(1-7)Verilog 编码语法篇:常数
  • 原文地址:https://blog.csdn.net/chinusyan/article/details/127615529