Gin学习笔记
Parameters in path
router := gin.router() //这个处理器匹配/user/yang,但是不匹配/user或者/user/ router.GET("/user/:name",func(c *gin.Context){...}) //这个处理器匹配/user/yang/和/user/yang/send,但是其中参数action带/,使用时需要处理 router.GET("/user/:name/*action", func(c *gin.Context) {...})
Querystring parameters
router.GET("/welcome", func(c *gin.Context) { //使用DefaultQuery时,如果url中没有相应查询,就会将第二个参数作为默认值 firstname := c.DefaultQuery("firstname", "Guest") lastname := c.Query("lastname") c.String(http.StatusOK, "Hello %s %s", firstname, lastname) })
Mutlipart/Urlencoded Form
router.POST("/form_post", func(c *gin.Context) { message := c.PostForm("message") //同DefaultQuery相同 nick := c.DefaultPostForm("nick", "annoymous") c.JSON(http.StatusOK, gin.H{ "status": "posted", "message": message, "nick": nick, }) fmt.Println(c.Params, c.Request) })
Map as querystring or postform parameters
//QueryMap解析出URL中以?开头的map,PostFormMap解析出Body中的map router.POST("/post", func(c *gin.Context) { ids := c.QueryMap("ids") names := c.PostFormMap("names") fmt.Println("ids: %v, names: %v", ids, names) })
Upload file
文件名不该可信。
router.POST("/upload", func(c *gin.Context) { file, _ := c.FormFile("file") log.Println(file.Filename) c.String(http.StatusOK, fmt.Sprintf("'%s' upload!", file.Filename)) }) //其中发送的请求头:Content-Type:multipart/form-data
Multiple files
router.POST("/upload", func(c *gin.Context) { form, _ := c.MultipartForm() files := form.File["file"] for _, file := range files { log.Println(file.Filename) tempfile, _ := os.OpenFile(file.Filename, os.O_CREATE, 0666) tempfile.Close() c.SaveUploadedFile(file, "./"+file.Filename) } c.String(http.StatusOK, fmt.Sprintf("%d files uploaded!", len(files))) })
Grouping routes
v1 := router.Group("/v1") { v1.POST("/login", loginEndPoint) v1.POST("/submit", submitEndPoint) } v2 := router.Group("/v2") { v2.POST("/login", loginEndPoint) v2.POST("/submit", submitEndPoint) }
不带中间件的默认gin
router := gin.New() router.Use(gin.Logger())//全局中间件 router.Use(gin.Recover()) //下面两句同:authorized := r.Group("/",AuthRequired()) authorized := router.Group("/") authorized.Use(AuthRequired())
写日志文件
f, _ := os.Create("gin.log") //同时写日志到多个文件 gin.DefaultWriter = io.MultiWriter(f, os.Stdout) router := gin.Default() router.GET("/ping", func(c *gin.Context) { c.String(http.StatusOK, "pong") })
定制日志格式
router := gin.New() router.Use(gin.LoggerWithFormatter(func(param gin.LogFormatterParams) string { 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, ) }))
模型绑定与验证
可以绑定JSON、XML、YAML和标准表单形式。
提供两种设置方法进行绑定:
- 类型-Must bind
- 方法-
Bind
,BindJSON
,BindQuery
,BindYAML
,BindHeader
- 行为-使用
MustBindWith
如果绑定错误,就会调用c.AbortWithError(400,err).SetType(ErrorTypeBind)
。这个响应状态是400,并且Content-Type
头设置为text/plain;charset=utf-8
。如果之后试图设置响应码,将会导致警告。如果希望有更好的控制,使用ShouldBind
。
- 方法-
- 类型-Should bind
- 方法-
ShouldBind
,ShouldJSON
,ShouldBindXML
,ShouldBindQuery
,ShouldBindYAML``ShouldBindHeader
。 - 行为-如果绑定发生错误,错误将会返回。
- 方法-
如果已经确保绑定,可以调用MustBindWith
或者ShouldBindWith
。如果一个域为binding:"required"
,有一个空值,将会返回一个错误。如果可以忽略一个绑定,可以使用binding:"-"
。
type Login struct { User string `form:"user" json:"user" xml:"user" binding:"required"` Password string `form:"password" json:"password" xml:"password" binding:"-"` } 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("/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"}) }) router.POST("/loginForm", func(c *gin.Context) { var form Login 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"}) })
定制验证器
type Booking struct { CheckIn time.Time `form:"check_in" binding:"required" time_format:"2006-01-02"` CheckOut time.Time `form:"check_out" binding:"required" time_format:"2006-01-02"` } var bookableDate validator.Func = func(fl validator.FieldLevel) bool { date, ok := fl.Field().Interface().(time.Time) if ok { today := time.Now() if today.After(date) { return false } } return true } //main router := gin.Default() if v, ok := binding.Validator.Engine().(*validator.Validate); ok { v.RegisterValidation("bookabledate", bookableDate) } router.GET("/bookable", getBookable) // func getBookable(c *gin.Context) { var b Booking if err := c.ShouldBindWith(&b, binding.Query); err == nil { c.JSON(http.StatusOK, gin.H{"message": "Booking dates are valid!"}) } else { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) } }
只绑定查询字符串
if c.ShouldBindQuery(&person) == nil { log.Println("=====Only Bind By Query String=====") log.Println(person.Name) log.Println(person.Address) }
绑定查询字符串或者Post Data
if c.ShouldBind(&person) == nil { log.Println(person) }
绑定URI
type Person struct { ID string `uri:"id" binding:"required,uuid"` Name string `uri:"name" binding:"required"` } 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}) return } c.JSON(http.StatusOK, gin.H{"name": person.Name, "uuid": person.ID}) })
绑定头部
type TestHeader struct { Rate int `header:"Rate"` Domain string `header:"Domain"` } r.GET("/", func(c *gin.Context) { h := TestHeader{} if err := c.ShouldBindHeader(&h); err != nil { c.JSON(http.StatusBadRequest, err) return } fmt.Printf("%#v\n", h) c.JSON(http.StatusOK, gin.H{"Rate": h.Rate, "Domain": h.Domain}) })
另外还可以绑定HTML检测
type myForm struct{ Colors []string `form:"colors[]"` } func formHandler(c *gin.Context){ var fakeForm myForm c.ShouldBind(&fakeForm) c.JSON(200,gin.H{"color":fakeForm.Colors}) }
<form action="/" method="POST"> <p> Check some colors </p> <label for="red">Red</label> <input type="checkbox" name="colors[]" value="red" id="red"> </form>
绑定Multipart/Urlencoded
package main import ( "mime/multipart" "net/http" "github.com/gin-gonic/gin" ) type ProfileForm struct { Name string `form:"name" binding:"required"` Avatar *multipart.FileHeader `form:"avatar" binding:"required"` //如果是多文件 //Avatar []*multipart.FileHeader `form:"avatar" binding:"required"` } func main() { router := gin.Default() router.POST("/profile", func(c *gin.Context) { var form ProfileForm //可以使用显式绑定声明:c.ShouldBindWith(&form,binding.Form) //或者使用自动绑定:c.ShouldBind() 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() }
提交XML,JSON,YAML和ProtoBuf
package main import ( "net/http" "github.com/gin-gonic/gin/testdata/protoexample" "github.com/gin-gonic/gin" ) func main() { r := gin.Default() 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) { var msg struct { Name string `json:"user"` Message string Number int } msg.Name = "Yang" msg.Message = "hey" msg.Number = 13 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("/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() }
安全JSON
使用SecureJSON可以预防JSON劫持,如果结构体是一个数组类型,默认添加前缀while(1),
package main import ( "net/http" "github.com/gin-gonic/gin" ) func main() { r := gin.Default() r.GET("/someJSON", func(c *gin.Context) { names := []string{"yang", "yu"} c.SecureJSON(http.StatusOK, names) }) r.Run() } //输出 while(1);[ "yang", "yu" ]