gin
1.quick start
package main import "github.com/gin-gonic/gin" func main() { r := gin.Default() //default: router with logger and recovery(crash-free) middleware // recovery: handle "panic" r.GET("/ping", func(c *gin.Context) { c.JSON(200, gin.H{ "message": "pong", }) }) r.Run(":8083") // listen and serve on 0.0.0.0:8080 }
2. restful
package main import "github.com/gin-gonic/gin" func main() { // 使用默认中间件创建一个gin路由器 // logger and recovery (crash-free) 中间件 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() // router.Run(":3000") for a hard coded port }
3. router.Group 路由分组
package main import ( "github.com/gin-gonic/gin" "net/http" ) func goodlist(ctx *gin.Context) { ctx.JSON(http.StatusOK, gin.H{ "name": "123", }) } func main() { router := gin.Default() group := router.Group("/goods") { group.GET("/list", goodlist) group.POST("/add", goodlist) } router.Run(":8083") }
4. variables in URL
package main import ( "github.com/gin-gonic/gin" "net/http" ) type Person struct { ID string `uri:"id" binding:"required,uuid"` Name string `uri:"name" binding:"required"` } func goodlist(ctx *gin.Context) { ctx.JSON(http.StatusOK, gin.H{ "name": ctx.Param("id"), }) } func main() { router := gin.Default() group := router.Group("/goods") { //group.GET("/list", goodlist) 不能和下面的同时出现。怎么解决? group.GET("/:id", goodlist) // *: show the path group.GET("/:id/*action", func(ctx *gin.Context) { ctx.JSON(http.StatusOK, gin.H{ "action": ctx.Param("action"), }) }) // add constrains to url group.GET("/:id/:name", func(c *gin.Context) { var person Person if err := c.ShouldBindUri(&person); err != nil { c.JSON(http.StatusNotFound, gin.H{"msg": err}) return } c.JSON(http.StatusOK, gin.H{"name": person.Name, "uuid": person.ID}) }) } router.Run(":8083") }
5. params from GET POST
package main import ( "github.com/gin-gonic/gin" "net/http" ) func main() { router := gin.Default() router.GET("/welcome", func(ctx *gin.Context) { fstName := ctx.DefaultQuery("name", "bobby") age := ctx.DefaultQuery("age", "12") ctx.JSON(http.StatusOK, gin.H{ "name": fstName, "age": age, }) }) router.POST("/post", func(ctx *gin.Context) { fstName := ctx.DefaultPostForm("name", "bobby") age := ctx.DefaultPostForm("age", "12") ctx.JSON(http.StatusOK, gin.H{ "name": fstName, "age": age, }) }) // url + param; body + param router.POST("/getpost", func(ctx *gin.Context) { gname := ctx.Query("gname") gage := ctx.DefaultQuery("gage", "12") fstName := ctx.PostForm("name") age := ctx.DefaultPostForm("age", "12") ctx.JSON(http.StatusOK, gin.H{ "name": fstName, "age": age, "gname": gname, "gage": gage, }) }) router.Run(":8083") }
6. output: JSON/protobuf/pure JSON
package main import ( "net/http" "github.com/gin-gonic/gin" "GoProjects/gin_start/ch05/proto" ) func main() { router := gin.Default() router.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) }) router.GET("/moreProtobuf", func(c *gin.Context) { courses := []string{"python", "django", "go"} // The specific definition of protobuf is written in the testdata/protoexample file. data := &proto.Teacher{ Name: "bobby", Course: courses, } c.ProtoBuf(http.StatusOK, data) }) router.Run(":8083") }
7. validator + error 注册翻译器
package main import ( "fmt" "github.com/gin-gonic/gin" "github.com/gin-gonic/gin/binding" "github.com/go-playground/locales/en" "github.com/go-playground/locales/zh" ut "github.com/go-playground/universal-translator" "github.com/go-playground/validator/v10" enTranslations "github.com/go-playground/validator/v10/translations/en" zhTranslations "github.com/go-playground/validator/v10/translations/zh" "net/http" ) // 定义一个全局翻译器T var trans ut.Translator // InitTrans 初始化翻译器 func InitTrans(locale string) (err error) { // 修改gin框架中的Validator引擎属性,实现自定制 if v, ok := binding.Validator.Engine().(*validator.Validate); ok { zhT := zh.New() // 中文翻译器 enT := en.New() // 英文翻译器 // 第一个参数是备用(fallback)的语言环境 // 后面的参数是应该支持的语言环境(支持多个) // uni := ut.New(zhT, zhT) 也是可以的 uni := ut.New(enT, zhT, enT) // locale 通常取决于 http 请求头的 'Accept-Language' var ok bool // 也可以使用 uni.FindTranslator(...) 传入多个locale进行查找 trans, ok = uni.GetTranslator(locale) if !ok { return fmt.Errorf("uni.GetTranslator(%s) failed", locale) } // 注册翻译器 switch locale { case "en": err = enTranslations.RegisterDefaultTranslations(v, trans) case "zh": err = zhTranslations.RegisterDefaultTranslations(v, trans) default: err = enTranslations.RegisterDefaultTranslations(v, trans) } return } return } // 绑定为json type Login struct { User string `form:"user" json:"user" xml:"user" binding:"required,m=3,max=10"` Password string `form:"password" json:"password" xml:"password" binding:"required"` } type SignUpParam struct { Age uint8 `json:"age" binding:"gte=1,lte=130"` Name string `json:"name" binding:"required"` Email string `json:"email" binding:"required,email"` Password string `json:"password" binding:"required"` RePassword string `json:"re_password" binding:"required,eqfield=Password"` } func main() { if err := InitTrans("en"); err != nil { fmt.Printf("init trans failed, err:%v\n", err) return } 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"}) }) router.POST("/signup", func(c *gin.Context) { var u SignUpParam if err := c.ShouldBind(&u); err != nil { // shouldBind()自动判断是JSON还是FORM errs, ok := err.(validator.ValidationErrors) if !ok { // 非validator.ValidationErrors类型错误直接返回 c.JSON(http.StatusOK, gin.H{ "msg": err.Error(), }) return } // validator.ValidationErrors类型错误则进行翻译 c.JSON(http.StatusOK, gin.H{ "msg": errs.Translate(trans), }) return } // 保存入库等业务逻辑代码... c.JSON(http.StatusOK, "success") }) router.Run(":8083") }
result:
{ "msg": { "SignUpParam.Age": "Age must be 1 or greater", "SignUpParam.Email": "Email is a required field", "SignUpParam.Name": "Name is a required field", "SignUpParam.Password": "Password is a required field", "SignUpParam.RePassword": "RePassword is a required field" } }
improve error form: "msg": improveStruct(errs.Translate(trans)), // map[string]string
func improveStruct(m map[string]string) map[string]string { rsp := map[string]string{} for key, value := range m { rsp[key[strings.Index(key, ".")+1:]] = value } return rsp }
7-1. validator - add Regex
form:
type Login struct { Mobile string `form:"mobile" json:"mobile" binding:"required,mobile"` Password string `form:"password" json:"password" binding:"required,min=3,max=6"` }
validator:
package validator import ( "github.com/go-playground/validator/v10" "regexp" ) func ValidateMobile(fl validator.FieldLevel) bool { mobile := fl.Field().String() ok, _ := regexp.MatchString(`^1([38][0-9]|14[579]|5[^4]|16[6]|7[1-35-8]|9[189])\d{8}$`, mobile) return ok }
main.go
bind regex to validator & transform return error
if v, ok := binding.Validator.Engine().(*validator.Validate); ok { _ = v.RegisterValidation("mobile", myvalidator.ValidateMobile) _ = v.RegisterTranslation("mobile", global.Trans, func(ut ut.Translator) error { return ut.Add("mobile", "{0} 非法的手机号码!", true) // see universal-translator for details }, func(ut ut.Translator, fe validator.FieldError) string { t, _ := ut.T("mobile", fe.Field()) return t }) }
8. middleware: not invade the code
package main import ( "fmt" "github.com/gin-gonic/gin" "time" ) func main() { router := gin.New() router.Use(MyLogger()) router.GET("/ping", func(c *gin.Context) { c.JSON(200, gin.H{ "message": "pong", }) }) router.Run(":8083") } func MyLogger() gin.HandlerFunc { return func(c *gin.Context) {
// before the logic t := time.Now() c.Set("example", "1234") // continue the business logic c.Next()
// logic ends end := time.Since(t) fmt.Printf("%v\n", end) status := c.Writer.Status() fmt.Printf("状态:%d", status) } }
why `return` not work?
func MyAuth() gin.HandlerFunc { return func(c *gin.Context) { var token string for k, v := range c.Request.Header { if k == "X-Token" { // input: x-token ---> X-Token token = v[0] } } if token != "bobby" { c.JSON(http.StatusUnauthorized, gin.H{ "message": "not logged in", }) c.Abort() // `return` not work } c.Next() } }
9. middleware's mechanism: [] handler

使用router.Use()是将handler顺序放在slice中

调用GET时,把最后一个放进slice中
router.GET("/ping", func(c *gin.Context) { c.JSON(200, gin.H{ "message": "pong", }) })
context.Next(): 移动index,执行next handler()

使用 return 只会结束当前handler(),进行下一个handler()

c.abort()是移动index到最后

10. template
--dir
--main.go
--main.exe
--templates
--index.gohtml
main.go:
package main import ( "github.com/gin-gonic/gin" "net/http" ) func main() { router := gin.Default() router.LoadHTMLFiles("templates/index.gohtml") // not use absolute path! router.GET("/index", func(c *gin.Context) { c.HTML(http.StatusOK, "index.gohtml", gin.H{ "title": "mooc", }) }) router.Run(":8083") }
index.gohtml: {{.title}}
How to load many .gohtmls?

package main import ( "github.com/gin-gonic/gin" "net/http" ) func main() { router := gin.Default() //router.LoadHTMLFiles("templates/index.gohtml") router.LoadHTMLGlob("templates/**/*") // load二级目录 router.GET("/index", func(c *gin.Context) { c.HTML(http.StatusOK, "index.gohtml", gin.H{ // without {{define}},use original name "title": "mooc", }) }) router.GET("/goods", func(c *gin.Context) { c.HTML(http.StatusOK, "goods/list.gohtml", gin.H{ // with {{define}}, use defined name "title": "goods", }) }) router.GET("/users", func(c *gin.Context) { c.HTML(http.StatusOK, "users/list.gohtml", gin.H{ "title": "users", }) }) router.Run(":8083") }
.gohtml:
{{define "goods/list.gohtml"}} <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1> {{.title}}</h1> </body> </html> {{end}}
how to test ? go build main.go; ./main.exe
11. static
main.go load static files router.Static("/static", "./static") 前者:URL以static开头,后者:在当前static文件夹下寻找
.gohtml: <link rel="stylesheet" href="/static/css/test.css">
12. gracefully exit
package main import ( "context" "log" "net/http" "os" "os/signal" "syscall" "time" "github.com/gin-gonic/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, } go func() { // 开始listen,hang on ... if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { log.Fatalf("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 syscanll.SIGTERM // kill -2 is syscall.SIGINT // kill -9 is syscall. SIGKILL but can"t be catch, so don't need add it signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) <-quit // 抓住停止信号 log.Println("Shutdown Server ...") // 主线程不挂,等待5s ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() // goroutine挂 if err := srv.Shutdown(ctx); err != nil { log.Fatalf("Server Shutdown:", err) } // catching ctx.Done(). timeout of 5 seconds. select { // 等待5s结束,主线程挂 case <-ctx.Done(): log.Println("timeout of 5 seconds.") } log.Println("Server exiting") }

浙公网安备 33010602011771号