// 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) })
// For each matched request Context will hold the route definition router.POST("/user/:name/*action", func(c *gin.Context) { b := c.FullPath() == "/user/:name/*action"// true c.String(http.StatusOK, "%t", b) })
// This handler will add a new router for /user/groups. // Exact routes are resolved before param routes, regardless of the order they were defined. // Routes starting with /user/groups are never interpreted as /user/:name/... routes router.GET("/user/groups", func(c *gin.Context) { c.String(http.StatusOK, "The available groups are [...]") })
router.Run(":8080") }
Querystring parameters
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 an 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")
The filename is always optional and must not be used blindly by the application: path information should be stripped, and conversion to the server file system rules should be done.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
funcmain() { router := gin.Default() // Set a lower memory limit for multipart forms (default is 32 MiB) router.MaxMultipartMemory = 8 << 20// 8 MiB router.POST("/upload", func(c *gin.Context) { // Single file file, _ := c.FormFile("file") log.Println(file.Filename)
// Upload the file to specific dst. c.SaveUploadedFile(file, dst)
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)
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.CustomRecovery(func(c *gin.Context, recovered any) { if err, ok := recovered.(string); ok { c.String(http.StatusInternalServerError, fmt.Sprintf("error: %s", err)) } c.AbortWithStatus(http.StatusInternalServerError) }))
r.GET("/panic", func(c *gin.Context) { // panic with a string -- the custom middleware could save this to a database or report it to the user panic("foo") })
// LoggerWithFormatter middleware will write the logs to gin.DefaultWriter // By default gin.DefaultWriter = os.Stdout router.Use(gin.LoggerWithFormatter(func(param gin.LogFormatterParams)string {
funcmain() { router := gin.New() // skip logging for desired paths by setting SkipPaths in LoggerConfig loggerConfig := gin.LoggerConfig{SkipPaths: []string{"/metrics"}} // skip logging based on your logic by setting Skip func in LoggerConfig loggerConfig.Skip = func(c *gin.Context)bool { // as an example skip non server side errors return c.Writer.Status() < http.StatusInternalServerError } engine.Use(gin.LoggerWithConfig(loggerConfig)) router.Use(gin.Recovery()) // skipped router.GET("/metrics", func(c *gin.Context) { c.Status(http.StatusNotImplemented) })
To bind a request body into a type, use model binding. We currently support binding of JSON, XML, YAML, TOML and standard form values (foo=bar&boo=baz).
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 an empty value when binding, an error will be returned.
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 }
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.
Custom Validators
It is also possible to register custom validators. See the example code.
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) { returnfalse } } returntrue }
funcmain() { route := gin.Default()
if v, ok := binding.Validator.Engine().(*validator.Validate); ok { v.RegisterValidation("bookabledate", bookableDate) }
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 6 7 8
$ curl "localhost:8085/bookable?check_in=2030-04-16&check_out=2030-04-17" {"message":"Booking dates are valid!"} $ curl "localhost:8085/bookable?check_in=2030-03-10&check_out=2030-03-09" {"error":"Key: 'Booking.CheckOut' Error:Field validation for 'CheckOut' failed on the 'gtfield' tag"} $ curl "localhost:8085/bookable?check_in=2000-03-09&check_out=2000-03-10" {"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(http.StatusOK, "Success") }
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#L88 if c.ShouldBind(&person) == nil { log.Println(person.Name) log.Println(person.Address) log.Println(person.Birthday) log.Println(person.CreateTime) log.Println(person.UnixTime) }
c.String(http.StatusOK, "Success") }
Test it with:
1
curl -X GET "localhost:8085/testing?name=appleboy&address=xyz&birthday=1992-03-15&createTime=1562400033000000123&unixTime=1562400033"
type ProfileForm struct { Name string`form:"name" binding:"required"` Avatar *multipart.FileHeader `form:"avatar" binding:"required"`
// or for multiple files // Avatars []*multipart.FileHeader `form:"avatar" binding:"required"` }
funcmain() { router := gin.Default() router.POST("/profile", 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 ProfileForm // in this case proper binding will be automatically selected if err := c.ShouldBind(&form); err != nil { c.String(http.StatusBadRequest, "bad request") return }
// gin.H is a shortcut for map[string]any 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 inside a middleware
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") }
There are a few approaches you can use to perform a graceful shutdown or restart. You can make use of third-party packages specifically built for that, or you can manually do the same with the functions and methods from the built-in packages.
Third-party packages
We can use fvbock/endless to replace the default ListenAndServe. Refer to issue #296 for more details.
grace: Graceful restart & zero downtime deploy for Go servers.
graceful: Graceful is a Go package enabling graceful shutdown of an http.Handler server.
manners: A polite Go HTTP server that shuts down gracefully.
Manually
In case you are using Go 1.8 or a later version, you may not need to use those libraries. Consider using http.Server‘s built-in Shutdown() method for graceful shutdowns. The example below describes its usage, and we’ve got more examples using gin here.
// Initializing the server in a goroutine so that // it won't block the graceful shutdown handling below gofunc() { if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { log.Printf("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 syscall.SIGTERM // kill -2 is syscall.SIGINT // kill -9 is syscall.SIGKILL but can't be caught, so don't need to add it signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) <-quit log.Println("Shutting down server...")
// The context is used to inform the server it has 5 seconds to finish // the request it is currently handling ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel()
if err := srv.Shutdown(ctx); err != nil { log.Fatal("Server forced to shutdown:", err) }
log.Println("Server exiting") }
Build a single binary with templates
You can build a server into a single binary containing templates by using the embed package.
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.Form); 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).
Bind form-data request with custom struct and custom tag
var html = template.Must(template.New("https").Parse(` <html> <head> <title>Https Test</title> <script src="/assets/app.js"></script> </head> <body> <h1 style="color:red;">Welcome, Ginner!</h1> </body> </html> `))
funcmain() { r := gin.Default() r.Static("/assets", "./assets") r.SetHTMLTemplate(html)
r.GET("/", func(c *gin.Context) { if pusher := c.Writer.Pusher(); pusher != nil { // use pusher.Push() to do server push if err := pusher.Push("/assets/app.js", nil); err != nil { log.Printf("Failed to push: %v", err) } } c.HTML(http.StatusOK, "https", gin.H{ "status": "success", }) })
// Listen and Server in https://127.0.0.1:8080 r.RunTLS(":8080", "./testdata/server.pem", "./testdata/server.key") }
Define format for the log of routes
The default log of routes is:
1 2 3
[GIN-debug] POST /foo --> main.main.func1 (3 handlers) [GIN-debug] GET /bar --> main.main.func2 (3 handlers) [GIN-debug] GET /status --> main.main.func3 (3 handlers)
If you want to log this information in given format (e.g. JSON, key values or something else), then you can define this format with gin.DebugPrintRouteFunc. In the example below, we log all routes with standard log package but you can use another log tools that suits of your needs.
Gin lets you specify which headers to hold the real client IP (if any), as well as specifying which proxies (or direct clients) you trust to specify one of these headers.
Use function SetTrustedProxies() on your gin.Engine to specify network addresses or network CIDRs from where clients which their request headers related to client IP can be trusted. They can be IPv4 addresses, IPv4 CIDRs, IPv6 addresses or IPv6 CIDRs.
Attention: Gin trust all proxies by default if you don’t specify a trusted proxy using the function above, this is NOT safe. At the same time, if you don’t use any proxy, you can disable this feature by using Engine.SetTrustedProxies(nil), then Context.ClientIP() will return the remote address directly to avoid some unnecessary computation.
router.GET("/", func(c *gin.Context) { // If the client is 192.168.1.2, use the X-Forwarded-For // header to deduce the original client IP from the trust- // worthy parts of that header. // Otherwise, simply return the direct client IP fmt.Printf("ClientIP: %s\n", c.ClientIP()) }) router.Run() }
Notice: If you are using a CDN service, you can set the Engine.TrustedPlatform to skip TrustedProxies check, it has a higher priority than TrustedProxies. Look at the example below:
funcmain() { router := gin.Default() // Use predefined header gin.PlatformXXX // Google App Engine router.TrustedPlatform = gin.PlatformGoogleAppEngine // Cloudflare router.TrustedPlatform = gin.PlatformCloudflare // Fly.io router.TrustedPlatform = gin.PlatformFlyIO // Or, you can set your own trusted request header. But be sure your CDN // prevents users from passing this header! For example, if your CDN puts // the client IP in X-CDN-Client-IP: router.TrustedPlatform = "X-CDN-Client-IP"
router.GET("/", func(c *gin.Context) { // If you set TrustedPlatform, ClientIP() will resolve the // corresponding header and return IP directly fmt.Printf("ClientIP: %s\n", c.ClientIP()) }) router.Run() }
Testing
The net/http/httptest package is preferable way for HTTP testing.