From 30f014c7540d42fdc4035587621dbc2b3084bd19 Mon Sep 17 00:00:00 2001 From: Danieliu Date: Thu, 26 May 2016 16:21:50 +0800 Subject: [PATCH 001/119] 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 002/119] 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 003/119] 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 004/119] 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 005/119] 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 006/119] 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 007/119] 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 008/119] 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 009/119] 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 010/119] 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 011/119] 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 012/119] 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 013/119] 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 014/119] 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 93e36404a1d71ee738c9d5da1ea9a990621baf89 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Fri, 23 Dec 2016 09:12:13 +0800 Subject: [PATCH 015/119] 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 e48da263..e0307ea6 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 016/119] 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() From 713c3697f44fcb1b43291682133fc42d08a15f31 Mon Sep 17 00:00:00 2001 From: Javier Provecho Fernandez Date: Sun, 1 Jan 2017 11:54:37 +0100 Subject: [PATCH 017/119] Add instructions for pulling latest changes --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index e48da263..536022c0 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 018/119] 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 0beeef91..01c7cb4f 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 e488f423..fe22c492 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 019/119] 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 49345648..19d9088b 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 020/119] [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 19d9088b..7669edf7 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 021/119] 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 7669edf7..571c7ce7 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 00000000..2f4e9b52 --- /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 00000000..b8463601 --- /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 00000000..9acf5009 --- /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 00000000..b0c2a808 --- /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 022/119] 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 fe22c492..7dc3085b 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 023/119] 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 01c7cb4f..d19e1ee9 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 024/119] 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 571c7ce7..0c08556f 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 025/119] 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 0c08556f..c940a107 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 026/119] 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 186e3059..c7cbfe1f 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 027/119] 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 d19e1ee9..e937a4c7 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 7dc3085b..fd8ca4a2 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 273d003d..c77f8276 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 efa75d55..c296042c 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 8bfb23ac..a3cbda67 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 b652c4c8..3ee8b132 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 bd48d7d8..f874a351 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 3808666a..7e997374 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 5a9e280b..74cd26be 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 be22e6f2..cff1ac3e 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 46937d88..25d0ebd4 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 3c6b0edb..e754a3fd 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 028/119] 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 e937a4c7..601754fe 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 049da60f5114a479f04a668f3d8a6f3b44fe6658 Mon Sep 17 00:00:00 2001 From: Vasilyuk Vasiliy Date: Wed, 1 Feb 2017 18:49:24 +0400 Subject: [PATCH 029/119] Update gin version (#790) * 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. * Update gin version From 2ae8a25fc93067526eaa8d6cdf2b9a276a044986 Mon Sep 17 00:00:00 2001 From: Franz Bettag Date: Fri, 10 Feb 2017 15:44:55 +0100 Subject: [PATCH 030/119] 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 d084f2a3..12054090 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 031/119] 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 601754fe..b4f23952 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 fd8ca4a2..8d59f6e3 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 032/119] 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 e6a05bd0..1e5976fa 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 033/119] 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 1e5976fa..9fb59940 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 034/119] 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 07c83751..bc9e44cf 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 8d59f6e3..ebc5050e 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 035/119] 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 dc7397f1..d3a2c97e 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 72f60152..cf005948 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 00000000..69367175 --- /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 00000000..e6c13e58 --- /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 7e997374..46291421 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 c814ff6d..c48235c3 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 e754a3fd..00115c50 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 036/119] 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 c940a107..7046b98e 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 037/119] 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 6adf86bd..15d6ee9c 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 038/119] 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 12054090..61ac5c00 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 32f00983..7464d5d0 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 4f1da271..eee6bab3 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 ed21783c..22f01315 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 039/119] fix typo (#822) --- fs.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs.go b/fs.go index 6af3ded5..320fea67 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 040/119] Added Sourcegraph badge --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 7046b98e..ea184bb7 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 041/119] fix: gofmt error. (#833) --- mode.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mode.go b/mode.go index c600b7b5..e24dbdc2 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 042/119] 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 9fb59940..2f9385e3 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 c7cbfe1f..dc6f1415 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 00115c50..2bc5e194 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 043/119] 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 2f9385e3..644a178a 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 2feaf467..7ab7213d 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 181f75b3..6efe3ca4 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 ea184bb7..9bcc8f59 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 00000000..54778393 --- /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 00000000..060de081 --- /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 85216970..b4bde1af 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 044/119] 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 9bcc8f59..883f9d50 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 045/119] 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 b4f23952..f638e623 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 ebc5050e..52606281 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 046/119] 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 f638e623..18732699 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 0488a9b0..27e8f558 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 047/119] 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 f638e623..f519e5bb 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 52606281..0feeca89 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 048/119] 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 9f48f142..f3b636df 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 00000000..2c0d89d9 --- /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 61ac5c00..4a3a8332 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 2bc5e194..e3b3e91b 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 049/119] 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 4a3a8332..61ac5c00 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 00000000..dd8a65a9 --- /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 050/119] 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 2c0d89d9..896d3a9e 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 dd8a65a9..7599fdf0 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 051/119] 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 896d3a9e..9495c0ac 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 052/119] docs: update --- gin1.7.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gin1.7.go b/gin1.7.go index 7599fdf0..59de3d0b 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 053/119] 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 9495c0ac..49544c52 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 054/119] 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 59de3d0b..919c80f3 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 055/119] Update CHANGELOG.md --- CHANGELOG.md | 42 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 34 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 82f1bead..63ada3ef 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 056/119] bump version to 1.2 --- gin.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gin.go b/gin.go index 61ac5c00..6a8e193c 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 057/119] docs(readme) update changelog --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 63ada3ef..4ddbd9e4 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 058/119] 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 883f9d50..535ee4eb 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 00000000..fa9f4008 --- /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 00000000..ab8b81e7 --- /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 49544c52..00000000 --- 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 919c80f3..00000000 --- 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 059/119] 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 e3b3e91b..2bc5e194 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 060/119] docs: update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4ddbd9e4..441df247 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 d5b353c5d5a560322e6d96121c814115562501f7 Mon Sep 17 00:00:00 2001 From: Manu MA Date: Fri, 28 Apr 2017 12:59:23 +0200 Subject: [PATCH 061/119] Update README.md Github does not render README correctly, missing whitespaces --- README.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 82ea6a58..dc9c9e33 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,13 @@ -#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) + [![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) 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 6a3a8ae61bdb066ada6a0d270d8642c468149777 Mon Sep 17 00:00:00 2001 From: Andrey Nering Date: Wed, 3 May 2017 22:22:00 -0300 Subject: [PATCH 062/119] 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 bc9e44cf..1af8165e 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 063/119] 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 f519e5bb..7b57150d 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 781cbd19f02c42795bdc3adc2fda287d95f91500 Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Mon, 8 May 2017 19:04:22 -0500 Subject: [PATCH 064/119] panic if err is nil on c.Error A panic here provides a more informative stack trace than the panic which would otherwise occur while errors are being collected. --- context.go | 4 ++++ context_test.go | 7 +++++++ 2 files changed, 11 insertions(+) diff --git a/context.go b/context.go index 7b57150d..dd0dc5dc 100644 --- a/context.go +++ b/context.go @@ -144,7 +144,11 @@ func (c *Context) AbortWithError(code int, err error) *Error { // It's a good idea to call Error for each error that occurred during the resolution of a request. // A middleware can be used to collect all the errors // and push them to a database together, print a log, or append it in the HTTP response. +// Error will panic if err is nil. func (c *Context) Error(err error) *Error { + if err == nil { + panic("err is nil") + } var parsedError *Error switch err.(type) { case *Error: diff --git a/context_test.go b/context_test.go index 0feeca89..b4c1f08d 100644 --- a/context_test.go +++ b/context_test.go @@ -852,6 +852,13 @@ func TestContextError(t *testing.T) { assert.Equal(t, c.Errors[1].Type, ErrorTypePublic) assert.Equal(t, c.Errors.Last(), c.Errors[1]) + + defer func() { + if recover() == nil { + t.Error("didn't panic") + } + }() + c.Error(nil) } func TestContextTypedError(t *testing.T) { From 214a746b1d531cdacdcc0aa4039da119d4002837 Mon Sep 17 00:00:00 2001 From: Eason Lin Date: Wed, 24 May 2017 17:39:05 +0800 Subject: [PATCH 065/119] 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 0feeca89..fccca476 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 066/119] 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 fccca476..131d8914 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 067/119] 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 00000000..0767ef3f --- /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 6a8e193c..dcca2b3d 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 cc24bc92..3ab46976 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 a3cbda67..3ea5e08e 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 068/119] 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 7b57150d..16cffec8 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 131d8914..7923a4b0 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 069/119] 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 16cffec8..971675d8 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 7923a4b0..66a55cd2 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 070/119] 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 2bc5e194..cf9c8607 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 071/119] 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 cf005948..d7cdf77a 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 6e532443..486b9733 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 9f956228..c7eb84e9 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 a5e17962..da7e4ae4 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 b4bde1af..2674a618 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 c77f8276..0c56f4e4 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 072/119] 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 ebe9804c..a2c62ba3 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 760728bb..19885f16 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 d9cb22f3..38c189a0 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 320fea67..12645826 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 2674a618..0a6351d7 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 5bb3fa71..e7dd55f7 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 073/119] 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 43cdd047..7478a43e 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 01cb758a..0f2616c1 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 eee6bab3..98bb9cdb 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 22f01315..f70d6e35 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 074/119] 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 d3a2c97e..1dbf2460 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 075/119] 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 c9a3407b..1aa0cdde 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 076/119] 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 644a178a..6532a334 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 00000000..9ba475a4 --- /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 1af8165e..34f12678 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 971675d8..c5ff7937 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 66a55cd2..e8cea05d 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 694b428a..896af6fc 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 0a6351d7..f45dd6c1 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 077/119] 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 ac563a2c..ef9225e4 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 078/119] 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 dcca2b3d..992d2f15 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 98bb9cdb..d8aa3127 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 079/119] 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 b1877565..86da9bea 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 554ab86a..4bca3ae4 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 080/119] 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 00000000..104ce809 --- /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 00000000..dea583df --- /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 081/119] 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 7478a43e..43cdd047 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 0f2616c1..01cb758a 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 d8aa3127..ec9dd148 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 f70d6e35..22f01315 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 082/119] 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 43cdd047..d7e7458b 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 01cb758a..bf2e5f62 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 ec9dd148..a39f43bf 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 22f01315..c0edd42b 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 083/119] 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 c6d3ead6..7a0df312 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 084/119] 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 7a0df312..7556d485 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 085/119] 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 c5ff7937..7e862128 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 e8cea05d..9a294c52 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 0c56f4e4..5572e790 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 086/119] 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 cf9c8607..e520540b 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 087/119] 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 7556d485..3397d6bb 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 088/119] 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 f7c4f170..5c4d27dc 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 089/119] 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 3397d6bb..1276c2e2 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 090/119] docs(changelog): update v1.2 changelog --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 441df247..aba6417e 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 091/119] 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 992d2f15..c4118a4e 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 3ea5e08e..cf91219c 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 092/119] 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 00000000..8bc75703 --- /dev/null +++ b/fixtures/basic/raw.tmpl @@ -0,0 +1 @@ +Date: {[{.now | formatAsDate}]} diff --git a/gin_test.go b/gin_test.go index 3ab46976..3cd134c6 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 093/119] 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 3cd134c6..bdf5a9a9 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 094/119] 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 deceaa6e..a402ba7e 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 095/119] docs(changelog): add #962 template func maps --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index aba6417e..ee485ec3 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 096/119] 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 1276c2e2..029606b7 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 097/119] 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 a402ba7e..c30081c5 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") } From 0cbf0f48aaa3573141922d9dbdc57a15ea3aa626 Mon Sep 17 00:00:00 2001 From: Javier Provecho Fernandez Date: Sun, 2 Jul 2017 11:58:19 +0200 Subject: [PATCH 098/119] docs(readme): update contribution guide - change new prs base branch from develop to master --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 029606b7..76ec40db 100644 --- a/README.md +++ b/README.md @@ -963,7 +963,7 @@ func main() { - Please provide source code and commit sha if you found a bug. - Review existing issues and provide feedback or react to them. - With pull requests: - - Open your pull request against develop + - Open your pull request against master - Your pull request should have no more than two commits, if not you should squash them. - It should pass all tests in the available continuous integrations systems such as TravisCI. - You should add/modify tests to cover your proposed code changes. From 55dd6364cbdf2a4db3c2edbabc5377a708d5d282 Mon Sep 17 00:00:00 2001 From: Javier Provecho Fernandez Date: Sun, 2 Jul 2017 12:23:54 +0200 Subject: [PATCH 099/119] docs(readme): update to color logo --- README.md | 2 +- logo.jpg | Bin 12200 -> 0 bytes logo.png | Bin 0 -> 16831 bytes 3 files changed, 1 insertion(+), 1 deletion(-) delete mode 100644 logo.jpg create mode 100644 logo.png diff --git a/README.md b/README.md index 76ec40db..e35c2f94 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # 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) diff --git a/logo.jpg b/logo.jpg deleted file mode 100644 index bb51852e49aa24ab09ff7826b1f6c3dfdc99b16f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12200 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 Date: Sun, 2 Jul 2017 12:27:38 +0200 Subject: [PATCH 100/119] docs(readme): make logo smaller as before --- logo.png | Bin 16831 -> 21516 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/logo.png b/logo.png index c58c79864852b50763c2aab9106dd9ebd2290cf9..5f4888c4ba851dbfbae9dd1eb77da5e153b39048 100644 GIT binary patch literal 21516 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 DV^9bK literal 16831 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 Date: Sun, 2 Jul 2017 12:29:50 +0200 Subject: [PATCH 101/119] docs(readme): compress logo size --- logo.png | Bin 21516 -> 7987 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/logo.png b/logo.png index 5f4888c4ba851dbfbae9dd1eb77da5e153b39048..20b4fd6b5615c2d39231e7ad991abd5ce67ebcc8 100644 GIT binary patch literal 7987 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 literal 21516 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 DV^9bK From 5ce686113b29d5b275bcf3a46d5896a26ec23595 Mon Sep 17 00:00:00 2001 From: Javier Provecho Fernandez Date: Sun, 2 Jul 2017 12:46:36 +0200 Subject: [PATCH 102/119] docs(readme): add example on using vendor tool for version lock --- README.md | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/README.md b/README.md index e35c2f94..494cc8bf 100644 --- a/README.md +++ b/README.md @@ -103,6 +103,38 @@ import "github.com/gin-gonic/gin" import "net/http" ``` +### Use a vendor tool like [Govendor](https://github.com/kardianos/govendor) + +1. `go get` govendor + +```sh +$ go get github.com/kardianos/govendor +``` +2. Create your project folder and `cd` inside + +```sh +$ mkdir -p ~/go/src/github.com/myusername/project && cd "$_" +``` + +3. Vendor init your project and add gin + +```sh +$ govendor init +$ govendor add github.com/gin-gonic/gin@v1.2 +``` + +4. Copy a starting template inside your project + +```sh +$ cp ~/go/src/github.com/gin-gonic/gin/examples/basic/* . +``` + +5. Run your project + +```sh +$ go run main.go +``` + ## API Examples ### Using GET, POST, PUT, PATCH, DELETE and OPTIONS From 84fc31f9f8e18e44bf0be5aae6ed64b50a60a373 Mon Sep 17 00:00:00 2001 From: Javier Provecho Fernandez Date: Sun, 2 Jul 2017 14:35:10 +0200 Subject: [PATCH 103/119] docs(readme): move contribution guide to a separate file --- CONTRIBUTING.md | 12 ++++++++++++ README.md | 13 ------------- 2 files changed, 12 insertions(+), 13 deletions(-) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..eaa55a32 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,12 @@ +## Contributing + +- With issues: + - Use the search tool before opening a new issue. + - Please provide source code and commit sha if you found a bug. + - Review existing issues and provide feedback or react to them. +- With pull requests: + - Open your pull request against master + - Your pull request should have no more than two commits, if not you should squash them. + - It should pass all tests in the available continuous integrations systems such as TravisCI. + - 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. \ No newline at end of file diff --git a/README.md b/README.md index 494cc8bf..2200d9d9 100644 --- a/README.md +++ b/README.md @@ -988,19 +988,6 @@ func main() { } ``` -## Contributing - -- With issues: - - Use the search tool before opening a new issue. - - Please provide source code and commit sha if you found a bug. - - Review existing issues and provide feedback or react to them. -- With pull requests: - - Open your pull request against master - - Your pull request should have no more than two commits, if not you should squash them. - - It should pass all tests in the available continuous integrations systems such as TravisCI. - - 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. - ## Users Awesome project lists using [Gin](https://github.com/gin-gonic/gin) web framework. From 912a7df572192e140e27854a57b1ad2cf80d3037 Mon Sep 17 00:00:00 2001 From: Javier Provecho Fernandez Date: Sun, 2 Jul 2017 16:28:37 +0200 Subject: [PATCH 104/119] docs(readme): fix step in vendor tool example --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2200d9d9..8275383d 100644 --- a/README.md +++ b/README.md @@ -126,7 +126,7 @@ $ govendor add github.com/gin-gonic/gin@v1.2 4. Copy a starting template inside your project ```sh -$ cp ~/go/src/github.com/gin-gonic/gin/examples/basic/* . +$ curl https://raw.githubusercontent.com/gin-gonic/gin/master/examples/basic/main.go > main.go ``` 5. Run your project From de1fdfd1e54eb09f33b3a62a1b3ad6a955847351 Mon Sep 17 00:00:00 2001 From: Javier Provecho Fernandez Date: Sun, 2 Jul 2017 16:34:52 +0200 Subject: [PATCH 105/119] docs(readme): fix embedmd build --- CONTRIBUTING.md | 3 ++- README.md | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index eaa55a32..4583205e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,9 +4,10 @@ - Use the search tool before opening a new issue. - Please provide source code and commit sha if you found a bug. - Review existing issues and provide feedback or react to them. + - With pull requests: - Open your pull request against master - Your pull request should have no more than two commits, if not you should squash them. - It should pass all tests in the available continuous integrations systems such as TravisCI. - 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. \ No newline at end of file + - If your pull request contains a new feature, please document it on the README. diff --git a/README.md b/README.md index 8275383d..8b7d9b22 100644 --- a/README.md +++ b/README.md @@ -120,7 +120,7 @@ $ mkdir -p ~/go/src/github.com/myusername/project && cd "$_" ```sh $ govendor init -$ govendor add github.com/gin-gonic/gin@v1.2 +$ govendor fetch github.com/gin-gonic/gin@v1.2 ``` 4. Copy a starting template inside your project From 1923b3598377f53e0dd2e108e7f52761e3a69692 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Sun, 2 Jul 2017 09:41:06 -0500 Subject: [PATCH 106/119] update document format. (#964) Signed-off-by: Bo-Yi Wu --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4583205e..547b777a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -6,7 +6,7 @@ - Review existing issues and provide feedback or react to them. - With pull requests: - - Open your pull request against master + - Open your pull request against `master` - Your pull request should have no more than two commits, if not you should squash them. - It should pass all tests in the available continuous integrations systems such as TravisCI. - You should add/modify tests to cover your proposed code changes. From 89f0acc2f853be6e5af92a247e87a6d66613b819 Mon Sep 17 00:00:00 2001 From: Javier Provecho Fernandez Date: Sun, 2 Jul 2017 16:54:49 +0200 Subject: [PATCH 107/119] docs(auth): add missing logs for auth.go --- auth.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/auth.go b/auth.go index 125e659f..5f6d68e9 100644 --- a/auth.go +++ b/auth.go @@ -10,9 +10,11 @@ import ( "strconv" ) +// AuthUserKey is the cookie name for user credential in basic auth const AuthUserKey = "user" type ( + // Accounts defines a key/value for user/pass list of authorized logins Accounts map[string]string authPair struct { Value string From 114b71868ab46a299c1d9daa8aa20e2f5e7018b6 Mon Sep 17 00:00:00 2001 From: Javier Provecho Fernandez Date: Sun, 2 Jul 2017 16:59:21 +0200 Subject: [PATCH 108/119] refactor(deprecated): remove getcookie --- deprecated.go | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/deprecated.go b/deprecated.go index 27e8f558..7b50dc70 100644 --- a/deprecated.go +++ b/deprecated.go @@ -5,14 +5,10 @@ package gin 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) -} + "github.com/gin-gonic/gin/binding" +) // BindWith binds the passed struct pointer using the specified binding engine. // See the binding package. From 484acfc30396fa215376f9c2454d1bdf14c86c29 Mon Sep 17 00:00:00 2001 From: Javier Provecho Fernandez Date: Sun, 2 Jul 2017 17:05:21 +0200 Subject: [PATCH 109/119] docs(logger): add missing inline docs --- logger.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/logger.go b/logger.go index dc6f1415..a03cde6f 100644 --- a/logger.go +++ b/logger.go @@ -25,14 +25,17 @@ var ( disableColor = false ) +// DisableConsoleColor disables color output in the console func DisableConsoleColor() { disableColor = true } +// ErrorLogger returns a handlerfunc for any error type func ErrorLogger() HandlerFunc { return ErrorLoggerT(ErrorTypeAny) } +// ErrorLoggerT returns a handlerfunc for a given error type func ErrorLoggerT(typ ErrorType) HandlerFunc { return func(c *Context) { c.Next() From 92ddc7d2405b441d5fc9b64e938628dec1382759 Mon Sep 17 00:00:00 2001 From: Javier Provecho Fernandez Date: Sun, 2 Jul 2017 17:06:53 +0200 Subject: [PATCH 110/119] docs(test): add missing inline docs --- test_helpers.go | 1 + 1 file changed, 1 insertion(+) diff --git a/test_helpers.go b/test_helpers.go index e7dd55f7..2aed37f2 100644 --- a/test_helpers.go +++ b/test_helpers.go @@ -8,6 +8,7 @@ import ( "net/http" ) +// CreateTestContext returns a fresh engine and context for testing purposes func CreateTestContext(w http.ResponseWriter) (c *Context, r *Engine) { r = New() c = r.allocateContext() From f0b54a30238973ddb0bc9c9a58d8b7af038139b1 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Mon, 3 Jul 2017 00:55:21 -0500 Subject: [PATCH 111/119] fix: update autotls example. (#965) --- README.md | 2 +- examples/auto-tls/example2.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 8b7d9b22..de38839c 100644 --- a/README.md +++ b/README.md @@ -910,7 +910,7 @@ func main() { Cache: autocert.DirCache("/var/www/.cache"), } - log.Fatal(autotls.RunWithManager(r, m)) + log.Fatal(autotls.RunWithManager(r, &m)) } ``` diff --git a/examples/auto-tls/example2.go b/examples/auto-tls/example2.go index ab8b81e7..01718689 100644 --- a/examples/auto-tls/example2.go +++ b/examples/auto-tls/example2.go @@ -22,5 +22,5 @@ func main() { Cache: autocert.DirCache("/var/www/.cache"), } - log.Fatal(autotls.RunWithManager(r, m)) + log.Fatal(autotls.RunWithManager(r, &m)) } From df67c644795eaa050b378df62129eb08bd98d0ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Tue, 4 Jul 2017 10:08:41 +0800 Subject: [PATCH 112/119] fix cleanPath spell (#969) --- path.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/path.go b/path.go index d7e7458b..e3424b13 100644 --- a/path.go +++ b/path.go @@ -5,7 +5,7 @@ package gin -// CleanPath is the URL version of path.Clean, it returns a canonical URL path +// cleanPath is the URL version of path.Clean, it returns a canonical URL path // for p, eliminating . and .. elements. // // The following rules are applied iteratively until no further processing can From baf216e1675df21daf66e4b9e904e13116f09a35 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Tue, 4 Jul 2017 03:12:29 -0500 Subject: [PATCH 113/119] add error check. (#972) Signed-off-by: Bo-Yi Wu --- examples/upload-file/multiple/main.go | 18 +++++++++++++++--- examples/upload-file/single/main.go | 18 +++++++++++++++--- 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/examples/upload-file/multiple/main.go b/examples/upload-file/multiple/main.go index 2f4e9b52..22588348 100644 --- a/examples/upload-file/multiple/main.go +++ b/examples/upload-file/multiple/main.go @@ -17,16 +17,28 @@ func main() { email := c.PostForm("email") // Multipart form - form, _ := c.MultipartForm() + form, err := c.MultipartForm() + if err != nil { + c.String(http.StatusBadRequest, fmt.Sprintf("get form err: %s", err.Error())) + return + } files := form.File["files"] for _, file := range files { // Source - src, _ := file.Open() + src, err := file.Open() + if err != nil { + c.String(http.StatusBadRequest, fmt.Sprintf("file open err: %s", err.Error())) + return + } defer src.Close() // Destination - dst, _ := os.Create(file.Filename) + dst, err := os.Create(file.Filename) + if err != nil { + c.String(http.StatusBadRequest, fmt.Sprintf("Create file err: %s", err.Error())) + return + } defer dst.Close() // Copy diff --git a/examples/upload-file/single/main.go b/examples/upload-file/single/main.go index 9acf5009..1e9596cb 100644 --- a/examples/upload-file/single/main.go +++ b/examples/upload-file/single/main.go @@ -17,12 +17,24 @@ func main() { email := c.PostForm("email") // Source - file, _ := c.FormFile("file") - src, _ := file.Open() + file, err := c.FormFile("file") + if err != nil { + c.String(http.StatusBadRequest, fmt.Sprintf("get form err: %s", err.Error())) + return + } + src, err := file.Open() + if err != nil { + c.String(http.StatusBadRequest, fmt.Sprintf("file open err: %s", err.Error())) + return + } defer src.Close() // Destination - dst, _ := os.Create(file.Filename) + dst, err := os.Create(file.Filename) + if err != nil { + c.String(http.StatusBadRequest, fmt.Sprintf("Create file err: %s", err.Error())) + return + } defer dst.Close() // Copy From ecf1b37850461f680d48ba1cd1351e79b26f742a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Tue, 4 Jul 2017 16:17:54 +0800 Subject: [PATCH 114/119] add doc.go (#970) --- doc.go | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 doc.go diff --git a/doc.go b/doc.go new file mode 100644 index 00000000..01ac4a90 --- /dev/null +++ b/doc.go @@ -0,0 +1,6 @@ +/* +Package gin implements a HTTP web framework called gin. + +See https://gin-gonic.github.io/gin/ for more information about gin. +*/ +package gin // import "github.com/gin-gonic/gin" From 735748b359cee5e7f7597128580f6f94bbc0987f Mon Sep 17 00:00:00 2001 From: Eason Lin Date: Tue, 4 Jul 2017 16:36:12 +0800 Subject: [PATCH 115/119] fix(tests): TestDebugPrintLoadTemplate use assert.Regep instead of assert.Equal (#973) --- debug_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debug_test.go b/debug_test.go index c30081c5..2b010858 100644 --- a/debug_test.go +++ b/debug_test.go @@ -74,7 +74,7 @@ func TestDebugPrintLoadTemplate(t *testing.T) { 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") + assert.Regexp(t, `^\[GIN-debug\] Loaded HTML Templates \(2\): \n(\t- \n|\t- hello\.tmpl\n){2}\n`, w.String()) } func TestDebugPrintWARNINGSetHTMLTemplate(t *testing.T) { From 7d043cedb1ba33756ae9b69fa554a3062bff70d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Wed, 5 Jul 2017 09:55:50 +0800 Subject: [PATCH 116/119] improved swap (#974) --- tree.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tree.go b/tree.go index a39f43bf..55eab99e 100644 --- a/tree.go +++ b/tree.go @@ -105,9 +105,7 @@ func (n *node) incrementChildPrio(pos int) int { newPos := pos for newPos > 0 && n.children[newPos-1].priority < prio { // swap node positions - tmpN := n.children[newPos-1] - n.children[newPos-1] = n.children[newPos] - n.children[newPos] = tmpN + n.children[newPos-1], n.children[newPos] = n.children[newPos], n.children[newPos-1] newPos-- } From 22fc0284e3ae2101f44e8188dde8cad797ce51e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Wed, 5 Jul 2017 10:42:56 +0800 Subject: [PATCH 117/119] update readme (#976) --- README.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index de38839c..80158c8f 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,8 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi ![Gin console logger](https://gin-gonic.github.io/gin/other/console.png) ```sh -$ cat test.go +# assume the following codes in example.go file +$ cat example.go ``` ```go @@ -32,6 +33,11 @@ func main() { } ``` +``` +# run example.go and visit 0.0.0.0:8080/ping on browser +$ go run example.go +``` + ## Benchmarks Gin uses a custom version of [HttpRouter](https://github.com/julienschmidt/httprouter) From d535fcd59842094ea88307eafc48bc163c6a2f35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Wed, 5 Jul 2017 15:47:36 +0800 Subject: [PATCH 118/119] separate type define (#975) --- auth.go | 18 ++++---- errors.go | 14 +++--- fs.go | 15 +++---- gin.go | 109 ++++++++++++++++++++++----------------------- response_writer.go | 46 +++++++++---------- routergroup.go | 56 +++++++++++------------ 6 files changed, 125 insertions(+), 133 deletions(-) diff --git a/auth.go b/auth.go index 5f6d68e9..410fe5cc 100644 --- a/auth.go +++ b/auth.go @@ -13,15 +13,15 @@ import ( // AuthUserKey is the cookie name for user credential in basic auth const AuthUserKey = "user" -type ( - // Accounts defines a key/value for user/pass list of authorized logins - Accounts map[string]string - authPair struct { - Value string - User string - } - authPairs []authPair -) +// Accounts defines a key/value for user/pass list of authorized logins +type Accounts map[string]string + +type authPair struct { + Value string + User string +} + +type authPairs []authPair func (a authPairs) searchCredential(authValue string) (string, bool) { if len(authValue) == 0 { diff --git a/errors.go b/errors.go index 896af6fc..599d458a 100644 --- a/errors.go +++ b/errors.go @@ -23,15 +23,13 @@ const ( ErrorTypeNu = 2 ) -type ( - Error struct { - Err error - Type ErrorType - Meta interface{} - } +type Error struct { + Err error + Type ErrorType + Meta interface{} +} - errorMsgs []*Error -) +type errorMsgs []*Error var _ error = &Error{} diff --git a/fs.go b/fs.go index 12645826..c1693dd5 100644 --- a/fs.go +++ b/fs.go @@ -9,14 +9,13 @@ import ( "os" ) -type ( - onlyfilesFS struct { - fs http.FileSystem - } - neuteredReaddirFile struct { - http.File - } -) +type onlyfilesFS struct { + fs http.FileSystem +} + +type neuteredReaddirFile struct { + http.File +} // Dir returns a http.Filesystem that can be used by http.FileServer(). It is used internally // in router.Static(). diff --git a/gin.go b/gin.go index c4118a4e..e7b1fc15 100644 --- a/gin.go +++ b/gin.go @@ -33,67 +33,66 @@ func (c HandlersChain) Last() HandlerFunc { return nil } -type ( - RoutesInfo []RouteInfo - RouteInfo struct { - Method string - Path string - Handler string - } +type RouteInfo struct { + Method string + Path string + Handler string +} - // Engine is the framework's instance, it contains the muxer, middleware and configuration settings. - // Create an instance of Engine, by using New() or Default() - Engine struct { - RouterGroup - delims render.Delims - HTMLRender render.HTMLRender - FuncMap template.FuncMap - allNoRoute HandlersChain - allNoMethod HandlersChain - noRoute HandlersChain - noMethod HandlersChain - pool sync.Pool - trees methodTrees +type RoutesInfo []RouteInfo - // Enables automatic redirection if the current route can't be matched but a - // handler for the path with (without) the trailing slash exists. - // For example if /foo/ is requested but a route only exists for /foo, the - // client is redirected to /foo with http status code 301 for GET requests - // and 307 for all other request methods. - RedirectTrailingSlash bool +// Engine is the framework's instance, it contains the muxer, middleware and configuration settings. +// Create an instance of Engine, by using New() or Default() +type Engine struct { + RouterGroup + delims render.Delims + HTMLRender render.HTMLRender + FuncMap template.FuncMap + allNoRoute HandlersChain + allNoMethod HandlersChain + noRoute HandlersChain + noMethod HandlersChain + pool sync.Pool + trees methodTrees - // If enabled, the router tries to fix the current request path, if no - // handle is registered for it. - // First superfluous path elements like ../ or // are removed. - // Afterwards the router does a case-insensitive lookup of the cleaned path. - // If a handle can be found for this route, the router makes a redirection - // to the corrected path with status code 301 for GET requests and 307 for - // all other request methods. - // For example /FOO and /..//Foo could be redirected to /foo. - // RedirectTrailingSlash is independent of this option. - RedirectFixedPath bool + // Enables automatic redirection if the current route can't be matched but a + // handler for the path with (without) the trailing slash exists. + // For example if /foo/ is requested but a route only exists for /foo, the + // client is redirected to /foo with http status code 301 for GET requests + // and 307 for all other request methods. + RedirectTrailingSlash bool - // If enabled, the router checks if another method is allowed for the - // current route, if the current request can not be routed. - // If this is the case, the request is answered with 'Method Not Allowed' - // and HTTP status code 405. - // If no other Method is allowed, the request is delegated to the NotFound - // handler. - HandleMethodNotAllowed bool - ForwardedByClientIP bool + // If enabled, the router tries to fix the current request path, if no + // handle is registered for it. + // First superfluous path elements like ../ or // are removed. + // Afterwards the router does a case-insensitive lookup of the cleaned path. + // If a handle can be found for this route, the router makes a redirection + // to the corrected path with status code 301 for GET requests and 307 for + // all other request methods. + // For example /FOO and /..//Foo could be redirected to /foo. + // RedirectTrailingSlash is independent of this option. + RedirectFixedPath bool - // #726 #755 If enabled, it will thrust some headers starting with - // 'X-AppEngine...' for better integration with that PaaS. - AppEngine bool + // If enabled, the router checks if another method is allowed for the + // current route, if the current request can not be routed. + // If this is the case, the request is answered with 'Method Not Allowed' + // and HTTP status code 405. + // If no other Method is allowed, the request is delegated to the NotFound + // handler. + HandleMethodNotAllowed bool + ForwardedByClientIP 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 - } -) + // #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 +} var _ IRouter = &Engine{} diff --git a/response_writer.go b/response_writer.go index fcbe230d..c9d07c33 100644 --- a/response_writer.go +++ b/response_writer.go @@ -16,36 +16,34 @@ const ( defaultStatus = 200 ) -type ( - ResponseWriter interface { - http.ResponseWriter - http.Hijacker - http.Flusher - http.CloseNotifier +type ResponseWriter interface { + http.ResponseWriter + http.Hijacker + http.Flusher + http.CloseNotifier - // Returns the HTTP response status code of the current request. - Status() int + // Returns the HTTP response status code of the current request. + Status() int - // Returns the number of bytes already written into the response http body. - // See Written() - Size() int + // Returns the number of bytes already written into the response http body. + // See Written() + Size() int - // Writes the string into the response body. - WriteString(string) (int, error) + // Writes the string into the response body. + WriteString(string) (int, error) - // Returns true if the response body was already written. - Written() bool + // Returns true if the response body was already written. + Written() bool - // Forces to write the http header (status code + headers). - WriteHeaderNow() - } + // Forces to write the http header (status code + headers). + WriteHeaderNow() +} - responseWriter struct { - http.ResponseWriter - size int - status int - } -) +type responseWriter struct { + http.ResponseWriter + size int + status int +} var _ ResponseWriter = &responseWriter{} diff --git a/routergroup.go b/routergroup.go index f22729bb..89ec89aa 100644 --- a/routergroup.go +++ b/routergroup.go @@ -11,39 +11,37 @@ import ( "strings" ) -type ( - IRouter interface { - IRoutes - Group(string, ...HandlerFunc) *RouterGroup - } +type IRouter interface { + IRoutes + Group(string, ...HandlerFunc) *RouterGroup +} - IRoutes interface { - Use(...HandlerFunc) IRoutes +type IRoutes interface { + Use(...HandlerFunc) IRoutes - Handle(string, string, ...HandlerFunc) IRoutes - Any(string, ...HandlerFunc) IRoutes - GET(string, ...HandlerFunc) IRoutes - POST(string, ...HandlerFunc) IRoutes - DELETE(string, ...HandlerFunc) IRoutes - PATCH(string, ...HandlerFunc) IRoutes - PUT(string, ...HandlerFunc) IRoutes - OPTIONS(string, ...HandlerFunc) IRoutes - HEAD(string, ...HandlerFunc) IRoutes + Handle(string, string, ...HandlerFunc) IRoutes + Any(string, ...HandlerFunc) IRoutes + GET(string, ...HandlerFunc) IRoutes + POST(string, ...HandlerFunc) IRoutes + DELETE(string, ...HandlerFunc) IRoutes + PATCH(string, ...HandlerFunc) IRoutes + PUT(string, ...HandlerFunc) IRoutes + OPTIONS(string, ...HandlerFunc) IRoutes + HEAD(string, ...HandlerFunc) IRoutes - StaticFile(string, string) IRoutes - Static(string, string) IRoutes - StaticFS(string, http.FileSystem) IRoutes - } + StaticFile(string, string) IRoutes + Static(string, string) IRoutes + StaticFS(string, http.FileSystem) IRoutes +} - // RouterGroup is used internally to configure router, a RouterGroup is associated with a prefix - // and an array of handlers (middleware) - RouterGroup struct { - Handlers HandlersChain - basePath string - engine *Engine - root bool - } -) +// RouterGroup is used internally to configure router, a RouterGroup is associated with a prefix +// and an array of handlers (middleware) +type RouterGroup struct { + Handlers HandlersChain + basePath string + engine *Engine + root bool +} var _ IRouter = &RouterGroup{} From 31ac11a29891bf2d7c2da20054507f3fb5350a25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Wed, 5 Jul 2017 17:37:24 +0800 Subject: [PATCH 119/119] update comment method (#977) --- auth.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/auth.go b/auth.go index 410fe5cc..7ebf5576 100644 --- a/auth.go +++ b/auth.go @@ -89,6 +89,6 @@ func secureCompare(given, actual string) bool { if subtle.ConstantTimeEq(int32(len(given)), int32(len(actual))) == 1 { return subtle.ConstantTimeCompare([]byte(given), []byte(actual)) == 1 } - /* Securely compare actual to itself to keep constant time, but always return false */ + // Securely compare actual to itself to keep constant time, but always return false return subtle.ConstantTimeCompare([]byte(actual), []byte(actual)) == 1 && false }