Go语言Web框架Gin常见用法

1. 简介

Gin是目前Go语言最为常用的Web框架,日常工作中也少不了使用此框架,编写此使用总结文档以备后用。

此文档参考官方文档编写,仅用于自我学习总结和参考。

我一直认为编写文档的意义一方面是给其他人提供了些许帮助,另一方面则是让自己加深了对知识的理解并为自己提供了一份一眼就能看懂的参考文档。

注意:本文档中所涉及的API仅是很小的一部分,其他API请参考Gin API文档

推荐参考资源:

  • Gin
  • Gin快速入门(强烈推荐
  • Gin使用示例
  • Gin中间件
  • Gin API文档
  • 参数验证及标签

2. Web服务器

2.1. 实现一个最简单的Web服务器

引入gin程序包后,可以快速搭建一个Web服务器:

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() // 默认监听端口号:8080
}

当程序运行之后,使用浏览器访问 ping 即可,的确超级简单。

如果你希望编写自动化白盒测试用例,可以新建测试文件main_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(http.MethodGet, "/ping", nil)
    router.ServeHTTP(w, req)

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

再启动服务后运行测试用例:

go test .

2.2. 实现http服务请求接口

Gin提供了丰富的方法以实现不同的http服务请求接口:

func main() {
  // 使用默认路由,带日志和恢复中间件
  router := gin.Default()

  router.GET("/someGet", getting)
  router.POST("/somePost", posting)
  router.PUT("/somePut", putting)
  router.DELETE("/someDelete", deleting)
  router.PATCH("/somePatch", patching)
  router.HEAD("/someHead", head)
  router.OPTIONS("/someOptions", options)

  // 默认监听端口号:8080
  router.Run()
}

所有方法的处理函数需满足HandlerFunc的定义:

type HandlerFunc func(*Context)

2.3. 分组路由

可以通过gin.Engine对象的Group方法来创建分组路由,也可以通过gin.RouterGroup对象的Group方法来创建分组路由:

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

    v1 := router.Group("/v1")
    {
        v1.POST("/login", loginEndpoint)   // /v1/login
        v1.POST("/submit", submitEndpoint) // /v1/submit
    }

    auth := v1.Group("/auth")
    {
        auth.POST("/role", roleEndpoint) // /v1/auth/role
        auth.POST("/user", userEndpoint) // /v1/auth/user
    }

    router.Run(":8080")
}

2.4. 自定义HTTP配置

直接使用http.ListenAndServe()来自定义HTTP配置:

例1,指定端口号:

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

例2,指定更多自定义选项:

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

2.5. 运行多个服务

可以在一个程序中同时运行多个服务:

package main

import (
    "log"
    "net/http"
    "time"

    "github.com/gin-gonic/gin"
    "golang.org/x/sync/errgroup"
)

var (
    g errgroup.Group
)

func router01() http.Handler {
    e := gin.New()
    e.Use(gin.Recovery())
    e.GET("/", func(c *gin.Context) {
        c.JSON(
            http.StatusOK,
            gin.H{
                "code":  http.StatusOK,
                "error": "Welcome server 01",
            },
        )
    })

    return e
}

func router02() http.Handler {
    e := gin.New()
    e.Use(gin.Recovery())
    e.GET("/", func(c *gin.Context) {
        c.JSON(
            http.StatusOK,
            gin.H{
                "code":  http.StatusOK,
                "error": "Welcome server 02",
            },
        )
    })

    return e
}

func main() {
    server01 := &http.Server{
        Addr:         ":8080",
        Handler:      router01(),
        ReadTimeout:  5 * time.Second,
        WriteTimeout: 10 * time.Second,
    }

    server02 := &http.Server{
        Addr:         ":8081",
        Handler:      router02(),
        ReadTimeout:  5 * time.Second,
        WriteTimeout: 10 * time.Second,
    }

    g.Go(func() error {
        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)
    }
}

3. 请求

3.1. url路径参数

