From 30f014c7540d42fdc4035587621dbc2b3084bd19 Mon Sep 17 00:00:00 2001 From: Danieliu Date: Thu, 26 May 2016 16:21:50 +0800 Subject: [PATCH 01/15] 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 d56bc628..ff246a44 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/15] 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 8023dc5a..d6fddf41 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/15] 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 5d3b6a4e..50161750 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/15] 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 97d4957c..c7a1fc89 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 7d8020c3..00000000 --- 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 00000000..5bb3fa71 --- /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/15] 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 5d3b6a4e..d0fa7cb3 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 97d4957c..a1f9fa3d 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/15] 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 7716bfae..694b428a 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/15] 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 df001a40..ffda5ab1 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 00000000..d9cb22f3 --- /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 01ee6b83..d46be714 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 60f4ab29..70cc11b6 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/15] 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 60f4ab29..75adc473 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/15] 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 a9c828a2..00000000 --- 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 00000000..3f60f407 --- /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/15] 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 3f60f407..3c6b0edb 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/15] Update .travis.yml --- .travis.yml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 53f436f4..e6a05bd0 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/15] 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 32e6058d..b652c4c8 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/15] 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 32e596e0..81e18841 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 2ad1f474..7c064c90 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 3101d523..273d003d 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 7a6ffb7d..c814ff6d 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/15] 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 b215d3f8..e48da263 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 80f2bd3c..984c06ab 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 81904d2b..186e3059 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 7c064c90..6adf86bd 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 5cc3d5955f5a03c13daa878c5ef28c33d9a18800 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Sat, 24 Dec 2016 12:25:01 +0800 Subject: [PATCH 15/15] 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 d2b98b65..0beeef91 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 81e18841..e488f423 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()