From 30f014c7540d42fdc4035587621dbc2b3084bd19 Mon Sep 17 00:00:00 2001 From: Danieliu Date: Thu, 26 May 2016 16:21:50 +0800 Subject: [PATCH 01/95] fix default log format `reset` field should be after `method` in LoggerWithWriter function. --- logger.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/logger.go b/logger.go index d56bc62..ff246a4 100644 --- a/logger.go +++ b/logger.go @@ -80,7 +80,7 @@ func LoggerWithWriter(out io.Writer, notlogged ...string) HandlerFunc { statusColor, statusCode, reset, latency, clientIP, - methodColor, reset, method, + methodColor, method, reset, path, comment, ) From 8464b14974fe89a347febccfa37ef013f2c3c75e Mon Sep 17 00:00:00 2001 From: Tatsuya Hoshino Date: Wed, 8 Jun 2016 07:37:09 +0900 Subject: [PATCH 02/95] Remove an obsolete func in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8023dc5..d6fddf4 100644 --- a/README.md +++ b/README.md @@ -468,7 +468,7 @@ func main() { ####HTML rendering -Using LoadHTMLTemplates() +Using LoadHTMLGlob() or LoadHTMLFiles() ```go func main() { From bf8da4a08a99059f2e06da811be9cb08f0cf0a79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maxime=20Soul=C3=A9?= Date: Fri, 9 Sep 2016 11:37:22 +0200 Subject: [PATCH 03/95] Context.Get() does not need to test whether Keys is nil or not --- context.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/context.go b/context.go index 5d3b6a4..5016175 100644 --- a/context.go +++ b/context.go @@ -166,9 +166,7 @@ func (c *Context) Set(key string, value interface{}) { // Get returns the value for the given key, ie: (value, true). // If the value does not exists it returns (nil, false) func (c *Context) Get(key string) (value interface{}, exists bool) { - if c.Keys != nil { - value, exists = c.Keys[key] - } + value, exists = c.Keys[key] return } From 61ba9db5af23da0f11d5f68bced9ccc5a417859b Mon Sep 17 00:00:00 2001 From: Yehezkiel Syamsuhadi Date: Wed, 21 Sep 2016 12:16:51 +1000 Subject: [PATCH 04/95] Make CreateTestContext public without importing net/http/httptest --- context_test.go | 115 +++++++++++++++++++++++++++++++----------------- helpers_test.go | 14 ------ test_helpers.go | 13 ++++++ 3 files changed, 88 insertions(+), 54 deletions(-) delete mode 100644 helpers_test.go create mode 100644 test_helpers.go diff --git a/context_test.go b/context_test.go index 97d4957..c7a1fc8 100644 --- a/context_test.go +++ b/context_test.go @@ -74,7 +74,7 @@ func TestContextReset(t *testing.T) { } func TestContextHandlers(t *testing.T) { - c, _, _ := CreateTestContext() + c, _ := CreateTestContext(httptest.NewRecorder()) assert.Nil(t, c.handlers) assert.Nil(t, c.handlers.Last()) @@ -95,7 +95,7 @@ func TestContextHandlers(t *testing.T) { // TestContextSetGet tests that a parameter is set correctly on the // current context and can be retrieved using Get. func TestContextSetGet(t *testing.T) { - c, _, _ := CreateTestContext() + c, _ := CreateTestContext(httptest.NewRecorder()) c.Set("foo", "bar") value, err := c.Get("foo") @@ -111,7 +111,7 @@ func TestContextSetGet(t *testing.T) { } func TestContextSetGetValues(t *testing.T) { - c, _, _ := CreateTestContext() + c, _ := CreateTestContext(httptest.NewRecorder()) c.Set("string", "this is a string") c.Set("int32", int32(-42)) c.Set("int64", int64(42424242424242)) @@ -132,7 +132,7 @@ func TestContextSetGetValues(t *testing.T) { } func TestContextCopy(t *testing.T) { - c, _, _ := CreateTestContext() + c, _ := CreateTestContext(httptest.NewRecorder()) c.index = 2 c.Request, _ = http.NewRequest("POST", "/hola", nil) c.handlers = HandlersChain{func(c *Context) {}} @@ -151,7 +151,7 @@ func TestContextCopy(t *testing.T) { } func TestContextHandlerName(t *testing.T) { - c, _, _ := CreateTestContext() + c, _ := CreateTestContext(httptest.NewRecorder()) c.handlers = HandlersChain{func(c *Context) {}, handlerNameTest} assert.Regexp(t, "^(.*/vendor/)?github.com/gin-gonic/gin.handlerNameTest$", c.HandlerName()) @@ -162,7 +162,7 @@ func handlerNameTest(c *Context) { } func TestContextQuery(t *testing.T) { - c, _, _ := CreateTestContext() + c, _ := CreateTestContext(httptest.NewRecorder()) c.Request, _ = http.NewRequest("GET", "http://example.com/?foo=bar&page=10&id=", nil) value, ok := c.GetQuery("foo") @@ -197,7 +197,7 @@ func TestContextQuery(t *testing.T) { } func TestContextQueryAndPostForm(t *testing.T) { - c, _, _ := CreateTestContext() + c, _ := CreateTestContext(httptest.NewRecorder()) body := bytes.NewBufferString("foo=bar&page=11&both=&foo=second") c.Request, _ = http.NewRequest("POST", "/?both=GET&id=main&id=omit&array[]=first&array[]=second", body) c.Request.Header.Add("Content-Type", MIMEPOSTForm) @@ -254,7 +254,7 @@ func TestContextQueryAndPostForm(t *testing.T) { } func TestContextPostFormMultipart(t *testing.T) { - c, _, _ := CreateTestContext() + c, _ := CreateTestContext(httptest.NewRecorder()) c.Request = createMultipartRequest() var obj struct { @@ -302,13 +302,13 @@ func TestContextPostFormMultipart(t *testing.T) { } func TestContextSetCookie(t *testing.T) { - c, _, _ := CreateTestContext() + c, _ := CreateTestContext(httptest.NewRecorder()) c.SetCookie("user", "gin", 1, "/", "localhost", true, true) assert.Equal(t, c.Writer.Header().Get("Set-Cookie"), "user=gin; Path=/; Domain=localhost; Max-Age=1; HttpOnly; Secure") } func TestContextGetCookie(t *testing.T) { - c, _, _ := CreateTestContext() + c, _ := CreateTestContext(httptest.NewRecorder()) c.Request, _ = http.NewRequest("GET", "/get", nil) c.Request.Header.Set("Cookie", "user=gin") cookie, _ := c.Cookie("user") @@ -318,7 +318,9 @@ func TestContextGetCookie(t *testing.T) { // Tests that the response is serialized as JSON // and Content-Type is set to application/json func TestContextRenderJSON(t *testing.T) { - c, w, _ := CreateTestContext() + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + c.JSON(201, H{"foo": "bar"}) assert.Equal(t, w.Code, 201) @@ -329,7 +331,9 @@ func TestContextRenderJSON(t *testing.T) { // Tests that the response is serialized as JSON // we change the content-type before func TestContextRenderAPIJSON(t *testing.T) { - c, w, _ := CreateTestContext() + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + c.Header("Content-Type", "application/vnd.api+json") c.JSON(201, H{"foo": "bar"}) @@ -341,7 +345,9 @@ func TestContextRenderAPIJSON(t *testing.T) { // Tests that the response is serialized as JSON // and Content-Type is set to application/json func TestContextRenderIndentedJSON(t *testing.T) { - c, w, _ := CreateTestContext() + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + c.IndentedJSON(201, H{"foo": "bar", "bar": "foo", "nested": H{"foo": "bar"}}) assert.Equal(t, w.Code, 201) @@ -352,7 +358,8 @@ func TestContextRenderIndentedJSON(t *testing.T) { // Tests that the response executes the templates // and responds with Content-Type set to text/html func TestContextRenderHTML(t *testing.T) { - c, w, router := CreateTestContext() + w := httptest.NewRecorder() + c, router := CreateTestContext(w) templ := template.Must(template.New("t").Parse(`Hello {{.name}}`)) router.SetHTMLTemplate(templ) @@ -366,7 +373,9 @@ func TestContextRenderHTML(t *testing.T) { // TestContextXML tests that the response is serialized as XML // and Content-Type is set to application/xml func TestContextRenderXML(t *testing.T) { - c, w, _ := CreateTestContext() + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + c.XML(201, H{"foo": "bar"}) assert.Equal(t, w.Code, 201) @@ -377,7 +386,9 @@ func TestContextRenderXML(t *testing.T) { // TestContextString tests that the response is returned // with Content-Type set to text/plain func TestContextRenderString(t *testing.T) { - c, w, _ := CreateTestContext() + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + c.String(201, "test %s %d", "string", 2) assert.Equal(t, w.Code, 201) @@ -388,7 +399,9 @@ func TestContextRenderString(t *testing.T) { // TestContextString tests that the response is returned // with Content-Type set to text/html func TestContextRenderHTMLString(t *testing.T) { - c, w, _ := CreateTestContext() + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + c.Header("Content-Type", "text/html; charset=utf-8") c.String(201, "%s %d", "string", 3) @@ -400,7 +413,9 @@ func TestContextRenderHTMLString(t *testing.T) { // TestContextData tests that the response can be written from `bytesting` // with specified MIME type func TestContextRenderData(t *testing.T) { - c, w, _ := CreateTestContext() + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + c.Data(201, "text/csv", []byte(`foo,bar`)) assert.Equal(t, w.Code, 201) @@ -409,7 +424,9 @@ func TestContextRenderData(t *testing.T) { } func TestContextRenderSSE(t *testing.T) { - c, w, _ := CreateTestContext() + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + c.SSEvent("float", 1.5) c.Render(-1, sse.Event{ Id: "123", @@ -424,7 +441,9 @@ func TestContextRenderSSE(t *testing.T) { } func TestContextRenderFile(t *testing.T) { - c, w, _ := CreateTestContext() + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + c.Request, _ = http.NewRequest("GET", "/", nil) c.File("./gin.go") @@ -436,7 +455,9 @@ func TestContextRenderFile(t *testing.T) { // TestContextRenderYAML tests that the response is serialized as YAML // and Content-Type is set to application/x-yaml func TestContextRenderYAML(t *testing.T) { - c, w, _ := CreateTestContext() + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + c.YAML(201, H{"foo": "bar"}) assert.Equal(t, w.Code, 201) @@ -445,7 +466,7 @@ func TestContextRenderYAML(t *testing.T) { } func TestContextHeaders(t *testing.T) { - c, _, _ := CreateTestContext() + c, _ := CreateTestContext(httptest.NewRecorder()) c.Header("Content-Type", "text/plain") c.Header("X-Custom", "value") @@ -462,7 +483,9 @@ func TestContextHeaders(t *testing.T) { // TODO func TestContextRenderRedirectWithRelativePath(t *testing.T) { - c, w, _ := CreateTestContext() + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + c.Request, _ = http.NewRequest("POST", "http://example.com", nil) assert.Panics(t, func() { c.Redirect(299, "/new_path") }) assert.Panics(t, func() { c.Redirect(309, "/new_path") }) @@ -474,7 +497,9 @@ func TestContextRenderRedirectWithRelativePath(t *testing.T) { } func TestContextRenderRedirectWithAbsolutePath(t *testing.T) { - c, w, _ := CreateTestContext() + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + c.Request, _ = http.NewRequest("POST", "http://example.com", nil) c.Redirect(302, "http://google.com") c.Writer.WriteHeaderNow() @@ -484,7 +509,9 @@ func TestContextRenderRedirectWithAbsolutePath(t *testing.T) { } func TestContextRenderRedirectWith201(t *testing.T) { - c, w, _ := CreateTestContext() + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + c.Request, _ = http.NewRequest("POST", "http://example.com", nil) c.Redirect(201, "/resource") c.Writer.WriteHeaderNow() @@ -494,7 +521,7 @@ func TestContextRenderRedirectWith201(t *testing.T) { } func TestContextRenderRedirectAll(t *testing.T) { - c, _, _ := CreateTestContext() + c, _ := CreateTestContext(httptest.NewRecorder()) c.Request, _ = http.NewRequest("POST", "http://example.com", nil) assert.Panics(t, func() { c.Redirect(200, "/resource") }) assert.Panics(t, func() { c.Redirect(202, "/resource") }) @@ -505,7 +532,7 @@ func TestContextRenderRedirectAll(t *testing.T) { } func TestContextNegotiationFormat(t *testing.T) { - c, _, _ := CreateTestContext() + c, _ := CreateTestContext(httptest.NewRecorder()) c.Request, _ = http.NewRequest("POST", "", nil) assert.Panics(t, func() { c.NegotiateFormat() }) @@ -514,7 +541,7 @@ func TestContextNegotiationFormat(t *testing.T) { } func TestContextNegotiationFormatWithAccept(t *testing.T) { - c, _, _ := CreateTestContext() + c, _ := CreateTestContext(httptest.NewRecorder()) c.Request, _ = http.NewRequest("POST", "/", nil) c.Request.Header.Add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8") @@ -524,7 +551,7 @@ func TestContextNegotiationFormatWithAccept(t *testing.T) { } func TestContextNegotiationFormatCustum(t *testing.T) { - c, _, _ := CreateTestContext() + c, _ := CreateTestContext(httptest.NewRecorder()) c.Request, _ = http.NewRequest("POST", "/", nil) c.Request.Header.Add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8") @@ -537,7 +564,7 @@ func TestContextNegotiationFormatCustum(t *testing.T) { } func TestContextIsAborted(t *testing.T) { - c, _, _ := CreateTestContext() + c, _ := CreateTestContext(httptest.NewRecorder()) assert.False(t, c.IsAborted()) c.Abort() @@ -553,7 +580,9 @@ func TestContextIsAborted(t *testing.T) { // TestContextData tests that the response can be written from `bytesting` // with specified MIME type func TestContextAbortWithStatus(t *testing.T) { - c, w, _ := CreateTestContext() + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + c.index = 4 c.AbortWithStatus(401) @@ -564,7 +593,7 @@ func TestContextAbortWithStatus(t *testing.T) { } func TestContextError(t *testing.T) { - c, _, _ := CreateTestContext() + c, _ := CreateTestContext(httptest.NewRecorder()) assert.Empty(t, c.Errors) c.Error(errors.New("first error")) @@ -590,7 +619,7 @@ func TestContextError(t *testing.T) { } func TestContextTypedError(t *testing.T) { - c, _, _ := CreateTestContext() + c, _ := CreateTestContext(httptest.NewRecorder()) c.Error(errors.New("externo 0")).SetType(ErrorTypePublic) c.Error(errors.New("interno 0")).SetType(ErrorTypePrivate) @@ -604,7 +633,9 @@ func TestContextTypedError(t *testing.T) { } func TestContextAbortWithError(t *testing.T) { - c, w, _ := CreateTestContext() + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + c.AbortWithError(401, errors.New("bad input")).SetMeta("some input") assert.Equal(t, w.Code, 401) @@ -613,7 +644,7 @@ func TestContextAbortWithError(t *testing.T) { } func TestContextClientIP(t *testing.T) { - c, _, _ := CreateTestContext() + c, _ := CreateTestContext(httptest.NewRecorder()) c.Request, _ = http.NewRequest("POST", "/", nil) c.Request.Header.Set("X-Real-IP", " 10.10.10.10 ") @@ -633,7 +664,7 @@ func TestContextClientIP(t *testing.T) { } func TestContextContentType(t *testing.T) { - c, _, _ := CreateTestContext() + c, _ := CreateTestContext(httptest.NewRecorder()) c.Request, _ = http.NewRequest("POST", "/", nil) c.Request.Header.Set("Content-Type", "application/json; charset=utf-8") @@ -641,7 +672,7 @@ func TestContextContentType(t *testing.T) { } func TestContextAutoBindJSON(t *testing.T) { - c, _, _ := CreateTestContext() + c, _ := CreateTestContext(httptest.NewRecorder()) c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString("{\"foo\":\"bar\", \"bar\":\"foo\"}")) c.Request.Header.Add("Content-Type", MIMEJSON) @@ -656,7 +687,9 @@ func TestContextAutoBindJSON(t *testing.T) { } func TestContextBindWithJSON(t *testing.T) { - c, w, _ := CreateTestContext() + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString("{\"foo\":\"bar\", \"bar\":\"foo\"}")) c.Request.Header.Add("Content-Type", MIMEXML) // set fake content-type @@ -671,7 +704,9 @@ func TestContextBindWithJSON(t *testing.T) { } func TestContextBadAutoBind(t *testing.T) { - c, w, _ := CreateTestContext() + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + c.Request, _ = http.NewRequest("POST", "http://example.com", bytes.NewBufferString("\"foo\":\"bar\", \"bar\":\"foo\"}")) c.Request.Header.Add("Content-Type", MIMEJSON) var obj struct { @@ -690,7 +725,7 @@ func TestContextBadAutoBind(t *testing.T) { } func TestContextGolangContext(t *testing.T) { - c, _, _ := CreateTestContext() + c, _ := CreateTestContext(httptest.NewRecorder()) c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString("{\"foo\":\"bar\", \"bar\":\"foo\"}")) assert.NoError(t, c.Err()) assert.Nil(t, c.Done()) diff --git a/helpers_test.go b/helpers_test.go deleted file mode 100644 index 7d8020c..0000000 --- a/helpers_test.go +++ /dev/null @@ -1,14 +0,0 @@ -package gin - -import ( - "net/http/httptest" -) - -func CreateTestContext() (c *Context, w *httptest.ResponseRecorder, r *Engine) { - w = httptest.NewRecorder() - r = New() - c = r.allocateContext() - c.reset() - c.writermem.reset(w) - return -} diff --git a/test_helpers.go b/test_helpers.go new file mode 100644 index 0000000..5bb3fa7 --- /dev/null +++ b/test_helpers.go @@ -0,0 +1,13 @@ +package gin + +import ( + "net/http" +) + +func CreateTestContext(w http.ResponseWriter) (c *Context, r *Engine) { + r = New() + c = r.allocateContext() + c.reset() + c.writermem.reset(w) + return +} From ceb250ba20bea1721fa087b3db800926355287c8 Mon Sep 17 00:00:00 2001 From: Vyacheslav Dubinin Date: Wed, 19 Oct 2016 17:13:38 +0300 Subject: [PATCH 05/95] Move golang.org/x/net/context.Context interface implementation check to tests --- context.go | 3 --- context_test.go | 3 +++ 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/context.go b/context.go index 5d3b6a4..d0fa7cb 100644 --- a/context.go +++ b/context.go @@ -17,7 +17,6 @@ import ( "github.com/gin-gonic/gin/binding" "github.com/gin-gonic/gin/render" "github.com/manucorporat/sse" - "golang.org/x/net/context" ) // Content-Type MIME of the most common data formats @@ -50,8 +49,6 @@ type Context struct { Accepted []string } -var _ context.Context = &Context{} - /************************************/ /********** CONTEXT CREATION ********/ /************************************/ diff --git a/context_test.go b/context_test.go index 97d4957..a1f9fa3 100644 --- a/context_test.go +++ b/context_test.go @@ -17,8 +17,11 @@ import ( "github.com/manucorporat/sse" "github.com/stretchr/testify/assert" + "golang.org/x/net/context" ) +var _ context.Context = &Context{} + // Unit tests TODO // func (c *Context) File(filepath string) { // func (c *Context) Negotiate(code int, config Negotiate) { From c8b35d34452e9c1fc34d0aec480d6fa7e3092584 Mon Sep 17 00:00:00 2001 From: Tevin Jeffrey Date: Sun, 29 May 2016 16:08:24 -0400 Subject: [PATCH 06/95] Fix for #630 Details can be found in issue #630 --- errors.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/errors.go b/errors.go index 7716bfa..694b428 100644 --- a/errors.go +++ b/errors.go @@ -72,7 +72,7 @@ func (msg *Error) MarshalJSON() ([]byte, error) { } // Implements the error interface -func (msg *Error) Error() string { +func (msg Error) Error() string { return msg.Err.Error() } From 7e58c80a7c8e299183d947cd78affb8f60fbb5de Mon Sep 17 00:00:00 2001 From: Javier Provecho Fernandez Date: Mon, 5 Dec 2016 11:21:59 +0100 Subject: [PATCH 07/95] Fix #723 --- context.go | 8 ++++++++ context_appengine.go | 7 +++++++ context_test.go | 9 +++++++++ gin.go | 6 ++++++ 4 files changed, 30 insertions(+) create mode 100644 context_appengine.go diff --git a/context.go b/context.go index df001a4..ffda5ab 100644 --- a/context.go +++ b/context.go @@ -353,9 +353,17 @@ func (c *Context) ClientIP() string { return clientIP } } + + if c.engine.AppEngine { + if addr := c.Request.Header.Get("X-Appengine-Remote-Addr"); addr != "" { + return addr + } + } + if ip, _, err := net.SplitHostPort(strings.TrimSpace(c.Request.RemoteAddr)); err == nil { return ip } + return "" } diff --git a/context_appengine.go b/context_appengine.go new file mode 100644 index 0000000..d9cb22f --- /dev/null +++ b/context_appengine.go @@ -0,0 +1,7 @@ +// +build appengine + +package gin + +func init() { + defaultAppEngine = true +} diff --git a/context_test.go b/context_test.go index 01ee6b8..d46be71 100644 --- a/context_test.go +++ b/context_test.go @@ -650,6 +650,7 @@ func TestContextClientIP(t *testing.T) { c.Request.Header.Set("X-Real-IP", " 10.10.10.10 ") c.Request.Header.Set("X-Forwarded-For", " 20.20.20.20, 30.30.30.30") + c.Request.Header.Set("X-Appengine-Remote-Addr", "50.50.50.50") c.Request.RemoteAddr = " 40.40.40.40:42123 " assert.Equal(t, c.ClientIP(), "10.10.10.10") @@ -661,7 +662,15 @@ func TestContextClientIP(t *testing.T) { assert.Equal(t, c.ClientIP(), "30.30.30.30") c.Request.Header.Del("X-Forwarded-For") + c.engine.AppEngine = true + assert.Equal(t, c.ClientIP(), "50.50.50.50") + + c.Request.Header.Del("X-Appengine-Remote-Addr") assert.Equal(t, c.ClientIP(), "40.40.40.40") + + // no port + c.Request.RemoteAddr = "50.50.50.50" + assert.Equal(t, c.ClientIP(), "") } func TestContextContentType(t *testing.T) { diff --git a/gin.go b/gin.go index 60f4ab2..70cc11b 100644 --- a/gin.go +++ b/gin.go @@ -19,6 +19,7 @@ const Version = "v1.0rc2" var default404Body = []byte("404 page not found") var default405Body = []byte("405 method not allowed") +var defaultAppEngine bool type HandlerFunc func(*Context) type HandlersChain []HandlerFunc @@ -78,6 +79,10 @@ type ( // handler. HandleMethodNotAllowed bool ForwardedByClientIP bool + + // #726 #755 If enabled, it will thrust some headers starting with + // 'X-AppEngine...' for better integration with that PaaS. + AppEngine bool } ) @@ -101,6 +106,7 @@ func New() *Engine { RedirectFixedPath: false, HandleMethodNotAllowed: false, ForwardedByClientIP: true, + AppEngine: defaultAppEngine, trees: make(methodTrees, 0, 9), } engine.RouterGroup.engine = engine From 98af44604c755936c31d990f7135b34e1433095f Mon Sep 17 00:00:00 2001 From: Javier Provecho Fernandez Date: Tue, 6 Dec 2016 14:38:14 +0100 Subject: [PATCH 08/95] Update const framework version to 1.1.4 --- gin.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gin.go b/gin.go index 60f4ab2..75adc47 100644 --- a/gin.go +++ b/gin.go @@ -15,7 +15,7 @@ import ( ) // Version is Framework's version -const Version = "v1.0rc2" +const Version = "v1.1.4" var default404Body = []byte("404 page not found") var default405Body = []byte("405 method not allowed") From 2033b73551ffcba0002bfec84cfefc4b53b144fe Mon Sep 17 00:00:00 2001 From: Javier Provecho Fernandez Date: Tue, 6 Dec 2016 19:53:58 +0100 Subject: [PATCH 09/95] Migrate from godeps to govendor --- Godeps/Godeps.json | 36 ---------------------------------- vendor/vendor.json | 49 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 36 deletions(-) delete mode 100644 Godeps/Godeps.json create mode 100644 vendor/vendor.json diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json deleted file mode 100644 index a9c828a..0000000 --- a/Godeps/Godeps.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "ImportPath": "github.com/gin-gonic/gin", - "GoVersion": "go1.5.1", - "Deps": [ - { - "ImportPath": "github.com/davecgh/go-spew/spew", - "Rev": "5215b55f46b2b919f50a1df0eaa5886afe4e3b3d" - }, - { - "ImportPath": "github.com/golang/protobuf/proto", - "Rev": "2402d76f3d41f928c7902a765dfc872356dd3aad" - }, - { - "ImportPath": "github.com/manucorporat/sse", - "Rev": "ee05b128a739a0fb76c7ebd3ae4810c1de808d6d" - }, - { - "ImportPath": "github.com/pmezard/go-difflib/difflib", - "Rev": "792786c7400a136282c1664665ae0a8db921c6c2" - }, - { - "ImportPath": "github.com/stretchr/testify/assert", - "Comment": "v1.1.3", - "Rev": "f390dcf405f7b83c997eac1b06768bb9f44dec18" - }, - { - "ImportPath": "golang.org/x/net/context", - "Rev": "f315505cf3349909cdf013ea56690da34e96a451" - }, - { - "ImportPath": "gopkg.in/go-playground/validator.v8", - "Comment": "v8.15.1", - "Rev": "c193cecd124b5cc722d7ee5538e945bdb3348435" - } - ] -} diff --git a/vendor/vendor.json b/vendor/vendor.json new file mode 100644 index 0000000..3f60f40 --- /dev/null +++ b/vendor/vendor.json @@ -0,0 +1,49 @@ +{ + "comment": "", + "ignore": "test", + "package": [ + { + "path": "github.com/davecgh/go-spew/spew", + "revision": "5215b55f46b2b919f50a1df0eaa5886afe4e3b3d" + }, + { + "path": "github.com/golang/protobuf/proto", + "revision": "2402d76f3d41f928c7902a765dfc872356dd3aad" + }, + { + "path": "github.com/golang/protobuf/proto/proto3_proto", + "revision": "2402d76f3d41f928c7902a765dfc872356dd3aad" + }, + { + "path": "github.com/golang/protobuf/proto/testdata", + "revision": "2402d76f3d41f928c7902a765dfc872356dd3aad" + }, + { + "path": "github.com/golang/protobuf/protoc-gen-go/descriptor", + "revision": "2402d76f3d41f928c7902a765dfc872356dd3aad" + }, + { + "path": "github.com/manucorporat/sse", + "revision": "ee05b128a739a0fb76c7ebd3ae4810c1de808d6d" + }, + { + "path": "github.com/pmezard/go-difflib/difflib", + "revision": "792786c7400a136282c1664665ae0a8db921c6c2" + }, + { + "comment": "v1.1.3", + "path": "github.com/stretchr/testify/assert", + "revision": "f390dcf405f7b83c997eac1b06768bb9f44dec18" + }, + { + "path": "golang.org/x/net/context", + "revision": "f315505cf3349909cdf013ea56690da34e96a451" + }, + { + "comment": "v8.15.1", + "path": "gopkg.in/go-playground/validator.v8", + "revision": "c193cecd124b5cc722d7ee5538e945bdb3348435" + } + ], + "rootPath": "github.com/gin-gonic/gin" +} From 44529e4a348c1d76594237677cec72eaa2753beb Mon Sep 17 00:00:00 2001 From: Javier Provecho Fernandez Date: Tue, 6 Dec 2016 23:53:49 +0100 Subject: [PATCH 10/95] Update vendor.json --- vendor/vendor.json | 80 +++++++++++++++++++++++++++++++++------------- 1 file changed, 58 insertions(+), 22 deletions(-) diff --git a/vendor/vendor.json b/vendor/vendor.json index 3f60f40..3c6b0ed 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -1,48 +1,84 @@ { - "comment": "", + "comment": "v1.1.4", "ignore": "test", "package": [ { + "checksumSHA1": "dvabztWVQX8f6oMLRyv4dLH+TGY=", + "comment": "v1.1.0", "path": "github.com/davecgh/go-spew/spew", - "revision": "5215b55f46b2b919f50a1df0eaa5886afe4e3b3d" + "revision": "346938d642f2ec3594ed81d874461961cd0faa76", + "revisionTime": "2016-10-29T20:57:26Z" }, { + "checksumSHA1": "7c3FuEadBInl/4ExSrB7iJMXpe4=", + "path": "github.com/dustin/go-broadcast", + "revision": "3bdf6d4a7164a50bc19d5f230e2981d87d2584f1", + "revisionTime": "2014-06-27T04:00:55Z" + }, + { + "checksumSHA1": "kBeNcaKk56FguvPSUCEaH6AxpRc=", "path": "github.com/golang/protobuf/proto", - "revision": "2402d76f3d41f928c7902a765dfc872356dd3aad" - }, - { - "path": "github.com/golang/protobuf/proto/proto3_proto", - "revision": "2402d76f3d41f928c7902a765dfc872356dd3aad" - }, - { - "path": "github.com/golang/protobuf/proto/testdata", - "revision": "2402d76f3d41f928c7902a765dfc872356dd3aad" - }, - { - "path": "github.com/golang/protobuf/protoc-gen-go/descriptor", - "revision": "2402d76f3d41f928c7902a765dfc872356dd3aad" + "revision": "8ee79997227bf9b34611aee7946ae64735e6fd93", + "revisionTime": "2016-11-17T03:31:26Z" }, { + "checksumSHA1": "b0T0Hzd+zYk+OCDTFMps+jwa/nY=", "path": "github.com/manucorporat/sse", - "revision": "ee05b128a739a0fb76c7ebd3ae4810c1de808d6d" + "revision": "ee05b128a739a0fb76c7ebd3ae4810c1de808d6d", + "revisionTime": "2016-01-26T18:01:36Z" }, { + "checksumSHA1": "9if9IBLsxkarJ804NPWAzgskIAk=", + "path": "github.com/manucorporat/stats", + "revision": "8f2d6ace262eba462e9beb552382c98be51d807b", + "revisionTime": "2015-05-31T20:46:25Z" + }, + { + "checksumSHA1": "xZuhljnmBysJPta/lMyYmJdujCg=", + "path": "github.com/mattn/go-isatty", + "revision": "30a891c33c7cde7b02a981314b4228ec99380cca", + "revisionTime": "2016-11-23T14:36:37Z" + }, + { + "checksumSHA1": "LuFv4/jlrmFNnDb/5SCSEPAM9vU=", + "comment": "v1.0.0", "path": "github.com/pmezard/go-difflib/difflib", - "revision": "792786c7400a136282c1664665ae0a8db921c6c2" + "revision": "792786c7400a136282c1664665ae0a8db921c6c2", + "revisionTime": "2016-01-10T10:55:54Z" }, { - "comment": "v1.1.3", + "checksumSHA1": "Q2V7Zs3diLmLfmfbiuLpSxETSuY=", + "comment": "v1.1.4", "path": "github.com/stretchr/testify/assert", - "revision": "f390dcf405f7b83c997eac1b06768bb9f44dec18" + "revision": "976c720a22c8eb4eb6a0b4348ad85ad12491a506", + "revisionTime": "2016-09-25T22:06:09Z" }, { + "checksumSHA1": "9jjO5GjLa0XF/nfWihF02RoH4qc=", + "comment": "release-branch.go1.7", "path": "golang.org/x/net/context", - "revision": "f315505cf3349909cdf013ea56690da34e96a451" + "revision": "d4c55e66d8c3a2f3382d264b08e3e3454a66355a", + "revisionTime": "2016-10-18T08:54:36Z" }, { - "comment": "v8.15.1", + "checksumSHA1": "8SH0adTcQlA+W5dzqiQ3Hft2VXg=", + "path": "golang.org/x/sys/unix", + "revision": "478fcf54317e52ab69f40bb4c7a1520288d7f7ea", + "revisionTime": "2016-12-05T15:46:50Z" + }, + { + "checksumSHA1": "39V1idWER42Lmcmg2Uy40wMzOlo=", + "comment": "v8.18.1", "path": "gopkg.in/go-playground/validator.v8", - "revision": "c193cecd124b5cc722d7ee5538e945bdb3348435" + "revision": "5f57d2222ad794d0dffb07e664ea05e2ee07d60c", + "revisionTime": "2016-07-18T13:41:25Z" + }, + { + "checksumSHA1": "12GqsW8PiRPnezDDy0v4brZrndM=", + "comment": "v2", + "path": "gopkg.in/yaml.v2", + "revision": "a5b47d31c556af34a302ce5d659e6fea44d90de0", + "revisionTime": "2016-09-28T15:37:09Z" } ], "rootPath": "github.com/gin-gonic/gin" From abcbfb5d68b49ebf96b24c0eff637806176cb4f1 Mon Sep 17 00:00:00 2001 From: Javier Provecho Fernandez Date: Tue, 6 Dec 2016 23:54:11 +0100 Subject: [PATCH 11/95] Update .travis.yml --- .travis.yml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 53f436f..e6a05bd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,12 +1,17 @@ language: go sudo: false go: - - 1.4 - - 1.5.4 - 1.6.4 - 1.7.4 - tip +git: + depth: 3 + +install: + - go get -v github.com/kardianos/govendor + - govendor sync + script: - go test -v -covermode=count -coverprofile=coverage.out From 97d310b55ca24d9c0829aaff61ff646123f49442 Mon Sep 17 00:00:00 2001 From: chriswhelix Date: Tue, 15 Nov 2016 15:51:05 -0800 Subject: [PATCH 12/95] Honor normal gin write contract for context.JSON() Gin normally silently swallows errors writing to the client; however in WriteJSON (and thus context.JSON), the ResponseWriter was being passed directly into the JSON encoder, which will return an error if there's an error writing to the stream. For instance, context.JSON would panic with errors like "write tcp XXX-> YYY: write: connection reset by peer" if the client disconnected before the response was complete. This change makes JSON.Render() treat write errors the same as IndentedJSON, Data, and other renderers. --- render/json.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/render/json.go b/render/json.go index 32e6058..b652c4c 100644 --- a/render/json.go +++ b/render/json.go @@ -37,5 +37,10 @@ func (r IndentedJSON) Render(w http.ResponseWriter) error { func WriteJSON(w http.ResponseWriter, obj interface{}) error { writeContentType(w, jsonContentType) - return json.NewEncoder(w).Encode(obj) + jsonBytes, err := json.Marshal(obj) + if err != nil { + return err + } + w.Write(jsonBytes) + return nil } From 787bff85e58c5361ffe6c5d3b2bd261a65cf52c6 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Sun, 11 Dec 2016 10:14:20 +0800 Subject: [PATCH 13/95] fix testing. Signed-off-by: Bo-Yi Wu --- context_test.go | 12 ++++++------ logger_test.go | 12 ++++++------ middleware_test.go | 4 ++-- render/render_test.go | 4 ++-- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/context_test.go b/context_test.go index 32e596e..81e1884 100644 --- a/context_test.go +++ b/context_test.go @@ -358,9 +358,9 @@ func TestContextRenderJSON(t *testing.T) { c.JSON(201, H{"foo": "bar"}) - assert.Equal(t, w.Code, 201) - assert.Equal(t, w.Body.String(), "{\"foo\":\"bar\"}\n") - assert.Equal(t, w.HeaderMap.Get("Content-Type"), "application/json; charset=utf-8") + assert.Equal(t, 201, w.Code) + assert.Equal(t, "{\"foo\":\"bar\"}", w.Body.String()) + assert.Equal(t, "application/json; charset=utf-8", w.HeaderMap.Get("Content-Type")) } // Tests that the response is serialized as JSON @@ -372,9 +372,9 @@ func TestContextRenderAPIJSON(t *testing.T) { c.Header("Content-Type", "application/vnd.api+json") c.JSON(201, H{"foo": "bar"}) - assert.Equal(t, w.Code, 201) - assert.Equal(t, w.Body.String(), "{\"foo\":\"bar\"}\n") - assert.Equal(t, w.HeaderMap.Get("Content-Type"), "application/vnd.api+json") + assert.Equal(t, 201, w.Code) + assert.Equal(t, "{\"foo\":\"bar\"}", w.Body.String()) + assert.Equal(t, "application/vnd.api+json", w.HeaderMap.Get("Content-Type")) } // Tests that the response is serialized as JSON diff --git a/logger_test.go b/logger_test.go index 2ad1f47..7c064c9 100644 --- a/logger_test.go +++ b/logger_test.go @@ -107,16 +107,16 @@ func TestErrorLogger(t *testing.T) { }) w := performRequest(router, "GET", "/error") - assert.Equal(t, w.Code, 200) - assert.Equal(t, w.Body.String(), "{\"error\":\"this is an error\"}\n") + assert.Equal(t, 200, w.Code) + assert.Equal(t, "{\"error\":\"this is an error\"}", w.Body.String()) w = performRequest(router, "GET", "/abort") - assert.Equal(t, w.Code, 401) - assert.Equal(t, w.Body.String(), "{\"error\":\"no authorized\"}\n") + assert.Equal(t, 401, w.Code) + assert.Equal(t, "{\"error\":\"no authorized\"}", w.Body.String()) w = performRequest(router, "GET", "/print") - assert.Equal(t, w.Code, 500) - assert.Equal(t, w.Body.String(), "hola!{\"error\":\"this is an error\"}\n") + assert.Equal(t, 500, w.Code) + assert.Equal(t, "hola!{\"error\":\"this is an error\"}", w.Body.String()) } func TestSkippingPaths(t *testing.T) { diff --git a/middleware_test.go b/middleware_test.go index 3101d52..273d003 100644 --- a/middleware_test.go +++ b/middleware_test.go @@ -245,6 +245,6 @@ func TestMiddlewareWrite(t *testing.T) { w := performRequest(router, "GET", "/") - assert.Equal(t, w.Code, 400) - assert.Equal(t, strings.Replace(w.Body.String(), " ", "", -1), strings.Replace("hola\nbar{\"foo\":\"bar\"}\n{\"foo\":\"bar\"}\nevent:test\ndata:message\n\n", " ", "", -1)) + assert.Equal(t, 400, w.Code) + assert.Equal(t, strings.Replace("hola\nbar{\"foo\":\"bar\"}{\"foo\":\"bar\"}event:test\ndata:message\n\n", " ", "", -1), strings.Replace(w.Body.String(), " ", "", -1)) } diff --git a/render/render_test.go b/render/render_test.go index 7a6ffb7..c814ff6 100644 --- a/render/render_test.go +++ b/render/render_test.go @@ -25,8 +25,8 @@ func TestRenderJSON(t *testing.T) { err := (JSON{data}).Render(w) assert.NoError(t, err) - assert.Equal(t, w.Body.String(), "{\"foo\":\"bar\"}\n") - assert.Equal(t, w.Header().Get("Content-Type"), "application/json; charset=utf-8") + assert.Equal(t, "{\"foo\":\"bar\"}", w.Body.String()) + assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) } func TestRenderIndentedJSON(t *testing.T) { From d158ef2e82cf74c4144e7e709635e1fe2fb260c9 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Wed, 21 Dec 2016 14:24:01 +0800 Subject: [PATCH 14/95] Support disable console color. Signed-off-by: Bo-Yi Wu --- README.md | 3 +++ examples/basic/main.go | 2 ++ logger.go | 23 ++++++++++++++--------- logger_test.go | 7 +++++++ 4 files changed, 26 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index b215d3f..e48da26 100644 --- a/README.md +++ b/README.md @@ -108,6 +108,9 @@ BenchmarkZeus_GithubAll | 2000 | 944234 | 300688 | 2648 ```go func main() { + // Disable Console Color + // gin.DisableConsoleColor() + // Creates a gin router with default middleware: // logger and recovery (crash-free) middleware router := gin.Default() diff --git a/examples/basic/main.go b/examples/basic/main.go index 80f2bd3..984c06a 100644 --- a/examples/basic/main.go +++ b/examples/basic/main.go @@ -7,6 +7,8 @@ import ( var DB = make(map[string]string) func main() { + // Disable Console Color + // gin.DisableConsoleColor() r := gin.Default() // Ping test diff --git a/logger.go b/logger.go index 81904d2..186e305 100644 --- a/logger.go +++ b/logger.go @@ -14,16 +14,21 @@ import ( ) var ( - green = string([]byte{27, 91, 57, 55, 59, 52, 50, 109}) - white = string([]byte{27, 91, 57, 48, 59, 52, 55, 109}) - yellow = string([]byte{27, 91, 57, 55, 59, 52, 51, 109}) - red = string([]byte{27, 91, 57, 55, 59, 52, 49, 109}) - blue = string([]byte{27, 91, 57, 55, 59, 52, 52, 109}) - magenta = string([]byte{27, 91, 57, 55, 59, 52, 53, 109}) - cyan = string([]byte{27, 91, 57, 55, 59, 52, 54, 109}) - reset = string([]byte{27, 91, 48, 109}) + green = string([]byte{27, 91, 57, 55, 59, 52, 50, 109}) + white = string([]byte{27, 91, 57, 48, 59, 52, 55, 109}) + yellow = string([]byte{27, 91, 57, 55, 59, 52, 51, 109}) + red = string([]byte{27, 91, 57, 55, 59, 52, 49, 109}) + blue = string([]byte{27, 91, 57, 55, 59, 52, 52, 109}) + magenta = string([]byte{27, 91, 57, 55, 59, 52, 53, 109}) + cyan = string([]byte{27, 91, 57, 55, 59, 52, 54, 109}) + reset = string([]byte{27, 91, 48, 109}) + disableColor = false ) +func DisableConsoleColor() { + disableColor = true +} + func ErrorLogger() HandlerFunc { return ErrorLoggerT(ErrorTypeAny) } @@ -49,7 +54,7 @@ func Logger() HandlerFunc { func LoggerWithWriter(out io.Writer, notlogged ...string) HandlerFunc { isTerm := true - if w, ok := out.(*os.File); !ok || !isatty.IsTerminal(w.Fd()) { + if w, ok := out.(*os.File); !ok || !isatty.IsTerminal(w.Fd()) || disableColor { isTerm = false } diff --git a/logger_test.go b/logger_test.go index 7c064c9..6adf86b 100644 --- a/logger_test.go +++ b/logger_test.go @@ -132,3 +132,10 @@ func TestSkippingPaths(t *testing.T) { performRequest(router, "GET", "/skipped") assert.Contains(t, buffer.String(), "") } + +func TestDisableConsoleColor(t *testing.T) { + New() + assert.False(t, disableColor) + DisableConsoleColor() + assert.True(t, disableColor) +} From 93e36404a1d71ee738c9d5da1ea9a990621baf89 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Fri, 23 Dec 2016 09:12:13 +0800 Subject: [PATCH 15/95] Improve document for #742 Signed-off-by: Bo-Yi Wu --- README.md | 35 ++++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index e48da26..e0307ea 100644 --- a/README.md +++ b/README.md @@ -374,8 +374,41 @@ func main() { } ``` +#### Bind Query String + +See the [detail information](https://github.com/gin-gonic/gin/issues/742#issuecomment-264681292). + +```go +package main + +import "log" +import "github.com/gin-gonic/gin" + +type Person struct { + Name string `form:"name"` + Address string `form:"address"` +} + +func main() { + route := gin.Default() + route.GET("/testing", startPage) + route.Run(":8085") +} + +func startPage(c *gin.Context) { + var person Person + if c.Bind(&person) == nil { + log.Println(person.Name) + log.Println(person.Address) + } + + c.String(200, "Success") +} +``` + + +### Multipart/Urlencoded binding -###Multipart/Urlencoded binding ```go package main From 5cc3d5955f5a03c13daa878c5ef28c33d9a18800 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Sat, 24 Dec 2016 12:25:01 +0800 Subject: [PATCH 16/95] Support upload single or multiple files. Signed-off-by: Bo-Yi Wu --- context.go | 20 ++++++++++++++++++-- context_test.go | 31 +++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 2 deletions(-) diff --git a/context.go b/context.go index d2b98b6..0beeef9 100644 --- a/context.go +++ b/context.go @@ -8,6 +8,7 @@ import ( "errors" "io" "math" + "mime/multipart" "net" "net/http" "net/url" @@ -30,7 +31,10 @@ const ( MIMEMultipartPOSTForm = binding.MIMEMultipartPOSTForm ) -const abortIndex int8 = math.MaxInt8 / 2 +const ( + defaultMemory = 32 << 20 // 32 MB + abortIndex int8 = math.MaxInt8 / 2 +) // Context is the most important part of gin. It allows us to pass variables between middleware, // manage the flow, validate the JSON of a request and render a JSON response for example. @@ -291,7 +295,7 @@ func (c *Context) PostFormArray(key string) []string { func (c *Context) GetPostFormArray(key string) ([]string, bool) { req := c.Request req.ParseForm() - req.ParseMultipartForm(32 << 20) // 32 MB + req.ParseMultipartForm(defaultMemory) if values := req.PostForm[key]; len(values) > 0 { return values, true } @@ -303,6 +307,18 @@ func (c *Context) GetPostFormArray(key string) ([]string, bool) { return []string{}, false } +// FormFile returns the first file for the provided form key. +func (c *Context) FormFile(name string) (*multipart.FileHeader, error) { + _, fh, err := c.Request.FormFile(name) + return fh, err +} + +// MultipartForm is the parsed multipart form, including file uploads. +func (c *Context) MultipartForm() (*multipart.Form, error) { + err := c.Request.ParseMultipartForm(defaultMemory) + return c.Request.MultipartForm, err +} + // Bind checks the Content-Type to select a binding engine automatically, // Depending the "Content-Type" header different bindings are used: // "application/json" --> JSON binding diff --git a/context_test.go b/context_test.go index 81e1884..e488f42 100644 --- a/context_test.go +++ b/context_test.go @@ -53,6 +53,37 @@ func must(err error) { } } +func TestContextFormFile(t *testing.T) { + buf := new(bytes.Buffer) + mw := multipart.NewWriter(buf) + w, err := mw.CreateFormFile("file", "test") + if assert.NoError(t, err) { + w.Write([]byte("test")) + } + mw.Close() + c, _ := CreateTestContext(httptest.NewRecorder()) + c.Request, _ = http.NewRequest("POST", "/", buf) + c.Request.Header.Set("Content-Type", mw.FormDataContentType()) + f, err := c.FormFile("file") + if assert.NoError(t, err) { + assert.Equal(t, "test", f.Filename) + } +} + +func TestContextMultipartForm(t *testing.T) { + buf := new(bytes.Buffer) + mw := multipart.NewWriter(buf) + mw.WriteField("foo", "bar") + mw.Close() + c, _ := CreateTestContext(httptest.NewRecorder()) + c.Request, _ = http.NewRequest("POST", "/", buf) + c.Request.Header.Set("Content-Type", mw.FormDataContentType()) + f, err := c.MultipartForm() + if assert.NoError(t, err) { + assert.NotNil(t, f) + } +} + func TestContextReset(t *testing.T) { router := New() c := router.allocateContext() From 713c3697f44fcb1b43291682133fc42d08a15f31 Mon Sep 17 00:00:00 2001 From: Javier Provecho Fernandez Date: Sun, 1 Jan 2017 11:54:37 +0100 Subject: [PATCH 17/95] Add instructions for pulling latest changes --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index e48da26..536022c 100644 --- a/README.md +++ b/README.md @@ -102,6 +102,13 @@ BenchmarkZeus_GithubAll | 2000 | 944234 | 300688 | 2648 import "net/http" ``` +4. (Optional) Use latest changes (note: they may be broken and/or unstable): +    ```sh + $ GIN_PATH=$GOPATH/src/gopkg.in/gin-gonic/gin.v1 + $ git -C $GIN_PATH checkout develop + $ git -C $GIN_PATH pull origin develop +    ``` + ## API Examples #### Using GET, POST, PUT, PATCH, DELETE and OPTIONS From ebe3580daf97c0a38bd6237c418a303572249d03 Mon Sep 17 00:00:00 2001 From: David Irvine Date: Mon, 2 Jan 2017 03:05:30 -0500 Subject: [PATCH 18/95] Add convenience method to check if websockets required (#779) * Add convenience method to check if websockets required * Add tests * Fix up tests for develop branch --- context.go | 10 ++++++++++ context_test.go | 22 ++++++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/context.go b/context.go index 0beeef9..01c7cb4 100644 --- a/context.go +++ b/context.go @@ -383,6 +383,16 @@ func (c *Context) ContentType() string { return filterFlags(c.requestHeader("Content-Type")) } +// IsWebsocket returns true if the request headers indicate that a websocket +// handshake is being initiated by the client. +func (c *Context) IsWebsocket() bool { + if strings.Contains(strings.ToLower(c.requestHeader("Connection")), "upgrade") && + strings.ToLower(c.requestHeader("Upgrade")) == "websocket" { + return true + } + return false +} + func (c *Context) requestHeader(key string) string { if values, _ := c.Request.Header[key]; len(values) > 0 { return values[0] diff --git a/context_test.go b/context_test.go index e488f42..fe22c49 100644 --- a/context_test.go +++ b/context_test.go @@ -814,3 +814,25 @@ func TestContextGolangContext(t *testing.T) { assert.Equal(t, c.Value("foo"), "bar") assert.Nil(t, c.Value(1)) } + +func TestWebsocketsRequired(t *testing.T) { + // Example request from spec: https://tools.ietf.org/html/rfc6455#section-1.2 + c, _ := CreateTestContext(httptest.NewRecorder()) + c.Request, _ = http.NewRequest("GET", "/chat", nil) + c.Request.Header.Set("Host", "server.example.com") + c.Request.Header.Set("Upgrade", "websocket") + c.Request.Header.Set("Connection", "Upgrade") + c.Request.Header.Set("Sec-WebSocket-Key", "dGhlIHNhbXBsZSBub25jZQ==") + c.Request.Header.Set("Origin", "http://example.com") + c.Request.Header.Set("Sec-WebSocket-Protocol", "chat, superchat") + c.Request.Header.Set("Sec-WebSocket-Version", "13") + + assert.True(t, c.IsWebsocket()) + + // Normal request, no websocket required. + c, _ = CreateTestContext(httptest.NewRecorder()) + c.Request, _ = http.NewRequest("GET", "/chat", nil) + c.Request.Header.Set("Host", "server.example.com") + + assert.False(t, c.IsWebsocket()) +} From aa1a2b75fafe8fa2d25647da228fda4fa82f8f67 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Mon, 2 Jan 2017 18:01:25 +0800 Subject: [PATCH 19/95] Add comment for the logic behind func. Signed-off-by: Bo-Yi Wu --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 4934564..19d9088 100644 --- a/README.md +++ b/README.md @@ -404,6 +404,9 @@ func main() { 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/develop/binding/binding.go#L45 if c.Bind(&person) == nil { log.Println(person.Name) log.Println(person.Address) From 6596aa3b715b3bc0f12f8c319e4bad23012c9e11 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Tue, 3 Jan 2017 10:34:27 +0800 Subject: [PATCH 20/95] [ci skip] update readme for upload file. Signed-off-by: Bo-Yi Wu --- README.md | 60 +++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 45 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 19d9088..7669edf 100644 --- a/README.md +++ b/README.md @@ -229,34 +229,64 @@ func main() { id: 1234; page: 1; name: manu; message: this_is_great ``` -### Another example: upload file +### upload file -References issue [#548](https://github.com/gin-gonic/gin/issues/548). +#### upload single file + +References issue [#774](https://github.com/gin-gonic/gin/issues/774). ```go func main() { router := gin.Default() - router.POST("/upload", func(c *gin.Context) { + // single file + file, _ := c.FormFile("file") + log.Println(file.Filename) - file, header , err := c.Request.FormFile("upload") - filename := header.Filename - fmt.Println(header.Filename) - out, err := os.Create("./tmp/"+filename+".png") - if err != nil { - log.Fatal(err) - } - defer out.Close() - _, err = io.Copy(out, file) - if err != nil { - log.Fatal(err) - } + c.String(http.StatusOK, "Uploaded...") }) router.Run(":8080") } ``` +curl command: + +```bash +curl -X POST http://localhost:8080/upload \ + -F "file=@/Users/appleboy/test.zip" \ + -H "Content-Type: multipart/form-data" +``` + +#### upload multiple files + +```go +func main() { + router := gin.Default() + router.POST("/upload", func(c *gin.Context) { + // Multipart form + form, _ := c.MultipartForm() + files := form.File["upload[]"] + + for _, file := range files { + log.Println(file.Filename) + } + c.String(http.StatusOK, "Uploaded...") + }) + router.Run(":8080") +} +``` + +curl command: + +```bash +curl -X POST http://localhost:8080/upload \ + -F "upload[]=@/Users/appleboy/test1.zip" \ + -F "upload[]=@/Users/appleboy/test2.zip" \ + -H "Content-Type: multipart/form-data" +``` + #### Grouping routes + ```go func main() { router := gin.Default() From 17af53a565bff4483c734fe00f1870a4ecf05afc Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Tue, 3 Jan 2017 11:50:35 +0800 Subject: [PATCH 21/95] add upload file example. Signed-off-by: Bo-Yi Wu --- README.md | 4 +- examples/upload-file/multiple/main.go | 39 +++++++++++++++++++ .../upload-file/multiple/public/index.html | 17 ++++++++ examples/upload-file/single/main.go | 34 ++++++++++++++++ examples/upload-file/single/public/index.html | 16 ++++++++ 5 files changed, 109 insertions(+), 1 deletion(-) create mode 100644 examples/upload-file/multiple/main.go create mode 100644 examples/upload-file/multiple/public/index.html create mode 100644 examples/upload-file/single/main.go create mode 100644 examples/upload-file/single/public/index.html diff --git a/README.md b/README.md index 7669edf..571c7ce 100644 --- a/README.md +++ b/README.md @@ -233,7 +233,7 @@ id: 1234; page: 1; name: manu; message: this_is_great #### upload single file -References issue [#774](https://github.com/gin-gonic/gin/issues/774). +References issue [#774](https://github.com/gin-gonic/gin/issues/774) and detail [example code](examples/upload-file/single). ```go func main() { @@ -259,6 +259,8 @@ curl -X POST http://localhost:8080/upload \ #### upload multiple files +See the detail [example code](examples/upload-file/multiple). + ```go func main() { router := gin.Default() diff --git a/examples/upload-file/multiple/main.go b/examples/upload-file/multiple/main.go new file mode 100644 index 0000000..2f4e9b5 --- /dev/null +++ b/examples/upload-file/multiple/main.go @@ -0,0 +1,39 @@ +package main + +import ( + "fmt" + "io" + "net/http" + "os" + + "github.com/gin-gonic/gin" +) + +func main() { + router := gin.Default() + router.Static("/", "./public") + router.POST("/upload", func(c *gin.Context) { + name := c.PostForm("name") + email := c.PostForm("email") + + // Multipart form + form, _ := c.MultipartForm() + files := form.File["files"] + + for _, file := range files { + // Source + src, _ := file.Open() + defer src.Close() + + // Destination + dst, _ := os.Create(file.Filename) + defer dst.Close() + + // Copy + io.Copy(dst, src) + } + + c.String(http.StatusOK, fmt.Sprintf("Uploaded successfully %d files with fields name=%s and email=%s.", len(files), name, email)) + }) + router.Run(":8080") +} diff --git a/examples/upload-file/multiple/public/index.html b/examples/upload-file/multiple/public/index.html new file mode 100644 index 0000000..b846360 --- /dev/null +++ b/examples/upload-file/multiple/public/index.html @@ -0,0 +1,17 @@ + + + + + Multiple file upload + + +

Upload multiple files with fields

+ +
+ Name:
+ Email:
+ Files:

+ +
+ + diff --git a/examples/upload-file/single/main.go b/examples/upload-file/single/main.go new file mode 100644 index 0000000..9acf500 --- /dev/null +++ b/examples/upload-file/single/main.go @@ -0,0 +1,34 @@ +package main + +import ( + "fmt" + "io" + "net/http" + "os" + + "github.com/gin-gonic/gin" +) + +func main() { + router := gin.Default() + router.Static("/", "./public") + router.POST("/upload", func(c *gin.Context) { + name := c.PostForm("name") + email := c.PostForm("email") + + // Source + file, _ := c.FormFile("file") + src, _ := file.Open() + defer src.Close() + + // Destination + dst, _ := os.Create(file.Filename) + defer dst.Close() + + // Copy + io.Copy(dst, src) + + c.String(http.StatusOK, fmt.Sprintf("File %s uploaded successfully with fields name=%s and email=%s.", file.Filename, name, email)) + }) + router.Run(":8080") +} diff --git a/examples/upload-file/single/public/index.html b/examples/upload-file/single/public/index.html new file mode 100644 index 0000000..b0c2a80 --- /dev/null +++ b/examples/upload-file/single/public/index.html @@ -0,0 +1,16 @@ + + + + + Single file upload + + +

Upload single file with fields

+ +
+ Name:
+ Email:
+ Files:

+ +
+ From 970e96e38806c3636819dcc6b3df4ff7ecf382b3 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Tue, 3 Jan 2017 23:42:21 +0800 Subject: [PATCH 22/95] test: update client ip testing. --- context_test.go | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/context_test.go b/context_test.go index fe22c49..7dc3085 100644 --- a/context_test.go +++ b/context_test.go @@ -718,24 +718,25 @@ func TestContextClientIP(t *testing.T) { c.Request.Header.Set("X-Appengine-Remote-Addr", "50.50.50.50") c.Request.RemoteAddr = " 40.40.40.40:42123 " - assert.Equal(t, c.ClientIP(), "10.10.10.10") - - c.Request.Header.Del("X-Real-IP") - assert.Equal(t, c.ClientIP(), "20.20.20.20") - - c.Request.Header.Set("X-Forwarded-For", "30.30.30.30 ") - assert.Equal(t, c.ClientIP(), "30.30.30.30") + assert.Equal(t, "20.20.20.20", c.ClientIP()) c.Request.Header.Del("X-Forwarded-For") + assert.Equal(t, "10.10.10.10", c.ClientIP()) + + c.Request.Header.Set("X-Forwarded-For", "30.30.30.30 ") + assert.Equal(t, "30.30.30.30", c.ClientIP()) + + c.Request.Header.Del("X-Forwarded-For") + c.Request.Header.Del("X-Real-IP") c.engine.AppEngine = true - assert.Equal(t, c.ClientIP(), "50.50.50.50") + assert.Equal(t, "50.50.50.50", c.ClientIP()) c.Request.Header.Del("X-Appengine-Remote-Addr") - assert.Equal(t, c.ClientIP(), "40.40.40.40") + assert.Equal(t, "40.40.40.40", c.ClientIP()) // no port c.Request.RemoteAddr = "50.50.50.50" - assert.Equal(t, c.ClientIP(), "") + assert.Equal(t, "", c.ClientIP()) } func TestContextContentType(t *testing.T) { From c115074d773cb135f8c647992e792b91ad3bb3d9 Mon Sep 17 00:00:00 2001 From: tsirolnik Date: Tue, 30 Aug 2016 18:58:39 +0300 Subject: [PATCH 23/95] Use X-Forwarded-For before X-Real-Ip Nginx uses X-Real-Ip with its IP instead of the client's IP. Therefore, we should use X-Forwarded-For *before* X-Real-Ip --- context.go | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/context.go b/context.go index 01c7cb4..d19e1ee 100644 --- a/context.go +++ b/context.go @@ -349,13 +349,10 @@ func (c *Context) BindWith(obj interface{}, b binding.Binding) error { // ClientIP implements a best effort algorithm to return the real client IP, it parses // X-Real-IP and X-Forwarded-For in order to work properly with reverse-proxies such us: nginx or haproxy. +// Use X-Forwarded-For before X-Real-Ip as nginx uses X-Real-Ip with the proxy's IP. func (c *Context) ClientIP() string { if c.engine.ForwardedByClientIP { - clientIP := strings.TrimSpace(c.requestHeader("X-Real-Ip")) - if len(clientIP) > 0 { - return clientIP - } - clientIP = c.requestHeader("X-Forwarded-For") + clientIP := c.requestHeader("X-Forwarded-For") if index := strings.IndexByte(clientIP, ','); index >= 0 { clientIP = clientIP[0:index] } @@ -363,6 +360,10 @@ func (c *Context) ClientIP() string { if len(clientIP) > 0 { return clientIP } + clientIP = strings.TrimSpace(c.requestHeader("X-Real-Ip")) + if len(clientIP) > 0 { + return clientIP + } } if c.engine.AppEngine { From 050937dab80745a7fc666e5aa0b61f76169c48dc Mon Sep 17 00:00:00 2001 From: Javier Provecho Fernandez Date: Tue, 3 Jan 2017 17:04:29 +0100 Subject: [PATCH 24/95] Improve "Upload file" example, Fix MD typos --- README.md | 50 +++++++++++++++++++++++++------------------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index 571c7ce..0c08556 100644 --- a/README.md +++ b/README.md @@ -111,7 +111,7 @@ BenchmarkZeus_GithubAll | 2000 | 944234 | 300688 | 2648 ## API Examples -#### Using GET, POST, PUT, PATCH, DELETE and OPTIONS +### Using GET, POST, PUT, PATCH, DELETE and OPTIONS ```go func main() { @@ -137,7 +137,7 @@ func main() { } ``` -#### Parameters in path +### Parameters in path ```go func main() { @@ -162,7 +162,7 @@ func main() { } ``` -#### Querystring parameters +### Querystring parameters ```go func main() { router := gin.Default() @@ -229,9 +229,9 @@ func main() { id: 1234; page: 1; name: manu; message: this_is_great ``` -### upload file +### Upload files -#### upload single file +#### Single file References issue [#774](https://github.com/gin-gonic/gin/issues/774) and detail [example code](examples/upload-file/single). @@ -243,13 +243,13 @@ func main() { file, _ := c.FormFile("file") log.Println(file.Filename) - c.String(http.StatusOK, "Uploaded...") + c.String(http.StatusOK, fmt.Printf("'%s' uploaded!", file.Filename)) }) router.Run(":8080") } ``` -curl command: +How to `curl`: ```bash curl -X POST http://localhost:8080/upload \ @@ -257,7 +257,7 @@ curl -X POST http://localhost:8080/upload \ -H "Content-Type: multipart/form-data" ``` -#### upload multiple files +#### Multiple files See the detail [example code](examples/upload-file/multiple). @@ -272,13 +272,13 @@ func main() { for _, file := range files { log.Println(file.Filename) } - c.String(http.StatusOK, "Uploaded...") + c.String(http.StatusOK, fmt.Printf("%d files uploaded!", len(files))) }) router.Run(":8080") } ``` -curl command: +How to `curl`: ```bash curl -X POST http://localhost:8080/upload \ @@ -287,7 +287,7 @@ curl -X POST http://localhost:8080/upload \ -H "Content-Type: multipart/form-data" ``` -#### Grouping routes +### Grouping routes ```go func main() { @@ -314,7 +314,7 @@ func main() { ``` -#### Blank Gin without middleware by default +### Blank Gin without middleware by default Use @@ -328,7 +328,7 @@ r := gin.Default() ``` -#### Using middleware +### Using middleware ```go func main() { // Creates a router without any middleware by default @@ -363,7 +363,7 @@ func main() { } ``` -#### Model binding and validation +### Model binding and validation To bind a request body into a type, use model binding. We currently support binding of JSON, XML and standard form values (foo=bar&boo=baz). @@ -413,7 +413,7 @@ func main() { } ``` -#### Bind Query String +### Bind Query String See the [detail information](https://github.com/gin-gonic/gin/issues/742#issuecomment-264681292). @@ -489,7 +489,7 @@ $ curl -v --form user=user --form password=password http://localhost:8080/login ``` -#### XML, JSON and YAML rendering +### XML, JSON and YAML rendering ```go func main() { @@ -528,7 +528,7 @@ func main() { } ``` -####Serving static files +### Serving static files ```go func main() { @@ -542,7 +542,7 @@ func main() { } ``` -####HTML rendering +### HTML rendering Using LoadHTMLGlob() or LoadHTMLFiles() @@ -622,7 +622,7 @@ func main() { ``` -#### Redirects +### Redirects Issuing a HTTP redirect is easy: @@ -634,7 +634,7 @@ r.GET("/test", func(c *gin.Context) { Both internal and external locations are supported. -#### Custom Middleware +### Custom Middleware ```go func Logger() gin.HandlerFunc { @@ -674,7 +674,7 @@ func main() { } ``` -#### Using BasicAuth() middleware +### Using BasicAuth() middleware ```go // simulate some private data var secrets = gin.H{ @@ -713,7 +713,7 @@ func main() { ``` -#### Goroutines inside a middleware +### Goroutines inside a middleware When starting inside a middleware or handler, you **SHOULD NOT** use the original context inside it, you have to use a read-only copy. ```go @@ -745,7 +745,7 @@ func main() { } ``` -#### Custom HTTP configuration +### Custom HTTP configuration Use `http.ListenAndServe()` directly, like this: @@ -772,7 +772,7 @@ func main() { } ``` -#### Graceful restart or stop +### Graceful restart or stop Do you want to graceful restart or stop your web server? There are some ways this can be done. @@ -803,7 +803,7 @@ An alternative to endless: - You should add/modify tests to cover your proposed code changes. - If your pull request contains a new feature, please document it on the README. -## Example +## Users Awesome project lists using [Gin](https://github.com/gin-gonic/gin) web framework. From 8191cdf5d6ec3a5430c4599448b50491683c6a71 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Thu, 5 Jan 2017 12:22:32 +0800 Subject: [PATCH 25/95] docs: update readme for multiple template package. (#786) --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 0c08556..c940a10 100644 --- a/README.md +++ b/README.md @@ -621,6 +621,9 @@ func main() { } ``` +### Multitemplate + +Gin allow by default use only one html.Template. Check [a multitemplate render](https://github.com/gin-contrib/multitemplate) for using features like go 1.6 `block template`. ### Redirects From 75c2274b4b3c5dc47f11767324d7aefb655011d5 Mon Sep 17 00:00:00 2001 From: novaeye Date: Thu, 5 Jan 2017 16:29:33 +0800 Subject: [PATCH 26/95] better display for log message (#623) --- logger.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/logger.go b/logger.go index 186e305..c7cbfe1 100644 --- a/logger.go +++ b/logger.go @@ -92,7 +92,7 @@ func LoggerWithWriter(out io.Writer, notlogged ...string) HandlerFunc { } comment := c.Errors.ByType(ErrorTypePrivate).String() - fmt.Fprintf(out, "[GIN] %v |%s %3d %s| %13v | %s |%s %s %-7s %s\n%s", + fmt.Fprintf(out, "[GIN] %v |%s %3d %s| %13v | %15s |%s %s %-7s %s\n%s", end.Format("2006/01/02 - 15:04:05"), statusColor, statusCode, reset, latency, From 963acc4b0ce297782405d4aefd6fe173ff657b1f Mon Sep 17 00:00:00 2001 From: Javier Provecho Fernandez Date: Mon, 9 Jan 2017 16:24:48 +0100 Subject: [PATCH 27/95] Fix #198 (#781) * Add new function to Render interface for writing content type only * Add support for the new function in Render interface for writing content-type only * Fix unhandled merge conflict in context_test.go * Update vendor.json --- context.go | 30 ++++++++--- context_test.go | 132 ++++++++++++++++++++++++++++++++++++++++++++- middleware_test.go | 2 +- render/data.go | 15 +++--- render/html.go | 7 ++- render/json.go | 29 ++++++---- render/redirect.go | 2 + render/render.go | 1 + render/text.go | 5 +- render/xml.go | 6 ++- render/yaml.go | 6 ++- vendor/vendor.json | 12 ++--- 12 files changed, 213 insertions(+), 34 deletions(-) diff --git a/context.go b/context.go index d19e1ee..e937a4c 100644 --- a/context.go +++ b/context.go @@ -17,7 +17,7 @@ import ( "github.com/gin-gonic/gin/binding" "github.com/gin-gonic/gin/render" - "github.com/manucorporat/sse" + "gopkg.in/gin-contrib/sse.v0" ) // Content-Type MIME of the most common data formats @@ -405,6 +405,19 @@ func (c *Context) requestHeader(key string) string { /******** RESPONSE RENDERING ********/ /************************************/ +// bodyAllowedForStatus is a copy of http.bodyAllowedForStatus non-exported function +func bodyAllowedForStatus(status int) bool { + switch { + case status >= 100 && status <= 199: + return false + case status == 204: + return false + case status == 304: + return false + } + return true +} + func (c *Context) Status(code int) { c.writermem.WriteHeader(code) } @@ -454,6 +467,13 @@ func (c *Context) Cookie(name string) (string, error) { func (c *Context) Render(code int, r render.Render) { c.Status(code) + + if !bodyAllowedForStatus(code) { + r.WriteContentType(c.Writer) + c.Writer.WriteHeaderNow() + return + } + if err := r.Render(c.Writer); err != nil { panic(err) } @@ -478,10 +498,7 @@ func (c *Context) IndentedJSON(code int, obj interface{}) { // JSON serializes the given struct as JSON into the response body. // It also sets the Content-Type as "application/json". func (c *Context) JSON(code int, obj interface{}) { - c.Status(code) - if err := render.WriteJSON(c.Writer, obj); err != nil { - panic(err) - } + c.Render(code, render.JSON{Data: obj}) } // XML serializes the given struct as XML into the response body. @@ -497,8 +514,7 @@ func (c *Context) YAML(code int, obj interface{}) { // String writes the given string into the response body. func (c *Context) String(code int, format string, values ...interface{}) { - c.Status(code) - render.WriteString(c.Writer, format, values) + c.Render(code, render.String{Format: format, Data: values}) } // Redirect returns a HTTP redirect to the specific location. diff --git a/context_test.go b/context_test.go index 7dc3085..fd8ca4a 100644 --- a/context_test.go +++ b/context_test.go @@ -7,6 +7,7 @@ package gin import ( "bytes" "errors" + "fmt" "html/template" "mime/multipart" "net/http" @@ -15,9 +16,9 @@ import ( "testing" "time" - "github.com/manucorporat/sse" "github.com/stretchr/testify/assert" "golang.org/x/net/context" + "gopkg.in/gin-contrib/sse.v0" ) var _ context.Context = &Context{} @@ -381,6 +382,35 @@ func TestContextGetCookie(t *testing.T) { assert.Equal(t, cookie, "gin") } +func TestContextBodyAllowedForStatus(t *testing.T) { + assert.Equal(t, false, bodyAllowedForStatus(102)) + assert.Equal(t, false, bodyAllowedForStatus(204)) + assert.Equal(t, false, bodyAllowedForStatus(304)) + assert.Equal(t, true, bodyAllowedForStatus(500)) +} + +type TestPanicRender struct { +} + +func (*TestPanicRender) Render(http.ResponseWriter) error { + return errors.New("TestPanicRender") +} + +func (*TestPanicRender) WriteContentType(http.ResponseWriter) {} + +func TestContextRenderPanicIfErr(t *testing.T) { + defer func() { + r := recover() + assert.Equal(t, "TestPanicRender", fmt.Sprint(r)) + }() + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + + c.Render(http.StatusOK, &TestPanicRender{}) + + assert.Fail(t, "Panic not detected") +} + // Tests that the response is serialized as JSON // and Content-Type is set to application/json func TestContextRenderJSON(t *testing.T) { @@ -394,6 +424,18 @@ func TestContextRenderJSON(t *testing.T) { assert.Equal(t, "application/json; charset=utf-8", w.HeaderMap.Get("Content-Type")) } +// Tests that no JSON is rendered if code is 204 +func TestContextRenderNoContentJSON(t *testing.T) { + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + + c.JSON(204, H{"foo": "bar"}) + + assert.Equal(t, 204, w.Code) + assert.Equal(t, "", w.Body.String()) + assert.Equal(t, "application/json; charset=utf-8", w.HeaderMap.Get("Content-Type")) +} + // Tests that the response is serialized as JSON // we change the content-type before func TestContextRenderAPIJSON(t *testing.T) { @@ -408,6 +450,19 @@ func TestContextRenderAPIJSON(t *testing.T) { assert.Equal(t, "application/vnd.api+json", w.HeaderMap.Get("Content-Type")) } +// Tests that no Custom JSON is rendered if code is 204 +func TestContextRenderNoContentAPIJSON(t *testing.T) { + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + + c.Header("Content-Type", "application/vnd.api+json") + c.JSON(204, H{"foo": "bar"}) + + assert.Equal(t, 204, w.Code) + assert.Equal(t, "", w.Body.String()) + assert.Equal(t, w.HeaderMap.Get("Content-Type"), "application/vnd.api+json") +} + // Tests that the response is serialized as JSON // and Content-Type is set to application/json func TestContextRenderIndentedJSON(t *testing.T) { @@ -421,6 +476,18 @@ func TestContextRenderIndentedJSON(t *testing.T) { assert.Equal(t, w.HeaderMap.Get("Content-Type"), "application/json; charset=utf-8") } +// Tests that no Custom JSON is rendered if code is 204 +func TestContextRenderNoContentIndentedJSON(t *testing.T) { + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + + c.IndentedJSON(204, H{"foo": "bar", "bar": "foo", "nested": H{"foo": "bar"}}) + + assert.Equal(t, 204, w.Code) + assert.Equal(t, "", w.Body.String()) + assert.Equal(t, w.HeaderMap.Get("Content-Type"), "application/json; charset=utf-8") +} + // Tests that the response executes the templates // and responds with Content-Type set to text/html func TestContextRenderHTML(t *testing.T) { @@ -436,6 +503,20 @@ func TestContextRenderHTML(t *testing.T) { assert.Equal(t, w.HeaderMap.Get("Content-Type"), "text/html; charset=utf-8") } +// Tests that no HTML is rendered if code is 204 +func TestContextRenderNoContentHTML(t *testing.T) { + w := httptest.NewRecorder() + c, router := CreateTestContext(w) + templ := template.Must(template.New("t").Parse(`Hello {{.name}}`)) + router.SetHTMLTemplate(templ) + + c.HTML(204, "t", H{"name": "alexandernyquist"}) + + assert.Equal(t, 204, w.Code) + assert.Equal(t, "", w.Body.String()) + assert.Equal(t, w.HeaderMap.Get("Content-Type"), "text/html; charset=utf-8") +} + // TestContextXML tests that the response is serialized as XML // and Content-Type is set to application/xml func TestContextRenderXML(t *testing.T) { @@ -449,6 +530,18 @@ func TestContextRenderXML(t *testing.T) { assert.Equal(t, w.HeaderMap.Get("Content-Type"), "application/xml; charset=utf-8") } +// Tests that no XML is rendered if code is 204 +func TestContextRenderNoContentXML(t *testing.T) { + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + + c.XML(204, H{"foo": "bar"}) + + assert.Equal(t, 204, w.Code) + assert.Equal(t, "", w.Body.String()) + assert.Equal(t, w.HeaderMap.Get("Content-Type"), "application/xml; charset=utf-8") +} + // TestContextString tests that the response is returned // with Content-Type set to text/plain func TestContextRenderString(t *testing.T) { @@ -462,6 +555,18 @@ func TestContextRenderString(t *testing.T) { assert.Equal(t, w.HeaderMap.Get("Content-Type"), "text/plain; charset=utf-8") } +// Tests that no String is rendered if code is 204 +func TestContextRenderNoContentString(t *testing.T) { + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + + c.String(204, "test %s %d", "string", 2) + + assert.Equal(t, 204, w.Code) + assert.Equal(t, "", w.Body.String()) + assert.Equal(t, w.HeaderMap.Get("Content-Type"), "text/plain; charset=utf-8") +} + // TestContextString tests that the response is returned // with Content-Type set to text/html func TestContextRenderHTMLString(t *testing.T) { @@ -476,6 +581,19 @@ func TestContextRenderHTMLString(t *testing.T) { assert.Equal(t, w.HeaderMap.Get("Content-Type"), "text/html; charset=utf-8") } +// Tests that no HTML String is rendered if code is 204 +func TestContextRenderNoContentHTMLString(t *testing.T) { + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + + c.Header("Content-Type", "text/html; charset=utf-8") + c.String(204, "%s %d", "string", 3) + + assert.Equal(t, 204, w.Code) + assert.Equal(t, "", w.Body.String()) + assert.Equal(t, w.HeaderMap.Get("Content-Type"), "text/html; charset=utf-8") +} + // TestContextData tests that the response can be written from `bytesting` // with specified MIME type func TestContextRenderData(t *testing.T) { @@ -489,6 +607,18 @@ func TestContextRenderData(t *testing.T) { assert.Equal(t, w.HeaderMap.Get("Content-Type"), "text/csv") } +// Tests that no Custom Data is rendered if code is 204 +func TestContextRenderNoContentData(t *testing.T) { + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + + c.Data(204, "text/csv", []byte(`foo,bar`)) + + assert.Equal(t, 204, w.Code) + assert.Equal(t, "", w.Body.String()) + assert.Equal(t, w.HeaderMap.Get("Content-Type"), "text/csv") +} + func TestContextRenderSSE(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) diff --git a/middleware_test.go b/middleware_test.go index 273d003..c77f827 100644 --- a/middleware_test.go +++ b/middleware_test.go @@ -10,8 +10,8 @@ import ( "testing" - "github.com/manucorporat/sse" "github.com/stretchr/testify/assert" + "gopkg.in/gin-contrib/sse.v0" ) func TestMiddlewareGeneralCase(t *testing.T) { diff --git a/render/data.go b/render/data.go index efa75d5..c296042 100644 --- a/render/data.go +++ b/render/data.go @@ -11,10 +11,13 @@ type Data struct { Data []byte } -func (r Data) Render(w http.ResponseWriter) error { - if len(r.ContentType) > 0 { - w.Header()["Content-Type"] = []string{r.ContentType} - } - w.Write(r.Data) - return nil +// Render (Data) writes data with custom ContentType +func (r Data) Render(w http.ResponseWriter) (err error) { + r.WriteContentType(w) + _, err = w.Write(r.Data) + return +} + +func (r Data) WriteContentType(w http.ResponseWriter) { + writeContentType(w, []string{r.ContentType}) } diff --git a/render/html.go b/render/html.go index 8bfb23a..a3cbda6 100644 --- a/render/html.go +++ b/render/html.go @@ -58,9 +58,14 @@ func (r HTMLDebug) loadTemplate() *template.Template { } func (r HTML) Render(w http.ResponseWriter) error { - writeContentType(w, htmlContentType) + r.WriteContentType(w) + if len(r.Name) == 0 { return r.Template.Execute(w, r.Data) } return r.Template.ExecuteTemplate(w, r.Name, r.Data) } + +func (r HTML) WriteContentType(w http.ResponseWriter) { + writeContentType(w, htmlContentType) +} diff --git a/render/json.go b/render/json.go index b652c4c..3ee8b13 100644 --- a/render/json.go +++ b/render/json.go @@ -21,18 +21,15 @@ type ( var jsonContentType = []string{"application/json; charset=utf-8"} -func (r JSON) Render(w http.ResponseWriter) error { - return WriteJSON(w, r.Data) +func (r JSON) Render(w http.ResponseWriter) (err error) { + if err = WriteJSON(w, r.Data); err != nil { + panic(err) + } + return } -func (r IndentedJSON) Render(w http.ResponseWriter) error { +func (r JSON) WriteContentType(w http.ResponseWriter) { writeContentType(w, jsonContentType) - jsonBytes, err := json.MarshalIndent(r.Data, "", " ") - if err != nil { - return err - } - w.Write(jsonBytes) - return nil } func WriteJSON(w http.ResponseWriter, obj interface{}) error { @@ -44,3 +41,17 @@ func WriteJSON(w http.ResponseWriter, obj interface{}) error { w.Write(jsonBytes) return nil } + +func (r IndentedJSON) Render(w http.ResponseWriter) error { + r.WriteContentType(w) + jsonBytes, err := json.MarshalIndent(r.Data, "", " ") + if err != nil { + return err + } + w.Write(jsonBytes) + return nil +} + +func (r IndentedJSON) WriteContentType(w http.ResponseWriter) { + writeContentType(w, jsonContentType) +} diff --git a/render/redirect.go b/render/redirect.go index bd48d7d..f874a35 100644 --- a/render/redirect.go +++ b/render/redirect.go @@ -22,3 +22,5 @@ func (r Redirect) Render(w http.ResponseWriter) error { http.Redirect(w, r.Request, r.Location, r.Code) return nil } + +func (r Redirect) WriteContentType(http.ResponseWriter) {} diff --git a/render/render.go b/render/render.go index 3808666..7e99737 100644 --- a/render/render.go +++ b/render/render.go @@ -8,6 +8,7 @@ import "net/http" type Render interface { Render(http.ResponseWriter) error + WriteContentType(w http.ResponseWriter) } var ( diff --git a/render/text.go b/render/text.go index 5a9e280..74cd26b 100644 --- a/render/text.go +++ b/render/text.go @@ -22,9 +22,12 @@ func (r String) Render(w http.ResponseWriter) error { return nil } +func (r String) WriteContentType(w http.ResponseWriter) { + writeContentType(w, plainContentType) +} + func WriteString(w http.ResponseWriter, format string, data []interface{}) { writeContentType(w, plainContentType) - if len(data) > 0 { fmt.Fprintf(w, format, data...) } else { diff --git a/render/xml.go b/render/xml.go index be22e6f..cff1ac3 100644 --- a/render/xml.go +++ b/render/xml.go @@ -16,6 +16,10 @@ type XML struct { var xmlContentType = []string{"application/xml; charset=utf-8"} func (r XML) Render(w http.ResponseWriter) error { - writeContentType(w, xmlContentType) + r.WriteContentType(w) return xml.NewEncoder(w).Encode(r.Data) } + +func (r XML) WriteContentType(w http.ResponseWriter) { + writeContentType(w, xmlContentType) +} diff --git a/render/yaml.go b/render/yaml.go index 46937d8..25d0ebd 100644 --- a/render/yaml.go +++ b/render/yaml.go @@ -17,7 +17,7 @@ type YAML struct { var yamlContentType = []string{"application/x-yaml; charset=utf-8"} func (r YAML) Render(w http.ResponseWriter) error { - writeContentType(w, yamlContentType) + r.WriteContentType(w) bytes, err := yaml.Marshal(r.Data) if err != nil { @@ -27,3 +27,7 @@ func (r YAML) Render(w http.ResponseWriter) error { w.Write(bytes) return nil } + +func (r YAML) WriteContentType(w http.ResponseWriter) { + writeContentType(w, yamlContentType) +} diff --git a/vendor/vendor.json b/vendor/vendor.json index 3c6b0ed..e754a3f 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -21,12 +21,6 @@ "revision": "8ee79997227bf9b34611aee7946ae64735e6fd93", "revisionTime": "2016-11-17T03:31:26Z" }, - { - "checksumSHA1": "b0T0Hzd+zYk+OCDTFMps+jwa/nY=", - "path": "github.com/manucorporat/sse", - "revision": "ee05b128a739a0fb76c7ebd3ae4810c1de808d6d", - "revisionTime": "2016-01-26T18:01:36Z" - }, { "checksumSHA1": "9if9IBLsxkarJ804NPWAzgskIAk=", "path": "github.com/manucorporat/stats", @@ -66,6 +60,12 @@ "revision": "478fcf54317e52ab69f40bb4c7a1520288d7f7ea", "revisionTime": "2016-12-05T15:46:50Z" }, + { + "checksumSHA1": "pyAPYrymvmZl0M/Mr4yfjOQjA8I=", + "path": "gopkg.in/gin-contrib/sse.v0", + "revision": "22d885f9ecc78bf4ee5d72b937e4bbcdc58e8cae", + "revisionTime": "2017-01-09T09:34:21Z" + }, { "checksumSHA1": "39V1idWER42Lmcmg2Uy40wMzOlo=", "comment": "v8.18.1", From 2d8477fc427b4864194c0221a1c16b2659d8207f Mon Sep 17 00:00:00 2001 From: mbesancon Date: Wed, 1 Feb 2017 15:47:50 +0100 Subject: [PATCH 28/95] Fixed typos in Context (#797) Simple english typos in the Copy() method --- context.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/context.go b/context.go index e937a4c..601754f 100644 --- a/context.go +++ b/context.go @@ -68,7 +68,7 @@ func (c *Context) reset() { } // Copy returns a copy of the current context that can be safely used outside the request's scope. -// This have to be used then the context has to be passed to a goroutine. +// This has to be used when the context has to be passed to a goroutine. func (c *Context) Copy() *Context { var cp = *c cp.writermem.ResponseWriter = nil From 2ae8a25fc93067526eaa8d6cdf2b9a276a044986 Mon Sep 17 00:00:00 2001 From: Franz Bettag Date: Fri, 10 Feb 2017 15:44:55 +0100 Subject: [PATCH 30/95] Added HandleContext to re-enter soft-rewritten Requests. (#709) --- gin.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/gin.go b/gin.go index d084f2a..1205409 100644 --- a/gin.go +++ b/gin.go @@ -273,6 +273,15 @@ func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) { engine.pool.Put(c) } +// Re-enter a context that has been rewritten. +// This can be done by setting c.Request.Path to your new target. +// Disclaimer: You can loop yourself to death with this, use wisely. +func (engine *Engine) HandleContext(c *Context) { + c.reset() + engine.handleHTTPRequest(c) + engine.pool.Put(c) +} + func (engine *Engine) handleHTTPRequest(context *Context) { httpMethod := context.Request.Method path := context.Request.URL.Path From 6ce1e86a2715573af0c93ac2c4e4bda8788a0ad9 Mon Sep 17 00:00:00 2001 From: pjgg Date: Tue, 14 Feb 2017 02:11:01 +0100 Subject: [PATCH 31/95] chore(errorHandler):new abortWithStatus method with Json body (#800) --- context.go | 7 +++++++ context_test.go | 30 ++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/context.go b/context.go index 601754f..b4f2395 100644 --- a/context.go +++ b/context.go @@ -120,6 +120,13 @@ func (c *Context) AbortWithStatus(code int) { c.Abort() } +// AbortWithStatusJSON calls `Abort()` and then `JSON` internally. This method stops the chain, writes the status code and return a JSON body +// It also sets the Content-Type as "application/json". +func (c *Context) AbortWithStatusJSON(code int, jsonObj interface{}) { + c.Abort() + c.JSON(code, jsonObj) +} + // AbortWithError calls `AbortWithStatus()` and `Error()` internally. This method stops the chain, writes the status code and // pushes the specified error to `c.Errors`. // See Context.Error() for more details. diff --git a/context_test.go b/context_test.go index fd8ca4a..8d59f6e 100644 --- a/context_test.go +++ b/context_test.go @@ -788,6 +788,36 @@ func TestContextAbortWithStatus(t *testing.T) { assert.True(t, c.IsAborted()) } +type testJSONAbortMsg struct { + Foo string `json:"foo"` + Bar string `json:"bar"` +} + +func TestContextAbortWithStatusJSON(t *testing.T) { + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + c.index = 4 + + in := new(testJSONAbortMsg) + in.Bar = "barValue" + in.Foo = "fooValue" + + c.AbortWithStatusJSON(415, in) + + assert.Equal(t, c.index, abortIndex) + assert.Equal(t, c.Writer.Status(), 415) + assert.Equal(t, w.Code, 415) + assert.True(t, c.IsAborted()) + + contentType := w.Header().Get("Content-Type") + assert.Equal(t, contentType, "application/json; charset=utf-8") + + buf := new(bytes.Buffer) + buf.ReadFrom(w.Body) + jsonStringBody := buf.String() + assert.Equal(t, fmt.Sprint(`{"foo":"fooValue","bar":"barValue"}`), jsonStringBody) +} + func TestContextError(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) assert.Empty(t, c.Errors) From 7fcc6088c1756f28c0ad4f21c4f7363253df6b7f Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Thu, 16 Feb 2017 09:27:10 +0800 Subject: [PATCH 32/95] Support testing on latest version of golang. --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index e6a05bd..1e5976f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,8 @@ language: go sudo: false go: - - 1.6.4 - - 1.7.4 + - 1.6.x + - 1.7.x - tip git: From d94ee48fb7a4e7afd822ffc77d1d62a3f69fc18b Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Fri, 17 Feb 2017 16:38:56 +0800 Subject: [PATCH 33/95] Add go 1.8.x testing on travis (#806) --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 1e5976f..9fb5994 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,6 +3,7 @@ sudo: false go: - 1.6.x - 1.7.x + - 1.8.x - tip git: From 863248034b46c8292bf53681730fa033292710fa Mon Sep 17 00:00:00 2001 From: Andrey Nering Date: Fri, 17 Feb 2017 11:32:36 -0200 Subject: [PATCH 34/95] Support time.Time on form binding (#801) --- binding/form_mapping.go | 27 +++++++++++++++++++++++++++ context_test.go | 20 +++++++++++++++----- 2 files changed, 42 insertions(+), 5 deletions(-) diff --git a/binding/form_mapping.go b/binding/form_mapping.go index 07c8375..bc9e44c 100644 --- a/binding/form_mapping.go +++ b/binding/form_mapping.go @@ -8,6 +8,7 @@ import ( "errors" "reflect" "strconv" + "time" ) func mapForm(ptr interface{}, form map[string][]string) error { @@ -52,6 +53,12 @@ func mapForm(ptr interface{}, form map[string][]string) error { } val.Field(i).Set(slice) } else { + if _, isTime := structField.Interface().(time.Time); isTime { + if err := setTimeField(inputValue[0], typeField, structField); err != nil { + return err + } + continue + } if err := setWithProperType(typeField.Type.Kind(), inputValue[0], structField); err != nil { return err } @@ -140,6 +147,26 @@ func setFloatField(val string, bitSize int, field reflect.Value) error { return err } +func setTimeField(val string, structField reflect.StructField, value reflect.Value) error { + timeFormat := structField.Tag.Get("time_format") + if timeFormat == "" { + return errors.New("Blank time format") + } + + l := time.Local + if isUTC, _ := strconv.ParseBool(structField.Tag.Get("time_utc")); isUTC { + l = time.UTC + } + + t, err := time.ParseInLocation(timeFormat, val, l) + if err != nil { + return err + } + + value.Set(reflect.ValueOf(t)) + return nil +} + // Don't pass in pointers to bind to. Can lead to bugs. See: // https://github.com/codegangsta/martini-contrib/issues/40 // https://github.com/codegangsta/martini-contrib/pull/34#issuecomment-29683659 diff --git a/context_test.go b/context_test.go index 8d59f6e..ebc5050 100644 --- a/context_test.go +++ b/context_test.go @@ -42,6 +42,8 @@ func createMultipartRequest() *http.Request { must(mw.WriteField("array", "first")) must(mw.WriteField("array", "second")) must(mw.WriteField("id", "")) + must(mw.WriteField("time_local", "31/12/2016 14:55")) + must(mw.WriteField("time_utc", "31/12/2016 14:55")) req, err := http.NewRequest("POST", "/", body) must(err) req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary) @@ -309,11 +311,14 @@ func TestContextPostFormMultipart(t *testing.T) { c.Request = createMultipartRequest() var obj struct { - Foo string `form:"foo"` - Bar string `form:"bar"` - BarAsInt int `form:"bar"` - Array []string `form:"array"` - ID string `form:"id"` + Foo string `form:"foo"` + Bar string `form:"bar"` + BarAsInt int `form:"bar"` + Array []string `form:"array"` + ID string `form:"id"` + TimeLocal time.Time `form:"time_local" time_format:"02/01/2006 15:04"` + TimeUTC time.Time `form:"time_utc" time_format:"02/01/2006 15:04" time_utc:"1"` + BlankTime time.Time `form:"blank_time" time_format:"02/01/2006 15:04"` } assert.NoError(t, c.Bind(&obj)) assert.Equal(t, obj.Foo, "bar") @@ -321,6 +326,11 @@ func TestContextPostFormMultipart(t *testing.T) { assert.Equal(t, obj.BarAsInt, 10) assert.Equal(t, obj.Array, []string{"first", "second"}) assert.Equal(t, obj.ID, "") + assert.Equal(t, obj.TimeLocal.Format("02/01/2006 15:04"), "31/12/2016 14:55") + assert.Equal(t, obj.TimeLocal.Location(), time.Local) + assert.Equal(t, obj.TimeUTC.Format("02/01/2006 15:04"), "31/12/2016 14:55") + assert.Equal(t, obj.TimeUTC.Location(), time.UTC) + assert.True(t, obj.BlankTime.IsZero()) value, ok := c.GetQuery("foo") assert.False(t, ok) From 5be2123c1a4407f41062d33500db4de65e05bab1 Mon Sep 17 00:00:00 2001 From: Harindu Perera Date: Thu, 23 Feb 2017 15:08:37 +0100 Subject: [PATCH 35/95] Added support for MessagePack binding and rendering (#808) Added deps to vendor.json and fixed rendering bug --- binding/binding.go | 5 +++++ binding/binding_test.go | 45 +++++++++++++++++++++++++++++++++++++++-- binding/msgpack.go | 28 +++++++++++++++++++++++++ render/msgpack.go | 31 ++++++++++++++++++++++++++++ render/render.go | 2 ++ render/render_test.go | 23 +++++++++++++++++++++ vendor/vendor.json | 6 ++++++ 7 files changed, 138 insertions(+), 2 deletions(-) create mode 100644 binding/msgpack.go create mode 100644 render/msgpack.go diff --git a/binding/binding.go b/binding/binding.go index dc7397f..d3a2c97 100644 --- a/binding/binding.go +++ b/binding/binding.go @@ -15,6 +15,8 @@ const ( MIMEPOSTForm = "application/x-www-form-urlencoded" MIMEMultipartPOSTForm = "multipart/form-data" MIMEPROTOBUF = "application/x-protobuf" + MIMEMSGPACK = "application/x-msgpack" + MIMEMSGPACK2 = "application/msgpack" ) type Binding interface { @@ -40,6 +42,7 @@ var ( FormPost = formPostBinding{} FormMultipart = formMultipartBinding{} ProtoBuf = protobufBinding{} + MsgPack = msgpackBinding{} ) func Default(method, contentType string) Binding { @@ -53,6 +56,8 @@ func Default(method, contentType string) Binding { return XML case MIMEPROTOBUF: return ProtoBuf + case MIMEMSGPACK, MIMEMSGPACK2: + return MsgPack default: //case MIMEPOSTForm, MIMEMultipartPOSTForm: return Form } diff --git a/binding/binding_test.go b/binding/binding_test.go index 72f6015..cf00594 100644 --- a/binding/binding_test.go +++ b/binding/binding_test.go @@ -12,17 +12,18 @@ import ( "github.com/gin-gonic/gin/binding/example" "github.com/golang/protobuf/proto" + "github.com/ugorji/go/codec" "github.com/stretchr/testify/assert" ) type FooStruct struct { - Foo string `json:"foo" form:"foo" xml:"foo" binding:"required"` + Foo string `msgpack:"foo" json:"foo" form:"foo" xml:"foo" binding:"required"` } type FooBarStruct struct { FooStruct - Bar string `json:"bar" form:"bar" xml:"bar" binding:"required"` + Bar string `msgpack:"bar" json:"bar" form:"bar" xml:"bar" binding:"required"` } func TestBindingDefault(t *testing.T) { @@ -43,6 +44,9 @@ func TestBindingDefault(t *testing.T) { assert.Equal(t, Default("POST", MIMEPROTOBUF), ProtoBuf) assert.Equal(t, Default("PUT", MIMEPROTOBUF), ProtoBuf) + + assert.Equal(t, Default("POST", MIMEMSGPACK), MsgPack) + assert.Equal(t, Default("PUT", MIMEMSGPACK2), MsgPack) } func TestBindingJSON(t *testing.T) { @@ -121,6 +125,26 @@ func TestBindingProtoBuf(t *testing.T) { string(data), string(data[1:])) } +func TestBindingMsgPack(t *testing.T) { + test := FooStruct{ + Foo: "bar", + } + + h := new(codec.MsgpackHandle) + assert.NotNil(t, h) + buf := bytes.NewBuffer([]byte{}) + assert.NotNil(t, buf) + err := codec.NewEncoder(buf, h).Encode(test) + assert.NoError(t, err) + + data := buf.Bytes() + + testMsgPackBodyBinding(t, + MsgPack, "msgpack", + "/", "/", + string(data), string(data[1:])) +} + func TestValidationFails(t *testing.T) { var obj FooStruct req := requestWithBody("POST", "/", `{"bar": "foo"}`) @@ -213,6 +237,23 @@ func testProtoBodyBinding(t *testing.T, b Binding, name, path, badPath, body, ba assert.Error(t, err) } +func testMsgPackBodyBinding(t *testing.T, b Binding, name, path, badPath, body, badBody string) { + assert.Equal(t, b.Name(), name) + + obj := FooStruct{} + req := requestWithBody("POST", path, body) + req.Header.Add("Content-Type", MIMEMSGPACK) + err := b.Bind(req, &obj) + assert.NoError(t, err) + assert.Equal(t, obj.Foo, "bar") + + obj = FooStruct{} + req = requestWithBody("POST", badPath, badBody) + req.Header.Add("Content-Type", MIMEMSGPACK) + err = MsgPack.Bind(req, &obj) + assert.Error(t, err) +} + func requestWithBody(method, path, body string) (req *http.Request) { req, _ = http.NewRequest(method, path, bytes.NewBufferString(body)) return diff --git a/binding/msgpack.go b/binding/msgpack.go new file mode 100644 index 0000000..6936717 --- /dev/null +++ b/binding/msgpack.go @@ -0,0 +1,28 @@ +// Copyright 2017 Manu Martinez-Almeida. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package binding + +import ( + "net/http" + + "github.com/ugorji/go/codec" +) + +type msgpackBinding struct{} + +func (msgpackBinding) Name() string { + return "msgpack" +} + +func (msgpackBinding) Bind(req *http.Request, obj interface{}) error { + + if err := codec.NewDecoder(req.Body, new(codec.MsgpackHandle)).Decode(&obj); err != nil { + //var decoder *codec.Decoder = codec.NewDecoder(req.Body, &codec.MsgpackHandle) + //if err := decoder.Decode(&obj); err != nil { + return err + } + return validate(obj) + +} diff --git a/render/msgpack.go b/render/msgpack.go new file mode 100644 index 0000000..e6c13e5 --- /dev/null +++ b/render/msgpack.go @@ -0,0 +1,31 @@ +// Copyright 2017 Manu Martinez-Almeida. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package render + +import ( + "net/http" + + "github.com/ugorji/go/codec" +) + +type MsgPack struct { + Data interface{} +} + +var msgpackContentType = []string{"application/msgpack; charset=utf-8"} + +func (r MsgPack) WriteContentType(w http.ResponseWriter) { + writeContentType(w, msgpackContentType) +} + +func (r MsgPack) Render(w http.ResponseWriter) error { + return WriteMsgPack(w, r.Data) +} + +func WriteMsgPack(w http.ResponseWriter, obj interface{}) error { + writeContentType(w, msgpackContentType) + var h codec.Handle = new(codec.MsgpackHandle) + return codec.NewEncoder(w, h).Encode(obj) +} diff --git a/render/render.go b/render/render.go index 7e99737..4629142 100644 --- a/render/render.go +++ b/render/render.go @@ -22,6 +22,8 @@ var ( _ HTMLRender = HTMLDebug{} _ HTMLRender = HTMLProduction{} _ Render = YAML{} + _ Render = MsgPack{} + _ Render = MsgPack{} ) func writeContentType(w http.ResponseWriter, value []string) { diff --git a/render/render_test.go b/render/render_test.go index c814ff6..c48235c 100644 --- a/render/render_test.go +++ b/render/render_test.go @@ -5,17 +5,40 @@ package render import ( + "bytes" "encoding/xml" "html/template" "net/http/httptest" "testing" "github.com/stretchr/testify/assert" + "github.com/ugorji/go/codec" ) // TODO unit tests // test errors +func TestRenderMsgPack(t *testing.T) { + w := httptest.NewRecorder() + data := map[string]interface{}{ + "foo": "bar", + } + + err := (MsgPack{data}).Render(w) + + assert.NoError(t, err) + + h := new(codec.MsgpackHandle) + assert.NotNil(t, h) + buf := bytes.NewBuffer([]byte{}) + assert.NotNil(t, buf) + err = codec.NewEncoder(buf, h).Encode(data) + + assert.NoError(t, err) + assert.Equal(t, w.Body.String(), string(buf.Bytes())) + assert.Equal(t, w.Header().Get("Content-Type"), "application/msgpack; charset=utf-8") +} + func TestRenderJSON(t *testing.T) { w := httptest.NewRecorder() data := map[string]interface{}{ diff --git a/vendor/vendor.json b/vendor/vendor.json index e754a3f..00115c5 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -47,6 +47,12 @@ "revision": "976c720a22c8eb4eb6a0b4348ad85ad12491a506", "revisionTime": "2016-09-25T22:06:09Z" }, + { + "checksumSHA1": "CoxdaTYdPZNJXr8mJfLxye428N0=", + "path": "github.com/ugorji/go/codec", + "revision": "c88ee250d0221a57af388746f5cf03768c21d6e2", + "revisionTime": "2017-02-15T20:11:44Z" + }, { "checksumSHA1": "9jjO5GjLa0XF/nfWihF02RoH4qc=", "comment": "release-branch.go1.7", From a47a8ca6e778dcdc2932107127df49447733c676 Mon Sep 17 00:00:00 2001 From: Manu MA Date: Sun, 26 Feb 2017 20:03:05 +0100 Subject: [PATCH 36/95] docs(README): fixes the markdown code format --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index c940a10..7046b98 100644 --- a/README.md +++ b/README.md @@ -103,11 +103,12 @@ BenchmarkZeus_GithubAll | 2000 | 944234 | 300688 | 2648 ``` 4. (Optional) Use latest changes (note: they may be broken and/or unstable): -    ```sh + + ```sh $ GIN_PATH=$GOPATH/src/gopkg.in/gin-gonic/gin.v1 $ git -C $GIN_PATH checkout develop - $ git -C $GIN_PATH pull origin develop -    ``` + $ git -C $GIN_PATH pull origin develop +``` ## API Examples From f4dec22c5097ba3d7635363b0a8d59766cb8aae6 Mon Sep 17 00:00:00 2001 From: cssivision Date: Tue, 28 Feb 2017 15:15:52 +0800 Subject: [PATCH 37/95] fix: buffer should reset when test logger output (#819) --- logger_test.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/logger_test.go b/logger_test.go index 6adf86b..15d6ee9 100644 --- a/logger_test.go +++ b/logger_test.go @@ -36,37 +36,43 @@ func TestLogger(t *testing.T) { // 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") @@ -129,6 +135,7 @@ func TestSkippingPaths(t *testing.T) { performRequest(router, "GET", "/logged") assert.Contains(t, buffer.String(), "200") + buffer.Reset() performRequest(router, "GET", "/skipped") assert.Contains(t, buffer.String(), "") } From b1872ec3697b9a61c2a5135ac706094b18e355e5 Mon Sep 17 00:00:00 2001 From: Sergey Egorov Date: Tue, 28 Feb 2017 11:29:41 +0100 Subject: [PATCH 38/95] The url.RawPath used when engine.UseRawPath is set to true. (#810) --- gin.go | 25 +++++++++++++++++++--- routes_test.go | 39 +++++++++++++++++++++++++++++++++++ tree.go | 22 +++++++++++++++++--- tree_test.go | 56 ++++++++++++++++++++++++++++++++++++++++++++------ 4 files changed, 130 insertions(+), 12 deletions(-) diff --git a/gin.go b/gin.go index 1205409..61ac5c0 100644 --- a/gin.go +++ b/gin.go @@ -83,6 +83,13 @@ type ( // #726 #755 If enabled, it will thrust some headers starting with // 'X-AppEngine...' for better integration with that PaaS. AppEngine bool + + // If enabled, the url.RawPath will be used to find parameters. + UseRawPath bool + // If true, the path value will be unescaped. + // If UseRawPath is false (by default), the UnescapePathValues effectively is true, + // as url.Path gonna be used, which is already unescaped. + UnescapePathValues bool } ) @@ -94,6 +101,8 @@ var _ IRouter = &Engine{} // - RedirectFixedPath: false // - HandleMethodNotAllowed: false // - ForwardedByClientIP: true +// - UseRawPath: false +// - UnescapePathValues: true func New() *Engine { debugPrintWARNINGNew() engine := &Engine{ @@ -107,6 +116,8 @@ func New() *Engine { HandleMethodNotAllowed: false, ForwardedByClientIP: true, AppEngine: defaultAppEngine, + UseRawPath: false, + UnescapePathValues: true, trees: make(methodTrees, 0, 9), } engine.RouterGroup.engine = engine @@ -284,7 +295,15 @@ func (engine *Engine) HandleContext(c *Context) { func (engine *Engine) handleHTTPRequest(context *Context) { httpMethod := context.Request.Method - path := context.Request.URL.Path + var path string + var unescape bool + if engine.UseRawPath && len(context.Request.URL.RawPath) > 0 { + path = context.Request.URL.RawPath + unescape = engine.UnescapePathValues + } else { + path = context.Request.URL.Path + unescape = false + } // Find root of the tree for the given HTTP method t := engine.trees @@ -292,7 +311,7 @@ func (engine *Engine) handleHTTPRequest(context *Context) { if t[i].method == httpMethod { root := t[i].root // Find route in tree - handlers, params, tsr := root.getValue(path, context.Params) + handlers, params, tsr := root.getValue(path, context.Params, unescape) if handlers != nil { context.handlers = handlers context.Params = params @@ -317,7 +336,7 @@ func (engine *Engine) handleHTTPRequest(context *Context) { if engine.HandleMethodNotAllowed { for _, tree := range engine.trees { if tree.method != httpMethod { - if handlers, _, _ := tree.root.getValue(path, nil); handlers != nil { + if handlers, _, _ := tree.root.getValue(path, nil, unescape); handlers != nil { context.handlers = engine.allNoMethod serveError(context, 405, default405Body) return diff --git a/routes_test.go b/routes_test.go index 32f0098..7464d5d 100644 --- a/routes_test.go +++ b/routes_test.go @@ -400,3 +400,42 @@ func TestRouterNotFound(t *testing.T) { w = performRequest(router, "GET", "/") assert.Equal(t, w.Code, 404) } + +func TestRouteRawPath(t *testing.T) { + route := New() + route.UseRawPath = true + + route.POST("/project/:name/build/:num", func(c *Context) { + name := c.Params.ByName("name") + num := c.Params.ByName("num") + + assert.Equal(t, c.Param("name"), name) + assert.Equal(t, c.Param("num"), num) + + assert.Equal(t, "Some/Other/Project", name) + assert.Equal(t, "222", num) + }) + + w := performRequest(route, "POST", "/project/Some%2FOther%2FProject/build/222") + assert.Equal(t, w.Code, 200) +} + +func TestRouteRawPathNoUnescape(t *testing.T) { + route := New() + route.UseRawPath = true + route.UnescapePathValues = false + + route.POST("/project/:name/build/:num", func(c *Context) { + name := c.Params.ByName("name") + num := c.Params.ByName("num") + + assert.Equal(t, c.Param("name"), name) + assert.Equal(t, c.Param("num"), num) + + assert.Equal(t, "Some%2FOther%2FProject", name) + assert.Equal(t, "333", num) + }) + + w := performRequest(route, "POST", "/project/Some%2FOther%2FProject/build/333") + assert.Equal(t, w.Code, 200) +} diff --git a/tree.go b/tree.go index 4f1da27..eee6bab 100644 --- a/tree.go +++ b/tree.go @@ -5,6 +5,7 @@ package gin import ( + "net/url" "strings" "unicode" ) @@ -363,7 +364,7 @@ func (n *node) insertChild(numParams uint8, path string, fullPath string, handle // If no handle can be found, a TSR (trailing slash redirect) recommendation is // made if a handle exists with an extra (without the) trailing slash for the // given path. -func (n *node) getValue(path string, po Params) (handlers HandlersChain, p Params, tsr bool) { +func (n *node) getValue(path string, po Params, unescape bool) (handlers HandlersChain, p Params, tsr bool) { p = po walk: // Outer loop for walking the tree for { @@ -406,7 +407,15 @@ walk: // Outer loop for walking the tree i := len(p) p = p[:i+1] // expand slice within preallocated capacity p[i].Key = n.path[1:] - p[i].Value = path[:end] + val := path[:end] + if unescape { + var err error + if p[i].Value, err = url.QueryUnescape(val); err != nil { + p[i].Value = val // fallback, in case of error + } + } else { + p[i].Value = val + } // we need to go deeper! if end < len(path) { @@ -440,7 +449,14 @@ walk: // Outer loop for walking the tree i := len(p) p = p[:i+1] // expand slice within preallocated capacity p[i].Key = n.path[2:] - p[i].Value = path + if unescape { + var err error + if p[i].Value, err = url.QueryUnescape(path); err != nil { + p[i].Value = path // fallback, in case of error + } + } else { + p[i].Value = path + } handlers = n.handlers return diff --git a/tree_test.go b/tree_test.go index ed21783..22f0131 100644 --- a/tree_test.go +++ b/tree_test.go @@ -37,9 +37,14 @@ type testRequests []struct { ps Params } -func checkRequests(t *testing.T, tree *node, requests testRequests) { +func checkRequests(t *testing.T, tree *node, requests testRequests, unescapes ...bool) { + unescape := false + if len(unescapes) >= 1 { + unescape = unescapes[0] + } + for _, request := range requests { - handler, ps, _ := tree.getValue(request.path, nil) + handler, ps, _ := tree.getValue(request.path, nil, unescape) if handler == nil { if !request.nilHandler { @@ -197,6 +202,45 @@ func TestTreeWildcard(t *testing.T) { checkMaxParams(t, tree) } +func TestUnescapeParameters(t *testing.T) { + tree := &node{} + + routes := [...]string{ + "/", + "/cmd/:tool/:sub", + "/cmd/:tool/", + "/src/*filepath", + "/search/:query", + "/files/:dir/*filepath", + "/info/:user/project/:project", + "/info/:user", + } + for _, route := range routes { + tree.addRoute(route, fakeHandler(route)) + } + + //printChildren(tree, "") + unescape := true + checkRequests(t, tree, testRequests{ + {"/", false, "/", nil}, + {"/cmd/test/", false, "/cmd/:tool/", Params{Param{"tool", "test"}}}, + {"/cmd/test", true, "", Params{Param{"tool", "test"}}}, + {"/src/some/file.png", false, "/src/*filepath", Params{Param{"filepath", "/some/file.png"}}}, + {"/src/some/file+test.png", false, "/src/*filepath", Params{Param{"filepath", "/some/file test.png"}}}, + {"/src/some/file++++%%%%test.png", false, "/src/*filepath", Params{Param{"filepath", "/some/file++++%%%%test.png"}}}, + {"/src/some/file%2Ftest.png", false, "/src/*filepath", Params{Param{"filepath", "/some/file/test.png"}}}, + {"/search/someth!ng+in+ünìcodé", false, "/search/:query", Params{Param{"query", "someth!ng in ünìcodé"}}}, + {"/info/gordon/project/go", false, "/info/:user/project/:project", Params{Param{"user", "gordon"}, Param{"project", "go"}}}, + {"/info/slash%2Fgordon", false, "/info/:user", Params{Param{"user", "slash/gordon"}}}, + {"/info/slash%2Fgordon/project/Project%20%231", false, "/info/:user/project/:project", Params{Param{"user", "slash/gordon"}, Param{"project", "Project #1"}}}, + {"/info/slash%%%%", false, "/info/:user", Params{Param{"user", "slash%%%%"}}}, + {"/info/slash%%%%2Fgordon/project/Project%%%%20%231", false, "/info/:user/project/:project", Params{Param{"user", "slash%%%%2Fgordon"}, Param{"project", "Project%%%%20%231"}}}, + }, unescape) + + checkPriorities(t, tree) + checkMaxParams(t, tree) +} + func catchPanic(testFunc func()) (recv interface{}) { defer func() { recv = recover() @@ -430,7 +474,7 @@ func TestTreeTrailingSlashRedirect(t *testing.T) { "/doc/", } for _, route := range tsrRoutes { - handler, _, tsr := tree.getValue(route, nil) + handler, _, tsr := tree.getValue(route, nil, false) if handler != nil { t.Fatalf("non-nil handler for TSR route '%s", route) } else if !tsr { @@ -447,7 +491,7 @@ func TestTreeTrailingSlashRedirect(t *testing.T) { "/api/world/abc", } for _, route := range noTsrRoutes { - handler, _, tsr := tree.getValue(route, nil) + handler, _, tsr := tree.getValue(route, nil, false) if handler != nil { t.Fatalf("non-nil handler for No-TSR route '%s", route) } else if tsr { @@ -466,7 +510,7 @@ func TestTreeRootTrailingSlashRedirect(t *testing.T) { t.Fatalf("panic inserting test route: %v", recv) } - handler, _, tsr := tree.getValue("/", nil) + handler, _, tsr := tree.getValue("/", nil, false) if handler != nil { t.Fatalf("non-nil handler") } else if tsr { @@ -617,7 +661,7 @@ func TestTreeInvalidNodeType(t *testing.T) { // normal lookup recv := catchPanic(func() { - tree.getValue("/test", nil) + tree.getValue("/test", nil, false) }) if rs, ok := recv.(string); !ok || rs != panicMsg { t.Fatalf("Expected panic '"+panicMsg+"', got '%v'", recv) From 38cc731224fddfe8646d67bbced34510c42b2695 Mon Sep 17 00:00:00 2001 From: Sergey Gonimar Date: Wed, 1 Mar 2017 13:42:59 +0500 Subject: [PATCH 39/95] fix typo (#822) --- fs.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs.go b/fs.go index 6af3ded..320fea6 100644 --- a/fs.go +++ b/fs.go @@ -14,7 +14,7 @@ type ( } ) -// Dir returns a http.Filesystem that can be used by http.FileServer(). It is used interally +// Dir returns a http.Filesystem that can be used by http.FileServer(). It is used internally // in router.Static(). // if listDirectory == true, then it works the same as http.Dir() otherwise it returns // a filesystem that prevents http.FileServer() to list the directory files. From b2d7e35a60d16e45d9ea5c0a5feefdf57c244405 Mon Sep 17 00:00:00 2001 From: Rohan Pai Date: Wed, 1 Mar 2017 12:51:13 -0800 Subject: [PATCH 40/95] Added Sourcegraph badge --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 7046b98..ea184bb 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ [![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 Badge](https://sourcegraph.com/github.com/gin-gonic/gin/-/badge.svg)](https://sourcegraph.com/github.com/gin-gonic/gin?badge) Gin is a web framework written in Go (Golang). It features a martini-like API with much better performance, up to 40 times faster thanks to [httprouter](https://github.com/julienschmidt/httprouter). If you need performance and good productivity, you will love Gin. From b1a15020d0aa423019aa64311373601cd9e8d882 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Sat, 11 Mar 2017 07:35:29 -0600 Subject: [PATCH 41/95] fix: gofmt error. (#833) --- mode.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mode.go b/mode.go index c600b7b..e24dbdc 100644 --- a/mode.go +++ b/mode.go @@ -19,9 +19,9 @@ const ( TestMode string = "test" ) const ( - debugCode = iota - releaseCode - testCode + debugCode = iota + releaseCode + testCode ) // DefaultWriter is the default io.Writer used the Gin for debug output and From 28b18cd1fb827dde65a2094d7e57418364eac16c Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Wed, 15 Mar 2017 20:15:41 -0500 Subject: [PATCH 42/95] feat: support cygwin for log writer. (#834) --- .travis.yml | 2 +- logger.go | 4 +++- vendor/vendor.json | 12 ++++++------ 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/.travis.yml b/.travis.yml index 9fb5994..2f9385e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,7 +4,7 @@ go: - 1.6.x - 1.7.x - 1.8.x - - tip + - master git: depth: 3 diff --git a/logger.go b/logger.go index c7cbfe1..dc6f141 100644 --- a/logger.go +++ b/logger.go @@ -54,7 +54,9 @@ func Logger() HandlerFunc { func LoggerWithWriter(out io.Writer, notlogged ...string) HandlerFunc { isTerm := true - if w, ok := out.(*os.File); !ok || !isatty.IsTerminal(w.Fd()) || disableColor { + if w, ok := out.(*os.File); !ok || + (os.Getenv("TERM") == "dumb" || (!isatty.IsTerminal(w.Fd()) && !isatty.IsCygwinTerminal(w.Fd()))) || + disableColor { isTerm = false } diff --git a/vendor/vendor.json b/vendor/vendor.json index 00115c5..2bc5e19 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -28,10 +28,10 @@ "revisionTime": "2015-05-31T20:46:25Z" }, { - "checksumSHA1": "xZuhljnmBysJPta/lMyYmJdujCg=", + "checksumSHA1": "U6lX43KDDlNOn+Z0Yyww+ZzHfFo=", "path": "github.com/mattn/go-isatty", - "revision": "30a891c33c7cde7b02a981314b4228ec99380cca", - "revisionTime": "2016-11-23T14:36:37Z" + "revision": "57fdcb988a5c543893cc61bce354a6e24ab70022", + "revisionTime": "2017-03-07T16:30:44Z" }, { "checksumSHA1": "LuFv4/jlrmFNnDb/5SCSEPAM9vU=", @@ -61,10 +61,10 @@ "revisionTime": "2016-10-18T08:54:36Z" }, { - "checksumSHA1": "8SH0adTcQlA+W5dzqiQ3Hft2VXg=", + "checksumSHA1": "/oZpHfYc+ZgOwYAhlvcMhmETYpw=", "path": "golang.org/x/sys/unix", - "revision": "478fcf54317e52ab69f40bb4c7a1520288d7f7ea", - "revisionTime": "2016-12-05T15:46:50Z" + "revision": "99f16d856c9836c42d24e7ab64ea72916925fa97", + "revisionTime": "2017-03-08T15:04:45Z" }, { "checksumSHA1": "pyAPYrymvmZl0M/Mr4yfjOQjA8I=", From b8be9df6428446e7cc50f1c8e0d84e4c919dc739 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Thu, 16 Mar 2017 10:38:30 -0500 Subject: [PATCH 43/95] docs: add graceful-shutdown example for go 1.8 (#835) * docs: add graceful-shutdown example for go 1.8 * fix testing Signed-off-by: Bo-Yi Wu --- .travis.yml | 2 + AUTHORS.md | 6 +- BENCHMARKS.md | 2 +- README.md | 100 ++++++++++++++---- examples/graceful-shutdown/close/server.go | 45 ++++++++ .../graceful-shutdown/server.go | 48 +++++++++ gin_integration_test.go | 24 ++--- 7 files changed, 191 insertions(+), 36 deletions(-) create mode 100644 examples/graceful-shutdown/close/server.go create mode 100644 examples/graceful-shutdown/graceful-shutdown/server.go diff --git a/.travis.yml b/.travis.yml index 2f9385e..644a178 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,8 +12,10 @@ git: install: - go get -v github.com/kardianos/govendor - govendor sync + - go get -u github.com/campoy/embedmd script: + - embedmd -d README.md - go test -v -covermode=count -coverprofile=coverage.out after_success: diff --git a/AUTHORS.md b/AUTHORS.md index 2feaf46..7ab7213 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -1,8 +1,6 @@ List of all the awesome people working to make Gin the best Web Framework in Go. - - -##gin 0.x series authors +## gin 0.x series authors **Maintainer:** Manu Martinez-Almeida (@manucorporat), Javier Provecho (@javierprovecho) @@ -226,4 +224,4 @@ People and companies, who have contributed, in alphabetical order. **@yuyabee** -- Fixed README \ No newline at end of file +- Fixed README diff --git a/BENCHMARKS.md b/BENCHMARKS.md index 181f75b..6efe3ca 100644 --- a/BENCHMARKS.md +++ b/BENCHMARKS.md @@ -295,4 +295,4 @@ BenchmarkPossum_GPlusAll 100000 19685 ns/op 6240 B/op BenchmarkR2router_GPlusAll 100000 16251 ns/op 5040 B/op 76 allocs/op BenchmarkRevel_GPlusAll 20000 93489 ns/op 21656 B/op 368 allocs/op BenchmarkRivet_GPlusAll 100000 16907 ns/op 5408 B/op 64 allocs/op -``` \ No newline at end of file +``` diff --git a/README.md b/README.md index ea184bb..9bcc8f5 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,4 @@ - -#Gin Web Framework +# Gin Web Framework [![Build Status](https://travis-ci.org/gin-gonic/gin.svg)](https://travis-ci.org/gin-gonic/gin) @@ -16,6 +15,7 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi ```sh $ cat test.go ``` + ```go package main @@ -87,28 +87,28 @@ BenchmarkZeus_GithubAll | 2000 | 944234 | 300688 | 2648 1. Download and install it: - ```sh - $ go get gopkg.in/gin-gonic/gin.v1 - ``` +```sh +$ go get gopkg.in/gin-gonic/gin.v1 +``` 2. Import it in your code: - ```go - import "gopkg.in/gin-gonic/gin.v1" - ``` +```go +import "gopkg.in/gin-gonic/gin.v1" +``` 3. (Optional) Import `net/http`. This is required for example if using constants such as `http.StatusOK`. - ```go - import "net/http" - ``` +```go +import "net/http" +``` 4. (Optional) Use latest changes (note: they may be broken and/or unstable): - ```sh - $ GIN_PATH=$GOPATH/src/gopkg.in/gin-gonic/gin.v1 - $ git -C $GIN_PATH checkout develop - $ git -C $GIN_PATH pull origin develop +```sh +$ GIN_PATH=$GOPATH/src/gopkg.in/gin-gonic/gin.v1 +$ git -C $GIN_PATH checkout develop +$ git -C $GIN_PATH pull origin develop ``` ## API Examples @@ -165,6 +165,7 @@ func main() { ``` ### Querystring parameters + ```go func main() { router := gin.Default() @@ -315,7 +316,6 @@ func main() { } ``` - ### Blank Gin without middleware by default Use @@ -323,6 +323,7 @@ Use ```go r := gin.New() ``` + instead of ```go @@ -450,7 +451,6 @@ func startPage(c *gin.Context) { } ``` - ### Multipart/Urlencoded binding ```go @@ -490,7 +490,6 @@ Test it with: $ curl -v --form user=user --form password=password http://localhost:8080/login ``` - ### XML, JSON and YAML rendering ```go @@ -561,7 +560,9 @@ func main() { router.Run(":8080") } ``` + templates/index.tmpl + ```html

@@ -589,7 +590,9 @@ func main() { router.Run(":8080") } ``` + templates/posts/index.tmpl + ```html {{ define "posts/index.tmpl" }}

@@ -599,7 +602,9 @@ templates/posts/index.tmpl {{ end }} ``` + templates/users/index.tmpl + ```html {{ define "users/index.tmpl" }}

@@ -680,6 +685,7 @@ func main() { ``` ### Using BasicAuth() middleware + ```go // simulate some private data var secrets = gin.H{ @@ -717,8 +723,8 @@ func main() { } ``` - ### Goroutines inside a middleware + When starting inside a middleware or handler, you **SHOULD NOT** use the original context inside it, you have to use a read-only copy. ```go @@ -794,6 +800,62 @@ endless.ListenAndServe(":4242", router) An alternative to endless: * [manners](https://github.com/braintree/manners): A polite Go HTTP server that shuts down gracefully. +* [graceful](https://github.com/tylerb/graceful): Graceful is a Go package enabling graceful shutdown of an http.Handler server. +* [grace](https://github.com/facebookgo/grace): Graceful restart & zero downtime deploy for Go servers. + +If you are using Go 1.8, you may not need to use this library! Consider using http.Server's built-in [Shutdown()](https://golang.org/pkg/net/http/#Server.Shutdown) method for graceful shutdowns. See the full [graceful-shutdown](./examples/graceful-shutdown) example with gin. + +[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 { + log.Printf("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 exist") +} +``` ## Contributing diff --git a/examples/graceful-shutdown/close/server.go b/examples/graceful-shutdown/close/server.go new file mode 100644 index 0000000..5477839 --- /dev/null +++ b/examples/graceful-shutdown/close/server.go @@ -0,0 +1,45 @@ +// +build go1.8 + +package main + +import ( + "log" + "net/http" + "os" + "os/signal" + + "github.com/gin-gonic/gin" +) + +func main() { + router := gin.Default() + router.GET("/", func(c *gin.Context) { + c.String(http.StatusOK, "Welcome Gin Server") + }) + + server := &http.Server{ + Addr: ":8080", + Handler: router, + } + + quit := make(chan os.Signal) + signal.Notify(quit, os.Interrupt) + + go func() { + <-quit + log.Println("receive interrupt signal") + if err := server.Close(); err != nil { + log.Fatal("Server Close:", err) + } + }() + + if err := server.ListenAndServe(); err != nil { + if err == http.ErrServerClosed { + log.Println("Server closed under request") + } else { + log.Fatal("Server closed unexpect") + } + } + + log.Println("Server exist") +} diff --git a/examples/graceful-shutdown/graceful-shutdown/server.go b/examples/graceful-shutdown/graceful-shutdown/server.go new file mode 100644 index 0000000..060de08 --- /dev/null +++ b/examples/graceful-shutdown/graceful-shutdown/server.go @@ -0,0 +1,48 @@ +// +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 { + log.Printf("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 exist") +} diff --git a/gin_integration_test.go b/gin_integration_test.go index 8521697..b4bde1a 100644 --- a/gin_integration_test.go +++ b/gin_integration_test.go @@ -115,17 +115,17 @@ func TestWithHttptestWithAutoSelectedPort(t *testing.T) { testRequest(t, ts.URL+"/example") } -func TestWithHttptestWithSpecifiedPort(t *testing.T) { - router := New() - router.GET("/example", func(c *Context) { c.String(http.StatusOK, "it worked") }) +// func TestWithHttptestWithSpecifiedPort(t *testing.T) { +// router := New() +// router.GET("/example", func(c *Context) { c.String(http.StatusOK, "it worked") }) - l, _ := net.Listen("tcp", ":8033") - ts := httptest.Server{ - Listener: l, - Config: &http.Server{Handler: router}, - } - ts.Start() - defer ts.Close() +// l, _ := net.Listen("tcp", ":8033") +// ts := httptest.Server{ +// Listener: l, +// Config: &http.Server{Handler: router}, +// } +// ts.Start() +// defer ts.Close() - testRequest(t, "http://localhost:8033/example") -} +// testRequest(t, "http://localhost:8033/example") +// } From 6bdc9afc8f1f05a1c578293a3ee7c96e15cecd05 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Thu, 16 Mar 2017 10:54:00 -0500 Subject: [PATCH 44/95] fix readme format (#837) Signed-off-by: Bo-Yi Wu --- README.md | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 9bcc8f5..883f9d5 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,6 @@ -# Gin Web Framework +# Gin Web Framework - -[![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 Badge](https://sourcegraph.com/github.com/gin-gonic/gin/-/badge.svg)](https://sourcegraph.com/github.com/gin-gonic/gin?badge) +[![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 Badge](https://sourcegraph.com/github.com/gin-gonic/gin/-/badge.svg)](https://sourcegraph.com/github.com/gin-gonic/gin?badge) Gin is a web framework written in Go (Golang). It features a martini-like API with much better performance, up to 40 times faster thanks to [httprouter](https://github.com/julienschmidt/httprouter). If you need performance and good productivity, you will love Gin. From ad2dacedd654cdc1c1526971fe83c118318975ba Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Fri, 24 Mar 2017 20:43:23 +0800 Subject: [PATCH 45/95] feat: Support get value from request header. (#839) --- context.go | 5 +++++ context_test.go | 9 +++++++++ 2 files changed, 14 insertions(+) diff --git a/context.go b/context.go index b4f2395..f638e62 100644 --- a/context.go +++ b/context.go @@ -440,6 +440,11 @@ func (c *Context) Header(key, value string) { } } +// GetHeader returns value from request headers +func (c *Context) GetHeader(key string) string { + return c.requestHeader(key) +} + func (c *Context) SetCookie( name string, value string, diff --git a/context_test.go b/context_test.go index ebc5050..5260628 100644 --- a/context_test.go +++ b/context_test.go @@ -1007,3 +1007,12 @@ func TestWebsocketsRequired(t *testing.T) { assert.False(t, c.IsWebsocket()) } + +func TestGetRequestHeaderValue(t *testing.T) { + c, _ := CreateTestContext(httptest.NewRecorder()) + c.Request, _ = http.NewRequest("GET", "/chat", nil) + c.Request.Header.Set("Gin-Version", "1.0.0") + + assert.Equal(t, "1.0.0", c.GetHeader("Gin-Version")) + assert.Equal(t, "", c.GetHeader("Connection")) +} From 73e5fcd8554f42d935f2b79a5e9ce29eefa9c889 Mon Sep 17 00:00:00 2001 From: Javier Provecho Date: Wed, 29 Mar 2017 14:32:12 +0000 Subject: [PATCH 46/95] feat(context): add idiomatic binding functions for clear err managment --- context.go | 18 +++++++++++++----- deprecated.go | 15 ++++++++++++++- 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/context.go b/context.go index f638e62..1873269 100644 --- a/context.go +++ b/context.go @@ -344,14 +344,22 @@ func (c *Context) BindJSON(obj interface{}) error { return c.BindWith(obj, binding.JSON) } -// BindWith binds the passed struct pointer using the specified binding engine. +// MustBindWith binds the passed struct pointer using the specified binding +// engine. It will abort the request with HTTP 400 if any error ocurrs. // See the binding package. -func (c *Context) BindWith(obj interface{}, b binding.Binding) error { - if err := b.Bind(c.Request, obj); err != nil { +func (c *Context) MustBindWith(obj interface{}, b binding.Binding) (err error) { + if err = c.ShouldBindWith(obj, b); err != nil { c.AbortWithError(400, err).SetType(ErrorTypeBind) - return err } - return nil + + return +} + +// ShouldBindWith binds the passed struct pointer using the specified binding +// engine. +// See the binding package. +func (c *Context) ShouldBindWith(obj interface{}, b binding.Binding) error { + return b.Bind(c.Request, obj) } // ClientIP implements a best effort algorithm to return the real client IP, it parses diff --git a/deprecated.go b/deprecated.go index 0488a9b..27e8f55 100644 --- a/deprecated.go +++ b/deprecated.go @@ -4,9 +4,22 @@ package gin -import "log" +import ( + "github.com/gin-gonic/gin/binding" + "log" +) func (c *Context) GetCookie(name string) (string, error) { log.Println("GetCookie() method is deprecated. Use Cookie() instead.") return c.Cookie(name) } + +// BindWith binds the passed struct pointer using the specified binding engine. +// See the binding package. +func (c *Context) BindWith(obj interface{}, b binding.Binding) error { + log.Println(`BindWith(\"interface{}, binding.Binding\") error is going to + be deprecated, please check issue #662 and either use MustBindWith() if you + want HTTP 400 to be automatically returned if any error occur, of use + ShouldBindWith() if you need to manage the error.`) + return c.MustBindWith(obj, b) +} From 41316b9ca93739de6d6d0ddf2fa1d16bb93f216a Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Fri, 31 Mar 2017 08:45:56 +0800 Subject: [PATCH 47/95] feat: add GetRawData func. (#857) * feat: add GetRawData func. * update return style Signed-off-by: Bo-Yi Wu --- context.go | 6 ++++++ context_test.go | 11 +++++++++++ 2 files changed, 17 insertions(+) diff --git a/context.go b/context.go index f638e62..f519e5b 100644 --- a/context.go +++ b/context.go @@ -7,6 +7,7 @@ package gin import ( "errors" "io" + "io/ioutil" "math" "mime/multipart" "net" @@ -445,6 +446,11 @@ func (c *Context) GetHeader(key string) string { return c.requestHeader(key) } +// GetRawData return stream data +func (c *Context) GetRawData() ([]byte, error) { + return ioutil.ReadAll(c.Request.Body) +} + func (c *Context) SetCookie( name string, value string, diff --git a/context_test.go b/context_test.go index 5260628..0feeca8 100644 --- a/context_test.go +++ b/context_test.go @@ -1016,3 +1016,14 @@ func TestGetRequestHeaderValue(t *testing.T) { assert.Equal(t, "1.0.0", c.GetHeader("Gin-Version")) assert.Equal(t, "", c.GetHeader("Connection")) } + +func TestContextGetRawData(t *testing.T) { + c, _ := CreateTestContext(httptest.NewRecorder()) + body := bytes.NewBufferString("Fetch binary post data") + c.Request, _ = http.NewRequest("POST", "/", body) + c.Request.Header.Add("Content-Type", MIMEPOSTForm) + + data, err := c.GetRawData() + assert.Nil(t, err) + assert.Equal(t, "Fetch binary post data", string(data)) +} From 46220b726dce5425bf3f04ad49aa5cbc3d0f731e Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Mon, 3 Apr 2017 21:23:45 +0800 Subject: [PATCH 48/95] feat: support Let's Encrypt tls. --- .gitignore | 4 ++-- examples/auto-tls/main.go | 17 +++++++++++++++++ gin.go | 31 +++++++++++++++++++++++++++++++ vendor/vendor.json | 24 +++++++++++++++++++++--- 4 files changed, 71 insertions(+), 5 deletions(-) create mode 100644 examples/auto-tls/main.go diff --git a/.gitignore b/.gitignore index 9f48f14..f3b636d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -Godeps/* -!Godeps/Godeps.json +vendor/* +!vendor/vendor.json coverage.out count.out diff --git a/examples/auto-tls/main.go b/examples/auto-tls/main.go new file mode 100644 index 0000000..2c0d89d --- /dev/null +++ b/examples/auto-tls/main.go @@ -0,0 +1,17 @@ +package main + +import ( + "github.com/gin-gonic/gin" +) + +func main() { + r := gin.Default() + + // Ping handler + r.GET("/ping", func(c *gin.Context) { + c.String(200, "pong") + }) + + // Listen and Server in 0.0.0.0:443 + r.RunAutoTLS(":443", "/var/www/.cache", "example.com") +} diff --git a/gin.go b/gin.go index 61ac5c0..4a3a833 100644 --- a/gin.go +++ b/gin.go @@ -5,6 +5,7 @@ package gin import ( + "crypto/tls" "html/template" "net" "net/http" @@ -12,6 +13,7 @@ import ( "sync" "github.com/gin-gonic/gin/render" + "golang.org/x/crypto/acme/autocert" ) // Version is Framework's version @@ -255,6 +257,35 @@ func (engine *Engine) RunTLS(addr string, certFile string, keyFile string) (err return } +// RunAutoTLS attaches the router to a http.Server and starts listening and serving HTTPS (secure) requests. +// It obtains and refreshes certificates automatically, +// as well as providing them to a TLS server via tls.Config. +func (engine *Engine) RunAutoTLS(addr string, cache string, domain ...string) (err error) { + debugPrint("Listening and serving HTTPS on %s and host name is %s\n", addr, domain) + defer func() { debugPrintError(err) }() + m := autocert.Manager{ + Prompt: autocert.AcceptTOS, + } + + //your domain here + if len(domain) != 0 { + m.HostPolicy = autocert.HostWhitelist(domain...) + } + + // folder for storing certificates + if cache != "" { + m.Cache = autocert.DirCache(cache) + } + + s := &http.Server{ + Addr: addr, + TLSConfig: &tls.Config{GetCertificate: m.GetCertificate}, + Handler: engine, + } + err = s.ListenAndServeTLS("", "") + return +} + // RunUnix attaches the router to a http.Server and starts listening and serving HTTP requests // through the specified unix socket (ie. a file). // Note: this method will block the calling goroutine indefinitely unless an error happens. diff --git a/vendor/vendor.json b/vendor/vendor.json index 2bc5e19..e3b3e91 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -54,11 +54,29 @@ "revisionTime": "2017-02-15T20:11:44Z" }, { - "checksumSHA1": "9jjO5GjLa0XF/nfWihF02RoH4qc=", + "checksumSHA1": "didOyrMN69DzlBd+BPSC28G2YG0=", + "path": "golang.org/x/crypto/acme", + "revision": "88915ccf7aeb91e9324fe7cf3eddd1531ced61ea", + "revisionTime": "2017-04-02T20:18:05Z" + }, + { + "checksumSHA1": "yfiamzDHcZXb6irWt7DfHVxCs44=", + "path": "golang.org/x/crypto/acme/autocert", + "revision": "88915ccf7aeb91e9324fe7cf3eddd1531ced61ea", + "revisionTime": "2017-04-02T20:18:05Z" + }, + { + "checksumSHA1": "Y+HGqEkYM15ir+J93MEaHdyFy0c=", "comment": "release-branch.go1.7", "path": "golang.org/x/net/context", - "revision": "d4c55e66d8c3a2f3382d264b08e3e3454a66355a", - "revisionTime": "2016-10-18T08:54:36Z" + "revision": "ffcf1bedda3b04ebb15a168a59800a73d6dc0f4d", + "revisionTime": "2017-03-29T01:43:45Z" + }, + { + "checksumSHA1": "WHc3uByvGaMcnSoI21fhzYgbOgg=", + "path": "golang.org/x/net/context/ctxhttp", + "revision": "ffcf1bedda3b04ebb15a168a59800a73d6dc0f4d", + "revisionTime": "2017-03-29T01:43:45Z" }, { "checksumSHA1": "/oZpHfYc+ZgOwYAhlvcMhmETYpw=", From 70d0a4c5bab95de89d1d9fb057f6f1cc0defb554 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Mon, 3 Apr 2017 22:51:56 +0800 Subject: [PATCH 49/95] fix: build only from Go version 1.7 onward --- gin.go | 31 ------------------------------- gin1.7.go | 40 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 31 deletions(-) create mode 100644 gin1.7.go diff --git a/gin.go b/gin.go index 4a3a833..61ac5c0 100644 --- a/gin.go +++ b/gin.go @@ -5,7 +5,6 @@ package gin import ( - "crypto/tls" "html/template" "net" "net/http" @@ -13,7 +12,6 @@ import ( "sync" "github.com/gin-gonic/gin/render" - "golang.org/x/crypto/acme/autocert" ) // Version is Framework's version @@ -257,35 +255,6 @@ func (engine *Engine) RunTLS(addr string, certFile string, keyFile string) (err return } -// RunAutoTLS attaches the router to a http.Server and starts listening and serving HTTPS (secure) requests. -// It obtains and refreshes certificates automatically, -// as well as providing them to a TLS server via tls.Config. -func (engine *Engine) RunAutoTLS(addr string, cache string, domain ...string) (err error) { - debugPrint("Listening and serving HTTPS on %s and host name is %s\n", addr, domain) - defer func() { debugPrintError(err) }() - m := autocert.Manager{ - Prompt: autocert.AcceptTOS, - } - - //your domain here - if len(domain) != 0 { - m.HostPolicy = autocert.HostWhitelist(domain...) - } - - // folder for storing certificates - if cache != "" { - m.Cache = autocert.DirCache(cache) - } - - s := &http.Server{ - Addr: addr, - TLSConfig: &tls.Config{GetCertificate: m.GetCertificate}, - Handler: engine, - } - err = s.ListenAndServeTLS("", "") - return -} - // RunUnix attaches the router to a http.Server and starts listening and serving HTTP requests // through the specified unix socket (ie. a file). // Note: this method will block the calling goroutine indefinitely unless an error happens. diff --git a/gin1.7.go b/gin1.7.go new file mode 100644 index 0000000..dd8a65a --- /dev/null +++ b/gin1.7.go @@ -0,0 +1,40 @@ +// +build go1.7 + +package gin + +import ( + "crypto/tls" + "net/http" + + "golang.org/x/crypto/acme/autocert" +) + +// RunAutoTLS attaches the router to a http.Server and starts listening and serving HTTPS (secure) requests. +// It obtains and refreshes certificates automatically, +// as well as providing them to a TLS server via tls.Config. +// only from Go version 1.7 onward +func (engine *Engine) RunAutoTLS(addr string, cache string, domain ...string) (err error) { + debugPrint("Listening and serving HTTPS on %s and host name is %s\n", addr, domain) + defer func() { debugPrintError(err) }() + m := autocert.Manager{ + Prompt: autocert.AcceptTOS, + } + + //your domain here + if len(domain) != 0 { + m.HostPolicy = autocert.HostWhitelist(domain...) + } + + // folder for storing certificates + if cache != "" { + m.Cache = autocert.DirCache(cache) + } + + s := &http.Server{ + Addr: addr, + TLSConfig: &tls.Config{GetCertificate: m.GetCertificate}, + Handler: engine, + } + err = s.ListenAndServeTLS("", "") + return +} From d17e0a31b8ffd2df3a85734dfca5314b9e152a80 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Mon, 3 Apr 2017 23:30:46 +0800 Subject: [PATCH 50/95] fix: remove hard code for cache. --- examples/auto-tls/main.go | 5 ++++- gin1.7.go | 22 +++++++++------------- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/examples/auto-tls/main.go b/examples/auto-tls/main.go index 2c0d89d..896d3a9 100644 --- a/examples/auto-tls/main.go +++ b/examples/auto-tls/main.go @@ -2,16 +2,19 @@ package main import ( "github.com/gin-gonic/gin" + "golang.org/x/crypto/acme/autocert" ) func main() { r := gin.Default() + gin.AutoTLSManager.Cache = autocert.DirCache("/var/www/.cache") + // Ping handler r.GET("/ping", func(c *gin.Context) { c.String(200, "pong") }) // Listen and Server in 0.0.0.0:443 - r.RunAutoTLS(":443", "/var/www/.cache", "example.com") + r.RunAutoTLS("example.com") } diff --git a/gin1.7.go b/gin1.7.go index dd8a65a..7599fdf 100644 --- a/gin1.7.go +++ b/gin1.7.go @@ -9,30 +9,26 @@ import ( "golang.org/x/crypto/acme/autocert" ) +var AutoTLSManager = autocert.Manager{ + Prompt: autocert.AcceptTOS, +} + // RunAutoTLS attaches the router to a http.Server and starts listening and serving HTTPS (secure) requests. // It obtains and refreshes certificates automatically, // as well as providing them to a TLS server via tls.Config. // only from Go version 1.7 onward -func (engine *Engine) RunAutoTLS(addr string, cache string, domain ...string) (err error) { - debugPrint("Listening and serving HTTPS on %s and host name is %s\n", addr, domain) +func (engine *Engine) RunAutoTLS(domain ...string) (err error) { + debugPrint("Listening and serving HTTPS on host name is %s\n", domain) defer func() { debugPrintError(err) }() - m := autocert.Manager{ - Prompt: autocert.AcceptTOS, - } //your domain here if len(domain) != 0 { - m.HostPolicy = autocert.HostWhitelist(domain...) - } - - // folder for storing certificates - if cache != "" { - m.Cache = autocert.DirCache(cache) + AutoTLSManager.HostPolicy = autocert.HostWhitelist(domain...) } s := &http.Server{ - Addr: addr, - TLSConfig: &tls.Config{GetCertificate: m.GetCertificate}, + Addr: ":443", + TLSConfig: &tls.Config{GetCertificate: AutoTLSManager.GetCertificate}, Handler: engine, } err = s.ListenAndServeTLS("", "") From 6b0ae2a64a8e4444e56e8a316e700a5434e5365a Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Mon, 3 Apr 2017 23:34:00 +0800 Subject: [PATCH 51/95] docs: update example. --- examples/auto-tls/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/auto-tls/main.go b/examples/auto-tls/main.go index 896d3a9..9495c0a 100644 --- a/examples/auto-tls/main.go +++ b/examples/auto-tls/main.go @@ -16,5 +16,5 @@ func main() { }) // Listen and Server in 0.0.0.0:443 - r.RunAutoTLS("example.com") + r.RunAutoTLS("example1.com", "example2.com") } From fb502ca58b20561415b7d8b3d00aa987521b130d Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Mon, 3 Apr 2017 23:42:21 +0800 Subject: [PATCH 52/95] docs: update --- gin1.7.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gin1.7.go b/gin1.7.go index 7599fdf..59de3d0 100644 --- a/gin1.7.go +++ b/gin1.7.go @@ -9,6 +9,7 @@ import ( "golang.org/x/crypto/acme/autocert" ) +// AutoTLSManager is a stateful certificate manager built on top of acme.Client. var AutoTLSManager = autocert.Manager{ Prompt: autocert.AcceptTOS, } @@ -21,7 +22,7 @@ func (engine *Engine) RunAutoTLS(domain ...string) (err error) { debugPrint("Listening and serving HTTPS on host name is %s\n", domain) defer func() { debugPrintError(err) }() - //your domain here + // HostPolicy controls which domains the Manager will attempt if len(domain) != 0 { AutoTLSManager.HostPolicy = autocert.HostWhitelist(domain...) } From 0cb7cf88cb8758452b35d87b804c73906a324cb1 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Tue, 4 Apr 2017 14:19:04 +0800 Subject: [PATCH 53/95] docs: add comment. --- examples/auto-tls/main.go | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/auto-tls/main.go b/examples/auto-tls/main.go index 9495c0a..49544c5 100644 --- a/examples/auto-tls/main.go +++ b/examples/auto-tls/main.go @@ -8,6 +8,7 @@ import ( func main() { r := gin.Default() + // folder for storing certificates gin.AutoTLSManager.Cache = autocert.DirCache("/var/www/.cache") // Ping handler From 2da17294c906173561bec1707fb6d03c823f701c Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Tue, 4 Apr 2017 14:19:44 +0800 Subject: [PATCH 54/95] feat: listen https. --- gin1.7.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gin1.7.go b/gin1.7.go index 59de3d0..919c80f 100644 --- a/gin1.7.go +++ b/gin1.7.go @@ -28,7 +28,7 @@ func (engine *Engine) RunAutoTLS(domain ...string) (err error) { } s := &http.Server{ - Addr: ":443", + Addr: ":https", TLSConfig: &tls.Config{GetCertificate: AutoTLSManager.GetCertificate}, Handler: engine, } From a57bb191c79e24a85a75f657fde82b5a7af8c095 Mon Sep 17 00:00:00 2001 From: Javier Provecho Fernandez Date: Fri, 7 Apr 2017 22:17:46 +0200 Subject: [PATCH 55/95] Update CHANGELOG.md --- CHANGELOG.md | 42 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 34 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 82f1bea..63ada3e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,32 @@ -#CHANGELOG +# CHANGELOG -###Gin 1.0rc2 (...) +### Gin 1.2 + +- [NEW] Switch from godeps to govendor +- [NEW] Improve README examples and add extra at examples folder +- [NEW] Improved support with App Engine +- [FIX] Refactor render +- [FIX] Reworked tests + +### Gin 1.1.4 + +- [NEW] Support google appengine for IsTerminal func + +### Gin 1.1.3 + +- [FIX] Reverted Logger: skip ANSI color commands + +### Gin 1.1 + +- [NEW] Implement QueryArray and PostArray methods +- [NEW] Refactor GetQuery and GetPostForm +- [NEW] Add contribution guide +- [FIX] Corrected typos in README +- [FIX] Removed additional Iota +- [FIX] Changed imports to gopkg instead of github in README (#733) +- [FIX] Logger: skip ANSI color commands if output is not a tty + +### Gin 1.0rc2 (...) - [PERFORMANCE] Fast path for writing Content-Type. - [PERFORMANCE] Much faster 404 routing @@ -35,7 +61,7 @@ - [FIX] MIT license in every file -###Gin 1.0rc1 (May 22, 2015) +### Gin 1.0rc1 (May 22, 2015) - [PERFORMANCE] Zero allocation router - [PERFORMANCE] Faster JSON, XML and text rendering @@ -79,7 +105,7 @@ - [FIX] Better support for Google App Engine (using log instead of fmt) -###Gin 0.6 (Mar 9, 2015) +### Gin 0.6 (Mar 9, 2015) - [NEW] Support multipart/form-data - [NEW] NoMethod handler @@ -89,14 +115,14 @@ - [FIX] Improve color logger -###Gin 0.5 (Feb 7, 2015) +### Gin 0.5 (Feb 7, 2015) - [NEW] Content Negotiation - [FIX] Solved security bug that allow a client to spoof ip - [FIX] Fix unexported/ignored fields in binding -###Gin 0.4 (Aug 21, 2014) +### Gin 0.4 (Aug 21, 2014) - [NEW] Development mode - [NEW] Unit tests @@ -105,7 +131,7 @@ - [FIX] Improved documentation for model binding -###Gin 0.3 (Jul 18, 2014) +### Gin 0.3 (Jul 18, 2014) - [PERFORMANCE] Normal log and error log are printed in the same call. - [PERFORMANCE] Improve performance of NoRouter() @@ -123,7 +149,7 @@ - [FIX] Check application/x-www-form-urlencoded when parsing form -###Gin 0.2b (Jul 08, 2014) +### Gin 0.2b (Jul 08, 2014) - [PERFORMANCE] Using sync.Pool to allocatio/gc overhead - [NEW] Travis CI integration - [NEW] Completely new logger From c5d9c2f0faecdd2ccaad75c3db635331f0700251 Mon Sep 17 00:00:00 2001 From: Javier Provecho Fernandez Date: Fri, 7 Apr 2017 22:18:36 +0200 Subject: [PATCH 56/95] bump version to 1.2 --- gin.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gin.go b/gin.go index 61ac5c0..6a8e193 100644 --- a/gin.go +++ b/gin.go @@ -15,7 +15,7 @@ import ( ) // Version is Framework's version -const Version = "v1.1.4" +const Version = "v1.2" var default404Body = []byte("404 page not found") var default405Body = []byte("405 method not allowed") From 3b54702915a430e53625e8d667a495ab028514c2 Mon Sep 17 00:00:00 2001 From: Javier Provecho Fernandez Date: Fri, 7 Apr 2017 22:34:27 +0200 Subject: [PATCH 57/95] docs(readme) update changelog --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 63ada3e..4ddbd9e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,8 +5,13 @@ - [NEW] Switch from godeps to govendor - [NEW] Improve README examples and add extra at examples folder - [NEW] Improved support with App Engine +- [NEW] Add \*context.GetRawData() +- [NEW] Add \*context.GetHeader() (request) +- [NEW] Add \*context.AbortWithStatusJSON() (JSON content type) - [FIX] Refactor render - [FIX] Reworked tests +- [FIX] logger now supports cygwin +- [FIX] Use X-Forwarded-For before X-Real-Ip ### Gin 1.1.4 From f8520b83f912848dea56595ca7ec2f652a72b4ee Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Mon, 17 Apr 2017 13:41:47 +0800 Subject: [PATCH 58/95] update Signed-off-by: Bo-Yi Wu --- README.md | 59 +++++++++++++++++++++++++++++++++++ examples/auto-tls/example1.go | 19 +++++++++++ examples/auto-tls/example2.go | 26 +++++++++++++++ examples/auto-tls/main.go | 21 ------------- gin1.7.go | 37 ---------------------- 5 files changed, 104 insertions(+), 58 deletions(-) create mode 100644 examples/auto-tls/example1.go create mode 100644 examples/auto-tls/example2.go delete mode 100644 examples/auto-tls/main.go delete mode 100644 gin1.7.go diff --git a/README.md b/README.md index 883f9d5..535ee4e 100644 --- a/README.md +++ b/README.md @@ -777,6 +777,65 @@ func main() { } ``` +### Support Let's Encrypt + +example for 1-line LetsEncrypt HTTPS servers. + +[embedmd]:# (examples/auto-tls/example1.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")) +} +``` + +example for custom autocert manager. + +[embedmd]:# (examples/auto-tls/example2.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)) +} +``` + ### Graceful restart or stop Do you want to graceful restart or stop your web server? diff --git a/examples/auto-tls/example1.go b/examples/auto-tls/example1.go new file mode 100644 index 0000000..fa9f400 --- /dev/null +++ b/examples/auto-tls/example1.go @@ -0,0 +1,19 @@ +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")) +} diff --git a/examples/auto-tls/example2.go b/examples/auto-tls/example2.go new file mode 100644 index 0000000..ab8b81e --- /dev/null +++ b/examples/auto-tls/example2.go @@ -0,0 +1,26 @@ +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)) +} diff --git a/examples/auto-tls/main.go b/examples/auto-tls/main.go deleted file mode 100644 index 49544c5..0000000 --- a/examples/auto-tls/main.go +++ /dev/null @@ -1,21 +0,0 @@ -package main - -import ( - "github.com/gin-gonic/gin" - "golang.org/x/crypto/acme/autocert" -) - -func main() { - r := gin.Default() - - // folder for storing certificates - gin.AutoTLSManager.Cache = autocert.DirCache("/var/www/.cache") - - // Ping handler - r.GET("/ping", func(c *gin.Context) { - c.String(200, "pong") - }) - - // Listen and Server in 0.0.0.0:443 - r.RunAutoTLS("example1.com", "example2.com") -} diff --git a/gin1.7.go b/gin1.7.go deleted file mode 100644 index 919c80f..0000000 --- a/gin1.7.go +++ /dev/null @@ -1,37 +0,0 @@ -// +build go1.7 - -package gin - -import ( - "crypto/tls" - "net/http" - - "golang.org/x/crypto/acme/autocert" -) - -// AutoTLSManager is a stateful certificate manager built on top of acme.Client. -var AutoTLSManager = autocert.Manager{ - Prompt: autocert.AcceptTOS, -} - -// RunAutoTLS attaches the router to a http.Server and starts listening and serving HTTPS (secure) requests. -// It obtains and refreshes certificates automatically, -// as well as providing them to a TLS server via tls.Config. -// only from Go version 1.7 onward -func (engine *Engine) RunAutoTLS(domain ...string) (err error) { - debugPrint("Listening and serving HTTPS on host name is %s\n", domain) - defer func() { debugPrintError(err) }() - - // HostPolicy controls which domains the Manager will attempt - if len(domain) != 0 { - AutoTLSManager.HostPolicy = autocert.HostWhitelist(domain...) - } - - s := &http.Server{ - Addr: ":https", - TLSConfig: &tls.Config{GetCertificate: AutoTLSManager.GetCertificate}, - Handler: engine, - } - err = s.ListenAndServeTLS("", "") - return -} From 03a76c00ea02bd5fcbde25442261b6c5372b3948 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Mon, 17 Apr 2017 13:43:33 +0800 Subject: [PATCH 59/95] revert vendor.json Signed-off-by: Bo-Yi Wu --- vendor/vendor.json | 24 +++--------------------- 1 file changed, 3 insertions(+), 21 deletions(-) diff --git a/vendor/vendor.json b/vendor/vendor.json index e3b3e91..2bc5e19 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -54,29 +54,11 @@ "revisionTime": "2017-02-15T20:11:44Z" }, { - "checksumSHA1": "didOyrMN69DzlBd+BPSC28G2YG0=", - "path": "golang.org/x/crypto/acme", - "revision": "88915ccf7aeb91e9324fe7cf3eddd1531ced61ea", - "revisionTime": "2017-04-02T20:18:05Z" - }, - { - "checksumSHA1": "yfiamzDHcZXb6irWt7DfHVxCs44=", - "path": "golang.org/x/crypto/acme/autocert", - "revision": "88915ccf7aeb91e9324fe7cf3eddd1531ced61ea", - "revisionTime": "2017-04-02T20:18:05Z" - }, - { - "checksumSHA1": "Y+HGqEkYM15ir+J93MEaHdyFy0c=", + "checksumSHA1": "9jjO5GjLa0XF/nfWihF02RoH4qc=", "comment": "release-branch.go1.7", "path": "golang.org/x/net/context", - "revision": "ffcf1bedda3b04ebb15a168a59800a73d6dc0f4d", - "revisionTime": "2017-03-29T01:43:45Z" - }, - { - "checksumSHA1": "WHc3uByvGaMcnSoI21fhzYgbOgg=", - "path": "golang.org/x/net/context/ctxhttp", - "revision": "ffcf1bedda3b04ebb15a168a59800a73d6dc0f4d", - "revisionTime": "2017-03-29T01:43:45Z" + "revision": "d4c55e66d8c3a2f3382d264b08e3e3454a66355a", + "revisionTime": "2016-10-18T08:54:36Z" }, { "checksumSHA1": "/oZpHfYc+ZgOwYAhlvcMhmETYpw=", From 957f6205c83edda01be3a6dd74cdc1859b660ba0 Mon Sep 17 00:00:00 2001 From: Javier Provecho Fernandez Date: Mon, 17 Apr 2017 10:00:48 +0200 Subject: [PATCH 60/95] docs: update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4ddbd9e..441df24 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ### Gin 1.2 - [NEW] Switch from godeps to govendor +- [NEW] Add support for Let's Encrypt via gin-gonic/autotls - [NEW] Improve README examples and add extra at examples folder - [NEW] Improved support with App Engine - [NEW] Add \*context.GetRawData() From 6a3a8ae61bdb066ada6a0d270d8642c468149777 Mon Sep 17 00:00:00 2001 From: Andrey Nering Date: Wed, 3 May 2017 22:22:00 -0300 Subject: [PATCH 61/95] Fix time.Time binding (#904) If a empty string is given(`""`), them time should be zero. --- binding/form_mapping.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/binding/form_mapping.go b/binding/form_mapping.go index bc9e44c..1af8165 100644 --- a/binding/form_mapping.go +++ b/binding/form_mapping.go @@ -152,6 +152,11 @@ func setTimeField(val string, structField reflect.StructField, value reflect.Val if timeFormat == "" { return errors.New("Blank time format") } + + if val == "" { + value.Set(reflect.ValueOf(time.Time{})) + return nil + } l := time.Local if isUTC, _ := strconv.ParseBool(structField.Tag.Get("time_utc")); isUTC { From d4a53101c3da8fe67c29f0e52538167215d31fc4 Mon Sep 17 00:00:00 2001 From: Rahul Datta Roy Date: Thu, 4 May 2017 09:22:48 +0800 Subject: [PATCH 62/95] Fix minor type in context.go (#900) * Fix minor type in context.go * More spelling fixes in context.go --- context.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/context.go b/context.go index f519e5b..7b57150 100644 --- a/context.go +++ b/context.go @@ -91,7 +91,7 @@ func (c *Context) HandlerName() string { // Next should be used only inside middleware. // It executes the pending handlers in the chain inside the calling handler. -// See example in github. +// See example in GitHub. func (c *Context) Next() { c.index++ s := int8(len(c.handlers)) @@ -114,7 +114,7 @@ func (c *Context) Abort() { } // AbortWithStatus calls `Abort()` and writes the headers with the specified status code. -// For example, a failed attempt to authentificate a request could use: context.AbortWithStatus(401). +// For example, a failed attempt to authenticate a request could use: context.AbortWithStatus(401). func (c *Context) AbortWithStatus(code int) { c.Status(code) c.Writer.WriteHeaderNow() @@ -163,7 +163,7 @@ func (c *Context) Error(err error) *Error { /******** METADATA MANAGEMENT********/ /************************************/ -// Set is used to store a new key/value pair exclusivelly for this context. +// Set is used to store a new key/value pair exclusively for this context. // It also lazy initializes c.Keys if it was not used previously. func (c *Context) Set(key string, value interface{}) { if c.Keys == nil { @@ -202,7 +202,7 @@ func (c *Context) Param(key string) string { } // Query returns the keyed url query value if it exists, -// othewise it returns an empty string `("")`. +// otherwise it returns an empty string `("")`. // It is shortcut for `c.Request.URL.Query().Get(key)` // GET /path?id=1234&name=Manu&value= // c.Query("id") == "1234" @@ -215,7 +215,7 @@ func (c *Context) Query(key string) string { } // DefaultQuery returns the keyed url query value if it exists, -// othewise it returns the specified defaultValue string. +// otherwise it returns the specified defaultValue string. // See: Query() and GetQuery() for further information. // GET /?name=Manu&lastname= // c.DefaultQuery("name", "unknown") == "Manu" @@ -230,7 +230,7 @@ func (c *Context) DefaultQuery(key, defaultValue string) string { // GetQuery is like Query(), it returns the keyed url query value // if it exists `(value, true)` (even when the value is an empty string), -// othewise it returns `("", false)`. +// otherwise it returns `("", false)`. // It is shortcut for `c.Request.URL.Query().Get(key)` // GET /?name=Manu&lastname= // ("Manu", true) == c.GetQuery("name") @@ -507,7 +507,7 @@ func (c *Context) HTML(code int, name string, obj interface{}) { // IndentedJSON serializes the given struct as pretty JSON (indented + endlines) into the response body. // It also sets the Content-Type as "application/json". -// WARNING: we recommend to use this only for development propuses since printing pretty JSON is +// WARNING: we recommend to use this only for development purposes since printing pretty JSON is // more CPU and bandwidth consuming. Use Context.JSON() instead. func (c *Context) IndentedJSON(code int, obj interface{}) { c.Render(code, render.IndentedJSON{Data: obj}) From 214a746b1d531cdacdcc0aa4039da119d4002837 Mon Sep 17 00:00:00 2001 From: Eason Lin Date: Wed, 24 May 2017 17:39:05 +0800 Subject: [PATCH 63/95] Improve cookie tests code coverage (#918) --- context_test.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/context_test.go b/context_test.go index 0feeca8..fccca47 100644 --- a/context_test.go +++ b/context_test.go @@ -384,12 +384,21 @@ func TestContextSetCookie(t *testing.T) { assert.Equal(t, c.Writer.Header().Get("Set-Cookie"), "user=gin; Path=/; Domain=localhost; Max-Age=1; HttpOnly; Secure") } +func TestContextSetCookiePathEmpty(t *testing.T) { + c, _ := CreateTestContext(httptest.NewRecorder()) + c.SetCookie("user", "gin", 1, "", "localhost", true, true) + assert.Equal(t, c.Writer.Header().Get("Set-Cookie"), "user=gin; Path=/; Domain=localhost; Max-Age=1; HttpOnly; Secure") +} + func TestContextGetCookie(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) c.Request, _ = http.NewRequest("GET", "/get", nil) c.Request.Header.Set("Cookie", "user=gin") cookie, _ := c.Cookie("user") assert.Equal(t, cookie, "gin") + + _, err := c.Cookie("nokey") + assert.Error(t, err) } func TestContextBodyAllowedForStatus(t *testing.T) { From 8295db44ed904572ad133d974cad381eb961cdab Mon Sep 17 00:00:00 2001 From: Eason Lin Date: Mon, 29 May 2017 14:28:38 +0800 Subject: [PATCH 64/95] Add content negotiation tests code coverage (#921) --- context_test.go | 62 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/context_test.go b/context_test.go index fccca47..131d891 100644 --- a/context_test.go +++ b/context_test.go @@ -746,6 +746,68 @@ func TestContextRenderRedirectAll(t *testing.T) { assert.NotPanics(t, func() { c.Redirect(308, "/resource") }) } +func TestContextNegotiationWithJSON(t *testing.T) { + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + c.Request, _ = http.NewRequest("POST", "", nil) + + c.Negotiate(200, Negotiate{ + Offered: []string{MIMEJSON, MIMEXML}, + Data: H{"foo": "bar"}, + }) + + assert.Equal(t, 200, w.Code) + assert.Equal(t, "{\"foo\":\"bar\"}", w.Body.String()) + assert.Equal(t, "application/json; charset=utf-8", w.HeaderMap.Get("Content-Type")) +} + +func TestContextNegotiationWithXML(t *testing.T) { + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + c.Request, _ = http.NewRequest("POST", "", nil) + + c.Negotiate(200, Negotiate{ + Offered: []string{MIMEXML, MIMEJSON}, + Data: H{"foo": "bar"}, + }) + + assert.Equal(t, 200, w.Code) + assert.Equal(t, "bar", w.Body.String()) + assert.Equal(t, "application/xml; charset=utf-8", w.HeaderMap.Get("Content-Type")) +} + +func TestContextNegotiationWithHTML(t *testing.T) { + w := httptest.NewRecorder() + c, router := CreateTestContext(w) + c.Request, _ = http.NewRequest("POST", "", nil) + templ := template.Must(template.New("t").Parse(`Hello {{.name}}`)) + router.SetHTMLTemplate(templ) + + c.Negotiate(200, Negotiate{ + Offered: []string{MIMEHTML}, + Data: H{"name": "gin"}, + HTMLName: "t", + }) + + assert.Equal(t, 200, w.Code) + assert.Equal(t, "Hello gin", w.Body.String()) + assert.Equal(t, "text/html; charset=utf-8", w.HeaderMap.Get("Content-Type")) +} + +func TestContextNegotiationNotSupport(t *testing.T) { + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + c.Request, _ = http.NewRequest("POST", "", nil) + + c.Negotiate(200, Negotiate{ + Offered: []string{MIMEPOSTForm}, + }) + + assert.Equal(t, 406, w.Code) + assert.Equal(t, c.index, abortIndex) + assert.True(t, c.IsAborted()) +} + func TestContextNegotiationFormat(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) c.Request, _ = http.NewRequest("POST", "", nil) From 35f5df63e6379d6db4bfd27f23b2ddff030b11a2 Mon Sep 17 00:00:00 2001 From: sope Date: Mon, 29 May 2017 16:03:49 +0800 Subject: [PATCH 65/95] add custom Delims support (#860) * Revert "Merge pull request #753 from gin-gonic/bug" This reverts commit 556287ff0856a5ad1f9a1b493c188cabeceba929, reversing changes made to 32cab500ecc71d2975f5699c8a65c6debb29cfbe. * Revert "Merge pull request #744 from aviddiviner/logger-fix" This reverts commit c3bfd69303d0fdaf2d43a7ff07cc8ee45ec7bb3f, reversing changes made to 9177f01c2843b91820780197f521ba48554b9df3. * add custom Delims support * add some test for Delims * remove the empty line for import native package * remove unuseful comments --- fixtures/basic/hello.tmpl | 1 + gin.go | 18 ++++++++---- gin_test.go | 60 ++++++++++++++++++++++++++++++++++++++- render/html.go | 15 +++++++--- 4 files changed, 84 insertions(+), 10 deletions(-) create mode 100644 fixtures/basic/hello.tmpl diff --git a/fixtures/basic/hello.tmpl b/fixtures/basic/hello.tmpl new file mode 100644 index 0000000..0767ef3 --- /dev/null +++ b/fixtures/basic/hello.tmpl @@ -0,0 +1 @@ +

Hello {[{.name}]}

\ No newline at end of file diff --git a/gin.go b/gin.go index 6a8e193..dcca2b3 100644 --- a/gin.go +++ b/gin.go @@ -45,6 +45,7 @@ type ( // Create an instance of Engine, by using New() or Default() Engine struct { RouterGroup + delims render.Delims HTMLRender render.HTMLRender allNoRoute HandlersChain allNoMethod HandlersChain @@ -119,6 +120,7 @@ func New() *Engine { UseRawPath: false, UnescapePathValues: true, trees: make(methodTrees, 0, 9), + delims: render.Delims{"{{", "}}"}, } engine.RouterGroup.engine = engine engine.pool.New = func() interface{} { @@ -138,21 +140,26 @@ func (engine *Engine) allocateContext() *Context { return &Context{engine: engine} } +func (engine *Engine) Delims(left, right string) *Engine { + engine.delims = render.Delims{left, right} + return engine +} + func (engine *Engine) LoadHTMLGlob(pattern string) { if IsDebugging() { - debugPrintLoadTemplate(template.Must(template.ParseGlob(pattern))) - engine.HTMLRender = render.HTMLDebug{Glob: pattern} + debugPrintLoadTemplate(template.Must(template.New("").Delims(engine.delims.Left, engine.delims.Right).ParseGlob(pattern))) + engine.HTMLRender = render.HTMLDebug{Glob: pattern, Delims: engine.delims} } else { - templ := template.Must(template.ParseGlob(pattern)) + templ := template.Must(template.New("").Delims(engine.delims.Left, engine.delims.Right).ParseGlob(pattern)) engine.SetHTMLTemplate(templ) } } func (engine *Engine) LoadHTMLFiles(files ...string) { if IsDebugging() { - engine.HTMLRender = render.HTMLDebug{Files: files} + engine.HTMLRender = render.HTMLDebug{Files: files, Delims: engine.delims} } else { - templ := template.Must(template.ParseFiles(files...)) + templ := template.Must(template.New("").Delims(engine.delims.Left, engine.delims.Right).ParseFiles(files...)) engine.SetHTMLTemplate(templ) } } @@ -161,6 +168,7 @@ func (engine *Engine) SetHTMLTemplate(templ *template.Template) { if len(engine.trees) > 0 { debugPrintWARNINGSetHTMLTemplate() } + engine.HTMLRender = render.HTMLProduction{Template: templ} } diff --git a/gin_test.go b/gin_test.go index cc24bc9..3ab4697 100644 --- a/gin_test.go +++ b/gin_test.go @@ -5,14 +5,60 @@ package gin import ( + "fmt" + "io/ioutil" + "net/http" "reflect" "testing" + "time" "github.com/stretchr/testify/assert" ) +func setupHTMLFiles(t *testing.T) func() { + go func() { + router := New() + router.Delims("{[{", "}]}") + router.LoadHTMLFiles("./fixtures/basic/hello.tmpl") + router.GET("/test", func(c *Context) { + c.HTML(http.StatusOK, "hello.tmpl", map[string]string{"name": "world"}) + }) + router.Run(":8888") + }() + t.Log("waiting 1 second for server startup") + time.Sleep(1 * time.Second) + return func() {} +} + +func setupHTMLGlob(t *testing.T) func() { + go func() { + router := New() + router.Delims("{[{", "}]}") + router.LoadHTMLGlob("./fixtures/basic/*") + router.GET("/test", func(c *Context) { + c.HTML(http.StatusOK, "hello.tmpl", map[string]string{"name": "world"}) + }) + router.Run(":8888") + }() + t.Log("waiting 1 second for server startup") + time.Sleep(1 * time.Second) + return func() {} +} + //TODO -// func (engine *Engine) LoadHTMLGlob(pattern string) { +func TestLoadHTMLGlob(t *testing.T) { + td := setupHTMLGlob(t) + res, err := http.Get("http://127.0.0.1:8888/test") + if err != nil { + fmt.Println(err) + } + + resp, _ := ioutil.ReadAll(res.Body) + assert.Equal(t, "

Hello world

", string(resp[:])) + + td() +} + // func (engine *Engine) LoadHTMLFiles(files ...string) { // func (engine *Engine) RunTLS(addr string, cert string, key string) error { @@ -42,6 +88,18 @@ func TestCreateEngine(t *testing.T) { // SetMode(TestMode) // } +func TestLoadHTMLFiles(t *testing.T) { + td := setupHTMLFiles(t) + res, err := http.Get("http://127.0.0.1:8888/test") + if err != nil { + fmt.Println(err) + } + + resp, _ := ioutil.ReadAll(res.Body) + assert.Equal(t, "

Hello world

", string(resp[:])) + td() +} + func TestLoadHTMLReleaseMode(t *testing.T) { } diff --git a/render/html.go b/render/html.go index a3cbda6..3ea5e08 100644 --- a/render/html.go +++ b/render/html.go @@ -10,17 +10,24 @@ import ( ) type ( + Delims struct { + Left string + Right string + } + HTMLRender interface { Instance(string, interface{}) Render } HTMLProduction struct { Template *template.Template + Delims Delims } HTMLDebug struct { - Files []string - Glob string + Files []string + Glob string + Delims Delims } HTML struct { @@ -49,10 +56,10 @@ func (r HTMLDebug) Instance(name string, data interface{}) Render { } func (r HTMLDebug) loadTemplate() *template.Template { if len(r.Files) > 0 { - return template.Must(template.ParseFiles(r.Files...)) + return template.Must(template.New("").Delims(r.Delims.Left, r.Delims.Right).ParseFiles(r.Files...)) } if len(r.Glob) > 0 { - return template.Must(template.ParseGlob(r.Glob)) + return template.Must(template.New("").Delims(r.Delims.Left, r.Delims.Right).ParseGlob(r.Glob)) } panic("the HTML debug render was created without files or glob pattern") } From 5eea51b6c9517e44a033af6f8b1ce4c48b08f398 Mon Sep 17 00:00:00 2001 From: Javier Provecho Fernandez Date: Fri, 2 Jun 2017 03:00:04 +0200 Subject: [PATCH 66/95] feat(context): add cast helpers to c.Keys (#856) * feat(context): add cast helpers to c.Keys * Add tests for cast helpers to c.Keys --- context.go | 88 +++++++++++++++++++++++++++++++++++++++++++++++++ context_test.go | 79 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 167 insertions(+) diff --git a/context.go b/context.go index 7b57150..16cffec 100644 --- a/context.go +++ b/context.go @@ -187,6 +187,94 @@ func (c *Context) MustGet(key string) interface{} { panic("Key \"" + key + "\" does not exist") } +// GetString returns the value associated with the key as a string. +func (c *Context) GetString(key string) (s string) { + if val, ok := c.Get(key); ok && val != nil { + s, _ = val.(string) + } + return +} + +// GetBool returns the value associated with the key as a boolean. +func (c *Context) GetBool(key string) (b bool) { + if val, ok := c.Get(key); ok && val != nil { + b, _ = val.(bool) + } + return +} + +// GetInt returns the value associated with the key as an integer. +func (c *Context) GetInt(key string) (i int) { + if val, ok := c.Get(key); ok && val != nil { + i, _ = val.(int) + } + return +} + +// GetInt64 returns the value associated with the key as an integer. +func (c *Context) GetInt64(key string) (i64 int64) { + if val, ok := c.Get(key); ok && val != nil { + i64, _ = val.(int64) + } + return +} + +// GetFloat64 returns the value associated with the key as a float64. +func (c *Context) GetFloat64(key string) (f64 float64) { + if val, ok := c.Get(key); ok && val != nil { + f64, _ = val.(float64) + } + return +} + +// GetTime returns the value associated with the key as time. +func (c *Context) GetTime(key string) (t time.Time) { + if val, ok := c.Get(key); ok && val != nil { + t, _ = val.(time.Time) + } + return +} + +// GetDuration returns the value associated with the key as a duration. +func (c *Context) GetDuration(key string) (d time.Duration) { + if val, ok := c.Get(key); ok && val != nil { + d, _ = val.(time.Duration) + } + return +} + +// GetStringSlice returns the value associated with the key as a slice of strings. +func (c *Context) GetStringSlice(key string) (ss []string) { + if val, ok := c.Get(key); ok && val != nil { + ss, _ = val.([]string) + } + return +} + +// GetStringMap returns the value associated with the key as a map of interfaces. +func (c *Context) GetStringMap(key string) (sm map[string]interface{}) { + if val, ok := c.Get(key); ok && val != nil { + sm, _ = val.(map[string]interface{}) + } + return +} + +// GetStringMapString returns the value associated with the key as a map of strings. +func (c *Context) GetStringMapString(key string) (sms map[string]string) { + if val, ok := c.Get(key); ok && val != nil { + sms, _ = val.(map[string]string) + } + return +} + +// GetStringMapStringSlice returns the value associated with the key as a map to a slice of strings. +func (c *Context) GetStringMapStringSlice(key string) (smss map[string][]string) { + if val, ok := c.Get(key); ok && val != nil { + smss, _ = val.(map[string][]string) + } + return +} + /************************************/ /************ INPUT DATA ************/ /************************************/ diff --git a/context_test.go b/context_test.go index 131d891..7923a4b 100644 --- a/context_test.go +++ b/context_test.go @@ -168,6 +168,85 @@ func TestContextSetGetValues(t *testing.T) { } +func TestContextGetString(t *testing.T) { + c, _ := CreateTestContext(httptest.NewRecorder()) + c.Set("string", "this is a string") + assert.Equal(t, "this is a string", c.GetString("string")) +} + +func TestContextSetGetBool(t *testing.T) { + c, _ := CreateTestContext(httptest.NewRecorder()) + c.Set("bool", true) + assert.Equal(t, true, c.GetBool("bool")) +} + +func TestContextGetInt(t *testing.T) { + c, _ := CreateTestContext(httptest.NewRecorder()) + c.Set("int", 1) + assert.Equal(t, 1, c.GetInt("int")) +} + +func TestContextGetInt64(t *testing.T) { + c, _ := CreateTestContext(httptest.NewRecorder()) + c.Set("int64", int64(42424242424242)) + assert.Equal(t, int64(42424242424242), c.GetInt64("int64")) +} + +func TestContextGetFloat64(t *testing.T) { + c, _ := CreateTestContext(httptest.NewRecorder()) + c.Set("float64", 4.2) + assert.Equal(t, 4.2, c.GetFloat64("float64")) +} + +func TestContextGetTime(t *testing.T) { + c, _ := CreateTestContext(httptest.NewRecorder()) + t1, _ := time.Parse("1/2/2006 15:04:05", "01/01/2017 12:00:00") + c.Set("time", t1) + assert.Equal(t, t1, c.GetTime("time")) +} + +func TestContextGetDuration(t *testing.T) { + c, _ := CreateTestContext(httptest.NewRecorder()) + c.Set("duration", time.Second) + assert.Equal(t, time.Second, c.GetDuration("duration")) +} + +func TestContextGetStringSlice(t *testing.T) { + c, _ := CreateTestContext(httptest.NewRecorder()) + c.Set("slice", []string{"foo"}) + assert.Equal(t, []string{"foo"}, c.GetStringSlice("slice")) +} + +func TestContextGetStringMap(t *testing.T) { + c, _ := CreateTestContext(httptest.NewRecorder()) + var m = make(map[string]interface{}) + m["foo"] = 1 + c.Set("map", m) + + assert.Equal(t, m, c.GetStringMap("map")) + assert.Equal(t, 1, c.GetStringMap("map")["foo"]) +} + +func TestContextGetStringMapString(t *testing.T) { + c, _ := CreateTestContext(httptest.NewRecorder()) + var m = make(map[string]string) + m["foo"] = "bar" + c.Set("map", m) + + assert.Equal(t, m, c.GetStringMapString("map")) + assert.Equal(t, "bar", c.GetStringMapString("map")["foo"]) +} + +func TestContextGetStringMapStringSlice(t *testing.T) { + c, _ := CreateTestContext(httptest.NewRecorder()) + var m = make(map[string][]string) + m["foo"] = []string{"foo"} + c.Set("map", m) + + assert.Equal(t, m, c.GetStringMapStringSlice("map")) + assert.Equal(t, []string{"foo"}, c.GetStringMapStringSlice("map")["foo"]) +} + func TestContextCopy(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) c.index = 2 From 3f95933c3d8ab010c1454b35b6dd8b534948f928 Mon Sep 17 00:00:00 2001 From: collinmsn <4130944@qq.com> Date: Fri, 2 Jun 2017 16:00:55 +0800 Subject: [PATCH 67/95] Add method to return main handler (#930) Fix #928 Method to get main handler is desired --- context.go | 5 +++++ context_test.go | 13 +++++++++++++ 2 files changed, 18 insertions(+) diff --git a/context.go b/context.go index 16cffec..971675d 100644 --- a/context.go +++ b/context.go @@ -85,6 +85,11 @@ func (c *Context) HandlerName() string { return nameOfFunction(c.handlers.Last()) } +// Handler returns the main handler. +func (c *Context) Handler() HandlerFunc { + return c.handlers.Last() +} + /************************************/ /*********** FLOW CONTROL ***********/ /************************************/ diff --git a/context_test.go b/context_test.go index 7923a4b..66a55cd 100644 --- a/context_test.go +++ b/context_test.go @@ -12,6 +12,7 @@ import ( "mime/multipart" "net/http" "net/http/httptest" + "reflect" "strings" "testing" "time" @@ -277,6 +278,18 @@ func handlerNameTest(c *Context) { } +var handlerTest HandlerFunc = func(c *Context) { + +} + +func TestContextHandler(t *testing.T) { + c, _ := CreateTestContext(httptest.NewRecorder()) + c.handlers = HandlersChain{func(c *Context) {}, handlerTest} + + assert.Equal(t, reflect.ValueOf(handlerTest).Pointer(), reflect.ValueOf(c.Handler()).Pointer()) +} + + func TestContextQuery(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) c.Request, _ = http.NewRequest("GET", "http://example.com/?foo=bar&page=10&id=", nil) From 53295a75a40173538e28ef9c9a8b696b2a5f8117 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Tue, 6 Jun 2017 20:35:07 -0500 Subject: [PATCH 68/95] refactor: update protobuf version (#936) --- vendor/vendor.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/vendor/vendor.json b/vendor/vendor.json index 2bc5e19..cf9c860 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -16,10 +16,10 @@ "revisionTime": "2014-06-27T04:00:55Z" }, { - "checksumSHA1": "kBeNcaKk56FguvPSUCEaH6AxpRc=", + "checksumSHA1": "qlPUeFabwF4RKAOF1H+yBFU1Veg=", "path": "github.com/golang/protobuf/proto", - "revision": "8ee79997227bf9b34611aee7946ae64735e6fd93", - "revisionTime": "2016-11-17T03:31:26Z" + "revision": "5a0f697c9ed9d68fef0116532c6e05cfeae00e55", + "revisionTime": "2017-06-01T23:02:30Z" }, { "checksumSHA1": "9if9IBLsxkarJ804NPWAzgskIAk=", From d922143bc562320340be6caf2be6bd3d4da64d2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Mon, 12 Jun 2017 10:40:15 +0800 Subject: [PATCH 69/95] lint code for import (#939) --- binding/binding_test.go | 3 +-- binding/json.go | 1 - binding/protobuf.go | 4 ++-- examples/app-engine/hello.go | 3 ++- gin_integration_test.go | 2 +- middleware_test.go | 1 - 6 files changed, 6 insertions(+), 8 deletions(-) diff --git a/binding/binding_test.go b/binding/binding_test.go index cf00594..d7cdf77 100644 --- a/binding/binding_test.go +++ b/binding/binding_test.go @@ -12,9 +12,8 @@ import ( "github.com/gin-gonic/gin/binding/example" "github.com/golang/protobuf/proto" - "github.com/ugorji/go/codec" - "github.com/stretchr/testify/assert" + "github.com/ugorji/go/codec" ) type FooStruct struct { diff --git a/binding/json.go b/binding/json.go index 6e53244..486b973 100644 --- a/binding/json.go +++ b/binding/json.go @@ -6,7 +6,6 @@ package binding import ( "encoding/json" - "net/http" ) diff --git a/binding/protobuf.go b/binding/protobuf.go index 9f95622..c7eb84e 100644 --- a/binding/protobuf.go +++ b/binding/protobuf.go @@ -5,10 +5,10 @@ package binding import ( - "github.com/golang/protobuf/proto" - "io/ioutil" "net/http" + + "github.com/golang/protobuf/proto" ) type protobufBinding struct{} diff --git a/examples/app-engine/hello.go b/examples/app-engine/hello.go index a5e1796..da7e4ae 100644 --- a/examples/app-engine/hello.go +++ b/examples/app-engine/hello.go @@ -1,8 +1,9 @@ package hello import ( - "github.com/gin-gonic/gin" "net/http" + + "github.com/gin-gonic/gin" ) // This function's name is a must. App Engine uses it to drive the requests properly. diff --git a/gin_integration_test.go b/gin_integration_test.go index b4bde1a..2674a61 100644 --- a/gin_integration_test.go +++ b/gin_integration_test.go @@ -6,12 +6,12 @@ import ( "io/ioutil" "net" "net/http" + "net/http/httptest" "os" "testing" "time" "github.com/stretchr/testify/assert" - "net/http/httptest" ) func testRequest(t *testing.T, url string) { diff --git a/middleware_test.go b/middleware_test.go index c77f827..0c56f4e 100644 --- a/middleware_test.go +++ b/middleware_test.go @@ -7,7 +7,6 @@ package gin import ( "errors" "strings" - "testing" "github.com/stretchr/testify/assert" From 4ad3baf44e2e00c9982b0ce24fb2283e2184105f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Mon, 12 Jun 2017 14:04:52 +0800 Subject: [PATCH 70/95] Add license for some files (#940) --- benchmarks_test.go | 4 ++++ binding/default_validator.go | 4 ++++ context_appengine.go | 4 ++++ fs.go | 4 ++++ gin_integration_test.go | 4 ++++ test_helpers.go | 4 ++++ 6 files changed, 24 insertions(+) diff --git a/benchmarks_test.go b/benchmarks_test.go index ebe9804..a2c62ba 100644 --- a/benchmarks_test.go +++ b/benchmarks_test.go @@ -1,3 +1,7 @@ +// Copyright 2017 Manu Martinez-Almeida. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + package gin import ( diff --git a/binding/default_validator.go b/binding/default_validator.go index 760728b..19885f1 100644 --- a/binding/default_validator.go +++ b/binding/default_validator.go @@ -1,3 +1,7 @@ +// Copyright 2017 Manu Martinez-Almeida. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + package binding import ( diff --git a/context_appengine.go b/context_appengine.go index d9cb22f..38c189a 100644 --- a/context_appengine.go +++ b/context_appengine.go @@ -1,5 +1,9 @@ // +build appengine +// Copyright 2017 Manu Martinez-Almeida. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + package gin func init() { diff --git a/fs.go b/fs.go index 320fea6..1264582 100644 --- a/fs.go +++ b/fs.go @@ -1,3 +1,7 @@ +// Copyright 2017 Manu Martinez-Almeida. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + package gin import ( diff --git a/gin_integration_test.go b/gin_integration_test.go index 2674a61..0a6351d 100644 --- a/gin_integration_test.go +++ b/gin_integration_test.go @@ -1,3 +1,7 @@ +// Copyright 2017 Manu Martinez-Almeida. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + package gin import ( diff --git a/test_helpers.go b/test_helpers.go index 5bb3fa7..e7dd55f 100644 --- a/test_helpers.go +++ b/test_helpers.go @@ -1,3 +1,7 @@ +// Copyright 2017 Manu Martinez-Almeida. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + package gin import ( From 53799774f45a119234a89f791565e53aa26b4482 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Mon, 12 Jun 2017 17:01:09 +0800 Subject: [PATCH 71/95] unify license (#942) --- path.go | 7 +++---- path_test.go | 7 +++---- tree.go | 6 +++--- tree_test.go | 6 +++--- 4 files changed, 12 insertions(+), 14 deletions(-) diff --git a/path.go b/path.go index 43cdd04..7478a43 100644 --- a/path.go +++ b/path.go @@ -1,7 +1,6 @@ -// Copyright 2013 Julien Schmidt. All rights reserved. -// Based on the path package, Copyright 2009 The Go Authors. -// Use of this source code is governed by a BSD-style license that can be found -// in the LICENSE file. +// Copyright 2017 Manu Martinez-Almeida. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. package gin diff --git a/path_test.go b/path_test.go index 01cb758..0f2616c 100644 --- a/path_test.go +++ b/path_test.go @@ -1,7 +1,6 @@ -// Copyright 2013 Julien Schmidt. All rights reserved. -// Based on the path package, Copyright 2009 The Go Authors. -// Use of this source code is governed by a BSD-style license that can be found -// in the LICENSE file. +// Copyright 2017 Manu Martinez-Almeida. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. package gin diff --git a/tree.go b/tree.go index eee6bab..98bb9cd 100644 --- a/tree.go +++ b/tree.go @@ -1,6 +1,6 @@ -// Copyright 2013 Julien Schmidt. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be found -// in the LICENSE file. +// Copyright 2017 Manu Martinez-Almeida. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. package gin diff --git a/tree_test.go b/tree_test.go index 22f0131..f70d6e3 100644 --- a/tree_test.go +++ b/tree_test.go @@ -1,6 +1,6 @@ -// Copyright 2013 Julien Schmidt. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be found -// in the LICENSE file. +// Copyright 2017 Manu Martinez-Almeida. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. package gin From 4a2b55037f082f5473a4881f72e6c4f27009b182 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Tue, 13 Jun 2017 10:36:37 +0800 Subject: [PATCH 72/95] delete else keyword (#945) --- binding/binding.go | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/binding/binding.go b/binding/binding.go index d3a2c97..1dbf246 100644 --- a/binding/binding.go +++ b/binding/binding.go @@ -48,19 +48,19 @@ var ( func Default(method, contentType string) Binding { if method == "GET" { return Form - } else { - switch contentType { - case MIMEJSON: - return JSON - case MIMEXML, MIMEXML2: - return XML - case MIMEPROTOBUF: - return ProtoBuf - case MIMEMSGPACK, MIMEMSGPACK2: - return MsgPack - default: //case MIMEPOSTForm, MIMEMultipartPOSTForm: - return Form - } + } + + switch contentType { + case MIMEJSON: + return JSON + case MIMEXML, MIMEXML2: + return XML + case MIMEPROTOBUF: + return ProtoBuf + case MIMEMSGPACK, MIMEMSGPACK2: + return MsgPack + default: //case MIMEPOSTForm, MIMEMultipartPOSTForm: + return Form } } From 9ff8786b3d2746baca7dedd853fe1b5f27c0ab7c Mon Sep 17 00:00:00 2001 From: Eason Lin Date: Tue, 13 Jun 2017 10:37:20 +0800 Subject: [PATCH 73/95] Improve errors code coverage (#944) --- errors_test.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/errors_test.go b/errors_test.go index c9a3407..1aa0cdd 100644 --- a/errors_test.go +++ b/errors_test.go @@ -54,6 +54,13 @@ func TestError(t *testing.T) { "status": "200", "data": "some data", }) + + type customError struct { + status string + data string + } + err.SetMeta(customError{status: "200", data: "other data"}) + assert.Equal(t, err.JSON(), customError{status: "200", data: "other data"}) } func TestErrorSlice(t *testing.T) { From 1e1e4fc867b1729f600cf0e3ce096b815503e969 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Mon, 12 Jun 2017 21:50:42 -0500 Subject: [PATCH 74/95] Add Makefile to check the following thing (#947) * Add Makefile to check the following thing. * vet check * fmt check * embedmd check * misspell check Signed-off-by: Bo-Yi Wu * remove unused variable. Signed-off-by: Bo-Yi Wu --- .travis.yml | 11 ++++---- Makefile | 61 +++++++++++++++++++++++++++++++++++++++++ binding/form_mapping.go | 2 +- context.go | 2 +- context_test.go | 7 ++--- errors.go | 2 +- gin_integration_test.go | 2 +- 7 files changed, 74 insertions(+), 13 deletions(-) create mode 100644 Makefile diff --git a/.travis.yml b/.travis.yml index 644a178..6532a33 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,13 +10,14 @@ git: depth: 3 install: - - go get -v github.com/kardianos/govendor - - govendor sync - - go get -u github.com/campoy/embedmd + - make install script: - - embedmd -d README.md - - go test -v -covermode=count -coverprofile=coverage.out + - make vet + - make fmt-check + - make embedmd + - make misspell-check + - make test after_success: - bash <(curl -s https://codecov.io/bash) diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..9ba475a --- /dev/null +++ b/Makefile @@ -0,0 +1,61 @@ +GOFMT ?= gofmt "-s" +PACKAGES ?= $(shell go list ./... | grep -v /vendor/) +GOFILES := $(shell find . -name "*.go" -type f -not -path "./vendor/*") + +all: build + +install: deps + govendor sync + +.PHONY: test +test: + go test -v -covermode=count -coverprofile=coverage.out + +.PHONY: fmt +fmt: + $(GOFMT) -w $(GOFILES) + +.PHONY: fmt-check +fmt-check: + # get all go files and run go fmt on them + @diff=$$($(GOFMT) -d $(GOFILES)); \ + if [ -n "$$diff" ]; then \ + echo "Please run 'make fmt' and commit the result:"; \ + echo "$${diff}"; \ + exit 1; \ + fi; + +vet: + go vet $(PACKAGES) + +deps: + @hash govendor > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ + go get -u github.com/kardianos/govendor; \ + fi + @hash embedmd > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ + go get -u github.com/campoy/embedmd; \ + fi + +embedmd: + embedmd -d *.md + +.PHONY: lint +lint: + @hash golint > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ + go get -u github.com/golang/lint/golint; \ + fi + for PKG in $(PACKAGES); do golint -set_exit_status $$PKG || exit 1; done; + +.PHONY: misspell-check +misspell-check: + @hash misspell > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ + go get -u github.com/client9/misspell/cmd/misspell; \ + fi + misspell -error $(GOFILES) + +.PHONY: misspell +misspell: + @hash misspell > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ + go get -u github.com/client9/misspell/cmd/misspell; \ + fi + misspell -w $(GOFILES) diff --git a/binding/form_mapping.go b/binding/form_mapping.go index 1af8165..34f1267 100644 --- a/binding/form_mapping.go +++ b/binding/form_mapping.go @@ -152,7 +152,7 @@ func setTimeField(val string, structField reflect.StructField, value reflect.Val if timeFormat == "" { return errors.New("Blank time format") } - + if val == "" { value.Set(reflect.ValueOf(time.Time{})) return nil diff --git a/context.go b/context.go index 971675d..c5ff793 100644 --- a/context.go +++ b/context.go @@ -87,7 +87,7 @@ func (c *Context) HandlerName() string { // Handler returns the main handler. func (c *Context) Handler() HandlerFunc { - return c.handlers.Last() + return c.handlers.Last() } /************************************/ diff --git a/context_test.go b/context_test.go index 66a55cd..e8cea05 100644 --- a/context_test.go +++ b/context_test.go @@ -283,13 +283,12 @@ var handlerTest HandlerFunc = func(c *Context) { } func TestContextHandler(t *testing.T) { - c, _ := CreateTestContext(httptest.NewRecorder()) - c.handlers = HandlersChain{func(c *Context) {}, handlerTest} + c, _ := CreateTestContext(httptest.NewRecorder()) + c.handlers = HandlersChain{func(c *Context) {}, handlerTest} - assert.Equal(t, reflect.ValueOf(handlerTest).Pointer(), reflect.ValueOf(c.Handler()).Pointer()) + assert.Equal(t, reflect.ValueOf(handlerTest).Pointer(), reflect.ValueOf(c.Handler()).Pointer()) } - func TestContextQuery(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) c.Request, _ = http.NewRequest("GET", "http://example.com/?foo=bar&page=10&id=", nil) diff --git a/errors.go b/errors.go index 694b428..896af6f 100644 --- a/errors.go +++ b/errors.go @@ -80,7 +80,7 @@ func (msg *Error) IsType(flags ErrorType) bool { return (msg.Type & flags) > 0 } -// Returns a readonly copy filterd the byte. +// Returns a readonly copy filtered the byte. // ie ByType(gin.ErrorTypePublic) returns a slice of errors with type=ErrorTypePublic func (a errorMsgs) ByType(typ ErrorType) errorMsgs { if len(a) == 0 { diff --git a/gin_integration_test.go b/gin_integration_test.go index 0a6351d..f45dd6c 100644 --- a/gin_integration_test.go +++ b/gin_integration_test.go @@ -20,8 +20,8 @@ import ( func testRequest(t *testing.T, url string) { resp, err := http.Get(url) - defer resp.Body.Close() assert.NoError(t, err) + defer resp.Body.Close() body, ioerr := ioutil.ReadAll(resp.Body) assert.NoError(t, ioerr) From 9ee5afff482e1bade5ff6061cfbdf3e74f2089d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Tue, 13 Jun 2017 11:35:10 +0800 Subject: [PATCH 75/95] fix markdown render style (#943) --- ginS/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ginS/README.md b/ginS/README.md index ac563a2..ef9225e 100644 --- a/ginS/README.md +++ b/ginS/README.md @@ -1,4 +1,4 @@ -#Gin Default Server +# Gin Default Server This is API experiment for Gin. From 6dac8c8a486dab63a4198e8421a2ae6a263129b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Tue, 13 Jun 2017 11:36:05 +0800 Subject: [PATCH 76/95] delete else keyword (#948) --- gin.go | 4 ++-- tree.go | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/gin.go b/gin.go index dcca2b3..992d2f1 100644 --- a/gin.go +++ b/gin.go @@ -326,8 +326,8 @@ func (engine *Engine) handleHTTPRequest(context *Context) { context.Next() context.writermem.WriteHeaderNow() return - - } else if httpMethod != "CONNECT" && path != "/" { + } + if httpMethod != "CONNECT" && path != "/" { if tsr && engine.RedirectTrailingSlash { redirectTrailingSlash(context) return diff --git a/tree.go b/tree.go index 98bb9cd..d8aa312 100644 --- a/tree.go +++ b/tree.go @@ -432,7 +432,8 @@ walk: // Outer loop for walking the tree if handlers = n.handlers; handlers != nil { return - } else if len(n.children) == 1 { + } + if len(n.children) == 1 { // No handle found. Check if a handle for this path + a // trailing slash exists for TSR recommendation n = n.children[0] From 1a627c24494bc5b50712234e1718b88e1e441b60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Thu, 15 Jun 2017 10:14:25 +0800 Subject: [PATCH 77/95] fmt code style (#950) --- examples/realtime-advanced/routes.go | 1 - examples/realtime-advanced/stats.go | 12 +++++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/examples/realtime-advanced/routes.go b/examples/realtime-advanced/routes.go index b187756..86da9be 100644 --- a/examples/realtime-advanced/routes.go +++ b/examples/realtime-advanced/routes.go @@ -11,7 +11,6 @@ import ( ) func rateLimit(c *gin.Context) { - ip := c.ClientIP() value := int(ips.Add(ip, 1)) if value%50 == 0 { diff --git a/examples/realtime-advanced/stats.go b/examples/realtime-advanced/stats.go index 554ab86..4bca3ae 100644 --- a/examples/realtime-advanced/stats.go +++ b/examples/realtime-advanced/stats.go @@ -8,11 +8,13 @@ import ( "github.com/manucorporat/stats" ) -var ips = stats.New() -var messages = stats.New() -var users = stats.New() -var mutexStats sync.RWMutex -var savedStats map[string]uint64 +var ( + ips = stats.New() + messages = stats.New() + users = stats.New() + mutexStats sync.RWMutex + savedStats map[string]uint64 +) func statsWorker() { c := time.Tick(1 * time.Second) From 0681715dda0cb0df85cbaf9d877a53fe01c7b674 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Fri, 16 Jun 2017 09:07:42 +0800 Subject: [PATCH 78/95] add Makefile for example (#951) --- examples/realtime-advanced/Makefile | 10 ++++++++++ examples/realtime-chat/Makefile | 9 +++++++++ 2 files changed, 19 insertions(+) create mode 100644 examples/realtime-advanced/Makefile create mode 100644 examples/realtime-chat/Makefile diff --git a/examples/realtime-advanced/Makefile b/examples/realtime-advanced/Makefile new file mode 100644 index 0000000..104ce80 --- /dev/null +++ b/examples/realtime-advanced/Makefile @@ -0,0 +1,10 @@ +all: deps build + +.PHONY: deps +deps: + go get -d -v github.com/dustin/go-broadcast/... + go get -d -v github.com/manucorporat/stats/... + +.PHONY: build +build: deps + go build -o realtime-advanced main.go rooms.go routes.go stats.go diff --git a/examples/realtime-chat/Makefile b/examples/realtime-chat/Makefile new file mode 100644 index 0000000..dea583d --- /dev/null +++ b/examples/realtime-chat/Makefile @@ -0,0 +1,9 @@ +all: deps build + +.PHONY: deps +deps: + go get -d -v github.com/dustin/go-broadcast/... + +.PHONY: build +build: deps + go build -o realtime-chat main.go rooms.go template.go From 3b8150c83c5304be4dd6b49f212cda6fe3aeed10 Mon Sep 17 00:00:00 2001 From: Javier Provecho Fernandez Date: Tue, 27 Jun 2017 22:54:42 +0200 Subject: [PATCH 79/95] Revert "unify license (#942)" This reverts commit 53799774f45a119234a89f791565e53aa26b4482. --- path.go | 7 ++++--- path_test.go | 7 ++++--- tree.go | 6 +++--- tree_test.go | 6 +++--- 4 files changed, 14 insertions(+), 12 deletions(-) diff --git a/path.go b/path.go index 7478a43..43cdd04 100644 --- a/path.go +++ b/path.go @@ -1,6 +1,7 @@ -// Copyright 2017 Manu Martinez-Almeida. All rights reserved. -// Use of this source code is governed by a MIT style -// license that can be found in the LICENSE file. +// Copyright 2013 Julien Schmidt. All rights reserved. +// Based on the path package, Copyright 2009 The Go Authors. +// Use of this source code is governed by a BSD-style license that can be found +// in the LICENSE file. package gin diff --git a/path_test.go b/path_test.go index 0f2616c..01cb758 100644 --- a/path_test.go +++ b/path_test.go @@ -1,6 +1,7 @@ -// Copyright 2017 Manu Martinez-Almeida. All rights reserved. -// Use of this source code is governed by a MIT style -// license that can be found in the LICENSE file. +// Copyright 2013 Julien Schmidt. All rights reserved. +// Based on the path package, Copyright 2009 The Go Authors. +// Use of this source code is governed by a BSD-style license that can be found +// in the LICENSE file. package gin diff --git a/tree.go b/tree.go index d8aa312..ec9dd14 100644 --- a/tree.go +++ b/tree.go @@ -1,6 +1,6 @@ -// Copyright 2017 Manu Martinez-Almeida. All rights reserved. -// Use of this source code is governed by a MIT style -// license that can be found in the LICENSE file. +// Copyright 2013 Julien Schmidt. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be found +// in the LICENSE file. package gin diff --git a/tree_test.go b/tree_test.go index f70d6e3..22f0131 100644 --- a/tree_test.go +++ b/tree_test.go @@ -1,6 +1,6 @@ -// Copyright 2017 Manu Martinez-Almeida. All rights reserved. -// Use of this source code is governed by a MIT style -// license that can be found in the LICENSE file. +// Copyright 2013 Julien Schmidt. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be found +// in the LICENSE file. package gin From 80942e67a8fabde099edd7307245e69b42f52f2c Mon Sep 17 00:00:00 2001 From: Javier Provecho Fernandez Date: Tue, 27 Jun 2017 22:58:49 +0200 Subject: [PATCH 80/95] docs(license): add julienschmidt/httprouter license URL --- path.go | 2 +- path_test.go | 2 +- tree.go | 2 +- tree_test.go | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/path.go b/path.go index 43cdd04..d7e7458 100644 --- a/path.go +++ b/path.go @@ -1,7 +1,7 @@ // Copyright 2013 Julien Schmidt. All rights reserved. // Based on the path package, Copyright 2009 The Go Authors. // Use of this source code is governed by a BSD-style license that can be found -// in the LICENSE file. +// at https://github.com/julienschmidt/httprouter/blob/master/LICENSE. package gin diff --git a/path_test.go b/path_test.go index 01cb758..bf2e5f6 100644 --- a/path_test.go +++ b/path_test.go @@ -1,7 +1,7 @@ // Copyright 2013 Julien Schmidt. All rights reserved. // Based on the path package, Copyright 2009 The Go Authors. // Use of this source code is governed by a BSD-style license that can be found -// in the LICENSE file. +// at https://github.com/julienschmidt/httprouter/blob/master/LICENSE package gin diff --git a/tree.go b/tree.go index ec9dd14..a39f43b 100644 --- a/tree.go +++ b/tree.go @@ -1,6 +1,6 @@ // Copyright 2013 Julien Schmidt. All rights reserved. // Use of this source code is governed by a BSD-style license that can be found -// in the LICENSE file. +// at https://github.com/julienschmidt/httprouter/blob/master/LICENSE package gin diff --git a/tree_test.go b/tree_test.go index 22f0131..c0edd42 100644 --- a/tree_test.go +++ b/tree_test.go @@ -1,6 +1,6 @@ // Copyright 2013 Julien Schmidt. All rights reserved. // Use of this source code is governed by a BSD-style license that can be found -// in the LICENSE file. +// at https://github.com/julienschmidt/httprouter/blob/master/LICENSE package gin From 0b3f6d13be802e727ce8324bbeb934d45e867a93 Mon Sep 17 00:00:00 2001 From: Javier Provecho Fernandez Date: Tue, 27 Jun 2017 23:03:29 +0200 Subject: [PATCH 81/95] docs(readme): switch back import from gopkg to github --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c6d3ead..7a0df31 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ $ cat test.go ```go package main -import "gopkg.in/gin-gonic/gin.v1" +import "github.com/gin-gonic/gin" func main() { r := gin.Default() From dc016d0bcbaaae90af78e3038103fd77565d2959 Mon Sep 17 00:00:00 2001 From: Javier Provecho Fernandez Date: Tue, 27 Jun 2017 23:16:18 +0200 Subject: [PATCH 82/95] docs(readme): s/gopkg.in.../github.com.../ --- README.md | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 7a0df31..7556d48 100644 --- a/README.md +++ b/README.md @@ -88,13 +88,13 @@ BenchmarkZeus_GithubAll | 2000 | 944234 | 300688 | 2648 1. Download and install it: ```sh -$ go get gopkg.in/gin-gonic/gin.v1 +$ go get github.com/gin-gonic/gin ``` 2. Import it in your code: ```go -import "gopkg.in/gin-gonic/gin.v1" +import "github.com/gin-gonic/gin" ``` 3. (Optional) Import `net/http`. This is required for example if using constants such as `http.StatusOK`. @@ -103,14 +103,6 @@ import "gopkg.in/gin-gonic/gin.v1" import "net/http" ``` -4. (Optional) Use latest changes (note: they may be broken and/or unstable): - -```sh -$ GIN_PATH=$GOPATH/src/gopkg.in/gin-gonic/gin.v1 -$ git -C $GIN_PATH checkout develop -$ git -C $GIN_PATH pull origin develop -``` - ## API Examples ### Using GET, POST, PUT, PATCH, DELETE and OPTIONS @@ -457,7 +449,7 @@ func startPage(c *gin.Context) { package main import ( - "gopkg.in/gin-gonic/gin.v1" + "github.com/gin-gonic/gin" ) type LoginForm struct { From 9a79e3f14410ede6073c587b31d72630444896d0 Mon Sep 17 00:00:00 2001 From: Javier Provecho Fernandez Date: Tue, 27 Jun 2017 23:17:02 +0200 Subject: [PATCH 83/95] fix(import): switch sse import from gopkg to github --- context.go | 2 +- context_test.go | 2 +- middleware_test.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/context.go b/context.go index c5ff793..7e86212 100644 --- a/context.go +++ b/context.go @@ -16,9 +16,9 @@ import ( "strings" "time" + "github.com/gin-contrib/sse" "github.com/gin-gonic/gin/binding" "github.com/gin-gonic/gin/render" - "gopkg.in/gin-contrib/sse.v0" ) // Content-Type MIME of the most common data formats diff --git a/context_test.go b/context_test.go index e8cea05..9a294c5 100644 --- a/context_test.go +++ b/context_test.go @@ -17,9 +17,9 @@ import ( "testing" "time" + "github.com/gin-contrib/sse" "github.com/stretchr/testify/assert" "golang.org/x/net/context" - "gopkg.in/gin-contrib/sse.v0" ) var _ context.Context = &Context{} diff --git a/middleware_test.go b/middleware_test.go index 0c56f4e..5572e79 100644 --- a/middleware_test.go +++ b/middleware_test.go @@ -9,8 +9,8 @@ import ( "strings" "testing" + "github.com/gin-contrib/sse" "github.com/stretchr/testify/assert" - "gopkg.in/gin-contrib/sse.v0" ) func TestMiddlewareGeneralCase(t *testing.T) { From c8af2768650c3ee5eb43ab774aa9169bd836c5a8 Mon Sep 17 00:00:00 2001 From: Javier Provecho Fernandez Date: Tue, 27 Jun 2017 23:31:11 +0200 Subject: [PATCH 84/95] chore(vendor): update vendor.json --- vendor/vendor.json | 34 ++++++++++++++++++++++++++-------- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/vendor/vendor.json b/vendor/vendor.json index cf9c860..e520540 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -1,5 +1,5 @@ { - "comment": "v1.1.4", + "comment": "v1.2", "ignore": "test", "package": [ { @@ -15,6 +15,18 @@ "revision": "3bdf6d4a7164a50bc19d5f230e2981d87d2584f1", "revisionTime": "2014-06-27T04:00:55Z" }, + { + "checksumSHA1": "QeKwBtN2df+j+4stw3bQJ6yO4EY=", + "path": "github.com/gin-contrib/sse", + "revision": "22d885f9ecc78bf4ee5d72b937e4bbcdc58e8cae", + "revisionTime": "2017-01-09T09:34:21Z" + }, + { + "checksumSHA1": "FJKrZuFmeLJp8HDeJc6UkIDBPUw=", + "path": "github.com/gin-gonic/autotls", + "revision": "5b3297bdcee778ff3bbdc99ab7c41e1c2677d22d", + "revisionTime": "2017-04-16T09:39:34Z" + }, { "checksumSHA1": "qlPUeFabwF4RKAOF1H+yBFU1Veg=", "path": "github.com/golang/protobuf/proto", @@ -53,6 +65,18 @@ "revision": "c88ee250d0221a57af388746f5cf03768c21d6e2", "revisionTime": "2017-02-15T20:11:44Z" }, + { + "checksumSHA1": "W0j4I7QpxXlChjyhAojZmFjU6Bg=", + "path": "golang.org/x/crypto/acme", + "revision": "adbae1b6b6fb4b02448a0fc0dbbc9ba2b95b294d", + "revisionTime": "2017-06-19T06:03:41Z" + }, + { + "checksumSHA1": "TrKJW+flz7JulXU3sqnBJjGzgQc=", + "path": "golang.org/x/crypto/acme/autocert", + "revision": "adbae1b6b6fb4b02448a0fc0dbbc9ba2b95b294d", + "revisionTime": "2017-06-19T06:03:41Z" + }, { "checksumSHA1": "9jjO5GjLa0XF/nfWihF02RoH4qc=", "comment": "release-branch.go1.7", @@ -61,17 +85,11 @@ "revisionTime": "2016-10-18T08:54:36Z" }, { - "checksumSHA1": "/oZpHfYc+ZgOwYAhlvcMhmETYpw=", + "checksumSHA1": "TVEkpH3gq84iQ39I4R+mlDwjuVI=", "path": "golang.org/x/sys/unix", "revision": "99f16d856c9836c42d24e7ab64ea72916925fa97", "revisionTime": "2017-03-08T15:04:45Z" }, - { - "checksumSHA1": "pyAPYrymvmZl0M/Mr4yfjOQjA8I=", - "path": "gopkg.in/gin-contrib/sse.v0", - "revision": "22d885f9ecc78bf4ee5d72b937e4bbcdc58e8cae", - "revisionTime": "2017-01-09T09:34:21Z" - }, { "checksumSHA1": "39V1idWER42Lmcmg2Uy40wMzOlo=", "comment": "v8.18.1", From cbf414d6002e3e088ad433efb288433513a48319 Mon Sep 17 00:00:00 2001 From: Javier Provecho Fernandez Date: Wed, 28 Jun 2017 00:44:33 +0200 Subject: [PATCH 85/95] docs(readme): add example on custom delims #860 --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index 7556d48..3397d6b 100644 --- a/README.md +++ b/README.md @@ -620,6 +620,14 @@ func main() { } ``` +You may use custom delims + +```go + r := gin.Default() + r.Delims("{[{", "}]}") + r.LoadHTMLGlob("/path/to/templates")) +``` + ### Multitemplate Gin allow by default use only one html.Template. Check [a multitemplate render](https://github.com/gin-contrib/multitemplate) for using features like go 1.6 `block template`. From 68aa2c38da9582aa37b3ee3b099251e5bfee3598 Mon Sep 17 00:00:00 2001 From: Javier Provecho Fernandez Date: Wed, 28 Jun 2017 00:46:35 +0200 Subject: [PATCH 86/95] fix(context): switch deprecated bindwith for mustbindwith --- context.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/context.go b/context.go index f7c4f17..5c4d27d 100644 --- a/context.go +++ b/context.go @@ -430,12 +430,12 @@ func (c *Context) MultipartForm() (*multipart.Form, error) { // Like ParseBody() but this method also writes a 400 error if the json is not valid. func (c *Context) Bind(obj interface{}) error { b := binding.Default(c.Request.Method, c.ContentType()) - return c.BindWith(obj, b) + return c.MustBindWith(obj, b) } -// BindJSON is a shortcut for c.BindWith(obj, binding.JSON) +// BindJSON is a shortcut for c.MustBindWith(obj, binding.JSON) func (c *Context) BindJSON(obj interface{}) error { - return c.BindWith(obj, binding.JSON) + return c.MustBindWith(obj, binding.JSON) } // MustBindWith binds the passed struct pointer using the specified binding From 544b8b4dc815f0ff2f409e311bf8bf9c38eca163 Mon Sep 17 00:00:00 2001 From: Javier Provecho Fernandez Date: Wed, 28 Jun 2017 00:53:28 +0200 Subject: [PATCH 87/95] docs(readme): switch deprecated bindwith for mustbindwith --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3397d6b..1276c2e 100644 --- a/README.md +++ b/README.md @@ -461,7 +461,7 @@ func main() { router := gin.Default() router.POST("/login", func(c *gin.Context) { // you can bind multipart form with explicit binding declaration: - // c.BindWith(&form, binding.Form) + // c.MustBindWith(&form, binding.Form) // or you can simply use autobinding with Bind method: var form LoginForm // in this case proper binding will be automatically selected From 470fe6263724af05ac10a0d6d23e27494a53646e Mon Sep 17 00:00:00 2001 From: Javier Provecho Fernandez Date: Wed, 28 Jun 2017 00:53:53 +0200 Subject: [PATCH 88/95] docs(changelog): update v1.2 changelog --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 441df24..aba6417 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,13 +6,20 @@ - [NEW] Add support for Let's Encrypt via gin-gonic/autotls - [NEW] Improve README examples and add extra at examples folder - [NEW] Improved support with App Engine +- [NEW] Add custom template delimiters, see #860 +- [NEW] Add \*context.Handler(), see #928 - [NEW] Add \*context.GetRawData() - [NEW] Add \*context.GetHeader() (request) - [NEW] Add \*context.AbortWithStatusJSON() (JSON content type) +- [NEW] Add \*context.Keys type cast helpers +- [NEW] Add \*context.ShouldBindWith() +- [NEW] Add \*context.MustBindWith() +- [DEPRECATE] On next release: \*context.BindWith(), see #855 - [FIX] Refactor render - [FIX] Reworked tests - [FIX] logger now supports cygwin - [FIX] Use X-Forwarded-For before X-Real-Ip +- [FIX] time.Time binding (#904) ### Gin 1.1.4 From e38c36ee0d6d2131b263b571a8a7e2e8e2a708dc Mon Sep 17 00:00:00 2001 From: Thomas Boerger Date: Fri, 30 Jun 2017 21:22:40 +0200 Subject: [PATCH 89/95] Made funMaps for the templates configurable and hot-reloadable --- gin.go | 18 ++++++++++++------ render/html.go | 14 +++++++++----- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/gin.go b/gin.go index 992d2f1..c4118a4 100644 --- a/gin.go +++ b/gin.go @@ -47,6 +47,7 @@ type ( RouterGroup delims render.Delims HTMLRender render.HTMLRender + FuncMap template.FuncMap allNoRoute HandlersChain allNoMethod HandlersChain noRoute HandlersChain @@ -112,6 +113,7 @@ func New() *Engine { basePath: "/", root: true, }, + FuncMap: template.FuncMap{}, RedirectTrailingSlash: true, RedirectFixedPath: false, HandleMethodNotAllowed: false, @@ -147,19 +149,19 @@ func (engine *Engine) Delims(left, right string) *Engine { func (engine *Engine) LoadHTMLGlob(pattern string) { if IsDebugging() { - debugPrintLoadTemplate(template.Must(template.New("").Delims(engine.delims.Left, engine.delims.Right).ParseGlob(pattern))) - engine.HTMLRender = render.HTMLDebug{Glob: pattern, Delims: engine.delims} + debugPrintLoadTemplate(template.Must(template.New("").Delims(engine.delims.Left, engine.delims.Right).Funcs(engine.FuncMap).ParseGlob(pattern))) + engine.HTMLRender = render.HTMLDebug{Glob: pattern, FuncMap: engine.FuncMap, Delims: engine.delims} } else { - templ := template.Must(template.New("").Delims(engine.delims.Left, engine.delims.Right).ParseGlob(pattern)) + templ := template.Must(template.New("").Delims(engine.delims.Left, engine.delims.Right).Funcs(engine.FuncMap).ParseGlob(pattern)) engine.SetHTMLTemplate(templ) } } func (engine *Engine) LoadHTMLFiles(files ...string) { if IsDebugging() { - engine.HTMLRender = render.HTMLDebug{Files: files, Delims: engine.delims} + engine.HTMLRender = render.HTMLDebug{Files: files, FuncMap: engine.FuncMap, Delims: engine.delims} } else { - templ := template.Must(template.New("").Delims(engine.delims.Left, engine.delims.Right).ParseFiles(files...)) + templ := template.Must(template.New("").Delims(engine.delims.Left, engine.delims.Right).Funcs(engine.FuncMap).ParseFiles(files...)) engine.SetHTMLTemplate(templ) } } @@ -169,7 +171,11 @@ func (engine *Engine) SetHTMLTemplate(templ *template.Template) { debugPrintWARNINGSetHTMLTemplate() } - engine.HTMLRender = render.HTMLProduction{Template: templ} + engine.HTMLRender = render.HTMLProduction{Template: templ.Funcs(engine.FuncMap)} +} + +func (engine *Engine) SetFuncMap(funcMap template.FuncMap) { + engine.FuncMap = funcMap } // NoRoute adds handlers for NoRoute. It return a 404 code by default. diff --git a/render/html.go b/render/html.go index 3ea5e08..cf91219 100644 --- a/render/html.go +++ b/render/html.go @@ -25,9 +25,10 @@ type ( } HTMLDebug struct { - Files []string - Glob string - Delims Delims + Files []string + Glob string + Delims Delims + FuncMap template.FuncMap } HTML struct { @@ -55,11 +56,14 @@ func (r HTMLDebug) Instance(name string, data interface{}) Render { } } func (r HTMLDebug) loadTemplate() *template.Template { + if r.FuncMap == nil { + r.FuncMap = template.FuncMap{} + } if len(r.Files) > 0 { - return template.Must(template.New("").Delims(r.Delims.Left, r.Delims.Right).ParseFiles(r.Files...)) + return template.Must(template.New("").Delims(r.Delims.Left, r.Delims.Right).Funcs(r.FuncMap).ParseFiles(r.Files...)) } if len(r.Glob) > 0 { - return template.Must(template.New("").Delims(r.Delims.Left, r.Delims.Right).ParseGlob(r.Glob)) + return template.Must(template.New("").Delims(r.Delims.Left, r.Delims.Right).Funcs(r.FuncMap).ParseGlob(r.Glob)) } panic("the HTML debug render was created without files or glob pattern") } From a40699e07f71b33c3c4a064af3468c09d333688c Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Sun, 2 Jul 2017 12:38:05 +0800 Subject: [PATCH 90/95] add FuncMap testing Signed-off-by: Bo-Yi Wu --- fixtures/basic/raw.tmpl | 1 + gin_test.go | 29 +++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+) create mode 100644 fixtures/basic/raw.tmpl diff --git a/fixtures/basic/raw.tmpl b/fixtures/basic/raw.tmpl new file mode 100644 index 0000000..8bc7570 --- /dev/null +++ b/fixtures/basic/raw.tmpl @@ -0,0 +1 @@ +Date: {[{.now | formatAsDate}]} diff --git a/gin_test.go b/gin_test.go index 3ab4697..3cd134c 100644 --- a/gin_test.go +++ b/gin_test.go @@ -6,6 +6,7 @@ package gin import ( "fmt" + "html/template" "io/ioutil" "net/http" "reflect" @@ -15,6 +16,11 @@ import ( "github.com/stretchr/testify/assert" ) +func formatAsDate(t time.Time) string { + year, month, day := t.Date() + return fmt.Sprintf("%d/%02d/%02d", year, month, day) +} + func setupHTMLFiles(t *testing.T) func() { go func() { router := New() @@ -32,12 +38,21 @@ func setupHTMLFiles(t *testing.T) func() { func setupHTMLGlob(t *testing.T) func() { go func() { + SetMode(DebugMode) router := New() router.Delims("{[{", "}]}") + router.SetFuncMap(template.FuncMap{ + "formatAsDate": formatAsDate, + }) router.LoadHTMLGlob("./fixtures/basic/*") router.GET("/test", func(c *Context) { c.HTML(http.StatusOK, "hello.tmpl", map[string]string{"name": "world"}) }) + router.GET("/raw", func(c *Context) { + c.HTML(http.StatusOK, "raw.tmpl", map[string]interface{}{ + "now": time.Date(2017, 07, 01, 0, 0, 0, 0, time.UTC), + }) + }) router.Run(":8888") }() t.Log("waiting 1 second for server startup") @@ -59,6 +74,20 @@ func TestLoadHTMLGlob(t *testing.T) { td() } +func TestLoadHTMLFromFuncMap(t *testing.T) { + time.Now() + td := setupHTMLGlob(t) + res, err := http.Get("http://127.0.0.1:8888/raw") + if err != nil { + fmt.Println(err) + } + + resp, _ := ioutil.ReadAll(res.Body) + assert.Equal(t, "Date: 2017/07/01\n", string(resp[:])) + + td() +} + // func (engine *Engine) LoadHTMLFiles(files ...string) { // func (engine *Engine) RunTLS(addr string, cert string, key string) error { From 6d071c1d360141168de629c4b686175df2efa61f Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Sun, 2 Jul 2017 13:06:43 +0800 Subject: [PATCH 91/95] Add load html file and func map. Signed-off-by: Bo-Yi Wu --- gin_test.go | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/gin_test.go b/gin_test.go index 3cd134c..bdf5a9a 100644 --- a/gin_test.go +++ b/gin_test.go @@ -23,12 +23,21 @@ func formatAsDate(t time.Time) string { func setupHTMLFiles(t *testing.T) func() { go func() { + SetMode(TestMode) router := New() router.Delims("{[{", "}]}") - router.LoadHTMLFiles("./fixtures/basic/hello.tmpl") + router.SetFuncMap(template.FuncMap{ + "formatAsDate": formatAsDate, + }) + router.LoadHTMLFiles("./fixtures/basic/hello.tmpl", "./fixtures/basic/raw.tmpl") router.GET("/test", func(c *Context) { c.HTML(http.StatusOK, "hello.tmpl", map[string]string{"name": "world"}) }) + router.GET("/raw", func(c *Context) { + c.HTML(http.StatusOK, "raw.tmpl", map[string]interface{}{ + "now": time.Date(2017, 07, 01, 0, 0, 0, 0, time.UTC), + }) + }) router.Run(":8888") }() t.Log("waiting 1 second for server startup") @@ -74,7 +83,7 @@ func TestLoadHTMLGlob(t *testing.T) { td() } -func TestLoadHTMLFromFuncMap(t *testing.T) { +func TestLoadHTMLGlobFromFuncMap(t *testing.T) { time.Now() td := setupHTMLGlob(t) res, err := http.Get("http://127.0.0.1:8888/raw") @@ -129,6 +138,20 @@ func TestLoadHTMLFiles(t *testing.T) { td() } +func TestLoadHTMLFilesFuncMap(t *testing.T) { + time.Now() + td := setupHTMLFiles(t) + res, err := http.Get("http://127.0.0.1:8888/raw") + if err != nil { + fmt.Println(err) + } + + resp, _ := ioutil.ReadAll(res.Body) + assert.Equal(t, "Date: 2017/07/01\n", string(resp[:])) + + td() +} + func TestLoadHTMLReleaseMode(t *testing.T) { } From 76a63b257d9989ec4eb365c6b984246944f0305a Mon Sep 17 00:00:00 2001 From: Eason Lin Date: Sun, 2 Jul 2017 15:29:29 +0800 Subject: [PATCH 92/95] Improve debug code coverage (#963) * Improve debug.go code coverage * lint code --- debug_test.go | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/debug_test.go b/debug_test.go index deceaa6..a402ba7 100644 --- a/debug_test.go +++ b/debug_test.go @@ -7,6 +7,7 @@ package gin import ( "bytes" "errors" + "html/template" "io" "log" "os" @@ -66,6 +67,25 @@ func TestDebugPrintRoutes(t *testing.T) { assert.Regexp(t, `^\[GIN-debug\] GET /path/to/route/:param --> (.*/vendor/)?github.com/gin-gonic/gin.handlerNameTest \(2 handlers\)\n$`, w.String()) } +func TestDebugPrintLoadTemplate(t *testing.T) { + var w bytes.Buffer + setup(&w) + defer teardown() + + templ := template.Must(template.New("").Delims("{[{", "}]}").ParseGlob("./fixtures/basic/*")) + debugPrintLoadTemplate(templ) + assert.Equal(t, w.String(), "[GIN-debug] Loaded HTML Templates (2): \n\t- \n\t- hello.tmpl\n\n") +} + +func TestDebugPrintWARNINGSetHTMLTemplate(t *testing.T) { + var w bytes.Buffer + setup(&w) + defer teardown() + + debugPrintWARNINGSetHTMLTemplate() + assert.Equal(t, w.String(), "[GIN-debug] [WARNING] Since SetHTMLTemplate() is NOT thread-safe. It should only be called\nat initialization. ie. before any route is registered or the router is listening in a socket:\n\n\trouter := gin.Default()\n\trouter.SetHTMLTemplate(template) // << good place\n\n") +} + func setup(w io.Writer) { SetMode(DebugMode) log.SetOutput(w) From bea012a17ffee4cc1f16352cf567e7791f226530 Mon Sep 17 00:00:00 2001 From: Javier Provecho Fernandez Date: Sun, 2 Jul 2017 10:55:39 +0200 Subject: [PATCH 93/95] docs(changelog): add #962 template func maps --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index aba6417..ee485ec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ - [NEW] Improve README examples and add extra at examples folder - [NEW] Improved support with App Engine - [NEW] Add custom template delimiters, see #860 +- [NEW] Add Template Func Maps, see #962 - [NEW] Add \*context.Handler(), see #928 - [NEW] Add \*context.GetRawData() - [NEW] Add \*context.GetHeader() (request) @@ -14,6 +15,7 @@ - [NEW] Add \*context.Keys type cast helpers - [NEW] Add \*context.ShouldBindWith() - [NEW] Add \*context.MustBindWith() +- [NEW] Add \*engine.SetFuncMap() - [DEPRECATE] On next release: \*context.BindWith(), see #855 - [FIX] Refactor render - [FIX] Reworked tests From 2535b46bab759cf0a43a7e0b9c6e6621e89eca4b Mon Sep 17 00:00:00 2001 From: Javier Provecho Fernandez Date: Sun, 2 Jul 2017 11:07:22 +0200 Subject: [PATCH 94/95] docs(readme): add template func maps example copied from gin_test.go @appleboy commit --- README.md | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/README.md b/README.md index 1276c2e..029606b 100644 --- a/README.md +++ b/README.md @@ -628,6 +628,46 @@ You may use custom delims r.LoadHTMLGlob("/path/to/templates")) ``` +#### Add custom template funcs + +main.go + +```go + ... + + func formatAsDate(t time.Time) string { + year, month, day := t.Date() + return fmt.Sprintf("%d/%02d/%02d", year, month, day) + } + + ... + + router.SetFuncMap(template.FuncMap{ + "formatAsDate": formatAsDate, + }) + + ... + + router.GET("/raw", func(c *Context) { + c.HTML(http.StatusOK, "raw.tmpl", map[string]interface{}{ + "now": time.Date(2017, 07, 01, 0, 0, 0, 0, time.UTC), + }) + }) + + ... +``` + +raw.tmpl + +```html +Date: {[{.now | formatAsDate}]} +``` + +Result: +``` +Date: 2017/07/01 +``` + ### Multitemplate Gin allow by default use only one html.Template. Check [a multitemplate render](https://github.com/gin-contrib/multitemplate) for using features like go 1.6 `block template`. From a8ea89ea385b2e11f0147006147649d432919121 Mon Sep 17 00:00:00 2001 From: Javier Provecho Fernandez Date: Sun, 2 Jul 2017 11:17:42 +0200 Subject: [PATCH 95/95] fix(test): remove wildcard loadtemplate --- debug_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debug_test.go b/debug_test.go index a402ba7..c30081c 100644 --- a/debug_test.go +++ b/debug_test.go @@ -72,7 +72,7 @@ func TestDebugPrintLoadTemplate(t *testing.T) { setup(&w) defer teardown() - templ := template.Must(template.New("").Delims("{[{", "}]}").ParseGlob("./fixtures/basic/*")) + templ := template.Must(template.New("").Delims("{[{", "}]}").ParseGlob("./fixtures/basic/hello.tmpl")) debugPrintLoadTemplate(templ) assert.Equal(t, w.String(), "[GIN-debug] Loaded HTML Templates (2): \n\t- \n\t- hello.tmpl\n\n") }