gin官方文档
安装
1
|
go get -u github.com/gin-gonic/gin
|
快速开始
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
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() // listen and serve on 0.0.0.0:8080 (for windows "localhost:8080")
}
|
API 范例
GET、POST、PUT、PATCH、DELETE 和 OPTIONS
1
2
3
4
5
6
7
|
router.GET("/someGet", handlefunc)
router.POST("/somePost", handlefunc)
router.PUT("/somePut", handlefunc)
router.DELETE("/someDelete", handlefunc)
router.PATCH("/somePatch", handlefunc)
router.HEAD("/someHead", handlefunc)
router.OPTIONS("/someOptions", handlefunc)
|
路径中的参数
/user/:name
中的name
1
|
name := c.Param("name")
|
查询字符串参数
url中"?“后的参数
1
2
3
|
lastname := c.Query("lastname")
//带默认值的查询
firstname := c.DefaultQuery("firstname", "Guest")
|
多部分/URL 编码形式
post-form类型body的参数
1
2
3
|
message := c.PostForm("message")
//带默认值
nick := c.DefaultPostForm("nick", "anonymous")
|
上传文件
单个文件
1
2
3
4
5
|
file, _ := c.FormFile("file")
log.Println(file.Filename)
// Upload the file to specific dst.
c.SaveUploadedFile(file, dst)
|
不要相信filename
多个文件
1
2
3
4
5
6
7
8
9
|
form, _ := c.MultipartForm()
files := form.File["upload[]"]
for _, file := range files {
log.Println(file.Filename)
// Upload the file to specific dst.
c.SaveUploadedFile(file, dst)
}
|
分组路由
1
|
v1 := router.Group("/v1")
|
可以对分组再分组,分组具有和router一样的api
创建不使用默认中间件的路由
1
2
|
//r := gin.Default()使用Logger和Recovery 中间件
r := gin.New()
|
使用中间件
1
2
3
|
r.Use(gin.Logger())
//分组路由也可以使用中间件
v.Use(gin.Logger())
|
自定义恢复行为
处理过程中panic后自动调用
1
2
3
4
5
6
|
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)
}))
|
写日志文件
1
2
3
4
5
6
7
8
9
|
// 取消日志颜色
gin.DisableConsoleColor()
// 将日志写入文件重定向到目标文件
f, _ := os.Create("gin.log")
gin.DefaultWriter = io.MultiWriter(f)
// 创建路由
router := gin.Default()
|
控制日志输出着色
不着色日志
1
|
gin.DisableConsoleColor()
|
始终为日志着色
1
|
gin.ForceConsoleColor()
|
配置需要在创建路由之前
模型绑定和验证
将请求体绑定到某个类型,支持JSON、XML、YAML、TOML 和标准格式值 (foo=bar&boo=baz)
需要在所有要绑定的字段上设置相应的绑定标签,例如,从 JSON 绑定时,设置json:“fieldname”。
binding:“required"指定必须有的字段
- 必须绑定:Bind, BindJSON, BindXML, BindQuery, BindYAML, BindHeader,BindTOML。绑定错误,请求将被c.AbortWithError(400, err).SetType(ErrorTypeBind)中止。
- 应该绑定:ShouldBind, ShouldBindJSON, ShouldBindXML, ShouldBindQuery, ShouldBindYAML, ShouldBindHeader, ShouldBindTOML。存在绑定错误,则返回错误(err:=c.ShouldBind(xxx))
仅绑定查询字符串
“?“后面的参数
1
2
3
4
5
6
|
type Person struct {
Name string `form:"name"`
Address string `form:"address"`
}
err:=c.ShouldBindQuery(&person)
|
绑定查询字符串或Post数据
1
2
3
4
5
6
7
8
9
|
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"`
}
err:=c.ShouldBind(&person)
|
绑定URI
“/:name/:id"类型的Uri
1
2
3
4
5
6
|
type Person struct {
ID string `uri:"id" binding:"required,uuid"`
Name string `uri:"name" binding:"required"`
}
err := c.ShouldBindUri(&person)
|
Header中的参数
1
2
3
4
5
6
|
type testHeader struct {
Rate int `header:"Rate"`
Domain string `header:"Domain"`
}
err := c.ShouldBindHeader(&h)
|
多部分/URL 编码绑定
多种类型绑定
1
2
3
4
5
6
|
type ProfileForm struct {
Name string `form:"name" binding:"required"`
Avatar *multipart.FileHeader `form:"avatar" binding:"required"`
}
c.ShouldBind(&form)
|
XML、JSON、YAML、TOML 和 ProtoBuf 渲染
1
2
3
4
5
6
7
8
9
10
11
12
13
|
c.JSON(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK})
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)
|
1
2
3
|
c.XML(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK})
c.YAML(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK})
c.TOML(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK})
|
1
2
3
4
5
6
7
8
9
10
|
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)
|
安全JSON
防止 json 劫持
1
|
c.SecureJSON(http.StatusOK, names)
|
JSONP
从不同域中的服务器请求数据。如果查询参数回调存在,则将回调添加到响应主体。
1
|
c.JSONP(http.StatusOK, data)
|
提供静态文件
1
2
3
4
5
6
7
|
//设置静态资源路径
r.Static("/assets", "./assets")
//设置FS,目录下不存 index.html 文件时,会列出该目录下的所有文件
r.StaticFS("/more_static", http.Dir("my_file_system"))
//指定某个具体的文件作为静态资源访问
r.StaticFile("/favicon.ico", "./resources/favicon.ico")
r.StaticFileFS("/more_favicon.ico", "more_favicon.ico", http.Dir("my_file_system"))
|
从文件提供数据
1
2
3
4
5
6
|
//从默认静态资源寻找文件
c.File("local/file.go")
//从某个FS找文件
var fs http.FileSystem = // ...
c.FileFromFS("fs/file.go", fs)
|
重定向
HTTP 重定向支持内部和外部位置
1
2
3
4
|
//外部位置
c.Redirect(http.StatusMovedPermanently, "http://www.google.com/")
//内部位置
c.Redirect(http.StatusFound, "/foo")
|
路由器重定向
1
2
|
c.Request.URL.Path = "/test2"
r.HandleContext(c)
|
自定义中间件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
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)
}
}
r.Use(Logger())
|
中间件内的协程
中间件或处理程序中启动新的 Goroutine 时,你不应该使用其中的原始上下文,你必须使用只读副本
1
2
3
4
5
6
7
8
|
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)
}()
|
自定义 HTTP 配置
直接使用http.ListenAndServe()使用默认构造的Server对象
1
2
3
4
|
func main() {
router := gin.Default()
http.ListenAndServe(":8080", router)
}
|
自己新建一个Server对象
1
2
3
4
5
6
7
8
9
10
11
12
|
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()
}
|
HTTPS
单行配置
1
2
3
4
5
6
7
8
9
10
|
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
|
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))
}
|
使用 Gin 运行多个服务
gin定义的Router(*gin.Engine)其实就是标准net/http包的ServeMux,所以要启动多个服务就需要通过gin定义多个Router,然后使用协程启动定义多个Server对象来启动多个服务。
使用自定义结构绑定表单数据请求
gin会自动查询内嵌结构体的字段来绑定,不需要一定是匿名结构体
1
2
3
4
5
6
7
8
9
10
|
type StructA struct {
FieldA string `form:"field_a"`
}
type StructB struct {
NestedStruct StructA
FieldB string `form:"field_b"`
}
c.Bind(&b)
|
对于代理过的请求,我们可以设置信任的代理服务器来丢弃不信任的代理过的请求
1
|
router.SetTrustedProxies([]string{"192.168.1.2"})
|
测试
net/http/httptest包
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
func setupRouter() *gin.Engine {
r := gin.Default()
r.GET("/ping", func(c *gin.Context) {
c.String(http.StatusOK, "pong")
})
return r
}
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())
}
|