Gin提供的路由可精确匹配一条url路径,也可使用规则匹配多条url路径,使用gin.Context对象的Param方法提取url路径参数:

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

  // 此路由匹配 /user/john,不匹配 /user/ 或 /user
  router.GET("/user/:name", func(c *gin.Context) {
    name := c.Param("name")
    c.String(http.StatusOK, "Hello %s", name)
  })

  // 此路由匹配 /user/john/ 和 /user/john/send
  // 如果没有其他路由能匹配 /user/john,就重定向到 /user/john/
  router.GET("/user/:name/*action", func(c *gin.Context) {
    name := c.Param("name")
    action := c.Param("action")
    message := name + " is " + action
    c.String(http.StatusOK, message)
  })

  // gin.Context 对象中包含了与其匹配的路由,可以通过其 FullPath 方法来获取
  router.POST("/user/:name/*action", func(c *gin.Context) {
    b := c.FullPath() == "/user/:name/*action" // true
    c.String(http.StatusOK, "%t", b)
  })

  // 定义精确匹配的url:/user/groups
  // url路径为 /user/groups 时只会匹配此路由
  // 永远不会匹配 /user/:name/... 路由(即使该路由定义在 /user/groups 路由之前)
  router.GET("/user/groups", func(c *gin.Context) {
    c.String(http.StatusOK, "The available groups are [...]")
  })

  router.Run(":8080")
}

3.2. url查询参数

使用gin.Context对象的Query方法提取url查询参数,使用DefaultQuery方法指定查询参数不存在时的默认值:

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

  // 匹配url示例:/welcome?firstname=Jane&lastname=Doe
  router.GET("/welcome", func(c *gin.Context) {
    firstname := c.DefaultQuery("firstname", "Guest")
    lastname := c.Query("lastname") // c.Request.URL.Query().Get("lastname") 的简写

    c.String(http.StatusOK, "Hello %s %s", firstname, lastname)
  })
  router.Run(":8080")
}

3.3. Multipart/Urlencoded形式的参数

使用gin.Context对象的PostForm方法提取Multipart/Urlencoded形式的参数,使用DefaultPostForm方法指定Multipart/Urlencoded形式的参数不存在时的默认值:

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

    router.POST("/form_post", func(c *gin.Context) {
        message := c.PostForm("message")
        nick := c.DefaultPostForm("nick", "anonymous")

        c.JSON(http.StatusOK, gin.H{
            "status":  "posted",
            "message": message,
            "nick":    nick,
        })
    })
    router.Run(":8080")
}

http请求如下所述:

POST /form_post
Content-Type: application/x-www-form-urlencoded

nick=Jack&message=this_is_great

3.4. 模型绑定和验证

之前获取请求参数都是一个一个获取,可以通过模型绑定功能将参数绑定到类型上,支持JSON、XML、YAML、TOML和标准的表单格式数据。

绑定失败自动返回400的方法有:Bind、BindJSON、BindXML、BindQuery、BindYAML、BindHeader、BindTOML。

绑定失败后由用户自行处理错误的方法有:ShouldBind、ShouldBindJSON、ShouldBindXML、ShouldBindQuery、ShouldBindYAML、ShouldBindHeader、ShouldBindTOML。

Gin自动从请求头的Content-Type字段推断绑定的数据格式,当然也可以直接使用MustBindWithShouldBindWith显示指定数据格式。

可以通过在字段标签中加入binding:"required"来表示该参数为必需传递的参数,当参数值为空时会报错。

3.4.1. 常见模型绑定

可以在数据字段标签中添加多种格式绑定标签:

// JSON格式数据绑定
type Login struct {
	User     string `form:"user" json:"user" xml:"user"  binding:"required"`
	Password string `form:"password" json:"password" xml:"password" binding:"required"`
}

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

	// JSON格式数据绑定示例:{"user": "manu", "password": "123"}
	router.POST("/loginJSON", func(c *gin.Context) {
		var json 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"})
	})

	// XML格式数据绑定示例:
	//  <?xml version="1.0" encoding="UTF-8"?>
	//  <root>
	//    <user>manu</user>
	//    <password>123</password>
	//  </root>
	router.POST("/loginXML", func(c *gin.Context) {
		var xml 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"})
	})

	// HTML表单格式数据绑定示例:user=manu&password=123
	router.POST("/loginForm", func(c *gin.Context) {
		var form Login
		// 从请求头的Content-Type字段推断绑定数据格式
		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"})
	})

	router.Run(":8080")
}

