// This handler will match /user/john but will not match /user/ or /user router.GET("/user/:name", func(c *gin.Context) { name := c.Param("name") c.String(http.StatusOK, "Hello %s", name) })
// However, this one will match /user/john/ and also /user/john/send // If no other routers match /user/john, it will redirect to /user/john/ router.GET("/user/:name/*action", func(c *gin.Context) { name := c.Param("name") action := c.Param("action") message := name + " is " + action c.String(http.StatusOK, message) })
router.Run(":8080") }
获取 Query 中的参数
1 2 3 4 5 6 7 8 9 10 11 12 13
funcmain() { router := gin.Default()
// Query string parameters are parsed using the existing underlying request object. // The request responds to a url matching: /welcome?firstname=Jane&lastname=Doe router.GET("/welcome", func(c *gin.Context) { firstname := c.DefaultQuery("firstname", "Guest") lastname := c.Query("lastname") // shortcut for c.Request.URL.Query().Get("lastname")
funcmain() { // Creates a router without any middleware by default r := gin.New()
// Global middleware // Logger middleware will write the logs to gin.DefaultWriter even if you set with GIN_MODE=release. // By default gin.DefaultWriter = os.Stdout r.Use(gin.Logger())
// Recovery middleware recovers from any panics and writes a 500 if there was one. r.Use(gin.Recovery())
// Per route middleware, you can add as many as you desire. r.GET("/benchmark", MyBenchLogger(), benchEndpoint)
// Authorization group // authorized := r.Group("/", AuthRequired()) // exactly the same as: authorized := r.Group("/") // per group middleware! in this case we use the custom created // AuthRequired() middleware just in the "authorized" group. authorized.Use(AuthRequired()) { authorized.POST("/login", loginEndpoint) authorized.POST("/submit", submitEndpoint) authorized.POST("/read", readEndpoint)
// nested group testing := authorized.Group("testing") testing.GET("/analytics", analyticsEndpoint) }
// Listen and serve on 0.0.0.0:8080 r.Run(":8080") }
输出日志文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
funcmain() { // Disable Console Color, you don't need console color when writing the logs to file. gin.DisableConsoleColor()
// Logging to a file. f, _ := os.Create("gin.log") gin.DefaultWriter = io.MultiWriter(f)
// Use the following code if you need to write the logs to file and console at the same time. // gin.DefaultWriter = io.MultiWriter(f, os.Stdout)
Note that you need to set the corresponding binding tag on all fields you want to bind. For example, when binding from JSON, set json:"fieldname".
Also, Gin provides two sets of methods for binding:
Type - Must bind
Methods - Bind, BindJSON, BindXML, BindQuery
Behavior - These methods use MustBindWith under the hood. If there is a binding error, the request is aborted with c.AbortWithError(400, err).SetType(ErrorTypeBind). This sets the response status code to 400 and the Content-Type header is set to text/plain; charset=utf-8. Note that if you try to set the response code after this, it will result in a warning [GIN-debug] [WARNING] Headers were already written. Wanted to override status code 400 with 422. If you wish to have greater control over the behavior, consider using the ShouldBind equivalent method.
Behavior - These methods use ShouldBindWith under the hood. If there is a binding error, the error is returned and it is the developer’s responsibility to handle the request and error appropriately.
When using the Bind-method, Gin tries to infer the binder depending on the Content-Type header. If you are sure what you are binding, you can use MustBindWith or ShouldBindWith.
You can also specify that specific fields are required. If a field is decorated with binding:"required" and has a empty value when binding, an error will be returned.
// Binding from JSON type Login struct { User string`form:"user" json:"user" xml:"user" binding:"required"` Password string`form:"password" json:"password" xml:"password" binding:"required"` }
funcmain() { 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"}) })
// Example for binding XML ( // <?xml version="1.0" encoding="UTF-8"?> // <root> // <user>user</user> // <password>123</user> // </root>) 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"}) })
// Example for binding a HTML form (user=manu&password=123) router.POST("/loginForm", func(c *gin.Context) { var form Login // This will infer what binder to use depending on the content-type header. 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"}) })
// Listen and serve on 0.0.0.0:8080 router.Run(":8080") }
Sample request
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
$ curl -v -X POST \ http://localhost:8080/loginJSON \ -H 'content-type: application/json' \ -d '{ "user": "manu" }' > POST /loginJSON HTTP/1.1 > Host: localhost:8080 > User-Agent: curl/7.51.0 > Accept: */* > content-type: application/json > Content-Length: 18 > * upload completely sent off: 18 out of 18 bytes < HTTP/1.1 400 Bad Request < Content-Type: application/json; charset=utf-8 < Date: Fri, 04 Aug 2017 03:51:31 GMT < Content-Length: 100 < {"error":"Key: 'Login.Password' Error:Field validation for 'Password' failed on the 'required' tag"}
Skip validate
When running the above example using the above the curl command, it returns error. Because the example use binding:"required" for Password. If use binding:"-" for Password, then it will not return error when running the above example again.
自定义检验方法
It is also possible to register custom validators. See the example code.
funcgetBookable(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()}) } }
1 2 3 4 5
$ curl "localhost:8085/bookable?check_in=2018-04-16&check_out=2018-04-17" {"message":"Booking dates are valid!"}
$ curl "localhost:8085/bookable?check_in=2018-03-08&check_out=2018-03-09" {"error":"Key: 'Booking.CheckIn' Error:Field validation for 'CheckIn' failed on the 'bookabledate' tag"}
funcstartPage(c *gin.Context) { var person Person if c.ShouldBindQuery(&person) == nil { log.Println("====== Only Bind By Query String ======") log.Println(person.Name) log.Println(person.Address) } c.String(200, "Success") }
type Person struct { Name string`form:"name"` Address string`form:"address"` Birthday time.Time `form:"birthday" time_format:"2006-01-02" time_utc:"1"` }
funcstartPage(c *gin.Context) { var person Person // If `GET`, only `Form` binding engine (`query`) used. // If `POST`, first checks the `content-type` for `JSON` or `XML`, then uses `Form` (`form-data`). // See more at https://github.com/gin-gonic/gin/blob/master/binding/binding.go#L48 if c.ShouldBind(&person) == nil { log.Println(person.Name) log.Println(person.Address) log.Println(person.Birthday) }
c.String(200, "Success") }
Test it with:
1
$ curl -X GET "localhost:8085/testing?name=appleboy&address=xyz&birthday=1992-03-15"
type LoginForm struct { User string`form:"user" binding:"required"` Password string`form:"password" binding:"required"` }
funcmain() { router := gin.Default() router.POST("/login", func(c *gin.Context) { // you can bind multipart form with explicit binding declaration: // c.ShouldBindWith(&form, binding.Form) // or you can simply use autobinding with ShouldBind method: var form LoginForm // in this case proper binding will be automatically selected if c.ShouldBind(&form) == nil { if form.User == "user" && form.Password == "password" { c.JSON(200, gin.H{"status": "you are logged in"}) } else { c.JSON(401, gin.H{"status": "unauthorized"}) } } }) router.Run(":8080") }
// gin.H is a shortcut for map[string]interface{} 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) { // 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) })
r.GET("/someProtoBuf", func(c *gin.Context) { 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) })
// Listen and serve on 0.0.0.0:8080 r.Run(":8080") }
使用 SecureJSON
Using SecureJSON to prevent json hijacking. Default prepends "while(1)," to response body if the given struct is array values.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
funcmain() { r := gin.Default()
// You can also use your own secure json prefix // r.SecureJsonPrefix(")]}',\n")
// will output : {"lang":"GO\u8bed\u8a00","tag":"\u003cbr\u003e"} c.AsciiJSON(http.StatusOK, data) })
// Listen and serve on 0.0.0.0:8080 r.Run(":8080") }
使用 PureJSON
Normally, JSON replaces special HTML characters with their unicode entities, e.g. < becomes \u003c. If you want to encode such characters literally, you can use PureJSON instead. This feature is unavailable in Go 1.6 and lower.
// simulate some private data var secrets = gin.H{ "foo": gin.H{"email": "foo@bar.com", "phone": "123433"}, "austin": gin.H{"email": "austin@example.com", "phone": "666"}, "lena": gin.H{"email": "lena@guapa.com", "phone": "523443"}, }
funcmain() { r := gin.Default()
// Group using gin.BasicAuth() middleware // gin.Accounts is a shortcut for map[string]string authorized := r.Group("/admin", gin.BasicAuth(gin.Accounts{ "foo": "bar", "austin": "1234", "lena": "hello2", "manu": "4321", }))
// /admin/secrets endpoint // hit "localhost:8080/admin/secrets authorized.GET("/secrets", func(c *gin.Context) { // get user, it was set by the BasicAuth middleware user := c.MustGet(gin.AuthUserKey).(string) if secret, ok := secrets[user]; ok { c.JSON(http.StatusOK, gin.H{"user": user, "secret": secret}) } else { c.JSON(http.StatusOK, gin.H{"user": user, "secret": "NO SECRET :("}) } })
// Listen and serve on 0.0.0.0:8080 r.Run(":8080") }
在中间件中使用 Goroutines 协程
When starting new Goroutines inside a middleware or handler, you SHOULD NOT use the original context inside it, you have to use a read-only copy.
r.GET("/long_async", func(c *gin.Context) { // create copy to be used inside the goroutine cCp := c.Copy() gofunc() { // 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) }() })
r.GET("/long_sync", func(c *gin.Context) { // simulate a long task with time.Sleep(). 5 seconds time.Sleep(5 * time.Second)
// since we are NOT using a goroutine, we do not have to copy the context log.Println("Done! in path " + c.Request.URL.Path) })
// Listen and serve on 0.0.0.0:8080 r.Run(":8080") }
manners: A polite Go HTTP server that shuts down gracefully.
graceful: Graceful is a Go package enabling graceful shutdown of an http.Handler server.
grace: Graceful restart & zero downtime deploy for Go servers.
If you are using Go 1.8, you may not need to use this library! Consider using http.Server’s built-in Shutdown() method for graceful shutdowns. See the full graceful-shutdown example with gin.
// Wait for interrupt signal to gracefully shutdown the server with // a timeout of 5 seconds. quit := make(chan os.Signal) signal.Notify(quit, os.Interrupt) <-quit log.Println("Shutdown Server ...")
type formA struct { Foo string`json:"foo" xml:"foo" binding:"required"` }
type formB struct { Bar string`json:"bar" xml:"bar" binding:"required"` }
funcSomeHandler(c *gin.Context) { objA := formA{} objB := formB{} // This c.ShouldBind consumes c.Request.Body and it cannot be reused. if errA := c.ShouldBind(&objA); errA == nil { c.String(http.StatusOK, `the body should be formA`) // Always an error is occurred by this because c.Request.Body is EOF now. } elseif errB := c.ShouldBind(&objB); errB == nil { c.String(http.StatusOK, `the body should be formB`) } else { ... } }
For this, you can use c.ShouldBindBodyWith.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
funcSomeHandler(c *gin.Context) { objA := formA{} objB := formB{} // This reads c.Request.Body and stores the result into the context. if errA := c.ShouldBindBodyWith(&objA, binding.JSON); errA == nil { c.String(http.StatusOK, `the body should be formA`) // At this time, it reuses body stored in the context. } elseif errB := c.ShouldBindBodyWith(&objB, binding.JSON); errB == nil { c.String(http.StatusOK, `the body should be formB JSON`) // And it can accepts other formats } elseif errB2 := c.ShouldBindBodyWith(&objB, binding.XML); errB2 == nil { c.String(http.StatusOK, `the body should be formB XML`) } else { ... } }
c.ShouldBindBodyWith stores body into the context before binding. This has a slight impact to performance, so you should not use this method if you are enough to call binding at once.
This feature is only needed for some formats – JSON, XML, MsgPack, ProtoBuf. For other formats, Query, Form, FormPost, FormMultipart, can be called by c.ShouldBind() multiple times without any damage to performance (See #1341).
http2 服务端推送
http.Pusher is supported only go1.8+. See the golang blog for detail information.