From 4a23c4f7b9ced6b8e4476f2e021a61165153b71d Mon Sep 17 00:00:00 2001 From: Sai Date: Mon, 11 Mar 2019 11:52:47 +0900 Subject: [PATCH 01/14] fix #1804 which is caused by calling middleware twice. (#1805) Fix: https://github.com/gin-gonic/gin/issues/1804 `allNoRoute` contains middlewares such as `gin.Logger`, `gin.Recovery`, so on. The correct code is to use `noRoute`. cc: @MetalBreaker --- routergroup.go | 2 +- routes_test.go | 22 +++++++++++++++++++++- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/routergroup.go b/routergroup.go index 297d3574..a1e6c928 100644 --- a/routergroup.go +++ b/routergroup.go @@ -195,7 +195,7 @@ func (group *RouterGroup) createStaticHandler(relativePath string, fs http.FileS // Check if file exists and/or if we have permission to access it if _, err := fs.Open(file); err != nil { c.Writer.WriteHeader(http.StatusNotFound) - c.handlers = group.engine.allNoRoute + c.handlers = group.engine.noRoute // Reset index c.index = -1 return diff --git a/routes_test.go b/routes_test.go index a842704f..de363a8c 100644 --- a/routes_test.go +++ b/routes_test.go @@ -429,7 +429,6 @@ func TestRouterNotFound(t *testing.T) { func TestRouterStaticFSNotFound(t *testing.T) { router := New() - router.StaticFS("/", http.FileSystem(http.Dir("/thisreallydoesntexist/"))) router.NoRoute(func(c *Context) { c.String(404, "non existent") @@ -452,6 +451,27 @@ func TestRouterStaticFSFileNotFound(t *testing.T) { }) } +// Reproduction test for the bug of issue #1805 +func TestMiddlewareCalledOnceByRouterStaticFSNotFound(t *testing.T) { + router := New() + + // Middleware must be called just only once by per request. + middlewareCalledNum := 0 + router.Use(func(c *Context) { + middlewareCalledNum += 1 + }) + + router.StaticFS("/", http.FileSystem(http.Dir("/thisreallydoesntexist/"))) + + // First access + performRequest(router, "GET", "/nonexistent") + assert.Equal(t, 1, middlewareCalledNum) + + // Second access + performRequest(router, "HEAD", "/nonexistent") + assert.Equal(t, 2, middlewareCalledNum) +} + func TestRouteRawPath(t *testing.T) { route := New() route.UseRawPath = true From e5261480fde106ac5385f00fb00f9bc6b4035485 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Tue, 12 Mar 2019 14:01:12 +0800 Subject: [PATCH 02/14] chore(readme.md): fix invalid link (#1807) --- README.md | 2 +- testdata/assets/console.png | Bin 0 -> 59545 bytes 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 testdata/assets/console.png diff --git a/README.md b/README.md index 28ebd740..df5302e1 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ Gin 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. -![Gin console logger](https://gin-gonic.github.io/gin/other/console.png) +![Gin console logger](testdata/assets/console.png) ## Contents diff --git a/testdata/assets/console.png b/testdata/assets/console.png new file mode 100644 index 0000000000000000000000000000000000000000..7a695718fa31f9b1b42cfa547c16da394378aeae GIT binary patch literal 59545 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 literal 0 HcmV?d00001 From cab0749b4f3fadc8e9a23a5daf7083d3597fdbfc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Thu, 14 Mar 2019 13:23:35 +0800 Subject: [PATCH 03/14] chore: update readme (#1793) * update readme * add multi-language version doc link * add multi-language version doc link * update readme * update * update readme * update readme * update readme --- README.md | 2011 +---------------------------------------------------- 1 file changed, 37 insertions(+), 1974 deletions(-) diff --git a/README.md b/README.md index df5302e1..f9c10b22 100644 --- a/README.md +++ b/README.md @@ -13,123 +13,35 @@ Gin 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. -![Gin console logger](testdata/assets/console.png) +**The key features of Gin are:** -## Contents +- Zero allocation router +- Fast +- Middleware support +- Crash-free +- JSON validation +- Routes grouping +- Error management +- Rendering built-in +- Extendable -- [Installation](#installation) -- [Prerequisite](#prerequisite) -- [Quick start](#quick-start) -- [Benchmarks](#benchmarks) -- [Gin v1.stable](#gin-v1-stable) -- [Build with jsoniter](#build-with-jsoniter) -- [API Examples](#api-examples) - - [Using GET,POST,PUT,PATCH,DELETE and OPTIONS](#using-get-post-put-patch-delete-and-options) - - [Parameters in path](#parameters-in-path) - - [Querystring parameters](#querystring-parameters) - - [Multipart/Urlencoded Form](#multiparturlencoded-form) - - [Another example: query + post form](#another-example-query--post-form) - - [Map as querystring or postform parameters](#map-as-querystring-or-postform-parameters) - - [Upload files](#upload-files) - - [Grouping routes](#grouping-routes) - - [Blank Gin without middleware by default](#blank-gin-without-middleware-by-default) - - [Using middleware](#using-middleware) - - [How to write log file](#how-to-write-log-file) - - [Custom Log Format](#custom-log-format) - - [Model binding and validation](#model-binding-and-validation) - - [Custom Validators](#custom-validators) - - [Only Bind Query String](#only-bind-query-string) - - [Bind Query String or Post Data](#bind-query-string-or-post-data) - - [Bind Uri](#bind-uri) - - [Bind HTML checkboxes](#bind-html-checkboxes) - - [Multipart/Urlencoded binding](#multiparturlencoded-binding) - - [XML, JSON, YAML and ProtoBuf rendering](#xml-json-yaml-and-protobuf-rendering) - - [JSONP rendering](#jsonp) - - [Serving static files](#serving-static-files) - - [Serving data from reader](#serving-data-from-reader) - - [HTML rendering](#html-rendering) - - [Multitemplate](#multitemplate) - - [Redirects](#redirects) - - [Custom Middleware](#custom-middleware) - - [Using BasicAuth() middleware](#using-basicauth-middleware) - - [Goroutines inside a middleware](#goroutines-inside-a-middleware) - - [Custom HTTP configuration](#custom-http-configuration) - - [Support Let's Encrypt](#support-lets-encrypt) - - [Run multiple service using Gin](#run-multiple-service-using-gin) - - [Graceful restart or stop](#graceful-restart-or-stop) - - [Build a single binary with templates](#build-a-single-binary-with-templates) - - [Bind form-data request with custom struct](#bind-form-data-request-with-custom-struct) - - [Try to bind body into different structs](#try-to-bind-body-into-different-structs) - - [http2 server push](#http2-server-push) - - [Define format for the log of routes](#define-format-for-the-log-of-routes) - - [Set and get a cookie](#set-and-get-a-cookie) -- [Testing](#testing) -- [Users](#users) +For more feature details, please see the [Gin website introduction](https://gin-gonic.com/docs/introduction/). -## Installation +## Getting started -To install Gin package, you need to install Go and set your Go workspace first. +### Getting Gin -1. Download and install it: +The first need [Go](https://golang.org/) installed (version 1.6+ is required), then you can use the below Go command to install Gin. ```sh $ go get -u github.com/gin-gonic/gin ``` -2. Import it in your code: +For more installation guides such as vendor tool, please check out [Gin quickstart](https://gin-gonic.com/docs/quickstart/). -```go -import "github.com/gin-gonic/gin" -``` +### Running Gin -3. (Optional) Import `net/http`. This is required for example if using constants such as `http.StatusOK`. - -```go -import "net/http" -``` - -### Use a vendor tool like [Govendor](https://github.com/kardianos/govendor) - -1. `go get` govendor - -```sh -$ go get github.com/kardianos/govendor -``` -2. Create your project folder and `cd` inside - -```sh -$ mkdir -p $GOPATH/src/github.com/myusername/project && cd "$_" -``` - -3. Vendor init your project and add gin - -```sh -$ govendor init -$ govendor fetch github.com/gin-gonic/gin@v1.3 -``` - -4. Copy a starting template inside your project - -```sh -$ curl https://raw.githubusercontent.com/gin-gonic/examples/master/basic/main.go > main.go -``` - -5. Run your project - -```sh -$ go run main.go -``` - -## Prerequisite - -Now Gin requires Go 1.6 or later and Go 1.7 will be required soon. - -## Quick start - -```sh -# assume the following codes in example.go file -$ cat example.go -``` +First you need to import Gin package for using Gin, one simplest example likes the follow `example.go`: ```go package main @@ -147,6 +59,8 @@ func main() { } ``` +And use the Go command to run the demo: + ``` # run example.go and visit 0.0.0.0:8080/ping on browser $ go run example.go @@ -154,9 +68,7 @@ $ go run example.go ## Benchmarks -Gin uses a custom version of [HttpRouter](https://github.com/julienschmidt/httprouter) - -[See all benchmarks](/BENCHMARKS.md) +Please see all benchmarks details from [Gin website](https://gin-gonic.com/docs/benchmarks/). Benchmark name | (1) | (2) | (3) | (4) --------------------------------------------|-----------:|------------:|-----------:|---------: @@ -193,1879 +105,30 @@ BenchmarkVulcan_GithubAll | 5000 | 394253 | 19894 - (3): Heap Memory (B/op), lower is better - (4): Average Allocations per Repetition (allocs/op), lower is better -## Gin v1. stable +## Middlewares -- [x] Zero allocation router. -- [x] Still the fastest http router and framework. From routing to writing. -- [x] Complete suite of unit tests -- [x] Battle tested -- [x] API frozen, new releases will not break your code. +You can find many useful Gin middlewares at [gin-contrib](https://github.com/gin-contrib). -## Build with [jsoniter](https://github.com/json-iterator/go) +## Documentation -Gin uses `encoding/json` as default json package but you can change to [jsoniter](https://github.com/json-iterator/go) by build from other tags. +All documentation is available on the Gin website. -```sh -$ go build -tags=jsoniter . -``` +- [English](https://gin-gonic.com/docs/) +- [简体中文](https://gin-gonic.com/zh-cn/docs/) +- [繁體中文](https://gin-gonic.com/zh-tw/docs/) +- [にほんご](https://gin-gonic.com/ja/docs/) -## API Examples +## Examples -You can find a number of ready-to-run examples at [Gin examples repository](https://github.com/gin-gonic/examples). - -### Using GET, POST, PUT, PATCH, DELETE and OPTIONS - -```go -func main() { - // Creates a gin router with default middleware: - // logger and recovery (crash-free) middleware - router := gin.Default() - - router.GET("/someGet", getting) - router.POST("/somePost", posting) - router.PUT("/somePut", putting) - router.DELETE("/someDelete", deleting) - router.PATCH("/somePatch", patching) - router.HEAD("/someHead", head) - router.OPTIONS("/someOptions", options) - - // By default it serves on :8080 unless a - // PORT environment variable was defined. - router.Run() - // router.Run(":3000") for a hard coded port -} -``` - -### Parameters in path - -```go -func main() { - router := gin.Default() - - // This handler will match /user/john but will not match /user/ or /user - router.GET("/user/:name", func(c *gin.Context) { - name := c.Param("name") - c.String(http.StatusOK, "Hello %s", name) - }) - - // However, this one will match /user/john/ and also /user/john/send - // If no other routers match /user/john, it will redirect to /user/john/ - router.GET("/user/:name/*action", func(c *gin.Context) { - name := c.Param("name") - action := c.Param("action") - message := name + " is " + action - c.String(http.StatusOK, message) - }) - - router.Run(":8080") -} -``` - -### Querystring parameters - -```go -func main() { - router := gin.Default() - - // Query string parameters are parsed using the existing underlying request object. - // The request responds to a url matching: /welcome?firstname=Jane&lastname=Doe - router.GET("/welcome", func(c *gin.Context) { - firstname := c.DefaultQuery("firstname", "Guest") - lastname := c.Query("lastname") // shortcut for c.Request.URL.Query().Get("lastname") - - c.String(http.StatusOK, "Hello %s %s", firstname, lastname) - }) - router.Run(":8080") -} -``` - -### Multipart/Urlencoded Form - -```go -func main() { - router := gin.Default() - - router.POST("/form_post", func(c *gin.Context) { - message := c.PostForm("message") - nick := c.DefaultPostForm("nick", "anonymous") - - c.JSON(200, gin.H{ - "status": "posted", - "message": message, - "nick": nick, - }) - }) - router.Run(":8080") -} -``` - -### Another example: query + post form - -``` -POST /post?id=1234&page=1 HTTP/1.1 -Content-Type: application/x-www-form-urlencoded - -name=manu&message=this_is_great -``` - -```go -func main() { - router := gin.Default() - - router.POST("/post", func(c *gin.Context) { - - id := c.Query("id") - page := c.DefaultQuery("page", "0") - name := c.PostForm("name") - message := c.PostForm("message") - - fmt.Printf("id: %s; page: %s; name: %s; message: %s", id, page, name, message) - }) - router.Run(":8080") -} -``` - -``` -id: 1234; page: 1; name: manu; message: this_is_great -``` - -### Map as querystring or postform parameters - -``` -POST /post?ids[a]=1234&ids[b]=hello HTTP/1.1 -Content-Type: application/x-www-form-urlencoded - -names[first]=thinkerou&names[second]=tianou -``` - -```go -func main() { - router := gin.Default() - - router.POST("/post", func(c *gin.Context) { - - ids := c.QueryMap("ids") - names := c.PostFormMap("names") - - fmt.Printf("ids: %v; names: %v", ids, names) - }) - router.Run(":8080") -} -``` - -``` -ids: map[b:hello a:1234], names: map[second:tianou first:thinkerou] -``` - -### Upload files - -#### Single file - -References 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). - -`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) - -> 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. - -```go -func main() { - router := gin.Default() - // Set a lower memory limit for multipart forms (default is 32 MiB) - // router.MaxMultipartMemory = 8 << 20 // 8 MiB - router.POST("/upload", func(c *gin.Context) { - // single file - file, _ := c.FormFile("file") - log.Println(file.Filename) - - // Upload the file to specific dst. - // c.SaveUploadedFile(file, dst) - - c.String(http.StatusOK, fmt.Sprintf("'%s' uploaded!", file.Filename)) - }) - router.Run(":8080") -} -``` - -How to `curl`: - -```bash -curl -X POST http://localhost:8080/upload \ - -F "file=@/Users/appleboy/test.zip" \ - -H "Content-Type: multipart/form-data" -``` - -#### Multiple files - -See the detail [example code](https://github.com/gin-gonic/examples/tree/master/upload-file/multiple). - -```go -func main() { - router := gin.Default() - // Set a lower memory limit for multipart forms (default is 32 MiB) - // router.MaxMultipartMemory = 8 << 20 // 8 MiB - router.POST("/upload", func(c *gin.Context) { - // Multipart form - form, _ := c.MultipartForm() - files := form.File["upload[]"] - - for _, file := range files { - log.Println(file.Filename) - - // Upload the file to specific dst. - // c.SaveUploadedFile(file, dst) - } - c.String(http.StatusOK, fmt.Sprintf("%d files uploaded!", len(files))) - }) - router.Run(":8080") -} -``` - -How to `curl`: - -```bash -curl -X POST http://localhost:8080/upload \ - -F "upload[]=@/Users/appleboy/test1.zip" \ - -F "upload[]=@/Users/appleboy/test2.zip" \ - -H "Content-Type: multipart/form-data" -``` - -### Grouping routes - -```go -func main() { - router := gin.Default() - - // Simple group: v1 - v1 := router.Group("/v1") - { - v1.POST("/login", loginEndpoint) - v1.POST("/submit", submitEndpoint) - v1.POST("/read", readEndpoint) - } - - // Simple group: v2 - v2 := router.Group("/v2") - { - v2.POST("/login", loginEndpoint) - v2.POST("/submit", submitEndpoint) - v2.POST("/read", readEndpoint) - } - - router.Run(":8080") -} -``` - -### Blank Gin without middleware by default - -Use - -```go -r := gin.New() -``` - -instead of - -```go -// Default With the Logger and Recovery middleware already attached -r := gin.Default() -``` - - -### Using middleware -```go -func main() { - // Creates a router without any middleware by default - r := gin.New() - - // Global middleware - // Logger middleware will write the logs to gin.DefaultWriter even if you set with GIN_MODE=release. - // By default gin.DefaultWriter = os.Stdout - r.Use(gin.Logger()) - - // Recovery middleware recovers from any panics and writes a 500 if there was one. - r.Use(gin.Recovery()) - - // Per route middleware, you can add as many as you desire. - r.GET("/benchmark", MyBenchLogger(), benchEndpoint) - - // Authorization group - // authorized := r.Group("/", AuthRequired()) - // exactly the same as: - authorized := r.Group("/") - // per group middleware! in this case we use the custom created - // AuthRequired() middleware just in the "authorized" group. - authorized.Use(AuthRequired()) - { - authorized.POST("/login", loginEndpoint) - authorized.POST("/submit", submitEndpoint) - authorized.POST("/read", readEndpoint) - - // nested group - testing := authorized.Group("testing") - testing.GET("/analytics", analyticsEndpoint) - } - - // Listen and serve on 0.0.0.0:8080 - r.Run(":8080") -} -``` - -### How to write log file -```go -func main() { - // Disable Console Color, you don't need console color when writing the logs to file. - gin.DisableConsoleColor() - - // Logging to a file. - f, _ := os.Create("gin.log") - gin.DefaultWriter = io.MultiWriter(f) - - // Use the following code if you need to write the logs to file and console at the same time. - // gin.DefaultWriter = io.MultiWriter(f, os.Stdout) - - router := gin.Default() - router.GET("/ping", func(c *gin.Context) { - c.String(200, "pong") - }) - -    router.Run(":8080") -} -``` - -### Custom Log Format -```go -func main() { - router := gin.New() - - // LoggerWithFormatter middleware will write the logs to gin.DefaultWriter - // By default gin.DefaultWriter = os.Stdout - router.Use(gin.LoggerWithFormatter(func(param gin.LogFormatterParams) string { - - // your custom format - return fmt.Sprintf("%s - [%s] \"%s %s %s %d %s \"%s\" %s\"\n", - param.ClientIP, - param.TimeStamp.Format(time.RFC1123), - param.Method, - param.Path, - param.Request.Proto, - param.StatusCode, - param.Latency, - param.Request.UserAgent(), - param.ErrorMessage, - ) - })) - router.Use(gin.Recovery()) - - router.GET("/ping", func(c *gin.Context) { - c.String(200, "pong") - }) - - router.Run(":8080") -} -``` - -**Sample Output** -``` -::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" " -``` - -### Controlling Log output coloring - -By default, logs output on console should be colorized depending on the detected TTY. - -Never colorize logs: - -```go -func main() { - // Disable log's color - gin.DisableConsoleColor() - - // Creates a gin router with default middleware: - // logger and recovery (crash-free) middleware - router := gin.Default() - - router.GET("/ping", func(c *gin.Context) { - c.String(200, "pong") - }) - - router.Run(":8080") -} -``` - -Always colorize logs: - -```go -func main() { - // Force log's color - gin.ForceConsoleColor() - - // Creates a gin router with default middleware: - // logger and recovery (crash-free) middleware - router := gin.Default() - - router.GET("/ping", func(c *gin.Context) { - c.String(200, "pong") - }) - - router.Run(":8080") -} -``` - -### Model binding and validation - -To bind a request body into a type, use model binding. We currently support binding of JSON, XML, YAML and standard form values (foo=bar&boo=baz). - -Gin 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). - -Note that you need to set the corresponding binding tag on all fields you want to bind. For example, when binding from JSON, set `json:"fieldname"`. - -Also, Gin provides two sets of methods for binding: -- **Type** - Must bind - - **Methods** - `Bind`, `BindJSON`, `BindXML`, `BindQuery`, `BindYAML` - - **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. -- **Type** - Should bind - - **Methods** - `ShouldBind`, `ShouldBindJSON`, `ShouldBindXML`, `ShouldBindQuery`, `ShouldBindYAML` - - **Behavior** - These methods use `ShouldBindWith` under the hood. If there is a binding error, the error is returned and it is the developer's responsibility to handle the request and error appropriately. - -When using the Bind-method, Gin tries to infer the binder depending on the Content-Type header. If you are sure what you are binding, you can use `MustBindWith` or `ShouldBindWith`. - -You can also specify that specific fields are required. If a field is decorated with `binding:"required"` and has a empty value when binding, an error will be returned. - -```go -// Binding from JSON -type Login struct { - User string `form:"user" json:"user" xml:"user" binding:"required"` - Password string `form:"password" json:"password" xml:"password" binding:"required"` -} - -func main() { - router := gin.Default() - - // Example for binding JSON ({"user": "manu", "password": "123"}) - router.POST("/loginJSON", func(c *gin.Context) { - var json Login - if err := c.ShouldBindJSON(&json); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) - return - } - - if json.User != "manu" || json.Password != "123" { - c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"}) - return - } - - c.JSON(http.StatusOK, gin.H{"status": "you are logged in"}) - }) - - // Example for binding XML ( - // - // - // user - // 123 - // ) - router.POST("/loginXML", func(c *gin.Context) { - var xml Login - if err := c.ShouldBindXML(&xml); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) - return - } - - if xml.User != "manu" || xml.Password != "123" { - c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"}) - return - } - - c.JSON(http.StatusOK, gin.H{"status": "you are logged in"}) - }) - - // Example for binding a HTML form (user=manu&password=123) - router.POST("/loginForm", func(c *gin.Context) { - var form Login - // This will infer what binder to use depending on the content-type header. - if err := c.ShouldBind(&form); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) - return - } - - if form.User != "manu" || form.Password != "123" { - c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"}) - return - } - - c.JSON(http.StatusOK, gin.H{"status": "you are logged in"}) - }) - - // Listen and serve on 0.0.0.0:8080 - router.Run(":8080") -} -``` - -**Sample request** -```shell -$ curl -v -X POST \ - http://localhost:8080/loginJSON \ - -H 'content-type: application/json' \ - -d '{ "user": "manu" }' -> POST /loginJSON HTTP/1.1 -> Host: localhost:8080 -> User-Agent: curl/7.51.0 -> Accept: */* -> content-type: application/json -> Content-Length: 18 -> -* upload completely sent off: 18 out of 18 bytes -< HTTP/1.1 400 Bad Request -< Content-Type: application/json; charset=utf-8 -< Date: Fri, 04 Aug 2017 03:51:31 GMT -< Content-Length: 100 -< -{"error":"Key: 'Login.Password' Error:Field validation for 'Password' failed on the 'required' tag"} -``` - -**Skip validate** - -When running the above example using the above the `curl` command, it returns error. Because the example use `binding:"required"` for `Password`. If use `binding:"-"` for `Password`, then it will not return error when running the above example again. - -### Custom Validators - -It is also possible to register custom validators. See the [example code](https://github.com/gin-gonic/examples/tree/master/custom-validation/server.go). - -```go -package main - -import ( - "net/http" - "reflect" - "time" - - "github.com/gin-gonic/gin" - "github.com/gin-gonic/gin/binding" - "gopkg.in/go-playground/validator.v8" -) - -// Booking contains binded and validated data. -type Booking struct { - CheckIn time.Time `form:"check_in" binding:"required,bookabledate" time_format:"2006-01-02"` - CheckOut time.Time `form:"check_out" binding:"required,gtfield=CheckIn" time_format:"2006-01-02"` -} - -func bookableDate( - v *validator.Validate, topStruct reflect.Value, currentStructOrField reflect.Value, - field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string, -) bool { - if date, ok := field.Interface().(time.Time); ok { - today := time.Now() - if today.Year() > date.Year() || today.YearDay() > date.YearDay() { - return false - } - } - return true -} - -func main() { - route := gin.Default() - - if v, ok := binding.Validator.Engine().(*validator.Validate); ok { - v.RegisterValidation("bookabledate", bookableDate) - } - - route.GET("/bookable", getBookable) - route.Run(":8085") -} - -func getBookable(c *gin.Context) { - var b Booking - if err := c.ShouldBindWith(&b, binding.Query); err == nil { - c.JSON(http.StatusOK, gin.H{"message": "Booking dates are valid!"}) - } else { - c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) - } -} -``` - -```console -$ curl "localhost:8085/bookable?check_in=2018-04-16&check_out=2018-04-17" -{"message":"Booking dates are valid!"} - -$ curl "localhost:8085/bookable?check_in=2018-03-08&check_out=2018-03-09" -{"error":"Key: 'Booking.CheckIn' Error:Field validation for 'CheckIn' failed on the 'bookabledate' tag"} -``` - -[Struct level validations](https://github.com/go-playground/validator/releases/tag/v8.7) can also be registered this way. -See the [struct-lvl-validation example](https://github.com/gin-gonic/examples/tree/master/struct-lvl-validations) to learn more. - -### Only Bind Query String - -`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). - -```go -package main - -import ( - "log" - - "github.com/gin-gonic/gin" -) - -type Person struct { - Name string `form:"name"` - Address string `form:"address"` -} - -func main() { - route := gin.Default() - route.Any("/testing", startPage) - route.Run(":8085") -} - -func startPage(c *gin.Context) { - var person Person - if c.ShouldBindQuery(&person) == nil { - log.Println("====== Only Bind By Query String ======") - log.Println(person.Name) - log.Println(person.Address) - } - c.String(200, "Success") -} - -``` - -### Bind Query String or Post Data - -See the [detail information](https://github.com/gin-gonic/gin/issues/742#issuecomment-264681292). - -```go -package main - -import ( - "log" - "time" - - "github.com/gin-gonic/gin" -) - -type Person struct { - Name string `form:"name"` - Address string `form:"address"` - Birthday time.Time `form:"birthday" time_format:"2006-01-02" time_utc:"1"` -} - -func main() { - route := gin.Default() - route.GET("/testing", startPage) - route.Run(":8085") -} - -func startPage(c *gin.Context) { - var person Person - // If `GET`, only `Form` binding engine (`query`) used. - // If `POST`, first checks the `content-type` for `JSON` or `XML`, then uses `Form` (`form-data`). - // See more at https://github.com/gin-gonic/gin/blob/master/binding/binding.go#L48 - if c.ShouldBind(&person) == nil { - log.Println(person.Name) - log.Println(person.Address) - log.Println(person.Birthday) - } - - c.String(200, "Success") -} -``` - -Test it with: -```sh -$ curl -X GET "localhost:8085/testing?name=appleboy&address=xyz&birthday=1992-03-15" -``` - -### Bind Uri - -See the [detail information](https://github.com/gin-gonic/gin/issues/846). - -```go -package main - -import "github.com/gin-gonic/gin" - -type Person struct { - ID string `uri:"id" binding:"required,uuid"` - Name string `uri:"name" binding:"required"` -} - -func main() { - route := gin.Default() - route.GET("/:name/:id", func(c *gin.Context) { - var person Person - if err := c.ShouldBindUri(&person); err != nil { - c.JSON(400, gin.H{"msg": err}) - return - } - c.JSON(200, gin.H{"name": person.Name, "uuid": person.ID}) - }) - route.Run(":8088") -} -``` - -Test it with: -```sh -$ curl -v localhost:8088/thinkerou/987fbc97-4bed-5078-9f07-9141ba07c9f3 -$ curl -v localhost:8088/thinkerou/not-uuid -``` - -### Bind HTML checkboxes - -See the [detail information](https://github.com/gin-gonic/gin/issues/129#issuecomment-124260092) - -main.go - -```go -... - -type myForm struct { - Colors []string `form:"colors[]"` -} - -... - -func formHandler(c *gin.Context) { - var fakeForm myForm - c.ShouldBind(&fakeForm) - c.JSON(200, gin.H{"color": fakeForm.Colors}) -} - -... - -``` - -form.html - -```html -
-