3.4.2. 只绑定查询参数

使用ShouldBindQuery可指定只绑定查询参数:

type Person struct {
    Name    string `form:"name"`
    Address string `form:"address"`
}

func main() {
    route := gin.Default()
    route.Any("/testing", startPage)
    route.Run(":8085")
}

func startPage(c *gin.Context) {
    var person 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")
}

3.4.3. 绑定查询参数或者POST数据

使用ShouldBind可在GET请求中绑定查询参数,或者在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
    // 对于GET请求,只绑定查询参数
    // 对于POST请求,从请求头的Content-Type字段推断数据是JSON格式或者XML格式,无法推断数据格式时当成表单格式来处理
    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")
}

3.4.4. 绑定路径参数

使用ShouldBindUri来绑定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")
}

3.4.5. 绑定请求头

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

3.4.6. 绑定表单格式数据

使用ShouldBind或者ShouldBindWith来绑定表单格式数据:

type ProfileForm struct {
    Name   string                `form:"name" binding:"required"`
    Avatar *multipart.FileHeader `form:"avatar" binding:"required"`

    // 请求中有多个文件时使用切片
    // Avatars []*multipart.FileHeader `form:"avatar" binding:"required"`
}

func main() {
    router := gin.Default()
    router.POST("/profile", func(c *gin.Context) {
        // 也可以显示指定数据格式
        // c.ShouldBindWith(&form, binding.Form)
        var form ProfileForm
        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
        }

        c.String(http.StatusOK, "ok")
    })
    router.Run(":8080")
}

4. 响应

4.1. 结构化数据响应

当响应数据为结构化数据(JSON、XML、YAML、TOML、 ProtoBuf)等时,可以直接使用gin.Context对象提供的方法:

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

    // gin.H 是 map[string]any 类型的缩写
    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
        // 值得注意的是为msg.Name指定了json标签,因此序列化之后的字段名为"user"
        // 输出:{"user": "Lena", "Message": "hey", "Number": 123}
        c.JSON(http.StatusOK, msg)
    })

    r.GET("/someXML", func(c *gin.Context) {
        c.XML(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK})
    })

    r.GET("/someYAML", func(c *gin.Context) {
        c.YAML(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK})
    })

    r.GET("/someTOML", func(c *gin.Context) {
        c.TOML(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK})
    })

    r.GET("/someProtoBuf", func(c *gin.Context) {
        reps := []int64{int64(1), int64(2)}
        label := "test"
        data := &protoexample.Test{
            Label: &label,
            Reps:  reps,
        }
        c.ProtoBuf(http.StatusOK, data)
    })

    r.Run(":8080")
}

4.2. 提供文件下载接口

例1,可以使用Static*方法来实现提供静态文件的http接口:

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

    router.Run(":8080")
}

例2,使用FileFileFromFS方法实现提供静态文件的http接口:

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

    router.GET("/local/file", func(c *gin.Context) {
        c.File("local/file.go")
    })

    var fs http.FileSystem = MyFileSystem{}
    router.GET("/fs/file", func(c *gin.Context) {
        c.FileFromFS("fs/file.go", fs)
    })

    router.Run(":8080")
}

type MyFileSystem struct {
}

func (fs MyFileSystem) Open(name string) (http.File, error) {
    filepath := path.Join("./", name)
    file, err := os.OpenFile(filepath, os.O_RDONLY, os.ModePerm)
    return file, err
}

例3,使用DataFromReader方法从Reader中读取数据并提供文件下载接口:

func main() {
    router := gin.Default()
    router.GET("/someDataFromReader", func(c *gin.Context) {
        response, err := http.Get("https://www.baidu.com/")
        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")
}

4.3. HTML渲染

使用LoadHTMLGlobLoadHTMLFiles方法来加载HTML模板,使用HTML方法来渲染模板:

func main() {
    router := gin.Default()
    router.LoadHTMLGlob("templates/*")
    //router.LoadHTMLFiles("templates/template1.html", "templates/template2.html")
    router.GET("/index", func(c *gin.Context) {
        c.HTML(http.StatusOK, "index.tmpl", gin.H{
            "title": "Main website",
        })
    })
    router.Run(":8080")
}

templates/index.tmpl文件内容示例:

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

5. 中间件

5.1. 使用不带任何中间件的引擎

使用gin包的New函数可以创建不带任何中间件的引擎:

r := gin.New()

5.2. 使用带默认中间件的引擎

使用gin包的Default函数可以创建带默认中间件(日志中间件和恢复中间件)的引擎:

r := gin.Default()

5.3. 使用中间件

可以通过gin提供的方法添加全局中间件、分组路由中间件和指定一条路由的中间件:

func main() {
    // 创建一个不带任何中间件的路由
    r := gin.New()

    // 全局日志中间件
    r.Use(gin.Logger())

    // 全局恢复中间件:在http接口内部panic之后返回500给客户端
    r.Use(gin.Recovery())

    // 只用于一条路由的中间件,支持一次添加多个中间件
    r.GET("/benchmark", MyBenchLogger(), benchEndpoint)

    // 中间件分组路由,等效于:authorized := r.Group("/", AuthRequired())
    authorized := r.Group("/")
    authorized.Use(AuthRequired())
    {
        authorized.POST("/login", loginEndpoint)
        authorized.POST("/submit", submitEndpoint)
        authorized.POST("/read", readEndpoint)

        // 嵌套分组路由
        testing := authorized.Group("testing")
        testing.GET("/analytics", analyticsEndpoint)
    }

    // 监听地址:0.0.0.0:8080
    r.Run(":8080")
}

5.4. 用中间件定义恢复行为

可以使用gin提供的CustomRecovery函数来自定义panic后的恢复行为,通常就是向客户端返回500:

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

    // 出现panic时返回500和错误信息
    r.Use(gin.CustomRecovery(func(c *gin.Context, recovered any) {
        if err, ok := recovered.(string); ok {
            c.String(http.StatusInternalServerError, fmt.Sprintf("error: %s", err))
        }
        c.AbortWithStatus(http.StatusInternalServerError)
    }))

    r.GET("/panic", func(c *gin.Context) {
        panic("foo")

    })

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

    r.Run(":8080")
}

5.5. 日志中间件

5.5.1. 将日志保存到文件

通过设置gin.DefaultWriter可以将日志保存到文件:

func main() {
    // 当将日志保存到文件时关闭控制台日志颜色渲染功能
    gin.DisableConsoleColor()

    // 将日志保存到文件
    f, _ := os.Create("gin.log")
    gin.DefaultWriter = io.MultiWriter(f)

    // 使用下一行代码可以将日志保存到文件的同时输出到标准输出
    // gin.DefaultWriter = io.MultiWriter(f, os.Stdout)

    router := gin.Default()
    router.GET("/ping", func(c *gin.Context) {
        c.String(http.StatusOK, "pong")
    })

    router.Run(":8080")
}

5.5.2. 自定义日志内容格式

如果你觉得默认日志内容格式不是你想要的,可以使用gin.LoggerWithFormatter函数来创建自定义日志内容格式中间件:

func main() {
    router := gin.New()

    // 自定义日志内容格式
    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.GET("/ping", func(c *gin.Context) {
        c.String(http.StatusOK, "pong")
    })

    router.Run(":8080")
}

5.5.3. 控制台日志颜色渲染

可通过gin.ForceConsoleColor函数来打开控制台日志颜色渲染功能,通过gin.DisableConsoleColor函数关闭:

func main() {
	// 打开控制台日志颜色渲染功能
	gin.ForceConsoleColor()

	// 关闭控制台日志颜色渲染功能
	// gin.DisableConsoleColor()

	router := gin.Default()

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

	router.Run(":8080")
}

5.6. 自定义中间件

自定义中间件时,只需要满足HandlerFunc函数定义即可:

type HandlerFunc func(*Context)
func Logger() gin.HandlerFunc {
    return func(c *gin.Context) {
        t := time.Now()

        // 设置示例变量
        c.Set("example", "12345")

        // 执行请求之前

        c.Next()

        // 执行请求之后
        latency := time.Since(t)
        log.Print(latency)

        // 获取返回状态码
        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)

        // 打印:"12345"
        log.Println(example)
    })

    r.Run(":8080")
}

