Push branch develop into master

This commit is contained in:
Javier Provecho Fernandez 2016-04-15 01:39:28 +02:00
commit 5caaac4c5c
22 changed files with 135 additions and 91 deletions

View File

@ -3,6 +3,7 @@
<img align="right" src="https://raw.githubusercontent.com/gin-gonic/gin/master/logo.jpg"> <img align="right" src="https://raw.githubusercontent.com/gin-gonic/gin/master/logo.jpg">
[![Build Status](https://travis-ci.org/gin-gonic/gin.svg)](https://travis-ci.org/gin-gonic/gin) [![Build Status](https://travis-ci.org/gin-gonic/gin.svg)](https://travis-ci.org/gin-gonic/gin)
[![Coverage Status](https://coveralls.io/repos/gin-gonic/gin/badge.svg?branch=master)](https://coveralls.io/r/gin-gonic/gin?branch=master) [![Coverage Status](https://coveralls.io/repos/gin-gonic/gin/badge.svg?branch=master)](https://coveralls.io/r/gin-gonic/gin?branch=master)
[![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) [![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) [![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)

View File

@ -3,6 +3,7 @@ package gin
import ( import (
"html/template" "html/template"
"net/http" "net/http"
"os"
"testing" "testing"
) )
@ -36,7 +37,7 @@ func BenchmarkManyHandlers(B *testing.B) {
} }
func Benchmark5Params(B *testing.B) { func Benchmark5Params(B *testing.B) {
DefaultWriter = newMockWriter() DefaultWriter = os.Stdout
router := New() router := New()
router.Use(func(c *Context) {}) router.Use(func(c *Context) {})
router.GET("/param/:param1/:params2/:param3/:param4/:param5", func(c *Context) {}) router.GET("/param/:param1/:params2/:param3/:param4/:param5", func(c *Context) {})

View File

@ -13,11 +13,11 @@ import (
type protobufBinding struct{} type protobufBinding struct{}
func (_ protobufBinding) Name() string { func (protobufBinding) Name() string {
return "protobuf" return "protobuf"
} }
func (_ protobufBinding) Bind(req *http.Request, obj interface{}) error { func (protobufBinding) Bind(req *http.Request, obj interface{}) error {
buf, err := ioutil.ReadAll(req.Body) buf, err := ioutil.ReadAll(req.Body)
if err != nil { if err != nil {

View File

@ -16,15 +16,15 @@ type testInterface interface {
String() string String() string
} }
type substruct_noValidation struct { type substructNoValidation struct {
I_String string IString string
I_Int int IInt int
} }
type mapNoValidationSub map[string]substruct_noValidation type mapNoValidationSub map[string]substructNoValidation
type struct_noValidation_values struct { type structNoValidationValues struct {
substruct_noValidation substructNoValidation
Boolean bool Boolean bool
@ -46,7 +46,7 @@ type struct_noValidation_values struct {
Date time.Time Date time.Time
Struct substruct_noValidation Struct substructNoValidation
InlinedStruct struct { InlinedStruct struct {
String []string String []string
Integer int Integer int
@ -54,8 +54,8 @@ type struct_noValidation_values struct {
IntSlice []int IntSlice []int
IntPointerSlice []*int IntPointerSlice []*int
StructPointerSlice []*substruct_noValidation StructPointerSlice []*substructNoValidation
StructSlice []substruct_noValidation StructSlice []substructNoValidation
InterfaceSlice []testInterface InterfaceSlice []testInterface
UniversalInterface interface{} UniversalInterface interface{}
@ -65,9 +65,9 @@ type struct_noValidation_values struct {
StructMap mapNoValidationSub StructMap mapNoValidationSub
} }
func createNoValidation_values() struct_noValidation_values { func createNoValidationValues() structNoValidationValues {
integer := 1 integer := 1
s := struct_noValidation_values{ s := structNoValidationValues{
Boolean: true, Boolean: true,
Uinteger: 1 << 29, Uinteger: 1 << 29,
Integer: -10000, Integer: -10000,
@ -84,33 +84,33 @@ func createNoValidation_values() struct_noValidation_values {
String: "text", String: "text",
Date: time.Time{}, Date: time.Time{},
CustomInterface: &bytes.Buffer{}, CustomInterface: &bytes.Buffer{},
Struct: substruct_noValidation{}, Struct: substructNoValidation{},
IntSlice: []int{-3, -2, 1, 0, 1, 2, 3}, IntSlice: []int{-3, -2, 1, 0, 1, 2, 3},
IntPointerSlice: []*int{&integer}, IntPointerSlice: []*int{&integer},
StructSlice: []substruct_noValidation{}, StructSlice: []substructNoValidation{},
UniversalInterface: 1.2, UniversalInterface: 1.2,
FloatMap: map[string]float32{ FloatMap: map[string]float32{
"foo": 1.23, "foo": 1.23,
"bar": 232.323, "bar": 232.323,
}, },
StructMap: mapNoValidationSub{ StructMap: mapNoValidationSub{
"foo": substruct_noValidation{}, "foo": substructNoValidation{},
"bar": substruct_noValidation{}, "bar": substructNoValidation{},
}, },
// StructPointerSlice []noValidationSub // StructPointerSlice []noValidationSub
// InterfaceSlice []testInterface // InterfaceSlice []testInterface
} }
s.InlinedStruct.Integer = 1000 s.InlinedStruct.Integer = 1000
s.InlinedStruct.String = []string{"first", "second"} s.InlinedStruct.String = []string{"first", "second"}
s.I_String = "substring" s.IString = "substring"
s.I_Int = 987654 s.IInt = 987654
return s return s
} }
func TestValidateNoValidationValues(t *testing.T) { func TestValidateNoValidationValues(t *testing.T) {
origin := createNoValidation_values() origin := createNoValidationValues()
test := createNoValidation_values() test := createNoValidationValues()
empty := struct_noValidation_values{} empty := structNoValidationValues{}
assert.Nil(t, validate(test)) assert.Nil(t, validate(test))
assert.Nil(t, validate(&test)) assert.Nil(t, validate(&test))
@ -120,8 +120,8 @@ func TestValidateNoValidationValues(t *testing.T) {
assert.Equal(t, origin, test) assert.Equal(t, origin, test)
} }
type struct_noValidation_pointer struct { type structNoValidationPointer struct {
substruct_noValidation substructNoValidation
Boolean bool Boolean bool
@ -143,12 +143,12 @@ type struct_noValidation_pointer struct {
Date *time.Time Date *time.Time
Struct *substruct_noValidation Struct *substructNoValidation
IntSlice *[]int IntSlice *[]int
IntPointerSlice *[]*int IntPointerSlice *[]*int
StructPointerSlice *[]*substruct_noValidation StructPointerSlice *[]*substructNoValidation
StructSlice *[]substruct_noValidation StructSlice *[]substructNoValidation
InterfaceSlice *[]testInterface InterfaceSlice *[]testInterface
FloatMap *map[string]float32 FloatMap *map[string]float32
@ -158,7 +158,7 @@ type struct_noValidation_pointer struct {
func TestValidateNoValidationPointers(t *testing.T) { func TestValidateNoValidationPointers(t *testing.T) {
//origin := createNoValidation_values() //origin := createNoValidation_values()
//test := createNoValidation_values() //test := createNoValidation_values()
empty := struct_noValidation_pointer{} empty := structNoValidationPointer{}
//assert.Nil(t, validate(test)) //assert.Nil(t, validate(test))
//assert.Nil(t, validate(&test)) //assert.Nil(t, validate(&test))

View File

@ -69,7 +69,7 @@ func (c *Context) reset() {
// Copy returns a copy of the current context that can be safely used outside the request's scope. // 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 have to be used then the context has to be passed to a goroutine.
func (c *Context) Copy() *Context { func (c *Context) Copy() *Context {
var cp Context = *c var cp = *c
cp.writermem.ResponseWriter = nil cp.writermem.ResponseWriter = nil
cp.Writer = &cp.writermem cp.Writer = &cp.writermem
cp.index = abortIndex cp.index = abortIndex
@ -115,6 +115,7 @@ func (c *Context) Abort() {
// For example, a failed attempt to authentificate a request could use: context.AbortWithStatus(401). // For example, a failed attempt to authentificate a request could use: context.AbortWithStatus(401).
func (c *Context) AbortWithStatus(code int) { func (c *Context) AbortWithStatus(code int) {
c.Status(code) c.Status(code)
c.Writer.WriteHeaderNow()
c.Abort() c.Abort()
} }
@ -171,7 +172,7 @@ func (c *Context) Get(key string) (value interface{}, exists bool) {
return return
} }
// Returns the value for the given key if it exists, otherwise it panics. // MustGet returns the value for the given key if it exists, otherwise it panics.
func (c *Context) MustGet(key string) interface{} { func (c *Context) MustGet(key string) interface{} {
if value, exists := c.Get(key); exists { if value, exists := c.Get(key); exists {
return value return value
@ -243,7 +244,7 @@ func (c *Context) PostForm(key string) string {
return value return value
} }
// PostForm returns the specified key from a POST urlencoded form or multipart form // DefaultPostForm returns the specified key from a POST urlencoded form or multipart form
// when it exists, otherwise it returns the specified defaultValue string. // when it exists, otherwise it returns the specified defaultValue string.
// See: PostForm() and GetPostForm() for further information. // See: PostForm() and GetPostForm() for further information.
func (c *Context) DefaultPostForm(key, defaultValue string) string { func (c *Context) DefaultPostForm(key, defaultValue string) string {
@ -426,6 +427,11 @@ func (c *Context) XML(code int, obj interface{}) {
c.Render(code, render.XML{Data: obj}) c.Render(code, render.XML{Data: obj})
} }
// YAML serializes the given struct as YAML into the response body.
func (c *Context) YAML(code int, obj interface{}) {
c.Render(code, render.YAML{Data: obj})
}
// String writes the given string into the response body. // String writes the given string into the response body.
func (c *Context) String(code int, format string, values ...interface{}) { func (c *Context) String(code int, format string, values ...interface{}) {
c.Status(code) c.Status(code)

View File

@ -262,14 +262,14 @@ func TestContextPostFormMultipart(t *testing.T) {
Bar string `form:"bar"` Bar string `form:"bar"`
BarAsInt int `form:"bar"` BarAsInt int `form:"bar"`
Array []string `form:"array"` Array []string `form:"array"`
Id string `form:"id"` ID string `form:"id"`
} }
assert.NoError(t, c.Bind(&obj)) assert.NoError(t, c.Bind(&obj))
assert.Equal(t, obj.Foo, "bar") assert.Equal(t, obj.Foo, "bar")
assert.Equal(t, obj.Bar, "10") assert.Equal(t, obj.Bar, "10")
assert.Equal(t, obj.BarAsInt, 10) assert.Equal(t, obj.BarAsInt, 10)
assert.Equal(t, obj.Array, []string{"first", "second"}) assert.Equal(t, obj.Array, []string{"first", "second"})
assert.Equal(t, obj.Id, "") assert.Equal(t, obj.ID, "")
value, ok := c.GetQuery("foo") value, ok := c.GetQuery("foo")
assert.False(t, ok) assert.False(t, ok)
@ -433,6 +433,17 @@ func TestContextRenderFile(t *testing.T) {
assert.Equal(t, w.HeaderMap.Get("Content-Type"), "text/plain; charset=utf-8") assert.Equal(t, w.HeaderMap.Get("Content-Type"), "text/plain; charset=utf-8")
} }
// 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()
c.YAML(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/x-yaml; charset=utf-8")
}
func TestContextHeaders(t *testing.T) { func TestContextHeaders(t *testing.T) {
c, _, _ := CreateTestContext() c, _, _ := CreateTestContext()
c.Header("Content-Type", "text/plain") c.Header("Content-Type", "text/plain")
@ -545,7 +556,6 @@ func TestContextAbortWithStatus(t *testing.T) {
c, w, _ := CreateTestContext() c, w, _ := CreateTestContext()
c.index = 4 c.index = 4
c.AbortWithStatus(401) c.AbortWithStatus(401)
c.Writer.WriteHeaderNow()
assert.Equal(t, c.index, abortIndex) assert.Equal(t, c.index, abortIndex)
assert.Equal(t, c.Writer.Status(), 401) assert.Equal(t, c.Writer.Status(), 401)
@ -596,7 +606,6 @@ func TestContextTypedError(t *testing.T) {
func TestContextAbortWithError(t *testing.T) { func TestContextAbortWithError(t *testing.T) {
c, w, _ := CreateTestContext() c, w, _ := CreateTestContext()
c.AbortWithError(401, errors.New("bad input")).SetMeta("some input") c.AbortWithError(401, errors.New("bad input")).SetMeta("some input")
c.Writer.WriteHeaderNow()
assert.Equal(t, w.Code, 401) assert.Equal(t, w.Code, 401)
assert.Equal(t, c.index, abortIndex) assert.Equal(t, c.index, abortIndex)

View File

@ -66,7 +66,7 @@ func (msg *Error) JSON() interface{} {
return json return json
} }
// Implements the json.Marshaller interface // MarshalJSON implements the json.Marshaller interface
func (msg *Error) MarshalJSON() ([]byte, error) { func (msg *Error) MarshalJSON() ([]byte, error) {
return json.Marshal(msg.JSON()) return json.Marshal(msg.JSON())
} }
@ -89,7 +89,7 @@ func (a errorMsgs) ByType(typ ErrorType) errorMsgs {
if typ == ErrorTypeAny { if typ == ErrorTypeAny {
return a return a
} }
var result errorMsgs = nil var result errorMsgs
for _, msg := range a { for _, msg := range a {
if msg.IsType(typ) { if msg.IsType(typ) {
result = append(result, msg) result = append(result, msg)

View File

@ -16,9 +16,9 @@ var savedStats map[string]uint64
func statsWorker() { func statsWorker() {
c := time.Tick(1 * time.Second) c := time.Tick(1 * time.Second)
var lastMallocs uint64 = 0 var lastMallocs uint64
var lastFrees uint64 = 0 var lastFrees uint64
for _ = range c { for range c {
var stats runtime.MemStats var stats runtime.MemStats
runtime.ReadMemStats(&stats) runtime.ReadMemStats(&stats)

8
gin.go
View File

@ -14,7 +14,7 @@ import (
"github.com/gin-gonic/gin/render" "github.com/gin-gonic/gin/render"
) )
// Framework's version // Version is Framework's version
const Version = "v1.0rc2" const Version = "v1.0rc2"
var default404Body = []byte("404 page not found") var default404Body = []byte("404 page not found")
@ -147,19 +147,19 @@ func (engine *Engine) SetHTMLTemplate(templ *template.Template) {
engine.HTMLRender = render.HTMLProduction{Template: templ} engine.HTMLRender = render.HTMLProduction{Template: templ}
} }
// Adds handlers for NoRoute. It return a 404 code by default. // NoRoute adds handlers for NoRoute. It return a 404 code by default.
func (engine *Engine) NoRoute(handlers ...HandlerFunc) { func (engine *Engine) NoRoute(handlers ...HandlerFunc) {
engine.noRoute = handlers engine.noRoute = handlers
engine.rebuild404Handlers() engine.rebuild404Handlers()
} }
// Sets the handlers called when... TODO // NoMethod sets the handlers called when... TODO
func (engine *Engine) NoMethod(handlers ...HandlerFunc) { func (engine *Engine) NoMethod(handlers ...HandlerFunc) {
engine.noMethod = handlers engine.noMethod = handlers
engine.rebuild405Handlers() engine.rebuild405Handlers()
} }
// Attachs a global middleware to the router. ie. the middleware attached though Use() will be // Use attachs a global middleware to the router. ie. the middleware attached though Use() will be
// included in the handlers chain for every single request. Even 404, 405, static files... // included in the handlers chain for every single request. Even 404, 405, static files...
// For example, this is the right place for a logger or error management middleware. // For example, this is the right place for a logger or error management middleware.
func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes { func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes {

View File

@ -34,17 +34,17 @@ func SetHTMLTemplate(templ *template.Template) {
engine().SetHTMLTemplate(templ) engine().SetHTMLTemplate(templ)
} }
// Adds handlers for NoRoute. It return a 404 code by default. // NoRoute adds handlers for NoRoute. It return a 404 code by default.
func NoRoute(handlers ...HandlerFunc) { func NoRoute(handlers ...HandlerFunc) {
engine().NoRoute(handlers...) engine().NoRoute(handlers...)
} }
// Sets the handlers called when... TODO // NoMethod sets the handlers called when... TODO
func NoMethod(handlers ...HandlerFunc) { func NoMethod(handlers ...HandlerFunc) {
engine().NoMethod(handlers...) engine().NoMethod(handlers...)
} }
// Creates a new router group. You should add all the routes that have common middlwares or the same path prefix. // Group creates a new router group. You should add all the routes that have common middlwares or the same path prefix.
// For example, all the routes that use a common middlware for authorization could be grouped. // For example, all the routes that use a common middlware for authorization could be grouped.
func Group(relativePath string, handlers ...HandlerFunc) *RouterGroup { func Group(relativePath string, handlers ...HandlerFunc) *RouterGroup {
return engine().Group(relativePath, handlers...) return engine().Group(relativePath, handlers...)
@ -111,28 +111,28 @@ func StaticFS(relativePath string, fs http.FileSystem) IRoutes {
return engine().StaticFS(relativePath, fs) return engine().StaticFS(relativePath, fs)
} }
// Attachs a global middleware to the router. ie. the middlewares attached though Use() will be // Use attachs a global middleware to the router. ie. the middlewares attached though Use() will be
// included in the handlers chain for every single request. Even 404, 405, static files... // included in the handlers chain for every single request. Even 404, 405, static files...
// For example, this is the right place for a logger or error management middleware. // For example, this is the right place for a logger or error management middleware.
func Use(middlewares ...HandlerFunc) IRoutes { func Use(middlewares ...HandlerFunc) IRoutes {
return engine().Use(middlewares...) return engine().Use(middlewares...)
} }
// The router is attached to a http.Server and starts listening and serving HTTP requests. // Run : The router is attached to a http.Server and starts listening and serving HTTP requests.
// It is a shortcut for http.ListenAndServe(addr, router) // It is a shortcut for http.ListenAndServe(addr, router)
// Note: this method will block the calling goroutine undefinitelly unless an error happens. // Note: this method will block the calling goroutine undefinitelly unless an error happens.
func Run(addr ...string) (err error) { func Run(addr ...string) (err error) {
return engine().Run(addr...) return engine().Run(addr...)
} }
// The router is attached to a http.Server and starts listening and serving HTTPS requests. // RunTLS : The router is attached to a http.Server and starts listening and serving HTTPS requests.
// It is a shortcut for http.ListenAndServeTLS(addr, certFile, keyFile, router) // It is a shortcut for http.ListenAndServeTLS(addr, certFile, keyFile, router)
// Note: this method will block the calling goroutine undefinitelly unless an error happens. // Note: this method will block the calling goroutine undefinitelly unless an error happens.
func RunTLS(addr string, certFile string, keyFile string) (err error) { func RunTLS(addr string, certFile string, keyFile string) (err error) {
return engine().RunTLS(addr, certFile, keyFile) return engine().RunTLS(addr, certFile, keyFile)
} }
// The router is attached to a http.Server and starts listening and serving HTTP requests // RunUnix : The router is attached to a http.Server and starts listening and serving HTTP requests
// through the specified unix socket (ie. a file) // through the specified unix socket (ie. a file)
// Note: this method will block the calling goroutine undefinitelly unless an error happens. // Note: this method will block the calling goroutine undefinitelly unless an error happens.
func RunUnix(file string) (err error) { func RunUnix(file string) (err error) {

View File

@ -201,13 +201,13 @@ func compareFunc(t *testing.T, a, b interface{}) {
func TestListOfRoutes(t *testing.T) { func TestListOfRoutes(t *testing.T) {
router := New() router := New()
router.GET("/favicon.ico", handler_test1) router.GET("/favicon.ico", handlerTest1)
router.GET("/", handler_test1) router.GET("/", handlerTest1)
group := router.Group("/users") group := router.Group("/users")
{ {
group.GET("/", handler_test2) group.GET("/", handlerTest2)
group.GET("/:id", handler_test1) group.GET("/:id", handlerTest1)
group.POST("/:id", handler_test2) group.POST("/:id", handlerTest2)
} }
router.Static("/static", ".") router.Static("/static", ".")
@ -217,27 +217,27 @@ func TestListOfRoutes(t *testing.T) {
assertRoutePresent(t, list, RouteInfo{ assertRoutePresent(t, list, RouteInfo{
Method: "GET", Method: "GET",
Path: "/favicon.ico", Path: "/favicon.ico",
Handler: "^(.*/vendor/)?github.com/gin-gonic/gin.handler_test1$", Handler: "^(.*/vendor/)?github.com/gin-gonic/gin.handlerTest1$",
}) })
assertRoutePresent(t, list, RouteInfo{ assertRoutePresent(t, list, RouteInfo{
Method: "GET", Method: "GET",
Path: "/", Path: "/",
Handler: "^(.*/vendor/)?github.com/gin-gonic/gin.handler_test1$", Handler: "^(.*/vendor/)?github.com/gin-gonic/gin.handlerTest1$",
}) })
assertRoutePresent(t, list, RouteInfo{ assertRoutePresent(t, list, RouteInfo{
Method: "GET", Method: "GET",
Path: "/users/", Path: "/users/",
Handler: "^(.*/vendor/)?github.com/gin-gonic/gin.handler_test2$", Handler: "^(.*/vendor/)?github.com/gin-gonic/gin.handlerTest2$",
}) })
assertRoutePresent(t, list, RouteInfo{ assertRoutePresent(t, list, RouteInfo{
Method: "GET", Method: "GET",
Path: "/users/:id", Path: "/users/:id",
Handler: "^(.*/vendor/)?github.com/gin-gonic/gin.handler_test1$", Handler: "^(.*/vendor/)?github.com/gin-gonic/gin.handlerTest1$",
}) })
assertRoutePresent(t, list, RouteInfo{ assertRoutePresent(t, list, RouteInfo{
Method: "POST", Method: "POST",
Path: "/users/:id", Path: "/users/:id",
Handler: "^(.*/vendor/)?github.com/gin-gonic/gin.handler_test2$", Handler: "^(.*/vendor/)?github.com/gin-gonic/gin.handlerTest2$",
}) })
} }
@ -251,5 +251,5 @@ func assertRoutePresent(t *testing.T, gotRoutes RoutesInfo, wantRoute RouteInfo)
t.Errorf("route not found: %v", wantRoute) t.Errorf("route not found: %v", wantRoute)
} }
func handler_test1(c *Context) {} func handlerTest1(c *Context) {}
func handler_test2(c *Context) {} func handlerTest2(c *Context) {}

View File

@ -10,6 +10,7 @@ import (
"math/rand" "math/rand"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"os"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -298,7 +299,7 @@ func githubConfigRouter(router *Engine) {
} }
func TestGithubAPI(t *testing.T) { func TestGithubAPI(t *testing.T) {
DefaultWriter = newMockWriter() DefaultWriter = os.Stdout
router := Default() router := Default()
githubConfigRouter(router) githubConfigRouter(router)
@ -341,7 +342,7 @@ func exampleFromPath(path string) (string, Params) {
if start >= 0 { if start >= 0 {
value := fmt.Sprint(rand.Intn(100000)) value := fmt.Sprint(rand.Intn(100000))
params = append(params, Param{ params = append(params, Param{
Key: path[start:len(path)], Key: path[start:],
Value: value, Value: value,
}) })
output.WriteString(value) output.WriteString(value)
@ -357,7 +358,7 @@ func BenchmarkGithub(b *testing.B) {
} }
func BenchmarkParallelGithub(b *testing.B) { func BenchmarkParallelGithub(b *testing.B) {
DefaultWriter = newMockWriter() DefaultWriter = os.Stdout
router := New() router := New()
githubConfigRouter(router) githubConfigRouter(router)
@ -373,7 +374,7 @@ func BenchmarkParallelGithub(b *testing.B) {
} }
func BenchmarkParallelGithubDefault(b *testing.B) { func BenchmarkParallelGithubDefault(b *testing.B) {
DefaultWriter = newMockWriter() DefaultWriter = os.Stdout
router := Default() router := Default()
githubConfigRouter(router) githubConfigRouter(router)

View File

@ -28,23 +28,20 @@ func ErrorLogger() HandlerFunc {
func ErrorLoggerT(typ ErrorType) HandlerFunc { func ErrorLoggerT(typ ErrorType) HandlerFunc {
return func(c *Context) { return func(c *Context) {
c.Next() c.Next()
// avoid writting if we already wrote into the response body
if !c.Writer.Written() {
errors := c.Errors.ByType(typ) errors := c.Errors.ByType(typ)
if len(errors) > 0 { if len(errors) > 0 {
c.JSON(-1, errors) c.JSON(-1, errors)
} }
} }
} }
}
// Instances a Logger middleware that will write the logs to gin.DefaultWriter // Logger instances a Logger middleware that will write the logs to gin.DefaultWriter
// By default gin.DefaultWriter = os.Stdout // By default gin.DefaultWriter = os.Stdout
func Logger() HandlerFunc { func Logger() HandlerFunc {
return LoggerWithWriter(DefaultWriter) return LoggerWithWriter(DefaultWriter)
} }
// Instance a Logger middleware with the specified writter buffer. // LoggerWithWriter instance a Logger middleware with the specified writter buffer.
// Example: os.Stdout, a file opened in write mode, a socket... // Example: os.Stdout, a file opened in write mode, a socket...
func LoggerWithWriter(out io.Writer, notlogged ...string) HandlerFunc { func LoggerWithWriter(out io.Writer, notlogged ...string) HandlerFunc {
var skip map[string]struct{} var skip map[string]struct{}

View File

@ -116,7 +116,7 @@ func TestErrorLogger(t *testing.T) {
w = performRequest(router, "GET", "/print") w = performRequest(router, "GET", "/print")
assert.Equal(t, w.Code, 500) assert.Equal(t, w.Code, 500)
assert.Equal(t, w.Body.String(), "hola!") assert.Equal(t, w.Body.String(), "hola!{\"error\":\"this is an error\"}\n")
} }
func TestSkippingPaths(t *testing.T) { func TestSkippingPaths(t *testing.T) {

View File

@ -5,7 +5,6 @@
package gin package gin
import ( import (
"io"
"os" "os"
"github.com/gin-gonic/gin/binding" "github.com/gin-gonic/gin/binding"
@ -31,11 +30,11 @@ const (
// To support coloring in Windows use: // To support coloring in Windows use:
// import "github.com/mattn/go-colorable" // import "github.com/mattn/go-colorable"
// gin.DefaultWriter = colorable.NewColorableStdout() // gin.DefaultWriter = colorable.NewColorableStdout()
var DefaultWriter io.Writer = os.Stdout var DefaultWriter = os.Stdout
var DefaultErrorWriter io.Writer = os.Stderr var DefaultErrorWriter = os.Stderr
var ginMode int = debugCode var ginMode = debugCode
var modeName string = DebugMode var modeName = DebugMode
func init() { func init() {
mode := os.Getenv(ENV_GIN_MODE) mode := os.Getenv(ENV_GIN_MODE)

View File

@ -39,5 +39,5 @@ func TestPanicWithAbort(t *testing.T) {
// RUN // RUN
w := performRequest(router, "GET", "/recovery") w := performRequest(router, "GET", "/recovery")
// TEST // TEST
assert.Equal(t, w.Code, 500) // NOT SURE assert.Equal(t, w.Code, 400)
} }

View File

@ -61,7 +61,6 @@ func (r HTML) Render(w http.ResponseWriter) error {
writeContentType(w, htmlContentType) writeContentType(w, htmlContentType)
if len(r.Name) == 0 { if len(r.Name) == 0 {
return r.Template.Execute(w, r.Data) return r.Template.Execute(w, r.Data)
} else { }
return r.Template.ExecuteTemplate(w, r.Name, r.Data) return r.Template.ExecuteTemplate(w, r.Name, r.Data)
} }
}

View File

@ -20,6 +20,7 @@ var (
_ Render = HTML{} _ Render = HTML{}
_ HTMLRender = HTMLDebug{} _ HTMLRender = HTMLDebug{}
_ HTMLRender = HTMLProduction{} _ HTMLRender = HTMLProduction{}
_ Render = YAML{}
) )
func writeContentType(w http.ResponseWriter, value []string) { func writeContentType(w http.ResponseWriter, value []string) {

29
render/yaml.go Normal file
View File

@ -0,0 +1,29 @@
// Copyright 2014 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"
"gopkg.in/yaml.v2"
)
type YAML struct {
Data interface{}
}
var yamlContentType = []string{"application/x-yaml; charset=utf-8"}
func (r YAML) Render(w http.ResponseWriter) error {
writeContentType(w, yamlContentType)
bytes, err := yaml.Marshal(r.Data)
if err != nil {
return err
}
w.Write(bytes)
return nil
}

View File

@ -20,7 +20,7 @@ type Param struct {
// It is therefore safe to read values by the index. // It is therefore safe to read values by the index.
type Params []Param type Params []Param
// ByName returns the value of the first Param which key matches the given name. // Get returns the value of the first Param which key matches the given name.
// If no matching Param is found, an empty string is returned. // If no matching Param is found, an empty string is returned.
func (ps Params) Get(name string) (string, bool) { func (ps Params) Get(name string) (string, bool) {
for _, entry := range ps { for _, entry := range ps {
@ -31,6 +31,8 @@ func (ps Params) Get(name string) (string, bool) {
return "", false return "", false
} }
// ByName returns the value of the first Param which key matches the given name.
// If no matching Param is found, an empty string is returned.
func (ps Params) ByName(name string) (va string) { func (ps Params) ByName(name string) (va string) {
va, _ = ps.Get(name) va, _ = ps.Get(name)
return return

View File

@ -21,7 +21,7 @@ func printChildren(n *node, prefix string) {
} }
} }
// Used as a workaround since we can't compare functions or their adresses // Used as a workaround since we can't compare functions or their addressses
var fakeHandlerValue string var fakeHandlerValue string
func fakeHandler(val string) HandlersChain { func fakeHandler(val string) HandlersChain {

View File

@ -47,7 +47,7 @@ func WrapH(h http.Handler) HandlerFunc {
type H map[string]interface{} type H map[string]interface{}
// Allows type H to be used with xml.Marshal // MarshalXML allows type H to be used with xml.Marshal
func (h H) MarshalXML(e *xml.Encoder, start xml.StartElement) error { func (h H) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
start.Name = xml.Name{ start.Name = xml.Name{
Space: "", Space: "",
@ -143,10 +143,9 @@ func resolveAddress(addr []string) string {
if port := os.Getenv("PORT"); len(port) > 0 { if port := os.Getenv("PORT"); len(port) > 0 {
debugPrint("Environment variable PORT=\"%s\"", port) debugPrint("Environment variable PORT=\"%s\"", port)
return ":" + port return ":" + port
} else { }
debugPrint("Environment variable PORT is undefined. Using port :8080 by default") debugPrint("Environment variable PORT is undefined. Using port :8080 by default")
return ":8080" return ":8080"
}
case 1: case 1:
return addr[0] return addr[0]
default: default: