mirror of https://github.com/gin-gonic/gin.git
- More unit tests
- Improves HTML debug render - InputHolder removed - More debug logs
This commit is contained in:
parent
0a192fb0fa
commit
f414648384
|
@ -4,8 +4,7 @@ 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
|
||||||
|
|
||||||
**Original Developer:** Manu Martinez-Almeida (@manucorporat)
|
**Maintainer:** Manu Martinez-Almeida (@manucorporat), Javier Provecho (@javierprovecho)
|
||||||
**Long-term Maintainer:** Javier Provecho (@javierprovecho)
|
|
||||||
|
|
||||||
People and companies, who have contributed, in alphabetical order.
|
People and companies, who have contributed, in alphabetical order.
|
||||||
|
|
||||||
|
|
|
@ -30,23 +30,20 @@ var _validator = validator.NewValidator("binding", validator.BakedInValidators)
|
||||||
var (
|
var (
|
||||||
JSON = jsonBinding{}
|
JSON = jsonBinding{}
|
||||||
XML = xmlBinding{}
|
XML = xmlBinding{}
|
||||||
GETForm = getFormBinding{}
|
Form = formBinding{}
|
||||||
POSTForm = postFormBinding{}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func Default(method, contentType string) Binding {
|
func Default(method, contentType string) Binding {
|
||||||
if method == "GET" {
|
if method == "GET" {
|
||||||
return GETForm
|
return Form
|
||||||
} else {
|
} else {
|
||||||
switch contentType {
|
switch contentType {
|
||||||
case MIMEPOSTForm:
|
|
||||||
return POSTForm
|
|
||||||
case MIMEJSON:
|
case MIMEJSON:
|
||||||
return JSON
|
return JSON
|
||||||
case MIMEXML, MIMEXML2:
|
case MIMEXML, MIMEXML2:
|
||||||
return XML
|
return XML
|
||||||
default:
|
default:
|
||||||
return GETForm
|
return Form
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,8 +17,8 @@ type FooStruct struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBindingDefault(t *testing.T) {
|
func TestBindingDefault(t *testing.T) {
|
||||||
assert.Equal(t, Default("GET", ""), GETForm)
|
assert.Equal(t, Default("GET", ""), Form)
|
||||||
assert.Equal(t, Default("GET", MIMEJSON), GETForm)
|
assert.Equal(t, Default("GET", MIMEJSON), Form)
|
||||||
|
|
||||||
assert.Equal(t, Default("POST", MIMEJSON), JSON)
|
assert.Equal(t, Default("POST", MIMEJSON), JSON)
|
||||||
assert.Equal(t, Default("PUT", MIMEJSON), JSON)
|
assert.Equal(t, Default("PUT", MIMEJSON), JSON)
|
||||||
|
@ -26,54 +26,71 @@ func TestBindingDefault(t *testing.T) {
|
||||||
assert.Equal(t, Default("POST", MIMEXML), XML)
|
assert.Equal(t, Default("POST", MIMEXML), XML)
|
||||||
assert.Equal(t, Default("PUT", MIMEXML2), XML)
|
assert.Equal(t, Default("PUT", MIMEXML2), XML)
|
||||||
|
|
||||||
assert.Equal(t, Default("POST", MIMEPOSTForm), POSTForm)
|
assert.Equal(t, Default("POST", MIMEPOSTForm), Form)
|
||||||
assert.Equal(t, Default("DELETE", MIMEPOSTForm), POSTForm)
|
assert.Equal(t, Default("DELETE", MIMEPOSTForm), Form)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBindingJSON(t *testing.T) {
|
func TestBindingJSON(t *testing.T) {
|
||||||
testBinding(t,
|
testBodyBinding(t,
|
||||||
JSON, "json",
|
JSON, "json",
|
||||||
"/", "/",
|
"/", "/",
|
||||||
`{"foo": "bar"}`, `{"bar": "foo"}`)
|
`{"foo": "bar"}`, `{"bar": "foo"}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBindingPOSTForm(t *testing.T) {
|
func TestBindingForm(t *testing.T) {
|
||||||
testBinding(t,
|
testFormBinding(t, "POST",
|
||||||
POSTForm, "post_form",
|
|
||||||
"/", "/",
|
"/", "/",
|
||||||
"foo=bar", "bar=foo")
|
"foo=bar", "bar=foo")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBindingGETForm(t *testing.T) {
|
func TestBindingForm2(t *testing.T) {
|
||||||
testBinding(t,
|
testFormBinding(t, "GET",
|
||||||
GETForm, "get_form",
|
|
||||||
"/?foo=bar", "/?bar=foo",
|
"/?foo=bar", "/?bar=foo",
|
||||||
"", "")
|
"", "")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBindingXML(t *testing.T) {
|
func TestBindingXML(t *testing.T) {
|
||||||
testBinding(t,
|
testBodyBinding(t,
|
||||||
XML, "xml",
|
XML, "xml",
|
||||||
"/", "/",
|
"/", "/",
|
||||||
"<map><foo>bar</foo></map>", "<map><bar>foo</bar></map>")
|
"<map><foo>bar</foo></map>", "<map><bar>foo</bar></map>")
|
||||||
}
|
}
|
||||||
|
|
||||||
func testBinding(t *testing.T, b Binding, name, path, badPath, body, badBody string) {
|
func testFormBinding(t *testing.T, method, path, badPath, body, badBody string) {
|
||||||
assert.Equal(t, b.Name(), name)
|
b := Form
|
||||||
|
assert.Equal(t, b.Name(), "query")
|
||||||
|
|
||||||
obj := FooStruct{}
|
obj := FooStruct{}
|
||||||
req := requestWithBody(path, body)
|
req := requestWithBody(method, path, body)
|
||||||
|
if method == "POST" {
|
||||||
|
req.Header.Add("Content-Type", MIMEPOSTForm)
|
||||||
|
}
|
||||||
err := b.Bind(req, &obj)
|
err := b.Bind(req, &obj)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, obj.Foo, "bar")
|
assert.Equal(t, obj.Foo, "bar")
|
||||||
|
|
||||||
obj = FooStruct{}
|
obj = FooStruct{}
|
||||||
req = requestWithBody(badPath, badBody)
|
req = requestWithBody(method, badPath, badBody)
|
||||||
err = JSON.Bind(req, &obj)
|
err = JSON.Bind(req, &obj)
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func requestWithBody(path, body string) (req *http.Request) {
|
func testBodyBinding(t *testing.T, b Binding, name, path, badPath, body, badBody string) {
|
||||||
req, _ = http.NewRequest("POST", path, bytes.NewBufferString(body))
|
assert.Equal(t, b.Name(), name)
|
||||||
|
|
||||||
|
obj := FooStruct{}
|
||||||
|
req := requestWithBody("POST", path, body)
|
||||||
|
err := b.Bind(req, &obj)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, obj.Foo, "bar")
|
||||||
|
|
||||||
|
obj = FooStruct{}
|
||||||
|
req = requestWithBody("POST", badPath, badBody)
|
||||||
|
err = JSON.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
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,13 +6,13 @@ package binding
|
||||||
|
|
||||||
import "net/http"
|
import "net/http"
|
||||||
|
|
||||||
type getFormBinding struct{}
|
type formBinding struct{}
|
||||||
|
|
||||||
func (_ getFormBinding) Name() string {
|
func (_ formBinding) Name() string {
|
||||||
return "get_form"
|
return "query"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_ getFormBinding) Bind(req *http.Request, obj interface{}) error {
|
func (_ formBinding) Bind(req *http.Request, obj interface{}) error {
|
||||||
if err := req.ParseForm(); err != nil {
|
if err := req.ParseForm(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
|
@ -1,23 +0,0 @@
|
||||||
// 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 binding
|
|
||||||
|
|
||||||
import "net/http"
|
|
||||||
|
|
||||||
type postFormBinding struct{}
|
|
||||||
|
|
||||||
func (_ postFormBinding) Name() string {
|
|
||||||
return "post_form"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_ postFormBinding) Bind(req *http.Request, obj interface{}) error {
|
|
||||||
if err := req.ParseForm(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := mapForm(obj, req.PostForm); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return Validate(obj)
|
|
||||||
}
|
|
106
context.go
106
context.go
|
@ -13,6 +13,7 @@ import (
|
||||||
|
|
||||||
"github.com/gin-gonic/gin/binding"
|
"github.com/gin-gonic/gin/binding"
|
||||||
"github.com/gin-gonic/gin/render"
|
"github.com/gin-gonic/gin/render"
|
||||||
|
"golang.org/x/net/context"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -27,15 +28,37 @@ const (
|
||||||
|
|
||||||
const AbortIndex = math.MaxInt8 / 2
|
const AbortIndex = math.MaxInt8 / 2
|
||||||
|
|
||||||
|
// Param is a single URL parameter, consisting of a key and a value.
|
||||||
|
type Param struct {
|
||||||
|
Key string
|
||||||
|
Value string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Params is a Param-slice, as returned by the router.
|
||||||
|
// The slice is ordered, the first URL parameter is also the first slice value.
|
||||||
|
// It is therefore safe to read values by the index.
|
||||||
|
type Params []Param
|
||||||
|
|
||||||
|
// 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) string {
|
||||||
|
for _, entry := range ps {
|
||||||
|
if entry.Key == name {
|
||||||
|
return entry.Value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
// Context is the most important part of gin. It allows us to pass variables between middleware,
|
// 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.
|
// manage the flow, validate the JSON of a request and render a JSON response for example.
|
||||||
type Context struct {
|
type Context struct {
|
||||||
|
context.Context
|
||||||
writermem responseWriter
|
writermem responseWriter
|
||||||
Request *http.Request
|
Request *http.Request
|
||||||
Writer ResponseWriter
|
Writer ResponseWriter
|
||||||
|
|
||||||
Params Params
|
Params Params
|
||||||
Input inputHolder
|
|
||||||
handlers []HandlerFunc
|
handlers []HandlerFunc
|
||||||
index int8
|
index int8
|
||||||
|
|
||||||
|
@ -63,7 +86,6 @@ func (c *Context) Copy() *Context {
|
||||||
var cp Context = *c
|
var cp Context = *c
|
||||||
cp.writermem.ResponseWriter = nil
|
cp.writermem.ResponseWriter = nil
|
||||||
cp.Writer = &cp.writermem
|
cp.Writer = &cp.writermem
|
||||||
cp.Input.context = &cp
|
|
||||||
cp.index = AbortIndex
|
cp.index = AbortIndex
|
||||||
cp.handlers = nil
|
cp.handlers = nil
|
||||||
return &cp
|
return &cp
|
||||||
|
@ -138,6 +160,75 @@ func (c *Context) LastError() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/************************************/
|
||||||
|
/************ INPUT DATA ************/
|
||||||
|
/************************************/
|
||||||
|
|
||||||
|
/** Shortcut for c.Request.FormValue(key) */
|
||||||
|
func (c *Context) FormValue(key string) (va string) {
|
||||||
|
va, _ = c.formValue(key)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Shortcut for c.Request.PostFormValue(key) */
|
||||||
|
func (c *Context) PostFormValue(key string) (va string) {
|
||||||
|
va, _ = c.postFormValue(key)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Shortcut for c.Params.ByName(key) */
|
||||||
|
func (c *Context) ParamValue(key string) (va string) {
|
||||||
|
va, _ = c.paramValue(key)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Context) DefaultPostFormValue(key, defaultValue string) string {
|
||||||
|
if va, ok := c.postFormValue(key); ok {
|
||||||
|
return va
|
||||||
|
} else {
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Context) DefaultFormValue(key, defaultValue string) string {
|
||||||
|
if va, ok := c.formValue(key); ok {
|
||||||
|
return va
|
||||||
|
} else {
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Context) DefaultParamValue(key, defaultValue string) string {
|
||||||
|
if va, ok := c.paramValue(key); ok {
|
||||||
|
return va
|
||||||
|
} else {
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Context) paramValue(key string) (string, bool) {
|
||||||
|
va := c.Params.ByName(key)
|
||||||
|
return va, len(va) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Context) formValue(key string) (string, bool) {
|
||||||
|
req := c.Request
|
||||||
|
req.ParseForm()
|
||||||
|
if values, ok := req.Form[key]; ok && len(values) > 0 {
|
||||||
|
return values[0], true
|
||||||
|
}
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Context) postFormValue(key string) (string, bool) {
|
||||||
|
req := c.Request
|
||||||
|
req.ParseForm()
|
||||||
|
if values, ok := req.PostForm[key]; ok && len(values) > 0 {
|
||||||
|
return values[0], true
|
||||||
|
}
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
/************************************/
|
/************************************/
|
||||||
/******** METADATA MANAGEMENT********/
|
/******** METADATA MANAGEMENT********/
|
||||||
/************************************/
|
/************************************/
|
||||||
|
@ -168,6 +259,17 @@ func (c *Context) MustGet(key string) interface{} {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Context) Value(key interface{}) interface{} {
|
||||||
|
if key == 0 {
|
||||||
|
return c.Request
|
||||||
|
}
|
||||||
|
if keyAsString, ok := key.(string); ok {
|
||||||
|
val, _ := c.Get(keyAsString)
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
return c.Context.Value(key)
|
||||||
|
}
|
||||||
|
|
||||||
/************************************/
|
/************************************/
|
||||||
/********* PARSING REQUEST **********/
|
/********* PARSING REQUEST **********/
|
||||||
/************************************/
|
/************************************/
|
||||||
|
|
|
@ -79,15 +79,58 @@ func TestContextCopy(t *testing.T) {
|
||||||
|
|
||||||
cp := c.Copy()
|
cp := c.Copy()
|
||||||
assert.Nil(t, cp.handlers)
|
assert.Nil(t, cp.handlers)
|
||||||
|
assert.Nil(t, cp.writermem.ResponseWriter)
|
||||||
|
assert.Equal(t, &cp.writermem, cp.Writer.(*responseWriter))
|
||||||
assert.Equal(t, cp.Request, c.Request)
|
assert.Equal(t, cp.Request, c.Request)
|
||||||
assert.Equal(t, cp.index, AbortIndex)
|
assert.Equal(t, cp.index, AbortIndex)
|
||||||
assert.Equal(t, &cp.writermem, cp.Writer.(*responseWriter))
|
|
||||||
assert.Equal(t, cp.Input.context, cp)
|
|
||||||
assert.Equal(t, cp.Keys, c.Keys)
|
assert.Equal(t, cp.Keys, c.Keys)
|
||||||
assert.Equal(t, cp.Engine, c.Engine)
|
assert.Equal(t, cp.Engine, c.Engine)
|
||||||
assert.Equal(t, cp.Params, c.Params)
|
assert.Equal(t, cp.Params, c.Params)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestContextFormParse(t *testing.T) {
|
||||||
|
c, _, _ := createTestContext()
|
||||||
|
c.Request, _ = http.NewRequest("GET", "http://example.com/?foo=bar&page=10", nil)
|
||||||
|
|
||||||
|
assert.Equal(t, c.DefaultFormValue("foo", "none"), "bar")
|
||||||
|
assert.Equal(t, c.FormValue("foo"), "bar")
|
||||||
|
assert.Empty(t, c.PostFormValue("foo"))
|
||||||
|
|
||||||
|
assert.Equal(t, c.DefaultFormValue("page", "0"), "10")
|
||||||
|
assert.Equal(t, c.FormValue("page"), "10")
|
||||||
|
assert.Empty(t, c.PostFormValue("page"))
|
||||||
|
|
||||||
|
assert.Equal(t, c.DefaultFormValue("NoKey", "nada"), "nada")
|
||||||
|
assert.Empty(t, c.FormValue("NoKey"))
|
||||||
|
assert.Empty(t, c.PostFormValue("NoKey"))
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestContextPostFormParse(t *testing.T) {
|
||||||
|
c, _, _ := createTestContext()
|
||||||
|
body := bytes.NewBufferString("foo=bar&page=11&both=POST")
|
||||||
|
c.Request, _ = http.NewRequest("POST", "http://example.com/?both=GET&id=main", body)
|
||||||
|
c.Request.Header.Add("Content-Type", MIMEPOSTForm)
|
||||||
|
|
||||||
|
assert.Equal(t, c.DefaultPostFormValue("foo", "none"), "bar")
|
||||||
|
assert.Equal(t, c.PostFormValue("foo"), "bar")
|
||||||
|
assert.Equal(t, c.FormValue("foo"), "bar")
|
||||||
|
|
||||||
|
assert.Equal(t, c.DefaultPostFormValue("page", "0"), "11")
|
||||||
|
assert.Equal(t, c.PostFormValue("page"), "11")
|
||||||
|
assert.Equal(t, c.FormValue("page"), "11")
|
||||||
|
|
||||||
|
assert.Equal(t, c.PostFormValue("both"), "POST")
|
||||||
|
assert.Equal(t, c.FormValue("both"), "POST")
|
||||||
|
|
||||||
|
assert.Equal(t, c.FormValue("id"), "main")
|
||||||
|
assert.Empty(t, c.PostFormValue("id"))
|
||||||
|
|
||||||
|
assert.Equal(t, c.DefaultPostFormValue("NoKey", "nada"), "nada")
|
||||||
|
assert.Empty(t, c.PostFormValue("NoKey"))
|
||||||
|
assert.Empty(t, c.FormValue("NoKey"))
|
||||||
|
}
|
||||||
|
|
||||||
// Tests that the response is serialized as JSON
|
// Tests that the response is serialized as JSON
|
||||||
// and Content-Type is set to application/json
|
// and Content-Type is set to application/json
|
||||||
func TestContextRenderJSON(t *testing.T) {
|
func TestContextRenderJSON(t *testing.T) {
|
||||||
|
|
12
gin.go
12
gin.go
|
@ -86,9 +86,7 @@ func Default() *Engine {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (engine *Engine) allocateContext() (context *Context) {
|
func (engine *Engine) allocateContext() (context *Context) {
|
||||||
context = &Context{Engine: engine}
|
return &Context{Engine: engine}
|
||||||
context.Input = inputHolder{context: context}
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (engine *Engine) LoadHTMLGlob(pattern string) {
|
func (engine *Engine) LoadHTMLGlob(pattern string) {
|
||||||
|
@ -110,9 +108,7 @@ func (engine *Engine) LoadHTMLFiles(files ...string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (engine *Engine) SetHTMLTemplate(templ *template.Template) {
|
func (engine *Engine) SetHTMLTemplate(templ *template.Template) {
|
||||||
engine.HTMLRender = render.HTMLRender{
|
engine.HTMLRender = render.HTMLRender{Template: templ}
|
||||||
Template: templ,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Adds handlers for NoRoute. It return a 404 code by default.
|
// Adds handlers for NoRoute. It return a 404 code by default.
|
||||||
|
@ -160,11 +156,13 @@ func (engine *Engine) handle(method, path string, handlers []HandlerFunc) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (engine *Engine) Run(addr string) error {
|
func (engine *Engine) Run(addr string) error {
|
||||||
|
debugPrint("[WARNING] Running in DEBUG mode! Disable it before going production")
|
||||||
debugPrint("Listening and serving HTTP on %s\n", addr)
|
debugPrint("Listening and serving HTTP on %s\n", addr)
|
||||||
return http.ListenAndServe(addr, engine)
|
return http.ListenAndServe(addr, engine)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (engine *Engine) RunTLS(addr string, cert string, key string) error {
|
func (engine *Engine) RunTLS(addr string, cert string, key string) error {
|
||||||
|
debugPrint("[WARNING] Running in DEBUG mode! Disable it before going production")
|
||||||
debugPrint("Listening and serving HTTPS on %s\n", addr)
|
debugPrint("Listening and serving HTTPS on %s\n", addr)
|
||||||
return http.ListenAndServeTLS(addr, cert, key, engine)
|
return http.ListenAndServeTLS(addr, cert, key, engine)
|
||||||
}
|
}
|
||||||
|
@ -233,6 +231,7 @@ func (engine *Engine) serveAutoRedirect(c *Context, root *node, tsr bool) bool {
|
||||||
} else {
|
} else {
|
||||||
req.URL.Path = path + "/"
|
req.URL.Path = path + "/"
|
||||||
}
|
}
|
||||||
|
debugPrint("redirecting request %d: %s --> %s", code, path, req.URL.String())
|
||||||
http.Redirect(c.Writer, req, req.URL.String(), code)
|
http.Redirect(c.Writer, req, req.URL.String(), code)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -245,6 +244,7 @@ func (engine *Engine) serveAutoRedirect(c *Context, root *node, tsr bool) bool {
|
||||||
)
|
)
|
||||||
if found {
|
if found {
|
||||||
req.URL.Path = string(fixedPath)
|
req.URL.Path = string(fixedPath)
|
||||||
|
debugPrint("redirecting request %d: %s --> %s", code, path, req.URL.String())
|
||||||
http.Redirect(c.Writer, req, req.URL.String(), code)
|
http.Redirect(c.Writer, req, req.URL.String(), code)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,69 +0,0 @@
|
||||||
// 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
|
|
||||||
|
|
||||||
// Param is a single URL parameter, consisting of a key and a value.
|
|
||||||
type Param struct {
|
|
||||||
Key string
|
|
||||||
Value string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Params is a Param-slice, as returned by the router.
|
|
||||||
// The slice is ordered, the first URL parameter is also the first slice value.
|
|
||||||
// It is therefore safe to read values by the index.
|
|
||||||
type Params []Param
|
|
||||||
|
|
||||||
// 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) string {
|
|
||||||
for _, entry := range ps {
|
|
||||||
if entry.Key == name {
|
|
||||||
return entry.Value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
type inputHolder struct {
|
|
||||||
context *Context
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i inputHolder) FromGET(key string) (va string) {
|
|
||||||
va, _ = i.fromGET(key)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i inputHolder) FromPOST(key string) (va string) {
|
|
||||||
va, _ = i.fromPOST(key)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i inputHolder) Get(key string) string {
|
|
||||||
if value, exists := i.fromPOST(key); exists {
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
if value, exists := i.fromGET(key); exists {
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i inputHolder) fromGET(key string) (string, bool) {
|
|
||||||
req := i.context.Request
|
|
||||||
req.ParseForm()
|
|
||||||
if values, ok := req.Form[key]; ok && len(values) > 0 {
|
|
||||||
return values[0], true
|
|
||||||
}
|
|
||||||
return "", false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i inputHolder) fromPOST(key string) (string, bool) {
|
|
||||||
req := i.context.Request
|
|
||||||
req.ParseForm()
|
|
||||||
if values, ok := req.PostForm[key]; ok && len(values) > 0 {
|
|
||||||
return values[0], true
|
|
||||||
}
|
|
||||||
return "", false
|
|
||||||
}
|
|
|
@ -0,0 +1,144 @@
|
||||||
|
// 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 (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMiddlewareGeneralCase(t *testing.T) {
|
||||||
|
signature := ""
|
||||||
|
router := New()
|
||||||
|
router.Use(func(c *Context) {
|
||||||
|
signature += "A"
|
||||||
|
c.Next()
|
||||||
|
signature += "B"
|
||||||
|
})
|
||||||
|
router.Use(func(c *Context) {
|
||||||
|
signature += "C"
|
||||||
|
})
|
||||||
|
router.GET("/", func(c *Context) {
|
||||||
|
signature += "D"
|
||||||
|
})
|
||||||
|
router.NoRoute(func(c *Context) {
|
||||||
|
signature += "X"
|
||||||
|
})
|
||||||
|
router.NoMethod(func(c *Context) {
|
||||||
|
signature += "X"
|
||||||
|
})
|
||||||
|
// RUN
|
||||||
|
w := performRequest(router, "GET", "/")
|
||||||
|
|
||||||
|
// TEST
|
||||||
|
assert.Equal(t, w.Code, 200)
|
||||||
|
assert.Equal(t, signature, "ACDB")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestBadAbortHandlersChain - ensure that Abort after switch context will not interrupt pending handlers
|
||||||
|
func TestMiddlewareNextOrder(t *testing.T) {
|
||||||
|
signature := ""
|
||||||
|
router := New()
|
||||||
|
router.Use(func(c *Context) {
|
||||||
|
signature += "A"
|
||||||
|
c.Next()
|
||||||
|
signature += "B"
|
||||||
|
})
|
||||||
|
router.Use(func(c *Context) {
|
||||||
|
signature += "C"
|
||||||
|
c.Next()
|
||||||
|
signature += "D"
|
||||||
|
})
|
||||||
|
router.NoRoute(func(c *Context) {
|
||||||
|
signature += "E"
|
||||||
|
c.Next()
|
||||||
|
signature += "F"
|
||||||
|
}, func(c *Context) {
|
||||||
|
signature += "G"
|
||||||
|
c.Next()
|
||||||
|
signature += "H"
|
||||||
|
})
|
||||||
|
// RUN
|
||||||
|
w := performRequest(router, "GET", "/")
|
||||||
|
|
||||||
|
// TEST
|
||||||
|
assert.Equal(t, w.Code, 404)
|
||||||
|
assert.Equal(t, signature, "ACEGHFDB")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestAbortHandlersChain - ensure that Abort interrupt used middlewares in fifo order
|
||||||
|
func TestMiddlewareAbortHandlersChain(t *testing.T) {
|
||||||
|
signature := ""
|
||||||
|
router := New()
|
||||||
|
router.Use(func(c *Context) {
|
||||||
|
signature += "A"
|
||||||
|
})
|
||||||
|
router.Use(func(c *Context) {
|
||||||
|
signature += "C"
|
||||||
|
c.AbortWithStatus(409)
|
||||||
|
c.Next()
|
||||||
|
signature += "D"
|
||||||
|
})
|
||||||
|
router.GET("/", func(c *Context) {
|
||||||
|
signature += "D"
|
||||||
|
c.Next()
|
||||||
|
signature += "E"
|
||||||
|
})
|
||||||
|
|
||||||
|
// RUN
|
||||||
|
w := performRequest(router, "GET", "/")
|
||||||
|
|
||||||
|
// TEST
|
||||||
|
assert.Equal(t, w.Code, 409)
|
||||||
|
assert.Equal(t, signature, "ACD")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMiddlewareAbortHandlersChainAndNext(t *testing.T) {
|
||||||
|
signature := ""
|
||||||
|
router := New()
|
||||||
|
router.Use(func(c *Context) {
|
||||||
|
signature += "A"
|
||||||
|
c.AbortWithStatus(410)
|
||||||
|
c.Next()
|
||||||
|
signature += "B"
|
||||||
|
|
||||||
|
})
|
||||||
|
router.GET("/", func(c *Context) {
|
||||||
|
signature += "C"
|
||||||
|
c.Next()
|
||||||
|
})
|
||||||
|
// RUN
|
||||||
|
w := performRequest(router, "GET", "/")
|
||||||
|
|
||||||
|
// TEST
|
||||||
|
assert.Equal(t, w.Code, 410)
|
||||||
|
assert.Equal(t, signature, "AB")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestFailHandlersChain - ensure that Fail interrupt used middlewares in fifo order as
|
||||||
|
// as well as Abort
|
||||||
|
func TestMiddlewareFailHandlersChain(t *testing.T) {
|
||||||
|
// SETUP
|
||||||
|
signature := ""
|
||||||
|
router := New()
|
||||||
|
router.Use(func(context *Context) {
|
||||||
|
signature += "A"
|
||||||
|
context.Fail(500, errors.New("foo"))
|
||||||
|
})
|
||||||
|
router.Use(func(context *Context) {
|
||||||
|
signature += "B"
|
||||||
|
context.Next()
|
||||||
|
signature += "C"
|
||||||
|
})
|
||||||
|
// RUN
|
||||||
|
w := performRequest(router, "GET", "/")
|
||||||
|
|
||||||
|
// TEST
|
||||||
|
assert.Equal(t, w.Code, 500)
|
||||||
|
assert.Equal(t, signature, "A")
|
||||||
|
}
|
|
@ -5,6 +5,7 @@
|
||||||
package render
|
package render
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"html/template"
|
"html/template"
|
||||||
"net/http"
|
"net/http"
|
||||||
)
|
)
|
||||||
|
@ -19,24 +20,19 @@ func (r *HTMLDebugRender) Render(w http.ResponseWriter, code int, data ...interf
|
||||||
file := data[0].(string)
|
file := data[0].(string)
|
||||||
obj := data[1]
|
obj := data[1]
|
||||||
|
|
||||||
if t, err := r.newTemplate(); err == nil {
|
if t, err := r.loadTemplate(); err == nil {
|
||||||
return t.ExecuteTemplate(w, file, obj)
|
return t.ExecuteTemplate(w, file, obj)
|
||||||
} else {
|
} else {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *HTMLDebugRender) newTemplate() (*template.Template, error) {
|
func (r *HTMLDebugRender) loadTemplate() (*template.Template, error) {
|
||||||
t := template.New("")
|
|
||||||
if len(r.Files) > 0 {
|
if len(r.Files) > 0 {
|
||||||
if _, err := t.ParseFiles(r.Files...); err != nil {
|
return template.ParseFiles(r.Files...)
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if len(r.Glob) > 0 {
|
if len(r.Glob) > 0 {
|
||||||
if _, err := t.ParseGlob(r.Glob); err != nil {
|
return template.ParseGlob(r.Glob)
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
}
|
return nil, errors.New("the HTML debug render was created without files or glob pattern")
|
||||||
return t, nil
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -75,5 +75,4 @@ func TestRenderJoinStrings(t *testing.T) {
|
||||||
assert.Equal(t, joinStrings("a", "BB", "c"), "aBBc")
|
assert.Equal(t, joinStrings("a", "BB", "c"), "aBBc")
|
||||||
assert.Equal(t, joinStrings("a", "", "c"), "ac")
|
assert.Equal(t, joinStrings("a", "", "c"), "ac")
|
||||||
assert.Equal(t, joinStrings("text/html", "; charset=utf-8"), "text/html; charset=utf-8")
|
assert.Equal(t, joinStrings("text/html", "; charset=utf-8"), "text/html; charset=utf-8")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
174
routes_test.go
174
routes_test.go
|
@ -5,7 +5,6 @@
|
||||||
package gin
|
package gin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
|
@ -107,8 +106,26 @@ func TestRouteNotOK2(t *testing.T) {
|
||||||
testRouteNotOK2("HEAD", t)
|
testRouteNotOK2("HEAD", t)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestContextParamsGet tests that a parameter can be parsed from the URL.
|
||||||
|
func TestRouteParamsByName(t *testing.T) {
|
||||||
|
name := ""
|
||||||
|
lastName := ""
|
||||||
|
router := New()
|
||||||
|
router.GET("/test/:name/:last_name", func(c *Context) {
|
||||||
|
name = c.Params.ByName("name")
|
||||||
|
lastName = c.Params.ByName("last_name")
|
||||||
|
})
|
||||||
|
// RUN
|
||||||
|
w := performRequest(router, "GET", "/test/john/smith")
|
||||||
|
|
||||||
|
// TEST
|
||||||
|
assert.Equal(t, w.Code, 200)
|
||||||
|
assert.Equal(t, name, "john")
|
||||||
|
assert.Equal(t, lastName, "smith")
|
||||||
|
}
|
||||||
|
|
||||||
// TestHandleStaticFile - ensure the static file handles properly
|
// TestHandleStaticFile - ensure the static file handles properly
|
||||||
func TestHandleStaticFile(t *testing.T) {
|
func TestRouteStaticFile(t *testing.T) {
|
||||||
// SETUP file
|
// SETUP file
|
||||||
testRoot, _ := os.Getwd()
|
testRoot, _ := os.Getwd()
|
||||||
f, err := ioutil.TempFile(testRoot, "")
|
f, err := ioutil.TempFile(testRoot, "")
|
||||||
|
@ -134,7 +151,7 @@ func TestHandleStaticFile(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestHandleStaticDir - ensure the root/sub dir handles properly
|
// TestHandleStaticDir - ensure the root/sub dir handles properly
|
||||||
func TestHandleStaticDir(t *testing.T) {
|
func TestRouteStaticDir(t *testing.T) {
|
||||||
// SETUP
|
// SETUP
|
||||||
r := New()
|
r := New()
|
||||||
r.Static("/", "./")
|
r.Static("/", "./")
|
||||||
|
@ -151,7 +168,7 @@ func TestHandleStaticDir(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestHandleHeadToDir - ensure the root/sub dir handles properly
|
// TestHandleHeadToDir - ensure the root/sub dir handles properly
|
||||||
func TestHandleHeadToDir(t *testing.T) {
|
func TestRouteHeadToDir(t *testing.T) {
|
||||||
// SETUP
|
// SETUP
|
||||||
router := New()
|
router := New()
|
||||||
router.Static("/", "./")
|
router.Static("/", "./")
|
||||||
|
@ -166,152 +183,3 @@ func TestHandleHeadToDir(t *testing.T) {
|
||||||
assert.Contains(t, bodyAsString, "gin.go")
|
assert.Contains(t, bodyAsString, "gin.go")
|
||||||
assert.Equal(t, w.HeaderMap.Get("Content-Type"), "text/html; charset=utf-8")
|
assert.Equal(t, w.HeaderMap.Get("Content-Type"), "text/html; charset=utf-8")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestContextGeneralCase(t *testing.T) {
|
|
||||||
signature := ""
|
|
||||||
router := New()
|
|
||||||
router.Use(func(c *Context) {
|
|
||||||
signature += "A"
|
|
||||||
c.Next()
|
|
||||||
signature += "B"
|
|
||||||
})
|
|
||||||
router.Use(func(c *Context) {
|
|
||||||
signature += "C"
|
|
||||||
})
|
|
||||||
router.GET("/", func(c *Context) {
|
|
||||||
signature += "D"
|
|
||||||
})
|
|
||||||
router.NoRoute(func(c *Context) {
|
|
||||||
signature += "X"
|
|
||||||
})
|
|
||||||
router.NoMethod(func(c *Context) {
|
|
||||||
signature += "X"
|
|
||||||
})
|
|
||||||
// RUN
|
|
||||||
w := performRequest(router, "GET", "/")
|
|
||||||
|
|
||||||
// TEST
|
|
||||||
assert.Equal(t, w.Code, 200)
|
|
||||||
assert.Equal(t, signature, "ACDB")
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestBadAbortHandlersChain - ensure that Abort after switch context will not interrupt pending handlers
|
|
||||||
func TestContextNextOrder(t *testing.T) {
|
|
||||||
signature := ""
|
|
||||||
router := New()
|
|
||||||
router.Use(func(c *Context) {
|
|
||||||
signature += "A"
|
|
||||||
c.Next()
|
|
||||||
signature += "B"
|
|
||||||
})
|
|
||||||
router.Use(func(c *Context) {
|
|
||||||
signature += "C"
|
|
||||||
c.Next()
|
|
||||||
signature += "D"
|
|
||||||
})
|
|
||||||
router.NoRoute(func(c *Context) {
|
|
||||||
signature += "E"
|
|
||||||
c.Next()
|
|
||||||
signature += "F"
|
|
||||||
}, func(c *Context) {
|
|
||||||
signature += "G"
|
|
||||||
c.Next()
|
|
||||||
signature += "H"
|
|
||||||
})
|
|
||||||
// RUN
|
|
||||||
w := performRequest(router, "GET", "/")
|
|
||||||
|
|
||||||
// TEST
|
|
||||||
assert.Equal(t, w.Code, 404)
|
|
||||||
assert.Equal(t, signature, "ACEGHFDB")
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestAbortHandlersChain - ensure that Abort interrupt used middlewares in fifo order
|
|
||||||
func TestAbortHandlersChain(t *testing.T) {
|
|
||||||
signature := ""
|
|
||||||
router := New()
|
|
||||||
router.Use(func(c *Context) {
|
|
||||||
signature += "A"
|
|
||||||
})
|
|
||||||
router.Use(func(c *Context) {
|
|
||||||
signature += "C"
|
|
||||||
c.AbortWithStatus(409)
|
|
||||||
c.Next()
|
|
||||||
signature += "D"
|
|
||||||
})
|
|
||||||
router.GET("/", func(c *Context) {
|
|
||||||
signature += "D"
|
|
||||||
c.Next()
|
|
||||||
signature += "E"
|
|
||||||
})
|
|
||||||
|
|
||||||
// RUN
|
|
||||||
w := performRequest(router, "GET", "/")
|
|
||||||
|
|
||||||
// TEST
|
|
||||||
assert.Equal(t, w.Code, 409)
|
|
||||||
assert.Equal(t, signature, "ACD")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAbortHandlersChainAndNext(t *testing.T) {
|
|
||||||
signature := ""
|
|
||||||
router := New()
|
|
||||||
router.Use(func(c *Context) {
|
|
||||||
signature += "A"
|
|
||||||
c.AbortWithStatus(410)
|
|
||||||
c.Next()
|
|
||||||
signature += "B"
|
|
||||||
|
|
||||||
})
|
|
||||||
router.GET("/", func(c *Context) {
|
|
||||||
signature += "C"
|
|
||||||
c.Next()
|
|
||||||
})
|
|
||||||
// RUN
|
|
||||||
w := performRequest(router, "GET", "/")
|
|
||||||
|
|
||||||
// TEST
|
|
||||||
assert.Equal(t, w.Code, 410)
|
|
||||||
assert.Equal(t, signature, "AB")
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestContextParamsGet tests that a parameter can be parsed from the URL.
|
|
||||||
func TestContextParamsByName(t *testing.T) {
|
|
||||||
name := ""
|
|
||||||
lastName := ""
|
|
||||||
router := New()
|
|
||||||
router.GET("/test/:name/:last_name", func(c *Context) {
|
|
||||||
name = c.Params.ByName("name")
|
|
||||||
lastName = c.Params.ByName("last_name")
|
|
||||||
})
|
|
||||||
// RUN
|
|
||||||
w := performRequest(router, "GET", "/test/john/smith")
|
|
||||||
|
|
||||||
// TEST
|
|
||||||
assert.Equal(t, w.Code, 200)
|
|
||||||
assert.Equal(t, name, "john")
|
|
||||||
assert.Equal(t, lastName, "smith")
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestFailHandlersChain - ensure that Fail interrupt used middlewares in fifo order as
|
|
||||||
// as well as Abort
|
|
||||||
func TestFailHandlersChain(t *testing.T) {
|
|
||||||
// SETUP
|
|
||||||
signature := ""
|
|
||||||
router := New()
|
|
||||||
router.Use(func(context *Context) {
|
|
||||||
signature += "A"
|
|
||||||
context.Fail(500, errors.New("foo"))
|
|
||||||
})
|
|
||||||
router.Use(func(context *Context) {
|
|
||||||
signature += "B"
|
|
||||||
context.Next()
|
|
||||||
signature += "C"
|
|
||||||
})
|
|
||||||
// RUN
|
|
||||||
w := performRequest(router, "GET", "/")
|
|
||||||
|
|
||||||
// TEST
|
|
||||||
assert.Equal(t, w.Code, 500)
|
|
||||||
assert.Equal(t, signature, "A")
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in New Issue