5.7. 在中间件中使用Go协程

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

    r.GET("/long_async", func(c *gin.Context) {
        // 为gin.Context创建一个拷贝
        cCp := c.Copy()
        go func() {
            // 模拟耗时任务:5s
            time.Sleep(5 * time.Second)

            // 使用拷贝对象
            log.Println("Done! in path " + cCp.Request.URL.Path)
        }()
    })

    r.GET("/long_sync", func(c *gin.Context) {
        // 模拟耗时任务:5s
        time.Sleep(5 * time.Second)

        // 不需要使用拷贝对象
        log.Println("Done! in path " + c.Request.URL.Path)
    })

    r.Run(":8080")
}

6. 实现上传文件接口

6.1. 上传单个文件

通过gin.Context提供的FormFile方法来获取文件信息,通过SaveUploadedFile方法来保存文件内容:

func main() {
    router := gin.Default()
    // 设置请求内容内存大小限制(默认值为:32MB)
    router.MaxMultipartMemory = 8 << 20 // 8 MB
    router.POST("/upload", func(c *gin.Context) {
        // 单个文件
        file, _ := c.FormFile("file")
        log.Println(file.Filename)

        // 将文件保存到upload目录下
        dst := path.Join("upload", file.Filename)
        c.SaveUploadedFile(file, dst)

        c.String(http.StatusOK, fmt.Sprintf("'%s' uploaded!", file.Filename))
    })
    router.Run(":8080")
}

可以使用curl命令来测试这个接口,例:

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

6.2. 上传多个文件

通过gin.Context提供的MultipartForm方法来获取多文件信息,通过SaveUploadedFile方法来保存文件内容:

func main() {
    router := gin.Default()
    // 设置请求内容内存大小限制(默认值为:32MB)
    router.MaxMultipartMemory = 8 << 20 // 8 MB
    router.POST("/upload", func(c *gin.Context) {
        // Multipart form
        form, _ := c.MultipartForm()
        files := form.File["upload[]"]

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

            // 将文件保存到upload目录下
            dst := path.Join("upload", file.Filename)
            c.SaveUploadedFile(file, dst)
        }
        c.String(http.StatusOK, fmt.Sprintf("%d files uploaded!", len(files)))
    })
    router.Run(":8080")
}

可以使用curl命令来测试这个接口,例:

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

7. 其他

7.1. 重定向

例1,重定向到系统外的地址:

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

例2,重定向到系统内的地址:

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

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

7.2. 构建时使用其他json包来优化程序性能

Gin默认使用encoding/json作为json包来进行json格式数据的序列化和反序列化。

当你大量使用json的序列化和反序列化功能且出现了性能瓶颈时,可以根据需要将encoding/json包替换为其他json包。

在构建时通过标签指定即可:

jsoniter

go build -tags=jsoniter .

go-json

go build -tags=go_json .

sonic

$ go build -tags="sonic avx" .

7.3. 为可执行文件瘦身

当你觉得构建之后的可执行文件太大,并且你的程序不会用到 codec 相关功能时,可以在构建时通过标签禁用 MsgPack 功能:

go build -tags=nomsgpack .

详情参考 Add build tag nomsgpack #1852

7.4. 获取与设置cookie

使用CookieSetCookie方法来获取或设置cookie:

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

    router.GET("/cookie", func(c *gin.Context) {
        // 获取cookie
        cookie, err := c.Cookie("gin_cookie")

        if err != nil {
            cookie = "NotSet"
            // 设置cookie
            c.SetCookie("gin_cookie", "test", 3600, "/", "localhost", false, true)
        }

        fmt.Printf("Cookie value: %s \n", cookie)
    })

    router.Run()
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

企业级实战项目:基于 pycaret 自动化预测公司是否破产

