From 8e9619767c12607afcb086a5e9c791eda2b9e116 Mon Sep 17 00:00:00 2001 From: forging2012 Date: Wed, 31 Oct 2018 20:19:58 +0800 Subject: [PATCH 01/46] FIX r.LoadHTMLGlob("/path/to/templates") (#1616) FIX r.LoadHTMLGlob("/path/to/templates")) to r.LoadHTMLGlob("/path/to/templates") --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1b5fb493..ce413325 100644 --- a/README.md +++ b/README.md @@ -1160,7 +1160,7 @@ You may use custom delims ```go r := gin.Default() r.Delims("{[{", "}]}") - r.LoadHTMLGlob("/path/to/templates")) + r.LoadHTMLGlob("/path/to/templates") ``` #### Custom Template Funcs From 8fb21a8beff46febac15894200e381473889f0e6 Mon Sep 17 00:00:00 2001 From: "root@andrea:~#" Date: Thu, 1 Nov 2018 01:30:19 -0600 Subject: [PATCH 02/46] Added some comments to avoid having golint warnings (#1619) The following comments to vars, conts and method were added to pass `golinter` with 100%. ![captura de pantalla 2018-10-31 a la s 15 23 37](https://user-images.githubusercontent.com/10160626/47819725-faba3780-dd20-11e8-978c-1b3ab7de26ed.png) --- errors.go | 6 ++++-- mode.go | 4 +++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/errors.go b/errors.go index 477b9d58..ab13ca61 100644 --- a/errors.go +++ b/errors.go @@ -24,9 +24,10 @@ const ( ErrorTypePrivate ErrorType = 1 << 0 // ErrorTypePublic indicates a public error. ErrorTypePublic ErrorType = 1 << 1 - // ErrorTypeAny indicates other any error. + // ErrorTypeAny indicates any other error. ErrorTypeAny ErrorType = 1<<64 - 1 - ErrorTypeNu = 2 + // ErrorTypeNu indicates any other error. + ErrorTypeNu = 2 ) // Error represents a error's specification. @@ -52,6 +53,7 @@ func (msg *Error) SetMeta(data interface{}) *Error { return msg } +// JSON creates a properly formated JSON func (msg *Error) JSON() interface{} { json := H{} if msg.Meta != nil { diff --git a/mode.go b/mode.go index 7cb0143a..6a696329 100644 --- a/mode.go +++ b/mode.go @@ -28,7 +28,7 @@ const ( testCode ) -// DefaultWriter is the default io.Writer used the Gin for debug output and +// DefaultWriter is the default io.Writer used by Gin for debug output and // middleware output like Logger() or Recovery(). // Note that both Logger and Recovery provides custom ways to configure their // output io.Writer. @@ -36,6 +36,8 @@ const ( // import "github.com/mattn/go-colorable" // gin.DefaultWriter = colorable.NewColorableStdout() var DefaultWriter io.Writer = os.Stdout + +// DefaultErrorWriter is the default io.Writer used by Gin to debug errors var DefaultErrorWriter io.Writer = os.Stderr var ginMode = debugCode From 6f7fe487b38baec254343376df603adecd201f10 Mon Sep 17 00:00:00 2001 From: Barnabus Date: Thu, 1 Nov 2018 18:05:40 +1000 Subject: [PATCH 03/46] Change HTML input tags to use HTML5 syntax. (#1617) In XHTML, the tag must be properly closed, like this ``. In HTML5 the `` tag has no ending slash. https://www.w3schools.com/tags/tag_input.asp --- README.md | 8 ++++---- .../realtime-advanced/resources/room_login.templ.html | 8 ++++---- examples/realtime-chat/template.go | 6 +++--- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index ce413325..8c2c2c5b 100644 --- a/README.md +++ b/README.md @@ -824,12 +824,12 @@ form.html

Check some colors

- + - + - - + +
``` diff --git a/examples/realtime-advanced/resources/room_login.templ.html b/examples/realtime-advanced/resources/room_login.templ.html index 27dac387..5fb2b334 100644 --- a/examples/realtime-advanced/resources/room_login.templ.html +++ b/examples/realtime-advanced/resources/room_login.templ.html @@ -79,19 +79,19 @@
{{.nick}}
- +
- + {{else}}
Join the SSE real-time chat
- +
- +
{{end}} diff --git a/examples/realtime-chat/template.go b/examples/realtime-chat/template.go index b9024de6..4190251d 100644 --- a/examples/realtime-chat/template.go +++ b/examples/realtime-chat/template.go @@ -35,9 +35,9 @@ var html = template.Must(template.New("chat_room").Parse(`

Welcome to {{.roomid}} room

- User: - Message: - + User: + Message: +
From 6be9b5437b0c1092a94800c449988699a24b3f51 Mon Sep 17 00:00:00 2001 From: Barnabus Date: Thu, 1 Nov 2018 23:48:26 +1000 Subject: [PATCH 04/46] Change HTML link tags to use HTML5 syntax. (#1621) The `` element is an empty element, it contains attributes only. In HTML5 the `` tag has no end tag. In XHTML the `` tag must be properly closed. --- examples/realtime-advanced/resources/room_login.templ.html | 2 +- examples/realtime-chat/template.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/realtime-advanced/resources/room_login.templ.html b/examples/realtime-advanced/resources/room_login.templ.html index 5fb2b334..d4991d8d 100644 --- a/examples/realtime-advanced/resources/room_login.templ.html +++ b/examples/realtime-advanced/resources/room_login.templ.html @@ -20,7 +20,7 @@ - + + + +

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") +} +``` + +### 定义路由日志的格式 + +路由的默认日志是: +``` +[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) +``` + +如果要以给定格式记录此信息(例如JSON,键值或其他内容),则可以使用`gin.DebugPrintRouteFunc`定义此格式。 +在下面的示例中,我们使用标准日志包记录所有路由,但您可以使用其他适合您需求的日志工具。 +```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() +} +``` + + +## 测试 + +`net / http / httptest`包是HTTP测试的首选方式。 + +```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") +} +``` + +测试上面的代码示例: + +```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()) +} +``` + +## 用户 + + +使用[Gin](https://github.com/gin-gonic/gin)Web框架的令人敬畏的项目列表。 + +* [drone](https://github.com/drone/drone):drone,用Go编写。 +* [gorush](https://github.com/appleboy/gorush):用Go编写的推送通知服务器。 +* [fnproject](https://github.com/fnproject/fn):容器本机,云无关的无服务器平台。 From 1f576fb27c3f3ec00baa0d2868e41830e37d0b54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=B9=E5=AE=9D=E5=BC=BA?= Date: Fri, 23 Nov 2018 09:46:41 +0800 Subject: [PATCH 19/46] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E8=BF=9E=E6=8E=A5?= =?UTF-8?q?=E9=94=99=E8=AF=AF=EF=BC=8C=E4=BF=AE=E6=AD=A3=E4=B8=80=E4=BA=9B?= =?UTF-8?q?=E6=8A=A5=E5=BC=95=E7=94=A8=E9=94=99=E8=AF=AF=20(#1655)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 修复了全角括号导致超链接不能正常访问的错误。 修复了一些URL中的"/"被改成" / "的错误。 修复了一些包引用中"/"被改成" / "的错误。 修复有超链接被翻译成中文的错误。 --- README_ZH.md | 68 ++++++++++++++++++++++++++-------------------------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/README_ZH.md b/README_ZH.md index d411f9eb..c689ba28 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -79,7 +79,7 @@ $ go get -u github.com/gin-gonic/gin import "github.com/gin-gonic/gin" ``` -3. (可选)如果使用诸如`http.StatusOK`之类的常量,则需要引入 `net / http` 包。 +3. (可选)如果使用诸如`http.StatusOK`之类的常量,则需要引入 `net/http` 包。 ```go import "net/http" @@ -200,7 +200,7 @@ BenchmarkVulcan_GithubAll | 5000 | 394253 | 19894 ## 使用 [jsoniter](https://github.com/json-iterator/go) 构建 -Gin使用`encoding / json`作为默认的json包,但您可以通过其他标签的构建更改为[jsoniter](https://github.com/json-iterator/go)。 +Gin使用`encoding/json`作为默认的json包,但您可以通过其他标签的构建更改为[jsoniter](https://github.com/json-iterator/go)。 ```sh $ go build -tags=jsoniter . @@ -359,7 +359,7 @@ ids: map[b:hello a:1234], names: map[second:tianou first:thinkerou] #### 单个文件上传 -参考问题[#774](https://github.com/gin-gonic/gin/issues/774)和详细[示例代码](examples / upload-file / single)。 +参考问题[#774](https://github.com/gin-gonic/gin/issues/774)和详细[示例代码](examples/upload-file/single)。 ```go func main() { @@ -390,7 +390,7 @@ curl -X POST http://localhost:8080/upload \ #### 多文件上传 -查看详细信息[示例代码](examples / upload-file / multiple)。 +查看详细信息[示例代码](examples/upload-file/multiple)。 ```go func main() { @@ -530,14 +530,14 @@ func main() { 要将请求主体绑定到类型中,请使用模型绑定。我们目前支持JSON,XML和标准表单值的绑定(foo = bar&boo = baz)。 -Gin使用[** go-playground / validator.v8 **](https://github.com/go-playground/validator)进行验证。检查有关标签用法的完整文档[此处](http://godoc.org/gopkg.in/go-playground/validator.v8#hdr-Baked_In_Validators_and_Tags)。 +Gin使用[** go-playground/validator.v8 **](https://github.com/go-playground/validator)进行验证。检查有关标签用法的完整文档[此处](http://godoc.org/gopkg.in/go-playground/validator.v8#hdr-Baked_In_Validators_and_Tags)。 请注意,您需要在要绑定的所有字段上设置相应的绑定标记。例如,从JSON绑定时,设置`json:“fieldname”`。 此外,Gin提供了两组绑定方法: - **类型** - 必须绑定    - **方法** - `Bind`,`BindJSON`,`BindXML`,`BindQuery` -   - **行为** - 这些方法在引擎盖下使用`MustBindWith`。如果存在绑定错误,则使用`c.AbortWithError(400,err).SetType(ErrorTypeBind)`中止请求。这将响应状态代码设置为400,并将`Content-Type`标头设置为`text / plain;字符集= UTF-8`。请注意,如果您在此之后尝试设置响应代码,则会发出警告“[GIN-debug] [警告]标题已经写入。想用422`覆盖状态代码400。如果您希望更好地控制行为,请考虑使用`ShouldBind`等效方法。 +   - **行为** - 这些方法在引擎盖下使用`MustBindWith`。如果存在绑定错误,则使用`c.AbortWithError(400,err).SetType(ErrorTypeBind)`中止请求。这将响应状态代码设置为400,并将`Content-Type`标头设置为`text/plain;字符集= UTF-8`。请注意,如果您在此之后尝试设置响应代码,则会发出警告“[GIN-debug] [警告]标题已经写入。想用422`覆盖状态代码400。如果您希望更好地控制行为,请考虑使用`ShouldBind`等效方法。 - **类型** - 应该绑定    - **方法** - `ShouldBind`,`ShouldBindJSON`,`ShouldBindXML`,`ShouldBindQuery`    - **行为** - 这些方法在引擎盖下使用`ShouldBindWith`。如果存在绑定错误,则返回错误,开发人员有责任正确处理请求和错误。 @@ -643,9 +643,9 @@ $ curl -v -X POST \ ### 自定义验证器 -也可以注册自定义验证器。 请参阅[示例代码](examples / custom-validation / server.go)。 +也可以注册自定义验证器。 请参阅[示例代码](examples/custom-validation/server.go)。 -[embedmd]:#(examples / custom-validation / server.go go) +[embedmd]:#(examples/custom-validation/server.go go) ```go package main @@ -707,12 +707,12 @@ $ 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"} ``` -[结构级验证](https://github.com/go-playground/validator/releases/tag/v8.7)也可以这种方式注册。 -请参阅[struct-lvl-validation示例](examples / struct-lvl-validations)以了解更多信息。 +[结构级验证](https://github.com/go-playground/validator/releases/tag/v8.7)也可以这种方式注册。 +请参阅[struct-lvl-validation示例](examples/struct-lvl-validations)以了解更多信息。 ### 只绑定查询字符串 -`ShouldBindQuery` 函数只绑定查询参数而不是后期数据。 请参阅[详细信息](https://github.com/gin-gonic/gin/issues/742#issuecomment-315953017)。 +`ShouldBindQuery` 函数只绑定查询参数而不是后期数据。 请参阅[详细信息](https://github.com/gin-gonic/gin/issues/742#issuecomment-315953017)。 ```go package main @@ -748,7 +748,7 @@ func startPage(c *gin.Context) { ### 绑定查询字符串或发布数据 -请参阅[详细信息](https://github.com/gin-gonic/gin/issues/742#issuecomment-264681292)。 +请参阅[详细信息](https://github.com/gin-gonic/gin/issues/742#issuecomment-264681292)。 ```go package main @@ -794,7 +794,7 @@ $ curl -X GET "localhost:8085/testing?name=appleboy&address=xyz&birthday=1992-03 ### 绑定 HTML 复选框 -参见[详细信息](https://github.com/gin-gonic/gin/issues/129#issuecomment-124260092) +参见[详细信息](https://github.com/gin-gonic/gin/issues/129#issuecomment-124260092) main.go @@ -931,7 +931,7 @@ func main() { #### SecureJSON -使用 SecureJSON 来防止 json 劫持。 如果给定的结构是数组值,则默认预 置`“while(1),”` 到响应体。 +使用 SecureJSON 来防止 json 劫持。 如果给定的结构是数组值,则默认预 置`“while(1),”` 到响应体。 ```go func main() { @@ -1067,7 +1067,7 @@ func main() { ### HTML 渲染 -使用LoadHTMLGlob()或LoadHTMLFiles() +使用LoadHTMLGlob()或LoadHTMLFiles() ```go func main() { @@ -1159,12 +1159,12 @@ func main() { ```go r := gin.Default() r.Delims("{[{", "}]}") - r.LoadHTMLGlob("/path/to/templates")) + r.LoadHTMLGlob("/path/to/templates") ``` #### 自定义模板功能 -查看详细信息[示例代码](示例/模板)。 +查看详细信息[示例代码](examples/template)。 main.go @@ -1215,7 +1215,7 @@ Date: 2017/07/01 ### 多模板 -Gin 允许默认只使用一个 html 模板。 检查[多模板渲染](https://github.com/gin-contrib/multitemplate)以使用 go 1.6 `block template` 等功能。 +Gin 允许默认只使用一个 html 模板。 检查[多模板渲染](https://github.com/gin-contrib/multitemplate)以使用 go 1.6 `block template` 等功能。 ### 重定向 @@ -1355,7 +1355,7 @@ func main() { ### 自定义 HTTP 配置 -直接使用`http.ListenAndServe()`,如下所示: +直接使用`http.ListenAndServe()`,如下所示: ```go func main() { @@ -1441,7 +1441,7 @@ func main() { ### 使用 Gin 运行多个服务 -请参阅[问题](https://github.com/gin-gonic/gin/issues/346)并尝试以下示例: +请参阅[问题](https://github.com/gin-gonic/gin/issues/346)并尝试以下示例: [embedmd]:# (examples/multiple-service/main.go go) ```go @@ -1526,7 +1526,7 @@ func main() { 您想要优雅地重启或停止您的Web服务器吗? 有一些方法可以做到这一点。 -我们可以使用[fvbock / endless](https://github.com/fvbock/endless)来替换默认的`ListenAndServe`。 有关更多详细信息,请参阅问题[#296](https://github.com/gin-gonic/gin/issues/296)。 +我们可以使用[fvbock/endless](https://github.com/fvbock/endless)来替换默认的`ListenAndServe`。 有关更多详细信息,请参阅问题[#296](https://github.com/gin-gonic/gin/issues/296)。 ```go router := gin.Default() @@ -1537,11 +1537,11 @@ endless.ListenAndServe(":4242", router) 另一种替代方案: -* [manners](https://github.com/braintree/manners):礼貌的Go HTTP服务器,可以正常关闭。 -* [graceful](https://github.com/tylerb/graceful):Graceful是一个Go包,可以正常关闭http.Handler服务器。 -* [grace](https://github.com/facebookgo/grace):Go服务器的平滑重启和零停机时间部署。 +* [manners](https://github.com/braintree/manners):礼貌的Go HTTP服务器,可以正常关闭。 +* [graceful](https://github.com/tylerb/graceful):Graceful是一个Go包,可以正常关闭http.Handler服务器。 +* [grace](https://github.com/facebookgo/grace):Go服务器的平滑重启和零停机时间部署。 -如果您使用的是Go 1.8,则可能不需要使用此库! 考虑使用http.Server的内置[Shutdown()](https://golang.org/pkg/net/http/#Server.Shutdown)方法进行正常关机。 请参阅gin的完整[graceful-shutdown](./ examples / graceful-shutdown)示例。 +如果您使用的是Go 1.8,则可能不需要使用此库! 考虑使用http.Server的内置[Shutdown()](https://golang.org/pkg/net/http/#Server.Shutdown)方法进行正常关机。 请参阅gin的完整[graceful-shutdown](./examples /graceful-shutdown)示例。 [embedmd]:# (examples/graceful-shutdown/graceful-shutdown/server.go go) ```go @@ -1637,7 +1637,7 @@ func loadTemplate() (*template.Template, error) { } ``` -请参阅`examples / assets-in-binary`目录中的完整示例。 +请参阅`examples/assets-in-binary`目录中的完整示例。 ### Bind form-data request with custom struct @@ -1788,13 +1788,13 @@ func SomeHandler(c *gin.Context) { 足以立刻调用绑定。 *只有某些格式需要此功能 - “JSON”,“XML”,“MsgPack”, `ProtoBuf`。 对于其他格式,`Query`,`Form`,`FormPost`,`FormMultipart`, -可以被`c.ShouldBind()`多次调用而不会造成任何损害 -表现(见[#1341](https://github.com/gin-gonic/gin/pull/1341))。 +可以被`c.ShouldBind()`多次调用而不会造成任何损害 +表现(见[#1341](https://github.com/gin-gonic/gin/pull/1341)。 ### http2 server 推送 -http.Pusher仅支持** go1.8 + **。 有关详细信息,请参阅[golang blog](https://blog.golang.org/h2push)。 +http.Pusher仅支持** go1.8 + **。 有关详细信息,请参阅[golang blog](https://blog.golang.org/h2push)。 [embedmd]:# (examples/http-pusher/main.go go) ```go @@ -1886,7 +1886,7 @@ func main() { ## 测试 -`net / http / httptest`包是HTTP测试的首选方式。 +`net/http/httptest`包是HTTP测试的首选方式。 ```go package main @@ -1933,8 +1933,8 @@ func TestPingRoute(t *testing.T) { ## 用户 -使用[Gin](https://github.com/gin-gonic/gin)Web框架的令人敬畏的项目列表。 +使用[Gin](https://github.com/gin-gonic/gin)Web框架的令人尊敬的项目列表。 -* [drone](https://github.com/drone/drone):drone,用Go编写。 -* [gorush](https://github.com/appleboy/gorush):用Go编写的推送通知服务器。 -* [fnproject](https://github.com/fnproject/fn):容器本机,云无关的无服务器平台。 +* [drone](https://github.com/drone/drone):drone,用Go编写。 +* [gorush](https://github.com/appleboy/gorush):用Go编写的推送通知服务器。 +* [fnproject](https://github.com/fnproject/fn):容器本机,云无关的无服务器平台。 From f52bea87f60248c00e55f7599d9584f9bc8e8978 Mon Sep 17 00:00:00 2001 From: weibaohui Date: Sat, 24 Nov 2018 19:15:19 +0800 Subject: [PATCH 20/46] =?UTF-8?q?=E8=B0=83=E6=95=B4=E6=8F=8F=E8=BF=B0?= =?UTF-8?q?=E8=AF=AD=E5=8F=A5=20(#1657)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 调整描述语句 --- README_ZH.md | 95 +++++++++++++++++++++++++--------------------------- 1 file changed, 46 insertions(+), 49 deletions(-) diff --git a/README_ZH.md b/README_ZH.md index c689ba28..7867c1f7 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -25,20 +25,20 @@ Gin 是一个 Go (Golang) 语言框架。 它是一个拥有更好性能的 mart - [使用 jsoniter 构建](#使用-jsoniter-构建) - [API 示例](#api-示例) - [GET,POST,PUT,PATCH,DELETE,OPTIONS 使用](#get-post-put-patch-delete-options-使用) - - [路由参数](#路由参数) - - [查询字符串参数](#查询字符串参数) + - [获取路由参数](#获取路由参数) + - [获取url查询参数](#获取url查询参数) - [Multipart Urlencoded 表单](#multipart-urlencoded-表单) - - [另一个实列 query + post form](#另一个实列:-query-+-post-form) + - [获取post表单数据(url带查询参数)](#获取post表单数据(url带查询参数)) - [映射参数 表单参数](#映射参数-表单参数) - [上传文件](#上传文件) - [路由组](#路由组) - [默认初始化 Gin](#默认初始化-gin) - - [中间件使用](#中间件使用) + - [使用中间件](#使用中间件) - [如何记录日志](#如何记录日志) - [模型绑定和验证](#模型绑定和验证) - [自定义验证器](#自定义验证器) - - [只绑定查询字符串](#只绑定查询字符串) - - [绑定查询字符串或发布数据](#绑定查询字符串或发布数据) + - [只绑定url查询参数](#只绑定url查询参数) + - [url查询参数绑定到struct(或POST表单数据)](#url查询参数绑定到struct(或POST表单数据)) - [绑定 HTML 复选框](#绑定-html-复选框) - [Multipart Urlencoded 绑定](#multipart-urlencoded-绑定) - [XML JSON YAML ProtoBuf 渲染](#xml-json-yaml-protobuf-渲染) @@ -56,8 +56,8 @@ Gin 是一个 Go (Golang) 语言框架。 它是一个拥有更好性能的 mart - [使用 Gin 运行多个服务](使用-gin-运行多个服务) - [优雅重启或停止](#优雅重启或停止) - [使用模板构建单个二进制文件](#使用模板构建单个二进制文件) - - [使用自定义结构绑定表单数据请求](#使用自定义结构绑定表单数据请求) - - [尝试将body绑定到不同的结构中](#尝试将-body-绑定到不同的结构中) + - [表单数据绑定到自定义结构体](#表单数据绑定到自定义结构体) + - [将request body绑定到不同的结构体中](#将request body绑定到不同的结构体中) - [http2 server 推送](#http2-server-推送) - [定义路由日志的格式](#定义路由日志的格式) - [测试](#测试) @@ -105,7 +105,7 @@ $ govendor init $ govendor fetch github.com/gin-gonic/gin@v1.3 ``` -4. 复制一个启动文件模板到项目目录中 +4. 复制启动文件模板到项目目录中 ```sh $ curl https://raw.githubusercontent.com/gin-gonic/gin/master/examples/basic/main.go > main.go @@ -119,7 +119,7 @@ $ go run main.go ## 前提条件 -新版本的 Gin 需要 Go 1.6 或者更高版本并且很快就会升级到 Go 1.7. +新版本的 Gin 需要 Go 1.6 或者更高版本,并且很快就会升级到 Go 1.7. ## 快速启动 @@ -234,7 +234,7 @@ func main() { } ``` -### 路由参数 +### 获取路由参数 ```go func main() { @@ -259,7 +259,7 @@ func main() { } ``` -### 查询字符串参数 +### 获取url查询参数 ```go func main() { @@ -297,7 +297,7 @@ func main() { } ``` -### 另一个实列 query + post form +### 获取post表单数据(url带查询参数) ``` POST /post?id=1234&page=1 HTTP/1.1 @@ -380,7 +380,7 @@ func main() { } ``` -如何 `curl`: +`curl`示例: ```bash curl -X POST http://localhost:8080/upload \ @@ -414,7 +414,7 @@ func main() { } ``` -如何 `curl`: +`curl`示例: ```bash curl -X POST http://localhost:8080/upload \ @@ -465,7 +465,7 @@ r := gin.Default() ``` -### 中间件使用 +### 使用中间件 ```go func main() { // Creates a router without any middleware by default @@ -710,9 +710,9 @@ $ curl "localhost:8085/bookable?check_in=2018-03-08&check_out=2018-03-09" [结构级验证](https://github.com/go-playground/validator/releases/tag/v8.7)也可以这种方式注册。 请参阅[struct-lvl-validation示例](examples/struct-lvl-validations)以了解更多信息。 -### 只绑定查询字符串 +### 只绑定url查询参数 -`ShouldBindQuery` 函数只绑定查询参数而不是后期数据。 请参阅[详细信息](https://github.com/gin-gonic/gin/issues/742#issuecomment-315953017)。 +`ShouldBindQuery` 函数只绑定url查询参数而不是post字段。 请参阅[详细信息](https://github.com/gin-gonic/gin/issues/742#issuecomment-315953017)。 ```go package main @@ -746,7 +746,7 @@ func startPage(c *gin.Context) { ``` -### 绑定查询字符串或发布数据 +### url查询参数绑定到struct(或POST表单数据) 请参阅[详细信息](https://github.com/gin-gonic/gin/issues/742#issuecomment-264681292)。 @@ -872,7 +872,7 @@ func main() { } ``` -Test it with: +测试: ```sh $ curl -v --form user=user --form password=password http://localhost:8080/login ``` @@ -999,8 +999,8 @@ func main() { #### PureJSON -通常,JSON 用其 unicod e实体替换特殊 HTML 字符,例如 `<` 变为 `\ u003c`。 如果要按字面意思对这些字符进行编码,则可以使用 PureJSON。 -Go 1.6 及更低版本无法使用此功能。 +通常,JSON使用unicode替换特殊HTML字符,例如 `<` 变为 `\ u003c`。 如果要按字面意思对这些字符进行编码,则可以使用 PureJSON。 +Go 1.6及更低版本无法使用此功能。 ```go func main() { @@ -1382,7 +1382,7 @@ func main() { ### Let's Encrypt 支持 -单行 LetsEncrypt HTTPS 服务器的示例。 +一行代码支持 LetsEncrypt HTTPS示例。 [embedmd]:# (examples/auto-tls/example1/main.go go) ```go @@ -1407,7 +1407,7 @@ func main() { } ``` -自定义autocert管理器的示例。 +autocert使用示例。 [embedmd]:# (examples/auto-tls/example2/main.go go) ```go @@ -1535,13 +1535,13 @@ router.GET("/", handler) endless.ListenAndServe(":4242", router) ``` -另一种替代方案: +替代方案: * [manners](https://github.com/braintree/manners):礼貌的Go HTTP服务器,可以正常关闭。 * [graceful](https://github.com/tylerb/graceful):Graceful是一个Go包,可以正常关闭http.Handler服务器。 * [grace](https://github.com/facebookgo/grace):Go服务器的平滑重启和零停机时间部署。 -如果您使用的是Go 1.8,则可能不需要使用此库! 考虑使用http.Server的内置[Shutdown()](https://golang.org/pkg/net/http/#Server.Shutdown)方法进行正常关机。 请参阅gin的完整[graceful-shutdown](./examples /graceful-shutdown)示例。 +如果您使用的是Go 1.8,可以考虑使用http.Server内置[Shutdown()](https://golang.org/pkg/net/http/#Server.Shutdown)方法进行正常关机。 请参阅gin的完整[graceful-shutdown](./examples /graceful-shutdown)示例。 [embedmd]:# (examples/graceful-shutdown/graceful-shutdown/server.go go) ```go @@ -1597,7 +1597,7 @@ func main() { ### 使用模板构建单个二进制文件 -您可以使用[go-assets] []将服务器构建到包含模板的单个二进制文件中。 +您可以使用[go-assets]将服务器打包为包含模板的单个二进制可执行文件。 [go-assets]:https://github.com/jessevdk/go-assets @@ -1639,7 +1639,7 @@ func loadTemplate() (*template.Template, error) { 请参阅`examples/assets-in-binary`目录中的完整示例。 -### Bind form-data request with custom struct +### 表单数据绑定到自定义结构体 以下示例使用自定义结构: @@ -1731,10 +1731,9 @@ type StructZ struct { 总之,只支持现在没有`form`的嵌套自定义结构。 -### 尝试将 body 绑定到不同的结构中 +### 将request body绑定到不同的结构体中 -绑定请求体的常规方法使用`c.Request.Body`和它们 -不能多次调用。 +一般通过调用`c.Request.Body`方法绑定数据,但不能多次调用这个方法。 ```go type formA struct { @@ -1760,7 +1759,7 @@ func SomeHandler(c *gin.Context) { } ``` -为此,您可以使用`c.ShouldBindBodyWith`. +为此,要想多次绑定,需要使用`c.ShouldBindBodyWith`. ```go func SomeHandler(c *gin.Context) { @@ -1782,14 +1781,11 @@ func SomeHandler(c *gin.Context) { ``` -482/5000 -*`c.ShouldBindBodyWith`在绑定之前将body存储到上下文中。 这有 -对性能有轻微影响,所以如果你这样做,你不应该使用这种方法 -足以立刻调用绑定。 -*只有某些格式需要此功能 - “JSON”,“XML”,“MsgPack”, -`ProtoBuf`。 对于其他格式,`Query`,`Form`,`FormPost`,`FormMultipart`, -可以被`c.ShouldBind()`多次调用而不会造成任何损害 -表现(见[#1341](https://github.com/gin-gonic/gin/pull/1341)。 +*`c.ShouldBindBodyWith`会在绑定之前将body存储到上下文中。 这会 +对性能造成轻微影响,如果调用一次就能完成绑定的话,那就不要用这个方法。 +*只有某些格式需要此功能 ,如“JSON”,“XML”,“MsgPack”, +`ProtoBuf`。 对于其他格式,如`Query`,`Form`,`FormPost`,`FormMultipart`, +可以多次调用`c.ShouldBind()`而不会造成任任何性能损失(见[#1341](https://github.com/gin-gonic/gin/pull/1341))。 ### http2 server 推送 @@ -1843,15 +1839,15 @@ func main() { ### 定义路由日志的格式 -路由的默认日志是: +默认的路由日志格式: ``` [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) ``` -如果要以给定格式记录此信息(例如JSON,键值或其他内容),则可以使用`gin.DebugPrintRouteFunc`定义此格式。 -在下面的示例中,我们使用标准日志包记录所有路由,但您可以使用其他适合您需求的日志工具。 +如果要以指的格式(例如JSON,Key Values或其他格式)记录信息,则可以使用`gin.DebugPrintRouteFunc`指定格式。 +在下面的示例中,我们使用标准日志包记录所有路由,但您可以使用其他满足需求的日志工具。 ```go import ( "log" @@ -1886,7 +1882,7 @@ func main() { ## 测试 -`net/http/httptest`包是HTTP测试的首选方式。 +HTTP测试首选`net/http/httptest`包。 ```go package main @@ -1905,7 +1901,7 @@ func main() { } ``` -测试上面的代码示例: +上面这段代码的测试用例: ```go package main @@ -1933,8 +1929,9 @@ func TestPingRoute(t *testing.T) { ## 用户 -使用[Gin](https://github.com/gin-gonic/gin)Web框架的令人尊敬的项目列表。 +使用[Gin](https://github.com/gin-gonic/gin)框架的著名项目。 -* [drone](https://github.com/drone/drone):drone,用Go编写。 -* [gorush](https://github.com/appleboy/gorush):用Go编写的推送通知服务器。 -* [fnproject](https://github.com/fnproject/fn):容器本机,云无关的无服务器平台。 +* [drone](https://github.com/drone/drone):用Go编写的基于docker的持续集成平台。 +* [gorush](https://github.com/appleboy/gorush):用Go编写的推送通知服务。 +* [fnproject](https://github.com/fnproject/fn):容器驱动、云无关的无服务器平台。 +* [photoprism](https://github.com/photoprism/photoprism): 用Go编写的基于TensorFlow的个人相册管理系统. From 331af2219c9e6376e39221c6e85255fbfc64dcc8 Mon Sep 17 00:00:00 2001 From: thinkerou Date: Sat, 24 Nov 2018 20:49:26 +0800 Subject: [PATCH 21/46] add krakend to gin user list (#1658) --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 3f0c9170..0f74d30a 100644 --- a/README.md +++ b/README.md @@ -2000,3 +2000,4 @@ Awesome project lists using [Gin](https://github.com/gin-gonic/gin) web framewor * [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. From 687d8b9ac6178c31b0b1119cc669e6062a59bc93 Mon Sep 17 00:00:00 2001 From: thinkerou Date: Sun, 25 Nov 2018 20:52:46 +0800 Subject: [PATCH 22/46] add picfit to gin user list (#1661) agreed with the project's author. cc @thoas thanks! --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 0f74d30a..e7b92b2d 100644 --- a/README.md +++ b/README.md @@ -2001,3 +2001,4 @@ Awesome project lists using [Gin](https://github.com/gin-gonic/gin) web framewor * [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 465ead47d0f40a24b067798f5bab1c263f09660d Mon Sep 17 00:00:00 2001 From: weibaohui Date: Sun, 25 Nov 2018 21:18:00 +0800 Subject: [PATCH 23/46] doc: update README_ZH.md (#1659) --- README_ZH.md | 166 ++++++++++++++++++++++++++++++++++----------------- 1 file changed, 111 insertions(+), 55 deletions(-) diff --git a/README_ZH.md b/README_ZH.md index 7867c1f7..8c9f8abb 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -19,16 +19,16 @@ Gin 是一个 Go (Golang) 语言框架。 它是一个拥有更好性能的 mart - [安装](#安装) - [前提条件](#前提条件) -- [快速启动](#快速启动) +- [快速开始](#快速开始) - [性能测试](#性能测试) - [Gin v1 稳定版](#gin-v1-稳定版) -- [使用 jsoniter 构建](#使用-jsoniter-构建) +- [使用 jsoniter ](#使用-jsoniter) - [API 示例](#api-示例) - [GET,POST,PUT,PATCH,DELETE,OPTIONS 使用](#get-post-put-patch-delete-options-使用) - [获取路由参数](#获取路由参数) - [获取url查询参数](#获取url查询参数) - [Multipart Urlencoded 表单](#multipart-urlencoded-表单) - - [获取post表单数据(url带查询参数)](#获取post表单数据(url带查询参数)) + - [获取post表单数据(url带查询参数)](#获取post表单数据url带查询参数) - [映射参数 表单参数](#映射参数-表单参数) - [上传文件](#上传文件) - [路由组](#路由组) @@ -38,28 +38,30 @@ Gin 是一个 Go (Golang) 语言框架。 它是一个拥有更好性能的 mart - [模型绑定和验证](#模型绑定和验证) - [自定义验证器](#自定义验证器) - [只绑定url查询参数](#只绑定url查询参数) - - [url查询参数绑定到struct(或POST表单数据)](#url查询参数绑定到struct(或POST表单数据)) + - [url查询参数或表单数据绑定到结构体](#url查询参数或表单数据绑定到结构体) + - [url路径参数绑定](#url路径参数绑定) - [绑定 HTML 复选框](#绑定-html-复选框) - [Multipart Urlencoded 绑定](#multipart-urlencoded-绑定) - [XML JSON YAML ProtoBuf 渲染](#xml-json-yaml-protobuf-渲染) - [SecureJSON](#SecureJSON) - [静态文件服务](#静态文件服务) - - [从读者服务数据](#从读者服务数据) + - [从reader 读取数据](#从-reader-读取数据) - [HTML 渲染](#html-渲染) - [多模板](#多模板) - [重定向](#重定向) - [自定义中间件](#自定义中间件) - [使用 BasicAuth() 中间件](#使用-basicauth()-中间件) - - [Goroutines](#goroutines) + - [在中间件中使用Goroutines](#在中间件中使用Goroutines) - [自定义 HTTP 配置](#自定义-http-配置) - [Let's Encrypt 支持](#lets-encrypt-支持) - [使用 Gin 运行多个服务](使用-gin-运行多个服务) - [优雅重启或停止](#优雅重启或停止) - - [使用模板构建单个二进制文件](#使用模板构建单个二进制文件) + - [静态资源嵌入](#静态资源嵌入) - [表单数据绑定到自定义结构体](#表单数据绑定到自定义结构体) - - [将request body绑定到不同的结构体中](#将request body绑定到不同的结构体中) + - [将request body绑定到不同的结构体中](#将request-body绑定到不同的结构体中) - [http2 server 推送](#http2-server-推送) - [定义路由日志的格式](#定义路由日志的格式) + - [如何使用Cookie](#如何使用Cookie) - [测试](#测试) - [用户](#用户) @@ -119,9 +121,9 @@ $ go run main.go ## 前提条件 -新版本的 Gin 需要 Go 1.6 或者更高版本,并且很快就会升级到 Go 1.7. +新版本的 Gin 需要 Go 1.6 或者更高版本,并且很快就会要求升级到 Go 1.7. -## 快速启动 +## 快速开始 ```sh # assume the following codes in example.go file @@ -192,15 +194,15 @@ BenchmarkVulcan_GithubAll | 5000 | 394253 | 19894 ## Gin v1 稳定版 -- [x] 零分配路由器。 +- [x] 零分配路由。 - [x] 仍然是最快的http路由器和框架。 -- [x] 完整的单元测试套件 +- [x] 完整的单元测试支持 - [x] 对战测试 -- [x] API冻结,新版本不会破坏您的代码。 +- [x] API冻结,使用新版本不需要修改原有代码。 -## 使用 [jsoniter](https://github.com/json-iterator/go) 构建 +## 使用 [jsoniter](https://github.com/json-iterator/go) -Gin使用`encoding/json`作为默认的json包,但您可以通过其他标签的构建更改为[jsoniter](https://github.com/json-iterator/go)。 +Gin默认使用`encoding/json`解析json数据,但您可以通过`go build -tags=`更改为使用[jsoniter](https://github.com/json-iterator/go)。 ```sh $ go build -tags=jsoniter . @@ -208,7 +210,7 @@ $ go build -tags=jsoniter . ## API 示例 -### GET, POST, PUT, PATCH, DELETE , OPTIONS 使用 +### 使用GET, POST, PUT, PATCH, DELETE , OPTIONS ```go func main() { @@ -297,7 +299,7 @@ func main() { } ``` -### 获取post表单数据(url带查询参数) +### 获取post表单数据(url带查询参数) ``` POST /post?id=1234&page=1 HTTP/1.1 @@ -528,23 +530,23 @@ func main() { ### 模型绑定和验证 -要将请求主体绑定到类型中,请使用模型绑定。我们目前支持JSON,XML和标准表单值的绑定(foo = bar&boo = baz)。 +要将请求主体绑定到结构体中,请使用模型绑定。Gin目前支持JSON、XML、YAML和标准表单值的绑定(foo=bar&boo=baz)。 -Gin使用[** go-playground/validator.v8 **](https://github.com/go-playground/validator)进行验证。检查有关标签用法的完整文档[此处](http://godoc.org/gopkg.in/go-playground/validator.v8#hdr-Baked_In_Validators_and_Tags)。 +Gin使用[go-playground/validator.v8](https://github.com/go-playground/validator)进行验证。[完整文档](http://godoc.org/gopkg.in/go-playground/validator.v8#hdr-Baked_In_Validators_and_Tags)。 -请注意,您需要在要绑定的所有字段上设置相应的绑定标记。例如,从JSON绑定时,设置`json:“fieldname”`。 +使用时,需要在要绑定的所有字段上,设置相应的tag。例如,使用JSON绑定时,字段tag设置为`json:"fieldname"`。 -此外,Gin提供了两组绑定方法: -- **类型** - 必须绑定 -   - **方法** - `Bind`,`BindJSON`,`BindXML`,`BindQuery` -   - **行为** - 这些方法在引擎盖下使用`MustBindWith`。如果存在绑定错误,则使用`c.AbortWithError(400,err).SetType(ErrorTypeBind)`中止请求。这将响应状态代码设置为400,并将`Content-Type`标头设置为`text/plain;字符集= UTF-8`。请注意,如果您在此之后尝试设置响应代码,则会发出警告“[GIN-debug] [警告]标题已经写入。想用422`覆盖状态代码400。如果您希望更好地控制行为,请考虑使用`ShouldBind`等效方法。 -- **类型** - 应该绑定 +Gin提供了两类绑定方法: +- **MustBind** - + - **方法** - `Bind`, `BindJSON`, `BindXML`, `BindQuery`, `BindYAML` + - **说明** - 这些方法属于`MustBindWith`的具体调用。如果发生绑定错误,则请求终止,并触发`c.AbortWithError(400,err).SetType(ErrorTypeBind)`。响应状态码被设置为400,`Content-Type`被设置为`text/plain;charset= UTF-8`。如果您在此之后尝试设置响应状态码,Gin会输出日志“ `[GIN-debug] [WARNING] Headers were already written. Wanted to override status code 400 with 422`。如果您希望更好地把控绑定,请考虑使用`ShouldBind`等效方法。 +- **ShouldBind** -    - **方法** - `ShouldBind`,`ShouldBindJSON`,`ShouldBindXML`,`ShouldBindQuery` -   - **行为** - 这些方法在引擎盖下使用`ShouldBindWith`。如果存在绑定错误,则返回错误,开发人员有责任正确处理请求和错误。 + - **说明** - 这些方法属于`ShouldBindWith`的具体调用。如果发生绑定错误,Gin会返回错误。由您处理错误以及请求。 -使用Bind方法时,Gin会尝试根据Content-Type标头推断出绑定器。如果你确定你绑定了什么,你可以使用 `MustBindWith` 或 `ShouldBindWith`。 +使用Bind方法时,Gin会根据Content-Type尝试推断如何绑定,如果您明确知道,您可以使用 `MustBindWith` 或 `ShouldBindWith`。 -您还可以指定需要特定字段。如果字段用 `binding:“必需”` 来装饰,并且在绑定时具有空值,则会返回错误。 +指定必须绑定的字段,在该字段Tag上加上`binding:"required"` ,如果绑定时是空值,Gin会报错。 ```go // Binding from JSON @@ -615,7 +617,7 @@ func main() { } ``` -**简单请求** +**测试** ```shell $ curl -v -X POST \ http://localhost:8080/loginJSON \ @@ -637,15 +639,14 @@ $ curl -v -X POST \ {"error":"Key: 'Login.Password' Error:Field validation for 'Password' failed on the 'required' tag"} ``` -**跳过验证** +**忽略验证** -使用上面 的`curl` 命令运行上面的例子时,它返回错误。 因为这个例子使用 `binding:'需要``````。 如果使用 `binding:“ - ``````,那么在再次运行上面的例子时它不会返回错误。 +使用`curl` 命令运行上面的例子时,它返回错误,因为这个例子使用`binding:"required"` 。 如果使用 `binding:"-"` ,那么再次运行上面的例子时它不会返回错误。 ### 自定义验证器 -也可以注册自定义验证器。 请参阅[示例代码](examples/custom-validation/server.go)。 +注册自定义验证器, 请参阅[示例代码](examples/custom-validation/server.go)。 -[embedmd]:#(examples/custom-validation/server.go go) ```go package main @@ -707,7 +708,7 @@ $ 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"} ``` -[结构级验证](https://github.com/go-playground/validator/releases/tag/v8.7)也可以这种方式注册。 +结构体验证也可以参考[这种方法](https://github.com/go-playground/validator/releases/tag/v8.7)注册。 请参阅[struct-lvl-validation示例](examples/struct-lvl-validations)以了解更多信息。 ### 只绑定url查询参数 @@ -746,7 +747,7 @@ func startPage(c *gin.Context) { ``` -### url查询参数绑定到struct(或POST表单数据) +### url查询参数或表单数据绑定到结构体 请参阅[详细信息](https://github.com/gin-gonic/gin/issues/742#issuecomment-264681292)。 @@ -791,6 +792,39 @@ func startPage(c *gin.Context) { ```sh $ curl -X GET "localhost:8085/testing?name=appleboy&address=xyz&birthday=1992-03-15" ``` +### url路径参数绑定 + +查看[详细信息](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") +} +``` + +测试: +```sh +$ curl -v localhost:8088/thinkerou/987fbc97-4bed-5078-9f07-9141ba07c9f3 +$ curl -v localhost:8088/thinkerou/not-uuid +``` ### 绑定 HTML 复选框 @@ -1320,9 +1354,9 @@ func main() { } ``` -### Goroutines +### 在中间件中使用Goroutines -当在中间件或处理程序中启动新的 Goroutines 时,你不应该**使用其中的原始上下文,你必须使用只读副本。 +当在中间件或handler中启动新的Goroutines时,不能使用原始上下文,必须使用只读副本。 ```go func main() { @@ -1541,7 +1575,8 @@ endless.ListenAndServe(":4242", router) * [graceful](https://github.com/tylerb/graceful):Graceful是一个Go包,可以正常关闭http.Handler服务器。 * [grace](https://github.com/facebookgo/grace):Go服务器的平滑重启和零停机时间部署。 -如果您使用的是Go 1.8,可以考虑使用http.Server内置[Shutdown()](https://golang.org/pkg/net/http/#Server.Shutdown)方法进行正常关机。 请参阅gin的完整[graceful-shutdown](./examples /graceful-shutdown)示例。 +如果您使用的是Go 1.8,可以考虑使用http.Server的内置[Shutdown()](https://golang.org/pkg/net/http/#Server.Shutdown)方法进行正常关机。 +请参阅gin的完整[graceful-shutdown](/examples /graceful-shutdown)示例。 [embedmd]:# (examples/graceful-shutdown/graceful-shutdown/server.go go) ```go @@ -1595,11 +1630,9 @@ func main() { } ``` -### 使用模板构建单个二进制文件 +### 静态资源嵌入 -您可以使用[go-assets]将服务器打包为包含模板的单个二进制可执行文件。 - -[go-assets]:https://github.com/jessevdk/go-assets +使用[go-assets](https://github.com/jessevdk/go-assets)将静态资源打包到可执行文件中。 ```go func main() { @@ -1641,7 +1674,7 @@ func loadTemplate() (*template.Template, error) { ### 表单数据绑定到自定义结构体 -以下示例使用自定义结构: +以下示例使用自定义结构体: ```go type StructA struct { @@ -1702,7 +1735,7 @@ func main() { } ``` -使用命令`curl`命令结果: +`curl`命令示例: ``` $ curl "http://localhost:8080/getb?field_a=hello&field_b=world" @@ -1713,7 +1746,7 @@ $ curl "http://localhost:8080/getd?field_x=hello&field_d=world" {"d":"world","x":{"FieldX":"hello"}} ``` -**注意**:不支持以下样式结构: +**注意**:不支持以下格式结构体: ```go type StructX struct { @@ -1729,7 +1762,7 @@ type StructZ struct { } ``` -总之,只支持现在没有`form`的嵌套自定义结构。 +一句话,现在只支持没有`form`的嵌套结构体。 ### 将request body绑定到不同的结构体中 @@ -1781,16 +1814,13 @@ func SomeHandler(c *gin.Context) { ``` -*`c.ShouldBindBodyWith`会在绑定之前将body存储到上下文中。 这会 -对性能造成轻微影响,如果调用一次就能完成绑定的话,那就不要用这个方法。 -*只有某些格式需要此功能 ,如“JSON”,“XML”,“MsgPack”, -`ProtoBuf`。 对于其他格式,如`Query`,`Form`,`FormPost`,`FormMultipart`, -可以多次调用`c.ShouldBind()`而不会造成任任何性能损失(见[#1341](https://github.com/gin-gonic/gin/pull/1341))。 +* `c.ShouldBindBodyWith`会在绑定之前将body存储到上下文中。 这会对性能造成轻微影响,如果调用一次就能完成绑定的话,那就不要用这个方法。 +* 只有某些格式需要此功能 ,如`JSON`、`XML`、`MsgPack`、`ProtoBuf`。 对于其他格式,如`Query`、`Form`、`FormPost`、`FormMultipart`可以多次调用`c.ShouldBind()`而不会造成任任何性能损失(见[#1341](https://github.com/gin-gonic/gin/pull/1341))。 ### http2 server 推送 -http.Pusher仅支持** go1.8 + **。 有关详细信息,请参阅[golang blog](https://blog.golang.org/h2push)。 +http.Pusher仅支持go1.8 +。 有关详细信息,请参阅[golang blog](https://blog.golang.org/h2push)。 [embedmd]:# (examples/http-pusher/main.go go) ```go @@ -1878,7 +1908,34 @@ func main() { r.Run() } ``` +### 如何使用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() +} +``` ## 测试 @@ -1927,11 +1984,10 @@ func TestPingRoute(t *testing.T) { ``` ## 用户 - - -使用[Gin](https://github.com/gin-gonic/gin)框架的著名项目。 +使用[Gin](https://github.com/gin-gonic/gin)框架的著名项目: * [drone](https://github.com/drone/drone):用Go编写的基于docker的持续集成平台。 * [gorush](https://github.com/appleboy/gorush):用Go编写的推送通知服务。 * [fnproject](https://github.com/fnproject/fn):容器驱动、云无关的无服务器平台。 * [photoprism](https://github.com/photoprism/photoprism): 用Go编写的基于TensorFlow的个人相册管理系统. +* [krakend](https://github.com/devopsfaith/krakend): 表现优异的API网关中间件。 From 149ef75cdd1848d76b0bc2a205898a5eb53cb059 Mon Sep 17 00:00:00 2001 From: thinkerou Date: Mon, 26 Nov 2018 21:05:54 +0800 Subject: [PATCH 24/46] doc: remove README_ZH.md (#1667) --- README_ZH.md | 1993 -------------------------------------------------- 1 file changed, 1993 deletions(-) delete mode 100644 README_ZH.md diff --git a/README_ZH.md b/README_ZH.md deleted file mode 100644 index 8c9f8abb..00000000 --- a/README_ZH.md +++ /dev/null @@ -1,1993 +0,0 @@ -# Gin Web 框架中文文档 - - - -[![Build Status](https://travis-ci.org/gin-gonic/gin.svg)](https://travis-ci.org/gin-gonic/gin) -[![codecov](https://codecov.io/gh/gin-gonic/gin/branch/master/graph/badge.svg)](https://codecov.io/gh/gin-gonic/gin) -[![Go Report Card](https://goreportcard.com/badge/github.com/gin-gonic/gin)](https://goreportcard.com/report/github.com/gin-gonic/gin) -[![GoDoc](https://godoc.org/github.com/gin-gonic/gin?status.svg)](https://godoc.org/github.com/gin-gonic/gin) -[![Join the chat at https://gitter.im/gin-gonic/gin](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/gin-gonic/gin?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) -[![Sourcegraph](https://sourcegraph.com/github.com/gin-gonic/gin/-/badge.svg)](https://sourcegraph.com/github.com/gin-gonic/gin?badge) -[![Open Source Helpers](https://www.codetriage.com/gin-gonic/gin/badges/users.svg)](https://www.codetriage.com/gin-gonic/gin) -[![Release](https://img.shields.io/github/release/gin-gonic/gin.svg?style=flat-square)](https://github.com/gin-gonic/gin/releases) - -Gin 是一个 Go (Golang) 语言框架。 它是一个拥有更好性能的 martini-like API 框架, 比 [httprouter](https://github.com/julienschmidt/httprouter) 的速度快了40倍. 如果你是性能和高效的追求者, 那么你会爱上 Gin. - -![Gin console logger](https://gin-gonic.github.io/gin/other/console.png) - -## Contents - -- [安装](#安装) -- [前提条件](#前提条件) -- [快速开始](#快速开始) -- [性能测试](#性能测试) -- [Gin v1 稳定版](#gin-v1-稳定版) -- [使用 jsoniter ](#使用-jsoniter) -- [API 示例](#api-示例) - - [GET,POST,PUT,PATCH,DELETE,OPTIONS 使用](#get-post-put-patch-delete-options-使用) - - [获取路由参数](#获取路由参数) - - [获取url查询参数](#获取url查询参数) - - [Multipart Urlencoded 表单](#multipart-urlencoded-表单) - - [获取post表单数据(url带查询参数)](#获取post表单数据url带查询参数) - - [映射参数 表单参数](#映射参数-表单参数) - - [上传文件](#上传文件) - - [路由组](#路由组) - - [默认初始化 Gin](#默认初始化-gin) - - [使用中间件](#使用中间件) - - [如何记录日志](#如何记录日志) - - [模型绑定和验证](#模型绑定和验证) - - [自定义验证器](#自定义验证器) - - [只绑定url查询参数](#只绑定url查询参数) - - [url查询参数或表单数据绑定到结构体](#url查询参数或表单数据绑定到结构体) - - [url路径参数绑定](#url路径参数绑定) - - [绑定 HTML 复选框](#绑定-html-复选框) - - [Multipart Urlencoded 绑定](#multipart-urlencoded-绑定) - - [XML JSON YAML ProtoBuf 渲染](#xml-json-yaml-protobuf-渲染) - - [SecureJSON](#SecureJSON) - - [静态文件服务](#静态文件服务) - - [从reader 读取数据](#从-reader-读取数据) - - [HTML 渲染](#html-渲染) - - [多模板](#多模板) - - [重定向](#重定向) - - [自定义中间件](#自定义中间件) - - [使用 BasicAuth() 中间件](#使用-basicauth()-中间件) - - [在中间件中使用Goroutines](#在中间件中使用Goroutines) - - [自定义 HTTP 配置](#自定义-http-配置) - - [Let's Encrypt 支持](#lets-encrypt-支持) - - [使用 Gin 运行多个服务](使用-gin-运行多个服务) - - [优雅重启或停止](#优雅重启或停止) - - [静态资源嵌入](#静态资源嵌入) - - [表单数据绑定到自定义结构体](#表单数据绑定到自定义结构体) - - [将request body绑定到不同的结构体中](#将request-body绑定到不同的结构体中) - - [http2 server 推送](#http2-server-推送) - - [定义路由日志的格式](#定义路由日志的格式) - - [如何使用Cookie](#如何使用Cookie) -- [测试](#测试) -- [用户](#用户) - -## 安装 - -要安装 Gin 软件包,需要先安装 Go 并设置 Go 工作区。 - -1. 下载并安装 gin: - -```sh -$ go get -u github.com/gin-gonic/gin -``` - -2. 将 gin 引入到代码中: - -```go -import "github.com/gin-gonic/gin" -``` - -3. (可选)如果使用诸如`http.StatusOK`之类的常量,则需要引入 `net/http` 包。 - -```go -import "net/http" -``` - -### 使用 [Govendor](https://github.com/kardianos/govendor) 工具创建项目 - -1. `go get` govendor - -```sh -$ go get github.com/kardianos/govendor -``` -2.创建项目并且 `cd` 到项目目录中 - -```sh -$ mkdir -p $GOPATH/src/github.com/myusername/project && cd "$_" -``` - -3. 使用 govendor 初始化项目,并且引入gin - -```sh -$ govendor init -$ govendor fetch github.com/gin-gonic/gin@v1.3 -``` - -4. 复制启动文件模板到项目目录中 - -```sh -$ curl https://raw.githubusercontent.com/gin-gonic/gin/master/examples/basic/main.go > main.go -``` - -5.启动项目 - -```sh -$ go run main.go -``` - -## 前提条件 - -新版本的 Gin 需要 Go 1.6 或者更高版本,并且很快就会要求升级到 Go 1.7. - -## 快速开始 - -```sh -# assume the following codes in example.go file -$ cat example.go -``` - -```go -package main - -import "github.com/gin-gonic/gin" - -func main() { - r := gin.Default() - r.GET("/ping", func(c *gin.Context) { - c.JSON(200, gin.H{ - "message": "pong", - }) - }) - r.Run() // listen and serve on 0.0.0.0:8080 -} -``` - -``` -# run example.go and visit 0.0.0.0:8080/ping on browser -$ go run example.go -``` - -## 性能测试 - -Gin 使用自定义版本的 [HttpRouter](https://github.com/julienschmidt/httprouter) - -[所有性能测试](/BENCHMARKS.md) - -Benchmark name | (1) | (2) | (3) | (4) ---------------------------------------------|-----------:|------------:|-----------:|---------: -**BenchmarkGin_GithubAll** | **30000** | **48375** | **0** | **0** -BenchmarkAce_GithubAll | 10000 | 134059 | 13792 | 167 -BenchmarkBear_GithubAll | 5000 | 534445 | 86448 | 943 -BenchmarkBeego_GithubAll | 3000 | 592444 | 74705 | 812 -BenchmarkBone_GithubAll | 200 | 6957308 | 698784 | 8453 -BenchmarkDenco_GithubAll | 10000 | 158819 | 20224 | 167 -BenchmarkEcho_GithubAll | 10000 | 154700 | 6496 | 203 -BenchmarkGocraftWeb_GithubAll | 3000 | 570806 | 131656 | 1686 -BenchmarkGoji_GithubAll | 2000 | 818034 | 56112 | 334 -BenchmarkGojiv2_GithubAll | 2000 | 1213973 | 274768 | 3712 -BenchmarkGoJsonRest_GithubAll | 2000 | 785796 | 134371 | 2737 -BenchmarkGoRestful_GithubAll | 300 | 5238188 | 689672 | 4519 -BenchmarkGorillaMux_GithubAll | 100 | 10257726 | 211840 | 2272 -BenchmarkHttpRouter_GithubAll | 20000 | 105414 | 13792 | 167 -BenchmarkHttpTreeMux_GithubAll | 10000 | 319934 | 65856 | 671 -BenchmarkKocha_GithubAll | 10000 | 209442 | 23304 | 843 -BenchmarkLARS_GithubAll | 20000 | 62565 | 0 | 0 -BenchmarkMacaron_GithubAll | 2000 | 1161270 | 204194 | 2000 -BenchmarkMartini_GithubAll | 200 | 9991713 | 226549 | 2325 -BenchmarkPat_GithubAll | 200 | 5590793 | 1499568 | 27435 -BenchmarkPossum_GithubAll | 10000 | 319768 | 84448 | 609 -BenchmarkR2router_GithubAll | 10000 | 305134 | 77328 | 979 -BenchmarkRivet_GithubAll | 10000 | 132134 | 16272 | 167 -BenchmarkTango_GithubAll | 3000 | 552754 | 63826 | 1618 -BenchmarkTigerTonic_GithubAll | 1000 | 1439483 | 239104 | 5374 -BenchmarkTraffic_GithubAll | 100 | 11383067 | 2659329 | 21848 -BenchmarkVulcan_GithubAll | 5000 | 394253 | 19894 | 609 - -- (1): 在不断的时间内实现总重复,更高意味着更自信的结果 -- (2): 单次重复持续时间(ns / op),越低越好 -- (3): 堆内存(B / op),越低越好 -- (4): 每次重复的平均分配(allocs / op)越低越好 - -## Gin v1 稳定版 - -- [x] 零分配路由。 -- [x] 仍然是最快的http路由器和框架。 -- [x] 完整的单元测试支持 -- [x] 对战测试 -- [x] API冻结,使用新版本不需要修改原有代码。 - -## 使用 [jsoniter](https://github.com/json-iterator/go) - -Gin默认使用`encoding/json`解析json数据,但您可以通过`go build -tags=`更改为使用[jsoniter](https://github.com/json-iterator/go)。 - -```sh -$ go build -tags=jsoniter . -``` - -## API 示例 - -### 使用GET, POST, PUT, PATCH, DELETE , OPTIONS - -```go -func main() { - // Disable Console Color - // gin.DisableConsoleColor() - - // 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 -} -``` - -### 获取路由参数 - -```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") -} -``` - -### 获取url查询参数 - -```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 表单 - -```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") -} -``` - -### 获取post表单数据(url带查询参数) - -``` -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 -``` - -### 映射参数 表单参数 - -``` -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] -``` - -### 上传文件 - -#### 单个文件上传 - -参考问题[#774](https://github.com/gin-gonic/gin/issues/774)和详细[示例代码](examples/upload-file/single)。 - -```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") -} -``` - -`curl`示例: - -```bash -curl -X POST http://localhost:8080/upload \ - -F "file=@/Users/appleboy/test.zip" \ - -H "Content-Type: multipart/form-data" -``` - -#### 多文件上传 - -查看详细信息[示例代码](examples/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") -} -``` - -`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" -``` - -### 路由组 - -```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") -} -``` - -### 默认初始化 Gin - -用 - -```go -r := gin.New() -``` - -代替 - -```go -// Default With the Logger and Recovery middleware already attached -r := gin.Default() -``` - - -### 使用中间件 -```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") -} -``` - -### 如何记录日志 -```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") -} -``` - -### 模型绑定和验证 - -要将请求主体绑定到结构体中,请使用模型绑定。Gin目前支持JSON、XML、YAML和标准表单值的绑定(foo=bar&boo=baz)。 - -Gin使用[go-playground/validator.v8](https://github.com/go-playground/validator)进行验证。[完整文档](http://godoc.org/gopkg.in/go-playground/validator.v8#hdr-Baked_In_Validators_and_Tags)。 - -使用时,需要在要绑定的所有字段上,设置相应的tag。例如,使用JSON绑定时,字段tag设置为`json:"fieldname"`。 - -Gin提供了两类绑定方法: -- **MustBind** - - - **方法** - `Bind`, `BindJSON`, `BindXML`, `BindQuery`, `BindYAML` - - **说明** - 这些方法属于`MustBindWith`的具体调用。如果发生绑定错误,则请求终止,并触发`c.AbortWithError(400,err).SetType(ErrorTypeBind)`。响应状态码被设置为400,`Content-Type`被设置为`text/plain;charset= UTF-8`。如果您在此之后尝试设置响应状态码,Gin会输出日志“ `[GIN-debug] [WARNING] Headers were already written. Wanted to override status code 400 with 422`。如果您希望更好地把控绑定,请考虑使用`ShouldBind`等效方法。 -- **ShouldBind** - -   - **方法** - `ShouldBind`,`ShouldBindJSON`,`ShouldBindXML`,`ShouldBindQuery` - - **说明** - 这些方法属于`ShouldBindWith`的具体调用。如果发生绑定错误,Gin会返回错误。由您处理错误以及请求。 - -使用Bind方法时,Gin会根据Content-Type尝试推断如何绑定,如果您明确知道,您可以使用 `MustBindWith` 或 `ShouldBindWith`。 - -指定必须绑定的字段,在该字段Tag上加上`binding:"required"` ,如果绑定时是空值,Gin会报错。 - -```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") -} -``` - -**测试** -```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"} -``` - -**忽略验证** - -使用`curl` 命令运行上面的例子时,它返回错误,因为这个例子使用`binding:"required"` 。 如果使用 `binding:"-"` ,那么再次运行上面的例子时它不会返回错误。 - -### 自定义验证器 - -注册自定义验证器, 请参阅[示例代码](examples/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"} -``` - -结构体验证也可以参考[这种方法](https://github.com/go-playground/validator/releases/tag/v8.7)注册。 -请参阅[struct-lvl-validation示例](examples/struct-lvl-validations)以了解更多信息。 - -### 只绑定url查询参数 - -`ShouldBindQuery` 函数只绑定url查询参数而不是post字段。 请参阅[详细信息](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") -} - -``` - -### url查询参数或表单数据绑定到结构体 - -请参阅[详细信息](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") -} -``` - -测试: -```sh -$ curl -X GET "localhost:8085/testing?name=appleboy&address=xyz&birthday=1992-03-15" -``` -### url路径参数绑定 - -查看[详细信息](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") -} -``` - -测试: -```sh -$ curl -v localhost:8088/thinkerou/987fbc97-4bed-5078-9f07-9141ba07c9f3 -$ curl -v localhost:8088/thinkerou/not-uuid -``` - -### 绑定 HTML 复选框 - -参见[详细信息](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 绑定 - -```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") -} -``` - -测试: -```sh -$ curl -v --form user=user --form password=password http://localhost:8080/login -``` - -### XML JSON YAML ProtoBuf 渲染 - -```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 - -使用 SecureJSON 来防止 json 劫持。 如果给定的结构是数组值,则默认预 置`“while(1),”` 到响应体。 - -```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 - -使用 JSONP 从不同域中的服务器请求数据。 如果查询参数回调存在,则将回调添加到响应正文。 - -```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 - -使用 Ascii JSON 生成具有转义的非 ASCII 字符的仅 ASCII JSON。 - -```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 - -通常,JSON使用unicode替换特殊HTML字符,例如 `<` 变为 `\ u003c`。 如果要按字面意思对这些字符进行编码,则可以使用 PureJSON。 -Go 1.6及更低版本无法使用此功能。 - -```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) -} -``` - -### 静态文件服务 - -```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") -} -``` - -### 从 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 渲染 - -使用LoadHTMLGlob()或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 }} -

- -``` - -在不同目录中使用具有相同名称的模板 - -```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 }} -``` - -#### 自定义模板渲染器 - -您还可以使用自己的 html 模板渲染 - -```go -import "html/template" - -func main() { - router := gin.Default() - html := template.Must(template.ParseFiles("file1", "file2")) - router.SetHTMLTemplate(html) - router.Run(":8080") -} -``` - -#### 自定义分隔符 - -您可以使用自定义分隔 - -```go - r := gin.Default() - r.Delims("{[{", "}]}") - r.LoadHTMLGlob("/path/to/templates") -``` - -#### 自定义模板功能 - -查看详细信息[示例代码](examples/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 -日期: {[{.now | formatAsDate}]} -``` - -结果: -``` -Date: 2017/07/01 -``` - -### 多模板 - -Gin 允许默认只使用一个 html 模板。 检查[多模板渲染](https://github.com/gin-contrib/multitemplate)以使用 go 1.6 `block template` 等功能。 - -### 重定向 - -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/") -}) -``` - - -发出路由器重定向,使用如下的“HandleContext”。 - -``` 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"}) -}) -``` - - -### 自定义中间件 - -```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") -} -``` - -### BasicAuth() 中间件 - -```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 - -当在中间件或handler中启动新的Goroutines时,不能使用原始上下文,必须使用只读副本。 - -```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") -} -``` - -### 自定义 HTTP 配置 - -直接使用`http.ListenAndServe()`,如下所示: - -```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() -} -``` - -### Let's Encrypt 支持 - -一行代码支持 LetsEncrypt HTTPS示例。 - -[embedmd]:# (examples/auto-tls/example1/main.go go) -```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")) -} -``` - -autocert使用示例。 - -[embedmd]:# (examples/auto-tls/example2/main.go go) -```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)) -} -``` - -### 使用 Gin 运行多个服务 - -请参阅[问题](https://github.com/gin-gonic/gin/issues/346)并尝试以下示例: - -[embedmd]:# (examples/multiple-service/main.go go) -```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) - } -} -``` - -### 优雅重启或停止 - -您想要优雅地重启或停止您的Web服务器吗? -有一些方法可以做到这一点。 - -我们可以使用[fvbock/endless](https://github.com/fvbock/endless)来替换默认的`ListenAndServe`。 有关更多详细信息,请参阅问题[#296](https://github.com/gin-gonic/gin/issues/296)。 - -```go -router := gin.Default() -router.GET("/", handler) -// [...] -endless.ListenAndServe(":4242", router) -``` - -替代方案: - -* [manners](https://github.com/braintree/manners):礼貌的Go HTTP服务器,可以正常关闭。 -* [graceful](https://github.com/tylerb/graceful):Graceful是一个Go包,可以正常关闭http.Handler服务器。 -* [grace](https://github.com/facebookgo/grace):Go服务器的平滑重启和零停机时间部署。 - -如果您使用的是Go 1.8,可以考虑使用http.Server的内置[Shutdown()](https://golang.org/pkg/net/http/#Server.Shutdown)方法进行正常关机。 -请参阅gin的完整[graceful-shutdown](/examples /graceful-shutdown)示例。 - -[embedmd]:# (examples/graceful-shutdown/graceful-shutdown/server.go go) -```go -// +build go1.8 - -package main - -import ( - "context" - "log" - "net/http" - "os" - "os/signal" - "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) - signal.Notify(quit, os.Interrupt) - <-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) - } - log.Println("Server exiting") -} -``` - -### 静态资源嵌入 - -使用[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 -} -``` - -请参阅`examples/assets-in-binary`目录中的完整示例。 - -### 表单数据绑定到自定义结构体 - -以下示例使用自定义结构体: - -```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() -} -``` - -`curl`命令示例: - -``` -$ 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"}} -``` - -**注意**:不支持以下格式结构体: - -```go -type StructX struct { - X struct {} `form:"name_x"` // HERE have form -} - -type StructY struct { - Y StructX `form:"name_y"` // HERE have form -} - -type StructZ struct { - Z *StructZ `form:"name_z"` // HERE have form -} -``` - -一句话,现在只支持没有`form`的嵌套结构体。 - -### 将request body绑定到不同的结构体中 - -一般通过调用`c.Request.Body`方法绑定数据,但不能多次调用这个方法。 - -```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 { - ... - } -} -``` - -为此,要想多次绑定,需要使用`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`会在绑定之前将body存储到上下文中。 这会对性能造成轻微影响,如果调用一次就能完成绑定的话,那就不要用这个方法。 -* 只有某些格式需要此功能 ,如`JSON`、`XML`、`MsgPack`、`ProtoBuf`。 对于其他格式,如`Query`、`Form`、`FormPost`、`FormMultipart`可以多次调用`c.ShouldBind()`而不会造成任任何性能损失(见[#1341](https://github.com/gin-gonic/gin/pull/1341))。 - -### http2 server 推送 - - -http.Pusher仅支持go1.8 +。 有关详细信息,请参阅[golang blog](https://blog.golang.org/h2push)。 - -[embedmd]:# (examples/http-pusher/main.go go) -```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") -} -``` - -### 定义路由日志的格式 - -默认的路由日志格式: -``` -[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) -``` - -如果要以指的格式(例如JSON,Key Values或其他格式)记录信息,则可以使用`gin.DebugPrintRouteFunc`指定格式。 -在下面的示例中,我们使用标准日志包记录所有路由,但您可以使用其他满足需求的日志工具。 -```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() -} -``` -### 如何使用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() -} -``` - -## 测试 - -HTTP测试首选`net/http/httptest`包。 - -```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") -} -``` - -上面这段代码的测试用例: - -```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()) -} -``` - -## 用户 -使用[Gin](https://github.com/gin-gonic/gin)框架的著名项目: - -* [drone](https://github.com/drone/drone):用Go编写的基于docker的持续集成平台。 -* [gorush](https://github.com/appleboy/gorush):用Go编写的推送通知服务。 -* [fnproject](https://github.com/fnproject/fn):容器驱动、云无关的无服务器平台。 -* [photoprism](https://github.com/photoprism/photoprism): 用Go编写的基于TensorFlow的个人相册管理系统. -* [krakend](https://github.com/devopsfaith/krakend): 表现优异的API网关中间件。 From b97ccf3a43d2901d295870b5876277bf478a4909 Mon Sep 17 00:00:00 2001 From: MetalBreaker Date: Mon, 26 Nov 2018 16:01:51 +0100 Subject: [PATCH 25/46] Router: Route StaticFS() not found to Router's NoRoute() (#1663) Closes #1220 --- routergroup.go | 12 ++++++++++-- routes_test.go | 15 +++++++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/routergroup.go b/routergroup.go index 9cb0b989..5615a50c 100644 --- a/routergroup.go +++ b/routergroup.go @@ -185,11 +185,19 @@ func (group *RouterGroup) StaticFS(relativePath string, fs http.FileSystem) IRou func (group *RouterGroup) createStaticHandler(relativePath string, fs http.FileSystem) HandlerFunc { absolutePath := group.calculateAbsolutePath(relativePath) fileServer := http.StripPrefix(absolutePath, http.FileServer(fs)) - _, nolisting := fs.(*onlyfilesFS) + return func(c *Context) { - if nolisting { + file := c.Param("filepath") + + // 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 + // Reset index + c.index = -1 + return } + fileServer.ServeHTTP(c.Writer, c.Request) } } diff --git a/routes_test.go b/routes_test.go index 60f1c81b..c4d59725 100644 --- a/routes_test.go +++ b/routes_test.go @@ -411,6 +411,21 @@ func TestRouterNotFound(t *testing.T) { assert.Equal(t, http.StatusNotFound, w.Code) } +func TestRouterStaticFSNotFound(t *testing.T) { + router := New() + + router.StaticFS("/", http.FileSystem(http.Dir("/thisreallydoesntexist/"))) + router.NoRoute(func(c *Context) { + c.String(404, "non existent") + }) + + w := performRequest(router, "GET", "/nonexistent") + assert.Equal(t, "non existent", w.Body.String()) + + w = performRequest(router, "HEAD", "/nonexistent") + assert.Equal(t, "non existent", w.Body.String()) +} + func TestRouteRawPath(t *testing.T) { route := New() route.UseRawPath = true From 54e9610400af963d321f8bdd91ce1507f7f8af9f Mon Sep 17 00:00:00 2001 From: thinkerou Date: Wed, 5 Dec 2018 02:02:03 +0800 Subject: [PATCH 26/46] chore: remove wercker yml file (#1676) Now the `wercker.yml` have no longer used. --- wercker.yml | 1 - 1 file changed, 1 deletion(-) delete mode 100644 wercker.yml diff --git a/wercker.yml b/wercker.yml deleted file mode 100644 index 3ab8084c..00000000 --- a/wercker.yml +++ /dev/null @@ -1 +0,0 @@ -box: wercker/default \ No newline at end of file From f463d847c23c85070653fe725cf27172633f57a0 Mon Sep 17 00:00:00 2001 From: thinkerou Date: Wed, 5 Dec 2018 05:58:35 +0800 Subject: [PATCH 27/46] chore: fix test fail (#1669) * chore: fix test fail * fix binduri test fail --- binding/binding_test.go | 2 +- recovery_test.go | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/binding/binding_test.go b/binding/binding_test.go index 5b311764..c0204d7f 100644 --- a/binding/binding_test.go +++ b/binding/binding_test.go @@ -687,7 +687,7 @@ func TestUriBinding(t *testing.T) { } var not NotSupportStruct assert.Error(t, b.BindUri(m, ¬)) - assert.Equal(t, "", not.Name) + assert.Equal(t, map[string]interface{}(nil), not.Name) } func testFormBinding(t *testing.T, method, path, badPath, body, badBody string) { diff --git a/recovery_test.go b/recovery_test.go index c9fb29ce..e886eaac 100644 --- a/recovery_test.go +++ b/recovery_test.go @@ -11,6 +11,7 @@ import ( "net" "net/http" "os" + "strings" "syscall" "testing" @@ -84,7 +85,7 @@ func TestPanicWithBrokenPipe(t *testing.T) { const expectCode = 204 expectMsgs := map[syscall.Errno]string{ - syscall.EPIPE: "Broken pipe", + syscall.EPIPE: "broken pipe", syscall.ECONNRESET: "connection reset by peer", } @@ -108,7 +109,7 @@ func TestPanicWithBrokenPipe(t *testing.T) { w := performRequest(router, "GET", "/recovery") // TEST assert.Equal(t, expectCode, w.Code) - assert.Contains(t, buf.String(), expectMsg) + assert.Contains(t, strings.ToLower(buf.String()), expectMsg) }) } } From 98c7ac7202ffc7cfb60706bb48e0ef10f737abb1 Mon Sep 17 00:00:00 2001 From: thinkerou Date: Wed, 5 Dec 2018 13:36:49 +0800 Subject: [PATCH 28/46] fix bug (#1682) --- routergroup.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/routergroup.go b/routergroup.go index 5615a50c..2b41dfda 100644 --- a/routergroup.go +++ b/routergroup.go @@ -187,8 +187,11 @@ func (group *RouterGroup) createStaticHandler(relativePath string, fs http.FileS fileServer := http.StripPrefix(absolutePath, http.FileServer(fs)) return func(c *Context) { - file := c.Param("filepath") + if _, nolisting := fs.(*onlyfilesFS); nolisting { + c.Writer.WriteHeader(http.StatusNotFound) + } + file := c.Param("filepath") // 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) From cce49582d617333c72f4d86abd8ef0e62eb778a0 Mon Sep 17 00:00:00 2001 From: thinkerou Date: Wed, 5 Dec 2018 13:49:03 +0800 Subject: [PATCH 29/46] ci: break when test fail (#1671) --- .gitignore | 2 ++ Makefile | 7 ++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 14dc8f20..bdd50c95 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,5 @@ vendor/* coverage.out count.out test +profile.out +tmp.out diff --git a/Makefile b/Makefile index b698ac09..b0d2e24a 100644 --- a/Makefile +++ b/Makefile @@ -14,7 +14,12 @@ install: deps test: echo "mode: count" > coverage.out for d in $(TESTFOLDER); do \ - $(GO) test -v -covermode=count -coverprofile=profile.out $$d; \ + $(GO) test -v -covermode=count -coverprofile=profile.out $$d > tmp.out; \ + cat tmp.out; \ + if grep -q "^--- FAIL" tmp.out; then \ + rm tmp.out; \ + exit 1;\ + fi; \ if [ -f profile.out ]; then \ cat profile.out | grep -v "mode:" >> coverage.out; \ rm profile.out; \ From f76ccb25f1eee8684e545ec00f8f2934fec98f09 Mon Sep 17 00:00:00 2001 From: Sai Date: Wed, 12 Dec 2018 10:05:16 +0900 Subject: [PATCH 30/46] Add LoggerWithFormatter method (#1677) * Add LoggerWithFormatter * Add tests for LoggerWithFormatter & LoggerWithConfig * Add note for README * Add tests for DefaultLogFormatter * Add comment * Change DefaultLogFormatter to a private method --- README.md | 38 ++++++++++ logger.go | 116 +++++++++++++++++++++++------ logger_test.go | 194 ++++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 324 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index e7b92b2d..c1f902a9 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,7 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi - [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) @@ -528,6 +529,43 @@ func main() { } ``` +### 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" " +``` + ### 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). diff --git a/logger.go b/logger.go index 74dd2e6f..a64af697 100644 --- a/logger.go +++ b/logger.go @@ -26,6 +26,56 @@ var ( disableColor = false ) +// LoggerConfig defines the config for Logger middleware. +type LoggerConfig struct { + // Optional. Default value is gin.defaultLogFormatter + Formatter LogFormatter + + // Output is a writer where logs are written. + // Optional. Default value is gin.DefaultWriter. + Output io.Writer + + // SkipPathes is a url path array which logs are not written. + // Optional. + SkipPathes []string +} + +// LogFormatter gives the signature of the formatter function passed to LoggerWithFormatter +type LogFormatter func(params LogFormatterParams) string + +// LogFormatterParams is the structure any formatter will be handed when time to log comes +type LogFormatterParams struct { + Request *http.Request + TimeStamp time.Time + StatusCode int + Latency time.Duration + ClientIP string + Method string + Path string + ErrorMessage string + IsTerm bool +} + +// defaultLogFormatter is the default log format function Logger middleware uses. +var defaultLogFormatter = func(param LogFormatterParams) string { + var statusColor, methodColor, resetColor string + if param.IsTerm { + statusColor = colorForStatus(param.StatusCode) + methodColor = colorForMethod(param.Method) + resetColor = reset + } + + 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, + param.Latency, + param.ClientIP, + methodColor, param.Method, resetColor, + param.Path, + param.ErrorMessage, + ) +} + // DisableConsoleColor disables color output in the console. func DisableConsoleColor() { disableColor = true @@ -50,12 +100,39 @@ func ErrorLoggerT(typ ErrorType) HandlerFunc { // Logger instances a Logger middleware that will write the logs to gin.DefaultWriter. // By default gin.DefaultWriter = os.Stdout. func Logger() HandlerFunc { - return LoggerWithWriter(DefaultWriter) + return LoggerWithConfig(LoggerConfig{}) +} + +// LoggerWithFormatter instance a Logger middleware with the specified log format function. +func LoggerWithFormatter(f LogFormatter) HandlerFunc { + return LoggerWithConfig(LoggerConfig{ + Formatter: f, + }) } // LoggerWithWriter instance a Logger middleware with the specified writer buffer. // Example: os.Stdout, a file opened in write mode, a socket... func LoggerWithWriter(out io.Writer, notlogged ...string) HandlerFunc { + return LoggerWithConfig(LoggerConfig{ + Output: out, + SkipPathes: notlogged, + }) +} + +// LoggerWithConfig instance a Logger middleware with config. +func LoggerWithConfig(conf LoggerConfig) HandlerFunc { + formatter := conf.Formatter + if formatter == nil { + formatter = defaultLogFormatter + } + + out := conf.Output + if out == nil { + out = DefaultWriter + } + + notlogged := conf.SkipPathes + isTerm := true if w, ok := out.(*os.File); !ok || @@ -85,34 +162,27 @@ func LoggerWithWriter(out io.Writer, notlogged ...string) HandlerFunc { // Log only when path is not being skipped if _, ok := skip[path]; !ok { - // Stop timer - end := time.Now() - latency := end.Sub(start) - - clientIP := c.ClientIP() - method := c.Request.Method - statusCode := c.Writer.Status() - var statusColor, methodColor, resetColor string - if isTerm { - statusColor = colorForStatus(statusCode) - methodColor = colorForMethod(method) - resetColor = reset + param := LogFormatterParams{ + Request: c.Request, + IsTerm: isTerm, } - comment := c.Errors.ByType(ErrorTypePrivate).String() + + // Stop timer + param.TimeStamp = time.Now() + param.Latency = param.TimeStamp.Sub(start) + + param.ClientIP = c.ClientIP() + param.Method = c.Request.Method + param.StatusCode = c.Writer.Status() + param.ErrorMessage = c.Errors.ByType(ErrorTypePrivate).String() if raw != "" { path = path + "?" + raw } - fmt.Fprintf(out, "[GIN] %v |%s %3d %s| %13v | %15s |%s %-7s %s %s\n%s", - end.Format("2006/01/02 - 15:04:05"), - statusColor, statusCode, resetColor, - latency, - clientIP, - methodColor, method, resetColor, - path, - comment, - ) + param.Path = path + + fmt.Fprintf(out, formatter(param)) } } } diff --git a/logger_test.go b/logger_test.go index 6118cb04..909ddd39 100644 --- a/logger_test.go +++ b/logger_test.go @@ -7,8 +7,10 @@ package gin import ( "bytes" "errors" + "fmt" "net/http" "testing" + "time" "github.com/stretchr/testify/assert" ) @@ -79,7 +81,179 @@ func TestLogger(t *testing.T) { assert.Contains(t, buffer.String(), "404") assert.Contains(t, buffer.String(), "GET") assert.Contains(t, buffer.String(), "/notfound") +} +func TestLoggerWithConfig(t *testing.T) { + buffer := new(bytes.Buffer) + router := New() + router.Use(LoggerWithConfig(LoggerConfig{Output: buffer})) + router.GET("/example", func(c *Context) {}) + router.POST("/example", func(c *Context) {}) + router.PUT("/example", func(c *Context) {}) + router.DELETE("/example", func(c *Context) {}) + router.PATCH("/example", func(c *Context) {}) + router.HEAD("/example", func(c *Context) {}) + router.OPTIONS("/example", func(c *Context) {}) + + performRequest(router, "GET", "/example?a=100") + assert.Contains(t, buffer.String(), "200") + assert.Contains(t, buffer.String(), "GET") + assert.Contains(t, buffer.String(), "/example") + assert.Contains(t, buffer.String(), "a=100") + + // I wrote these first (extending the above) but then realized they are more + // like integration tests because they test the whole logging process rather + // than individual functions. Im not sure where these should go. + buffer.Reset() + performRequest(router, "POST", "/example") + assert.Contains(t, buffer.String(), "200") + assert.Contains(t, buffer.String(), "POST") + assert.Contains(t, buffer.String(), "/example") + + buffer.Reset() + performRequest(router, "PUT", "/example") + assert.Contains(t, buffer.String(), "200") + assert.Contains(t, buffer.String(), "PUT") + assert.Contains(t, buffer.String(), "/example") + + buffer.Reset() + performRequest(router, "DELETE", "/example") + assert.Contains(t, buffer.String(), "200") + assert.Contains(t, buffer.String(), "DELETE") + assert.Contains(t, buffer.String(), "/example") + + buffer.Reset() + performRequest(router, "PATCH", "/example") + assert.Contains(t, buffer.String(), "200") + assert.Contains(t, buffer.String(), "PATCH") + assert.Contains(t, buffer.String(), "/example") + + buffer.Reset() + performRequest(router, "HEAD", "/example") + assert.Contains(t, buffer.String(), "200") + assert.Contains(t, buffer.String(), "HEAD") + assert.Contains(t, buffer.String(), "/example") + + buffer.Reset() + performRequest(router, "OPTIONS", "/example") + assert.Contains(t, buffer.String(), "200") + assert.Contains(t, buffer.String(), "OPTIONS") + assert.Contains(t, buffer.String(), "/example") + + buffer.Reset() + performRequest(router, "GET", "/notfound") + assert.Contains(t, buffer.String(), "404") + assert.Contains(t, buffer.String(), "GET") + assert.Contains(t, buffer.String(), "/notfound") +} + +func TestLoggerWithFormatter(t *testing.T) { + buffer := new(bytes.Buffer) + + d := DefaultWriter + DefaultWriter = buffer + defer func() { + DefaultWriter = d + }() + + router := New() + router.Use(LoggerWithFormatter(func(param LogFormatterParams) string { + return fmt.Sprintf("[FORMATTER TEST] %v | %3d | %13v | %15s | %-7s %s\n%s", + param.TimeStamp.Format("2006/01/02 - 15:04:05"), + param.StatusCode, + param.Latency, + param.ClientIP, + param.Method, + param.Path, + param.ErrorMessage, + ) + })) + router.GET("/example", func(c *Context) {}) + performRequest(router, "GET", "/example?a=100") + + // output test + assert.Contains(t, buffer.String(), "[FORMATTER TEST]") + assert.Contains(t, buffer.String(), "200") + assert.Contains(t, buffer.String(), "GET") + assert.Contains(t, buffer.String(), "/example") + assert.Contains(t, buffer.String(), "a=100") +} + +func TestLoggerWithConfigFormatting(t *testing.T) { + var gotParam LogFormatterParams + buffer := new(bytes.Buffer) + + router := New() + router.Use(LoggerWithConfig(LoggerConfig{ + Output: buffer, + Formatter: func(param LogFormatterParams) string { + // for assert test + gotParam = param + + return fmt.Sprintf("[FORMATTER TEST] %v | %3d | %13v | %15s | %-7s %s\n%s", + param.TimeStamp.Format("2006/01/02 - 15:04:05"), + param.StatusCode, + param.Latency, + param.ClientIP, + param.Method, + param.Path, + param.ErrorMessage, + ) + }, + })) + router.GET("/example", func(c *Context) { + // set dummy ClientIP + c.Request.Header.Set("X-Forwarded-For", "20.20.20.20") + }) + performRequest(router, "GET", "/example?a=100") + + // output test + assert.Contains(t, buffer.String(), "[FORMATTER TEST]") + assert.Contains(t, buffer.String(), "200") + assert.Contains(t, buffer.String(), "GET") + assert.Contains(t, buffer.String(), "/example") + assert.Contains(t, buffer.String(), "a=100") + + // LogFormatterParams test + assert.NotNil(t, gotParam.Request) + assert.NotEmpty(t, gotParam.TimeStamp) + assert.Equal(t, 200, gotParam.StatusCode) + assert.NotEmpty(t, gotParam.Latency) + assert.Equal(t, "20.20.20.20", gotParam.ClientIP) + assert.Equal(t, "GET", gotParam.Method) + assert.Equal(t, "/example?a=100", gotParam.Path) + assert.Empty(t, gotParam.ErrorMessage) + +} + +func TestDefaultLogFormatter(t *testing.T) { + timeStamp := time.Unix(1544173902, 0).UTC() + + termFalseParam := LogFormatterParams{ + TimeStamp: timeStamp, + StatusCode: 200, + Latency: time.Second * 5, + ClientIP: "20.20.20.20", + Method: "GET", + Path: "/", + ErrorMessage: "", + IsTerm: false, + } + + termTrueParam := LogFormatterParams{ + TimeStamp: timeStamp, + StatusCode: 200, + Latency: time.Second * 5, + ClientIP: "20.20.20.20", + Method: "GET", + Path: "/", + ErrorMessage: "", + IsTerm: true, + } + + 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 |\x1b[97;42m 200 \x1b[0m| 5s | 20.20.20.20 |\x1b[97;44m GET \x1b[0m /\n", defaultLogFormatter(termTrueParam)) } func TestColorForMethod(t *testing.T) { @@ -127,7 +301,7 @@ func TestErrorLogger(t *testing.T) { assert.Equal(t, "hola!{\"error\":\"this is an error\"}", w.Body.String()) } -func TestSkippingPaths(t *testing.T) { +func TestLoggerWithWriterSkippingPaths(t *testing.T) { buffer := new(bytes.Buffer) router := New() router.Use(LoggerWithWriter(buffer, "/skipped")) @@ -142,6 +316,24 @@ func TestSkippingPaths(t *testing.T) { assert.Contains(t, buffer.String(), "") } +func TestLoggerWithConfigSkippingPaths(t *testing.T) { + buffer := new(bytes.Buffer) + router := New() + router.Use(LoggerWithConfig(LoggerConfig{ + Output: buffer, + SkipPathes: []string{"/skipped"}, + })) + router.GET("/logged", func(c *Context) {}) + router.GET("/skipped", func(c *Context) {}) + + performRequest(router, "GET", "/logged") + assert.Contains(t, buffer.String(), "200") + + buffer.Reset() + performRequest(router, "GET", "/skipped") + assert.Contains(t, buffer.String(), "") +} + func TestDisableConsoleColor(t *testing.T) { New() assert.False(t, disableColor) From 59695e7ba86dec1a8d847c3329a3e7c9f7705125 Mon Sep 17 00:00:00 2001 From: thinkerou Date: Wed, 12 Dec 2018 23:40:29 +0800 Subject: [PATCH 31/46] Add BindUri (#1694) * add BindUri * fix bug * fix code style --- context.go | 18 ++++++++++++++---- githubapi_test.go | 44 ++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 56 insertions(+), 6 deletions(-) diff --git a/context.go b/context.go index 478e8c09..c94926e1 100644 --- a/context.go +++ b/context.go @@ -530,15 +530,25 @@ func (c *Context) BindYAML(obj interface{}) error { return c.MustBindWith(obj, binding.YAML) } +// BindUri binds the passed struct pointer using binding.Uri. +// It will abort the request with HTTP 400 if any error occurs. +func (c *Context) BindUri(obj interface{}) error { + if err := c.ShouldBindUri(obj); err != nil { + c.AbortWithError(http.StatusBadRequest, err).SetType(ErrorTypeBind) + return err + } + return nil +} + // MustBindWith binds the passed struct pointer using the specified binding engine. // It will abort the request with HTTP 400 if any error occurs. // See the binding package. -func (c *Context) MustBindWith(obj interface{}, b binding.Binding) (err error) { - if err = c.ShouldBindWith(obj, b); err != nil { +func (c *Context) MustBindWith(obj interface{}, b binding.Binding) error { + if err := c.ShouldBindWith(obj, b); err != nil { c.AbortWithError(http.StatusBadRequest, err).SetType(ErrorTypeBind) + return err } - - return + return nil } // ShouldBind checks the Content-Type to select a binding engine automatically, diff --git a/githubapi_test.go b/githubapi_test.go index 6b56a2b7..5253425a 100644 --- a/githubapi_test.go +++ b/githubapi_test.go @@ -290,8 +290,8 @@ func TestShouldBindUri(t *testing.T) { router := Default() type Person struct { - Name string `uri:"name"` - Id string `uri:"id"` + Name string `uri:"name" binding:"required"` + Id string `uri:"id" binding:"required"` } router.Handle("GET", "/rest/:name/:id", func(c *Context) { var person Person @@ -304,6 +304,46 @@ func TestShouldBindUri(t *testing.T) { path, _ := exampleFromPath("/rest/:name/:id") w := performRequest(router, "GET", path) assert.Equal(t, "ShouldBindUri test OK", w.Body.String()) + assert.Equal(t, http.StatusOK, w.Code) +} + +func TestBindUri(t *testing.T) { + DefaultWriter = os.Stdout + router := Default() + + type Person struct { + Name string `uri:"name" binding:"required"` + Id string `uri:"id" binding:"required"` + } + router.Handle("GET", "/rest/:name/:id", func(c *Context) { + var person Person + assert.NoError(t, c.BindUri(&person)) + assert.True(t, "" != person.Name) + assert.True(t, "" != person.Id) + c.String(http.StatusOK, "BindUri test OK") + }) + + path, _ := exampleFromPath("/rest/:name/:id") + w := performRequest(router, "GET", path) + assert.Equal(t, "BindUri test OK", w.Body.String()) + assert.Equal(t, http.StatusOK, w.Code) +} + +func TestBindUriError(t *testing.T) { + DefaultWriter = os.Stdout + router := Default() + + type Member struct { + Number string `uri:"num" binding:"required,uuid"` + } + router.Handle("GET", "/new/rest/:num", func(c *Context) { + var m Member + c.BindUri(&m) + }) + + path1, _ := exampleFromPath("/new/rest/:num") + w1 := performRequest(router, "GET", path1) + assert.Equal(t, http.StatusBadRequest, w1.Code) } func githubConfigRouter(router *Engine) { From f67d7a90c4d2e5bdf310a78d7e6a04e3d9aee851 Mon Sep 17 00:00:00 2001 From: Romain Beuque Date: Thu, 13 Dec 2018 12:20:17 +0900 Subject: [PATCH 32/46] context: inherits context cancelation and deadline from http.Request context for Go>=1.7 (#1690) *gin.Context implements standard context.Context methods, but always returns data as context is still valid. Since Go 1.7, http.Request now contains a context.Context object, which can be controlled by the http.Server to indicates that the context is now closed, and persue of request should be canceled. This implements the propagation of http.Request context methods inside gin.Context to have HTTP context cancelation information at gin.Context level. Signed-off-by: Romain Beuque --- context.go | 28 -------------------------- context_17.go | 30 ++++++++++++++++++++++++++++ context_17_test.go | 49 ++++++++++++++++++++++++++++++++++++++++++++++ context_pre17.go | 39 ++++++++++++++++++++++++++++++++++++ 4 files changed, 118 insertions(+), 28 deletions(-) create mode 100644 context_pre17.go diff --git a/context.go b/context.go index c94926e1..c38b2b87 100644 --- a/context.go +++ b/context.go @@ -942,34 +942,6 @@ func (c *Context) SetAccepted(formats ...string) { c.Accepted = formats } -/************************************/ -/***** GOLANG.ORG/X/NET/CONTEXT *****/ -/************************************/ - -// Deadline returns the time when work done on behalf of this context -// should be canceled. Deadline returns ok==false when no deadline is -// set. Successive calls to Deadline return the same results. -func (c *Context) Deadline() (deadline time.Time, ok bool) { - return -} - -// Done returns a channel that's closed when work done on behalf of this -// context should be canceled. Done may return nil if this context can -// never be canceled. Successive calls to Done return the same value. -func (c *Context) Done() <-chan struct{} { - return nil -} - -// Err returns a non-nil error value after Done is closed, -// successive calls to Err return the same error. -// If Done is not yet closed, Err returns nil. -// If Done is closed, Err returns a non-nil error explaining why: -// Canceled if the context was canceled -// or DeadlineExceeded if the context's deadline passed. -func (c *Context) Err() error { - return nil -} - // Value returns the value associated with this context for key, or nil // if no value is associated with key. Successive calls to Value with // the same key returns the same result. diff --git a/context_17.go b/context_17.go index 8e9f75ad..024dcb70 100644 --- a/context_17.go +++ b/context_17.go @@ -7,6 +7,8 @@ package gin import ( + "time" + "github.com/gin-gonic/gin/render" ) @@ -15,3 +17,31 @@ import ( func (c *Context) PureJSON(code int, obj interface{}) { c.Render(code, render.PureJSON{Data: obj}) } + +/************************************/ +/***** GOLANG.ORG/X/NET/CONTEXT *****/ +/************************************/ + +// Deadline returns the time when work done on behalf of this context +// should be canceled. Deadline returns ok==false when no deadline is +// set. Successive calls to Deadline return the same results. +func (c *Context) Deadline() (time.Time, bool) { + return c.Request.Context().Deadline() +} + +// Done returns a channel that's closed when work done on behalf of this +// context should be canceled. Done may return nil if this context can +// never be canceled. Successive calls to Done return the same value. +func (c *Context) Done() <-chan struct{} { + return c.Request.Context().Done() +} + +// Err returns a non-nil error value after Done is closed, +// successive calls to Err return the same error. +// If Done is not yet closed, Err returns nil. +// If Done is closed, Err returns a non-nil error explaining why: +// Canceled if the context was canceled +// or DeadlineExceeded if the context's deadline passed. +func (c *Context) Err() error { + return c.Request.Context().Err() +} diff --git a/context_17_test.go b/context_17_test.go index 5b9ebcdc..f2a2f184 100644 --- a/context_17_test.go +++ b/context_17_test.go @@ -7,9 +7,12 @@ package gin import ( + "bytes" + "context" "net/http" "net/http/httptest" "testing" + "time" "github.com/stretchr/testify/assert" ) @@ -25,3 +28,49 @@ func TestContextRenderPureJSON(t *testing.T) { assert.Equal(t, "{\"foo\":\"bar\",\"html\":\"\"}\n", w.Body.String()) assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) } + +func TestContextHTTPContext(t *testing.T) { + c, _ := CreateTestContext(httptest.NewRecorder()) + req, _ := http.NewRequest("POST", "/", bytes.NewBufferString("{\"foo\":\"bar\", \"bar\":\"foo\"}")) + ctx, cancelFunc := context.WithCancel(context.Background()) + defer cancelFunc() + c.Request = req.WithContext(ctx) + + assert.NoError(t, c.Err()) + assert.NotNil(t, c.Done()) + select { + case <-c.Done(): + assert.Fail(t, "context should not be canceled") + default: + } + + ti, ok := c.Deadline() + assert.Equal(t, ti, time.Time{}) + assert.False(t, ok) + assert.Equal(t, c.Value(0), c.Request) + + cancelFunc() + assert.NotNil(t, c.Done()) + select { + case <-c.Done(): + default: + assert.Fail(t, "context should be canceled") + } +} + +func TestContextHTTPContextWithDeadline(t *testing.T) { + c, _ := CreateTestContext(httptest.NewRecorder()) + req, _ := http.NewRequest("POST", "/", bytes.NewBufferString("{\"foo\":\"bar\", \"bar\":\"foo\"}")) + location, _ := time.LoadLocation("Europe/Paris") + assert.NotNil(t, location) + date := time.Date(2031, 12, 27, 16, 00, 00, 00, location) + ctx, cancelFunc := context.WithDeadline(context.Background(), date) + defer cancelFunc() + c.Request = req.WithContext(ctx) + + assert.NoError(t, c.Err()) + + ti, ok := c.Deadline() + assert.Equal(t, ti, date) + assert.True(t, ok) +} diff --git a/context_pre17.go b/context_pre17.go new file mode 100644 index 00000000..2008d3c6 --- /dev/null +++ b/context_pre17.go @@ -0,0 +1,39 @@ +// Copyright 2018 Gin Core Team. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +// +build !go1.7 + +package gin + +import ( + "time" +) + +/************************************/ +/***** GOLANG.ORG/X/NET/CONTEXT *****/ +/************************************/ + +// Deadline returns the time when work done on behalf of this context +// should be canceled. Deadline returns ok==false when no deadline is +// set. Successive calls to Deadline return the same results. +func (c *Context) Deadline() (deadline time.Time, ok bool) { + return +} + +// Done returns a channel that's closed when work done on behalf of this +// context should be canceled. Done may return nil if this context can +// never be canceled. Successive calls to Done return the same value. +func (c *Context) Done() <-chan struct{} { + return nil +} + +// Err returns a non-nil error value after Done is closed, +// successive calls to Err return the same error. +// If Done is not yet closed, Err returns nil. +// If Done is closed, Err returns a non-nil error explaining why: +// Canceled if the context was canceled +// or DeadlineExceeded if the context's deadline passed. +func (c *Context) Err() error { + return nil +} From 1542eff27f7d67ef56543358e2f623eb4ea8adf9 Mon Sep 17 00:00:00 2001 From: Ganlv Date: Mon, 17 Dec 2018 08:13:07 +0800 Subject: [PATCH 33/46] Fix #1693: file.Filename should not be trusted (#1699) --- README.md | 4 ++++ examples/upload-file/multiple/main.go | 4 +++- examples/upload-file/single/main.go | 4 +++- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c1f902a9..2dc9e5ff 100644 --- a/README.md +++ b/README.md @@ -364,6 +364,10 @@ ids: map[b:hello a:1234], names: map[second:tianou first:thinkerou] References issue [#774](https://github.com/gin-gonic/gin/issues/774) and detail [example code](examples/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() diff --git a/examples/upload-file/multiple/main.go b/examples/upload-file/multiple/main.go index a55325ed..2b9d6d91 100644 --- a/examples/upload-file/multiple/main.go +++ b/examples/upload-file/multiple/main.go @@ -3,6 +3,7 @@ package main import ( "fmt" "net/http" + "path/filepath" "github.com/gin-gonic/gin" ) @@ -25,7 +26,8 @@ func main() { files := form.File["files"] for _, file := range files { - if err := c.SaveUploadedFile(file, file.Filename); err != nil { + filename := filepath.Base(file.Filename) + if err := c.SaveUploadedFile(file, filename); err != nil { c.String(http.StatusBadRequest, fmt.Sprintf("upload file err: %s", err.Error())) return } diff --git a/examples/upload-file/single/main.go b/examples/upload-file/single/main.go index 5d438651..ba289f54 100644 --- a/examples/upload-file/single/main.go +++ b/examples/upload-file/single/main.go @@ -3,6 +3,7 @@ package main import ( "fmt" "net/http" + "path/filepath" "github.com/gin-gonic/gin" ) @@ -23,7 +24,8 @@ func main() { return } - if err := c.SaveUploadedFile(file, file.Filename); err != nil { + filename := filepath.Base(file.Filename) + if err := c.SaveUploadedFile(file, filename); err != nil { c.String(http.StatusBadRequest, fmt.Sprintf("upload file err: %s", err.Error())) return } From 678e09c736505225e28d8c585087b84faaf4bb80 Mon Sep 17 00:00:00 2001 From: Sai Date: Thu, 20 Dec 2018 18:54:08 +0900 Subject: [PATCH 34/46] Plural is "Paths", not "Pathes" (#1706) --- logger.go | 10 +++++----- logger_test.go | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/logger.go b/logger.go index a64af697..b9c63c73 100644 --- a/logger.go +++ b/logger.go @@ -35,9 +35,9 @@ type LoggerConfig struct { // Optional. Default value is gin.DefaultWriter. Output io.Writer - // SkipPathes is a url path array which logs are not written. + // SkipPaths is a url path array which logs are not written. // Optional. - SkipPathes []string + SkipPaths []string } // LogFormatter gives the signature of the formatter function passed to LoggerWithFormatter @@ -114,8 +114,8 @@ func LoggerWithFormatter(f LogFormatter) HandlerFunc { // Example: os.Stdout, a file opened in write mode, a socket... func LoggerWithWriter(out io.Writer, notlogged ...string) HandlerFunc { return LoggerWithConfig(LoggerConfig{ - Output: out, - SkipPathes: notlogged, + Output: out, + SkipPaths: notlogged, }) } @@ -131,7 +131,7 @@ func LoggerWithConfig(conf LoggerConfig) HandlerFunc { out = DefaultWriter } - notlogged := conf.SkipPathes + notlogged := conf.SkipPaths isTerm := true diff --git a/logger_test.go b/logger_test.go index 909ddd39..350599d4 100644 --- a/logger_test.go +++ b/logger_test.go @@ -320,8 +320,8 @@ func TestLoggerWithConfigSkippingPaths(t *testing.T) { buffer := new(bytes.Buffer) router := New() router.Use(LoggerWithConfig(LoggerConfig{ - Output: buffer, - SkipPathes: []string{"/skipped"}, + Output: buffer, + SkipPaths: []string{"/skipped"}, })) router.GET("/logged", func(c *Context) {}) router.GET("/skipped", func(c *Context) {}) From 2d33c82028b4085827137c5a72cc0a076d8e2b08 Mon Sep 17 00:00:00 2001 From: Sai Date: Wed, 26 Dec 2018 00:27:24 +0900 Subject: [PATCH 35/46] Add comment to LogFormatterParams struct's fields (#1711) By https://github.com/gin-gonic/gin/issues/1701, I thought it's necessary. --- logger.go | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/logger.go b/logger.go index b9c63c73..a55d26e0 100644 --- a/logger.go +++ b/logger.go @@ -45,15 +45,24 @@ type LogFormatter func(params LogFormatterParams) string // LogFormatterParams is the structure any formatter will be handed when time to log comes type LogFormatterParams struct { - Request *http.Request - TimeStamp time.Time - StatusCode int - Latency time.Duration - ClientIP string - Method string - Path string + Request *http.Request + + // TimeStamp shows the time after the server returns a response. + TimeStamp time.Time + // StatusCode is HTTP response code. + StatusCode int + // Latency is how much time the server cost to process a certain request. + Latency time.Duration + // ClientIP equals Context's ClientIP method. + ClientIP string + // Method is the HTTP method given to the request. + Method string + // Path is a path the client requests. + Path string + // ErrorMessage is set if error has occurred in processing the request. ErrorMessage string - IsTerm bool + // IsTerm shows whether does gin's output descriptor refers to a terminal. + IsTerm bool } // defaultLogFormatter is the default log format function Logger middleware uses. From 1b34e8e8de41654004dfabf20fbadf43f619d41b Mon Sep 17 00:00:00 2001 From: thinkerou Date: Tue, 25 Dec 2018 23:40:11 +0800 Subject: [PATCH 36/46] chore: attemp to fix #1700 (#1707) --- tools.go => tools/tools.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename tools.go => tools/tools.go (88%) diff --git a/tools.go b/tools/tools.go similarity index 88% rename from tools.go rename to tools/tools.go index 9f96406a..7113e71e 100644 --- a/tools.go +++ b/tools/tools.go @@ -4,12 +4,12 @@ // +build tools -// This file exists to cause `go mod` and `go get` to believe these tools +// This package exists to cause `go mod` and `go get` to believe these tools // are dependencies, even though they are not runtime dependencies of any // gin package. This means they will appear in `go.mod` file, but will not // be a part of the build. -package gin +package tools import ( _ "github.com/campoy/embedmd" From 0bfc9cbcdbaa13e5fd633f77a32f22c30f1e2553 Mon Sep 17 00:00:00 2001 From: thinkerou Date: Wed, 26 Dec 2018 00:27:46 +0800 Subject: [PATCH 37/46] ci: exit 1 when build fail (#1695) Like this: ``` FAIL github.com/gin-gonic/gin [build failed] ``` --- Makefile | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index b0d2e24a..7211144a 100644 --- a/Makefile +++ b/Makefile @@ -18,7 +18,10 @@ test: cat tmp.out; \ if grep -q "^--- FAIL" tmp.out; then \ rm tmp.out; \ - exit 1;\ + exit 1; \ + elif grep -q "build failed" tmp.out; then \ + rm tmp.out; \ + exit; \ fi; \ if [ -f profile.out ]; then \ cat profile.out | grep -v "mode:" >> coverage.out; \ From 49e4b0c60cb533e943c34a8d637944f25fa47ee6 Mon Sep 17 00:00:00 2001 From: Dmitry Kutakov Date: Fri, 28 Dec 2018 04:57:09 +0300 Subject: [PATCH 38/46] fix mapping inner structs with correct tag (#1718) --- binding/binding_test.go | 23 +++++++++++++++++++++++ binding/form_mapping.go | 2 +- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/binding/binding_test.go b/binding/binding_test.go index c0204d7f..740749be 100644 --- a/binding/binding_test.go +++ b/binding/binding_test.go @@ -11,6 +11,7 @@ import ( "io/ioutil" "mime/multipart" "net/http" + "strconv" "testing" "time" @@ -690,6 +691,28 @@ func TestUriBinding(t *testing.T) { assert.Equal(t, map[string]interface{}(nil), not.Name) } +func TestUriInnerBinding(t *testing.T) { + type Tag struct { + Name string `uri:"name"` + S struct { + Age int `uri:"age"` + } + } + + expectedName := "mike" + expectedAge := 25 + + m := map[string][]string{ + "name": {expectedName}, + "age": {strconv.Itoa(expectedAge)}, + } + + var tag Tag + assert.NoError(t, Uri.BindUri(m, &tag)) + assert.Equal(t, tag.Name, expectedName) + assert.Equal(t, tag.S.Age, expectedAge) +} + func testFormBinding(t *testing.T, method, path, badPath, body, badBody string) { b := Form assert.Equal(t, "form", b.Name()) diff --git a/binding/form_mapping.go b/binding/form_mapping.go index d893c21c..8900ab70 100644 --- a/binding/form_mapping.go +++ b/binding/form_mapping.go @@ -55,7 +55,7 @@ func mapFormByTag(ptr interface{}, form map[string][]string, tag string) error { structFieldKind = structField.Kind() } if structFieldKind == reflect.Struct { - err := mapForm(structField.Addr().Interface(), form) + err := mapFormByTag(structField.Addr().Interface(), form, tag) if err != nil { return err } From 807368579f8939cedfa59ec689708754920f93e4 Mon Sep 17 00:00:00 2001 From: Dmitry Kutakov Date: Fri, 28 Dec 2018 05:26:29 +0300 Subject: [PATCH 39/46] fix test - auto choose port number (#1719) --- gin_integration_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gin_integration_test.go b/gin_integration_test.go index e14a688c..01d5cf5e 100644 --- a/gin_integration_test.go +++ b/gin_integration_test.go @@ -137,7 +137,7 @@ func TestBadUnixSocket(t *testing.T) { func TestFileDescriptor(t *testing.T) { router := New() - addr, err := net.ResolveTCPAddr("tcp", ":8000") + addr, err := net.ResolveTCPAddr("tcp", "localhost:0") assert.NoError(t, err) listener, err := net.ListenTCP("tcp", addr) assert.NoError(t, err) @@ -152,7 +152,7 @@ func TestFileDescriptor(t *testing.T) { // otherwise the main thread will complete time.Sleep(5 * time.Millisecond) - c, err := net.Dial("tcp", "localhost:8000") + c, err := net.Dial("tcp", listener.Addr().String()) assert.NoError(t, err) fmt.Fprintf(c, "GET /example HTTP/1.0\r\n\r\n") From 85b92cdf4bc9bf33fd6f199ff866a1eed3511c80 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Sat, 29 Dec 2018 11:46:26 +0800 Subject: [PATCH 40/46] chore(testing): case sensitive for query string (#1720) fix #1692 --- context_test.go | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/context_test.go b/context_test.go index dced73fd..836b3ecb 100644 --- a/context_test.go +++ b/context_test.go @@ -1457,7 +1457,7 @@ func TestContextShouldBindWithXML(t *testing.T) { c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString(` FOO - BAR + BAR `)) c.Request.Header.Add("Content-Type", MIMEXML) // set fake content-type @@ -1475,15 +1475,19 @@ func TestContextShouldBindWithQuery(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.Request, _ = http.NewRequest("POST", "/?foo=bar&bar=foo", bytes.NewBufferString("foo=unused")) + c.Request, _ = http.NewRequest("POST", "/?foo=bar&bar=foo&Foo=bar1&Bar=foo1", bytes.NewBufferString("foo=unused")) var obj struct { - Foo string `form:"foo"` - Bar string `form:"bar"` + Foo string `form:"foo"` + Bar string `form:"bar"` + Foo1 string `form:"Foo"` + Bar1 string `form:"Bar"` } assert.NoError(t, c.ShouldBindQuery(&obj)) assert.Equal(t, "foo", obj.Bar) assert.Equal(t, "bar", obj.Foo) + assert.Equal(t, "foo1", obj.Bar1) + assert.Equal(t, "bar1", obj.Foo1) assert.Equal(t, 0, w.Body.Len()) } From d8fb18c33b1657271b6302e0899d033902012f49 Mon Sep 17 00:00:00 2001 From: John Bampton Date: Mon, 31 Dec 2018 11:02:53 +1000 Subject: [PATCH 41/46] Fix case of GitHub (#1726) --- routergroup.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/routergroup.go b/routergroup.go index 2b41dfda..297d3574 100644 --- a/routergroup.go +++ b/routergroup.go @@ -47,7 +47,7 @@ type RouterGroup struct { var _ IRouter = &RouterGroup{} -// Use adds middleware to the group, see example code in github. +// Use adds middleware to the group, see example code in GitHub. func (group *RouterGroup) Use(middleware ...HandlerFunc) IRoutes { group.Handlers = append(group.Handlers, middleware...) return group.returnObj() @@ -78,7 +78,7 @@ func (group *RouterGroup) handle(httpMethod, relativePath string, handlers Handl // Handle registers a new request handle and middleware with the given path and method. // The last handler should be the real handler, the other ones should be middleware that can and should be shared among different routes. -// See the example code in github. +// See the example code in GitHub. // // For GET, POST, PUT, PATCH and DELETE requests the respective shortcut // functions can be used. From 29a145c85dc0fafc3dd0ada62d856c4d95240010 Mon Sep 17 00:00:00 2001 From: thinkerou Date: Wed, 9 Jan 2019 09:32:44 +0800 Subject: [PATCH 42/46] Revert "context: inherits context cancelation and deadline from http.Request context for Go>=1.7 (#1690)" (#1736) This reverts commit f67d7a90c4d2e5bdf310a78d7e6a04e3d9aee851. --- context.go | 28 ++++++++++++++++++++++++++ context_17.go | 30 ---------------------------- context_17_test.go | 49 ---------------------------------------------- context_pre17.go | 39 ------------------------------------ 4 files changed, 28 insertions(+), 118 deletions(-) delete mode 100644 context_pre17.go diff --git a/context.go b/context.go index c38b2b87..c94926e1 100644 --- a/context.go +++ b/context.go @@ -942,6 +942,34 @@ func (c *Context) SetAccepted(formats ...string) { c.Accepted = formats } +/************************************/ +/***** GOLANG.ORG/X/NET/CONTEXT *****/ +/************************************/ + +// Deadline returns the time when work done on behalf of this context +// should be canceled. Deadline returns ok==false when no deadline is +// set. Successive calls to Deadline return the same results. +func (c *Context) Deadline() (deadline time.Time, ok bool) { + return +} + +// Done returns a channel that's closed when work done on behalf of this +// context should be canceled. Done may return nil if this context can +// never be canceled. Successive calls to Done return the same value. +func (c *Context) Done() <-chan struct{} { + return nil +} + +// Err returns a non-nil error value after Done is closed, +// successive calls to Err return the same error. +// If Done is not yet closed, Err returns nil. +// If Done is closed, Err returns a non-nil error explaining why: +// Canceled if the context was canceled +// or DeadlineExceeded if the context's deadline passed. +func (c *Context) Err() error { + return nil +} + // Value returns the value associated with this context for key, or nil // if no value is associated with key. Successive calls to Value with // the same key returns the same result. diff --git a/context_17.go b/context_17.go index 024dcb70..8e9f75ad 100644 --- a/context_17.go +++ b/context_17.go @@ -7,8 +7,6 @@ package gin import ( - "time" - "github.com/gin-gonic/gin/render" ) @@ -17,31 +15,3 @@ import ( func (c *Context) PureJSON(code int, obj interface{}) { c.Render(code, render.PureJSON{Data: obj}) } - -/************************************/ -/***** GOLANG.ORG/X/NET/CONTEXT *****/ -/************************************/ - -// Deadline returns the time when work done on behalf of this context -// should be canceled. Deadline returns ok==false when no deadline is -// set. Successive calls to Deadline return the same results. -func (c *Context) Deadline() (time.Time, bool) { - return c.Request.Context().Deadline() -} - -// Done returns a channel that's closed when work done on behalf of this -// context should be canceled. Done may return nil if this context can -// never be canceled. Successive calls to Done return the same value. -func (c *Context) Done() <-chan struct{} { - return c.Request.Context().Done() -} - -// Err returns a non-nil error value after Done is closed, -// successive calls to Err return the same error. -// If Done is not yet closed, Err returns nil. -// If Done is closed, Err returns a non-nil error explaining why: -// Canceled if the context was canceled -// or DeadlineExceeded if the context's deadline passed. -func (c *Context) Err() error { - return c.Request.Context().Err() -} diff --git a/context_17_test.go b/context_17_test.go index f2a2f184..5b9ebcdc 100644 --- a/context_17_test.go +++ b/context_17_test.go @@ -7,12 +7,9 @@ package gin import ( - "bytes" - "context" "net/http" "net/http/httptest" "testing" - "time" "github.com/stretchr/testify/assert" ) @@ -28,49 +25,3 @@ func TestContextRenderPureJSON(t *testing.T) { assert.Equal(t, "{\"foo\":\"bar\",\"html\":\"\"}\n", w.Body.String()) assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) } - -func TestContextHTTPContext(t *testing.T) { - c, _ := CreateTestContext(httptest.NewRecorder()) - req, _ := http.NewRequest("POST", "/", bytes.NewBufferString("{\"foo\":\"bar\", \"bar\":\"foo\"}")) - ctx, cancelFunc := context.WithCancel(context.Background()) - defer cancelFunc() - c.Request = req.WithContext(ctx) - - assert.NoError(t, c.Err()) - assert.NotNil(t, c.Done()) - select { - case <-c.Done(): - assert.Fail(t, "context should not be canceled") - default: - } - - ti, ok := c.Deadline() - assert.Equal(t, ti, time.Time{}) - assert.False(t, ok) - assert.Equal(t, c.Value(0), c.Request) - - cancelFunc() - assert.NotNil(t, c.Done()) - select { - case <-c.Done(): - default: - assert.Fail(t, "context should be canceled") - } -} - -func TestContextHTTPContextWithDeadline(t *testing.T) { - c, _ := CreateTestContext(httptest.NewRecorder()) - req, _ := http.NewRequest("POST", "/", bytes.NewBufferString("{\"foo\":\"bar\", \"bar\":\"foo\"}")) - location, _ := time.LoadLocation("Europe/Paris") - assert.NotNil(t, location) - date := time.Date(2031, 12, 27, 16, 00, 00, 00, location) - ctx, cancelFunc := context.WithDeadline(context.Background(), date) - defer cancelFunc() - c.Request = req.WithContext(ctx) - - assert.NoError(t, c.Err()) - - ti, ok := c.Deadline() - assert.Equal(t, ti, date) - assert.True(t, ok) -} diff --git a/context_pre17.go b/context_pre17.go deleted file mode 100644 index 2008d3c6..00000000 --- a/context_pre17.go +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright 2018 Gin Core Team. All rights reserved. -// Use of this source code is governed by a MIT style -// license that can be found in the LICENSE file. - -// +build !go1.7 - -package gin - -import ( - "time" -) - -/************************************/ -/***** GOLANG.ORG/X/NET/CONTEXT *****/ -/************************************/ - -// Deadline returns the time when work done on behalf of this context -// should be canceled. Deadline returns ok==false when no deadline is -// set. Successive calls to Deadline return the same results. -func (c *Context) Deadline() (deadline time.Time, ok bool) { - return -} - -// Done returns a channel that's closed when work done on behalf of this -// context should be canceled. Done may return nil if this context can -// never be canceled. Successive calls to Done return the same value. -func (c *Context) Done() <-chan struct{} { - return nil -} - -// Err returns a non-nil error value after Done is closed, -// successive calls to Err return the same error. -// If Done is not yet closed, Err returns nil. -// If Done is closed, Err returns a non-nil error explaining why: -// Canceled if the context was canceled -// or DeadlineExceeded if the context's deadline passed. -func (c *Context) Err() error { - return nil -} From b056a34bdc2aa45256c4f5bdad306c35ec70c37e Mon Sep 17 00:00:00 2001 From: Dmitry Kutakov Date: Fri, 18 Jan 2019 04:32:53 +0300 Subject: [PATCH 43/46] fix errcheck warnings (#1739) --- binding/binding_test.go | 24 +++++++++++----------- binding/form.go | 6 +++++- context.go | 22 +++++++++++++------- context_test.go | 36 +++++++++++++++++++-------------- debug_test.go | 19 ++++++++--------- errors_test.go | 6 +++--- gin.go | 5 ++++- gin_integration_test.go | 2 +- githubapi_test.go | 2 +- logger_test.go | 6 +++--- middleware_test.go | 2 +- recovery.go | 2 +- render/json.go | 45 +++++++++++++++++++++++++++-------------- render/protobuf.go | 4 ++-- render/render_test.go | 4 ++-- render/text.go | 10 ++++----- render/yaml.go | 4 ++-- response_writer_test.go | 3 ++- routes_test.go | 3 ++- 19 files changed, 122 insertions(+), 83 deletions(-) diff --git a/binding/binding_test.go b/binding/binding_test.go index 740749be..1044e6c2 100644 --- a/binding/binding_test.go +++ b/binding/binding_test.go @@ -516,28 +516,28 @@ func createFormPostRequestFail() *http.Request { return req } -func createFormMultipartRequest() *http.Request { +func createFormMultipartRequest(t *testing.T) *http.Request { boundary := "--testboundary" body := new(bytes.Buffer) mw := multipart.NewWriter(body) defer mw.Close() - mw.SetBoundary(boundary) - mw.WriteField("foo", "bar") - mw.WriteField("bar", "foo") + assert.NoError(t, mw.SetBoundary(boundary)) + assert.NoError(t, mw.WriteField("foo", "bar")) + assert.NoError(t, mw.WriteField("bar", "foo")) req, _ := http.NewRequest("POST", "/?foo=getfoo&bar=getbar", body) req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary) return req } -func createFormMultipartRequestFail() *http.Request { +func createFormMultipartRequestFail(t *testing.T) *http.Request { boundary := "--testboundary" body := new(bytes.Buffer) mw := multipart.NewWriter(body) defer mw.Close() - mw.SetBoundary(boundary) - mw.WriteField("map_foo", "bar") + assert.NoError(t, mw.SetBoundary(boundary)) + assert.NoError(t, mw.WriteField("map_foo", "bar")) req, _ := http.NewRequest("POST", "/?map_foo=getfoo", body) req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary) return req @@ -546,7 +546,7 @@ func createFormMultipartRequestFail() *http.Request { func TestBindingFormPost(t *testing.T) { req := createFormPostRequest() var obj FooBarStruct - FormPost.Bind(req, &obj) + assert.NoError(t, FormPost.Bind(req, &obj)) assert.Equal(t, "form-urlencoded", FormPost.Name()) assert.Equal(t, "bar", obj.Foo) @@ -556,7 +556,7 @@ func TestBindingFormPost(t *testing.T) { func TestBindingDefaultValueFormPost(t *testing.T) { req := createDefaultFormPostRequest() var obj FooDefaultBarStruct - FormPost.Bind(req, &obj) + assert.NoError(t, FormPost.Bind(req, &obj)) assert.Equal(t, "bar", obj.Foo) assert.Equal(t, "hello", obj.Bar) @@ -570,9 +570,9 @@ func TestBindingFormPostFail(t *testing.T) { } func TestBindingFormMultipart(t *testing.T) { - req := createFormMultipartRequest() + req := createFormMultipartRequest(t) var obj FooBarStruct - FormMultipart.Bind(req, &obj) + assert.NoError(t, FormMultipart.Bind(req, &obj)) assert.Equal(t, "multipart/form-data", FormMultipart.Name()) assert.Equal(t, "bar", obj.Foo) @@ -580,7 +580,7 @@ func TestBindingFormMultipart(t *testing.T) { } func TestBindingFormMultipartFail(t *testing.T) { - req := createFormMultipartRequestFail() + req := createFormMultipartRequestFail(t) var obj FooStructForMapType err := FormMultipart.Bind(req, &obj) assert.Error(t, err) diff --git a/binding/form.go b/binding/form.go index 0be59660..8955c95b 100644 --- a/binding/form.go +++ b/binding/form.go @@ -20,7 +20,11 @@ func (formBinding) Bind(req *http.Request, obj interface{}) error { if err := req.ParseForm(); err != nil { return err } - req.ParseMultipartForm(defaultMemory) + if err := req.ParseMultipartForm(defaultMemory); err != nil { + if err != http.ErrNotMultipart { + return err + } + } if err := mapForm(obj, req.Form); err != nil { return err } diff --git a/context.go b/context.go index c94926e1..4dc94ea9 100644 --- a/context.go +++ b/context.go @@ -415,7 +415,11 @@ func (c *Context) PostFormArray(key string) []string { // a boolean value whether at least one value exists for the given key. func (c *Context) GetPostFormArray(key string) ([]string, bool) { req := c.Request - req.ParseMultipartForm(c.engine.MaxMultipartMemory) + if err := req.ParseMultipartForm(c.engine.MaxMultipartMemory); err != nil { + if err != http.ErrNotMultipart { + debugPrint("error on parse multipart form array: %v", err) + } + } if values := req.PostForm[key]; len(values) > 0 { return values, true } @@ -437,7 +441,11 @@ func (c *Context) PostFormMap(key string) map[string]string { // whether at least one value exists for the given key. func (c *Context) GetPostFormMap(key string) (map[string]string, bool) { req := c.Request - req.ParseMultipartForm(c.engine.MaxMultipartMemory) + if err := req.ParseMultipartForm(c.engine.MaxMultipartMemory); err != nil { + if err != http.ErrNotMultipart { + debugPrint("error on parse multipart form map: %v", err) + } + } dicts, exist := c.get(req.PostForm, key) if !exist && req.MultipartForm != nil && req.MultipartForm.File != nil { @@ -493,8 +501,8 @@ func (c *Context) SaveUploadedFile(file *multipart.FileHeader, dst string) error } defer out.Close() - io.Copy(out, src) - return nil + _, err = io.Copy(out, src) + return err } // Bind checks the Content-Type to select a binding engine automatically, @@ -534,7 +542,7 @@ func (c *Context) BindYAML(obj interface{}) error { // It will abort the request with HTTP 400 if any error occurs. func (c *Context) BindUri(obj interface{}) error { if err := c.ShouldBindUri(obj); err != nil { - c.AbortWithError(http.StatusBadRequest, err).SetType(ErrorTypeBind) + c.AbortWithError(http.StatusBadRequest, err).SetType(ErrorTypeBind) // nolint: errcheck return err } return nil @@ -545,7 +553,7 @@ func (c *Context) BindUri(obj interface{}) error { // See the binding package. func (c *Context) MustBindWith(obj interface{}, b binding.Binding) error { if err := c.ShouldBindWith(obj, b); err != nil { - c.AbortWithError(http.StatusBadRequest, err).SetType(ErrorTypeBind) + c.AbortWithError(http.StatusBadRequest, err).SetType(ErrorTypeBind) // nolint: errcheck return err } return nil @@ -913,7 +921,7 @@ func (c *Context) Negotiate(code int, config Negotiate) { c.XML(code, data) default: - c.AbortWithError(http.StatusNotAcceptable, errors.New("the accepted formats are not offered by the server")) + c.AbortWithError(http.StatusNotAcceptable, errors.New("the accepted formats are not offered by the server")) // nolint: errcheck } } diff --git a/context_test.go b/context_test.go index 836b3ecb..29ec1a24 100644 --- a/context_test.go +++ b/context_test.go @@ -70,7 +70,8 @@ func TestContextFormFile(t *testing.T) { mw := multipart.NewWriter(buf) w, err := mw.CreateFormFile("file", "test") if assert.NoError(t, err) { - w.Write([]byte("test")) + _, err = w.Write([]byte("test")) + assert.NoError(t, err) } mw.Close() c, _ := CreateTestContext(httptest.NewRecorder()) @@ -100,10 +101,11 @@ func TestContextFormFileFailed(t *testing.T) { func TestContextMultipartForm(t *testing.T) { buf := new(bytes.Buffer) mw := multipart.NewWriter(buf) - mw.WriteField("foo", "bar") + assert.NoError(t, mw.WriteField("foo", "bar")) w, err := mw.CreateFormFile("file", "test") if assert.NoError(t, err) { - w.Write([]byte("test")) + _, err = w.Write([]byte("test")) + assert.NoError(t, err) } mw.Close() c, _ := CreateTestContext(httptest.NewRecorder()) @@ -137,7 +139,8 @@ func TestSaveUploadedCreateFailed(t *testing.T) { mw := multipart.NewWriter(buf) w, err := mw.CreateFormFile("file", "test") if assert.NoError(t, err) { - w.Write([]byte("test")) + _, err = w.Write([]byte("test")) + assert.NoError(t, err) } mw.Close() c, _ := CreateTestContext(httptest.NewRecorder()) @@ -159,7 +162,7 @@ func TestContextReset(t *testing.T) { c.index = 2 c.Writer = &responseWriter{ResponseWriter: httptest.NewRecorder()} c.Params = Params{Param{}} - c.Error(errors.New("test")) + c.Error(errors.New("test")) // nolint: errcheck c.Set("foo", "bar") c.reset() @@ -798,7 +801,7 @@ func TestContextRenderHTML2(t *testing.T) { assert.Len(t, router.trees, 1) templ := template.Must(template.New("t").Parse(`Hello {{.name}}`)) - re := captureOutput(func() { + re := captureOutput(t, func() { SetMode(DebugMode) router.SetHTMLTemplate(templ) SetMode(TestMode) @@ -1211,7 +1214,8 @@ func TestContextAbortWithStatusJSON(t *testing.T) { assert.Equal(t, "application/json; charset=utf-8", contentType) buf := new(bytes.Buffer) - buf.ReadFrom(w.Body) + _, err := buf.ReadFrom(w.Body) + assert.NoError(t, err) jsonStringBody := buf.String() assert.Equal(t, fmt.Sprint(`{"foo":"fooValue","bar":"barValue"}`), jsonStringBody) } @@ -1220,11 +1224,11 @@ func TestContextError(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) assert.Empty(t, c.Errors) - c.Error(errors.New("first error")) + c.Error(errors.New("first error")) // nolint: errcheck assert.Len(t, c.Errors, 1) assert.Equal(t, "Error #01: first error\n", c.Errors.String()) - c.Error(&Error{ + c.Error(&Error{ // nolint: errcheck Err: errors.New("second error"), Meta: "some data 2", Type: ErrorTypePublic, @@ -1246,13 +1250,13 @@ func TestContextError(t *testing.T) { t.Error("didn't panic") } }() - c.Error(nil) + c.Error(nil) // nolint: errcheck } func TestContextTypedError(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) - c.Error(errors.New("externo 0")).SetType(ErrorTypePublic) - c.Error(errors.New("interno 0")).SetType(ErrorTypePrivate) + c.Error(errors.New("externo 0")).SetType(ErrorTypePublic) // nolint: errcheck + c.Error(errors.New("interno 0")).SetType(ErrorTypePrivate) // nolint: errcheck for _, err := range c.Errors.ByType(ErrorTypePublic) { assert.Equal(t, ErrorTypePublic, err.Type) @@ -1267,7 +1271,7 @@ func TestContextAbortWithError(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.AbortWithError(http.StatusUnauthorized, errors.New("bad input")).SetMeta("some input") + c.AbortWithError(http.StatusUnauthorized, errors.New("bad input")).SetMeta("some input") // nolint: errcheck assert.Equal(t, http.StatusUnauthorized, w.Code) assert.Equal(t, abortIndex, c.index) @@ -1713,7 +1717,8 @@ func TestContextStream(t *testing.T) { stopStream = false }() - w.Write([]byte("test")) + _, err := w.Write([]byte("test")) + assert.NoError(t, err) return stopStream }) @@ -1730,7 +1735,8 @@ func TestContextStreamWithClientGone(t *testing.T) { w.closeClient() }() - writer.Write([]byte("test")) + _, err := writer.Write([]byte("test")) + assert.NoError(t, err) return true }) diff --git a/debug_test.go b/debug_test.go index 97ff166b..b3485d70 100644 --- a/debug_test.go +++ b/debug_test.go @@ -32,7 +32,7 @@ func TestIsDebugging(t *testing.T) { } func TestDebugPrint(t *testing.T) { - re := captureOutput(func() { + re := captureOutput(t, func() { SetMode(DebugMode) SetMode(ReleaseMode) debugPrint("DEBUG this!") @@ -46,7 +46,7 @@ func TestDebugPrint(t *testing.T) { } func TestDebugPrintError(t *testing.T) { - re := captureOutput(func() { + re := captureOutput(t, func() { SetMode(DebugMode) debugPrintError(nil) debugPrintError(errors.New("this is an error")) @@ -56,7 +56,7 @@ func TestDebugPrintError(t *testing.T) { } func TestDebugPrintRoutes(t *testing.T) { - re := captureOutput(func() { + re := captureOutput(t, func() { SetMode(DebugMode) debugPrintRoute("GET", "/path/to/route/:param", HandlersChain{func(c *Context) {}, handlerNameTest}) SetMode(TestMode) @@ -65,7 +65,7 @@ func TestDebugPrintRoutes(t *testing.T) { } func TestDebugPrintLoadTemplate(t *testing.T) { - re := captureOutput(func() { + re := captureOutput(t, func() { SetMode(DebugMode) templ := template.Must(template.New("").Delims("{[{", "}]}").ParseGlob("./testdata/template/hello.tmpl")) debugPrintLoadTemplate(templ) @@ -75,7 +75,7 @@ func TestDebugPrintLoadTemplate(t *testing.T) { } func TestDebugPrintWARNINGSetHTMLTemplate(t *testing.T) { - re := captureOutput(func() { + re := captureOutput(t, func() { SetMode(DebugMode) debugPrintWARNINGSetHTMLTemplate() SetMode(TestMode) @@ -84,7 +84,7 @@ func TestDebugPrintWARNINGSetHTMLTemplate(t *testing.T) { } func TestDebugPrintWARNINGDefault(t *testing.T) { - re := captureOutput(func() { + re := captureOutput(t, func() { SetMode(DebugMode) debugPrintWARNINGDefault() SetMode(TestMode) @@ -98,7 +98,7 @@ func TestDebugPrintWARNINGDefault(t *testing.T) { } func TestDebugPrintWARNINGNew(t *testing.T) { - re := captureOutput(func() { + re := captureOutput(t, func() { SetMode(DebugMode) debugPrintWARNINGNew() SetMode(TestMode) @@ -106,7 +106,7 @@ func TestDebugPrintWARNINGNew(t *testing.T) { assert.Equal(t, "[GIN-debug] [WARNING] Running in \"debug\" mode. Switch to \"release\" mode in production.\n - using env:\texport GIN_MODE=release\n - using code:\tgin.SetMode(gin.ReleaseMode)\n\n", re) } -func captureOutput(f func()) string { +func captureOutput(t *testing.T, f func()) string { reader, writer, err := os.Pipe() if err != nil { panic(err) @@ -127,7 +127,8 @@ func captureOutput(f func()) string { go func() { var buf bytes.Buffer wg.Done() - io.Copy(&buf, reader) + _, err := io.Copy(&buf, reader) + assert.NoError(t, err) out <- buf.String() }() wg.Wait() diff --git a/errors_test.go b/errors_test.go index 9351b578..6aae1c10 100644 --- a/errors_test.go +++ b/errors_test.go @@ -34,7 +34,7 @@ func TestError(t *testing.T) { jsonBytes, _ := json.Marshal(err) assert.Equal(t, "{\"error\":\"test error\",\"meta\":\"some data\"}", string(jsonBytes)) - err.SetMeta(H{ + err.SetMeta(H{ // nolint: errcheck "status": "200", "data": "some data", }) @@ -44,7 +44,7 @@ func TestError(t *testing.T) { "data": "some data", }, err.JSON()) - err.SetMeta(H{ + err.SetMeta(H{ // nolint: errcheck "error": "custom error", "status": "200", "data": "some data", @@ -59,7 +59,7 @@ func TestError(t *testing.T) { status string data string } - err.SetMeta(customError{status: "200", data: "other data"}) + err.SetMeta(customError{status: "200", data: "other data"}) // nolint: errcheck assert.Equal(t, customError{status: "200", data: "other data"}, err.JSON()) } diff --git a/gin.go b/gin.go index b7c77e1f..cd47200f 100644 --- a/gin.go +++ b/gin.go @@ -422,7 +422,10 @@ func serveError(c *Context, code int, defaultMessage []byte) { } if c.writermem.Status() == code { c.writermem.Header()["Content-Type"] = mimePlain - c.Writer.Write(defaultMessage) + _, err := c.Writer.Write(defaultMessage) + if err != nil { + debugPrint("cannot write message to writer during serve error: %v", err) + } return } c.writermem.WriteHeaderNow() diff --git a/gin_integration_test.go b/gin_integration_test.go index 01d5cf5e..3ce6d80a 100644 --- a/gin_integration_test.go +++ b/gin_integration_test.go @@ -87,7 +87,7 @@ func TestRunEmptyWithEnv(t *testing.T) { func TestRunTooMuchParams(t *testing.T) { router := New() assert.Panics(t, func() { - router.Run("2", "2") + assert.NoError(t, router.Run("2", "2")) }) } diff --git a/githubapi_test.go b/githubapi_test.go index 5253425a..50faca09 100644 --- a/githubapi_test.go +++ b/githubapi_test.go @@ -338,7 +338,7 @@ func TestBindUriError(t *testing.T) { } router.Handle("GET", "/new/rest/:num", func(c *Context) { var m Member - c.BindUri(&m) + assert.Error(t, c.BindUri(&m)) }) path1, _ := exampleFromPath("/new/rest/:num") diff --git a/logger_test.go b/logger_test.go index 350599d4..d0169251 100644 --- a/logger_test.go +++ b/logger_test.go @@ -278,13 +278,13 @@ func TestErrorLogger(t *testing.T) { router := New() router.Use(ErrorLogger()) router.GET("/error", func(c *Context) { - c.Error(errors.New("this is an error")) + c.Error(errors.New("this is an error")) // nolint: errcheck }) router.GET("/abort", func(c *Context) { - c.AbortWithError(http.StatusUnauthorized, errors.New("no authorized")) + c.AbortWithError(http.StatusUnauthorized, errors.New("no authorized")) // nolint: errcheck }) router.GET("/print", func(c *Context) { - c.Error(errors.New("this is an error")) + c.Error(errors.New("this is an error")) // nolint: errcheck c.String(http.StatusInternalServerError, "hola!") }) diff --git a/middleware_test.go b/middleware_test.go index 983ad933..fca1c530 100644 --- a/middleware_test.go +++ b/middleware_test.go @@ -208,7 +208,7 @@ func TestMiddlewareFailHandlersChain(t *testing.T) { router := New() router.Use(func(context *Context) { signature += "A" - context.AbortWithError(http.StatusInternalServerError, errors.New("foo")) + context.AbortWithError(http.StatusInternalServerError, errors.New("foo")) // nolint: errcheck }) router.Use(func(context *Context) { signature += "B" diff --git a/recovery.go b/recovery.go index f06ad56b..0e35968f 100644 --- a/recovery.go +++ b/recovery.go @@ -66,7 +66,7 @@ func RecoveryWithWriter(out io.Writer) HandlerFunc { // If the connection is dead, we can't write a status to it. if brokenPipe { - c.Error(err.(error)) + c.Error(err.(error)) // nolint: errcheck c.Abort() } else { c.AbortWithStatus(http.StatusInternalServerError) diff --git a/render/json.go b/render/json.go index 32d0fc42..c7cf330e 100644 --- a/render/json.go +++ b/render/json.go @@ -67,8 +67,8 @@ func WriteJSON(w http.ResponseWriter, obj interface{}) error { if err != nil { return err } - w.Write(jsonBytes) - return nil + _, err = w.Write(jsonBytes) + return err } // Render (IndentedJSON) marshals the given interface object and writes it with custom ContentType. @@ -78,8 +78,8 @@ func (r IndentedJSON) Render(w http.ResponseWriter) error { if err != nil { return err } - w.Write(jsonBytes) - return nil + _, err = w.Write(jsonBytes) + return err } // WriteContentType (IndentedJSON) writes JSON ContentType. @@ -96,10 +96,13 @@ func (r SecureJSON) Render(w http.ResponseWriter) error { } // if the jsonBytes is array values if bytes.HasPrefix(jsonBytes, []byte("[")) && bytes.HasSuffix(jsonBytes, []byte("]")) { - w.Write([]byte(r.Prefix)) + _, err = w.Write([]byte(r.Prefix)) + if err != nil { + return err + } } - w.Write(jsonBytes) - return nil + _, err = w.Write(jsonBytes) + return err } // WriteContentType (SecureJSON) writes JSON ContentType. @@ -116,15 +119,27 @@ func (r JsonpJSON) Render(w http.ResponseWriter) (err error) { } if r.Callback == "" { - w.Write(ret) - return nil + _, err = w.Write(ret) + return err } callback := template.JSEscapeString(r.Callback) - w.Write([]byte(callback)) - w.Write([]byte("(")) - w.Write(ret) - w.Write([]byte(")")) + _, err = w.Write([]byte(callback)) + if err != nil { + return err + } + _, err = w.Write([]byte("(")) + if err != nil { + return err + } + _, err = w.Write(ret) + if err != nil { + return err + } + _, err = w.Write([]byte(")")) + if err != nil { + return err + } return nil } @@ -151,8 +166,8 @@ func (r AsciiJSON) Render(w http.ResponseWriter) (err error) { buffer.WriteString(cvt) } - w.Write(buffer.Bytes()) - return nil + _, err = w.Write(buffer.Bytes()) + return err } // WriteContentType (AsciiJSON) writes JSON ContentType. diff --git a/render/protobuf.go b/render/protobuf.go index 47895072..15aca995 100644 --- a/render/protobuf.go +++ b/render/protobuf.go @@ -26,8 +26,8 @@ func (r ProtoBuf) Render(w http.ResponseWriter) error { return err } - w.Write(bytes) - return nil + _, err = w.Write(bytes) + return err } // WriteContentType (ProtoBuf) writes ProtoBuf ContentType. diff --git a/render/render_test.go b/render/render_test.go index 4c9b180d..3df04a17 100644 --- a/render/render_test.go +++ b/render/render_test.go @@ -71,7 +71,7 @@ func TestRenderJSONPanics(t *testing.T) { data := make(chan int) // json: unsupported type: chan int - assert.Panics(t, func() { (JSON{data}).Render(w) }) + assert.Panics(t, func() { assert.NoError(t, (JSON{data}).Render(w)) }) } func TestRenderIndentedJSON(t *testing.T) { @@ -335,7 +335,7 @@ func TestRenderRedirect(t *testing.T) { } w = httptest.NewRecorder() - assert.Panics(t, func() { data2.Render(w) }) + assert.Panics(t, func() { assert.NoError(t, data2.Render(w)) }) // only improve coverage data2.WriteContentType(w) diff --git a/render/text.go b/render/text.go index 2ea7343c..4e52d4c5 100644 --- a/render/text.go +++ b/render/text.go @@ -20,8 +20,7 @@ var plainContentType = []string{"text/plain; charset=utf-8"} // Render (String) writes data with custom ContentType. func (r String) Render(w http.ResponseWriter) error { - WriteString(w, r.Format, r.Data) - return nil + return WriteString(w, r.Format, r.Data) } // WriteContentType (String) writes Plain ContentType. @@ -30,11 +29,12 @@ func (r String) WriteContentType(w http.ResponseWriter) { } // WriteString writes data according to its format and write custom ContentType. -func WriteString(w http.ResponseWriter, format string, data []interface{}) { +func WriteString(w http.ResponseWriter, format string, data []interface{}) (err error) { writeContentType(w, plainContentType) if len(data) > 0 { - fmt.Fprintf(w, format, data...) + _, err = fmt.Fprintf(w, format, data...) return } - io.WriteString(w, format) + _, err = io.WriteString(w, format) + return } diff --git a/render/yaml.go b/render/yaml.go index 33bc3254..0df78360 100644 --- a/render/yaml.go +++ b/render/yaml.go @@ -26,8 +26,8 @@ func (r YAML) Render(w http.ResponseWriter) error { return err } - w.Write(bytes) - return nil + _, err = w.Write(bytes) + return err } // WriteContentType (YAML) writes YAML ContentType for response. diff --git a/response_writer_test.go b/response_writer_test.go index cc5a89dc..a5e111e5 100644 --- a/response_writer_test.go +++ b/response_writer_test.go @@ -103,7 +103,8 @@ func TestResponseWriterHijack(t *testing.T) { w := ResponseWriter(writer) assert.Panics(t, func() { - w.Hijack() + _, _, err := w.Hijack() + assert.NoError(t, err) }) assert.True(t, w.Written()) diff --git a/routes_test.go b/routes_test.go index c4d59725..af4caf7f 100644 --- a/routes_test.go +++ b/routes_test.go @@ -251,7 +251,8 @@ func TestRouteStaticFile(t *testing.T) { t.Error(err) } defer os.Remove(f.Name()) - f.WriteString("Gin Web Framework") + _, err = f.WriteString("Gin Web Framework") + assert.NoError(t, err) f.Close() dir, filename := filepath.Split(f.Name()) From 4867ff9634d1889156587d5add70d2b29c447542 Mon Sep 17 00:00:00 2001 From: Dmitry Kutakov Date: Fri, 18 Jan 2019 04:57:06 +0300 Subject: [PATCH 44/46] fix Context.Next() - recheck len of handlers every iteration (#1745) * fix Context.Next() - recheck len of handlers every iteration * add tests when Context.reset() can be called inside of handler TestEngineHandleContext TestContextResetInHandler TestRouterStaticFSFileNotFound * Context.Next() - format to while style --- context.go | 3 ++- context_test.go | 12 ++++++++++++ gin_test.go | 17 +++++++++++++++++ routes_test.go | 10 ++++++++++ 4 files changed, 41 insertions(+), 1 deletion(-) diff --git a/context.go b/context.go index 4dc94ea9..26badfc3 100644 --- a/context.go +++ b/context.go @@ -105,8 +105,9 @@ func (c *Context) Handler() HandlerFunc { // See example in GitHub. func (c *Context) Next() { c.index++ - for s := int8(len(c.handlers)); c.index < s; c.index++ { + for c.index < int8(len(c.handlers)) { c.handlers[c.index](c) + c.index++ } } diff --git a/context_test.go b/context_test.go index 29ec1a24..ea936b85 100644 --- a/context_test.go +++ b/context_test.go @@ -1743,3 +1743,15 @@ func TestContextStreamWithClientGone(t *testing.T) { assert.Equal(t, "test", w.Body.String()) } + +func TestContextResetInHandler(t *testing.T) { + w := CreateTestResponseRecorder() + c, _ := CreateTestContext(w) + + c.handlers = []HandlerFunc{ + func(c *Context) { c.reset() }, + } + assert.NotPanics(t, func() { + c.Next() + }) +} diff --git a/gin_test.go b/gin_test.go index 353c9be1..e013df09 100644 --- a/gin_test.go +++ b/gin_test.go @@ -471,6 +471,23 @@ func TestListOfRoutes(t *testing.T) { }) } +func TestEngineHandleContext(t *testing.T) { + r := New() + r.GET("/", func(c *Context) { + c.Request.URL.Path = "/v2" + r.HandleContext(c) + }) + v2 := r.Group("/v2") + { + v2.GET("/", func(c *Context) {}) + } + + assert.NotPanics(t, func() { + w := performRequest(r, "GET", "/") + assert.Equal(t, 301, w.Code) + }) +} + func assertRoutePresent(t *testing.T, gotRoutes RoutesInfo, wantRoute RouteInfo) { for _, gotRoute := range gotRoutes { if gotRoute.Path == wantRoute.Path && gotRoute.Method == wantRoute.Method { diff --git a/routes_test.go b/routes_test.go index af4caf7f..8d50292d 100644 --- a/routes_test.go +++ b/routes_test.go @@ -427,6 +427,16 @@ func TestRouterStaticFSNotFound(t *testing.T) { assert.Equal(t, "non existent", w.Body.String()) } +func TestRouterStaticFSFileNotFound(t *testing.T) { + router := New() + + router.StaticFS("/", http.FileSystem(http.Dir("."))) + + assert.NotPanics(t, func() { + performRequest(router, "GET", "/nonexistent") + }) +} + func TestRouteRawPath(t *testing.T) { route := New() route.UseRawPath = true From b4f51559825a055dcec93fa282c20018d073aaaa Mon Sep 17 00:00:00 2001 From: Sai Date: Sun, 20 Jan 2019 09:39:09 +0900 Subject: [PATCH 45/46] Fix not to pass formatted string to Fprintf's format specifier parameter (#1747) --- logger.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/logger.go b/logger.go index a55d26e0..d2869f51 100644 --- a/logger.go +++ b/logger.go @@ -191,7 +191,7 @@ func LoggerWithConfig(conf LoggerConfig) HandlerFunc { param.Path = path - fmt.Fprintf(out, formatter(param)) + fmt.Fprint(out, formatter(param)) } } } From f38a3fe65f102dd765e097166e6a41f9e99777f6 Mon Sep 17 00:00:00 2001 From: Ryan <46182144+ryanker@users.noreply.github.com> Date: Sun, 20 Jan 2019 18:27:04 +0800 Subject: [PATCH 46/46] fix password error (#1728) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2dc9e5ff..0dcaa8e9 100644 --- a/README.md +++ b/README.md @@ -620,7 +620,7 @@ func main() { // // // user - // 123 + // 123 // ) router.POST("/loginXML", func(c *gin.Context) { var xml Login