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 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/README.md b/README.md index 82ea6a58..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() @@ -466,7 +469,7 @@ func main() { ####HTML rendering -Using LoadHTMLTemplates() +Using LoadHTMLGlob() or LoadHTMLFiles() ```go func main() { diff --git a/context.go b/context.go index df001a40..0beeef91 100644 --- a/context.go +++ b/context.go @@ -8,6 +8,7 @@ import ( "errors" "io" "math" + "mime/multipart" "net" "net/http" "net/url" @@ -17,7 +18,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 @@ -31,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. @@ -50,8 +53,6 @@ type Context struct { Accepted []string } -var _ context.Context = &Context{} - /************************************/ /********** CONTEXT CREATION ********/ /************************************/ @@ -166,9 +167,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 } @@ -296,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 } @@ -308,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 @@ -353,9 +364,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..e488f423 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) { @@ -50,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() @@ -74,7 +108,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 +129,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 +145,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 +166,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 +185,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 +196,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 +231,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) @@ -270,7 +304,7 @@ func TestContextQueryAndPostForm(t *testing.T) { } func TestContextPostFormMultipart(t *testing.T) { - c, _, _ := CreateTestContext() + c, _ := CreateTestContext(httptest.NewRecorder()) c.Request = createMultipartRequest() var obj struct { @@ -334,13 +368,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") @@ -350,30 +384,36 @@ 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) - 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 // 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"}) - 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 // 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) @@ -384,7 +424,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) @@ -398,7 +439,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) @@ -409,7 +452,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) @@ -420,7 +465,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) @@ -432,7 +479,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) @@ -441,7 +490,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", @@ -456,7 +507,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") @@ -468,7 +521,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) @@ -477,7 +532,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") @@ -494,7 +549,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") }) @@ -506,7 +563,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() @@ -516,7 +575,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() @@ -526,7 +587,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") }) @@ -537,7 +598,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() }) @@ -546,7 +607,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") @@ -556,7 +617,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") @@ -569,7 +630,7 @@ func TestContextNegotiationFormatCustum(t *testing.T) { } func TestContextIsAborted(t *testing.T) { - c, _, _ := CreateTestContext() + c, _ := CreateTestContext(httptest.NewRecorder()) assert.False(t, c.IsAborted()) c.Abort() @@ -585,7 +646,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) @@ -596,7 +659,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")) @@ -622,7 +685,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) @@ -636,7 +699,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) @@ -645,11 +710,12 @@ 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 ") 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,11 +727,19 @@ 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) { - c, _, _ := CreateTestContext() + c, _ := CreateTestContext(httptest.NewRecorder()) c.Request, _ = http.NewRequest("POST", "/", nil) c.Request.Header.Set("Content-Type", "application/json; charset=utf-8") @@ -673,7 +747,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) @@ -688,7 +762,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 @@ -703,7 +779,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 { @@ -722,7 +800,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/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() } 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/gin.go b/gin.go index ac6b1431..54980f6e 100644 --- a/gin.go +++ b/gin.go @@ -15,10 +15,11 @@ 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") +var defaultAppEngine bool type HandlerFunc func(*Context) type HandlersChain []HandlerFunc @@ -78,8 +79,13 @@ type ( // handler. HandleMethodNotAllowed bool ForwardedByClientIP bool + UseRawPath bool UnescapePathValues bool + + // #726 #755 If enabled, it will thrust some headers starting with + // 'X-AppEngine...' for better integration with that PaaS. + AppEngine bool } ) @@ -103,6 +109,7 @@ func New() *Engine { RedirectFixedPath: false, HandleMethodNotAllowed: false, ForwardedByClientIP: true, + AppEngine: defaultAppEngine, trees: make(methodTrees, 0, 9), } engine.RouterGroup.engine = engine 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/logger.go b/logger.go index 7c2a72be..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 } @@ -92,7 +97,7 @@ func LoggerWithWriter(out io.Writer, notlogged ...string) HandlerFunc { statusColor, statusCode, reset, latency, clientIP, - methodColor, reset, method, + methodColor, method, reset, path, comment, ) diff --git a/logger_test.go b/logger_test.go index 2ad1f474..6adf86bd 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) { @@ -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) +} 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/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 } 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) { 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 +} diff --git a/vendor/vendor.json b/vendor/vendor.json new file mode 100644 index 00000000..3c6b0edb --- /dev/null +++ b/vendor/vendor.json @@ -0,0 +1,85 @@ +{ + "comment": "v1.1.4", + "ignore": "test", + "package": [ + { + "checksumSHA1": "dvabztWVQX8f6oMLRyv4dLH+TGY=", + "comment": "v1.1.0", + "path": "github.com/davecgh/go-spew/spew", + "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": "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", + "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", + "revisionTime": "2016-01-10T10:55:54Z" + }, + { + "checksumSHA1": "Q2V7Zs3diLmLfmfbiuLpSxETSuY=", + "comment": "v1.1.4", + "path": "github.com/stretchr/testify/assert", + "revision": "976c720a22c8eb4eb6a0b4348ad85ad12491a506", + "revisionTime": "2016-09-25T22:06:09Z" + }, + { + "checksumSHA1": "9jjO5GjLa0XF/nfWihF02RoH4qc=", + "comment": "release-branch.go1.7", + "path": "golang.org/x/net/context", + "revision": "d4c55e66d8c3a2f3382d264b08e3e3454a66355a", + "revisionTime": "2016-10-18T08:54:36Z" + }, + { + "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": "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" +}