安装
Go版本在1.13+,从 Go 1.13 开始,Go Module模式将成为默认模式
# go version
go version go1.16 darwin/amd64
# go env GO111MODULE
on
gin_test_project # go mod init
gin_test_project # go get -u github.com/gin-gonic/gin
请求路由 - 多种请求
package main
import "github.com/gin-gonic/gin"
func main() {
r := gin.Default()
r.GET("/get", func(c *gin.Context) {
c.String(200, "get")
})
r.POST("/post", func(c *gin.Context) {
c.String(200, "post")
})
// 另一种方式
r.Handle("DELETE", "/delete", func(c *gin.Context) {
c.String(200, "delete")
})
// 一个请求路由对应多个请求方法
r.Any("/any", func(c *gin.Context) {
c.String(200, "any")
})
r.Run()
}
测试
router_type % curl -X GET "http://127.0.0.1:8080/get"
get
router_type % curl -X POST "http://127.0.0.1:8080/post"
post
router_type % curl -X DELETE "http://127.0.0.1:8080/delete"
delete
router_type % curl -X PUT "http://127.0.0.1:8080/any"
any
router_type % curl -X CONNECT "http://127.0.0.1:8080/any"
any
请求路由 - 静态文件夹
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
r := gin.Default()
// 静态文件夹绑定
r.Static("/assets", "./assets")
// 另一种写法
r.StaticFS("/static", http.Dir("static"))
// 设置单个文件资源
r.StaticFile("/favicon.ico", "./favicon.ico")
r.Run()
}
命令行启动
router_static % go build -o router_static && ./router_static
测试
router_static % curl "http://127.0.0.1:8080/assets/a.html"
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
assets content
</body>
</html>
router_static % curl "http://127.0.0.1:8080/static/b.html"
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
static content
</body>
</html>
目录结构
请求路由 - 参数作为url
package main
import "github.com/gin-gonic/gin"
func main() {
r := gin.Default()
r.GET("/:name/:id", func(c *gin.Context) {
c.JSON(200, gin.H{
"name": c.Param("name"),
"id": c.Param("id"),
})
})
r.Run()
}
测试
router_url % curl -X GET "http://127.0.0.1:8080/zhangsan/10"
{"id":"10","name":"zhangsan"}
请求路由 - 泛绑定
package main
import "github.com/gin-gonic/gin"
func main() {
r := gin.Default()
r.GET("/user/*action", func(c *gin.Context) {
c.String(200, "hello world")
})
r.Run()
}
测试
router_generic % curl -X GET "http://127.0.0.1:8080/user/xxxx"
hello world
router_generic % curl -X GET "http://127.0.0.1:8080/user/yyyy"
hello world
获取请求参数 - 获取GET参数
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
r := gin.Default()
r.GET("/test", func(c *gin.Context) {
firstName := c.Query("first_name")
lastName := c.DefaultQuery("last_name", "wang")
c.String(http.StatusOK, "%s, %s", firstName, lastName)
})
r.Run(":8080")
}
测试
param_get % curl -X GET "http://127.0.0.1:8080/test?first_name=zhang"
zhang, wang
param_get % curl -X GET "http://127.0.0.1:8080/test?first_name=zhang&last_name=san"
zhang, san
获取请求参数 - 获取body内容
package main
import (
"bytes"
"github.com/gin-gonic/gin"
"io/ioutil"
"net/http"
)
func main() {
r := gin.Default()
r.POST("/test", func(c *gin.Context) {
bodyBytes, err := ioutil.ReadAll(c.Request.Body)
if err != nil {
c.String(http.StatusBadRequest, err.Error())
c.Abort()
}
//c.String(http.StatusOK, string(bodyBytes))
//firstName := c.PostForm("first_name") // ioutil.ReadAll读完之后PostForm、DefaultPostForm获取不到数据
//lastName := c.DefaultPostForm("last_name", "wang")
//c.String(http.StatusOK, "%s, %s, %s", firstName, lastName, string(bodyBytes))
c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes)) // ioutil.ReadAll读完之后需要将bodyBytes回存到c.Request.Body, PostForm、DefaultPostForm就可以获取到数据
firstName := c.PostForm("first_name")
lastName := c.DefaultPostForm("last_name", "wang")
c.String(http.StatusOK, "%s, %s, %s", firstName, lastName, string(bodyBytes))
})
r.Run()
}
测试
param_body % curl -X POST 'http://127.0.0.1:8080/test' -d 'first_name=zhang&last_name=san'
zhang, san, first_name=zhang&last_name=san
获取请求参数 - 获取bind参数
package main
import (
"github.com/gin-gonic/gin"
"time"
)
type Student struct {
Name string `form:"name"`
Address string `form:"address"`
Birthday time.Time `form:"birthday" time_format:"2006-01-02"`
}
func main() {
r := gin.Default()
r.GET("/stu", student)
r.POST("/stu", student)
r.Run()
}
func student(c *gin.Context) {
var stu Student
// 根据请求content-type来做不同的binding操作
if err := c.ShouldBind(&stu); err == nil {
c.String(200, "%v", stu)
} else {
c.String(200, "student bind error: %v", err)
}
}
测试
param_struct % curl -X GET "http://127.0.0.1:8080/stu?name=zhang&address=hangzhou&birthday=2022-03-20"
{zhang hangzhou 2022-03-20 00:00:00 +0800 CST}
param_struct % curl -X POST "http://127.0.0.1:8080/stu" -d "name=zhang&address=hangzhou&birthday=2022-03-20"
{zhang hangzhou 2022-03-20 00:00:00 +0800 CST}
param_struct % curl -H "Content-Type:application/json" -X POST "http://127.0.0.1:8080/stu" -d '{"name": "zhang", "address": "hangzhou"}'
{zhang hangzhou 0001-01-01 00:00:00 +0000 UTC}
验证请求参数 - 结构体验证
package main
import "github.com/gin-gonic/gin"
type Student struct {
Age int `form:"age" binding:"required,gt=18"` // 同时满足用,有一个满足用|
Name string `form:"name" binding:"required"`
Address string `form:"address" binding:"required"`
}
func main() {
r := gin.Default()
r.GET("/stu", func(c *gin.Context) {
var stu Student
if err := c.ShouldBind(&stu); err != nil {
c.String(500, "%v", err)
c.Abort()
return
}
c.String(200, "%v", stu)
})
r.Run()
}
测试
valid_binding % curl -X GET "http://127.0.0.1:8080/stu?age=11&name=zhang&address=hangzhou"
Key: 'Student.Age' Error:Field validation for 'Age' failed on the 'gt' tag
valid_binding % curl -X GET "http://127.0.0.1:8080/stu?age=19&name=zhang&address=hangzhou"
{19 zhang hangzhou}
valid_binding % curl -X GET "http://127.0.0.1:8080/stu?age=19&name=zhang"
Key: 'Student.Address' Error:Field validation for 'Address' failed on the 'required' tag
验证请求参数 - 自定义验证规则
package main
import (
"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/binding"
"github.com/go-playground/validator/v10" // gin.v1.7.7 use validator.v10
"time"
)
type Book struct {
CheckIn time.Time `form:"check_in" binding:"required,bookdate" time_format:"2006-01-02"` // 在参数 binding 上使用自定义的校验方法函数注册时候的名称
CheckOut time.Time `form:"check_out" binding:"required,gtfield=CheckIn" time_format:"2006-01-02"`
}
var bookDate validator.Func = func(fl validator.FieldLevel) bool {
if date, ok := fl.Field().Interface().(time.Time); ok {
today := time.Now()
if date.Unix() > today.Unix() {
return true
}
}
return false
}
func main() {
r := gin.Default()
// 将我们自定义的校验方法注册到 validator中
if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
// 这里的 key 和 fn 可以不一样最终在 struct 使用的是 key
_ = v.RegisterValidation("bookdate", bookDate)
}
r.GET("/book", func(c *gin.Context) {
var b Book
if err := c.ShouldBind(&b); err != nil {
c.JSON(500, gin.H{"error": err.Error()})
c.Abort()
return
}
c.JSON(200, gin.H{"message": "ok", "book": b})
})
r.Run()
}
测试
valid_custom % curl -X GET "http://127.0.0.1:8080/book?check_in=2022-03-21&check_out=2022-03-23"
{"book":{"CheckIn":"2022-03-21T00:00:00+08:00","CheckOut":"2022-03-23T00:00:00+08:00"},"message":"ok"}
valid_custom % curl -X GET "http://127.0.0.1:8080/book?check_in=2022-03-20&check_out=2022-03-23"
{"error":"Key: 'Book.CheckIn' Error:Field validation for 'CheckIn' failed on the 'bookdate' tag"}
valid_custom % curl -X GET "http://127.0.0.1:8080/book?check_in=2022-03-23&check_out=2022-03-22"
{"error":"Key: 'Book.CheckOut' Error:Field validation for 'CheckOut' failed on the 'gtfield' tag"}
验证请求参数 - 多语言翻译验证
package main
import (
"github.com/gin-gonic/gin"
en2 "github.com/go-playground/locales/en"
zh2 "github.com/go-playground/locales/zh"
ut "github.com/go-playground/universal-translator"
"github.com/go-playground/validator/v10" // 支持多语言化
en_translations "github.com/go-playground/validator/v10/translations/en" // 基于语言做特殊的错误显示,英文语言包
zh_translations "github.com/go-playground/validator/v10/translations/zh" // 基于语言做特殊的错误显示,中文语言包
)
type Student struct {
Age int `form:"age" validate:"required,gt=18"` // 验证tag为validate
Name string `form:"name" validate:"required"`
Address string `form:"address" validate:"required"`
}
var (
Uni *ut.UniversalTranslator
Validate *validator.Validate
)
// 验证信息多语言化
func main() {
Validate = validator.New() // 创建验证器
zh := zh2.New()
en := en2.New()
Uni = ut.New(zh, en) // 创建翻译器,设置支持的语言
r := gin.Default()
r.GET("/stu", func(c *gin.Context) {
locale := c.DefaultQuery("locale", "zh")
// 根据请求语言参数获取相应的翻译器
trans, _ := Uni.GetTranslator(locale)
switch locale {
case "zh":
_ = zh_translations.RegisterDefaultTranslations(Validate, trans) // 中文语言翻译器注册到验证器
case "en":
_ = en_translations.RegisterDefaultTranslations(Validate, trans) // 英文语言翻译器注册到验证器
default:
_ = zh_translations.RegisterDefaultTranslations(Validate, trans)
}
var stu Student
if err := c.ShouldBind(&stu); err != nil {
c.String(500, "%v", err)
c.Abort()
return
}
// 验证结构体
if err := Validate.Struct(stu); err != nil {
errs := err.(validator.ValidationErrors) // 把错误转化为错误集合
sliceErrs := []string{}
// 遍历错误,翻译成相应的语言
for _, e := range errs {
sliceErrs = append(sliceErrs, e.Translate(trans))
}
c.String(500, "%v", sliceErrs)
c.Abort()
return
}
c.String(200, "%v", stu)
})
r.Run()
}
测试
valid_v10 % curl -X GET "http://127.0.0.1:8080/stu"
[Age为必填字段 Name为必填字段 Address为必填字段]
valid_v10 % curl -X GET "http://127.0.0.1:8080/stu?name=zhang"
[Age为必填字段 Address为必填字段]
valid_v10 % curl -X GET "http://127.0.0.1:8080/stu?name=zhang&age=17"
[Age必须大于18 Address为必填字段]
valid_v10 % curl -X GET "http://127.0.0.1:8080/stu?name=zhang&age=17&address=hangzhou&locale=en"
[Age must be greater than 18]
valid_v10 % curl -X GET "http://127.0.0.1:8080/stu?name=zhang&age=19&address=hangzhou"
{19 zhang hangzhou}
中间件 - 使用
package main
import (
"github.com/gin-gonic/gin"
"io"
"os"
)
func main() {
f, _ := os.Create("gin.log")
gin.DefaultWriter = io.MultiWriter(f)
gin.DefaultErrorWriter = io.MultiWriter(f)
r := gin.New()
r.Use(gin.Logger(), gin.Recovery())
r.GET("/test", func(c *gin.Context) {
name := c.DefaultQuery("name", "zhang")
panic("test painc")
c.String(200, "%s", name)
})
r.Run()
}
测试
middleware_gin % curl -X GET "http://127.0.0.1:8080/test"
[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
- using env: export GIN_MODE=release
- using code: gin.SetMode(gin.ReleaseMode)
[GIN-debug] GET /test --> main.main.func1 (3 handlers)
[GIN-debug] [WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.
Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.
[GIN-debug] Environment variable PORT is undefined. Using port :8080 by default
[GIN-debug] Listening and serving HTTP on :8080
[31m2022/03/22 20:38:46 [Recovery] 2022/03/22 - 20:38:46 panic recovered:
GET /test HTTP/1.1
Host: 127.0.0.1:8080
Accept: */*
User-Agent: curl/7.64.1
test painc
/Users/zhanghao/go/src/scripts/gin_test_project/middleware_gin/main.go:17 (0x153eaf0)
main.func1: panic("test painc")
/Users/zhanghao/go/pkg/mod/github.com/gin-gonic/gin@v1.7.7/context.go:168 (0x153cff9)
(*Context).Next: c.handlers[c.index](c)
/Users/zhanghao/go/pkg/mod/github.com/gin-gonic/gin@v1.7.7/recovery.go:99 (0x153cfe0)
CustomRecoveryWithWriter.func1: c.Next()
/Users/zhanghao/go/pkg/mod/github.com/gin-gonic/gin@v1.7.7/context.go:168 (0x153c0d3)
(*Context).Next: c.handlers[c.index](c)
/Users/zhanghao/go/pkg/mod/github.com/gin-gonic/gin@v1.7.7/logger.go:241 (0x153c092)
LoggerWithConfig.func1: c.Next()
/Users/zhanghao/go/pkg/mod/github.com/gin-gonic/gin@v1.7.7/context.go:168 (0x1531faf)
(*Context).Next: c.handlers[c.index](c)
/Users/zhanghao/go/pkg/mod/github.com/gin-gonic/gin@v1.7.7/gin.go:555 (0x1531f95)
(*Engine).handleHTTPRequest: c.Next()
/Users/zhanghao/go/pkg/mod/github.com/gin-gonic/gin@v1.7.7/gin.go:511 (0x1531a4a)
(*Engine).ServeHTTP: engine.handleHTTPRequest(c)
/usr/local/go/src/net/http/server.go:2887 (0x12a6a02)
serverHandler.ServeHTTP: handler.ServeHTTP(rw, req)
/usr/local/go/src/net/http/server.go:1952 (0x12a1e2c)
(*conn).serve: serverHandler{c.server}.ServeHTTP(w, w.req)
/usr/local/go/src/runtime/asm_amd64.s:1371 (0x106d660)
goexit: BYTE $0x90 // NOP
[0m
[GIN] 2022/03/22 - 20:38:46 | 500 | 5.549236ms | 127.0.0.1 | GET "/test"
中间件 - 自定义中间件
package main
import "github.com/gin-gonic/gin"
func main() {
r := gin.Default()
r.Use(IPAuthMiddleware())
r.GET("/test", func(c *gin.Context) {
c.String(200, "test gin middleware")
})
r.Run()
}
func IPAuthMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
ipList := []string{
"127.0.0.2",
}
flag := false
clientIP := c.ClientIP()
for _, host := range ipList {
if clientIP == host {
flag = true
break
}
}
if !flag {
c.String(401, "%s not in ipList", clientIP)
c.Abort()
}
}
}
测试
middleware_whitelist % curl -X GET "http://127.0.0.1:8080/test"
127.0.0.1 not in ipList
优雅关停服务器
package main
import (
"context"
"github.com/gin-gonic/gin"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"time"
)
func main() {
r := gin.Default()
r.GET("/test", func(c *gin.Context) {
time.Sleep(time.Second * 10)
c.String(200, "test shutdown")
})
srv := &http.Server{
Addr: ":8080",
Handler: r,
}
go func() {
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("listen: %s\n", err)
}
}()
exit := make(chan os.Signal)
signal.Notify(exit, syscall.SIGINT, syscall.SIGTERM) // 捕获Ctrl + C , kill -15
<-exit
log.Println("shutdown server...")
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) // 创建超时上下文
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
log.Fatal("server shutdown:", err)
}
log.Println("server exiting")
}
测试
gin_shutdown % curl -X GET "http://127.0.0.1:8080/test"
test shutdown
^C2022/03/22 21:27:52 shutdown server...
[GIN] 2022/03/22 - 21:27:56 | 200 | 10.001138189s | 127.0.0.1 | GET "/test"
2022/03/22 21:27:56 server exiting
模版渲染
package main
import "github.com/gin-gonic/gin"
func main() {
r := gin.Default()
r.LoadHTMLGlob("template/*")
r.GET("index", func(c *gin.Context) {
c.HTML(200, "index.html", gin.H{
"title": "index.html",
})
})
r.Run()
}
测试
gin_template % curl -X GET "http://127.0.0.1:8080/index"
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>
index.html, test template
</h1>
</body>
</html>