本文系数据挖掘实战系列文章&#xff0c;我跟大家分享一个数据挖掘实战&#xff0c;与以往的数据实战不同的是&#xff0c;用自动机器学习方法完成模型构建与调优部分工作&#xff0c;深入理解由此带来的便利与效果。 1. Introduction 本文是一篇数据挖掘实战案例&#xff0c;…

深信服技术认证“SCCA-C”划重点:云计算基础

为帮助大家更加系统化地学习云计算知识&#xff0c;高效通过云计算工程师认证&#xff0c;深信服特推出“SCCA-C认证备考秘笈”&#xff0c;共十期内容。“考试重点”内容框架&#xff0c;帮助大家快速get重点知识。 划重点来啦 *点击图片放大展示 深信服云计算认证&#xff08…

华锐视点为广汽集团打造VR汽车在线展厅,打破地域限制,尽享购车乐趣

随着科技的飞速发展&#xff0c;我们正在进入一个全新的时代——元宇宙时代。元宇宙是一个虚拟的世界&#xff0c;它不仅能够模拟现实世界&#xff0c;还能够创造出现实世界无法实现的事物。而汽车行业作为人类生活的重要组成部分&#xff0c;也在积极探索与元宇宙的融合&#…

如何使用ArcGIS Pro将Excel表转换为SHP文件

有的时候我们得到的数据是一张张的Excel表格&#xff0c;如果想要在ArcGIS Pro中进行分析或者制图则需要先转换为SHP格式&#xff0c;这里为大家介绍一下转换方法&#xff0c;希望能对你有所帮助。 数据来源 本教程所使用的数据是从水经微图中下载的POI数据&#xff0c;除了P…

Jenkins 自动设置镜像版本号

使用Jenkins环境变量当作镜像版本号 这样version变量就是版本号,在镜像构建的过程中可以使用 docker build 之后&#xff0c;如果有自己的镜像库&#xff0c;肯定要docker push 一下 至于部署的步骤&#xff0c;一般需要stop并删除原有的容器.我这里用的是docker-compose。同样…

OKCC语音机器人的人机耦合来啦

目前市场上语音机器人的外呼形式基本就分为三种&#xff0c;一种纯AI外呼&#xff0c;第二种也是目前主流的AI外呼转人工。那么第三种也可能是未来的一种趋势&#xff0c;人机耦合&#xff0c;或者也叫人机协同。 那么什么是人机耦合呢&#xff1f; 人机耦合是为真人坐席创造相…

线性代数基础【3】向量

第一节 向量的概念与运算 一、基本概念 ①向量 ②向量的模(长度) ③向量的单位化 ④向量的三则运算 ⑤向量的内积 二、向量运算的性质 (一)向量三则运算的性质 α β β αα (β γ) (α β) γk (α β) kα kβ(k l) α kα lα (二)向量内积运…

什么是GeoTrust?

在当今数字化时代&#xff0c;网络安全是至关重要的。GeoTrust&#xff0c;作为全球领先的SSL证书提供商&#xff0c;致力于为用户提供卓越的数字安全解决方案。 产品与服务&#xff1a; 域名验证证书&#xff1a; 提供快速简便的验证&#xff0c;是保护网站和用户数据的基础。…

华清远见嵌入式学习——ARM——作业4

作业要求&#xff1a; 代码运行效果图&#xff1a; 代码&#xff1a; do_irq.c: #include "key_it.h" extern void printf(const char *fmt, ...); unsigned int i 0;//延时函数 void delay(int ms) {int i,j;for(i0;i<ms;i){for(j0;j<2000;j);} }void do_i…

【linux】Linux重定向

在Linux操作系统中&#xff0c;命令行界面是一个强大的工具&#xff0c;它允许用户与系统进行高效的交互。重定向是命令行中一个非常重要的概念&#xff0c;它可以改变命令输入和输出的默认路径。通过重定向&#xff0c;用户可以将数据从一个程序传递到另一个程序&#xff0c;或…

LENOVO联想笔记本小新Pro 14 IRH8 2023款(83AL)电脑原装出厂Win11系统恢复预装OEM系统

