1# Gin Quick Start
2
3## Contents
4
5- [Build Tags](#build-tags)
6 - [Build with json replacement](#build-with-json-replacement)
7 - [Build without `MsgPack` rendering feature](#build-without-msgpack-rendering-feature)
8- [API Examples](#api-examples)
9 - [Using GET, POST, PUT, PATCH, DELETE and OPTIONS](#using-get-post-put-patch-delete-and-options)
10 - [Parameters in path](#parameters-in-path)
11 - [Querystring parameters](#querystring-parameters)
12 - [Multipart/Urlencoded Form](#multiparturlencoded-form)
13 - [Another example: query + post form](#another-example-query--post-form)
14 - [Map as querystring or postform parameters](#map-as-querystring-or-postform-parameters)
15 - [Upload files](#upload-files)
16 - [Single file](#single-file)
17 - [Multiple files](#multiple-files)
18 - [Grouping routes](#grouping-routes)
19 - [Blank Gin without middleware by default](#blank-gin-without-middleware-by-default)
20 - [Using middleware](#using-middleware)
21 - [Custom Recovery behavior](#custom-recovery-behavior)
22 - [How to write log file](#how-to-write-log-file)
23 - [Custom Log Format](#custom-log-format)
24 - [Controlling Log output coloring](#controlling-log-output-coloring)
25 - [Model binding and validation](#model-binding-and-validation)
26 - [Custom Validators](#custom-validators)
27 - [Only Bind Query String](#only-bind-query-string)
28 - [Bind Query String or Post Data](#bind-query-string-or-post-data)
29 - [Bind Uri](#bind-uri)
30 - [Bind Header](#bind-header)
31 - [Bind HTML checkboxes](#bind-html-checkboxes)
32 - [Multipart/Urlencoded binding](#multiparturlencoded-binding)
33 - [XML, JSON, YAML, TOML and ProtoBuf rendering](#xml-json-yaml-toml-and-protobuf-rendering)
34 - [SecureJSON](#securejson)
35 - [JSONP](#jsonp)
36 - [AsciiJSON](#asciijson)
37 - [PureJSON](#purejson)
38 - [Serving static files](#serving-static-files)
39 - [Serving data from file](#serving-data-from-file)
40 - [Serving data from reader](#serving-data-from-reader)
41 - [HTML rendering](#html-rendering)
42 - [Custom Template renderer](#custom-template-renderer)
43 - [Custom Delimiters](#custom-delimiters)
44 - [Custom Template Funcs](#custom-template-funcs)
45 - [Multitemplate](#multitemplate)
46 - [Redirects](#redirects)
47 - [Custom Middleware](#custom-middleware)
48 - [Using BasicAuth() middleware](#using-basicauth-middleware)
49 - [Goroutines inside a middleware](#goroutines-inside-a-middleware)
50 - [Custom HTTP configuration](#custom-http-configuration)
51 - [Support Let's Encrypt](#support-lets-encrypt)
52 - [Run multiple service using Gin](#run-multiple-service-using-gin)
53 - [Graceful shutdown or restart](#graceful-shutdown-or-restart)
54 - [Third-party packages](#third-party-packages)
55 - [Manually](#manually)
56 - [Build a single binary with templates](#build-a-single-binary-with-templates)
57 - [Bind form-data request with custom struct](#bind-form-data-request-with-custom-struct)
58 - [Try to bind body into different structs](#try-to-bind-body-into-different-structs)
59 - [Bind form-data request with custom struct and custom tag](#bind-form-data-request-with-custom-struct-and-custom-tag)
60 - [http2 server push](#http2-server-push)
61 - [Define format for the log of routes](#define-format-for-the-log-of-routes)
62 - [Set and get a cookie](#set-and-get-a-cookie)
63- [Don't trust all proxies](#dont-trust-all-proxies)
64- [Testing](#testing)
65
66## Build tags
67
68### Build with json replacement
69
70Gin uses `encoding/json` as default json package but you can change it by build from other tags.
71
72[jsoniter](https://github.com/json-iterator/go)
73
74```sh
75go build -tags=jsoniter .
76```
77
78[go-json](https://github.com/goccy/go-json)
79
80```sh
81go build -tags=go_json .
82```
83
84[sonic](https://github.com/bytedance/sonic) (you have to ensure that your cpu support avx instruction.)
85
86```sh
87$ go build -tags="sonic avx" .
88```
89
90### Build without `MsgPack` rendering feature
91
92Gin enables `MsgPack` rendering feature by default. But you can disable this feature by specifying `nomsgpack` build tag.
93
94```sh
95go build -tags=nomsgpack .
96```
97
98This is useful to reduce the binary size of executable files. See the [detail information](https://github.com/gin-gonic/gin/pull/1852).
99
100## API Examples
101
102You can find a number of ready-to-run examples at [Gin examples repository](https://github.com/gin-gonic/examples).
103
104### Using GET, POST, PUT, PATCH, DELETE and OPTIONS
105
106```go
107func main() {
108 // Creates a gin router with default middleware:
109 // logger and recovery (crash-free) middleware
110 router := gin.Default()
111
112 router.GET("/someGet", getting)
113 router.POST("/somePost", posting)
114 router.PUT("/somePut", putting)
115 router.DELETE("/someDelete", deleting)
116 router.PATCH("/somePatch", patching)
117 router.HEAD("/someHead", head)
118 router.OPTIONS("/someOptions", options)
119
120 // By default it serves on :8080 unless a
121 // PORT environment variable was defined.
122 router.Run()
123 // router.Run(":3000") for a hard coded port
124}
125```
126
127### Parameters in path
128
129```go
130func main() {
131 router := gin.Default()
132
133 // This handler will match /user/john but will not match /user/ or /user
134 router.GET("/user/:name", func(c *gin.Context) {
135 name := c.Param("name")
136 c.String(http.StatusOK, "Hello %s", name)
137 })
138
139 // However, this one will match /user/john/ and also /user/john/send
140 // If no other routers match /user/john, it will redirect to /user/john/
141 router.GET("/user/:name/*action", func(c *gin.Context) {
142 name := c.Param("name")
143 action := c.Param("action")
144 message := name + " is " + action
145 c.String(http.StatusOK, message)
146 })
147
148 // For each matched request Context will hold the route definition
149 router.POST("/user/:name/*action", func(c *gin.Context) {
150 b := c.FullPath() == "/user/:name/*action" // true
151 c.String(http.StatusOK, "%t", b)
152 })
153
154 // This handler will add a new router for /user/groups.
155 // Exact routes are resolved before param routes, regardless of the order they were defined.
156 // Routes starting with /user/groups are never interpreted as /user/:name/... routes
157 router.GET("/user/groups", func(c *gin.Context) {
158 c.String(http.StatusOK, "The available groups are [...]")
159 })
160
161 router.Run(":8080")
162}
163```
164
165### Querystring parameters
166
167```go
168func main() {
169 router := gin.Default()
170
171 // Query string parameters are parsed using the existing underlying request object.
172 // The request responds to an url matching: /welcome?firstname=Jane&lastname=Doe
173 router.GET("/welcome", func(c *gin.Context) {
174 firstname := c.DefaultQuery("firstname", "Guest")
175 lastname := c.Query("lastname") // shortcut for c.Request.URL.Query().Get("lastname")
176
177 c.String(http.StatusOK, "Hello %s %s", firstname, lastname)
178 })
179 router.Run(":8080")
180}
181```
182
183### Multipart/Urlencoded Form
184
185```go
186func main() {
187 router := gin.Default()
188
189 router.POST("/form_post", func(c *gin.Context) {
190 message := c.PostForm("message")
191 nick := c.DefaultPostForm("nick", "anonymous")
192
193 c.JSON(http.StatusOK, gin.H{
194 "status": "posted",
195 "message": message,
196 "nick": nick,
197 })
198 })
199 router.Run(":8080")
200}
201```
202
203### Another example: query + post form
204
205```sh
206POST /post?id=1234&page=1 HTTP/1.1
207Content-Type: application/x-www-form-urlencoded
208
209name=manu&message=this_is_great
210```
211
212```go
213func main() {
214 router := gin.Default()
215
216 router.POST("/post", func(c *gin.Context) {
217
218 id := c.Query("id")
219 page := c.DefaultQuery("page", "0")
220 name := c.PostForm("name")
221 message := c.PostForm("message")
222
223 fmt.Printf("id: %s; page: %s; name: %s; message: %s", id, page, name, message)
224 })
225 router.Run(":8080")
226}
227```
228
229```sh
230id: 1234; page: 1; name: manu; message: this_is_great
231```
232
233### Map as querystring or postform parameters
234
235```sh
236POST /post?ids[a]=1234&ids[b]=hello HTTP/1.1
237Content-Type: application/x-www-form-urlencoded
238
239names[first]=thinkerou&names[second]=tianou
240```
241
242```go
243func main() {
244 router := gin.Default()
245
246 router.POST("/post", func(c *gin.Context) {
247
248 ids := c.QueryMap("ids")
249 names := c.PostFormMap("names")
250
251 fmt.Printf("ids: %v; names: %v", ids, names)
252 })
253 router.Run(":8080")
254}
255```
256
257```sh
258ids: map[b:hello a:1234]; names: map[second:tianou first:thinkerou]
259```
260
261### Upload files
262
263#### Single file
264
265References issue [#774](https://github.com/gin-gonic/gin/issues/774) and detail [example code](https://github.com/gin-gonic/examples/tree/master/upload-file/single).
266
267`file.Filename` **SHOULD NOT** be trusted. See [`Content-Disposition` on MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition#Directives) and [#1693](https://github.com/gin-gonic/gin/issues/1693)
268
269> 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.
270
271```go
272func main() {
273 router := gin.Default()
274 // Set a lower memory limit for multipart forms (default is 32 MiB)
275 router.MaxMultipartMemory = 8 << 20 // 8 MiB
276 router.POST("/upload", func(c *gin.Context) {
277 // Single file
278 file, _ := c.FormFile("file")
279 log.Println(file.Filename)
280
281 // Upload the file to specific dst.
282 c.SaveUploadedFile(file, dst)
283
284 c.String(http.StatusOK, fmt.Sprintf("'%s' uploaded!", file.Filename))
285 })
286 router.Run(":8080")
287}
288```
289
290How to `curl`:
291
292```bash
293curl -X POST http://localhost:8080/upload \
294 -F "file=@/Users/appleboy/test.zip" \
295 -H "Content-Type: multipart/form-data"
296```
297
298#### Multiple files
299
300See the detail [example code](https://github.com/gin-gonic/examples/tree/master/upload-file/multiple).
301
302```go
303func main() {
304 router := gin.Default()
305 // Set a lower memory limit for multipart forms (default is 32 MiB)
306 router.MaxMultipartMemory = 8 << 20 // 8 MiB
307 router.POST("/upload", func(c *gin.Context) {
308 // Multipart form
309 form, _ := c.MultipartForm()
310 files := form.File["upload[]"]
311
312 for _, file := range files {
313 log.Println(file.Filename)
314
315 // Upload the file to specific dst.
316 c.SaveUploadedFile(file, dst)
317 }
318 c.String(http.StatusOK, fmt.Sprintf("%d files uploaded!", len(files)))
319 })
320 router.Run(":8080")
321}
322```
323
324How to `curl`:
325
326```bash
327curl -X POST http://localhost:8080/upload \
328 -F "upload[]=@/Users/appleboy/test1.zip" \
329 -F "upload[]=@/Users/appleboy/test2.zip" \
330 -H "Content-Type: multipart/form-data"
331```
332
333### Grouping routes
334
335```go
336func main() {
337 router := gin.Default()
338
339 // Simple group: v1
340 v1 := router.Group("/v1")
341 {
342 v1.POST("/login", loginEndpoint)
343 v1.POST("/submit", submitEndpoint)
344 v1.POST("/read", readEndpoint)
345 }
346
347 // Simple group: v2
348 v2 := router.Group("/v2")
349 {
350 v2.POST("/login", loginEndpoint)
351 v2.POST("/submit", submitEndpoint)
352 v2.POST("/read", readEndpoint)
353 }
354
355 router.Run(":8080")
356}
357```
358
359### Blank Gin without middleware by default
360
361Use
362
363```go
364r := gin.New()
365```
366
367instead of
368
369```go
370// Default With the Logger and Recovery middleware already attached
371r := gin.Default()
372```
373
374### Using middleware
375
376```go
377func main() {
378 // Creates a router without any middleware by default
379 r := gin.New()
380
381 // Global middleware
382 // Logger middleware will write the logs to gin.DefaultWriter even if you set with GIN_MODE=release.
383 // By default gin.DefaultWriter = os.Stdout
384 r.Use(gin.Logger())
385
386 // Recovery middleware recovers from any panics and writes a 500 if there was one.
387 r.Use(gin.Recovery())
388
389 // Per route middleware, you can add as many as you desire.
390 r.GET("/benchmark", MyBenchLogger(), benchEndpoint)
391
392 // Authorization group
393 // authorized := r.Group("/", AuthRequired())
394 // exactly the same as:
395 authorized := r.Group("/")
396 // per group middleware! in this case we use the custom created
397 // AuthRequired() middleware just in the "authorized" group.
398 authorized.Use(AuthRequired())
399 {
400 authorized.POST("/login", loginEndpoint)
401 authorized.POST("/submit", submitEndpoint)
402 authorized.POST("/read", readEndpoint)
403
404 // nested group
405 testing := authorized.Group("testing")
406 // visit 0.0.0.0:8080/testing/analytics
407 testing.GET("/analytics", analyticsEndpoint)
408 }
409
410 // Listen and serve on 0.0.0.0:8080
411 r.Run(":8080")
412}
413```
414
415### Custom Recovery behavior
416
417```go
418func main() {
419 // Creates a router without any middleware by default
420 r := gin.New()
421
422 // Global middleware
423 // Logger middleware will write the logs to gin.DefaultWriter even if you set with GIN_MODE=release.
424 // By default gin.DefaultWriter = os.Stdout
425 r.Use(gin.Logger())
426
427 // Recovery middleware recovers from any panics and writes a 500 if there was one.
428 r.Use(gin.CustomRecovery(func(c *gin.Context, recovered any) {
429 if err, ok := recovered.(string); ok {
430 c.String(http.StatusInternalServerError, fmt.Sprintf("error: %s", err))
431 }
432 c.AbortWithStatus(http.StatusInternalServerError)
433 }))
434
435 r.GET("/panic", func(c *gin.Context) {
436 // panic with a string -- the custom middleware could save this to a database or report it to the user
437 panic("foo")
438 })
439
440 r.GET("/", func(c *gin.Context) {
441 c.String(http.StatusOK, "ohai")
442 })
443
444 // Listen and serve on 0.0.0.0:8080
445 r.Run(":8080")
446}
447```
448
449### How to write log file
450
451```go
452func main() {
453 // Disable Console Color, you don't need console color when writing the logs to file.
454 gin.DisableConsoleColor()
455
456 // Logging to a file.
457 f, _ := os.Create("gin.log")
458 gin.DefaultWriter = io.MultiWriter(f)
459
460 // Use the following code if you need to write the logs to file and console at the same time.
461 // gin.DefaultWriter = io.MultiWriter(f, os.Stdout)
462
463 router := gin.Default()
464 router.GET("/ping", func(c *gin.Context) {
465 c.String(http.StatusOK, "pong")
466 })
467
468 router.Run(":8080")
469}
470```
471
472### Custom Log Format
473
474```go
475func main() {
476 router := gin.New()
477
478 // LoggerWithFormatter middleware will write the logs to gin.DefaultWriter
479 // By default gin.DefaultWriter = os.Stdout
480 router.Use(gin.LoggerWithFormatter(func(param gin.LogFormatterParams) string {
481
482 // your custom format
483 return fmt.Sprintf("%s - [%s] \"%s %s %s %d %s \"%s\" %s\"\n",
484 param.ClientIP,
485 param.TimeStamp.Format(time.RFC1123),
486 param.Method,
487 param.Path,
488 param.Request.Proto,
489 param.StatusCode,
490 param.Latency,
491 param.Request.UserAgent(),
492 param.ErrorMessage,
493 )
494 }))
495 router.Use(gin.Recovery())
496
497 router.GET("/ping", func(c *gin.Context) {
498 c.String(http.StatusOK, "pong")
499 })
500
501 router.Run(":8080")
502}
503```
504
505Sample Output
506
507```sh
508::1 - [Fri, 07 Dec 2018 17:04:38 JST] "GET /ping HTTP/1.1 200 122.767µs "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.80 Safari/537.36" "
509```
510
511### Controlling Log output coloring
512
513By default, logs output on console should be colorized depending on the detected TTY.
514
515Never colorize logs:
516
517```go
518func main() {
519 // Disable log's color
520 gin.DisableConsoleColor()
521
522 // Creates a gin router with default middleware:
523 // logger and recovery (crash-free) middleware
524 router := gin.Default()
525
526 router.GET("/ping", func(c *gin.Context) {
527 c.String(http.StatusOK, "pong")
528 })
529
530 router.Run(":8080")
531}
532```
533
534Always colorize logs:
535
536```go
537func main() {
538 // Force log's color
539 gin.ForceConsoleColor()
540
541 // Creates a gin router with default middleware:
542 // logger and recovery (crash-free) middleware
543 router := gin.Default()
544
545 router.GET("/ping", func(c *gin.Context) {
546 c.String(http.StatusOK, "pong")
547 })
548
549 router.Run(":8080")
550}
551```
552
553### Model binding and validation
554
555To 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).
556
557Gin uses [**go-playground/validator/v10**](https://github.com/go-playground/validator) for validation. Check the full docs on tags usage [here](https://pkg.go.dev/github.com/go-playground/validator#hdr-Baked_In_Validators_and_Tags).
558
559Note 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"`.
560
561Also, Gin provides two sets of methods for binding:
562
563- **Type** - Must bind
564 - **Methods** - `Bind`, `BindJSON`, `BindXML`, `BindQuery`, `BindYAML`, `BindHeader`, `BindTOML`
565 - **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.
566- **Type** - Should bind
567 - **Methods** - `ShouldBind`, `ShouldBindJSON`, `ShouldBindXML`, `ShouldBindQuery`, `ShouldBindYAML`, `ShouldBindHeader`, `ShouldBindTOML`,
568 - **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.
569
570When 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`.
571
572You 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.
573
574```go
575// Binding from JSON
576type Login struct {
577 User string `form:"user" json:"user" xml:"user" binding:"required"`
578 Password string `form:"password" json:"password" xml:"password" binding:"required"`
579}
580
581func main() {
582 router := gin.Default()
583
584 // Example for binding JSON ({"user": "manu", "password": "123"})
585 router.POST("/loginJSON", func(c *gin.Context) {
586 var json Login
587 if err := c.ShouldBindJSON(&json); err != nil {
588 c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
589 return
590 }
591
592 if json.User != "manu" || json.Password != "123" {
593 c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
594 return
595 }
596
597 c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})
598 })
599
600 // Example for binding XML (
601 // <?xml version="1.0" encoding="UTF-8"?>
602 // <root>
603 // <user>manu</user>
604 // <password>123</password>
605 // </root>)
606 router.POST("/loginXML", func(c *gin.Context) {
607 var xml Login
608 if err := c.ShouldBindXML(&xml); err != nil {
609 c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
610 return
611 }
612
613 if xml.User != "manu" || xml.Password != "123" {
614 c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
615 return
616 }
617
618 c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})
619 })
620
621 // Example for binding a HTML form (user=manu&password=123)
622 router.POST("/loginForm", func(c *gin.Context) {
623 var form Login
624 // This will infer what binder to use depending on the content-type header.
625 if err := c.ShouldBind(&form); err != nil {
626 c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
627 return
628 }
629
630 if form.User != "manu" || form.Password != "123" {
631 c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
632 return
633 }
634
635 c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})
636 })
637
638 // Listen and serve on 0.0.0.0:8080
639 router.Run(":8080")
640}
641```
642
643Sample request
644
645```sh
646$ curl -v -X POST \
647 http://localhost:8080/loginJSON \
648 -H 'content-type: application/json' \
649 -d '{ "user": "manu" }'
650> POST /loginJSON HTTP/1.1
651> Host: localhost:8080
652> User-Agent: curl/7.51.0
653> Accept: */*
654> content-type: application/json
655> Content-Length: 18
656>
657* upload completely sent off: 18 out of 18 bytes
658< HTTP/1.1 400 Bad Request
659< Content-Type: application/json; charset=utf-8
660< Date: Fri, 04 Aug 2017 03:51:31 GMT
661< Content-Length: 100
662<
663{"error":"Key: 'Login.Password' Error:Field validation for 'Password' failed on the 'required' tag"}
664```
665
666Skip 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.
667
668### Custom Validators
669
670It is also possible to register custom validators. See the [example code](https://github.com/gin-gonic/examples/tree/master/custom-validation/server.go).
671
672```go
673package main
674
675import (
676 "net/http"
677 "time"
678
679 "github.com/gin-gonic/gin"
680 "github.com/gin-gonic/gin/binding"
681 "github.com/go-playground/validator/v10"
682)
683
684// Booking contains binded and validated data.
685type Booking struct {
686 CheckIn time.Time `form:"check_in" binding:"required,bookabledate" time_format:"2006-01-02"`
687 CheckOut time.Time `form:"check_out" binding:"required,gtfield=CheckIn" time_format:"2006-01-02"`
688}
689
690var bookableDate validator.Func = func(fl validator.FieldLevel) bool {
691 date, ok := fl.Field().Interface().(time.Time)
692 if ok {
693 today := time.Now()
694 if today.After(date) {
695 return false
696 }
697 }
698 return true
699}
700
701func main() {
702 route := gin.Default()
703
704 if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
705 v.RegisterValidation("bookabledate", bookableDate)
706 }
707
708 route.GET("/bookable", getBookable)
709 route.Run(":8085")
710}
711
712func getBookable(c *gin.Context) {
713 var b Booking
714 if err := c.ShouldBindWith(&b, binding.Query); err == nil {
715 c.JSON(http.StatusOK, gin.H{"message": "Booking dates are valid!"})
716 } else {
717 c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
718 }
719}
720```
721
722```console
723$ curl "localhost:8085/bookable?check_in=2030-04-16&check_out=2030-04-17"
724{"message":"Booking dates are valid!"}
725
726$ curl "localhost:8085/bookable?check_in=2030-03-10&check_out=2030-03-09"
727{"error":"Key: 'Booking.CheckOut' Error:Field validation for 'CheckOut' failed on the 'gtfield' tag"}
728
729$ curl "localhost:8085/bookable?check_in=2000-03-09&check_out=2000-03-10"
730{"error":"Key: 'Booking.CheckIn' Error:Field validation for 'CheckIn' failed on the 'bookabledate' tag"}%
731```
732
733[Struct level validations](https://github.com/go-playground/validator/releases/tag/v8.7) can also be registered this way.
734See the [struct-lvl-validation example](https://github.com/gin-gonic/examples/tree/master/struct-lvl-validations) to learn more.
735
736### Only Bind Query String
737
738`ShouldBindQuery` function only binds the query params and not the post data. See the [detail information](https://github.com/gin-gonic/gin/issues/742#issuecomment-315953017).
739
740```go
741package main
742
743import (
744 "log"
745 "net/http"
746
747 "github.com/gin-gonic/gin"
748)
749
750type Person struct {
751 Name string `form:"name"`
752 Address string `form:"address"`
753}
754
755func main() {
756 route := gin.Default()
757 route.Any("/testing", startPage)
758 route.Run(":8085")
759}
760
761func startPage(c *gin.Context) {
762 var person Person
763 if c.ShouldBindQuery(&person) == nil {
764 log.Println("====== Only Bind By Query String ======")
765 log.Println(person.Name)
766 log.Println(person.Address)
767 }
768 c.String(http.StatusOK, "Success")
769}
770
771```
772
773### Bind Query String or Post Data
774
775See the [detail information](https://github.com/gin-gonic/gin/issues/742#issuecomment-264681292).
776
777```go
778package main
779
780import (
781 "log"
782 "net/http"
783 "time"
784
785 "github.com/gin-gonic/gin"
786)
787
788type Person struct {
789 Name string `form:"name"`
790 Address string `form:"address"`
791 Birthday time.Time `form:"birthday" time_format:"2006-01-02" time_utc:"1"`
792 CreateTime time.Time `form:"createTime" time_format:"unixNano"`
793 UnixTime time.Time `form:"unixTime" time_format:"unix"`
794}
795
796func main() {
797 route := gin.Default()
798 route.GET("/testing", startPage)
799 route.Run(":8085")
800}
801
802func startPage(c *gin.Context) {
803 var person Person
804 // If `GET`, only `Form` binding engine (`query`) used.
805 // If `POST`, first checks the `content-type` for `JSON` or `XML`, then uses `Form` (`form-data`).
806 // See more at https://github.com/gin-gonic/gin/blob/master/binding/binding.go#L88
807 if c.ShouldBind(&person) == nil {
808 log.Println(person.Name)
809 log.Println(person.Address)
810 log.Println(person.Birthday)
811 log.Println(person.CreateTime)
812 log.Println(person.UnixTime)
813 }
814
815 c.String(http.StatusOK, "Success")
816}
817```
818
819Test it with:
820
821```sh
822curl -X GET "localhost:8085/testing?name=appleboy&address=xyz&birthday=1992-03-15&createTime=1562400033000000123&unixTime=1562400033"
823```
824
825### Bind Uri
826
827See the [detail information](https://github.com/gin-gonic/gin/issues/846).
828
829```go
830package main
831
832import (
833 "net/http"
834
835 "github.com/gin-gonic/gin"
836)
837
838type Person struct {
839 ID string `uri:"id" binding:"required,uuid"`
840 Name string `uri:"name" binding:"required"`
841}
842
843func main() {
844 route := gin.Default()
845 route.GET("/:name/:id", func(c *gin.Context) {
846 var person Person
847 if err := c.ShouldBindUri(&person); err != nil {
848 c.JSON(http.StatusBadRequest, gin.H{"msg": err.Error()})
849 return
850 }
851 c.JSON(http.StatusOK, gin.H{"name": person.Name, "uuid": person.ID})
852 })
853 route.Run(":8088")
854}
855```
856
857Test it with:
858
859```sh
860curl -v localhost:8088/thinkerou/987fbc97-4bed-5078-9f07-9141ba07c9f3
861curl -v localhost:8088/thinkerou/not-uuid
862```
863
864### Bind Header
865
866```go
867package main
868
869import (
870 "fmt"
871 "net/http"
872
873 "github.com/gin-gonic/gin"
874)
875
876type testHeader struct {
877 Rate int `header:"Rate"`
878 Domain string `header:"Domain"`
879}
880
881func main() {
882 r := gin.Default()
883 r.GET("/", func(c *gin.Context) {
884 h := testHeader{}
885
886 if err := c.ShouldBindHeader(&h); err != nil {
887 c.JSON(http.StatusOK, err)
888 }
889
890 fmt.Printf("%#v\n", h)
891 c.JSON(http.StatusOK, gin.H{"Rate": h.Rate, "Domain": h.Domain})
892 })
893
894 r.Run()
895
896// client
897// curl -H "rate:300" -H "domain:music" 127.0.0.1:8080/
898// output
899// {"Domain":"music","Rate":300}
900}
901```
902
903### Bind HTML checkboxes
904
905See the [detail information](https://github.com/gin-gonic/gin/issues/129#issuecomment-124260092)
906
907main.go
908
909```go
910...
911
912type myForm struct {
913 Colors []string `form:"colors[]"`
914}
915
916...
917
918func formHandler(c *gin.Context) {
919 var fakeForm myForm
920 c.ShouldBind(&fakeForm)
921 c.JSON(http.StatusOK, gin.H{"color": fakeForm.Colors})
922}
923
924...
925
926```
927
928form.html
929
930```html
931<form action="/" method="POST">
932 <p>Check some colors</p>
933 <label for="red">Red</label>
934 <input type="checkbox" name="colors[]" value="red" id="red">
935 <label for="green">Green</label>
936 <input type="checkbox" name="colors[]" value="green" id="green">
937 <label for="blue">Blue</label>
938 <input type="checkbox" name="colors[]" value="blue" id="blue">
939 <input type="submit">
940</form>
941```
942
943result:
944
945```json
946{"color":["red","green","blue"]}
947```
948
949### Multipart/Urlencoded binding
950
951```go
952type ProfileForm struct {
953 Name string `form:"name" binding:"required"`
954 Avatar *multipart.FileHeader `form:"avatar" binding:"required"`
955
956 // or for multiple files
957 // Avatars []*multipart.FileHeader `form:"avatar" binding:"required"`
958}
959
960func main() {
961 router := gin.Default()
962 router.POST("/profile", func(c *gin.Context) {
963 // you can bind multipart form with explicit binding declaration:
964 // c.ShouldBindWith(&form, binding.Form)
965 // or you can simply use autobinding with ShouldBind method:
966 var form ProfileForm
967 // in this case proper binding will be automatically selected
968 if err := c.ShouldBind(&form); err != nil {
969 c.String(http.StatusBadRequest, "bad request")
970 return
971 }
972
973 err := c.SaveUploadedFile(form.Avatar, form.Avatar.Filename)
974 if err != nil {
975 c.String(http.StatusInternalServerError, "unknown error")
976 return
977 }
978
979 // db.Save(&form)
980
981 c.String(http.StatusOK, "ok")
982 })
983 router.Run(":8080")
984}
985```
986
987Test it with:
988
989```sh
990curl -X POST -v --form name=user --form "avatar=@./avatar.png" http://localhost:8080/profile
991```
992
993### XML, JSON, YAML, TOML and ProtoBuf rendering
994
995```go
996func main() {
997 r := gin.Default()
998
999 // gin.H is a shortcut for map[string]any
1000 r.GET("/someJSON", func(c *gin.Context) {
1001 c.JSON(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK})
1002 })
1003
1004 r.GET("/moreJSON", func(c *gin.Context) {
1005 // You also can use a struct
1006 var msg struct {
1007 Name string `json:"user"`
1008 Message string
1009 Number int
1010 }
1011 msg.Name = "Lena"
1012 msg.Message = "hey"
1013 msg.Number = 123
1014 // Note that msg.Name becomes "user" in the JSON
1015 // Will output : {"user": "Lena", "Message": "hey", "Number": 123}
1016 c.JSON(http.StatusOK, msg)
1017 })
1018
1019 r.GET("/someXML", func(c *gin.Context) {
1020 c.XML(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK})
1021 })
1022
1023 r.GET("/someYAML", func(c *gin.Context) {
1024 c.YAML(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK})
1025 })
1026
1027 r.GET("/someTOML", func(c *gin.Context) {
1028 c.TOML(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK})
1029 })
1030
1031 r.GET("/someProtoBuf", func(c *gin.Context) {
1032 reps := []int64{int64(1), int64(2)}
1033 label := "test"
1034 // The specific definition of protobuf is written in the testdata/protoexample file.
1035 data := &protoexample.Test{
1036 Label: &label,
1037 Reps: reps,
1038 }
1039 // Note that data becomes binary data in the response
1040 // Will output protoexample.Test protobuf serialized data
1041 c.ProtoBuf(http.StatusOK, data)
1042 })
1043
1044 // Listen and serve on 0.0.0.0:8080
1045 r.Run(":8080")
1046}
1047```
1048
1049#### SecureJSON
1050
1051Using SecureJSON to prevent json hijacking. Default prepends `"while(1),"` to response body if the given struct is array values.
1052
1053```go
1054func main() {
1055 r := gin.Default()
1056
1057 // You can also use your own secure json prefix
1058 // r.SecureJsonPrefix(")]}',\n")
1059
1060 r.GET("/someJSON", func(c *gin.Context) {
1061 names := []string{"lena", "austin", "foo"}
1062
1063 // Will output : while(1);["lena","austin","foo"]
1064 c.SecureJSON(http.StatusOK, names)
1065 })
1066
1067 // Listen and serve on 0.0.0.0:8080
1068 r.Run(":8080")
1069}
1070```
1071
1072#### JSONP
1073
1074Using JSONP to request data from a server in a different domain. Add callback to response body if the query parameter callback exists.
1075
1076```go
1077func main() {
1078 r := gin.Default()
1079
1080 r.GET("/JSONP", func(c *gin.Context) {
1081 data := gin.H{
1082 "foo": "bar",
1083 }
1084
1085 //callback is x
1086 // Will output : x({\"foo\":\"bar\"})
1087 c.JSONP(http.StatusOK, data)
1088 })
1089
1090 // Listen and serve on 0.0.0.0:8080
1091 r.Run(":8080")
1092
1093 // client
1094 // curl http://127.0.0.1:8080/JSONP?callback=x
1095}
1096```
1097
1098#### AsciiJSON
1099
1100Using AsciiJSON to Generates ASCII-only JSON with escaped non-ASCII characters.
1101
1102```go
1103func main() {
1104 r := gin.Default()
1105
1106 r.GET("/someJSON", func(c *gin.Context) {
1107 data := gin.H{
1108 "lang": "GO语言",
1109 "tag": "<br>",
1110 }
1111
1112 // will output : {"lang":"GO\u8bed\u8a00","tag":"\u003cbr\u003e"}
1113 c.AsciiJSON(http.StatusOK, data)
1114 })
1115
1116 // Listen and serve on 0.0.0.0:8080
1117 r.Run(":8080")
1118}
1119```
1120
1121#### PureJSON
1122
1123Normally, 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.
1124This feature is unavailable in Go 1.6 and lower.
1125
1126```go
1127func main() {
1128 r := gin.Default()
1129
1130 // Serves unicode entities
1131 r.GET("/json", func(c *gin.Context) {
1132 c.JSON(http.StatusOK, gin.H{
1133 "html": "<b>Hello, world!</b>",
1134 })
1135 })
1136
1137 // Serves literal characters
1138 r.GET("/purejson", func(c *gin.Context) {
1139 c.PureJSON(http.StatusOK, gin.H{
1140 "html": "<b>Hello, world!</b>",
1141 })
1142 })
1143
1144 // listen and serve on 0.0.0.0:8080
1145 r.Run(":8080")
1146}
1147```
1148
1149### Serving static files
1150
1151```go
1152func main() {
1153 router := gin.Default()
1154 router.Static("/assets", "./assets")
1155 router.StaticFS("/more_static", http.Dir("my_file_system"))
1156 router.StaticFile("/favicon.ico", "./resources/favicon.ico")
1157 router.StaticFileFS("/more_favicon.ico", "more_favicon.ico", http.Dir("my_file_system"))
1158
1159 // Listen and serve on 0.0.0.0:8080
1160 router.Run(":8080")
1161}
1162```
1163
1164### Serving data from file
1165
1166```go
1167func main() {
1168 router := gin.Default()
1169
1170 router.GET("/local/file", func(c *gin.Context) {
1171 c.File("local/file.go")
1172 })
1173
1174 var fs http.FileSystem = // ...
1175 router.GET("/fs/file", func(c *gin.Context) {
1176 c.FileFromFS("fs/file.go", fs)
1177 })
1178}
1179
1180```
1181
1182### Serving data from reader
1183
1184```go
1185func main() {
1186 router := gin.Default()
1187 router.GET("/someDataFromReader", func(c *gin.Context) {
1188 response, err := http.Get("https://raw.githubusercontent.com/gin-gonic/logo/master/color.png")
1189 if err != nil || response.StatusCode != http.StatusOK {
1190 c.Status(http.StatusServiceUnavailable)
1191 return
1192 }
1193
1194 reader := response.Body
1195 defer reader.Close()
1196 contentLength := response.ContentLength
1197 contentType := response.Header.Get("Content-Type")
1198
1199 extraHeaders := map[string]string{
1200 "Content-Disposition": `attachment; filename="gopher.png"`,
1201 }
1202
1203 c.DataFromReader(http.StatusOK, contentLength, contentType, reader, extraHeaders)
1204 })
1205 router.Run(":8080")
1206}
1207```
1208
1209### HTML rendering
1210
1211Using LoadHTMLGlob() or LoadHTMLFiles()
1212
1213```go
1214func main() {
1215 router := gin.Default()
1216 router.LoadHTMLGlob("templates/*")
1217 //router.LoadHTMLFiles("templates/template1.html", "templates/template2.html")
1218 router.GET("/index", func(c *gin.Context) {
1219 c.HTML(http.StatusOK, "index.tmpl", gin.H{
1220 "title": "Main website",
1221 })
1222 })
1223 router.Run(":8080")
1224}
1225```
1226
1227templates/index.tmpl
1228
1229```html
1230<html>
1231 <h1>
1232 {{ .title }}
1233 </h1>
1234</html>
1235```
1236
1237Using templates with same name in different directories
1238
1239```go
1240func main() {
1241 router := gin.Default()
1242 router.LoadHTMLGlob("templates/**/*")
1243 router.GET("/posts/index", func(c *gin.Context) {
1244 c.HTML(http.StatusOK, "posts/index.tmpl", gin.H{
1245 "title": "Posts",
1246 })
1247 })
1248 router.GET("/users/index", func(c *gin.Context) {
1249 c.HTML(http.StatusOK, "users/index.tmpl", gin.H{
1250 "title": "Users",
1251 })
1252 })
1253 router.Run(":8080")
1254}
1255```
1256
1257templates/posts/index.tmpl
1258
1259```html
1260{{ define "posts/index.tmpl" }}
1261<html><h1>
1262 {{ .title }}
1263</h1>
1264<p>Using posts/index.tmpl</p>
1265</html>
1266{{ end }}
1267```
1268
1269templates/users/index.tmpl
1270
1271```html
1272{{ define "users/index.tmpl" }}
1273<html><h1>
1274 {{ .title }}
1275</h1>
1276<p>Using users/index.tmpl</p>
1277</html>
1278{{ end }}
1279```
1280
1281#### Custom Template renderer
1282
1283You can also use your own html template render
1284
1285```go
1286import "html/template"
1287
1288func main() {
1289 router := gin.Default()
1290 html := template.Must(template.ParseFiles("file1", "file2"))
1291 router.SetHTMLTemplate(html)
1292 router.Run(":8080")
1293}
1294```
1295
1296#### Custom Delimiters
1297
1298You may use custom delims
1299
1300```go
1301 r := gin.Default()
1302 r.Delims("{[{", "}]}")
1303 r.LoadHTMLGlob("/path/to/templates")
1304```
1305
1306#### Custom Template Funcs
1307
1308See the detail [example code](https://github.com/gin-gonic/examples/tree/master/template).
1309
1310main.go
1311
1312```go
1313import (
1314 "fmt"
1315 "html/template"
1316 "net/http"
1317 "time"
1318
1319 "github.com/gin-gonic/gin"
1320)
1321
1322func formatAsDate(t time.Time) string {
1323 year, month, day := t.Date()
1324 return fmt.Sprintf("%d/%02d/%02d", year, month, day)
1325}
1326
1327func main() {
1328 router := gin.Default()
1329 router.Delims("{[{", "}]}")
1330 router.SetFuncMap(template.FuncMap{
1331 "formatAsDate": formatAsDate,
1332 })
1333 router.LoadHTMLFiles("./testdata/template/raw.tmpl")
1334
1335 router.GET("/raw", func(c *gin.Context) {
1336 c.HTML(http.StatusOK, "raw.tmpl", gin.H{
1337 "now": time.Date(2017, 07, 01, 0, 0, 0, 0, time.UTC),
1338 })
1339 })
1340
1341 router.Run(":8080")
1342}
1343
1344```
1345
1346raw.tmpl
1347
1348```html
1349Date: {[{.now | formatAsDate}]}
1350```
1351
1352Result:
1353
1354```sh
1355Date: 2017/07/01
1356```
1357
1358### Multitemplate
1359
1360Gin allow by default use only one html.Template. Check [a multitemplate render](https://github.com/gin-contrib/multitemplate) for using features like go 1.6 `block template`.
1361
1362### Redirects
1363
1364Issuing a HTTP redirect is easy. Both internal and external locations are supported.
1365
1366```go
1367r.GET("/test", func(c *gin.Context) {
1368 c.Redirect(http.StatusMovedPermanently, "http://www.google.com/")
1369})
1370```
1371
1372Issuing a HTTP redirect from POST. Refer to issue: [#444](https://github.com/gin-gonic/gin/issues/444)
1373
1374```go
1375r.POST("/test", func(c *gin.Context) {
1376 c.Redirect(http.StatusFound, "/foo")
1377})
1378```
1379
1380Issuing a Router redirect, use `HandleContext` like below.
1381
1382``` go
1383r.GET("/test", func(c *gin.Context) {
1384 c.Request.URL.Path = "/test2"
1385 r.HandleContext(c)
1386})
1387r.GET("/test2", func(c *gin.Context) {
1388 c.JSON(http.StatusOK, gin.H{"hello": "world"})
1389})
1390```
1391
1392### Custom Middleware
1393
1394```go
1395func Logger() gin.HandlerFunc {
1396 return func(c *gin.Context) {
1397 t := time.Now()
1398
1399 // Set example variable
1400 c.Set("example", "12345")
1401
1402 // before request
1403
1404 c.Next()
1405
1406 // after request
1407 latency := time.Since(t)
1408 log.Print(latency)
1409
1410 // access the status we are sending
1411 status := c.Writer.Status()
1412 log.Println(status)
1413 }
1414}
1415
1416func main() {
1417 r := gin.New()
1418 r.Use(Logger())
1419
1420 r.GET("/test", func(c *gin.Context) {
1421 example := c.MustGet("example").(string)
1422
1423 // it would print: "12345"
1424 log.Println(example)
1425 })
1426
1427 // Listen and serve on 0.0.0.0:8080
1428 r.Run(":8080")
1429}
1430```
1431
1432### Using BasicAuth() middleware
1433
1434```go
1435// simulate some private data
1436var secrets = gin.H{
1437 "foo": gin.H{"email": "foo@bar.com", "phone": "123433"},
1438 "austin": gin.H{"email": "austin@example.com", "phone": "666"},
1439 "lena": gin.H{"email": "lena@guapa.com", "phone": "523443"},
1440}
1441
1442func main() {
1443 r := gin.Default()
1444
1445 // Group using gin.BasicAuth() middleware
1446 // gin.Accounts is a shortcut for map[string]string
1447 authorized := r.Group("/admin", gin.BasicAuth(gin.Accounts{
1448 "foo": "bar",
1449 "austin": "1234",
1450 "lena": "hello2",
1451 "manu": "4321",
1452 }))
1453
1454 // /admin/secrets endpoint
1455 // hit "localhost:8080/admin/secrets
1456 authorized.GET("/secrets", func(c *gin.Context) {
1457 // get user, it was set by the BasicAuth middleware
1458 user := c.MustGet(gin.AuthUserKey).(string)
1459 if secret, ok := secrets[user]; ok {
1460 c.JSON(http.StatusOK, gin.H{"user": user, "secret": secret})
1461 } else {
1462 c.JSON(http.StatusOK, gin.H{"user": user, "secret": "NO SECRET :("})
1463 }
1464 })
1465
1466 // Listen and serve on 0.0.0.0:8080
1467 r.Run(":8080")
1468}
1469```
1470
1471### Goroutines inside a middleware
1472
1473When 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.
1474
1475```go
1476func main() {
1477 r := gin.Default()
1478
1479 r.GET("/long_async", func(c *gin.Context) {
1480 // create copy to be used inside the goroutine
1481 cCp := c.Copy()
1482 go func() {
1483 // simulate a long task with time.Sleep(). 5 seconds
1484 time.Sleep(5 * time.Second)
1485
1486 // note that you are using the copied context "cCp", IMPORTANT
1487 log.Println("Done! in path " + cCp.Request.URL.Path)
1488 }()
1489 })
1490
1491 r.GET("/long_sync", func(c *gin.Context) {
1492 // simulate a long task with time.Sleep(). 5 seconds
1493 time.Sleep(5 * time.Second)
1494
1495 // since we are NOT using a goroutine, we do not have to copy the context
1496 log.Println("Done! in path " + c.Request.URL.Path)
1497 })
1498
1499 // Listen and serve on 0.0.0.0:8080
1500 r.Run(":8080")
1501}
1502```
1503
1504### Custom HTTP configuration
1505
1506Use `http.ListenAndServe()` directly, like this:
1507
1508```go
1509func main() {
1510 router := gin.Default()
1511 http.ListenAndServe(":8080", router)
1512}
1513```
1514
1515or
1516
1517```go
1518func main() {
1519 router := gin.Default()
1520
1521 s := &http.Server{
1522 Addr: ":8080",
1523 Handler: router,
1524 ReadTimeout: 10 * time.Second,
1525 WriteTimeout: 10 * time.Second,
1526 MaxHeaderBytes: 1 << 20,
1527 }
1528 s.ListenAndServe()
1529}
1530```
1531
1532### Support Let's Encrypt
1533
1534example for 1-line LetsEncrypt HTTPS servers.
1535
1536```go
1537package main
1538
1539import (
1540 "log"
1541 "net/http"
1542
1543 "github.com/gin-gonic/autotls"
1544 "github.com/gin-gonic/gin"
1545)
1546
1547func main() {
1548 r := gin.Default()
1549
1550 // Ping handler
1551 r.GET("/ping", func(c *gin.Context) {
1552 c.String(http.StatusOK, "pong")
1553 })
1554
1555 log.Fatal(autotls.Run(r, "example1.com", "example2.com"))
1556}
1557```
1558
1559example for custom autocert manager.
1560
1561```go
1562package main
1563
1564import (
1565 "log"
1566 "net/http"
1567
1568 "github.com/gin-gonic/autotls"
1569 "github.com/gin-gonic/gin"
1570 "golang.org/x/crypto/acme/autocert"
1571)
1572
1573func main() {
1574 r := gin.Default()
1575
1576 // Ping handler
1577 r.GET("/ping", func(c *gin.Context) {
1578 c.String(http.StatusOK, "pong")
1579 })
1580
1581 m := autocert.Manager{
1582 Prompt: autocert.AcceptTOS,
1583 HostPolicy: autocert.HostWhitelist("example1.com", "example2.com"),
1584 Cache: autocert.DirCache("/var/www/.cache"),
1585 }
1586
1587 log.Fatal(autotls.RunWithManager(r, &m))
1588}
1589```
1590
1591### Run multiple service using Gin
1592
1593See the [question](https://github.com/gin-gonic/gin/issues/346) and try the following example:
1594
1595```go
1596package main
1597
1598import (
1599 "log"
1600 "net/http"
1601 "time"
1602
1603 "github.com/gin-gonic/gin"
1604 "golang.org/x/sync/errgroup"
1605)
1606
1607var (
1608 g errgroup.Group
1609)
1610
1611func router01() http.Handler {
1612 e := gin.New()
1613 e.Use(gin.Recovery())
1614 e.GET("/", func(c *gin.Context) {
1615 c.JSON(
1616 http.StatusOK,
1617 gin.H{
1618 "code": http.StatusOK,
1619 "error": "Welcome server 01",
1620 },
1621 )
1622 })
1623
1624 return e
1625}
1626
1627func router02() http.Handler {
1628 e := gin.New()
1629 e.Use(gin.Recovery())
1630 e.GET("/", func(c *gin.Context) {
1631 c.JSON(
1632 http.StatusOK,
1633 gin.H{
1634 "code": http.StatusOK,
1635 "error": "Welcome server 02",
1636 },
1637 )
1638 })
1639
1640 return e
1641}
1642
1643func main() {
1644 server01 := &http.Server{
1645 Addr: ":8080",
1646 Handler: router01(),
1647 ReadTimeout: 5 * time.Second,
1648 WriteTimeout: 10 * time.Second,
1649 }
1650
1651 server02 := &http.Server{
1652 Addr: ":8081",
1653 Handler: router02(),
1654 ReadTimeout: 5 * time.Second,
1655 WriteTimeout: 10 * time.Second,
1656 }
1657
1658 g.Go(func() error {
1659 err := server01.ListenAndServe()
1660 if err != nil && err != http.ErrServerClosed {
1661 log.Fatal(err)
1662 }
1663 return err
1664 })
1665
1666 g.Go(func() error {
1667 err := server02.ListenAndServe()
1668 if err != nil && err != http.ErrServerClosed {
1669 log.Fatal(err)
1670 }
1671 return err
1672 })
1673
1674 if err := g.Wait(); err != nil {
1675 log.Fatal(err)
1676 }
1677}
1678```
1679
1680### Graceful shutdown or restart
1681
1682There 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.
1683
1684#### Third-party packages
1685
1686We can use [fvbock/endless](https://github.com/fvbock/endless) to replace the default `ListenAndServe`. Refer to issue [#296](https://github.com/gin-gonic/gin/issues/296) for more details.
1687
1688```go
1689router := gin.Default()
1690router.GET("/", handler)
1691// [...]
1692endless.ListenAndServe(":4242", router)
1693```
1694
1695Alternatives:
1696
1697* [grace](https://github.com/facebookgo/grace): Graceful restart & zero downtime deploy for Go servers.
1698* [graceful](https://github.com/tylerb/graceful): Graceful is a Go package enabling graceful shutdown of an http.Handler server.
1699* [manners](https://github.com/braintree/manners): A polite Go HTTP server that shuts down gracefully.
1700
1701#### Manually
1702
1703In 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()](https://pkg.go.dev/net/http#Server.Shutdown) method for graceful shutdowns. The example below describes its usage, and we've got more examples using gin [here](https://github.com/gin-gonic/examples/tree/master/graceful-shutdown).
1704
1705```go
1706// +build go1.8
1707
1708package main
1709
1710import (
1711 "context"
1712 "log"
1713 "net/http"
1714 "os"
1715 "os/signal"
1716 "syscall"
1717 "time"
1718
1719 "github.com/gin-gonic/gin"
1720)
1721
1722func main() {
1723 router := gin.Default()
1724 router.GET("/", func(c *gin.Context) {
1725 time.Sleep(5 * time.Second)
1726 c.String(http.StatusOK, "Welcome Gin Server")
1727 })
1728
1729 srv := &http.Server{
1730 Addr: ":8080",
1731 Handler: router,
1732 }
1733
1734 // Initializing the server in a goroutine so that
1735 // it won't block the graceful shutdown handling below
1736 go func() {
1737 if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
1738 log.Printf("listen: %s\n", err)
1739 }
1740 }()
1741
1742 // Wait for interrupt signal to gracefully shutdown the server with
1743 // a timeout of 5 seconds.
1744 quit := make(chan os.Signal)
1745 // kill (no param) default send syscall.SIGTERM
1746 // kill -2 is syscall.SIGINT
1747 // kill -9 is syscall.SIGKILL but can't be caught, so don't need to add it
1748 signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
1749 <-quit
1750 log.Println("Shutting down server...")
1751
1752 // The context is used to inform the server it has 5 seconds to finish
1753 // the request it is currently handling
1754 ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
1755 defer cancel()
1756
1757 if err := srv.Shutdown(ctx); err != nil {
1758 log.Fatal("Server forced to shutdown:", err)
1759 }
1760
1761 log.Println("Server exiting")
1762}
1763```
1764
1765### Build a single binary with templates
1766
1767You can build a server into a single binary containing templates by using the [embed](https://pkg.go.dev/embed) package.
1768
1769```go
1770package main
1771
1772import (
1773 "embed"
1774 "html/template"
1775 "net/http"
1776
1777 "github.com/gin-gonic/gin"
1778)
1779
1780//go:embed assets/* templates/*
1781var f embed.FS
1782
1783func main() {
1784 router := gin.Default()
1785 templ := template.Must(template.New("").ParseFS(f, "templates/*.tmpl", "templates/foo/*.tmpl"))
1786 router.SetHTMLTemplate(templ)
1787
1788 // example: /public/assets/images/example.png
1789 router.StaticFS("/public", http.FS(f))
1790
1791 router.GET("/", func(c *gin.Context) {
1792 c.HTML(http.StatusOK, "index.tmpl", gin.H{
1793 "title": "Main website",
1794 })
1795 })
1796
1797 router.GET("/foo", func(c *gin.Context) {
1798 c.HTML(http.StatusOK, "bar.tmpl", gin.H{
1799 "title": "Foo website",
1800 })
1801 })
1802
1803 router.GET("favicon.ico", func(c *gin.Context) {
1804 file, _ := f.ReadFile("assets/favicon.ico")
1805 c.Data(
1806 http.StatusOK,
1807 "image/x-icon",
1808 file,
1809 )
1810 })
1811
1812 router.Run(":8080")
1813}
1814```
1815
1816See a complete example in the `https://github.com/gin-gonic/examples/tree/master/assets-in-binary/example02` directory.
1817
1818### Bind form-data request with custom struct
1819
1820The follow example using custom struct:
1821
1822```go
1823type StructA struct {
1824 FieldA string `form:"field_a"`
1825}
1826
1827type StructB struct {
1828 NestedStruct StructA
1829 FieldB string `form:"field_b"`
1830}
1831
1832type StructC struct {
1833 NestedStructPointer *StructA
1834 FieldC string `form:"field_c"`
1835}
1836
1837type StructD struct {
1838 NestedAnonyStruct struct {
1839 FieldX string `form:"field_x"`
1840 }
1841 FieldD string `form:"field_d"`
1842}
1843
1844func GetDataB(c *gin.Context) {
1845 var b StructB
1846 c.Bind(&b)
1847 c.JSON(http.StatusOK, gin.H{
1848 "a": b.NestedStruct,
1849 "b": b.FieldB,
1850 })
1851}
1852
1853func GetDataC(c *gin.Context) {
1854 var b StructC
1855 c.Bind(&b)
1856 c.JSON(http.StatusOK, gin.H{
1857 "a": b.NestedStructPointer,
1858 "c": b.FieldC,
1859 })
1860}
1861
1862func GetDataD(c *gin.Context) {
1863 var b StructD
1864 c.Bind(&b)
1865 c.JSON(http.StatusOK, gin.H{
1866 "x": b.NestedAnonyStruct,
1867 "d": b.FieldD,
1868 })
1869}
1870
1871func main() {
1872 r := gin.Default()
1873 r.GET("/getb", GetDataB)
1874 r.GET("/getc", GetDataC)
1875 r.GET("/getd", GetDataD)
1876
1877 r.Run()
1878}
1879```
1880
1881Using the command `curl` command result:
1882
1883```sh
1884$ curl "http://localhost:8080/getb?field_a=hello&field_b=world"
1885{"a":{"FieldA":"hello"},"b":"world"}
1886$ curl "http://localhost:8080/getc?field_a=hello&field_c=world"
1887{"a":{"FieldA":"hello"},"c":"world"}
1888$ curl "http://localhost:8080/getd?field_x=hello&field_d=world"
1889{"d":"world","x":{"FieldX":"hello"}}
1890```
1891
1892### Try to bind body into different structs
1893
1894The normal methods for binding request body consumes `c.Request.Body` and they
1895cannot be called multiple times.
1896
1897```go
1898type formA struct {
1899 Foo string `json:"foo" xml:"foo" binding:"required"`
1900}
1901
1902type formB struct {
1903 Bar string `json:"bar" xml:"bar" binding:"required"`
1904}
1905
1906func SomeHandler(c *gin.Context) {
1907 objA := formA{}
1908 objB := formB{}
1909 // This c.ShouldBind consumes c.Request.Body and it cannot be reused.
1910 if errA := c.ShouldBind(&objA); errA == nil {
1911 c.String(http.StatusOK, `the body should be formA`)
1912 // Always an error is occurred by this because c.Request.Body is EOF now.
1913 } else if errB := c.ShouldBind(&objB); errB == nil {
1914 c.String(http.StatusOK, `the body should be formB`)
1915 } else {
1916 ...
1917 }
1918}
1919```
1920
1921For this, you can use `c.ShouldBindBodyWith`.
1922
1923```go
1924func SomeHandler(c *gin.Context) {
1925 objA := formA{}
1926 objB := formB{}
1927 // This reads c.Request.Body and stores the result into the context.
1928 if errA := c.ShouldBindBodyWith(&objA, binding.Form); errA == nil {
1929 c.String(http.StatusOK, `the body should be formA`)
1930 // At this time, it reuses body stored in the context.
1931 } else if errB := c.ShouldBindBodyWith(&objB, binding.JSON); errB == nil {
1932 c.String(http.StatusOK, `the body should be formB JSON`)
1933 // And it can accepts other formats
1934 } else if errB2 := c.ShouldBindBodyWith(&objB, binding.XML); errB2 == nil {
1935 c.String(http.StatusOK, `the body should be formB XML`)
1936 } else {
1937 ...
1938 }
1939}
1940```
1941
19421. `c.ShouldBindBodyWith` stores body into the context before binding. This has
1943a slight impact to performance, so you should not use this method if you are
1944enough to call binding at once.
19452. This feature is only needed for some formats -- `JSON`, `XML`, `MsgPack`,
1946`ProtoBuf`. For other formats, `Query`, `Form`, `FormPost`, `FormMultipart`,
1947can be called by `c.ShouldBind()` multiple times without any damage to
1948performance (See [#1341](https://github.com/gin-gonic/gin/pull/1341)).
1949
1950### Bind form-data request with custom struct and custom tag
1951
1952```go
1953const (
1954 customerTag = "url"
1955 defaultMemory = 32 << 20
1956)
1957
1958type customerBinding struct {}
1959
1960func (customerBinding) Name() string {
1961 return "form"
1962}
1963
1964func (customerBinding) Bind(req *http.Request, obj any) error {
1965 if err := req.ParseForm(); err != nil {
1966 return err
1967 }
1968 if err := req.ParseMultipartForm(defaultMemory); err != nil {
1969 if err != http.ErrNotMultipart {
1970 return err
1971 }
1972 }
1973 if err := binding.MapFormWithTag(obj, req.Form, customerTag); err != nil {
1974 return err
1975 }
1976 return validate(obj)
1977}
1978
1979func validate(obj any) error {
1980 if binding.Validator == nil {
1981 return nil
1982 }
1983 return binding.Validator.ValidateStruct(obj)
1984}
1985
1986// Now we can do this!!!
1987// FormA is an external type that we can't modify it's tag
1988type FormA struct {
1989 FieldA string `url:"field_a"`
1990}
1991
1992func ListHandler(s *Service) func(ctx *gin.Context) {
1993 return func(ctx *gin.Context) {
1994 var urlBinding = customerBinding{}
1995 var opt FormA
1996 err := ctx.MustBindWith(&opt, urlBinding)
1997 if err != nil {
1998 ...
1999 }
2000 ...
2001 }
2002}
2003```
2004
2005### http2 server push
2006
2007http.Pusher is supported only **go1.8+**. See the [golang blog](https://go.dev/blog/h2push) for detail information.
2008
2009```go
2010package main
2011
2012import (
2013 "html/template"
2014 "log"
2015 "net/http"
2016
2017 "github.com/gin-gonic/gin"
2018)
2019
2020var html = template.Must(template.New("https").Parse(`
2021<html>
2022<head>
2023 <title>Https Test</title>
2024 <script src="/assets/app.js"></script>
2025</head>
2026<body>
2027 <h1 style="color:red;">Welcome, Ginner!</h1>
2028</body>
2029</html>
2030`))
2031
2032func main() {
2033 r := gin.Default()
2034 r.Static("/assets", "./assets")
2035 r.SetHTMLTemplate(html)
2036
2037 r.GET("/", func(c *gin.Context) {
2038 if pusher := c.Writer.Pusher(); pusher != nil {
2039 // use pusher.Push() to do server push
2040 if err := pusher.Push("/assets/app.js", nil); err != nil {
2041 log.Printf("Failed to push: %v", err)
2042 }
2043 }
2044 c.HTML(http.StatusOK, "https", gin.H{
2045 "status": "success",
2046 })
2047 })
2048
2049 // Listen and Server in https://127.0.0.1:8080
2050 r.RunTLS(":8080", "./testdata/server.pem", "./testdata/server.key")
2051}
2052```
2053
2054### Define format for the log of routes
2055
2056The default log of routes is:
2057
2058```sh
2059[GIN-debug] POST /foo --> main.main.func1 (3 handlers)
2060[GIN-debug] GET /bar --> main.main.func2 (3 handlers)
2061[GIN-debug] GET /status --> main.main.func3 (3 handlers)
2062```
2063
2064If 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`.
2065In the example below, we log all routes with standard log package but you can use another log tools that suits of your needs.
2066
2067```go
2068import (
2069 "log"
2070 "net/http"
2071
2072 "github.com/gin-gonic/gin"
2073)
2074
2075func main() {
2076 r := gin.Default()
2077 gin.DebugPrintRouteFunc = func(httpMethod, absolutePath, handlerName string, nuHandlers int) {
2078 log.Printf("endpoint %v %v %v %v\n", httpMethod, absolutePath, handlerName, nuHandlers)
2079 }
2080
2081 r.POST("/foo", func(c *gin.Context) {
2082 c.JSON(http.StatusOK, "foo")
2083 })
2084
2085 r.GET("/bar", func(c *gin.Context) {
2086 c.JSON(http.StatusOK, "bar")
2087 })
2088
2089 r.GET("/status", func(c *gin.Context) {
2090 c.JSON(http.StatusOK, "ok")
2091 })
2092
2093 // Listen and Server in http://0.0.0.0:8080
2094 r.Run()
2095}
2096```
2097
2098### Set and get a cookie
2099
2100```go
2101import (
2102 "fmt"
2103
2104 "github.com/gin-gonic/gin"
2105)
2106
2107func main() {
2108 router := gin.Default()
2109
2110 router.GET("/cookie", func(c *gin.Context) {
2111
2112 cookie, err := c.Cookie("gin_cookie")
2113
2114 if err != nil {
2115 cookie = "NotSet"
2116 c.SetCookie("gin_cookie", "test", 3600, "/", "localhost", false, true)
2117 }
2118
2119 fmt.Printf("Cookie value: %s \n", cookie)
2120 })
2121
2122 router.Run()
2123}
2124```
2125
2126## Don't trust all proxies
2127
2128Gin lets you specify which headers to hold the real client IP (if any),
2129as well as specifying which proxies (or direct clients) you trust to
2130specify one of these headers.
2131
2132Use function `SetTrustedProxies()` on your `gin.Engine` to specify network addresses
2133or network CIDRs from where clients which their request headers related to client
2134IP can be trusted. They can be IPv4 addresses, IPv4 CIDRs, IPv6 addresses or
2135IPv6 CIDRs.
2136
2137**Attention:** Gin trust all proxies by default if you don't specify a trusted
2138proxy using the function above, **this is NOT safe**. At the same time, if you don't
2139use any proxy, you can disable this feature by using `Engine.SetTrustedProxies(nil)`,
2140then `Context.ClientIP()` will return the remote address directly to avoid some
2141unnecessary computation.
2142
2143```go
2144import (
2145 "fmt"
2146
2147 "github.com/gin-gonic/gin"
2148)
2149
2150func main() {
2151 router := gin.Default()
2152 router.SetTrustedProxies([]string{"192.168.1.2"})
2153
2154 router.GET("/", func(c *gin.Context) {
2155 // If the client is 192.168.1.2, use the X-Forwarded-For
2156 // header to deduce the original client IP from the trust-
2157 // worthy parts of that header.
2158 // Otherwise, simply return the direct client IP
2159 fmt.Printf("ClientIP: %s\n", c.ClientIP())
2160 })
2161 router.Run()
2162}
2163```
2164
2165**Notice:** If you are using a CDN service, you can set the `Engine.TrustedPlatform`
2166to skip TrustedProxies check, it has a higher priority than TrustedProxies.
2167Look at the example below:
2168
2169```go
2170import (
2171 "fmt"
2172
2173 "github.com/gin-gonic/gin"
2174)
2175
2176func main() {
2177 router := gin.Default()
2178 // Use predefined header gin.PlatformXXX
2179 router.TrustedPlatform = gin.PlatformGoogleAppEngine
2180 // Or set your own trusted request header for another trusted proxy service
2181 // Don't set it to any suspect request header, it's unsafe
2182 router.TrustedPlatform = "X-CDN-IP"
2183
2184 router.GET("/", func(c *gin.Context) {
2185 // If you set TrustedPlatform, ClientIP() will resolve the
2186 // corresponding header and return IP directly
2187 fmt.Printf("ClientIP: %s\n", c.ClientIP())
2188 })
2189 router.Run()
2190}
2191```
2192
2193## Testing
2194
2195The `net/http/httptest` package is preferable way for HTTP testing.
2196
2197```go
2198package main
2199
2200import (
2201 "net/http"
2202
2203 "github.com/gin-gonic/gin"
2204)
2205
2206func setupRouter() *gin.Engine {
2207 r := gin.Default()
2208 r.GET("/ping", func(c *gin.Context) {
2209 c.String(http.StatusOK, "pong")
2210 })
2211 return r
2212}
2213
2214func main() {
2215 r := setupRouter()
2216 r.Run(":8080")
2217}
2218```
2219
2220Test for code example above:
2221
2222```go
2223package main
2224
2225import (
2226 "net/http"
2227 "net/http/httptest"
2228 "testing"
2229
2230 "github.com/stretchr/testify/assert"
2231)
2232
2233func TestPingRoute(t *testing.T) {
2234 router := setupRouter()
2235
2236 w := httptest.NewRecorder()
2237 req, _ := http.NewRequest(http.MethodGet, "/ping", nil)
2238 router.ServeHTTP(w, req)
2239
2240 assert.Equal(t, http.StatusOK, w.Code)
2241 assert.Equal(t, "pong", w.Body.String())
2242}
2243```
View as plain text