Check some colors

- - - - - - - -
-``` - -result: - -``` -{"color":["red","green","blue"]} -``` - -### Multipart/Urlencoded binding - -```go -package main - -import ( - "github.com/gin-gonic/gin" -) - -type LoginForm struct { - User string `form:"user" binding:"required"` - Password string `form:"password" binding:"required"` -} - -func main() { - router := gin.Default() - router.POST("/login", func(c *gin.Context) { - // you can bind multipart form with explicit binding declaration: - // c.ShouldBindWith(&form, binding.Form) - // or you can simply use autobinding with ShouldBind method: - var form LoginForm - // in this case proper binding will be automatically selected - if c.ShouldBind(&form) == nil { - if form.User == "user" && form.Password == "password" { - c.JSON(200, gin.H{"status": "you are logged in"}) - } else { - c.JSON(401, gin.H{"status": "unauthorized"}) - } - } - }) - router.Run(":8080") -} -``` - -Test it with: -```sh -$ curl -v --form user=user --form password=password http://localhost:8080/login -``` - -### XML, JSON, YAML and ProtoBuf rendering - -```go -func main() { - r := gin.Default() - - // gin.H is a shortcut for map[string]interface{} - r.GET("/someJSON", func(c *gin.Context) { - c.JSON(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK}) - }) - - r.GET("/moreJSON", func(c *gin.Context) { - // You also can use a struct - var msg struct { - Name string `json:"user"` - Message string - Number int - } - msg.Name = "Lena" - msg.Message = "hey" - msg.Number = 123 - // Note that msg.Name becomes "user" in the JSON - // Will output : {"user": "Lena", "Message": "hey", "Number": 123} - c.JSON(http.StatusOK, msg) - }) - - r.GET("/someXML", func(c *gin.Context) { - c.XML(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK}) - }) - - r.GET("/someYAML", func(c *gin.Context) { - c.YAML(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK}) - }) - - r.GET("/someProtoBuf", func(c *gin.Context) { - reps := []int64{int64(1), int64(2)} - label := "test" - // The specific definition of protobuf is written in the testdata/protoexample file. - data := &protoexample.Test{ - Label: &label, - Reps: reps, - } - // Note that data becomes binary data in the response - // Will output protoexample.Test protobuf serialized data - c.ProtoBuf(http.StatusOK, data) - }) - - // Listen and serve on 0.0.0.0:8080 - r.Run(":8080") -} -``` - -#### SecureJSON - -Using SecureJSON to prevent json hijacking. Default prepends `"while(1),"` to response body if the given struct is array values. - -```go -func main() { - r := gin.Default() - - // You can also use your own secure json prefix - // r.SecureJsonPrefix(")]}',\n") - - r.GET("/someJSON", func(c *gin.Context) { - names := []string{"lena", "austin", "foo"} - - // Will output : while(1);["lena","austin","foo"] - c.SecureJSON(http.StatusOK, names) - }) - - // Listen and serve on 0.0.0.0:8080 - r.Run(":8080") -} -``` -#### JSONP - -Using JSONP to request data from a server in a different domain. Add callback to response body if the query parameter callback exists. - -```go -func main() { - r := gin.Default() - - r.GET("/JSONP?callback=x", func(c *gin.Context) { - data := map[string]interface{}{ - "foo": "bar", - } - - //callback is x - // Will output : x({\"foo\":\"bar\"}) - c.JSONP(http.StatusOK, data) - }) - - // Listen and serve on 0.0.0.0:8080 - r.Run(":8080") -} -``` - -#### AsciiJSON - -Using AsciiJSON to Generates ASCII-only JSON with escaped non-ASCII chracters. - -```go -func main() { - r := gin.Default() - - r.GET("/someJSON", func(c *gin.Context) { - data := map[string]interface{}{ - "lang": "GO语言", - "tag": "
", - } - - // will output : {"lang":"GO\u8bed\u8a00","tag":"\u003cbr\u003e"} - c.AsciiJSON(http.StatusOK, data) - }) - - // Listen and serve on 0.0.0.0:8080 - r.Run(":8080") -} -``` - -#### PureJSON - -Normally, JSON replaces special HTML characters with their unicode entities, e.g. `<` becomes `\u003c`. If you want to encode such characters literally, you can use PureJSON instead. -This feature is unavailable in Go 1.6 and lower. - -```go -func main() { - r := gin.Default() - - // Serves unicode entities - r.GET("/json", func(c *gin.Context) { - c.JSON(200, gin.H{ - "html": "Hello, world!", - }) - }) - - // Serves literal characters - r.GET("/purejson", func(c *gin.Context) { - c.PureJSON(200, gin.H{ - "html": "Hello, world!", - }) - }) - - // listen and serve on 0.0.0.0:8080 - r.Run(":8080") -} -``` - -### Serving static files - -```go -func main() { - router := gin.Default() - router.Static("/assets", "./assets") - router.StaticFS("/more_static", http.Dir("my_file_system")) - router.StaticFile("/favicon.ico", "./resources/favicon.ico") - - // Listen and serve on 0.0.0.0:8080 - router.Run(":8080") -} -``` - -### Serving data from reader - -```go -func main() { - router := gin.Default() - router.GET("/someDataFromReader", func(c *gin.Context) { - response, err := http.Get("https://raw.githubusercontent.com/gin-gonic/logo/master/color.png") - if err != nil || response.StatusCode != http.StatusOK { - c.Status(http.StatusServiceUnavailable) - return - } - - reader := response.Body - contentLength := response.ContentLength - contentType := response.Header.Get("Content-Type") - - extraHeaders := map[string]string{ - "Content-Disposition": `attachment; filename="gopher.png"`, - } - - c.DataFromReader(http.StatusOK, contentLength, contentType, reader, extraHeaders) - }) - router.Run(":8080") -} -``` - -### HTML rendering - -Using LoadHTMLGlob() or LoadHTMLFiles() - -```go -func main() { - router := gin.Default() - router.LoadHTMLGlob("templates/*") - //router.LoadHTMLFiles("templates/template1.html", "templates/template2.html") - router.GET("/index", func(c *gin.Context) { - c.HTML(http.StatusOK, "index.tmpl", gin.H{ - "title": "Main website", - }) - }) - router.Run(":8080") -} -``` - -templates/index.tmpl - -```html - -

- {{ .title }} -

- -``` - -Using templates with same name in different directories - -```go -func main() { - router := gin.Default() - router.LoadHTMLGlob("templates/**/*") - router.GET("/posts/index", func(c *gin.Context) { - c.HTML(http.StatusOK, "posts/index.tmpl", gin.H{ - "title": "Posts", - }) - }) - router.GET("/users/index", func(c *gin.Context) { - c.HTML(http.StatusOK, "users/index.tmpl", gin.H{ - "title": "Users", - }) - }) - router.Run(":8080") -} -``` - -templates/posts/index.tmpl - -```html -{{ define "posts/index.tmpl" }} -

- {{ .title }} -

-

Using posts/index.tmpl

- -{{ end }} -``` - -templates/users/index.tmpl - -```html -{{ define "users/index.tmpl" }} -

- {{ .title }} -

-

Using users/index.tmpl

- -{{ end }} -``` - -#### Custom Template renderer - -You can also use your own html template render - -```go -import "html/template" - -func main() { - router := gin.Default() - html := template.Must(template.ParseFiles("file1", "file2")) - router.SetHTMLTemplate(html) - router.Run(":8080") -} -``` - -#### Custom Delimiters - -You may use custom delims - -```go - r := gin.Default() - r.Delims("{[{", "}]}") - r.LoadHTMLGlob("/path/to/templates") -``` - -#### Custom Template Funcs - -See the detail [example code](https://github.com/gin-gonic/examples/tree/master/template). - -main.go - -```go -import ( - "fmt" - "html/template" - "net/http" - "time" - - "github.com/gin-gonic/gin" -) - -func formatAsDate(t time.Time) string { - year, month, day := t.Date() - return fmt.Sprintf("%d%02d/%02d", year, month, day) -} - -func main() { - router := gin.Default() - router.Delims("{[{", "}]}") - router.SetFuncMap(template.FuncMap{ - "formatAsDate": formatAsDate, - }) - router.LoadHTMLFiles("./testdata/template/raw.tmpl") - - router.GET("/raw", func(c *gin.Context) { - c.HTML(http.StatusOK, "raw.tmpl", map[string]interface{}{ - "now": time.Date(2017, 07, 01, 0, 0, 0, 0, time.UTC), - }) - }) - - router.Run(":8080") -} - -``` - -raw.tmpl - -```html -Date: {[{.now | formatAsDate}]} -``` - -Result: -``` -Date: 2017/07/01 -``` - -### Multitemplate - -Gin 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`. - -### Redirects - -Issuing a HTTP redirect is easy. Both internal and external locations are supported. - -```go -r.GET("/test", func(c *gin.Context) { - c.Redirect(http.StatusMovedPermanently, "http://www.google.com/") -}) -``` - - -Issuing a Router redirect, use `HandleContext` like below. - -``` go -r.GET("/test", func(c *gin.Context) { - c.Request.URL.Path = "/test2" - r.HandleContext(c) -}) -r.GET("/test2", func(c *gin.Context) { - c.JSON(200, gin.H{"hello": "world"}) -}) -``` - - -### Custom Middleware - -```go -func Logger() gin.HandlerFunc { - return func(c *gin.Context) { - t := time.Now() - - // Set example variable - c.Set("example", "12345") - - // before request - - c.Next() - - // after request - latency := time.Since(t) - log.Print(latency) - - // access the status we are sending - status := c.Writer.Status() - log.Println(status) - } -} - -func main() { - r := gin.New() - r.Use(Logger()) - - r.GET("/test", func(c *gin.Context) { - example := c.MustGet("example").(string) - - // it would print: "12345" - log.Println(example) - }) - - // Listen and serve on 0.0.0.0:8080 - r.Run(":8080") -} -``` - -### Using BasicAuth() middleware - -```go -// simulate some private data -var secrets = gin.H{ - "foo": gin.H{"email": "foo@bar.com", "phone": "123433"}, - "austin": gin.H{"email": "austin@example.com", "phone": "666"}, - "lena": gin.H{"email": "lena@guapa.com", "phone": "523443"}, -} - -func main() { - r := gin.Default() - - // Group using gin.BasicAuth() middleware - // gin.Accounts is a shortcut for map[string]string - authorized := r.Group("/admin", gin.BasicAuth(gin.Accounts{ - "foo": "bar", - "austin": "1234", - "lena": "hello2", - "manu": "4321", - })) - - // /admin/secrets endpoint - // hit "localhost:8080/admin/secrets - authorized.GET("/secrets", func(c *gin.Context) { - // get user, it was set by the BasicAuth middleware - user := c.MustGet(gin.AuthUserKey).(string) - if secret, ok := secrets[user]; ok { - c.JSON(http.StatusOK, gin.H{"user": user, "secret": secret}) - } else { - c.JSON(http.StatusOK, gin.H{"user": user, "secret": "NO SECRET :("}) - } - }) - - // Listen and serve on 0.0.0.0:8080 - r.Run(":8080") -} -``` - -### Goroutines inside a middleware - -When starting new Goroutines inside a middleware or handler, you **SHOULD NOT** use the original context inside it, you have to use a read-only copy. - -```go -func main() { - r := gin.Default() - - r.GET("/long_async", func(c *gin.Context) { - // create copy to be used inside the goroutine - cCp := c.Copy() - go func() { - // simulate a long task with time.Sleep(). 5 seconds - time.Sleep(5 * time.Second) - - // note that you are using the copied context "cCp", IMPORTANT - log.Println("Done! in path " + cCp.Request.URL.Path) - }() - }) - - r.GET("/long_sync", func(c *gin.Context) { - // simulate a long task with time.Sleep(). 5 seconds - time.Sleep(5 * time.Second) - - // since we are NOT using a goroutine, we do not have to copy the context - log.Println("Done! in path " + c.Request.URL.Path) - }) - - // Listen and serve on 0.0.0.0:8080 - r.Run(":8080") -} -``` - -### Custom HTTP configuration - -Use `http.ListenAndServe()` directly, like this: - -```go -func main() { - router := gin.Default() - http.ListenAndServe(":8080", router) -} -``` -or - -```go -func main() { - router := gin.Default() - - s := &http.Server{ - Addr: ":8080", - Handler: router, - ReadTimeout: 10 * time.Second, - WriteTimeout: 10 * time.Second, - MaxHeaderBytes: 1 << 20, - } - s.ListenAndServe() -} -``` - -### Support Let's Encrypt - -example for 1-line LetsEncrypt HTTPS servers. - -```go -package main - -import ( - "log" - - "github.com/gin-gonic/autotls" - "github.com/gin-gonic/gin" -) - -func main() { - r := gin.Default() - - // Ping handler - r.GET("/ping", func(c *gin.Context) { - c.String(200, "pong") - }) - - log.Fatal(autotls.Run(r, "example1.com", "example2.com")) -} -``` - -example for custom autocert manager. - -```go -package main - -import ( - "log" - - "github.com/gin-gonic/autotls" - "github.com/gin-gonic/gin" - "golang.org/x/crypto/acme/autocert" -) - -func main() { - r := gin.Default() - - // Ping handler - r.GET("/ping", func(c *gin.Context) { - c.String(200, "pong") - }) - - m := autocert.Manager{ - Prompt: autocert.AcceptTOS, - HostPolicy: autocert.HostWhitelist("example1.com", "example2.com"), - Cache: autocert.DirCache("/var/www/.cache"), - } - - log.Fatal(autotls.RunWithManager(r, &m)) -} -``` - -### Run multiple service using Gin - -See the [question](https://github.com/gin-gonic/gin/issues/346) and try the following example: - -```go -package main - -import ( - "log" - "net/http" - "time" - - "github.com/gin-gonic/gin" - "golang.org/x/sync/errgroup" -) - -var ( - g errgroup.Group -) - -func router01() http.Handler { - e := gin.New() - e.Use(gin.Recovery()) - e.GET("/", func(c *gin.Context) { - c.JSON( - http.StatusOK, - gin.H{ - "code": http.StatusOK, - "error": "Welcome server 01", - }, - ) - }) - - return e -} - -func router02() http.Handler { - e := gin.New() - e.Use(gin.Recovery()) - e.GET("/", func(c *gin.Context) { - c.JSON( - http.StatusOK, - gin.H{ - "code": http.StatusOK, - "error": "Welcome server 02", - }, - ) - }) - - return e -} - -func main() { - server01 := &http.Server{ - Addr: ":8080", - Handler: router01(), - ReadTimeout: 5 * time.Second, - WriteTimeout: 10 * time.Second, - } - - server02 := &http.Server{ - Addr: ":8081", - Handler: router02(), - ReadTimeout: 5 * time.Second, - WriteTimeout: 10 * time.Second, - } - - g.Go(func() error { - return server01.ListenAndServe() - }) - - g.Go(func() error { - return server02.ListenAndServe() - }) - - if err := g.Wait(); err != nil { - log.Fatal(err) - } -} -``` - -### Graceful restart or stop - -Do you want to graceful restart or stop your web server? -There are some ways this can be done. - -We 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. - -```go -router := gin.Default() -router.GET("/", handler) -// [...] -endless.ListenAndServe(":4242", router) -``` - -An alternative to endless: - -* [manners](https://github.com/braintree/manners): A polite Go HTTP server that shuts down gracefully. -* [graceful](https://github.com/tylerb/graceful): Graceful is a Go package enabling graceful shutdown of an http.Handler server. -* [grace](https://github.com/facebookgo/grace): Graceful restart & zero downtime deploy for Go servers. - -If you are using Go 1.8, you may not need to use this library! Consider using http.Server's built-in [Shutdown()](https://golang.org/pkg/net/http/#Server.Shutdown) method for graceful shutdowns. See the full [graceful-shutdown](https://github.com/gin-gonic/examples/tree/master/graceful-shutdown) example with gin. - -```go -// +build go1.8 - -package main - -import ( - "context" - "log" - "net/http" - "os" - "os/signal" - "syscall" - "time" - - "github.com/gin-gonic/gin" -) - -func main() { - router := gin.Default() - router.GET("/", func(c *gin.Context) { - time.Sleep(5 * time.Second) - c.String(http.StatusOK, "Welcome Gin Server") - }) - - srv := &http.Server{ - Addr: ":8080", - Handler: router, - } - - go func() { - // service connections - if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { - log.Fatalf("listen: %s\n", err) - } - }() - - // Wait for interrupt signal to gracefully shutdown the server with - // a timeout of 5 seconds. - quit := make(chan os.Signal) - // kill (no param) default send syscanll.SIGTERM - // kill -2 is syscall.SIGINT - // kill -9 is syscall. SIGKILL but can"t be catch, so don't need add it - signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) - <-quit - log.Println("Shutdown Server ...") - - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() - if err := srv.Shutdown(ctx); err != nil { - log.Fatal("Server Shutdown:", err) - } - // catching ctx.Done(). timeout of 5 seconds. - select { - case <-ctx.Done(): - log.Println("timeout of 5 seconds.") - } - log.Println("Server exiting") -} -``` - -### Build a single binary with templates - -You can build a server into a single binary containing templates by using [go-assets][]. - -[go-assets]: https://github.com/jessevdk/go-assets - -```go -func main() { - r := gin.New() - - t, err := loadTemplate() - if err != nil { - panic(err) - } - r.SetHTMLTemplate(t) - - r.GET("/", func(c *gin.Context) { - c.HTML(http.StatusOK, "/html/index.tmpl",nil) - }) - r.Run(":8080") -} - -// loadTemplate loads templates embedded by go-assets-builder -func loadTemplate() (*template.Template, error) { - t := template.New("") - for name, file := range Assets.Files { - if file.IsDir() || !strings.HasSuffix(name, ".tmpl") { - continue - } - h, err := ioutil.ReadAll(file) - if err != nil { - return nil, err - } - t, err = t.New(name).Parse(string(h)) - if err != nil { - return nil, err - } - } - return t, nil -} -``` - -See a complete example in the `https://github.com/gin-gonic/examples/tree/master/assets-in-binary` directory. - -### Bind form-data request with custom struct - -The follow example using custom struct: - -```go -type StructA struct { - FieldA string `form:"field_a"` -} - -type StructB struct { - NestedStruct StructA - FieldB string `form:"field_b"` -} - -type StructC struct { - NestedStructPointer *StructA - FieldC string `form:"field_c"` -} - -type StructD struct { - NestedAnonyStruct struct { - FieldX string `form:"field_x"` - } - FieldD string `form:"field_d"` -} - -func GetDataB(c *gin.Context) { - var b StructB - c.Bind(&b) - c.JSON(200, gin.H{ - "a": b.NestedStruct, - "b": b.FieldB, - }) -} - -func GetDataC(c *gin.Context) { - var b StructC - c.Bind(&b) - c.JSON(200, gin.H{ - "a": b.NestedStructPointer, - "c": b.FieldC, - }) -} - -func GetDataD(c *gin.Context) { - var b StructD - c.Bind(&b) - c.JSON(200, gin.H{ - "x": b.NestedAnonyStruct, - "d": b.FieldD, - }) -} - -func main() { - r := gin.Default() - r.GET("/getb", GetDataB) - r.GET("/getc", GetDataC) - r.GET("/getd", GetDataD) - - r.Run() -} -``` - -Using the command `curl` command result: - -``` -$ curl "http://localhost:8080/getb?field_a=hello&field_b=world" -{"a":{"FieldA":"hello"},"b":"world"} -$ curl "http://localhost:8080/getc?field_a=hello&field_c=world" -{"a":{"FieldA":"hello"},"c":"world"} -$ curl "http://localhost:8080/getd?field_x=hello&field_d=world" -{"d":"world","x":{"FieldX":"hello"}} -``` - -### Try to bind body into different structs - -The normal methods for binding request body consumes `c.Request.Body` and they -cannot be called multiple times. - -```go -type formA struct { - Foo string `json:"foo" xml:"foo" binding:"required"` -} - -type formB struct { - Bar string `json:"bar" xml:"bar" binding:"required"` -} - -func SomeHandler(c *gin.Context) { - objA := formA{} - objB := formB{} - // This c.ShouldBind consumes c.Request.Body and it cannot be reused. - if errA := c.ShouldBind(&objA); errA == nil { - c.String(http.StatusOK, `the body should be formA`) - // Always an error is occurred by this because c.Request.Body is EOF now. - } else if errB := c.ShouldBind(&objB); errB == nil { - c.String(http.StatusOK, `the body should be formB`) - } else { - ... - } -} -``` - -For this, you can use `c.ShouldBindBodyWith`. - -```go -func SomeHandler(c *gin.Context) { - objA := formA{} - objB := formB{} - // This reads c.Request.Body and stores the result into the context. - if errA := c.ShouldBindBodyWith(&objA, binding.JSON); errA == nil { - c.String(http.StatusOK, `the body should be formA`) - // At this time, it reuses body stored in the context. - } else if errB := c.ShouldBindBodyWith(&objB, binding.JSON); errB == nil { - c.String(http.StatusOK, `the body should be formB JSON`) - // And it can accepts other formats - } else if errB2 := c.ShouldBindBodyWith(&objB, binding.XML); errB2 == nil { - c.String(http.StatusOK, `the body should be formB XML`) - } else { - ... - } -} -``` - -* `c.ShouldBindBodyWith` stores body into the context before binding. This has -a slight impact to performance, so you should not use this method if you are -enough to call binding at once. -* This feature is only needed for some formats -- `JSON`, `XML`, `MsgPack`, -`ProtoBuf`. For other formats, `Query`, `Form`, `FormPost`, `FormMultipart`, -can be called by `c.ShouldBind()` multiple times without any damage to -performance (See [#1341](https://github.com/gin-gonic/gin/pull/1341)). - -### http2 server push - -http.Pusher is supported only **go1.8+**. See the [golang blog](https://blog.golang.org/h2push) for detail information. - -```go -package main - -import ( - "html/template" - "log" - - "github.com/gin-gonic/gin" -) - -var html = template.Must(template.New("https").Parse(` - - - Https Test - - - -

Welcome, Ginner!

- - -`)) - -func main() { - r := gin.Default() - r.Static("/assets", "./assets") - r.SetHTMLTemplate(html) - - r.GET("/", func(c *gin.Context) { - if pusher := c.Writer.Pusher(); pusher != nil { - // use pusher.Push() to do server push - if err := pusher.Push("/assets/app.js", nil); err != nil { - log.Printf("Failed to push: %v", err) - } - } - c.HTML(200, "https", gin.H{ - "status": "success", - }) - }) - - // Listen and Server in https://127.0.0.1:8080 - r.RunTLS(":8080", "./testdata/server.pem", "./testdata/server.key") -} -``` - -### Define format for the log of routes - -The default log of routes is: -``` -[GIN-debug] POST /foo --> main.main.func1 (3 handlers) -[GIN-debug] GET /bar --> main.main.func2 (3 handlers) -[GIN-debug] GET /status --> main.main.func3 (3 handlers) -``` - -If you want to log this information in given format (e.g. JSON, key values or something else), then you can define this format with `gin.DebugPrintRouteFunc`. -In the example below, we log all routes with standard log package but you can use another log tools that suits of your needs. -```go -import ( - "log" - "net/http" - - "github.com/gin-gonic/gin" -) - -func main() { - r := gin.Default() - gin.DebugPrintRouteFunc = func(httpMethod, absolutePath, handlerName string, nuHandlers int) { - log.Printf("endpoint %v %v %v %v\n", httpMethod, absolutePath, handlerName, nuHandlers) - } - - r.POST("/foo", func(c *gin.Context) { - c.JSON(http.StatusOK, "foo") - }) - - r.GET("/bar", func(c *gin.Context) { - c.JSON(http.StatusOK, "bar") - }) - - r.GET("/status", func(c *gin.Context) { - c.JSON(http.StatusOK, "ok") - }) - - // Listen and Server in http://0.0.0.0:8080 - r.Run() -} -``` - -### Set and get a cookie - -```go -import ( - "fmt" - - "github.com/gin-gonic/gin" -) - -func main() { - - router := gin.Default() - - router.GET("/cookie", func(c *gin.Context) { - - cookie, err := c.Cookie("gin_cookie") - - if err != nil { - cookie = "NotSet" - c.SetCookie("gin_cookie", "test", 3600, "/", "localhost", false, true) - } - - fmt.Printf("Cookie value: %s \n", cookie) - }) - - router.Run() -} -``` - - -## Testing - -The `net/http/httptest` package is preferable way for HTTP testing. - -```go -package main - -func setupRouter() *gin.Engine { - r := gin.Default() - r.GET("/ping", func(c *gin.Context) { - c.String(200, "pong") - }) - return r -} - -func main() { - r := setupRouter() - r.Run(":8080") -} -``` - -Test for code example above: - -```go -package main - -import ( - "net/http" - "net/http/httptest" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestPingRoute(t *testing.T) { - router := setupRouter() - - w := httptest.NewRecorder() - req, _ := http.NewRequest("GET", "/ping", nil) - router.ServeHTTP(w, req) - - assert.Equal(t, 200, w.Code) - assert.Equal(t, "pong", w.Body.String()) -} -``` +A number of ready-to-run examples demonstrating various use cases of Gin on the [Gin examples](https://github.com/gin-gonic/examples) repository. ## Users -Awesome project lists using [Gin](https://github.com/gin-gonic/gin) web framework. +[Gin website](https://gin-gonic.com/docs/users/) lists some awesome projects made with Gin web framework. + +## Contributing + +Gin is the work of hundreds of contributors. We appreciate your help! + +Please see [CONTRIBUTING](CONTRIBUTING.md) for details on submitting patches and the contribution workflow. -* [gorush](https://github.com/appleboy/gorush): A push notification server written in Go. -* [fnproject](https://github.com/fnproject/fn): The container native, cloud agnostic serverless platform. -* [photoprism](https://github.com/photoprism/photoprism): Personal photo management powered by Go and Google TensorFlow. -* [krakend](https://github.com/devopsfaith/krakend): Ultra performant API Gateway with middlewares. -* [picfit](https://github.com/thoas/picfit): An image resizing server written in Go. From 483f828bce15ff82fdec7414c32ee9b3f017b5ca Mon Sep 17 00:00:00 2001 From: Dmitry Kutakov Date: Thu, 14 Mar 2019 08:34:56 +0300 Subject: [PATCH 04/14] add support arrays on mapping (#1797) * add support arrays on mapping * not allow default value on array mapping --- binding/binding_test.go | 28 ++++++++++++++++++++++++++++ binding/form_mapping.go | 23 ++++++++++++++++++++--- 2 files changed, 48 insertions(+), 3 deletions(-) diff --git a/binding/binding_test.go b/binding/binding_test.go index 5ae87957..b265af36 100644 --- a/binding/binding_test.go +++ b/binding/binding_test.go @@ -1430,3 +1430,31 @@ func TestBindingTimeDuration(t *testing.T) { err = Form.Bind(req, &s) assert.Error(t, err) } + +func TestBindingArray(t *testing.T) { + var s struct { + Nums [2]int `form:"nums,default=4"` + } + + // default + req := formPostRequest("", "") + err := Form.Bind(req, &s) + assert.Error(t, err) + assert.Equal(t, [2]int{0, 0}, s.Nums) + + // ok + req = formPostRequest("", "nums=3&nums=8") + err = Form.Bind(req, &s) + assert.NoError(t, err) + assert.Equal(t, [2]int{3, 8}, s.Nums) + + // not enough vals + req = formPostRequest("", "nums=3") + err = Form.Bind(req, &s) + assert.Error(t, err) + + // error + req = formPostRequest("", "nums=3&nums=wrong") + err = Form.Bind(req, &s) + assert.Error(t, err) +} diff --git a/binding/form_mapping.go b/binding/form_mapping.go index 87edfbb2..ba9d2c4f 100644 --- a/binding/form_mapping.go +++ b/binding/form_mapping.go @@ -6,6 +6,7 @@ package binding import ( "errors" + "fmt" "reflect" "strconv" "strings" @@ -118,6 +119,14 @@ func tryToSetValue(value reflect.Value, field reflect.StructField, form map[stri vs = []string{defaultValue} } return true, setSlice(vs, value, field) + case reflect.Array: + if !ok { + vs = []string{defaultValue} + } + if len(vs) != value.Len() { + return false, fmt.Errorf("%q is not valid value for %s", vs, value.Type().String()) + } + return true, setArray(vs, value, field) default: var val string if !ok { @@ -256,14 +265,22 @@ func setTimeField(val string, structField reflect.StructField, value reflect.Val return nil } -func setSlice(vals []string, value reflect.Value, field reflect.StructField) error { - slice := reflect.MakeSlice(value.Type(), len(vals), len(vals)) +func setArray(vals []string, value reflect.Value, field reflect.StructField) error { for i, s := range vals { - err := setWithProperType(s, slice.Index(i), field) + err := setWithProperType(s, value.Index(i), field) if err != nil { return err } } + return nil +} + +func setSlice(vals []string, value reflect.Value, field reflect.StructField) error { + slice := reflect.MakeSlice(value.Type(), len(vals), len(vals)) + err := setArray(vals, slice, field) + if err != nil { + return err + } value.Set(slice) return nil } From 242a2622c839bf883c415fd088d24fec8727fb23 Mon Sep 17 00:00:00 2001 From: Sai Date: Thu, 14 Mar 2019 17:26:51 +0900 Subject: [PATCH 05/14] Fix Japanese text hiragana -> kanji (#1812) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f9c10b22..b46c5637 100644 --- a/README.md +++ b/README.md @@ -116,7 +116,7 @@ All documentation is available on the Gin website. - [English](https://gin-gonic.com/docs/) - [简体中文](https://gin-gonic.com/zh-cn/docs/) - [繁體中文](https://gin-gonic.com/zh-tw/docs/) -- [にほんご](https://gin-gonic.com/ja/docs/) +- [日本語](https://gin-gonic.com/ja/docs/) ## Examples From 05b5c3ba7495fb3cd737cad8b35e62e0862ed1c2 Mon Sep 17 00:00:00 2001 From: David Zhang Date: Fri, 15 Mar 2019 15:39:34 +0800 Subject: [PATCH 06/14] Doc: fix gin example notice syntax (#1814) --- examples/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/README.md b/examples/README.md index b02deae4..bfebc6c0 100644 --- a/examples/README.md +++ b/examples/README.md @@ -1,3 +1,3 @@ # Gin Examples -⚠️ **NOTICE:** All gin examples has moved as alone repository to [here](https://github.com/gin-gonic/examples). +⚠️ **NOTICE:** All gin examples have been moved as standalone repository to [here](https://github.com/gin-gonic/examples). From bcf36ade9f763b875bd781ad26cdb0549349c5f8 Mon Sep 17 00:00:00 2001 From: sekky0905 <20237968+sekky0905@users.noreply.github.com> Date: Sat, 16 Mar 2019 17:09:10 +0900 Subject: [PATCH 07/14] Remove sudo setting from travis.yml (#1816) --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index b38adcb1..2fd9c8a2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,4 @@ language: go -sudo: false matrix: fast_finish: true From c16bfa7949c6ca59c049d20df507d24b1f2ec629 Mon Sep 17 00:00:00 2001 From: Boyi Wu Date: Mon, 18 Mar 2019 10:16:34 +0800 Subject: [PATCH 08/14] update for supporting file binding (#1264) update for supporting multipart form and file binding example: ``` type PhoptUploadForm struct { imgData *multipart.FileHeader `form:"img_data" binding:"required"` ProjectID string `form:"project_id" binding:"required"` Description string `form:"description binding:"required"` } ``` ref: https://github.com/gin-gonic/gin/issues/1263 --- binding/binding.go | 4 +- binding/binding_test.go | 94 ++++++++++++++++++++++++++++++++++++++++- binding/form.go | 5 +++ binding/form_mapping.go | 29 +++++++++++++ 4 files changed, 129 insertions(+), 3 deletions(-) diff --git a/binding/binding.go b/binding/binding.go index 26d71c9f..520c5109 100644 --- a/binding/binding.go +++ b/binding/binding.go @@ -98,7 +98,9 @@ func Default(method, contentType string) Binding { return MsgPack case MIMEYAML: return YAML - default: //case MIMEPOSTForm, MIMEMultipartPOSTForm: + case MIMEMultipartPOSTForm: + return FormMultipart + default: // case MIMEPOSTForm: return Form } } diff --git a/binding/binding_test.go b/binding/binding_test.go index b265af36..ee788225 100644 --- a/binding/binding_test.go +++ b/binding/binding_test.go @@ -8,9 +8,11 @@ import ( "bytes" "encoding/json" "errors" + "io" "io/ioutil" "mime/multipart" "net/http" + "os" "strconv" "strings" "testing" @@ -31,6 +33,18 @@ type FooBarStruct struct { Bar string `msgpack:"bar" json:"bar" form:"bar" xml:"bar" binding:"required"` } +type FooBarFileStruct struct { + FooBarStruct + File *multipart.FileHeader `form:"file" binding:"required"` +} + +type FooBarFileFailStruct struct { + FooBarStruct + File *multipart.FileHeader `invalid_name:"file" binding:"required"` + // for unexport test + data *multipart.FileHeader `form:"data" binding:"required"` +} + type FooDefaultBarStruct struct { FooStruct Bar string `msgpack:"bar" json:"bar" form:"bar,default=hello" xml:"bar" binding:"required"` @@ -187,8 +201,8 @@ func TestBindingDefault(t *testing.T) { assert.Equal(t, Form, Default("POST", MIMEPOSTForm)) assert.Equal(t, Form, Default("PUT", MIMEPOSTForm)) - assert.Equal(t, Form, Default("POST", MIMEMultipartPOSTForm)) - assert.Equal(t, Form, Default("PUT", MIMEMultipartPOSTForm)) + assert.Equal(t, FormMultipart, Default("POST", MIMEMultipartPOSTForm)) + assert.Equal(t, FormMultipart, Default("PUT", MIMEMultipartPOSTForm)) assert.Equal(t, ProtoBuf, Default("POST", MIMEPROTOBUF)) assert.Equal(t, ProtoBuf, Default("PUT", MIMEPROTOBUF)) @@ -536,6 +550,54 @@ func createFormPostRequestForMapFail(t *testing.T) *http.Request { return req } +func createFormFilesMultipartRequest(t *testing.T) *http.Request { + boundary := "--testboundary" + body := new(bytes.Buffer) + mw := multipart.NewWriter(body) + defer mw.Close() + + assert.NoError(t, mw.SetBoundary(boundary)) + assert.NoError(t, mw.WriteField("foo", "bar")) + assert.NoError(t, mw.WriteField("bar", "foo")) + + f, err := os.Open("form.go") + assert.NoError(t, err) + defer f.Close() + fw, err1 := mw.CreateFormFile("file", "form.go") + assert.NoError(t, err1) + io.Copy(fw, f) + + req, err2 := http.NewRequest("POST", "/?foo=getfoo&bar=getbar", body) + assert.NoError(t, err2) + req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary) + + return req +} + +func createFormFilesMultipartRequestFail(t *testing.T) *http.Request { + boundary := "--testboundary" + body := new(bytes.Buffer) + mw := multipart.NewWriter(body) + defer mw.Close() + + assert.NoError(t, mw.SetBoundary(boundary)) + assert.NoError(t, mw.WriteField("foo", "bar")) + assert.NoError(t, mw.WriteField("bar", "foo")) + + f, err := os.Open("form.go") + assert.NoError(t, err) + defer f.Close() + fw, err1 := mw.CreateFormFile("file_foo", "form_foo.go") + assert.NoError(t, err1) + io.Copy(fw, f) + + req, err2 := http.NewRequest("POST", "/?foo=getfoo&bar=getbar", body) + assert.NoError(t, err2) + req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary) + + return req +} + func createFormMultipartRequest(t *testing.T) *http.Request { boundary := "--testboundary" body := new(bytes.Buffer) @@ -613,6 +675,34 @@ func TestBindingFormPostForMapFail(t *testing.T) { assert.Error(t, err) } +func TestBindingFormFilesMultipart(t *testing.T) { + req := createFormFilesMultipartRequest(t) + var obj FooBarFileStruct + FormMultipart.Bind(req, &obj) + + // file from os + f, _ := os.Open("form.go") + defer f.Close() + fileActual, _ := ioutil.ReadAll(f) + + // file from multipart + mf, _ := obj.File.Open() + defer mf.Close() + fileExpect, _ := ioutil.ReadAll(mf) + + assert.Equal(t, FormMultipart.Name(), "multipart/form-data") + assert.Equal(t, obj.Foo, "bar") + assert.Equal(t, obj.Bar, "foo") + assert.Equal(t, fileExpect, fileActual) +} + +func TestBindingFormFilesMultipartFail(t *testing.T) { + req := createFormFilesMultipartRequestFail(t) + var obj FooBarFileFailStruct + err := FormMultipart.Bind(req, &obj) + assert.Error(t, err) +} + func TestBindingFormMultipart(t *testing.T) { req := createFormMultipartRequest(t) var obj FooBarStruct diff --git a/binding/form.go b/binding/form.go index 8955c95b..f1f89195 100644 --- a/binding/form.go +++ b/binding/form.go @@ -56,5 +56,10 @@ func (formMultipartBinding) Bind(req *http.Request, obj interface{}) error { if err := mapForm(obj, req.MultipartForm.Value); err != nil { return err } + + if err := mapFiles(obj, req); err != nil { + return err + } + return validate(obj) } diff --git a/binding/form_mapping.go b/binding/form_mapping.go index ba9d2c4f..fc33b1df 100644 --- a/binding/form_mapping.go +++ b/binding/form_mapping.go @@ -7,6 +7,7 @@ package binding import ( "errors" "fmt" + "net/http" "reflect" "strconv" "strings" @@ -15,6 +16,34 @@ import ( "github.com/gin-gonic/gin/internal/json" ) +func mapFiles(ptr interface{}, req *http.Request) error { + typ := reflect.TypeOf(ptr).Elem() + val := reflect.ValueOf(ptr).Elem() + for i := 0; i < typ.NumField(); i++ { + typeField := typ.Field(i) + structField := val.Field(i) + + t := fmt.Sprintf("%s", typeField.Type) + if string(t) != "*multipart.FileHeader" { + continue + } + + inputFieldName := typeField.Tag.Get("form") + if inputFieldName == "" { + inputFieldName = typeField.Name + } + + _, fileHeader, err := req.FormFile(inputFieldName) + if err != nil { + return err + } + + structField.Set(reflect.ValueOf(fileHeader)) + + } + return nil +} + var errUnknownType = errors.New("Unknown type") func mapUri(ptr interface{}, m map[string][]string) error { From b40d4c175c079fa41cda2669c308485175cf2eee Mon Sep 17 00:00:00 2001 From: Sai Date: Mon, 18 Mar 2019 12:12:30 +0900 Subject: [PATCH 09/14] IsTerm flag should not be affected by DisableConsoleColor method. (#1802) * IsTerm flag should not be affected by DisableConsoleColor method. * change public property to private --- logger.go | 49 +++++++++++++++++++++++++++++------------------- logger_test.go | 51 ++++++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 75 insertions(+), 25 deletions(-) diff --git a/logger.go b/logger.go index 2ecaed7d..198a0192 100644 --- a/logger.go +++ b/logger.go @@ -14,17 +14,24 @@ import ( "github.com/mattn/go-isatty" ) +type consoleColorModeValue int + +const ( + autoColor consoleColorModeValue = iota + disableColor + forceColor +) + var ( - green = string([]byte{27, 91, 57, 55, 59, 52, 50, 109}) - white = string([]byte{27, 91, 57, 48, 59, 52, 55, 109}) - yellow = string([]byte{27, 91, 57, 48, 59, 52, 51, 109}) - red = string([]byte{27, 91, 57, 55, 59, 52, 49, 109}) - blue = string([]byte{27, 91, 57, 55, 59, 52, 52, 109}) - magenta = string([]byte{27, 91, 57, 55, 59, 52, 53, 109}) - cyan = string([]byte{27, 91, 57, 55, 59, 52, 54, 109}) - reset = string([]byte{27, 91, 48, 109}) - disableColor = false - forceColor = false + green = string([]byte{27, 91, 57, 55, 59, 52, 50, 109}) + white = string([]byte{27, 91, 57, 48, 59, 52, 55, 109}) + yellow = string([]byte{27, 91, 57, 48, 59, 52, 51, 109}) + red = string([]byte{27, 91, 57, 55, 59, 52, 49, 109}) + blue = string([]byte{27, 91, 57, 55, 59, 52, 52, 109}) + magenta = string([]byte{27, 91, 57, 55, 59, 52, 53, 109}) + cyan = string([]byte{27, 91, 57, 55, 59, 52, 54, 109}) + reset = string([]byte{27, 91, 48, 109}) + consoleColorMode = autoColor ) // LoggerConfig defines the config for Logger middleware. @@ -62,8 +69,8 @@ type LogFormatterParams struct { Path string // ErrorMessage is set if error has occurred in processing the request. ErrorMessage string - // IsTerm shows whether does gin's output descriptor refers to a terminal. - IsTerm bool + // isTerm shows whether does gin's output descriptor refers to a terminal. + isTerm bool // BodySize is the size of the Response Body BodySize int // Keys are the keys set on the request's context. @@ -115,10 +122,15 @@ func (p *LogFormatterParams) ResetColor() string { return reset } +// IsOutputColor indicates whether can colors be outputted to the log. +func (p *LogFormatterParams) IsOutputColor() bool { + return consoleColorMode == forceColor || (consoleColorMode == autoColor && p.isTerm) +} + // defaultLogFormatter is the default log format function Logger middleware uses. var defaultLogFormatter = func(param LogFormatterParams) string { var statusColor, methodColor, resetColor string - if param.IsTerm { + if param.IsOutputColor() { statusColor = param.StatusCodeColor() methodColor = param.MethodColor() resetColor = param.ResetColor() @@ -137,12 +149,12 @@ var defaultLogFormatter = func(param LogFormatterParams) string { // DisableConsoleColor disables color output in the console. func DisableConsoleColor() { - disableColor = true + consoleColorMode = disableColor } // ForceConsoleColor force color output in the console. func ForceConsoleColor() { - forceColor = true + consoleColorMode = forceColor } // ErrorLogger returns a handlerfunc for any error type. @@ -199,9 +211,8 @@ func LoggerWithConfig(conf LoggerConfig) HandlerFunc { isTerm := true - if w, ok := out.(*os.File); (!ok || - (os.Getenv("TERM") == "dumb" || (!isatty.IsTerminal(w.Fd()) && !isatty.IsCygwinTerminal(w.Fd()))) || - disableColor) && !forceColor { + if w, ok := out.(*os.File); !ok || os.Getenv("TERM") == "dumb" || + (!isatty.IsTerminal(w.Fd()) && !isatty.IsCygwinTerminal(w.Fd())) { isTerm = false } @@ -228,7 +239,7 @@ func LoggerWithConfig(conf LoggerConfig) HandlerFunc { if _, ok := skip[path]; !ok { param := LogFormatterParams{ Request: c.Request, - IsTerm: isTerm, + isTerm: isTerm, Keys: c.Keys, } diff --git a/logger_test.go b/logger_test.go index 36231371..11a859e6 100644 --- a/logger_test.go +++ b/logger_test.go @@ -240,7 +240,7 @@ func TestDefaultLogFormatter(t *testing.T) { Method: "GET", Path: "/", ErrorMessage: "", - IsTerm: false, + isTerm: false, } termTrueParam := LogFormatterParams{ @@ -251,7 +251,7 @@ func TestDefaultLogFormatter(t *testing.T) { Method: "GET", Path: "/", ErrorMessage: "", - IsTerm: true, + isTerm: true, } assert.Equal(t, "[GIN] 2018/12/07 - 09:11:42 | 200 | 5s | 20.20.20.20 | GET /\n", defaultLogFormatter(termFalseParam)) @@ -296,6 +296,39 @@ func TestResetColor(t *testing.T) { assert.Equal(t, string([]byte{27, 91, 48, 109}), p.ResetColor()) } +func TestIsOutputColor(t *testing.T) { + // test with isTerm flag true. + p := LogFormatterParams{ + isTerm: true, + } + + consoleColorMode = autoColor + assert.Equal(t, true, p.IsOutputColor()) + + ForceConsoleColor() + assert.Equal(t, true, p.IsOutputColor()) + + DisableConsoleColor() + assert.Equal(t, false, p.IsOutputColor()) + + // test with isTerm flag false. + p = LogFormatterParams{ + isTerm: false, + } + + consoleColorMode = autoColor + assert.Equal(t, false, p.IsOutputColor()) + + ForceConsoleColor() + assert.Equal(t, true, p.IsOutputColor()) + + DisableConsoleColor() + assert.Equal(t, false, p.IsOutputColor()) + + // reset console color mode. + consoleColorMode = autoColor +} + func TestErrorLogger(t *testing.T) { router := New() router.Use(ErrorLogger()) @@ -358,14 +391,20 @@ func TestLoggerWithConfigSkippingPaths(t *testing.T) { func TestDisableConsoleColor(t *testing.T) { New() - assert.False(t, disableColor) + assert.Equal(t, autoColor, consoleColorMode) DisableConsoleColor() - assert.True(t, disableColor) + assert.Equal(t, disableColor, consoleColorMode) + + // reset console color mode. + consoleColorMode = autoColor } func TestForceConsoleColor(t *testing.T) { New() - assert.False(t, forceColor) + assert.Equal(t, autoColor, consoleColorMode) ForceConsoleColor() - assert.True(t, forceColor) + assert.Equal(t, forceColor, consoleColorMode) + + // reset console color mode. + consoleColorMode = autoColor } From 0c1f3c4e81f6e969a3e465d933faefb5578c54d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Wed, 20 Mar 2019 12:07:34 +0800 Subject: [PATCH 10/14] chore: fix invalid link (#1820) --- README.md | 4 +++- doc.go | 2 +- testdata/assets/console.png | Bin 59545 -> 0 bytes 3 files changed, 4 insertions(+), 2 deletions(-) delete mode 100644 testdata/assets/console.png diff --git a/README.md b/README.md index b46c5637..d3433ed2 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ For more feature details, please see the [Gin website introduction](https://gin- ### Getting Gin -The first need [Go](https://golang.org/) installed (version 1.6+ is required), then you can use the below Go command to install Gin. +The first need [Go](https://golang.org/) installed (**version 1.6+ is required**), then you can use the below Go command to install Gin. ```sh $ go get -u github.com/gin-gonic/gin @@ -111,6 +111,8 @@ You can find many useful Gin middlewares at [gin-contrib](https://github.com/gin ## Documentation +See [API documentation and descriptions](https://godoc.org/github.com/gin-gonic/gin) for package. + All documentation is available on the Gin website. - [English](https://gin-gonic.com/docs/) diff --git a/doc.go b/doc.go index 01ac4a90..1bd03864 100644 --- a/doc.go +++ b/doc.go @@ -1,6 +1,6 @@ /* Package gin implements a HTTP web framework called gin. -See https://gin-gonic.github.io/gin/ for more information about gin. +See https://gin-gonic.com/ for more information about gin. */ package gin // import "github.com/gin-gonic/gin" diff --git a/testdata/assets/console.png b/testdata/assets/console.png deleted file mode 100644 index 7a695718fa31f9b1b42cfa547c16da394378aeae..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 59545 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
?hY(7P?kZ1wD_zt6I%hfC|R7KdZM)`&t88E6Ua# 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 From 1d462bbe3713bc2fea40ed45c80a06ce856d379f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Thu, 21 Mar 2019 15:12:06 +0800 Subject: [PATCH 11/14] chore: update ginS (#1822) --- ginS/gins.go | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/ginS/gins.go b/ginS/gins.go index 0f08645a..3ce4a6f6 100644 --- a/ginS/gins.go +++ b/ginS/gins.go @@ -125,23 +125,35 @@ func Use(middlewares ...gin.HandlerFunc) gin.IRoutes { return engine().Use(middlewares...) } -// Run : The router is attached to a http.Server and starts listening and serving HTTP requests. +// Routes returns a slice of registered routes. +func Routes() gin.RoutesInfo { + return engine().Routes() +} + +// Run attaches to a http.Server and starts listening and serving HTTP requests. // It is a shortcut for http.ListenAndServe(addr, router) // Note: this method will block the calling goroutine indefinitely unless an error happens. func Run(addr ...string) (err error) { return engine().Run(addr...) } -// RunTLS : The router is attached to a http.Server and starts listening and serving HTTPS requests. +// RunTLS attaches to a http.Server and starts listening and serving HTTPS requests. // It is a shortcut for http.ListenAndServeTLS(addr, certFile, keyFile, router) // Note: this method will block the calling goroutine indefinitely unless an error happens. func RunTLS(addr, certFile, keyFile string) (err error) { return engine().RunTLS(addr, certFile, keyFile) } -// RunUnix : The router is attached to a http.Server and starts listening and serving HTTP requests +// RunUnix attaches to a http.Server and starts listening and serving HTTP requests // through the specified unix socket (ie. a file) // Note: this method will block the calling goroutine indefinitely unless an error happens. func RunUnix(file string) (err error) { return engine().RunUnix(file) } + +// RunFd attaches the router to a http.Server and starts listening and serving HTTP requests +// through the specified file descriptor. +// Note: thie method will block the calling goroutine indefinitely unless on error happens. +func RunFd(fd int) (err error) { + return engine().RunFd(fd) +} From ce20f107f5dc498ec7489d7739541a25dcd48463 Mon Sep 17 00:00:00 2001 From: Dan Markham Date: Wed, 27 Mar 2019 23:14:00 -0700 Subject: [PATCH 12/14] Truncate Latency precision in long running request (#1830) fixes #1823 --- logger.go | 4 ++++ logger_test.go | 24 ++++++++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/logger.go b/logger.go index 198a0192..5ab4639e 100644 --- a/logger.go +++ b/logger.go @@ -136,6 +136,10 @@ var defaultLogFormatter = func(param LogFormatterParams) string { resetColor = param.ResetColor() } + if param.Latency > time.Minute { + // Truncate in a golang < 1.8 safe way + param.Latency = param.Latency - param.Latency%time.Second + } return fmt.Sprintf("[GIN] %v |%s %3d %s| %13v | %15s |%s %-7s %s %s\n%s", param.TimeStamp.Format("2006/01/02 - 15:04:05"), statusColor, param.StatusCode, resetColor, diff --git a/logger_test.go b/logger_test.go index 11a859e6..56bb3a00 100644 --- a/logger_test.go +++ b/logger_test.go @@ -253,10 +253,34 @@ func TestDefaultLogFormatter(t *testing.T) { ErrorMessage: "", isTerm: true, } + termTrueLongDurationParam := LogFormatterParams{ + TimeStamp: timeStamp, + StatusCode: 200, + Latency: time.Millisecond * 9876543210, + ClientIP: "20.20.20.20", + Method: "GET", + Path: "/", + ErrorMessage: "", + isTerm: true, + } + + termFalseLongDurationParam := LogFormatterParams{ + TimeStamp: timeStamp, + StatusCode: 200, + Latency: time.Millisecond * 9876543210, + ClientIP: "20.20.20.20", + Method: "GET", + Path: "/", + ErrorMessage: "", + isTerm: false, + } assert.Equal(t, "[GIN] 2018/12/07 - 09:11:42 | 200 | 5s | 20.20.20.20 | GET /\n", defaultLogFormatter(termFalseParam)) + assert.Equal(t, "[GIN] 2018/12/07 - 09:11:42 | 200 | 2743h29m3s | 20.20.20.20 | GET /\n", defaultLogFormatter(termFalseLongDurationParam)) assert.Equal(t, "[GIN] 2018/12/07 - 09:11:42 |\x1b[97;42m 200 \x1b[0m| 5s | 20.20.20.20 |\x1b[97;44m GET \x1b[0m /\n", defaultLogFormatter(termTrueParam)) + assert.Equal(t, "[GIN] 2018/12/07 - 09:11:42 |\x1b[97;42m 200 \x1b[0m| 2743h29m3s | 20.20.20.20 |\x1b[97;44m GET \x1b[0m /\n", defaultLogFormatter(termTrueLongDurationParam)) + } func TestColorForMethod(t *testing.T) { From 2e915f4e5083995154f65a600c86582b5396d02a Mon Sep 17 00:00:00 2001 From: Dmitry Kutakov Date: Tue, 2 Apr 2019 04:01:34 +0300 Subject: [PATCH 13/14] refactor(form_mapping.go): mapping multipart request (#1829) * refactor(form_mapping.go): mapping multipart request * add checkers for a types to match with the setter interface * form_mapping.go: rename method name on setter interface, add comments * fix style of comments --- binding/form.go | 36 +++++++++++++++--- binding/form_mapping.go | 84 ++++++++++++++++++++--------------------- 2 files changed, 71 insertions(+), 49 deletions(-) diff --git a/binding/form.go b/binding/form.go index f1f89195..0b28aa8a 100644 --- a/binding/form.go +++ b/binding/form.go @@ -4,7 +4,11 @@ package binding -import "net/http" +import ( + "mime/multipart" + "net/http" + "reflect" +) const defaultMemory = 32 * 1024 * 1024 @@ -53,13 +57,33 @@ func (formMultipartBinding) Bind(req *http.Request, obj interface{}) error { if err := req.ParseMultipartForm(defaultMemory); err != nil { return err } - if err := mapForm(obj, req.MultipartForm.Value); err != nil { - return err - } - - if err := mapFiles(obj, req); err != nil { + if err := mappingByPtr(obj, (*multipartRequest)(req), "form"); err != nil { return err } return validate(obj) } + +type multipartRequest http.Request + +var _ setter = (*multipartRequest)(nil) + +var ( + multipartFileHeaderStructType = reflect.TypeOf(multipart.FileHeader{}) +) + +// TrySet tries to set a value by the multipart request with the binding a form file +func (r *multipartRequest) TrySet(value reflect.Value, field reflect.StructField, key string, opt setOptions) (isSetted bool, err error) { + if value.Type() == multipartFileHeaderStructType { + _, file, err := (*http.Request)(r).FormFile(key) + if err != nil { + return false, err + } + if file != nil { + value.Set(reflect.ValueOf(*file)) + return true, nil + } + } + + return setByForm(value, field, r.MultipartForm.Value, key, opt) +} diff --git a/binding/form_mapping.go b/binding/form_mapping.go index fc33b1df..aaacf6c5 100644 --- a/binding/form_mapping.go +++ b/binding/form_mapping.go @@ -7,7 +7,6 @@ package binding import ( "errors" "fmt" - "net/http" "reflect" "strconv" "strings" @@ -16,34 +15,6 @@ import ( "github.com/gin-gonic/gin/internal/json" ) -func mapFiles(ptr interface{}, req *http.Request) error { - typ := reflect.TypeOf(ptr).Elem() - val := reflect.ValueOf(ptr).Elem() - for i := 0; i < typ.NumField(); i++ { - typeField := typ.Field(i) - structField := val.Field(i) - - t := fmt.Sprintf("%s", typeField.Type) - if string(t) != "*multipart.FileHeader" { - continue - } - - inputFieldName := typeField.Tag.Get("form") - if inputFieldName == "" { - inputFieldName = typeField.Name - } - - _, fileHeader, err := req.FormFile(inputFieldName) - if err != nil { - return err - } - - structField.Set(reflect.ValueOf(fileHeader)) - - } - return nil -} - var errUnknownType = errors.New("Unknown type") func mapUri(ptr interface{}, m map[string][]string) error { @@ -57,11 +28,29 @@ func mapForm(ptr interface{}, form map[string][]string) error { var emptyField = reflect.StructField{} func mapFormByTag(ptr interface{}, form map[string][]string, tag string) error { - _, err := mapping(reflect.ValueOf(ptr), emptyField, form, tag) + return mappingByPtr(ptr, formSource(form), tag) +} + +// setter tries to set value on a walking by fields of a struct +type setter interface { + TrySet(value reflect.Value, field reflect.StructField, key string, opt setOptions) (isSetted bool, err error) +} + +type formSource map[string][]string + +var _ setter = formSource(nil) + +// TrySet tries to set a value by request's form source (like map[string][]string) +func (form formSource) TrySet(value reflect.Value, field reflect.StructField, tagValue string, opt setOptions) (isSetted bool, err error) { + return setByForm(value, field, form, tagValue, opt) +} + +func mappingByPtr(ptr interface{}, setter setter, tag string) error { + _, err := mapping(reflect.ValueOf(ptr), emptyField, setter, tag) return err } -func mapping(value reflect.Value, field reflect.StructField, form map[string][]string, tag string) (bool, error) { +func mapping(value reflect.Value, field reflect.StructField, setter setter, tag string) (bool, error) { var vKind = value.Kind() if vKind == reflect.Ptr { @@ -71,7 +60,7 @@ func mapping(value reflect.Value, field reflect.StructField, form map[string][]s isNew = true vPtr = reflect.New(value.Type().Elem()) } - isSetted, err := mapping(vPtr.Elem(), field, form, tag) + isSetted, err := mapping(vPtr.Elem(), field, setter, tag) if err != nil { return false, err } @@ -81,7 +70,7 @@ func mapping(value reflect.Value, field reflect.StructField, form map[string][]s return isSetted, nil } - ok, err := tryToSetValue(value, field, form, tag) + ok, err := tryToSetValue(value, field, setter, tag) if err != nil { return false, err } @@ -97,7 +86,7 @@ func mapping(value reflect.Value, field reflect.StructField, form map[string][]s if !value.Field(i).CanSet() { continue } - ok, err := mapping(value.Field(i), tValue.Field(i), form, tag) + ok, err := mapping(value.Field(i), tValue.Field(i), setter, tag) if err != nil { return false, err } @@ -108,9 +97,14 @@ func mapping(value reflect.Value, field reflect.StructField, form map[string][]s return false, nil } -func tryToSetValue(value reflect.Value, field reflect.StructField, form map[string][]string, tag string) (bool, error) { - var tagValue, defaultValue string - var isDefaultExists bool +type setOptions struct { + isDefaultExists bool + defaultValue string +} + +func tryToSetValue(value reflect.Value, field reflect.StructField, setter setter, tag string) (bool, error) { + var tagValue string + var setOpt setOptions tagValue = field.Tag.Get(tag) tagValue, opts := head(tagValue, ",") @@ -132,25 +126,29 @@ func tryToSetValue(value reflect.Value, field reflect.StructField, form map[stri k, v := head(opt, "=") switch k { case "default": - isDefaultExists = true - defaultValue = v + setOpt.isDefaultExists = true + setOpt.defaultValue = v } } + return setter.TrySet(value, field, tagValue, setOpt) +} + +func setByForm(value reflect.Value, field reflect.StructField, form map[string][]string, tagValue string, opt setOptions) (isSetted bool, err error) { vs, ok := form[tagValue] - if !ok && !isDefaultExists { + if !ok && !opt.isDefaultExists { return false, nil } switch value.Kind() { case reflect.Slice: if !ok { - vs = []string{defaultValue} + vs = []string{opt.defaultValue} } return true, setSlice(vs, value, field) case reflect.Array: if !ok { - vs = []string{defaultValue} + vs = []string{opt.defaultValue} } if len(vs) != value.Len() { return false, fmt.Errorf("%q is not valid value for %s", vs, value.Type().String()) @@ -159,7 +157,7 @@ func tryToSetValue(value reflect.Value, field reflect.StructField, form map[stri default: var val string if !ok { - val = defaultValue + val = opt.defaultValue } if len(vs) > 0 { From ffcbe77b1e6222b4e0e97eb1920adc1813fb2224 Mon Sep 17 00:00:00 2001 From: Eason Lin Date: Sat, 6 Apr 2019 21:48:33 +0800 Subject: [PATCH 14/14] chore(readme): rollback readme (#1846) #1844 #1838 Keep the documentation in readme until full available on the new website. --- README.md | 2010 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 1972 insertions(+), 38 deletions(-) diff --git a/README.md b/README.md index d3433ed2..804041f9 100644 --- a/README.md +++ b/README.md @@ -13,35 +13,122 @@ Gin 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. -**The key features of Gin are:** -- Zero allocation router -- Fast -- Middleware support -- Crash-free -- JSON validation -- Routes grouping -- Error management -- Rendering built-in -- Extendable +## Contents -For more feature details, please see the [Gin website introduction](https://gin-gonic.com/docs/introduction/). +- [Installation](#installation) +- [Prerequisite](#prerequisite) +- [Quick start](#quick-start) +- [Benchmarks](#benchmarks) +- [Gin v1.stable](#gin-v1-stable) +- [Build with jsoniter](#build-with-jsoniter) +- [API Examples](#api-examples) + - [Using GET,POST,PUT,PATCH,DELETE and OPTIONS](#using-get-post-put-patch-delete-and-options) + - [Parameters in path](#parameters-in-path) + - [Querystring parameters](#querystring-parameters) + - [Multipart/Urlencoded Form](#multiparturlencoded-form) + - [Another example: query + post form](#another-example-query--post-form) + - [Map as querystring or postform parameters](#map-as-querystring-or-postform-parameters) + - [Upload files](#upload-files) + - [Grouping routes](#grouping-routes) + - [Blank Gin without middleware by default](#blank-gin-without-middleware-by-default) + - [Using middleware](#using-middleware) + - [How to write log file](#how-to-write-log-file) + - [Custom Log Format](#custom-log-format) + - [Model binding and validation](#model-binding-and-validation) + - [Custom Validators](#custom-validators) + - [Only Bind Query String](#only-bind-query-string) + - [Bind Query String or Post Data](#bind-query-string-or-post-data) + - [Bind Uri](#bind-uri) + - [Bind HTML checkboxes](#bind-html-checkboxes) + - [Multipart/Urlencoded binding](#multiparturlencoded-binding) + - [XML, JSON, YAML and ProtoBuf rendering](#xml-json-yaml-and-protobuf-rendering) + - [JSONP rendering](#jsonp) + - [Serving static files](#serving-static-files) + - [Serving data from reader](#serving-data-from-reader) + - [HTML rendering](#html-rendering) + - [Multitemplate](#multitemplate) + - [Redirects](#redirects) + - [Custom Middleware](#custom-middleware) + - [Using BasicAuth() middleware](#using-basicauth-middleware) + - [Goroutines inside a middleware](#goroutines-inside-a-middleware) + - [Custom HTTP configuration](#custom-http-configuration) + - [Support Let's Encrypt](#support-lets-encrypt) + - [Run multiple service using Gin](#run-multiple-service-using-gin) + - [Graceful restart or stop](#graceful-restart-or-stop) + - [Build a single binary with templates](#build-a-single-binary-with-templates) + - [Bind form-data request with custom struct](#bind-form-data-request-with-custom-struct) + - [Try to bind body into different structs](#try-to-bind-body-into-different-structs) + - [http2 server push](#http2-server-push) + - [Define format for the log of routes](#define-format-for-the-log-of-routes) + - [Set and get a cookie](#set-and-get-a-cookie) +- [Testing](#testing) +- [Users](#users) -## Getting started +## Installation -### Getting Gin +To install Gin package, you need to install Go and set your Go workspace first. -The first need [Go](https://golang.org/) installed (**version 1.6+ is required**), then you can use the below Go command to install Gin. +1. Download and install it: ```sh $ go get -u github.com/gin-gonic/gin ``` -For more installation guides such as vendor tool, please check out [Gin quickstart](https://gin-gonic.com/docs/quickstart/). +2. Import it in your code: -### Running Gin +```go +import "github.com/gin-gonic/gin" +``` -First you need to import Gin package for using Gin, one simplest example likes the follow `example.go`: +3. (Optional) Import `net/http`. This is required for example if using constants such as `http.StatusOK`. + +```go +import "net/http" +``` + +### Use a vendor tool like [Govendor](https://github.com/kardianos/govendor) + +1. `go get` govendor + +```sh +$ go get github.com/kardianos/govendor +``` +2. Create your project folder and `cd` inside + +```sh +$ mkdir -p $GOPATH/src/github.com/myusername/project && cd "$_" +``` + +3. Vendor init your project and add gin + +```sh +$ govendor init +$ govendor fetch github.com/gin-gonic/gin@v1.3 +``` + +4. Copy a starting template inside your project + +```sh +$ curl https://raw.githubusercontent.com/gin-gonic/examples/master/basic/main.go > main.go +``` + +5. Run your project + +```sh +$ go run main.go +``` + +## Prerequisite + +Now Gin requires Go 1.6 or later and Go 1.7 will be required soon. + +## Quick start + +```sh +# assume the following codes in example.go file +$ cat example.go +``` ```go package main @@ -59,8 +146,6 @@ func main() { } ``` -And use the Go command to run the demo: - ``` # run example.go and visit 0.0.0.0:8080/ping on browser $ go run example.go @@ -68,7 +153,9 @@ $ go run example.go ## Benchmarks -Please see all benchmarks details from [Gin website](https://gin-gonic.com/docs/benchmarks/). +Gin uses a custom version of [HttpRouter](https://github.com/julienschmidt/httprouter) + +[See all benchmarks](/BENCHMARKS.md) Benchmark name | (1) | (2) | (3) | (4) --------------------------------------------|-----------:|------------:|-----------:|---------: @@ -105,32 +192,1879 @@ BenchmarkVulcan_GithubAll | 5000 | 394253 | 19894 - (3): Heap Memory (B/op), lower is better - (4): Average Allocations per Repetition (allocs/op), lower is better -## Middlewares +## Gin v1. stable -You can find many useful Gin middlewares at [gin-contrib](https://github.com/gin-contrib). +- [x] Zero allocation router. +- [x] Still the fastest http router and framework. From routing to writing. +- [x] Complete suite of unit tests +- [x] Battle tested +- [x] API frozen, new releases will not break your code. -## Documentation +## Build with [jsoniter](https://github.com/json-iterator/go) -See [API documentation and descriptions](https://godoc.org/github.com/gin-gonic/gin) for package. +Gin uses `encoding/json` as default json package but you can change to [jsoniter](https://github.com/json-iterator/go) by build from other tags. -All documentation is available on the Gin website. +```sh +$ go build -tags=jsoniter . +``` -- [English](https://gin-gonic.com/docs/) -- [简体中文](https://gin-gonic.com/zh-cn/docs/) -- [繁體中文](https://gin-gonic.com/zh-tw/docs/) -- [日本語](https://gin-gonic.com/ja/docs/) +## API Examples -## Examples +You can find a number of ready-to-run examples at [Gin examples repository](https://github.com/gin-gonic/examples). -A number of ready-to-run examples demonstrating various use cases of Gin on the [Gin examples](https://github.com/gin-gonic/examples) repository. +### Using GET, POST, PUT, PATCH, DELETE and OPTIONS + +```go +func main() { + // Creates a gin router with default middleware: + // logger and recovery (crash-free) middleware + router := gin.Default() + + router.GET("/someGet", getting) + router.POST("/somePost", posting) + router.PUT("/somePut", putting) + router.DELETE("/someDelete", deleting) + router.PATCH("/somePatch", patching) + router.HEAD("/someHead", head) + router.OPTIONS("/someOptions", options) + + // By default it serves on :8080 unless a + // PORT environment variable was defined. + router.Run() + // router.Run(":3000") for a hard coded port +} +``` + +### Parameters in path + +```go +func main() { + router := gin.Default() + + // This handler will match /user/john but will not match /user/ or /user + router.GET("/user/:name", func(c *gin.Context) { + name := c.Param("name") + c.String(http.StatusOK, "Hello %s", name) + }) + + // However, this one will match /user/john/ and also /user/john/send + // If no other routers match /user/john, it will redirect to /user/john/ + router.GET("/user/:name/*action", func(c *gin.Context) { + name := c.Param("name") + action := c.Param("action") + message := name + " is " + action + c.String(http.StatusOK, message) + }) + + router.Run(":8080") +} +``` + +### Querystring parameters + +```go +func main() { + router := gin.Default() + + // Query string parameters are parsed using the existing underlying request object. + // The request responds to a url matching: /welcome?firstname=Jane&lastname=Doe + router.GET("/welcome", func(c *gin.Context) { + firstname := c.DefaultQuery("firstname", "Guest") + lastname := c.Query("lastname") // shortcut for c.Request.URL.Query().Get("lastname") + + c.String(http.StatusOK, "Hello %s %s", firstname, lastname) + }) + router.Run(":8080") +} +``` + +### Multipart/Urlencoded Form + +```go +func main() { + router := gin.Default() + + router.POST("/form_post", func(c *gin.Context) { + message := c.PostForm("message") + nick := c.DefaultPostForm("nick", "anonymous") + + c.JSON(200, gin.H{ + "status": "posted", + "message": message, + "nick": nick, + }) + }) + router.Run(":8080") +} +``` + +### Another example: query + post form + +``` +POST /post?id=1234&page=1 HTTP/1.1 +Content-Type: application/x-www-form-urlencoded + +name=manu&message=this_is_great +``` + +```go +func main() { + router := gin.Default() + + router.POST("/post", func(c *gin.Context) { + + id := c.Query("id") + page := c.DefaultQuery("page", "0") + name := c.PostForm("name") + message := c.PostForm("message") + + fmt.Printf("id: %s; page: %s; name: %s; message: %s", id, page, name, message) + }) + router.Run(":8080") +} +``` + +``` +id: 1234; page: 1; name: manu; message: this_is_great +``` + +### Map as querystring or postform parameters + +``` +POST /post?ids[a]=1234&ids[b]=hello HTTP/1.1 +Content-Type: application/x-www-form-urlencoded + +names[first]=thinkerou&names[second]=tianou +``` + +```go +func main() { + router := gin.Default() + + router.POST("/post", func(c *gin.Context) { + + ids := c.QueryMap("ids") + names := c.PostFormMap("names") + + fmt.Printf("ids: %v; names: %v", ids, names) + }) + router.Run(":8080") +} +``` + +``` +ids: map[b:hello a:1234], names: map[second:tianou first:thinkerou] +``` + +### Upload files + +#### Single file + +References 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). + +`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) + +> 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. + +```go +func main() { + router := gin.Default() + // Set a lower memory limit for multipart forms (default is 32 MiB) + // router.MaxMultipartMemory = 8 << 20 // 8 MiB + router.POST("/upload", func(c *gin.Context) { + // single file + file, _ := c.FormFile("file") + log.Println(file.Filename) + + // Upload the file to specific dst. + // c.SaveUploadedFile(file, dst) + + c.String(http.StatusOK, fmt.Sprintf("'%s' uploaded!", file.Filename)) + }) + router.Run(":8080") +} +``` + +How to `curl`: + +```bash +curl -X POST http://localhost:8080/upload \ + -F "file=@/Users/appleboy/test.zip" \ + -H "Content-Type: multipart/form-data" +``` + +#### Multiple files + +See the detail [example code](https://github.com/gin-gonic/examples/tree/master/upload-file/multiple). + +```go +func main() { + router := gin.Default() + // Set a lower memory limit for multipart forms (default is 32 MiB) + // router.MaxMultipartMemory = 8 << 20 // 8 MiB + router.POST("/upload", func(c *gin.Context) { + // Multipart form + form, _ := c.MultipartForm() + files := form.File["upload[]"] + + for _, file := range files { + log.Println(file.Filename) + + // Upload the file to specific dst. + // c.SaveUploadedFile(file, dst) + } + c.String(http.StatusOK, fmt.Sprintf("%d files uploaded!", len(files))) + }) + router.Run(":8080") +} +``` + +How to `curl`: + +```bash +curl -X POST http://localhost:8080/upload \ + -F "upload[]=@/Users/appleboy/test1.zip" \ + -F "upload[]=@/Users/appleboy/test2.zip" \ + -H "Content-Type: multipart/form-data" +``` + +### Grouping routes + +```go +func main() { + router := gin.Default() + + // Simple group: v1 + v1 := router.Group("/v1") + { + v1.POST("/login", loginEndpoint) + v1.POST("/submit", submitEndpoint) + v1.POST("/read", readEndpoint) + } + + // Simple group: v2 + v2 := router.Group("/v2") + { + v2.POST("/login", loginEndpoint) + v2.POST("/submit", submitEndpoint) + v2.POST("/read", readEndpoint) + } + + router.Run(":8080") +} +``` + +### Blank Gin without middleware by default + +Use + +```go +r := gin.New() +``` + +instead of + +```go +// Default With the Logger and Recovery middleware already attached +r := gin.Default() +``` + + +### Using middleware +```go +func main() { + // Creates a router without any middleware by default + r := gin.New() + + // Global middleware + // Logger middleware will write the logs to gin.DefaultWriter even if you set with GIN_MODE=release. + // By default gin.DefaultWriter = os.Stdout + r.Use(gin.Logger()) + + // Recovery middleware recovers from any panics and writes a 500 if there was one. + r.Use(gin.Recovery()) + + // Per route middleware, you can add as many as you desire. + r.GET("/benchmark", MyBenchLogger(), benchEndpoint) + + // Authorization group + // authorized := r.Group("/", AuthRequired()) + // exactly the same as: + authorized := r.Group("/") + // per group middleware! in this case we use the custom created + // AuthRequired() middleware just in the "authorized" group. + authorized.Use(AuthRequired()) + { + authorized.POST("/login", loginEndpoint) + authorized.POST("/submit", submitEndpoint) + authorized.POST("/read", readEndpoint) + + // nested group + testing := authorized.Group("testing") + testing.GET("/analytics", analyticsEndpoint) + } + + // Listen and serve on 0.0.0.0:8080 + r.Run(":8080") +} +``` + +### How to write log file +```go +func main() { + // Disable Console Color, you don't need console color when writing the logs to file. + gin.DisableConsoleColor() + + // Logging to a file. + f, _ := os.Create("gin.log") + gin.DefaultWriter = io.MultiWriter(f) + + // Use the following code if you need to write the logs to file and console at the same time. + // gin.DefaultWriter = io.MultiWriter(f, os.Stdout) + + router := gin.Default() + router.GET("/ping", func(c *gin.Context) { + c.String(200, "pong") + }) + +    router.Run(":8080") +} +``` + +### Custom Log Format +```go +func main() { + router := gin.New() + + // LoggerWithFormatter middleware will write the logs to gin.DefaultWriter + // By default gin.DefaultWriter = os.Stdout + router.Use(gin.LoggerWithFormatter(func(param gin.LogFormatterParams) string { + + // your custom format + return fmt.Sprintf("%s - [%s] \"%s %s %s %d %s \"%s\" %s\"\n", + param.ClientIP, + param.TimeStamp.Format(time.RFC1123), + param.Method, + param.Path, + param.Request.Proto, + param.StatusCode, + param.Latency, + param.Request.UserAgent(), + param.ErrorMessage, + ) + })) + router.Use(gin.Recovery()) + + router.GET("/ping", func(c *gin.Context) { + c.String(200, "pong") + }) + + router.Run(":8080") +} +``` + +**Sample Output** +``` +::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" " +``` + +### Controlling Log output coloring + +By default, logs output on console should be colorized depending on the detected TTY. + +Never colorize logs: + +```go +func main() { + // Disable log's color + gin.DisableConsoleColor() + + // Creates a gin router with default middleware: + // logger and recovery (crash-free) middleware + router := gin.Default() + + router.GET("/ping", func(c *gin.Context) { + c.String(200, "pong") + }) + + router.Run(":8080") +} +``` + +Always colorize logs: + +```go +func main() { + // Force log's color + gin.ForceConsoleColor() + + // Creates a gin router with default middleware: + // logger and recovery (crash-free) middleware + router := gin.Default() + + router.GET("/ping", func(c *gin.Context) { + c.String(200, "pong") + }) + + router.Run(":8080") +} +``` + +### Model binding and validation + +To bind a request body into a type, use model binding. We currently support binding of JSON, XML, YAML and standard form values (foo=bar&boo=baz). + +Gin 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). + +Note that you need to set the corresponding binding tag on all fields you want to bind. For example, when binding from JSON, set `json:"fieldname"`. + +Also, Gin provides two sets of methods for binding: +- **Type** - Must bind + - **Methods** - `Bind`, `BindJSON`, `BindXML`, `BindQuery`, `BindYAML` + - **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. +- **Type** - Should bind + - **Methods** - `ShouldBind`, `ShouldBindJSON`, `ShouldBindXML`, `ShouldBindQuery`, `ShouldBindYAML` + - **Behavior** - These methods use `ShouldBindWith` under the hood. If there is a binding error, the error is returned and it is the developer's responsibility to handle the request and error appropriately. + +When using the Bind-method, Gin tries to infer the binder depending on the Content-Type header. If you are sure what you are binding, you can use `MustBindWith` or `ShouldBindWith`. + +You can also specify that specific fields are required. If a field is decorated with `binding:"required"` and has a empty value when binding, an error will be returned. + +```go +// Binding from JSON +type Login struct { + User string `form:"user" json:"user" xml:"user" binding:"required"` + Password string `form:"password" json:"password" xml:"password" binding:"required"` +} + +func main() { + router := gin.Default() + + // Example for binding JSON ({"user": "manu", "password": "123"}) + router.POST("/loginJSON", func(c *gin.Context) { + var json Login + if err := c.ShouldBindJSON(&json); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + if json.User != "manu" || json.Password != "123" { + c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"}) + return + } + + c.JSON(http.StatusOK, gin.H{"status": "you are logged in"}) + }) + + // Example for binding XML ( + // + // + // user + // 123 + // ) + router.POST("/loginXML", func(c *gin.Context) { + var xml Login + if err := c.ShouldBindXML(&xml); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + if xml.User != "manu" || xml.Password != "123" { + c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"}) + return + } + + c.JSON(http.StatusOK, gin.H{"status": "you are logged in"}) + }) + + // Example for binding a HTML form (user=manu&password=123) + router.POST("/loginForm", func(c *gin.Context) { + var form Login + // This will infer what binder to use depending on the content-type header. + if err := c.ShouldBind(&form); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + if form.User != "manu" || form.Password != "123" { + c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"}) + return + } + + c.JSON(http.StatusOK, gin.H{"status": "you are logged in"}) + }) + + // Listen and serve on 0.0.0.0:8080 + router.Run(":8080") +} +``` + +**Sample request** +```shell +$ curl -v -X POST \ + http://localhost:8080/loginJSON \ + -H 'content-type: application/json' \ + -d '{ "user": "manu" }' +> POST /loginJSON HTTP/1.1 +> Host: localhost:8080 +> User-Agent: curl/7.51.0 +> Accept: */* +> content-type: application/json +> Content-Length: 18 +> +* upload completely sent off: 18 out of 18 bytes +< HTTP/1.1 400 Bad Request +< Content-Type: application/json; charset=utf-8 +< Date: Fri, 04 Aug 2017 03:51:31 GMT +< Content-Length: 100 +< +{"error":"Key: 'Login.Password' Error:Field validation for 'Password' failed on the 'required' tag"} +``` + +**Skip validate** + +When running the above example using the above the `curl` command, it returns error. Because the example use `binding:"required"` for `Password`. If use `binding:"-"` for `Password`, then it will not return error when running the above example again. + +### Custom Validators + +It is also possible to register custom validators. See the [example code](https://github.com/gin-gonic/examples/tree/master/custom-validation/server.go). + +```go +package main + +import ( + "net/http" + "reflect" + "time" + + "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin/binding" + "gopkg.in/go-playground/validator.v8" +) + +// Booking contains binded and validated data. +type Booking struct { + CheckIn time.Time `form:"check_in" binding:"required,bookabledate" time_format:"2006-01-02"` + CheckOut time.Time `form:"check_out" binding:"required,gtfield=CheckIn" time_format:"2006-01-02"` +} + +func bookableDate( + v *validator.Validate, topStruct reflect.Value, currentStructOrField reflect.Value, + field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string, +) bool { + if date, ok := field.Interface().(time.Time); ok { + today := time.Now() + if today.Year() > date.Year() || today.YearDay() > date.YearDay() { + return false + } + } + return true +} + +func main() { + route := gin.Default() + + if v, ok := binding.Validator.Engine().(*validator.Validate); ok { + v.RegisterValidation("bookabledate", bookableDate) + } + + route.GET("/bookable", getBookable) + route.Run(":8085") +} + +func getBookable(c *gin.Context) { + var b Booking + if err := c.ShouldBindWith(&b, binding.Query); err == nil { + c.JSON(http.StatusOK, gin.H{"message": "Booking dates are valid!"}) + } else { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + } +} +``` + +```console +$ curl "localhost:8085/bookable?check_in=2018-04-16&check_out=2018-04-17" +{"message":"Booking dates are valid!"} + +$ curl "localhost:8085/bookable?check_in=2018-03-08&check_out=2018-03-09" +{"error":"Key: 'Booking.CheckIn' Error:Field validation for 'CheckIn' failed on the 'bookabledate' tag"} +``` + +[Struct level validations](https://github.com/go-playground/validator/releases/tag/v8.7) can also be registered this way. +See the [struct-lvl-validation example](https://github.com/gin-gonic/examples/tree/master/struct-lvl-validations) to learn more. + +### Only Bind Query String + +`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). + +```go +package main + +import ( + "log" + + "github.com/gin-gonic/gin" +) + +type Person struct { + Name string `form:"name"` + Address string `form:"address"` +} + +func main() { + route := gin.Default() + route.Any("/testing", startPage) + route.Run(":8085") +} + +func startPage(c *gin.Context) { + var person Person + if c.ShouldBindQuery(&person) == nil { + log.Println("====== Only Bind By Query String ======") + log.Println(person.Name) + log.Println(person.Address) + } + c.String(200, "Success") +} + +``` + +### Bind Query String or Post Data + +See the [detail information](https://github.com/gin-gonic/gin/issues/742#issuecomment-264681292). + +```go +package main + +import ( + "log" + "time" + + "github.com/gin-gonic/gin" +) + +type Person struct { + Name string `form:"name"` + Address string `form:"address"` + Birthday time.Time `form:"birthday" time_format:"2006-01-02" time_utc:"1"` +} + +func main() { + route := gin.Default() + route.GET("/testing", startPage) + route.Run(":8085") +} + +func startPage(c *gin.Context) { + var person Person + // If `GET`, only `Form` binding engine (`query`) used. + // If `POST`, first checks the `content-type` for `JSON` or `XML`, then uses `Form` (`form-data`). + // See more at https://github.com/gin-gonic/gin/blob/master/binding/binding.go#L48 + if c.ShouldBind(&person) == nil { + log.Println(person.Name) + log.Println(person.Address) + log.Println(person.Birthday) + } + + c.String(200, "Success") +} +``` + +Test it with: +```sh +$ curl -X GET "localhost:8085/testing?name=appleboy&address=xyz&birthday=1992-03-15" +``` + +### Bind Uri + +See the [detail information](https://github.com/gin-gonic/gin/issues/846). + +```go +package main + +import "github.com/gin-gonic/gin" + +type Person struct { + ID string `uri:"id" binding:"required,uuid"` + Name string `uri:"name" binding:"required"` +} + +func main() { + route := gin.Default() + route.GET("/:name/:id", func(c *gin.Context) { + var person Person + if err := c.ShouldBindUri(&person); err != nil { + c.JSON(400, gin.H{"msg": err}) + return + } + c.JSON(200, gin.H{"name": person.Name, "uuid": person.ID}) + }) + route.Run(":8088") +} +``` + +Test it with: +```sh +$ curl -v localhost:8088/thinkerou/987fbc97-4bed-5078-9f07-9141ba07c9f3 +$ curl -v localhost:8088/thinkerou/not-uuid +``` + +### Bind HTML checkboxes + +See the [detail information](https://github.com/gin-gonic/gin/issues/129#issuecomment-124260092) + +main.go + +```go +... + +type myForm struct { + Colors []string `form:"colors[]"` +} + +... + +func formHandler(c *gin.Context) { + var fakeForm myForm + c.ShouldBind(&fakeForm) + c.JSON(200, gin.H{"color": fakeForm.Colors}) +} + +... + +``` + +form.html + +```html +
+

Check some colors

+ + + + + + + +
+``` + +result: + +``` +{"color":["red","green","blue"]} +``` + +### Multipart/Urlencoded binding + +```go +package main + +import ( + "github.com/gin-gonic/gin" +) + +type LoginForm struct { + User string `form:"user" binding:"required"` + Password string `form:"password" binding:"required"` +} + +func main() { + router := gin.Default() + router.POST("/login", func(c *gin.Context) { + // you can bind multipart form with explicit binding declaration: + // c.ShouldBindWith(&form, binding.Form) + // or you can simply use autobinding with ShouldBind method: + var form LoginForm + // in this case proper binding will be automatically selected + if c.ShouldBind(&form) == nil { + if form.User == "user" && form.Password == "password" { + c.JSON(200, gin.H{"status": "you are logged in"}) + } else { + c.JSON(401, gin.H{"status": "unauthorized"}) + } + } + }) + router.Run(":8080") +} +``` + +Test it with: +```sh +$ curl -v --form user=user --form password=password http://localhost:8080/login +``` + +### XML, JSON, YAML and ProtoBuf rendering + +```go +func main() { + r := gin.Default() + + // gin.H is a shortcut for map[string]interface{} + r.GET("/someJSON", func(c *gin.Context) { + c.JSON(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK}) + }) + + r.GET("/moreJSON", func(c *gin.Context) { + // You also can use a struct + var msg struct { + Name string `json:"user"` + Message string + Number int + } + msg.Name = "Lena" + msg.Message = "hey" + msg.Number = 123 + // Note that msg.Name becomes "user" in the JSON + // Will output : {"user": "Lena", "Message": "hey", "Number": 123} + c.JSON(http.StatusOK, msg) + }) + + r.GET("/someXML", func(c *gin.Context) { + c.XML(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK}) + }) + + r.GET("/someYAML", func(c *gin.Context) { + c.YAML(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK}) + }) + + r.GET("/someProtoBuf", func(c *gin.Context) { + reps := []int64{int64(1), int64(2)} + label := "test" + // The specific definition of protobuf is written in the testdata/protoexample file. + data := &protoexample.Test{ + Label: &label, + Reps: reps, + } + // Note that data becomes binary data in the response + // Will output protoexample.Test protobuf serialized data + c.ProtoBuf(http.StatusOK, data) + }) + + // Listen and serve on 0.0.0.0:8080 + r.Run(":8080") +} +``` + +#### SecureJSON + +Using SecureJSON to prevent json hijacking. Default prepends `"while(1),"` to response body if the given struct is array values. + +```go +func main() { + r := gin.Default() + + // You can also use your own secure json prefix + // r.SecureJsonPrefix(")]}',\n") + + r.GET("/someJSON", func(c *gin.Context) { + names := []string{"lena", "austin", "foo"} + + // Will output : while(1);["lena","austin","foo"] + c.SecureJSON(http.StatusOK, names) + }) + + // Listen and serve on 0.0.0.0:8080 + r.Run(":8080") +} +``` +#### JSONP + +Using JSONP to request data from a server in a different domain. Add callback to response body if the query parameter callback exists. + +```go +func main() { + r := gin.Default() + + r.GET("/JSONP?callback=x", func(c *gin.Context) { + data := map[string]interface{}{ + "foo": "bar", + } + + //callback is x + // Will output : x({\"foo\":\"bar\"}) + c.JSONP(http.StatusOK, data) + }) + + // Listen and serve on 0.0.0.0:8080 + r.Run(":8080") +} +``` + +#### AsciiJSON + +Using AsciiJSON to Generates ASCII-only JSON with escaped non-ASCII chracters. + +```go +func main() { + r := gin.Default() + + r.GET("/someJSON", func(c *gin.Context) { + data := map[string]interface{}{ + "lang": "GO语言", + "tag": "
", + } + + // will output : {"lang":"GO\u8bed\u8a00","tag":"\u003cbr\u003e"} + c.AsciiJSON(http.StatusOK, data) + }) + + // Listen and serve on 0.0.0.0:8080 + r.Run(":8080") +} +``` + +#### PureJSON + +Normally, JSON replaces special HTML characters with their unicode entities, e.g. `<` becomes `\u003c`. If you want to encode such characters literally, you can use PureJSON instead. +This feature is unavailable in Go 1.6 and lower. + +```go +func main() { + r := gin.Default() + + // Serves unicode entities + r.GET("/json", func(c *gin.Context) { + c.JSON(200, gin.H{ + "html": "Hello, world!", + }) + }) + + // Serves literal characters + r.GET("/purejson", func(c *gin.Context) { + c.PureJSON(200, gin.H{ + "html": "Hello, world!", + }) + }) + + // listen and serve on 0.0.0.0:8080 + r.Run(":8080") +} +``` + +### Serving static files + +```go +func main() { + router := gin.Default() + router.Static("/assets", "./assets") + router.StaticFS("/more_static", http.Dir("my_file_system")) + router.StaticFile("/favicon.ico", "./resources/favicon.ico") + + // Listen and serve on 0.0.0.0:8080 + router.Run(":8080") +} +``` + +### Serving data from reader + +```go +func main() { + router := gin.Default() + router.GET("/someDataFromReader", func(c *gin.Context) { + response, err := http.Get("https://raw.githubusercontent.com/gin-gonic/logo/master/color.png") + if err != nil || response.StatusCode != http.StatusOK { + c.Status(http.StatusServiceUnavailable) + return + } + + reader := response.Body + contentLength := response.ContentLength + contentType := response.Header.Get("Content-Type") + + extraHeaders := map[string]string{ + "Content-Disposition": `attachment; filename="gopher.png"`, + } + + c.DataFromReader(http.StatusOK, contentLength, contentType, reader, extraHeaders) + }) + router.Run(":8080") +} +``` + +### HTML rendering + +Using LoadHTMLGlob() or LoadHTMLFiles() + +```go +func main() { + router := gin.Default() + router.LoadHTMLGlob("templates/*") + //router.LoadHTMLFiles("templates/template1.html", "templates/template2.html") + router.GET("/index", func(c *gin.Context) { + c.HTML(http.StatusOK, "index.tmpl", gin.H{ + "title": "Main website", + }) + }) + router.Run(":8080") +} +``` + +templates/index.tmpl + +```html + +

+ {{ .title }} +

+ +``` + +Using templates with same name in different directories + +```go +func main() { + router := gin.Default() + router.LoadHTMLGlob("templates/**/*") + router.GET("/posts/index", func(c *gin.Context) { + c.HTML(http.StatusOK, "posts/index.tmpl", gin.H{ + "title": "Posts", + }) + }) + router.GET("/users/index", func(c *gin.Context) { + c.HTML(http.StatusOK, "users/index.tmpl", gin.H{ + "title": "Users", + }) + }) + router.Run(":8080") +} +``` + +templates/posts/index.tmpl + +```html +{{ define "posts/index.tmpl" }} +

+ {{ .title }} +

+

Using posts/index.tmpl

+ +{{ end }} +``` + +templates/users/index.tmpl + +```html +{{ define "users/index.tmpl" }} +

+ {{ .title }} +

+

Using users/index.tmpl

+ +{{ end }} +``` + +#### Custom Template renderer + +You can also use your own html template render + +```go +import "html/template" + +func main() { + router := gin.Default() + html := template.Must(template.ParseFiles("file1", "file2")) + router.SetHTMLTemplate(html) + router.Run(":8080") +} +``` + +#### Custom Delimiters + +You may use custom delims + +```go + r := gin.Default() + r.Delims("{[{", "}]}") + r.LoadHTMLGlob("/path/to/templates") +``` + +#### Custom Template Funcs + +See the detail [example code](https://github.com/gin-gonic/examples/tree/master/template). + +main.go + +```go +import ( + "fmt" + "html/template" + "net/http" + "time" + + "github.com/gin-gonic/gin" +) + +func formatAsDate(t time.Time) string { + year, month, day := t.Date() + return fmt.Sprintf("%d%02d/%02d", year, month, day) +} + +func main() { + router := gin.Default() + router.Delims("{[{", "}]}") + router.SetFuncMap(template.FuncMap{ + "formatAsDate": formatAsDate, + }) + router.LoadHTMLFiles("./testdata/template/raw.tmpl") + + router.GET("/raw", func(c *gin.Context) { + c.HTML(http.StatusOK, "raw.tmpl", map[string]interface{}{ + "now": time.Date(2017, 07, 01, 0, 0, 0, 0, time.UTC), + }) + }) + + router.Run(":8080") +} + +``` + +raw.tmpl + +```html +Date: {[{.now | formatAsDate}]} +``` + +Result: +``` +Date: 2017/07/01 +``` + +### Multitemplate + +Gin 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`. + +### Redirects + +Issuing a HTTP redirect is easy. Both internal and external locations are supported. + +```go +r.GET("/test", func(c *gin.Context) { + c.Redirect(http.StatusMovedPermanently, "http://www.google.com/") +}) +``` + + +Issuing a Router redirect, use `HandleContext` like below. + +``` go +r.GET("/test", func(c *gin.Context) { + c.Request.URL.Path = "/test2" + r.HandleContext(c) +}) +r.GET("/test2", func(c *gin.Context) { + c.JSON(200, gin.H{"hello": "world"}) +}) +``` + + +### Custom Middleware + +```go +func Logger() gin.HandlerFunc { + return func(c *gin.Context) { + t := time.Now() + + // Set example variable + c.Set("example", "12345") + + // before request + + c.Next() + + // after request + latency := time.Since(t) + log.Print(latency) + + // access the status we are sending + status := c.Writer.Status() + log.Println(status) + } +} + +func main() { + r := gin.New() + r.Use(Logger()) + + r.GET("/test", func(c *gin.Context) { + example := c.MustGet("example").(string) + + // it would print: "12345" + log.Println(example) + }) + + // Listen and serve on 0.0.0.0:8080 + r.Run(":8080") +} +``` + +### Using BasicAuth() middleware + +```go +// simulate some private data +var secrets = gin.H{ + "foo": gin.H{"email": "foo@bar.com", "phone": "123433"}, + "austin": gin.H{"email": "austin@example.com", "phone": "666"}, + "lena": gin.H{"email": "lena@guapa.com", "phone": "523443"}, +} + +func main() { + r := gin.Default() + + // Group using gin.BasicAuth() middleware + // gin.Accounts is a shortcut for map[string]string + authorized := r.Group("/admin", gin.BasicAuth(gin.Accounts{ + "foo": "bar", + "austin": "1234", + "lena": "hello2", + "manu": "4321", + })) + + // /admin/secrets endpoint + // hit "localhost:8080/admin/secrets + authorized.GET("/secrets", func(c *gin.Context) { + // get user, it was set by the BasicAuth middleware + user := c.MustGet(gin.AuthUserKey).(string) + if secret, ok := secrets[user]; ok { + c.JSON(http.StatusOK, gin.H{"user": user, "secret": secret}) + } else { + c.JSON(http.StatusOK, gin.H{"user": user, "secret": "NO SECRET :("}) + } + }) + + // Listen and serve on 0.0.0.0:8080 + r.Run(":8080") +} +``` + +### Goroutines inside a middleware + +When starting new Goroutines inside a middleware or handler, you **SHOULD NOT** use the original context inside it, you have to use a read-only copy. + +```go +func main() { + r := gin.Default() + + r.GET("/long_async", func(c *gin.Context) { + // create copy to be used inside the goroutine + cCp := c.Copy() + go func() { + // simulate a long task with time.Sleep(). 5 seconds + time.Sleep(5 * time.Second) + + // note that you are using the copied context "cCp", IMPORTANT + log.Println("Done! in path " + cCp.Request.URL.Path) + }() + }) + + r.GET("/long_sync", func(c *gin.Context) { + // simulate a long task with time.Sleep(). 5 seconds + time.Sleep(5 * time.Second) + + // since we are NOT using a goroutine, we do not have to copy the context + log.Println("Done! in path " + c.Request.URL.Path) + }) + + // Listen and serve on 0.0.0.0:8080 + r.Run(":8080") +} +``` + +### Custom HTTP configuration + +Use `http.ListenAndServe()` directly, like this: + +```go +func main() { + router := gin.Default() + http.ListenAndServe(":8080", router) +} +``` +or + +```go +func main() { + router := gin.Default() + + s := &http.Server{ + Addr: ":8080", + Handler: router, + ReadTimeout: 10 * time.Second, + WriteTimeout: 10 * time.Second, + MaxHeaderBytes: 1 << 20, + } + s.ListenAndServe() +} +``` + +### Support Let's Encrypt + +example for 1-line LetsEncrypt HTTPS servers. + +```go +package main + +import ( + "log" + + "github.com/gin-gonic/autotls" + "github.com/gin-gonic/gin" +) + +func main() { + r := gin.Default() + + // Ping handler + r.GET("/ping", func(c *gin.Context) { + c.String(200, "pong") + }) + + log.Fatal(autotls.Run(r, "example1.com", "example2.com")) +} +``` + +example for custom autocert manager. + +```go +package main + +import ( + "log" + + "github.com/gin-gonic/autotls" + "github.com/gin-gonic/gin" + "golang.org/x/crypto/acme/autocert" +) + +func main() { + r := gin.Default() + + // Ping handler + r.GET("/ping", func(c *gin.Context) { + c.String(200, "pong") + }) + + m := autocert.Manager{ + Prompt: autocert.AcceptTOS, + HostPolicy: autocert.HostWhitelist("example1.com", "example2.com"), + Cache: autocert.DirCache("/var/www/.cache"), + } + + log.Fatal(autotls.RunWithManager(r, &m)) +} +``` + +### Run multiple service using Gin + +See the [question](https://github.com/gin-gonic/gin/issues/346) and try the following example: + +```go +package main + +import ( + "log" + "net/http" + "time" + + "github.com/gin-gonic/gin" + "golang.org/x/sync/errgroup" +) + +var ( + g errgroup.Group +) + +func router01() http.Handler { + e := gin.New() + e.Use(gin.Recovery()) + e.GET("/", func(c *gin.Context) { + c.JSON( + http.StatusOK, + gin.H{ + "code": http.StatusOK, + "error": "Welcome server 01", + }, + ) + }) + + return e +} + +func router02() http.Handler { + e := gin.New() + e.Use(gin.Recovery()) + e.GET("/", func(c *gin.Context) { + c.JSON( + http.StatusOK, + gin.H{ + "code": http.StatusOK, + "error": "Welcome server 02", + }, + ) + }) + + return e +} + +func main() { + server01 := &http.Server{ + Addr: ":8080", + Handler: router01(), + ReadTimeout: 5 * time.Second, + WriteTimeout: 10 * time.Second, + } + + server02 := &http.Server{ + Addr: ":8081", + Handler: router02(), + ReadTimeout: 5 * time.Second, + WriteTimeout: 10 * time.Second, + } + + g.Go(func() error { + return server01.ListenAndServe() + }) + + g.Go(func() error { + return server02.ListenAndServe() + }) + + if err := g.Wait(); err != nil { + log.Fatal(err) + } +} +``` + +### Graceful restart or stop + +Do you want to graceful restart or stop your web server? +There are some ways this can be done. + +We 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. + +```go +router := gin.Default() +router.GET("/", handler) +// [...] +endless.ListenAndServe(":4242", router) +``` + +An alternative to endless: + +* [manners](https://github.com/braintree/manners): A polite Go HTTP server that shuts down gracefully. +* [graceful](https://github.com/tylerb/graceful): Graceful is a Go package enabling graceful shutdown of an http.Handler server. +* [grace](https://github.com/facebookgo/grace): Graceful restart & zero downtime deploy for Go servers. + +If you are using Go 1.8, you may not need to use this library! Consider using http.Server's built-in [Shutdown()](https://golang.org/pkg/net/http/#Server.Shutdown) method for graceful shutdowns. See the full [graceful-shutdown](https://github.com/gin-gonic/examples/tree/master/graceful-shutdown) example with gin. + +```go +// +build go1.8 + +package main + +import ( + "context" + "log" + "net/http" + "os" + "os/signal" + "syscall" + "time" + + "github.com/gin-gonic/gin" +) + +func main() { + router := gin.Default() + router.GET("/", func(c *gin.Context) { + time.Sleep(5 * time.Second) + c.String(http.StatusOK, "Welcome Gin Server") + }) + + srv := &http.Server{ + Addr: ":8080", + Handler: router, + } + + go func() { + // service connections + if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { + log.Fatalf("listen: %s\n", err) + } + }() + + // Wait for interrupt signal to gracefully shutdown the server with + // a timeout of 5 seconds. + quit := make(chan os.Signal) + // kill (no param) default send syscanll.SIGTERM + // kill -2 is syscall.SIGINT + // kill -9 is syscall. SIGKILL but can"t be catch, so don't need add it + signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) + <-quit + log.Println("Shutdown Server ...") + + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + if err := srv.Shutdown(ctx); err != nil { + log.Fatal("Server Shutdown:", err) + } + // catching ctx.Done(). timeout of 5 seconds. + select { + case <-ctx.Done(): + log.Println("timeout of 5 seconds.") + } + log.Println("Server exiting") +} +``` + +### Build a single binary with templates + +You can build a server into a single binary containing templates by using [go-assets][]. + +[go-assets]: https://github.com/jessevdk/go-assets + +```go +func main() { + r := gin.New() + + t, err := loadTemplate() + if err != nil { + panic(err) + } + r.SetHTMLTemplate(t) + + r.GET("/", func(c *gin.Context) { + c.HTML(http.StatusOK, "/html/index.tmpl",nil) + }) + r.Run(":8080") +} + +// loadTemplate loads templates embedded by go-assets-builder +func loadTemplate() (*template.Template, error) { + t := template.New("") + for name, file := range Assets.Files { + if file.IsDir() || !strings.HasSuffix(name, ".tmpl") { + continue + } + h, err := ioutil.ReadAll(file) + if err != nil { + return nil, err + } + t, err = t.New(name).Parse(string(h)) + if err != nil { + return nil, err + } + } + return t, nil +} +``` + +See a complete example in the `https://github.com/gin-gonic/examples/tree/master/assets-in-binary` directory. + +### Bind form-data request with custom struct + +The follow example using custom struct: + +```go +type StructA struct { + FieldA string `form:"field_a"` +} + +type StructB struct { + NestedStruct StructA + FieldB string `form:"field_b"` +} + +type StructC struct { + NestedStructPointer *StructA + FieldC string `form:"field_c"` +} + +type StructD struct { + NestedAnonyStruct struct { + FieldX string `form:"field_x"` + } + FieldD string `form:"field_d"` +} + +func GetDataB(c *gin.Context) { + var b StructB + c.Bind(&b) + c.JSON(200, gin.H{ + "a": b.NestedStruct, + "b": b.FieldB, + }) +} + +func GetDataC(c *gin.Context) { + var b StructC + c.Bind(&b) + c.JSON(200, gin.H{ + "a": b.NestedStructPointer, + "c": b.FieldC, + }) +} + +func GetDataD(c *gin.Context) { + var b StructD + c.Bind(&b) + c.JSON(200, gin.H{ + "x": b.NestedAnonyStruct, + "d": b.FieldD, + }) +} + +func main() { + r := gin.Default() + r.GET("/getb", GetDataB) + r.GET("/getc", GetDataC) + r.GET("/getd", GetDataD) + + r.Run() +} +``` + +Using the command `curl` command result: + +``` +$ curl "http://localhost:8080/getb?field_a=hello&field_b=world" +{"a":{"FieldA":"hello"},"b":"world"} +$ curl "http://localhost:8080/getc?field_a=hello&field_c=world" +{"a":{"FieldA":"hello"},"c":"world"} +$ curl "http://localhost:8080/getd?field_x=hello&field_d=world" +{"d":"world","x":{"FieldX":"hello"}} +``` + +### Try to bind body into different structs + +The normal methods for binding request body consumes `c.Request.Body` and they +cannot be called multiple times. + +```go +type formA struct { + Foo string `json:"foo" xml:"foo" binding:"required"` +} + +type formB struct { + Bar string `json:"bar" xml:"bar" binding:"required"` +} + +func SomeHandler(c *gin.Context) { + objA := formA{} + objB := formB{} + // This c.ShouldBind consumes c.Request.Body and it cannot be reused. + if errA := c.ShouldBind(&objA); errA == nil { + c.String(http.StatusOK, `the body should be formA`) + // Always an error is occurred by this because c.Request.Body is EOF now. + } else if errB := c.ShouldBind(&objB); errB == nil { + c.String(http.StatusOK, `the body should be formB`) + } else { + ... + } +} +``` + +For this, you can use `c.ShouldBindBodyWith`. + +```go +func SomeHandler(c *gin.Context) { + objA := formA{} + objB := formB{} + // This reads c.Request.Body and stores the result into the context. + if errA := c.ShouldBindBodyWith(&objA, binding.JSON); errA == nil { + c.String(http.StatusOK, `the body should be formA`) + // At this time, it reuses body stored in the context. + } else if errB := c.ShouldBindBodyWith(&objB, binding.JSON); errB == nil { + c.String(http.StatusOK, `the body should be formB JSON`) + // And it can accepts other formats + } else if errB2 := c.ShouldBindBodyWith(&objB, binding.XML); errB2 == nil { + c.String(http.StatusOK, `the body should be formB XML`) + } else { + ... + } +} +``` + +* `c.ShouldBindBodyWith` stores body into the context before binding. This has +a slight impact to performance, so you should not use this method if you are +enough to call binding at once. +* This feature is only needed for some formats -- `JSON`, `XML`, `MsgPack`, +`ProtoBuf`. For other formats, `Query`, `Form`, `FormPost`, `FormMultipart`, +can be called by `c.ShouldBind()` multiple times without any damage to +performance (See [#1341](https://github.com/gin-gonic/gin/pull/1341)). + +### http2 server push + +http.Pusher is supported only **go1.8+**. See the [golang blog](https://blog.golang.org/h2push) for detail information. + +```go +package main + +import ( + "html/template" + "log" + + "github.com/gin-gonic/gin" +) + +var html = template.Must(template.New("https").Parse(` + + + Https Test + + + +

Welcome, Ginner!

+ + +`)) + +func main() { + r := gin.Default() + r.Static("/assets", "./assets") + r.SetHTMLTemplate(html) + + r.GET("/", func(c *gin.Context) { + if pusher := c.Writer.Pusher(); pusher != nil { + // use pusher.Push() to do server push + if err := pusher.Push("/assets/app.js", nil); err != nil { + log.Printf("Failed to push: %v", err) + } + } + c.HTML(200, "https", gin.H{ + "status": "success", + }) + }) + + // Listen and Server in https://127.0.0.1:8080 + r.RunTLS(":8080", "./testdata/server.pem", "./testdata/server.key") +} +``` + +### Define format for the log of routes + +The default log of routes is: +``` +[GIN-debug] POST /foo --> main.main.func1 (3 handlers) +[GIN-debug] GET /bar --> main.main.func2 (3 handlers) +[GIN-debug] GET /status --> main.main.func3 (3 handlers) +``` + +If you want to log this information in given format (e.g. JSON, key values or something else), then you can define this format with `gin.DebugPrintRouteFunc`. +In the example below, we log all routes with standard log package but you can use another log tools that suits of your needs. +```go +import ( + "log" + "net/http" + + "github.com/gin-gonic/gin" +) + +func main() { + r := gin.Default() + gin.DebugPrintRouteFunc = func(httpMethod, absolutePath, handlerName string, nuHandlers int) { + log.Printf("endpoint %v %v %v %v\n", httpMethod, absolutePath, handlerName, nuHandlers) + } + + r.POST("/foo", func(c *gin.Context) { + c.JSON(http.StatusOK, "foo") + }) + + r.GET("/bar", func(c *gin.Context) { + c.JSON(http.StatusOK, "bar") + }) + + r.GET("/status", func(c *gin.Context) { + c.JSON(http.StatusOK, "ok") + }) + + // Listen and Server in http://0.0.0.0:8080 + r.Run() +} +``` + +### Set and get a cookie + +```go +import ( + "fmt" + + "github.com/gin-gonic/gin" +) + +func main() { + + router := gin.Default() + + router.GET("/cookie", func(c *gin.Context) { + + cookie, err := c.Cookie("gin_cookie") + + if err != nil { + cookie = "NotSet" + c.SetCookie("gin_cookie", "test", 3600, "/", "localhost", false, true) + } + + fmt.Printf("Cookie value: %s \n", cookie) + }) + + router.Run() +} +``` + + +## Testing + +The `net/http/httptest` package is preferable way for HTTP testing. + +```go +package main + +func setupRouter() *gin.Engine { + r := gin.Default() + r.GET("/ping", func(c *gin.Context) { + c.String(200, "pong") + }) + return r +} + +func main() { + r := setupRouter() + r.Run(":8080") +} +``` + +Test for code example above: + +```go +package main + +import ( + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestPingRoute(t *testing.T) { + router := setupRouter() + + w := httptest.NewRecorder() + req, _ := http.NewRequest("GET", "/ping", nil) + router.ServeHTTP(w, req) + + assert.Equal(t, 200, w.Code) + assert.Equal(t, "pong", w.Body.String()) +} +``` ## Users -[Gin website](https://gin-gonic.com/docs/users/) lists some awesome projects made with Gin web framework. - -## Contributing - -Gin is the work of hundreds of contributors. We appreciate your help! - -Please see [CONTRIBUTING](CONTRIBUTING.md) for details on submitting patches and the contribution workflow. +Awesome project lists using [Gin](https://github.com/gin-gonic/gin) web framework. +* [gorush](https://github.com/appleboy/gorush): A push notification server written in Go. +* [fnproject](https://github.com/fnproject/fn): The container native, cloud agnostic serverless platform. +* [photoprism](https://github.com/photoprism/photoprism): Personal photo management powered by Go and Google TensorFlow. +* [krakend](https://github.com/devopsfaith/krakend): Ultra performant API Gateway with middlewares. +* [picfit](https://github.com/thoas/picfit): An image resizing server written in Go.