{"id":37111313,"url":"https://github.com/danielkov/gin","last_synced_at":"2026-01-14T13:11:49.288Z","repository":{"id":57582013,"uuid":"109985106","full_name":"danielkov/gin","owner":"danielkov","description":"Gin is a HTTP web framework written in Go (Golang). It features a Martini-like API with much better performance -- up to 40 times faster. If you need smashing performance, get yourself some Gin.","archived":false,"fork":true,"pushed_at":"2017-11-08T14:18:52.000Z","size":1695,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-04-08T22:02:01.523Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://gin-gonic.github.io/gin/","language":"Go","has_issues":false,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":"gin-gonic/gin","license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/danielkov.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-11-08T14:17:54.000Z","updated_at":"2017-11-08T14:17:57.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/danielkov/gin","commit_stats":null,"previous_names":[],"tags_count":15,"template":false,"template_full_name":null,"purl":"pkg:github/danielkov/gin","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/danielkov%2Fgin","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/danielkov%2Fgin/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/danielkov%2Fgin/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/danielkov%2Fgin/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/danielkov","download_url":"https://codeload.github.com/danielkov/gin/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/danielkov%2Fgin/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28420843,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-14T10:47:48.104Z","status":"ssl_error","status_checked_at":"2026-01-14T10:46:19.031Z","response_time":107,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-01-14T13:11:48.685Z","updated_at":"2026-01-14T13:11:49.275Z","avatar_url":"https://github.com/danielkov.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Gin Web Framework\n\n\u003cimg align=\"right\" width=\"159px\" src=\"https://raw.githubusercontent.com/gin-gonic/logo/master/color.png\"\u003e\n\n[![Build Status](https://travis-ci.org/gin-gonic/gin.svg)](https://travis-ci.org/gin-gonic/gin)\n [![codecov](https://codecov.io/gh/gin-gonic/gin/branch/master/graph/badge.svg)](https://codecov.io/gh/gin-gonic/gin)\n [![Go Report Card](https://goreportcard.com/badge/github.com/gin-gonic/gin)](https://goreportcard.com/report/github.com/gin-gonic/gin)\n [![GoDoc](https://godoc.org/github.com/gin-gonic/gin?status.svg)](https://godoc.org/github.com/gin-gonic/gin)\n [![Join the chat at https://gitter.im/gin-gonic/gin](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/gin-gonic/gin?utm_source=badge\u0026utm_medium=badge\u0026utm_campaign=pr-badge\u0026utm_content=badge)\n\nGin is a web framework written in Go (Golang). It features a martini-like API with much better performance, up to 40 times faster thanks to [httprouter](https://github.com/julienschmidt/httprouter). If you need performance and good productivity, you will love Gin.\n\n![Gin console logger](https://gin-gonic.github.io/gin/other/console.png)\n\n```sh\n# assume the following codes in example.go file\n$ cat example.go\n```\n\n```go\npackage main\n\nimport \"github.com/gin-gonic/gin\"\n\nfunc main() {\n\tr := gin.Default()\n\tr.GET(\"/ping\", func(c *gin.Context) {\n\t\tc.JSON(200, gin.H{\n\t\t\t\"message\": \"pong\",\n\t\t})\n\t})\n\tr.Run() // listen and serve on 0.0.0.0:8080\n}\n```\n\n```\n# run example.go and visit 0.0.0.0:8080/ping on browser\n$ go run example.go\n```\n\n## Benchmarks\n\nGin uses a custom version of [HttpRouter](https://github.com/julienschmidt/httprouter)\n\n[See all benchmarks](/BENCHMARKS.md)\n\nBenchmark name                              | (1)        | (2)         | (3) \t\t    | (4)\n--------------------------------------------|-----------:|------------:|-----------:|---------:\n**BenchmarkGin_GithubAll**                  | **30000**  |  **48375**  |     **0**  |   **0**\nBenchmarkAce_GithubAll                      |   10000    |   134059    |   13792    |   167\nBenchmarkBear_GithubAll                     |    5000    |   534445    |   86448    |   943\nBenchmarkBeego_GithubAll                    |    3000    |   592444    |   74705    |   812\nBenchmarkBone_GithubAll                     |     200    |  6957308    |  698784    |  8453\nBenchmarkDenco_GithubAll                    |   10000    |   158819    |   20224    |   167\nBenchmarkEcho_GithubAll                     |   10000    |   154700    |    6496    |   203\nBenchmarkGocraftWeb_GithubAll               |    3000    |   570806    |  131656    |  1686\nBenchmarkGoji_GithubAll                     |    2000    |   818034    |   56112    |   334\nBenchmarkGojiv2_GithubAll                   |    2000    |  1213973    |  274768    |  3712\nBenchmarkGoJsonRest_GithubAll               |    2000    |   785796    |  134371    |  2737\nBenchmarkGoRestful_GithubAll                |     300    |  5238188    |  689672    |  4519\nBenchmarkGorillaMux_GithubAll               |     100    | 10257726    |  211840    |  2272\nBenchmarkHttpRouter_GithubAll               |   20000    |   105414    |   13792    |   167\nBenchmarkHttpTreeMux_GithubAll              |   10000    |   319934    |   65856    |   671\nBenchmarkKocha_GithubAll                    |   10000    |   209442    |   23304    |   843\nBenchmarkLARS_GithubAll                     |   20000    |    62565    |       0    |     0\nBenchmarkMacaron_GithubAll                  |    2000    |  1161270    |  204194    |  2000\nBenchmarkMartini_GithubAll                  |     200    |  9991713    |  226549    |  2325\nBenchmarkPat_GithubAll                      |     200    |  5590793    | 1499568    | 27435\nBenchmarkPossum_GithubAll                   |   10000    |   319768    |   84448    |   609\nBenchmarkR2router_GithubAll                 |   10000    |   305134    |   77328    |   979\nBenchmarkRivet_GithubAll                    |   10000    |   132134    |   16272    |   167\nBenchmarkTango_GithubAll                    |    3000    |   552754    |   63826    |  1618\nBenchmarkTigerTonic_GithubAll               |    1000    |  1439483    |  239104    |  5374\nBenchmarkTraffic_GithubAll                  |     100    | 11383067    | 2659329    | 21848\nBenchmarkVulcan_GithubAll                   |    5000    |   394253    |   19894    |   609\n\n- (1): Total Repetitions achieved in constant time, higher means more confident result\n- (2): Single Repetition Duration (ns/op), lower is better\n- (3): Heap Memory (B/op), lower is better\n- (4): Average Allocations per Repetition (allocs/op), lower is better\n\n## Gin v1. stable\n\n- [x] Zero allocation router.\n- [x] Still the fastest http router and framework. From routing to writing.\n- [x] Complete suite of unit tests\n- [x] Battle tested\n- [x] API frozen, new releases will not break your code.\n\n## Start using it\n\n1. Download and install it:\n\n```sh\n$ go get github.com/gin-gonic/gin\n```\n\n2. Import it in your code:\n\n```go\nimport \"github.com/gin-gonic/gin\"\n```\n\n3. (Optional) Import `net/http`. This is required for example if using constants such as `http.StatusOK`.\n\n```go\nimport \"net/http\"\n```\n\n### Use a vendor tool like [Govendor](https://github.com/kardianos/govendor)\n\n1. `go get` govendor\n\n```sh\n$ go get github.com/kardianos/govendor\n```\n2. Create your project folder and `cd` inside\n\n```sh\n$ mkdir -p ~/go/src/github.com/myusername/project \u0026\u0026 cd \"$_\"\n```\n\n3. Vendor init your project and add gin\n\n```sh\n$ govendor init\n$ govendor fetch github.com/gin-gonic/gin@v1.2\n```\n\n4. Copy a starting template inside your project\n\n```sh\n$ curl https://raw.githubusercontent.com/gin-gonic/gin/master/examples/basic/main.go \u003e main.go\n```\n\n5. Run your project\n\n```sh\n$ go run main.go\n```\n\n## Build with [jsoniter](https://github.com/json-iterator/go)\n\nGin use `encoding/json` as default json package but you can change to [jsoniter](https://github.com/json-iterator/go) by build from other tags.\n\n```sh\n$ go build -tags=jsoniter .\n```\n\n## API Examples\n\n### Using GET, POST, PUT, PATCH, DELETE and OPTIONS\n\n```go\nfunc main() {\n\t// Disable Console Color\n\t// gin.DisableConsoleColor()\n\n\t// Creates a gin router with default middleware:\n\t// logger and recovery (crash-free) middleware\n\trouter := gin.Default()\n\n\trouter.GET(\"/someGet\", getting)\n\trouter.POST(\"/somePost\", posting)\n\trouter.PUT(\"/somePut\", putting)\n\trouter.DELETE(\"/someDelete\", deleting)\n\trouter.PATCH(\"/somePatch\", patching)\n\trouter.HEAD(\"/someHead\", head)\n\trouter.OPTIONS(\"/someOptions\", options)\n\n\t// By default it serves on :8080 unless a\n\t// PORT environment variable was defined.\n\trouter.Run()\n\t// router.Run(\":3000\") for a hard coded port\n}\n```\n\n### Parameters in path\n\n```go\nfunc main() {\n\trouter := gin.Default()\n\n\t// This handler will match /user/john but will not match neither /user/ or /user\n\trouter.GET(\"/user/:name\", func(c *gin.Context) {\n\t\tname := c.Param(\"name\")\n\t\tc.String(http.StatusOK, \"Hello %s\", name)\n\t})\n\n\t// However, this one will match /user/john/ and also /user/john/send\n\t// If no other routers match /user/john, it will redirect to /user/john/\n\trouter.GET(\"/user/:name/*action\", func(c *gin.Context) {\n\t\tname := c.Param(\"name\")\n\t\taction := c.Param(\"action\")\n\t\tmessage := name + \" is \" + action\n\t\tc.String(http.StatusOK, message)\n\t})\n\n\trouter.Run(\":8080\")\n}\n```\n\n### Querystring parameters\n\n```go\nfunc main() {\n\trouter := gin.Default()\n\n\t// Query string parameters are parsed using the existing underlying request object.\n\t// The request responds to a url matching:  /welcome?firstname=Jane\u0026lastname=Doe\n\trouter.GET(\"/welcome\", func(c *gin.Context) {\n\t\tfirstname := c.DefaultQuery(\"firstname\", \"Guest\")\n\t\tlastname := c.Query(\"lastname\") // shortcut for c.Request.URL.Query().Get(\"lastname\")\n\n\t\tc.String(http.StatusOK, \"Hello %s %s\", firstname, lastname)\n\t})\n\trouter.Run(\":8080\")\n}\n```\n\n### Multipart/Urlencoded Form\n\n```go\nfunc main() {\n\trouter := gin.Default()\n\n\trouter.POST(\"/form_post\", func(c *gin.Context) {\n\t\tmessage := c.PostForm(\"message\")\n\t\tnick := c.DefaultPostForm(\"nick\", \"anonymous\")\n\n\t\tc.JSON(200, gin.H{\n\t\t\t\"status\":  \"posted\",\n\t\t\t\"message\": message,\n\t\t\t\"nick\":    nick,\n\t\t})\n\t})\n\trouter.Run(\":8080\")\n}\n```\n\n### Another example: query + post form\n\n```\nPOST /post?id=1234\u0026page=1 HTTP/1.1\nContent-Type: application/x-www-form-urlencoded\n\nname=manu\u0026message=this_is_great\n```\n\n```go\nfunc main() {\n\trouter := gin.Default()\n\n\trouter.POST(\"/post\", func(c *gin.Context) {\n\n\t\tid := c.Query(\"id\")\n\t\tpage := c.DefaultQuery(\"page\", \"0\")\n\t\tname := c.PostForm(\"name\")\n\t\tmessage := c.PostForm(\"message\")\n\n\t\tfmt.Printf(\"id: %s; page: %s; name: %s; message: %s\", id, page, name, message)\n\t})\n\trouter.Run(\":8080\")\n}\n```\n\n```\nid: 1234; page: 1; name: manu; message: this_is_great\n```\n\n### Upload files\n\n#### Single file\n\nReferences issue [#774](https://github.com/gin-gonic/gin/issues/774) and detail [example code](examples/upload-file/single).\n\n```go\nfunc main() {\n\trouter := gin.Default()\n\t// Set a lower memory limit for multipart forms (default is 32 MiB)\n\t// router.MaxMultipartMemory = 8 \u003c\u003c 20  // 8 MiB\n\trouter.POST(\"/upload\", func(c *gin.Context) {\n\t\t// single file\n\t\tfile, _ := c.FormFile(\"file\")\n\t\tlog.Println(file.Filename)\n\n\t\t// Upload the file to specific dst.\n\t\t// c.SaveUploadedFile(file, dst)\n\n\t\tc.String(http.StatusOK, fmt.Sprintf(\"'%s' uploaded!\", file.Filename))\n\t})\n\trouter.Run(\":8080\")\n}\n```\n\nHow to `curl`:\n\n```bash\ncurl -X POST http://localhost:8080/upload \\\n  -F \"file=@/Users/appleboy/test.zip\" \\\n  -H \"Content-Type: multipart/form-data\"\n```\n\n#### Multiple files\n\nSee the detail [example code](examples/upload-file/multiple).\n\n```go\nfunc main() {\n\trouter := gin.Default()\n\t// Set a lower memory limit for multipart forms (default is 32 MiB)\n\t// router.MaxMultipartMemory = 8 \u003c\u003c 20  // 8 MiB\n\trouter.POST(\"/upload\", func(c *gin.Context) {\n\t\t// Multipart form\n\t\tform, _ := c.MultipartForm()\n\t\tfiles := form.File[\"upload[]\"]\n\n\t\tfor _, file := range files {\n\t\t\tlog.Println(file.Filename)\n\n\t\t\t// Upload the file to specific dst.\n\t\t\t// c.SaveUploadedFile(file, dst)\n\t\t}\n\t\tc.String(http.StatusOK, fmt.Sprintf(\"%d files uploaded!\", len(files)))\n\t})\n\trouter.Run(\":8080\")\n}\n```\n\nHow to `curl`:\n\n```bash\ncurl -X POST http://localhost:8080/upload \\\n  -F \"upload[]=@/Users/appleboy/test1.zip\" \\\n  -F \"upload[]=@/Users/appleboy/test2.zip\" \\\n  -H \"Content-Type: multipart/form-data\"\n```\n\n### Grouping routes\n\n```go\nfunc main() {\n\trouter := gin.Default()\n\n\t// Simple group: v1\n\tv1 := router.Group(\"/v1\")\n\t{\n\t\tv1.POST(\"/login\", loginEndpoint)\n\t\tv1.POST(\"/submit\", submitEndpoint)\n\t\tv1.POST(\"/read\", readEndpoint)\n\t}\n\n\t// Simple group: v2\n\tv2 := router.Group(\"/v2\")\n\t{\n\t\tv2.POST(\"/login\", loginEndpoint)\n\t\tv2.POST(\"/submit\", submitEndpoint)\n\t\tv2.POST(\"/read\", readEndpoint)\n\t}\n\n\trouter.Run(\":8080\")\n}\n```\n\n### Blank Gin without middleware by default\n\nUse\n\n```go\nr := gin.New()\n```\n\ninstead of\n\n```go\n// Default With the Logger and Recovery middleware already attached\nr := gin.Default()\n```\n\n\n### Using middleware\n```go\nfunc main() {\n\t// Creates a router without any middleware by default\n\tr := gin.New()\n\n\t// Global middleware\n\t// Logger middleware will write the logs to gin.DefaultWriter even you set with GIN_MODE=release.\n\t// By default gin.DefaultWriter = os.Stdout\n\tr.Use(gin.Logger())\n\n\t// Recovery middleware recovers from any panics and writes a 500 if there was one.\n\tr.Use(gin.Recovery())\n\n\t// Per route middleware, you can add as many as you desire.\n\tr.GET(\"/benchmark\", MyBenchLogger(), benchEndpoint)\n\n\t// Authorization group\n\t// authorized := r.Group(\"/\", AuthRequired())\n\t// exactly the same as:\n\tauthorized := r.Group(\"/\")\n\t// per group middleware! in this case we use the custom created\n\t// AuthRequired() middleware just in the \"authorized\" group.\n\tauthorized.Use(AuthRequired())\n\t{\n\t\tauthorized.POST(\"/login\", loginEndpoint)\n\t\tauthorized.POST(\"/submit\", submitEndpoint)\n\t\tauthorized.POST(\"/read\", readEndpoint)\n\n\t\t// nested group\n\t\ttesting := authorized.Group(\"testing\")\n\t\ttesting.GET(\"/analytics\", analyticsEndpoint)\n\t}\n\n\t// Listen and serve on 0.0.0.0:8080\n\tr.Run(\":8080\")\n}\n```\n\n### How to write log file\n```go\nfunc main() {\n    // Disable Console Color, you don't need console color when writing the logs to file.\n    gin.DisableConsoleColor()\n\n    // Logging to a file.\n    f, _ := os.Create(\"gin.log\")\n    gin.DefaultWriter = io.MultiWriter(f)\n\n    // Use the following code if you need to write the logs to file and console at the same time.\n    // gin.DefaultWriter = io.MultiWriter(f, os.Stdout)\n\n    router := gin.Default()\n    router.GET(\"/ping\", func(c *gin.Context) {\n        c.String(200, \"pong\")\n    })\n\n    r.Run(\":8080\")\n}\n```\n\n### Model binding and validation\n\nTo bind a request body into a type, use model binding. We currently support binding of JSON, XML and standard form values (foo=bar\u0026boo=baz).\n\nGin uses [**go-playground/validator.v8**](https://github.com/go-playground/validator) for validation. Check the full docs on tags usage [here](http://godoc.org/gopkg.in/go-playground/validator.v8#hdr-Baked_In_Validators_and_Tags).\n\nNote 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\"`.\n\nAlso, Gin provides two sets of methods for binding:\n- **Type** - Must bind\n  - **Methods** - `Bind`, `BindJSON`, `BindQuery`\n  - **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.\n- **Type** - Should bind\n  - **Methods** - `ShouldBind`, `ShouldBindJSON`, `ShouldBindQuery`\n  - **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.\n\nWhen 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`.\n\nYou 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.\n\n```go\n// Binding from JSON\ntype Login struct {\n\tUser     string `form:\"user\" json:\"user\" binding:\"required\"`\n\tPassword string `form:\"password\" json:\"password\" binding:\"required\"`\n}\n\nfunc main() {\n\trouter := gin.Default()\n\n\t// Example for binding JSON ({\"user\": \"manu\", \"password\": \"123\"})\n\trouter.POST(\"/loginJSON\", func(c *gin.Context) {\n\t\tvar json Login\n\t\tif err = c.ShouldBindJSON(\u0026json); err == nil {\n\t\t\tif json.User == \"manu\" \u0026\u0026 json.Password == \"123\" {\n\t\t\t\tc.JSON(http.StatusOK, gin.H{\"status\": \"you are logged in\"})\n\t\t\t} else {\n\t\t\t\tc.JSON(http.StatusUnauthorized, gin.H{\"status\": \"unauthorized\"})\n\t\t\t}\n\t\t} else {\n\t\t\tc.JSON(http.StatusBadRequest, gin.H{\"error\": err.Error()})\n\t\t}\n\t})\n\n\t// Example for binding a HTML form (user=manu\u0026password=123)\n\trouter.POST(\"/loginForm\", func(c *gin.Context) {\n\t\tvar form Login\n\t\t// This will infer what binder to use depending on the content-type header.\n\t\tif err := c.ShouldBind(\u0026form); err == nil {\n\t\t\tif form.User == \"manu\" \u0026\u0026 form.Password == \"123\" {\n\t\t\t\tc.JSON(http.StatusOK, gin.H{\"status\": \"you are logged in\"})\n\t\t\t} else {\n\t\t\t\tc.JSON(http.StatusUnauthorized, gin.H{\"status\": \"unauthorized\"})\n\t\t\t}\n\t\t} else {\n\t\t\tc.JSON(http.StatusBadRequest, gin.H{\"error\": err.Error()})\n\t\t}\n\t})\n\n\t// Listen and serve on 0.0.0.0:8080\n\trouter.Run(\":8080\")\n}\n```\n\n**Sample request**\n```shell\n$ curl -v -X POST \\\n  http://localhost:8080/loginJSON \\\n  -H 'content-type: application/json' \\\n  -d '{ \"user\": \"manu\" }'\n\u003e POST /loginJSON HTTP/1.1\n\u003e Host: localhost:8080\n\u003e User-Agent: curl/7.51.0\n\u003e Accept: */*\n\u003e content-type: application/json\n\u003e Content-Length: 18\n\u003e\n* upload completely sent off: 18 out of 18 bytes\n\u003c HTTP/1.1 400 Bad Request\n\u003c Content-Type: application/json; charset=utf-8\n\u003c Date: Fri, 04 Aug 2017 03:51:31 GMT\n\u003c Content-Length: 100\n\u003c\n{\"error\":\"Key: 'Login.Password' Error:Field validation for 'Password' failed on the 'required' tag\"}\n```\n\n### Custom Validators\n\nIt is also possible to register custom validators. See the [example code](examples/custom-validation/server.go).\n\n[embedmd]:# (examples/custom-validation/server.go go)\n```go\npackage main\n\nimport (\n\t\"net/http\"\n\t\"reflect\"\n\t\"time\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/gin-gonic/gin/binding\"\n\tvalidator \"gopkg.in/go-playground/validator.v8\"\n)\n\ntype Booking struct {\n\tCheckIn  time.Time `form:\"check_in\" binding:\"required,bookabledate\" time_format:\"2006-01-02\"`\n\tCheckOut time.Time `form:\"check_out\" binding:\"required,gtfield=CheckIn\" time_format:\"2006-01-02\"`\n}\n\nfunc bookableDate(\n\tv *validator.Validate, topStruct reflect.Value, currentStructOrField reflect.Value,\n\tfield reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string,\n) bool {\n\tif date, ok := field.Interface().(time.Time); ok {\n\t\ttoday := time.Now()\n\t\tif today.Year() \u003e date.Year() || today.YearDay() \u003e date.YearDay() {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n\nfunc main() {\n\troute := gin.Default()\n\tbinding.Validator.RegisterValidation(\"bookabledate\", bookableDate)\n\troute.GET(\"/bookable\", getBookable)\n\troute.Run(\":8085\")\n}\n\nfunc getBookable(c *gin.Context) {\n\tvar b Booking\n\tif err := c.ShouldBindWith(\u0026b, binding.Query); err == nil {\n\t\tc.JSON(http.StatusOK, gin.H{\"message\": \"Booking dates are valid!\"})\n\t} else {\n\t\tc.JSON(http.StatusBadRequest, gin.H{\"error\": err.Error()})\n\t}\n}\n```\n\n```console\n$ curl \"localhost:8085/bookable?check_in=2017-08-16\u0026check_out=2017-08-17\"\n{\"message\":\"Booking dates are valid!\"}\n\n$ curl \"localhost:8085/bookable?check_in=2017-08-15\u0026check_out=2017-08-16\"\n{\"error\":\"Key: 'Booking.CheckIn' Error:Field validation for 'CheckIn' failed on the 'bookabledate' tag\"}\n```\n\n### Only Bind Query String\n\n`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).\n\n```go\npackage main\n\nimport (\n\t\"log\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\ntype Person struct {\n\tName    string `form:\"name\"`\n\tAddress string `form:\"address\"`\n}\n\nfunc main() {\n\troute := gin.Default()\n\troute.Any(\"/testing\", startPage)\n\troute.Run(\":8085\")\n}\n\nfunc startPage(c *gin.Context) {\n\tvar person Person\n\tif c.ShouldBindQuery(\u0026person) == nil {\n\t\tlog.Println(\"====== Only Bind By Query String ======\")\n\t\tlog.Println(person.Name)\n\t\tlog.Println(person.Address)\n\t}\n\tc.String(200, \"Success\")\n}\n\n```\n\n### Bind Query String or Post Data\n\nSee the [detail information](https://github.com/gin-gonic/gin/issues/742#issuecomment-264681292).\n\n```go\npackage main\n\nimport \"log\"\nimport \"github.com/gin-gonic/gin\"\nimport \"time\"\n\ntype Person struct {\n\tName     string    `form:\"name\"`\n\tAddress  string    `form:\"address\"`\n\tBirthday time.Time `form:\"birthday\" time_format:\"2006-01-02\" time_utc:\"1\"`\n}\n\nfunc main() {\n\troute := gin.Default()\n\troute.GET(\"/testing\", startPage)\n\troute.Run(\":8085\")\n}\n\nfunc startPage(c *gin.Context) {\n\tvar person Person\n\t// If `GET`, only `Form` binding engine (`query`) used.\n\t// If `POST`, first checks the `content-type` for `JSON` or `XML`, then uses `Form` (`form-data`).\n\t// See more at https://github.com/gin-gonic/gin/blob/master/binding/binding.go#L48\n\tif c.ShouldBind(\u0026person) == nil {\n\t\tlog.Println(person.Name)\n\t\tlog.Println(person.Address)\n\t\tlog.Println(person.Birthday)\n\t}\n\n\tc.String(200, \"Success\")\n}\n```\n\nTest it with:\n```sh\n$ curl -X GET \"localhost:8085/testing?name=appleboy\u0026address=xyz\u0026birthday=1992-03-15\"\n```\n\n### Bind HTML checkboxes\n\nSee the [detail information](https://github.com/gin-gonic/gin/issues/129#issuecomment-124260092)\n\nmain.go\n\n```go\n...\n\ntype myForm struct {\n    Colors []string `form:\"colors[]\"`\n}\n\n...\n\nfunc formHandler(c *gin.Context) {\n    var fakeForm myForm\n    c.ShouldBind(\u0026fakeForm)\n    c.JSON(200, gin.H{\"color\": fakeForm.Colors})\n}\n\n...\n\n```\n\nform.html\n\n```html\n\u003cform action=\"/\" method=\"POST\"\u003e\n    \u003cp\u003eCheck some colors\u003c/p\u003e\n    \u003clabel for=\"red\"\u003eRed\u003c/label\u003e\n    \u003cinput type=\"checkbox\" name=\"colors[]\" value=\"red\" id=\"red\" /\u003e\n    \u003clabel for=\"green\"\u003eGreen\u003c/label\u003e\n    \u003cinput type=\"checkbox\" name=\"colors[]\" value=\"green\" id=\"green\" /\u003e\n    \u003clabel for=\"blue\"\u003eBlue\u003c/label\u003e\n    \u003cinput type=\"checkbox\" name=\"colors[]\" value=\"blue\" id=\"blue\" /\u003e\n    \u003cinput type=\"submit\" /\u003e\n\u003c/form\u003e\n```\n\nresult:\n\n```\n{\"color\":[\"red\",\"green\",\"blue\"]}\n```\n\n### Multipart/Urlencoded binding\n\n```go\npackage main\n\nimport (\n\t\"github.com/gin-gonic/gin\"\n)\n\ntype LoginForm struct {\n\tUser     string `form:\"user\" binding:\"required\"`\n\tPassword string `form:\"password\" binding:\"required\"`\n}\n\nfunc main() {\n\trouter := gin.Default()\n\trouter.POST(\"/login\", func(c *gin.Context) {\n\t\t// you can bind multipart form with explicit binding declaration:\n\t\t// c.ShouldBindWith(\u0026form, binding.Form)\n\t\t// or you can simply use autobinding with ShouldBind method:\n\t\tvar form LoginForm\n\t\t// in this case proper binding will be automatically selected\n\t\tif c.ShouldBind(\u0026form) == nil {\n\t\t\tif form.User == \"user\" \u0026\u0026 form.Password == \"password\" {\n\t\t\t\tc.JSON(200, gin.H{\"status\": \"you are logged in\"})\n\t\t\t} else {\n\t\t\t\tc.JSON(401, gin.H{\"status\": \"unauthorized\"})\n\t\t\t}\n\t\t}\n\t})\n\trouter.Run(\":8080\")\n}\n```\n\nTest it with:\n```sh\n$ curl -v --form user=user --form password=password http://localhost:8080/login\n```\n\n### XML, JSON and YAML rendering\n\n```go\nfunc main() {\n\tr := gin.Default()\n\n\t// gin.H is a shortcut for map[string]interface{}\n\tr.GET(\"/someJSON\", func(c *gin.Context) {\n\t\tc.JSON(http.StatusOK, gin.H{\"message\": \"hey\", \"status\": http.StatusOK})\n\t})\n\n\tr.GET(\"/moreJSON\", func(c *gin.Context) {\n\t\t// You also can use a struct\n\t\tvar msg struct {\n\t\t\tName    string `json:\"user\"`\n\t\t\tMessage string\n\t\t\tNumber  int\n\t\t}\n\t\tmsg.Name = \"Lena\"\n\t\tmsg.Message = \"hey\"\n\t\tmsg.Number = 123\n\t\t// Note that msg.Name becomes \"user\" in the JSON\n\t\t// Will output  :   {\"user\": \"Lena\", \"Message\": \"hey\", \"Number\": 123}\n\t\tc.JSON(http.StatusOK, msg)\n\t})\n\n\tr.GET(\"/someXML\", func(c *gin.Context) {\n\t\tc.XML(http.StatusOK, gin.H{\"message\": \"hey\", \"status\": http.StatusOK})\n\t})\n\n\tr.GET(\"/someYAML\", func(c *gin.Context) {\n\t\tc.YAML(http.StatusOK, gin.H{\"message\": \"hey\", \"status\": http.StatusOK})\n\t})\n\n\t// Listen and serve on 0.0.0.0:8080\n\tr.Run(\":8080\")\n}\n```\n\n#### SecureJSON\n\nUsing SecureJSON to prevent json hijacking. Default prepends `\"while(1),\"` to response body if the given struct is array values.\n\n```go\nfunc main() {\n\tr := gin.Default()\n\n\t// You can also use your own secure json prefix\n\t// r.SecureJsonPrefix(\")]}',\\n\")\n\n\tr.GET(\"/someJSON\", func(c *gin.Context) {\n\t\tnames := []string{\"lena\", \"austin\", \"foo\"}\n\n\t\t// Will output  :   while(1);[\"lena\",\"austin\",\"foo\"]\n\t\tc.SecureJSON(http.StatusOK, names)\n\t})\n\n\t// Listen and serve on 0.0.0.0:8080\n\tr.Run(\":8080\")\n}\n```\n\n### Serving static files\n\n```go\nfunc main() {\n\trouter := gin.Default()\n\trouter.Static(\"/assets\", \"./assets\")\n\trouter.StaticFS(\"/more_static\", http.Dir(\"my_file_system\"))\n\trouter.StaticFile(\"/favicon.ico\", \"./resources/favicon.ico\")\n\n\t// Listen and serve on 0.0.0.0:8080\n\trouter.Run(\":8080\")\n}\n```\n\n### HTML rendering\n\nUsing LoadHTMLGlob() or LoadHTMLFiles()\n\n```go\nfunc main() {\n\trouter := gin.Default()\n\trouter.LoadHTMLGlob(\"templates/*\")\n\t//router.LoadHTMLFiles(\"templates/template1.html\", \"templates/template2.html\")\n\trouter.GET(\"/index\", func(c *gin.Context) {\n\t\tc.HTML(http.StatusOK, \"index.tmpl\", gin.H{\n\t\t\t\"title\": \"Main website\",\n\t\t})\n\t})\n\trouter.Run(\":8080\")\n}\n```\n\ntemplates/index.tmpl\n\n```html\n\u003chtml\u003e\n\t\u003ch1\u003e\n\t\t{{ .title }}\n\t\u003c/h1\u003e\n\u003c/html\u003e\n```\n\nUsing templates with same name in different directories\n\n```go\nfunc main() {\n\trouter := gin.Default()\n\trouter.LoadHTMLGlob(\"templates/**/*\")\n\trouter.GET(\"/posts/index\", func(c *gin.Context) {\n\t\tc.HTML(http.StatusOK, \"posts/index.tmpl\", gin.H{\n\t\t\t\"title\": \"Posts\",\n\t\t})\n\t})\n\trouter.GET(\"/users/index\", func(c *gin.Context) {\n\t\tc.HTML(http.StatusOK, \"users/index.tmpl\", gin.H{\n\t\t\t\"title\": \"Users\",\n\t\t})\n\t})\n\trouter.Run(\":8080\")\n}\n```\n\ntemplates/posts/index.tmpl\n\n```html\n{{ define \"posts/index.tmpl\" }}\n\u003chtml\u003e\u003ch1\u003e\n\t{{ .title }}\n\u003c/h1\u003e\n\u003cp\u003eUsing posts/index.tmpl\u003c/p\u003e\n\u003c/html\u003e\n{{ end }}\n```\n\ntemplates/users/index.tmpl\n\n```html\n{{ define \"users/index.tmpl\" }}\n\u003chtml\u003e\u003ch1\u003e\n\t{{ .title }}\n\u003c/h1\u003e\n\u003cp\u003eUsing users/index.tmpl\u003c/p\u003e\n\u003c/html\u003e\n{{ end }}\n```\n\n#### Custom Template renderer\n\nYou can also use your own html template render\n\n```go\nimport \"html/template\"\n\nfunc main() {\n\trouter := gin.Default()\n\thtml := template.Must(template.ParseFiles(\"file1\", \"file2\"))\n\trouter.SetHTMLTemplate(html)\n\trouter.Run(\":8080\")\n}\n```\n\n#### Custom Delimiters\n\nYou may use custom delims\n\n```go\n\tr := gin.Default()\n\tr.Delims(\"{[{\", \"}]}\")\n\tr.LoadHTMLGlob(\"/path/to/templates\"))\n```\n\n#### Custom Template Funcs\n\nSee the detail [example code](examples/template).\n\nmain.go\n\n```go\nimport (\n    \"fmt\"\n    \"html/template\"\n    \"net/http\"\n    \"time\"\n\n    \"github.com/gin-gonic/gin\"\n)\n\nfunc formatAsDate(t time.Time) string {\n    year, month, day := t.Date()\n    return fmt.Sprintf(\"%d%02d/%02d\", year, month, day)\n}\n\nfunc main() {\n    router := gin.Default()\n    router.Delims(\"{[{\", \"}]}\")\n    router.SetFuncMap(template.FuncMap{\n        \"formatAsDate\": formatAsDate,\n    })\n    router.LoadHTMLFiles(\"./fixtures/basic/raw.tmpl\")\n\n    router.GET(\"/raw\", func(c *gin.Context) {\n        c.HTML(http.StatusOK, \"raw.tmpl\", map[string]interface{}{\n            \"now\": time.Date(2017, 07, 01, 0, 0, 0, 0, time.UTC),\n        })\n    })\n\n    router.Run(\":8080\")\n}\n\n```\n\nraw.tmpl\n\n```html\nDate: {[{.now | formatAsDate}]}\n```\n\nResult:\n```\nDate: 2017/07/01\n```\n\n### Multitemplate\n\nGin 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`.\n\n### Redirects\n\nIssuing a HTTP redirect is easy:\n\n```go\nr.GET(\"/test\", func(c *gin.Context) {\n\tc.Redirect(http.StatusMovedPermanently, \"http://www.google.com/\")\n})\n```\nBoth internal and external locations are supported.\n\n\n### Custom Middleware\n\n```go\nfunc Logger() gin.HandlerFunc {\n\treturn func(c *gin.Context) {\n\t\tt := time.Now()\n\n\t\t// Set example variable\n\t\tc.Set(\"example\", \"12345\")\n\n\t\t// before request\n\n\t\tc.Next()\n\n\t\t// after request\n\t\tlatency := time.Since(t)\n\t\tlog.Print(latency)\n\n\t\t// access the status we are sending\n\t\tstatus := c.Writer.Status()\n\t\tlog.Println(status)\n\t}\n}\n\nfunc main() {\n\tr := gin.New()\n\tr.Use(Logger())\n\n\tr.GET(\"/test\", func(c *gin.Context) {\n\t\texample := c.MustGet(\"example\").(string)\n\n\t\t// it would print: \"12345\"\n\t\tlog.Println(example)\n\t})\n\n\t// Listen and serve on 0.0.0.0:8080\n\tr.Run(\":8080\")\n}\n```\n\n### Using BasicAuth() middleware\n\n```go\n// simulate some private data\nvar secrets = gin.H{\n\t\"foo\":    gin.H{\"email\": \"foo@bar.com\", \"phone\": \"123433\"},\n\t\"austin\": gin.H{\"email\": \"austin@example.com\", \"phone\": \"666\"},\n\t\"lena\":   gin.H{\"email\": \"lena@guapa.com\", \"phone\": \"523443\"},\n}\n\nfunc main() {\n\tr := gin.Default()\n\n\t// Group using gin.BasicAuth() middleware\n\t// gin.Accounts is a shortcut for map[string]string\n\tauthorized := r.Group(\"/admin\", gin.BasicAuth(gin.Accounts{\n\t\t\"foo\":    \"bar\",\n\t\t\"austin\": \"1234\",\n\t\t\"lena\":   \"hello2\",\n\t\t\"manu\":   \"4321\",\n\t}))\n\n\t// /admin/secrets endpoint\n\t// hit \"localhost:8080/admin/secrets\n\tauthorized.GET(\"/secrets\", func(c *gin.Context) {\n\t\t// get user, it was set by the BasicAuth middleware\n\t\tuser := c.MustGet(gin.AuthUserKey).(string)\n\t\tif secret, ok := secrets[user]; ok {\n\t\t\tc.JSON(http.StatusOK, gin.H{\"user\": user, \"secret\": secret})\n\t\t} else {\n\t\t\tc.JSON(http.StatusOK, gin.H{\"user\": user, \"secret\": \"NO SECRET :(\"})\n\t\t}\n\t})\n\n\t// Listen and serve on 0.0.0.0:8080\n\tr.Run(\":8080\")\n}\n```\n\n### Goroutines inside a middleware\n\nWhen starting inside a middleware or handler, you **SHOULD NOT** use the original context inside it, you have to use a read-only copy.\n\n```go\nfunc main() {\n\tr := gin.Default()\n\n\tr.GET(\"/long_async\", func(c *gin.Context) {\n\t\t// create copy to be used inside the goroutine\n\t\tcCp := c.Copy()\n\t\tgo func() {\n\t\t\t// simulate a long task with time.Sleep(). 5 seconds\n\t\t\ttime.Sleep(5 * time.Second)\n\n\t\t\t// note that you are using the copied context \"cCp\", IMPORTANT\n\t\t\tlog.Println(\"Done! in path \" + cCp.Request.URL.Path)\n\t\t}()\n\t})\n\n\tr.GET(\"/long_sync\", func(c *gin.Context) {\n\t\t// simulate a long task with time.Sleep(). 5 seconds\n\t\ttime.Sleep(5 * time.Second)\n\n\t\t// since we are NOT using a goroutine, we do not have to copy the context\n\t\tlog.Println(\"Done! in path \" + c.Request.URL.Path)\n\t})\n\n\t// Listen and serve on 0.0.0.0:8080\n\tr.Run(\":8080\")\n}\n```\n\n### Custom HTTP configuration\n\nUse `http.ListenAndServe()` directly, like this:\n\n```go\nfunc main() {\n\trouter := gin.Default()\n\thttp.ListenAndServe(\":8080\", router)\n}\n```\nor\n\n```go\nfunc main() {\n\trouter := gin.Default()\n\n\ts := \u0026http.Server{\n\t\tAddr:           \":8080\",\n\t\tHandler:        router,\n\t\tReadTimeout:    10 * time.Second,\n\t\tWriteTimeout:   10 * time.Second,\n\t\tMaxHeaderBytes: 1 \u003c\u003c 20,\n\t}\n\ts.ListenAndServe()\n}\n```\n\n### Support Let's Encrypt\n\nexample for 1-line LetsEncrypt HTTPS servers.\n\n[embedmd]:# (examples/auto-tls/example1.go go)\n```go\npackage main\n\nimport (\n\t\"log\"\n\n\t\"github.com/gin-gonic/autotls\"\n\t\"github.com/gin-gonic/gin\"\n)\n\nfunc main() {\n\tr := gin.Default()\n\n\t// Ping handler\n\tr.GET(\"/ping\", func(c *gin.Context) {\n\t\tc.String(200, \"pong\")\n\t})\n\n\tlog.Fatal(autotls.Run(r, \"example1.com\", \"example2.com\"))\n}\n```\n\nexample for custom autocert manager.\n\n[embedmd]:# (examples/auto-tls/example2.go go)\n```go\npackage main\n\nimport (\n\t\"log\"\n\n\t\"github.com/gin-gonic/autotls\"\n\t\"github.com/gin-gonic/gin\"\n\t\"golang.org/x/crypto/acme/autocert\"\n)\n\nfunc main() {\n\tr := gin.Default()\n\n\t// Ping handler\n\tr.GET(\"/ping\", func(c *gin.Context) {\n\t\tc.String(200, \"pong\")\n\t})\n\n\tm := autocert.Manager{\n\t\tPrompt:     autocert.AcceptTOS,\n\t\tHostPolicy: autocert.HostWhitelist(\"example1.com\", \"example2.com\"),\n\t\tCache:      autocert.DirCache(\"/var/www/.cache\"),\n\t}\n\n\tlog.Fatal(autotls.RunWithManager(r, \u0026m))\n}\n```\n\n### Run multiple service using Gin\n\nSee the [question](https://github.com/gin-gonic/gin/issues/346) and try the folling example:\n\n[embedmd]:# (examples/multiple-service/main.go go)\n```go\npackage main\n\nimport (\n\t\"log\"\n\t\"net/http\"\n\t\"time\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"golang.org/x/sync/errgroup\"\n)\n\nvar (\n\tg errgroup.Group\n)\n\nfunc router01() http.Handler {\n\te := gin.New()\n\te.Use(gin.Recovery())\n\te.GET(\"/\", func(c *gin.Context) {\n\t\tc.JSON(\n\t\t\thttp.StatusOK,\n\t\t\tgin.H{\n\t\t\t\t\"code\":  http.StatusOK,\n\t\t\t\t\"error\": \"Welcome server 01\",\n\t\t\t},\n\t\t)\n\t})\n\n\treturn e\n}\n\nfunc router02() http.Handler {\n\te := gin.New()\n\te.Use(gin.Recovery())\n\te.GET(\"/\", func(c *gin.Context) {\n\t\tc.JSON(\n\t\t\thttp.StatusOK,\n\t\t\tgin.H{\n\t\t\t\t\"code\":  http.StatusOK,\n\t\t\t\t\"error\": \"Welcome server 02\",\n\t\t\t},\n\t\t)\n\t})\n\n\treturn e\n}\n\nfunc main() {\n\tserver01 := \u0026http.Server{\n\t\tAddr:         \":8080\",\n\t\tHandler:      router01(),\n\t\tReadTimeout:  5 * time.Second,\n\t\tWriteTimeout: 10 * time.Second,\n\t}\n\n\tserver02 := \u0026http.Server{\n\t\tAddr:         \":8081\",\n\t\tHandler:      router02(),\n\t\tReadTimeout:  5 * time.Second,\n\t\tWriteTimeout: 10 * time.Second,\n\t}\n\n\tg.Go(func() error {\n\t\treturn server01.ListenAndServe()\n\t})\n\n\tg.Go(func() error {\n\t\treturn server02.ListenAndServe()\n\t})\n\n\tif err := g.Wait(); err != nil {\n\t\tlog.Fatal(err)\n\t}\n}\n```\n\n### Graceful restart or stop\n\nDo you want to graceful restart or stop your web server?\nThere are some ways this can be done.\n\nWe can use [fvbock/endless](https://github.com/fvbock/endless) to replace the default `ListenAndServe`. Refer issue [#296](https://github.com/gin-gonic/gin/issues/296) for more details.\n\n```go\nrouter := gin.Default()\nrouter.GET(\"/\", handler)\n// [...]\nendless.ListenAndServe(\":4242\", router)\n```\n\nAn alternative to endless:\n\n* [manners](https://github.com/braintree/manners): A polite Go HTTP server that shuts down gracefully.\n* [graceful](https://github.com/tylerb/graceful): Graceful is a Go package enabling graceful shutdown of an http.Handler server.\n* [grace](https://github.com/facebookgo/grace): Graceful restart \u0026 zero downtime deploy for Go servers.\n\nIf you are using Go 1.8, you may not need to use this library! Consider using http.Server's built-in [Shutdown()](https://golang.org/pkg/net/http/#Server.Shutdown) method for graceful shutdowns. See the full [graceful-shutdown](./examples/graceful-shutdown) example with gin.\n\n[embedmd]:# (examples/graceful-shutdown/graceful-shutdown/server.go go)\n```go\n// +build go1.8\n\npackage main\n\nimport (\n\t\"context\"\n\t\"log\"\n\t\"net/http\"\n\t\"os\"\n\t\"os/signal\"\n\t\"time\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\nfunc main() {\n\trouter := gin.Default()\n\trouter.GET(\"/\", func(c *gin.Context) {\n\t\ttime.Sleep(5 * time.Second)\n\t\tc.String(http.StatusOK, \"Welcome Gin Server\")\n\t})\n\n\tsrv := \u0026http.Server{\n\t\tAddr:    \":8080\",\n\t\tHandler: router,\n\t}\n\n\tgo func() {\n\t\t// service connections\n\t\tif err := srv.ListenAndServe(); err != nil {\n\t\t\tlog.Printf(\"listen: %s\\n\", err)\n\t\t}\n\t}()\n\n\t// Wait for interrupt signal to gracefully shutdown the server with\n\t// a timeout of 5 seconds.\n\tquit := make(chan os.Signal)\n\tsignal.Notify(quit, os.Interrupt)\n\t\u003c-quit\n\tlog.Println(\"Shutdown Server ...\")\n\n\tctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)\n\tdefer cancel()\n\tif err := srv.Shutdown(ctx); err != nil {\n\t\tlog.Fatal(\"Server Shutdown:\", err)\n\t}\n\tlog.Println(\"Server exiting\")\n}\n```\n\n## Users  [![Sourcegraph](https://sourcegraph.com/github.com/gin-gonic/gin/-/badge.svg)](https://sourcegraph.com/github.com/gin-gonic/gin?badge)\n\nAwesome project lists using [Gin](https://github.com/gin-gonic/gin) web framework.\n\n* [drone](https://github.com/drone/drone): Drone is a Continuous Delivery platform built on Docker, written in Go\n* [gorush](https://github.com/appleboy/gorush): A push notification server written in Go.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdanielkov%2Fgin","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdanielkov%2Fgin","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdanielkov%2Fgin/lists"}