链接&#xff1a;https://pan.baidu.com/s/1M1iSFahokiIHF3CppNpL4w?pwdzr8y 提取码&#xff1a;zr8y 联想原厂系统自带所有驱动、出厂主题壁纸、Office办公软件、联想电脑管家等自带的预装软件程序 所需要工具&#xff1a;16G或以上的U盘 文件格式&#xff1a;ISO 文件…

SpringBoot3 整合Kafka

官网&#xff1a;https://kafka.apache.org/documentation/ 消息队列-场景 1. 异步 2. 解耦 3. 削峰 4. 缓冲 消息队列-Kafka 1. 消息模式 消息发布订阅模式&#xff0c;MessageQueue中的消息不删除&#xff0c;会记录消费者的偏移量 2. Kafka工作原理 同一个消费者组里的消…

开发辅助一(网关gateway+ThreadLocal封装用户信息+远程调用+读取配置文件+统一异常处理)

网关gateway模块 ①、配置文件&#xff0c;添加各个服务模块的路由路径 gateway:routes:-id: server-cart #微服务名称uri: lb://service-cart #负责均衡predicates:- Path/api/order/cart/**ThreadLocal ①、定义一个工具类 public class AuthContextUtil{private static…

Zookeeper应用场景有哪些?

ZooKeeper是⼀个典型的发布/订阅模式的分布式数据管理与协调框架&#xff0c;我们可以使⽤它来进⾏分布式数据的发布与订阅。另⼀⽅⾯&#xff0c;通过对ZooKeeper中丰富的数据节点类型进⾏交叉使⽤&#xff0c;配合Watcher事件通知机制&#xff0c;可以⾮常⽅便地构建⼀系列分…

记一次Mac端mysql重置密码

在执行mysql命令的时候&#xff0c;报如下的错误&#xff0c;表示不支持mysql命令&#xff1a; zsh: command not found: mysql 1. 先查看mysql服务是否存在 在系统偏好设置中查看&#xff1a; 2. 发现mysql服务已经在运行&#xff0c;可能因为/usr/local/bin目录下缺失mysq…

How to Clean Text for Machine Learning with Python

NLP 在本教程中&#xff0c;您将了解如何清理和准备文本&#xff0c;以便使用机器学习进行建模。 完成本教程后&#xff0c;您将了解&#xff1a; 如何通过开发自己的非常简单的文本清理工具开始。 如何更上一层楼并使用 NLTK 库中更复杂的方法。 在使用现代文本表示方法&am…

python3遇到Can‘t connect to HTTPS URL because the SSL module is not available.

远程服务器centos7系统上有minicoda3&#xff0c;觉得太占空间&#xff0c;就把整个文件夹删了&#xff0c;原先的Python3也没了&#xff0c;都要重装。 我自己的步骤&#xff1a;进入管理员模式 1.下载Python3的源码&#xff1a; wget https://www.python.org/ftp/python/3.1…

熟悉DHCP面临的安全威胁与防护机制

一个网络如果要正常地运行&#xff0c;则网络中的主机&#xff08;Host&#xff09;必需要知道某些重要的网络参数&#xff0c;如IP地址、网络掩码、网关地址、DNS服务器地址、网络打印机地址等等。显然&#xff0c;在每台主机上都采用手工方式来配置这些参数是非常困难的、或是…

c语言结构体(初阶)

1. 结构体的声明 1.1 结构体的基础知识 结构是一些值的集合&#xff0c;这些值被称为成员变量。结构的每个成员可以是不同类型的变量。 1.2 结构的声明 struct tag {member - list; }variable-list; 例&#xff1a;描述一个人的信息&#xff1a;名字电话性别身高 //声明的…

使用python netmiko模块批量配置Cisco、华为、H3C路由器交换机(支持 telnet 和 ssh 方式)

0. 当前环境 外网电脑Python版本&#xff1a;3.8.5&#xff08;安装后不要删除安装包&#xff0c;以后卸载的时候用这个&#xff09;外网电脑安装netmiko第三方库&#xff1a;cmd中输入pip install netmiko内网电脑环境&#xff1a;无法搭建python环境&#xff0c;需外网电脑完…
最新文章