6 分钟
Go Gin Web 框架
快速开始
参考
官方表述
- 快 基于 Radix 树的路由,小内存占用。没有反射。可预测的 API 性能。
- 支持中间件 传入的 HTTP 请求可以由一系列中间件和最终操作来处理。 例如:Logger,Authorization,GZIP,最终操作 DB。
- Crash-free Gin 可以 catch 一个发生在 HTTP 请求中的 panic 并 recover 它。这样,你的服务器将始终可用。例如,你可以向 Sentry 报告这个 panic!
- JSON 校验 Gin 可以解析并验证请求的 JSON,例如检查所需值的存在。
- 路由组 更好地组织路由。是否需要授权,不同的 API 版本…… 此外,这些组可以无限制地嵌套而不会降低性能。
- 错误管理 Gin 提供了一种方便的方法来收集 HTTP 请求期间发生的所有错误。最终,中间件可以将它们写入日志文件,数据库并通过网络发送。
- 支持内建渲染 Gin 为 JSON,XML 和 HTML 渲染提供了易于使用的 API。
- 可扩展 创建新的中间件非常容易,只需查看 示例代码 即可。
Hello World
go version 1.15+ , gin version 1.6.3
创建项目
mkdir gin-learn
cd gin-learn
go mod init github.com/rectcircle/gin-learnmain.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() // 监听并在 0.0.0.0:8080 上启动服务
}功能特性
基本用法
- 路由为
"/ping",则/ping/将重定向到/ping 路由为
"/ping/",则/ping将重定向到/ping/// 创建 gin 引擎 r := gin.Default() // r.http方法("路由", 处理函数) // 处理函数/中间件函数: func(*gin.Context) // http://127.0.0.1:8080/ping // http://127.0.0.1:8080/ping/ 将重定向到 上一个 r.GET("/ping", func(c *gin.Context) { c.JSON(200, gin.H{ "message": "pong", }) })
路由注册 和 HTTP 方法
func handler(c *gin.Context) {
c.JSON(200, gin.H{
"message": "ok",
})
}
func routerMethod(r *gin.Engine) {
r.GET("router/method", handler)
r.POST("router/method", handler)
r.PUT("router/method", handler)
r.PATCH("router/method", handler)
r.DELETE("router/method", handler)
r.HEAD("router/method", handler)
r.OPTIONS("router/method", handler)
r.Any("router/any", handler) // 匹配所有方法
// r.Handle // 最终调用的函数
}请求参数
路径参数
router/request/path/require/:requirePathParam语法:requirePathParam匹配一个路径参数,必须存在- 使用
*gin.Context.Param("requirePathParam")提取 - 可以匹配
router/request/path/require/1router/request/path/require/1/,*gin.Context.Param("requirePathParam")将返回1 - 无法可以匹配
router/request/path/require/router/request/path/require
- 使用
router/request/path/remain/*remainPathParam语法*remainPathParam将匹配剩余全部- 使用
*gin.Context.Param("requirePathParam")提取 - 可以匹配
router/request/path/remain/1router/request/path/remain/1/提取将返回/1 可以匹配
router/request/path/remain/router/request/path/remain提取将返回/// 可以匹配 router/request/path/require/1 router/request/path/require/1/ // 无法可以匹配 router/request/path/require/ router/request/path/require r.GET("router/request/path/require/:requirePathParam", func(c *gin.Context) { c.String(http.StatusOK, "requirePathParam = %s", c.Param("requirePathParam")) }) // 可以匹配 router/request/path/remain/1 router/request/path/remain/1/ // 可以匹配 router/request/path/remain/ router/request/path/remain r.GET("router/request/path/remain/*remainPathParam", func(c *gin.Context) { c.String(http.StatusOK, "remainPathParam = %s", c.Param("remainPathParam")) })
- 使用
Query 参数
*gin.Context.Query(key)获取 query 不存在返回空串,多个返回第一个*gin.Context.GetQuery(key)类似于Query,返回(string, bool)*gin.Context.DefaultQuery(key, default)获取 query 不存在返回default,多个返回第一个*gin.Context.QueryArray(key)获取 query,支持多个,返回切片*gin.Context.GetQueryArray(key)类似于QueryArray*gin.Context.QueryMap(key)获取 query,类似map[key]的方式传参,返回map*gin.Context.GetQueryMap(key)类似于QueryMap// Query 参数 // http://127.0.0.1:8080/router/request/query?queryParam=123&queryParam=321&queryArr=1&queryArr=2&queryMap[a]=1&queryMap[b]=2 // 返回 queryParam = 123, queryParamWithDefault = default, queryArr = [1 2], queryMap = map[a:1 b:2] r.GET("router/request/query", func(c *gin.Context) { c.String(http.StatusOK, "queryParam = %s, queryParamWithDefault = %s, queryArr = %s, queryMap = %s", c.Query("queryParam"), c.DefaultQuery("queryParamWithDefault", "default"), c.QueryArray("queryArr"), c.QueryMap("queryMap"), ) })
Form 参数
Context-Type 为 application/x-www-form-urlencoded 或 multipart/form-data
类似于 Query 参数,方法名中的 Query 替换为 Form 即可,例如
*gin.Context.Form(key)*gin.Context.GetForm(key)*gin.Context.DefaultForm(key)- 等
文件上传
Context-Type 为 multipart/form-data
配置最大 内存 (默认 32 M)
*gin.Engine.MaxMultipartMemory = 8 << 20 // 8 MiB
API
file, _ := *gin.Context.FormFile(key)获取文件信息*gin.Context.SaveUploadedFile(file, dst)保存到磁盘
参考
Header 和 Cookie
请求绑定到自定义结构体
https://gin-gonic.com/zh-cn/docs/examples/binding-and-validation/
Gin 内建支持将各种请求参数绑定到一个自定义结构。
支持的类型的请求参数的绑定:
- JSON (
json:) - XML (
xml:) - YAML (
yaml:) - FORM 和 Query (
form:) - Header (
header:) - 路径参数 (
uri:) - 其他参见: Go Doc
相关 API
*gin.Context.MustBindWith(interface{}, binding.Binding)如果校验失败,返回 400,并将失败写入 Response,并返回error*gin.Context.Bind(interface{})根据Content-Type选择binding.Binding的实现者,然后调用MustBindWith*gin.Context.BindXxx(interface{})系列快捷方法,比如JSON、XML等,最终调用MustBindWith*gin.Context.ShouldBindWith(interface{}, binding.Binding)校验失败,仅返回error*gin.Context.ShouldBind(interface{}, binding.Binding)根据Content-Type选择binding.Binding的实现者,然后调用ShouldBindWith*gin.Context.ShouldBindXxx(interface{})系列快捷方法,比如JSON、XML等,最终调用ShouldBindWith
结构体示例
type User struct {
User string `json:"user"`
Password string
Accept string
}- 没有 Tag 的,HTTP 请求中需要绑定的 key 必须和 字段名 完全相等(大小写敏感)才能绑定成功
- 有 Tag 的,以 Tag 为准
- 字段绑定 tag,格式为
$type:"字段名",支持多个,常用的$type参见 上文的 支持的类型的请求参数的绑定 - 字段校验,参见下一小节
例子
r.GET("router/request/bind", func(c *gin.Context) {
// curl http://127.0.0.1:8080/router/request/bind\?User\=xiaoming\&password\=312
// user = {xiaoming */*}
user := struct {
User string `json:"user"`
Password string
Accept string
} {}
c.BindHeader(&user)
c.Bind(&user)
c.String(http.StatusOK, "user = %s", user)
})请求参数校验
Gin 提供了内建的参数校验功能,该功能需要与参数绑定结合使用。使用方式如下
- 定义结构体
- 添加
binding:"参数校验器"全部参见go-playground/validator.v8 - 使用 Bind 系列函数,返回
err != nil说明校验失败
参数绑定和参数校验补充说明
返回消息
常用API
假设 var c *gin.Context
- 返回 JSON
c.JSON(状态码, 结构体)(特殊 HTML 字符将会被转义为 Unicode 转义字符) - 返回 AsciiJSON 非 ASCII 码将使用 Unicode 转义字符串表示
c.AsciiJSON(状态码, 结构体) - 返回 PureJSON
c.PureJSON(状态码, 结构体)(不做任何转义) - 返回 安全 JSON
c.SecureJSON(状态码, 结构体)用于防止 json 劫持,在JSON基础上添加while(1),前缀 - 返回 JSONP
c.JSONP(状态码, 结构体)?callback=x将返回:x({\"foo\":\"bar\"}) - 返回 格式化 JSON(仅用于开发)
c.IndentedJSON(状态码, 结构体) - 返回 YAML
c.YAML(状态码, 结构体) - 返回 XML
c.XML(状态码, 结构体) - 返回 字符串
c.String(code int, format string, values ...interface{}) - 返回 ProtoBuf
c.ProtoBuf(状态码, 结构体) - 重定向
c.Redirect(状态码, 重定向地址) - 返回 字节数组
c.Data(code int, contentType string, data []byte) - 从 Reader 中返回数据
c.DataFromReader(code int, contentLength int64, contentType string, reader io.Reader, extraHeaders map[string]string) - 文件返回等参见 Doc
例子
responseStruct := struct {
Name string
Email string
}{
Name: "xiaoming",
Email: "[email protected]",
}
r.GET("router/response/json", func(c *gin.Context) {
c.JSON(http.StatusOK, responseStruct)
})
r.GET("router/response/yaml", func(c *gin.Context) {
c.YAML(http.StatusOK, responseStruct)
})
r.GET("router/response/xml", func(c *gin.Context) {
c.XML(http.StatusOK, responseStruct)
})路由组
func (*gin.RouterGroup).Group(relativePath string, handlers ...gin.HandlerFunc) *gin.RouterGroup支持多级路由组
func routerGroup(r *gin.Engine) { handler := func(c *gin.Context) { c.String(http.StatusOK, "Hello") } // 简单的路由组: v1 v1 := r.Group("/router/group/v1") { v1.GET("/hello", handler) } // 简单的路由组: v2 v2 := r.Group("/router/group/v2") { v2.GET("/hello", handler) } // curl http://127.0.0.1:8080/router/group/v1/hello // curl http://127.0.0.1:8080/router/group/v2/hello }
中间件
gin.Default() 将默认注册两个中间件
gin.Logger()gin.Recovery()
不使用以上默认中间件:使用 gin.New() 创建
在 Gin 中,中间件本质上是一个函数,该函数和业务函数声明是一致的。均为 type gin.HandlerFunc func(*gin.Context)
一般一个中间件的逻辑以 gin.Context.Next() 为分割点,在该函数调用前为请求前的处理,调用后为请求后的处理。
注意:当在中间件或 handler 中启动新的 Goroutine 时,不能使用原始的上下文,必须使用只读副本(*gin.Context.Copy())。
注册方式
- 全局使用
*gin.Engine.Use(middleware ...gin.HandlerFunc) - 路由组使用
*gin.RouterGroup.Use(middleware ...gin.HandlerFunc) - 单个路由使用
*gin.RouterGroup.GET等(relativePath string, handlers ...HandlerFunc)handlers 可以是中间件
例子
func MyLogger() gin.HandlerFunc {
return func(c *gin.Context) {
t := time.Now()
// 设置 example 变量
c.Set("example", "12345")
// 请求前
c.Next()
// 请求后
latency := time.Since(t)
// 获取发送的 status
status := c.Writer.Status()
log.Printf("latency=%s, status=%d", latency, status)
}
}
func routerWithMiddleware(r *gin.Engine) {
group := r.Group("router/group/middleware")
group.Use(MyLogger())
group.GET("/hello", func(c *gin.Context) {
example := c.MustGet("example").(string)
c.String(http.StatusOK, "example = %s", example)
})
// curl http://127.0.0.1:8080/router/group/middleware/hello
}模板
*gin.Context.HTML(code int, name string, obj interface{})支持template/html模板,参见,name 为相对路径,查找基于pwd- 多模板参见
静态资源与静态资源嵌入
运行多个服务
官方例子
测试
package main
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")
}测试代码
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())
}常见中间件
https://github.com/gin-contrib
最佳实践
项目结构
TODO