Merge branch 'master' into make

This commit is contained in:
Bo-Yi Wu 2018-01-23 10:38:44 +08:00 committed by GitHub
commit a3cca26cfd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 118 additions and 75 deletions

View File

@ -117,7 +117,7 @@ $ go get github.com/kardianos/govendor
2. Create your project folder and `cd` inside 2. Create your project folder and `cd` inside
```sh ```sh
$ mkdir -p ~/go/src/github.com/myusername/project && cd "$_" $ mkdir -p $GOPATH/src/github.com/myusername/project && cd "$_"
``` ```
3. Vendor init your project and add gin 3. Vendor init your project and add gin
@ -385,7 +385,7 @@ func main() {
r := gin.New() r := gin.New()
// Global middleware // Global middleware
// Logger middleware will write the logs to gin.DefaultWriter even you set with GIN_MODE=release. // Logger middleware will write the logs to gin.DefaultWriter even if you set with GIN_MODE=release.
// By default gin.DefaultWriter = os.Stdout // By default gin.DefaultWriter = os.Stdout
r.Use(gin.Logger()) r.Use(gin.Logger())
@ -435,7 +435,7 @@ func main() {
c.String(200, "pong") c.String(200, "pong")
}) })
r.Run(":8080")    router.Run(":8080")
} }
``` ```
@ -540,7 +540,7 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/binding" "github.com/gin-gonic/gin/binding"
validator "gopkg.in/go-playground/validator.v8" "gopkg.in/go-playground/validator.v8"
) )
type Booking struct { type Booking struct {
@ -1071,7 +1071,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. When starting new Goroutines inside a middleware or handler, you **SHOULD NOT** use the original context inside it, you have to use a read-only copy.
```go ```go
func main() { func main() {
@ -1190,7 +1190,7 @@ func main() {
### Run multiple service using Gin ### Run multiple service using Gin
See the [question](https://github.com/gin-gonic/gin/issues/346) and try the folling example: See the [question](https://github.com/gin-gonic/gin/issues/346) and try the following example:
[embedmd]:# (examples/multiple-service/main.go go) [embedmd]:# (examples/multiple-service/main.go go)
```go ```go

12
auth.go
View File

@ -17,8 +17,8 @@ const AuthUserKey = "user"
type Accounts map[string]string type Accounts map[string]string
type authPair struct { type authPair struct {
Value string value string
User string user string
} }
type authPairs []authPair type authPairs []authPair
@ -28,8 +28,8 @@ func (a authPairs) searchCredential(authValue string) (string, bool) {
return "", false return "", false
} }
for _, pair := range a { for _, pair := range a {
if pair.Value == authValue { if pair.value == authValue {
return pair.User, true return pair.user, true
} }
} }
return "", false return "", false
@ -74,8 +74,8 @@ func processAccounts(accounts Accounts) authPairs {
assert1(user != "", "User can not be empty") assert1(user != "", "User can not be empty")
value := authorizationHeader(user, password) value := authorizationHeader(user, password)
pairs = append(pairs, authPair{ pairs = append(pairs, authPair{
Value: value, value: value,
User: user, user: user,
}) })
} }
return pairs return pairs

View File

@ -22,16 +22,16 @@ func TestBasicAuth(t *testing.T) {
assert.Len(t, pairs, 3) assert.Len(t, pairs, 3)
assert.Contains(t, pairs, authPair{ assert.Contains(t, pairs, authPair{
User: "bar", user: "bar",
Value: "Basic YmFyOmZvbw==", value: "Basic YmFyOmZvbw==",
}) })
assert.Contains(t, pairs, authPair{ assert.Contains(t, pairs, authPair{
User: "foo", user: "foo",
Value: "Basic Zm9vOmJhcg==", value: "Basic Zm9vOmJhcg==",
}) })
assert.Contains(t, pairs, authPair{ assert.Contains(t, pairs, authPair{
User: "admin", user: "admin",
Value: "Basic YWRtaW46cGFzc3dvcmQ=", value: "Basic YWRtaW46cGFzc3dvcmQ=",
}) })
} }

View File

@ -59,8 +59,6 @@ func BenchmarkOneRouteJSON(B *testing.B) {
runRequest(B, router, "GET", "/json") runRequest(B, router, "GET", "/json")
} }
var htmlContentType = []string{"text/html; charset=utf-8"}
func BenchmarkOneRouteHTML(B *testing.B) { func BenchmarkOneRouteHTML(B *testing.B) {
router := New() router := New()
t := template.Must(template.New("index").Parse(` t := template.Must(template.New("index").Parse(`

View File

@ -179,12 +179,3 @@ func setTimeField(val string, structField reflect.StructField, value reflect.Val
value.Set(reflect.ValueOf(t)) value.Set(reflect.ValueOf(t))
return nil 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
func ensureNotPointer(obj interface{}) {
if reflect.TypeOf(obj).Kind() == reflect.Ptr {
panic("Pointers are not accepted as binding models")
}
}

View File

@ -449,10 +449,10 @@ func (c *Context) SaveUploadedFile(file *multipart.FileHeader, dst string) error
// Depending the "Content-Type" header different bindings are used: // Depending the "Content-Type" header different bindings are used:
// "application/json" --> JSON binding // "application/json" --> JSON binding
// "application/xml" --> XML binding // "application/xml" --> XML binding
// otherwise --> returns an error // otherwise --> returns an error.
// It parses the request's body as JSON if Content-Type == "application/json" using JSON or XML as a JSON input. // It parses the request's body as JSON if Content-Type == "application/json" using JSON or XML as a JSON input.
// It decodes the json payload into the struct specified as a pointer. // It decodes the json payload into the struct specified as a pointer.
// It will writes a 400 error and sets Content-Type header "text/plain" in the response if input is not valid. // It writes a 400 error and sets Content-Type header "text/plain" in the response if input is not valid.
func (c *Context) Bind(obj interface{}) error { func (c *Context) Bind(obj interface{}) error {
b := binding.Default(c.Request.Method, c.ContentType()) b := binding.Default(c.Request.Method, c.ContentType())
return c.MustBindWith(obj, b) return c.MustBindWith(obj, b)

View File

@ -676,6 +676,7 @@ func TestContextRenderNoContentSecureJSON(t *testing.T) {
func TestContextRenderHTML(t *testing.T) { func TestContextRenderHTML(t *testing.T) {
w := httptest.NewRecorder() w := httptest.NewRecorder()
c, router := CreateTestContext(w) c, router := CreateTestContext(w)
templ := template.Must(template.New("t").Parse(`Hello {{.name}}`)) templ := template.Must(template.New("t").Parse(`Hello {{.name}}`))
router.SetHTMLTemplate(templ) router.SetHTMLTemplate(templ)
@ -686,6 +687,30 @@ func TestContextRenderHTML(t *testing.T) {
assert.Equal(t, "text/html; charset=utf-8", w.HeaderMap.Get("Content-Type")) assert.Equal(t, "text/html; charset=utf-8", w.HeaderMap.Get("Content-Type"))
} }
func TestContextRenderHTML2(t *testing.T) {
w := httptest.NewRecorder()
c, router := CreateTestContext(w)
// print debug warning log when Engine.trees > 0
router.addRoute("GET", "/", HandlersChain{func(_ *Context) {}})
assert.Len(t, router.trees, 1)
var b bytes.Buffer
setup(&b)
defer teardown()
templ := template.Must(template.New("t").Parse(`Hello {{.name}}`))
router.SetHTMLTemplate(templ)
assert.Equal(t, "[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", b.String())
c.HTML(201, "t", H{"name": "alexandernyquist"})
assert.Equal(t, 201, w.Code)
assert.Equal(t, "Hello alexandernyquist", w.Body.String())
assert.Equal(t, "text/html; charset=utf-8", w.HeaderMap.Get("Content-Type"))
}
// Tests that no HTML is rendered if code is 204 // Tests that no HTML is rendered if code is 204
func TestContextRenderNoContentHTML(t *testing.T) { func TestContextRenderNoContentHTML(t *testing.T) {
w := httptest.NewRecorder() w := httptest.NewRecorder()

31
deprecated_test.go Normal file
View File

@ -0,0 +1,31 @@
// 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 gin
import (
"bytes"
"net/http"
"net/http/httptest"
"testing"
"github.com/gin-gonic/gin/binding"
"github.com/stretchr/testify/assert"
)
func TestBindWith(t *testing.T) {
w := httptest.NewRecorder()
c, _ := CreateTestContext(w)
c.Request, _ = http.NewRequest("POST", "/?foo=bar&bar=foo", bytes.NewBufferString("foo=unused"))
var obj struct {
Foo string `form:"foo"`
Bar string `form:"bar"`
}
assert.NoError(t, c.BindWith(&obj, binding.Form))
assert.Equal(t, "foo", obj.Bar)
assert.Equal(t, "bar", obj.Foo)
assert.Equal(t, 0, w.Body.Len())
}

View File

@ -148,7 +148,7 @@ func (a errorMsgs) String() string {
} }
var buffer bytes.Buffer var buffer bytes.Buffer
for i, msg := range a { for i, msg := range a {
fmt.Fprintf(&buffer, "Error #%02d: %s\n", (i + 1), msg.Err) fmt.Fprintf(&buffer, "Error #%02d: %s\n", i+1, msg.Err)
if msg.Meta != nil { if msg.Meta != nil {
fmt.Fprintf(&buffer, " Meta: %v\n", msg.Meta) fmt.Fprintf(&buffer, " Meta: %v\n", msg.Meta)
} }

View File

@ -7,7 +7,7 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/binding" "github.com/gin-gonic/gin/binding"
validator "gopkg.in/go-playground/validator.v8" "gopkg.in/go-playground/validator.v8"
) )
type Booking struct { type Booking struct {

View File

@ -29,8 +29,8 @@ func statsWorker() {
"timestamp": uint64(time.Now().Unix()), "timestamp": uint64(time.Now().Unix()),
"HeapInuse": stats.HeapInuse, "HeapInuse": stats.HeapInuse,
"StackInuse": stats.StackInuse, "StackInuse": stats.StackInuse,
"Mallocs": (stats.Mallocs - lastMallocs), "Mallocs": stats.Mallocs - lastMallocs,
"Frees": (stats.Frees - lastFrees), "Frees": stats.Frees - lastFrees,
"Inbound": uint64(messages.Get("inbound")), "Inbound": uint64(messages.Get("inbound")),
"Outbound": uint64(messages.Get("outbound")), "Outbound": uint64(messages.Get("outbound")),
"Connected": connectedUsers(), "Connected": connectedUsers(),

28
gin.go
View File

@ -49,16 +49,6 @@ type RoutesInfo []RouteInfo
// Create an instance of Engine, by using New() or Default() // Create an instance of Engine, by using New() or Default()
type Engine struct { type Engine struct {
RouterGroup RouterGroup
delims render.Delims
secureJsonPrefix string
HTMLRender render.HTMLRender
FuncMap template.FuncMap
allNoRoute HandlersChain
allNoMethod HandlersChain
noRoute HandlersChain
noMethod HandlersChain
pool sync.Pool
trees methodTrees
// Enables automatic redirection if the current route can't be matched but a // Enables automatic redirection if the current route can't be matched but a
// handler for the path with (without) the trailing slash exists. // handler for the path with (without) the trailing slash exists.
@ -102,6 +92,17 @@ type Engine struct {
// Value of 'maxMemory' param that is given to http.Request's ParseMultipartForm // Value of 'maxMemory' param that is given to http.Request's ParseMultipartForm
// method call. // method call.
MaxMultipartMemory int64 MaxMultipartMemory int64
delims render.Delims
secureJsonPrefix string
HTMLRender render.HTMLRender
FuncMap template.FuncMap
allNoRoute HandlersChain
allNoMethod HandlersChain
noRoute HandlersChain
noMethod HandlersChain
pool sync.Pool
trees methodTrees
} }
var _ IRouter = &Engine{} var _ IRouter = &Engine{}
@ -159,11 +160,14 @@ func (engine *Engine) Delims(left, right string) *Engine {
return engine return engine
} }
// SecureJsonPrefix sets the secureJsonPrefix used in Context.SecureJSON.
func (engine *Engine) SecureJsonPrefix(prefix string) *Engine { func (engine *Engine) SecureJsonPrefix(prefix string) *Engine {
engine.secureJsonPrefix = prefix engine.secureJsonPrefix = prefix
return engine return engine
} }
// LoadHTMLGlob loads HTML files identified by glob pattern
// and associates the result with HTML renderer.
func (engine *Engine) LoadHTMLGlob(pattern string) { func (engine *Engine) LoadHTMLGlob(pattern string) {
left := engine.delims.Left left := engine.delims.Left
right := engine.delims.Right right := engine.delims.Right
@ -178,6 +182,8 @@ func (engine *Engine) LoadHTMLGlob(pattern string) {
engine.SetHTMLTemplate(templ) engine.SetHTMLTemplate(templ)
} }
// LoadHTMLFiles loads a slice of HTML files
// and associates the result with HTML renderer.
func (engine *Engine) LoadHTMLFiles(files ...string) { func (engine *Engine) LoadHTMLFiles(files ...string) {
if IsDebugging() { if IsDebugging() {
engine.HTMLRender = render.HTMLDebug{Files: files, FuncMap: engine.FuncMap, Delims: engine.delims} engine.HTMLRender = render.HTMLDebug{Files: files, FuncMap: engine.FuncMap, Delims: engine.delims}
@ -188,6 +194,7 @@ func (engine *Engine) LoadHTMLFiles(files ...string) {
engine.SetHTMLTemplate(templ) engine.SetHTMLTemplate(templ)
} }
// SetHTMLTemplate associate a template with HTML renderer.
func (engine *Engine) SetHTMLTemplate(templ *template.Template) { func (engine *Engine) SetHTMLTemplate(templ *template.Template) {
if len(engine.trees) > 0 { if len(engine.trees) > 0 {
debugPrintWARNINGSetHTMLTemplate() debugPrintWARNINGSetHTMLTemplate()
@ -196,6 +203,7 @@ func (engine *Engine) SetHTMLTemplate(templ *template.Template) {
engine.HTMLRender = render.HTMLProduction{Template: templ.Funcs(engine.FuncMap)} engine.HTMLRender = render.HTMLProduction{Template: templ.Funcs(engine.FuncMap)}
} }
// SetFuncMap sets the FuncMap used for template.FuncMap.
func (engine *Engine) SetFuncMap(funcMap template.FuncMap) { func (engine *Engine) SetFuncMap(funcMap template.FuncMap) {
engine.FuncMap = funcMap engine.FuncMap = funcMap
} }

View File

@ -94,7 +94,7 @@ func TestUnixSocket(t *testing.T) {
c, err := net.Dial("unix", "/tmp/unix_unit_test") c, err := net.Dial("unix", "/tmp/unix_unit_test")
assert.NoError(t, err) assert.NoError(t, err)
fmt.Fprintf(c, "GET /example HTTP/1.0\r\n\r\n") fmt.Fprint(c, "GET /example HTTP/1.0\r\n\r\n")
scanner := bufio.NewScanner(c) scanner := bufio.NewScanner(c)
var response string var response string
for scanner.Scan() { for scanner.Scan() {

View File

@ -14,9 +14,9 @@ import (
const ENV_GIN_MODE = "GIN_MODE" const ENV_GIN_MODE = "GIN_MODE"
const ( const (
DebugMode string = "debug" DebugMode = "debug"
ReleaseMode string = "release" ReleaseMode = "release"
TestMode string = "test" TestMode = "test"
) )
const ( const (
debugCode = iota debugCode = iota

View File

@ -111,10 +111,8 @@ func (h xmlmap) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
return err return err
} }
} }
if err := e.EncodeToken(xml.EndElement{Name: start.Name}); err != nil {
return err return e.EncodeToken(xml.EndElement{Name: start.Name})
}
return nil
} }
func TestRenderXML(t *testing.T) { func TestRenderXML(t *testing.T) {

14
tree.go
View File

@ -87,13 +87,13 @@ const (
type node struct { type node struct {
path string path string
wildChild bool
nType nodeType
maxParams uint8
indices string indices string
children []*node children []*node
handlers HandlersChain handlers HandlersChain
priority uint32 priority uint32
nType nodeType
maxParams uint8
wildChild bool
} }
// increments priority of the given child and reorders if necessary. // increments priority of the given child and reorders if necessary.
@ -384,7 +384,7 @@ walk: // Outer loop for walking the tree
// Nothing found. // Nothing found.
// We can recommend to redirect to the same URL without a // We can recommend to redirect to the same URL without a
// trailing slash if a leaf exists for that path. // trailing slash if a leaf exists for that path.
tsr = (path == "/" && n.handlers != nil) tsr = path == "/" && n.handlers != nil
return return
} }
@ -424,7 +424,7 @@ walk: // Outer loop for walking the tree
} }
// ... but we can't // ... but we can't
tsr = (len(path) == end+1) tsr = len(path) == end+1
return return
} }
@ -435,7 +435,7 @@ walk: // Outer loop for walking the tree
// No handle found. Check if a handle for this path + a // No handle found. Check if a handle for this path + a
// trailing slash exists for TSR recommendation // trailing slash exists for TSR recommendation
n = n.children[0] n = n.children[0]
tsr = (n.path == "/" && n.handlers != nil) tsr = n.path == "/" && n.handlers != nil
} }
return return
@ -530,7 +530,7 @@ func (n *node) findCaseInsensitivePath(path string, fixTrailingSlash bool) (ciPa
// Nothing found. We can recommend to redirect to the same URL // Nothing found. We can recommend to redirect to the same URL
// without a trailing slash if a leaf exists for that path // without a trailing slash if a leaf exists for that path
found = (fixTrailingSlash && path == "/" && n.handlers != nil) found = fixTrailingSlash && path == "/" && n.handlers != nil
return return
} }

View File

@ -5,22 +5,11 @@
package gin package gin
import ( import (
"fmt"
"reflect" "reflect"
"strings" "strings"
"testing" "testing"
) )
func printChildren(n *node, prefix string) {
fmt.Printf(" %02d:%02d %s%s[%d] %v %t %d \r\n", n.priority, n.maxParams, prefix, n.path, len(n.children), n.handlers, n.wildChild, n.nType)
for l := len(n.path); l > 0; l-- {
prefix += " "
}
for _, child := range n.children {
printChildren(child, prefix)
}
}
// Used as a workaround since we can't compare functions or their addressses // Used as a workaround since we can't compare functions or their addressses
var fakeHandlerValue string var fakeHandlerValue string

View File

@ -33,18 +33,23 @@ func Bind(val interface{}) HandlerFunc {
} }
} }
// WrapF is a helper function for wrapping http.HandlerFunc
// Returns a Gin middleware
func WrapF(f http.HandlerFunc) HandlerFunc { func WrapF(f http.HandlerFunc) HandlerFunc {
return func(c *Context) { return func(c *Context) {
f(c.Writer, c.Request) f(c.Writer, c.Request)
} }
} }
// WrapH is a helper function for wrapping http.Handler
// Returns a Gin middleware
func WrapH(h http.Handler) HandlerFunc { func WrapH(h http.Handler) HandlerFunc {
return func(c *Context) { return func(c *Context) {
h.ServeHTTP(c.Writer, c.Request) h.ServeHTTP(c.Writer, c.Request)
} }
} }
// H is a shortcup for map[string]interface{}
type H map[string]interface{} type H map[string]interface{}
// MarshalXML allows type H to be used with xml.Marshal. // MarshalXML allows type H to be used with xml.Marshal.
@ -65,10 +70,8 @@ func (h H) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
return err return err
} }
} }
if err := e.EncodeToken(xml.EndElement{Name: start.Name}); err != nil {
return err return e.EncodeToken(xml.EndElement{Name: start.Name})
}
return nil
} }
func assert1(guard bool, text string) { func assert1(guard bool, text string) {