From 8e9619767c12607afcb086a5e9c791eda2b9e116 Mon Sep 17 00:00:00 2001 From: forging2012 Date: Wed, 31 Oct 2018 20:19:58 +0800 Subject: [PATCH 01/42] 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 1b5fb49353..ce4133257e 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/42] 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 477b9d5827..ab13ca6192 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 7cb0143a43..6a69632972 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/42] 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 ce4133257e..8c2c2c5b75 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 27dac3879d..5fb2b334de 100644 --- a/examples/realtime-advanced/resources/room_login.templ.html +++ b/examples/realtime-advanced/resources/room_login.templ.html @@ -79,19 +79,19 @@

Server-Sent Events in Go

{{.nick}}
- +
- + {{else}}
Join the SSE real-time chat
- +
- +
{{end}} diff --git a/examples/realtime-chat/template.go b/examples/realtime-chat/template.go index b9024de6da..4190251d13 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/42] 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 5fb2b334de..d4991d8d1b 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/42] =?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 d411f9ebd2..c689ba2809 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/42] =?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 c689ba2809..7867c1f78d 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/42] add krakend to gin user list (#1658) --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 3f0c9170be..0f74d30a5f 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/42] 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 0f74d30a5f..e7b92b2deb 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/42] 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 7867c1f78d..8c9f8abb22 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/42] 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 8c9f8abb22..0000000000 --- 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/42] 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 9cb0b989df..5615a50cd6 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 60f1c81b6f..c4d59725a9 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/42] 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 3ab8084cc6..0000000000 --- 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/42] 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 5b31176437..c0204d7f43 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 c9fb29ce55..e886eaac5c 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/42] fix bug (#1682) --- routergroup.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/routergroup.go b/routergroup.go index 5615a50cd6..2b41dfdaac 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/42] ci: break when test fail (#1671) --- .gitignore | 2 ++ Makefile | 7 ++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 14dc8f20da..bdd50c95cf 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 b698ac09e1..b0d2e24a5e 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/42] 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 e7b92b2deb..c1f902a9f6 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 74dd2e6fa6..a64af69710 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 6118cb0499..909ddd3908 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/42] 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 478e8c0944..c94926e1c6 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 6b56a2b7a9..5253425aac 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/42] 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 c94926e1c6..c38b2b876f 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 8e9f75ad7b..024dcb70dd 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 5b9ebcdc3b..f2a2f18419 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 0000000000..2008d3c673 --- /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/42] 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 c1f902a9f6..2dc9e5ff13 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 a55325ed5d..2b9d6d9142 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 5d438651a4..ba289f5477 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/42] 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 a64af69710..b9c63c7323 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 909ddd3908..350599d47f 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/42] 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 b9c63c7323..a55d26e033 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/42] 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 9f96406ac2..7113e71ece 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/42] 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 b0d2e24a5e..7211144a36 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/42] 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 c0204d7f43..740749be7e 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 d893c21c1b..8900ab70cd 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/42] 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 e14a688cd3..01d5cf5ed2 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/42] 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 dced73fd8a..836b3ecb8d 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/42] Fix case of GitHub (#1726) --- routergroup.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/routergroup.go b/routergroup.go index 2b41dfdaac..297d3574e9 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/42] 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 c38b2b876f..c94926e1c6 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 024dcb70dd..8e9f75ad7b 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 f2a2f18419..5b9ebcdc3b 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 2008d3c673..0000000000 --- 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 -}