Merge branch 'master' into mew-catch-all-v2

This commit is contained in:
Javier Provecho Fernandez 2017-08-26 11:31:03 +02:00 committed by GitHub
commit e2e0717646
18 changed files with 114 additions and 84 deletions

View File

@ -4,6 +4,7 @@ go:
- 1.6.x - 1.6.x
- 1.7.x - 1.7.x
- 1.8.x - 1.8.x
- 1.9.x
- master - master
git: git:

View File

@ -369,6 +369,7 @@ r := gin.New()
instead of instead of
```go ```go
// Default With the Logger and Recovery middleware already attached
r := gin.Default() r := gin.Default()
``` ```
@ -380,7 +381,11 @@ 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.
// By default gin.DefaultWriter = os.Stdout
r.Use(gin.Logger()) r.Use(gin.Logger())
// Recovery middleware recovers from any panics and writes a 500 if there was one.
r.Use(gin.Recovery()) r.Use(gin.Recovery())
// Per route middleware, you can add as many as you desire. // Per route middleware, you can add as many as you desire.
@ -408,6 +413,28 @@ func main() {
} }
``` ```
### How to write log file
```go
func main() {
// Disable Console Color, you don't need console color when writing the logs to file.
gin.DisableConsoleColor()
// Logging to a file.
f, _ := os.Create("gin.log")
gin.DefaultWriter = io.MultiWriter(f)
// Use the following code if you need to write the logs to file and console at the same time.
// gin.DefaultWriter = io.MultiWriter(f, os.Stdout)
router := gin.Default()
router.GET("/ping", func(c *gin.Context) {
c.String(200, "pong")
})
r.Run(":8080")
}
```
### Model binding and validation ### Model binding and validation
To bind a request body into a type, use model binding. We currently support binding of JSON, XML and standard form values (foo=bar&boo=baz). To bind a request body into a type, use model binding. We currently support binding of JSON, XML and standard form values (foo=bar&boo=baz).
@ -505,10 +532,12 @@ package main
import "log" import "log"
import "github.com/gin-gonic/gin" import "github.com/gin-gonic/gin"
import "time"
type Person struct { type Person struct {
Name string `form:"name"` Name string `form:"name"`
Address string `form:"address"` Address string `form:"address"`
Birthday time.Time `form:"birthday" time_format:"2006-01-02" time_utc:"1"`
} }
func main() { func main() {
@ -525,12 +554,18 @@ func startPage(c *gin.Context) {
if c.Bind(&person) == nil { if c.Bind(&person) == nil {
log.Println(person.Name) log.Println(person.Name)
log.Println(person.Address) log.Println(person.Address)
log.Println(person.Birthday)
} }
c.String(200, "Success") c.String(200, "Success")
} }
``` ```
Test it with:
```sh
$ curl -X GET "localhost:8085/testing?name=appleboy&address=xyz&birthday=1992-03-15"
```
### Bind HTML checkboxes ### Bind HTML checkboxes
See the [detail information](https://github.com/gin-gonic/gin/issues/129#issuecomment-124260092) See the [detail information](https://github.com/gin-gonic/gin/issues/129#issuecomment-124260092)

View File

@ -10,10 +10,10 @@ import (
"strconv" "strconv"
) )
// AuthUserKey is the cookie name for user credential in basic auth // AuthUserKey is the cookie name for user credential in basic auth.
const AuthUserKey = "user" const AuthUserKey = "user"
// Accounts defines a key/value for user/pass list of authorized logins // Accounts defines a key/value for user/pass list of authorized logins.
type Accounts map[string]string type Accounts map[string]string
type authPair struct { type authPair struct {
@ -56,7 +56,7 @@ func BasicAuthForRealm(accounts Accounts, realm string) HandlerFunc {
} }
// The user credentials was found, set user's id to key AuthUserKey in this context, the user's id can be read later using // The user credentials was found, set user's id to key AuthUserKey in this context, the user's id can be read later using
// c.MustGet(gin.AuthUserKey) // c.MustGet(gin.AuthUserKey).
c.Set(AuthUserKey, user) c.Set(AuthUserKey, user)
} }
} }
@ -90,6 +90,6 @@ func secureCompare(given, actual string) bool {
if subtle.ConstantTimeEq(int32(len(given)), int32(len(actual))) == 1 { if subtle.ConstantTimeEq(int32(len(given)), int32(len(actual))) == 1 {
return subtle.ConstantTimeCompare([]byte(given), []byte(actual)) == 1 return subtle.ConstantTimeCompare([]byte(given), []byte(actual)) == 1
} }
// Securely compare actual to itself to keep constant time, but always return false // Securely compare actual to itself to keep constant time, but always return false.
return subtle.ConstantTimeCompare([]byte(actual), []byte(actual)) == 1 && false return subtle.ConstantTimeCompare([]byte(actual), []byte(actual)) == 1 && false
} }

View File

@ -22,7 +22,7 @@ import (
"github.com/gin-gonic/gin/render" "github.com/gin-gonic/gin/render"
) )
// Content-Type MIME of the most common data formats // Content-Type MIME of the most common data formats.
const ( const (
MIMEJSON = binding.MIMEJSON MIMEJSON = binding.MIMEJSON
MIMEHTML = binding.MIMEHTML MIMEHTML = binding.MIMEHTML
@ -51,13 +51,13 @@ type Context struct {
engine *Engine engine *Engine
// Keys is a key/value pair exclusively for the context of each request // Keys is a key/value pair exclusively for the context of each request.
Keys map[string]interface{} Keys map[string]interface{}
// Errors is a list of errors attached to all the handlers/middlewares who used this context // Errors is a list of errors attached to all the handlers/middlewares who used this context.
Errors errorMsgs Errors errorMsgs
// Accepted defines a list of manually accepted formats for content negotiation // Accepted defines a list of manually accepted formats for content negotiation.
Accepted []string Accepted []string
} }
@ -87,7 +87,7 @@ func (c *Context) Copy() *Context {
} }
// HandlerName returns the main handler's name. For example if the handler is "handleGetUsers()", // HandlerName returns the main handler's name. For example if the handler is "handleGetUsers()",
// this function will return "main.handleGetUsers" // this function will return "main.handleGetUsers".
func (c *Context) HandlerName() string { func (c *Context) HandlerName() string {
return nameOfFunction(c.handlers.Last()) return nameOfFunction(c.handlers.Last())
} }
@ -462,18 +462,18 @@ func (c *Context) Bind(obj interface{}) error {
return c.MustBindWith(obj, b) return c.MustBindWith(obj, b)
} }
// BindJSON is a shortcut for c.MustBindWith(obj, binding.JSON) // BindJSON is a shortcut for c.MustBindWith(obj, binding.JSON).
func (c *Context) BindJSON(obj interface{}) error { func (c *Context) BindJSON(obj interface{}) error {
return c.MustBindWith(obj, binding.JSON) return c.MustBindWith(obj, binding.JSON)
} }
// BindQuery is a shortcut for c.MustBindWith(obj, binding.Query) // BindQuery is a shortcut for c.MustBindWith(obj, binding.Query).
func (c *Context) BindQuery(obj interface{}) error { func (c *Context) BindQuery(obj interface{}) error {
return c.MustBindWith(obj, binding.Query) return c.MustBindWith(obj, binding.Query)
} }
// MustBindWith binds the passed struct pointer using the specified binding // MustBindWith binds the passed struct pointer using the specified binding engine.
// engine. It will abort the request with HTTP 400 if any error ocurrs. // It will abort the request with HTTP 400 if any error ocurrs.
// See the binding package. // See the binding package.
func (c *Context) MustBindWith(obj interface{}, b binding.Binding) (err error) { func (c *Context) MustBindWith(obj interface{}, b binding.Binding) (err error) {
if err = c.ShouldBindWith(obj, b); err != nil { if err = c.ShouldBindWith(obj, b); err != nil {
@ -483,8 +483,7 @@ func (c *Context) MustBindWith(obj interface{}, b binding.Binding) (err error) {
return return
} }
// ShouldBindWith binds the passed struct pointer using the specified binding // ShouldBindWith binds the passed struct pointer using the specified binding engine.
// engine.
// See the binding package. // See the binding package.
func (c *Context) ShouldBindWith(obj interface{}, b binding.Binding) error { func (c *Context) ShouldBindWith(obj interface{}, b binding.Binding) error {
return b.Bind(c.Request, obj) return b.Bind(c.Request, obj)
@ -538,17 +537,14 @@ func (c *Context) IsWebsocket() bool {
} }
func (c *Context) requestHeader(key string) string { func (c *Context) requestHeader(key string) string {
if values, _ := c.Request.Header[key]; len(values) > 0 { return c.Request.Header.Get(key)
return values[0]
}
return ""
} }
/************************************/ /************************************/
/******** RESPONSE RENDERING ********/ /******** RESPONSE RENDERING ********/
/************************************/ /************************************/
// bodyAllowedForStatus is a copy of http.bodyAllowedForStatus non-exported function // bodyAllowedForStatus is a copy of http.bodyAllowedForStatus non-exported function.
func bodyAllowedForStatus(status int) bool { func bodyAllowedForStatus(status int) bool {
switch { switch {
case status >= 100 && status <= 199: case status >= 100 && status <= 199:
@ -566,7 +562,7 @@ func (c *Context) Status(code int) {
c.writermem.WriteHeader(code) c.writermem.WriteHeader(code)
} }
// Header is a intelligent shortcut for c.Writer.Header().Set(key, value) // Header is a intelligent shortcut for c.Writer.Header().Set(key, value).
// It writes a header in the response. // It writes a header in the response.
// If value == "", this method removes the header `c.Writer.Header().Del(key)` // If value == "", this method removes the header `c.Writer.Header().Del(key)`
func (c *Context) Header(key, value string) { func (c *Context) Header(key, value string) {
@ -577,12 +573,12 @@ func (c *Context) Header(key, value string) {
} }
} }
// GetHeader returns value from request headers // GetHeader returns value from request headers.
func (c *Context) GetHeader(key string) string { func (c *Context) GetHeader(key string) string {
return c.requestHeader(key) return c.requestHeader(key)
} }
// GetRawData return stream data // GetRawData return stream data.
func (c *Context) GetRawData() ([]byte, error) { func (c *Context) GetRawData() ([]byte, error) {
return ioutil.ReadAll(c.Request.Body) return ioutil.ReadAll(c.Request.Body)
} }

View File

@ -65,7 +65,7 @@ func (msg *Error) JSON() interface{} {
return json return json
} }
// MarshalJSON 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())
} }
@ -80,7 +80,7 @@ func (msg *Error) IsType(flags ErrorType) bool {
} }
// ByType returns a readonly copy filtered the byte. // ByType returns a readonly copy filtered the byte.
// ie ByType(gin.ErrorTypePublic) returns a slice of errors with type=ErrorTypePublic // ie ByType(gin.ErrorTypePublic) returns a slice of errors with type=ErrorTypePublic.
func (a errorMsgs) ByType(typ ErrorType) errorMsgs { func (a errorMsgs) ByType(typ ErrorType) errorMsgs {
if len(a) == 0 { if len(a) == 0 {
return nil return nil
@ -98,7 +98,7 @@ func (a errorMsgs) ByType(typ ErrorType) errorMsgs {
} }
// Last returns the last error in the slice. It returns nil if the array is empty. // Last returns the last error in the slice. It returns nil if the array is empty.
// Shortcut for errors[len(errors)-1] // Shortcut for errors[len(errors)-1].
func (a errorMsgs) Last() *Error { func (a errorMsgs) Last() *Error {
if length := len(a); length > 0 { if length := len(a); length > 0 {
return a[length-1] return a[length-1]

View File

@ -13,10 +13,10 @@ func init() {
// Define your handlers // Define your handlers
r.GET("/", func(c *gin.Context) { r.GET("/", func(c *gin.Context) {
c.String(200, "Hello World!") c.String(http.StatusOK, "Hello World!")
}) })
r.GET("/ping", func(c *gin.Context) { r.GET("/ping", func(c *gin.Context) {
c.String(200, "pong") c.String(http.StatusOK, "pong")
}) })
// Handle all requests using net/http // Handle all requests using net/http

4
fs.go
View File

@ -29,7 +29,7 @@ func Dir(root string, listDirectory bool) http.FileSystem {
return &onlyfilesFS{fs} return &onlyfilesFS{fs}
} }
// Open conforms to http.Filesystem // Open conforms to http.Filesystem.
func (fs onlyfilesFS) Open(name string) (http.File, error) { func (fs onlyfilesFS) Open(name string) (http.File, error) {
f, err := fs.fs.Open(name) f, err := fs.fs.Open(name)
if err != nil { if err != nil {
@ -38,7 +38,7 @@ func (fs onlyfilesFS) Open(name string) (http.File, error) {
return neuteredReaddirFile{f}, nil return neuteredReaddirFile{f}, nil
} }
// Readdir overrides the http.File default implementation // Readdir overrides the http.File default implementation.
func (f neuteredReaddirFile) Readdir(count int) ([]os.FileInfo, error) { func (f neuteredReaddirFile) Readdir(count int) ([]os.FileInfo, error) {
// this disables directory listing // this disables directory listing
return nil, nil return nil, nil

13
gin.go
View File

@ -14,7 +14,7 @@ import (
"github.com/gin-gonic/gin/render" "github.com/gin-gonic/gin/render"
) )
// Version is Framework's version // Version is Framework's version.
const Version = "v1.2" const Version = "v1.2"
var default404Body = []byte("404 page not found") var default404Body = []byte("404 page not found")
@ -191,7 +191,7 @@ func (engine *Engine) NoRoute(handlers ...HandlerFunc) {
engine.rebuild404Handlers() engine.rebuild404Handlers()
} }
// NoMethod 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()
@ -268,7 +268,7 @@ func (engine *Engine) Run(addr ...string) (err error) {
// RunTLS attaches the router to a http.Server and starts listening and serving HTTPS (secure) requests. // RunTLS attaches the router to a http.Server and starts listening and serving HTTPS (secure) 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 indefinitely unless an error happens. // Note: this method will block the calling goroutine indefinitely unless an error happens.
func (engine *Engine) RunTLS(addr string, certFile string, keyFile string) (err error) { func (engine *Engine) RunTLS(addr, certFile, keyFile string) (err error) {
debugPrint("Listening and serving HTTPS on %s\n", addr) debugPrint("Listening and serving HTTPS on %s\n", addr)
defer func() { debugPrintError(err) }() defer func() { debugPrintError(err) }()
@ -316,14 +316,11 @@ func (engine *Engine) HandleContext(c *Context) {
func (engine *Engine) handleHTTPRequest(context *Context) { func (engine *Engine) handleHTTPRequest(context *Context) {
httpMethod := context.Request.Method httpMethod := context.Request.Method
var path string path := context.Request.URL.Path
var unescape bool unescape := false
if engine.UseRawPath && len(context.Request.URL.RawPath) > 0 { if engine.UseRawPath && len(context.Request.URL.RawPath) > 0 {
path = context.Request.URL.RawPath path = context.Request.URL.RawPath
unescape = engine.UnescapePathValues unescape = engine.UnescapePathValues
} else {
path = context.Request.URL.Path
unescape = false
} }
// Find root of the tree for the given HTTP method // Find root of the tree for the given HTTP method

View File

@ -9,15 +9,15 @@ import (
"net/http" "net/http"
"sync" "sync"
. "github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
var once sync.Once var once sync.Once
var internalEngine *Engine var internalEngine *gin.Engine
func engine() *Engine { func engine() *gin.Engine {
once.Do(func() { once.Do(func() {
internalEngine = Default() internalEngine = gin.Default()
}) })
return internalEngine return internalEngine
} }

View File

@ -25,17 +25,17 @@ var (
disableColor = false disableColor = false
) )
// DisableConsoleColor disables color output in the console // DisableConsoleColor disables color output in the console.
func DisableConsoleColor() { func DisableConsoleColor() {
disableColor = true disableColor = true
} }
// ErrorLogger returns a handlerfunc for any error type // ErrorLogger returns a handlerfunc for any error type.
func ErrorLogger() HandlerFunc { func ErrorLogger() HandlerFunc {
return ErrorLoggerT(ErrorTypeAny) return ErrorLoggerT(ErrorTypeAny)
} }
// ErrorLoggerT returns a handlerfunc for a given error type // ErrorLoggerT returns a handlerfunc for a given error type.
func ErrorLoggerT(typ ErrorType) HandlerFunc { func ErrorLoggerT(typ ErrorType) HandlerFunc {
return func(c *Context) { return func(c *Context) {
c.Next() c.Next()
@ -46,8 +46,8 @@ func ErrorLoggerT(typ ErrorType) HandlerFunc {
} }
} }
// Logger 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)
} }
@ -91,10 +91,11 @@ func LoggerWithWriter(out io.Writer, notlogged ...string) HandlerFunc {
clientIP := c.ClientIP() clientIP := c.ClientIP()
method := c.Request.Method method := c.Request.Method
statusCode := c.Writer.Status() statusCode := c.Writer.Status()
var statusColor, methodColor string var statusColor, methodColor, resetColor string
if isTerm { if isTerm {
statusColor = colorForStatus(statusCode) statusColor = colorForStatus(statusCode)
methodColor = colorForMethod(method) methodColor = colorForMethod(method)
resetColor = reset
} }
comment := c.Errors.ByType(ErrorTypePrivate).String() comment := c.Errors.ByType(ErrorTypePrivate).String()
@ -102,12 +103,12 @@ func LoggerWithWriter(out io.Writer, notlogged ...string) HandlerFunc {
path = path + "?" + raw path = path + "?" + raw
} }
fmt.Fprintf(out, "[GIN] %v |%s %3d %s| %13v | %15s |%s %s %-7s %s\n%s", fmt.Fprintf(out, "[GIN] %v |%s %3d %s| %13v | %15s |%s %-7s %s %s\n%s",
end.Format("2006/01/02 - 15:04:05"), end.Format("2006/01/02 - 15:04:05"),
statusColor, statusCode, reset, statusColor, statusCode, resetColor,
latency, latency,
clientIP, clientIP,
methodColor, method, reset, methodColor, method, resetColor,
path, path,
comment, comment,
) )

View File

@ -17,7 +17,7 @@ package gin
// 4. Eliminate .. elements that begin a rooted path: // 4. Eliminate .. elements that begin a rooted path:
// that is, replace "/.." by "/" at the beginning of a path. // that is, replace "/.." by "/" at the beginning of a path.
// //
// If the result of this process is an empty string, "/" is returned // If the result of this process is an empty string, "/" is returned.
func cleanPath(p string) string { func cleanPath(p string) string {
// Turn empty string into "/" // Turn empty string into "/"
if p == "" { if p == "" {
@ -109,7 +109,7 @@ func cleanPath(p string) string {
return string(buf[:w]) return string(buf[:w])
} }
// internal helper to lazily create a buffer if necessary // internal helper to lazily create a buffer if necessary.
func bufApp(buf *[]byte, s string, w int, c byte) { func bufApp(buf *[]byte, s string, w int, c byte) {
if *buf == nil { if *buf == nil {
if s[w] == c { if s[w] == c {

View File

@ -46,7 +46,7 @@ func RecoveryWithWriter(out io.Writer) HandlerFunc {
} }
} }
// stack returns a nicely formated stack frame, skipping skip frames // stack returns a nicely formated stack frame, skipping skip frames.
func stack(skip int) []byte { func stack(skip int) []byte {
buf := new(bytes.Buffer) // the returned data buf := new(bytes.Buffer) // the returned data
// As we loop, we open files and read them. These variables record the currently // As we loop, we open files and read them. These variables record the currently

View File

@ -11,7 +11,7 @@ type Data struct {
Data []byte Data []byte
} }
// Render (Data) writes data with custom ContentType // Render (Data) writes data with custom ContentType.
func (r Data) Render(w http.ResponseWriter) (err error) { func (r Data) Render(w http.ResponseWriter) (err error) {
r.WriteContentType(w) r.WriteContentType(w)
_, err = w.Write(r.Data) _, err = w.Write(r.Data)

View File

@ -95,7 +95,7 @@ func (w *responseWriter) Written() bool {
return w.size != noWritten return w.size != noWritten
} }
// Hijack implements the http.Hijacker interface // Hijack implements the http.Hijacker interface.
func (w *responseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) { func (w *responseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
if w.size < 0 { if w.size < 0 {
w.size = 0 w.size = 0
@ -103,12 +103,12 @@ func (w *responseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
return w.ResponseWriter.(http.Hijacker).Hijack() return w.ResponseWriter.(http.Hijacker).Hijack()
} }
// CloseNotify implements the http.CloseNotify interface // CloseNotify implements the http.CloseNotify interface.
func (w *responseWriter) CloseNotify() <-chan bool { func (w *responseWriter) CloseNotify() <-chan bool {
return w.ResponseWriter.(http.CloseNotifier).CloseNotify() return w.ResponseWriter.(http.CloseNotifier).CloseNotify()
} }
// Flush implements the http.Flush interface // Flush implements the http.Flush interface.
func (w *responseWriter) Flush() { func (w *responseWriter) Flush() {
w.ResponseWriter.(http.Flusher).Flush() w.ResponseWriter.(http.Flusher).Flush()
} }

View File

@ -35,7 +35,7 @@ type IRoutes interface {
} }
// RouterGroup is used internally to configure router, a RouterGroup is associated with a prefix // RouterGroup is used internally to configure router, a RouterGroup is associated with a prefix
// and an array of handlers (middleware) // and an array of handlers (middleware).
type RouterGroup struct { type RouterGroup struct {
Handlers HandlersChain Handlers HandlersChain
basePath string basePath string
@ -89,43 +89,43 @@ func (group *RouterGroup) Handle(httpMethod, relativePath string, handlers ...Ha
return group.handle(httpMethod, relativePath, handlers) return group.handle(httpMethod, relativePath, handlers)
} }
// POST is a shortcut for router.Handle("POST", path, handle) // POST is a shortcut for router.Handle("POST", path, handle).
func (group *RouterGroup) POST(relativePath string, handlers ...HandlerFunc) IRoutes { func (group *RouterGroup) POST(relativePath string, handlers ...HandlerFunc) IRoutes {
return group.handle("POST", relativePath, handlers) return group.handle("POST", relativePath, handlers)
} }
// GET is a shortcut for router.Handle("GET", path, handle) // GET is a shortcut for router.Handle("GET", path, handle).
func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes { func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes {
return group.handle("GET", relativePath, handlers) return group.handle("GET", relativePath, handlers)
} }
// DELETE is a shortcut for router.Handle("DELETE", path, handle) // DELETE is a shortcut for router.Handle("DELETE", path, handle).
func (group *RouterGroup) DELETE(relativePath string, handlers ...HandlerFunc) IRoutes { func (group *RouterGroup) DELETE(relativePath string, handlers ...HandlerFunc) IRoutes {
return group.handle("DELETE", relativePath, handlers) return group.handle("DELETE", relativePath, handlers)
} }
// PATCH is a shortcut for router.Handle("PATCH", path, handle) // PATCH is a shortcut for router.Handle("PATCH", path, handle).
func (group *RouterGroup) PATCH(relativePath string, handlers ...HandlerFunc) IRoutes { func (group *RouterGroup) PATCH(relativePath string, handlers ...HandlerFunc) IRoutes {
return group.handle("PATCH", relativePath, handlers) return group.handle("PATCH", relativePath, handlers)
} }
// PUT is a shortcut for router.Handle("PUT", path, handle) // PUT is a shortcut for router.Handle("PUT", path, handle).
func (group *RouterGroup) PUT(relativePath string, handlers ...HandlerFunc) IRoutes { func (group *RouterGroup) PUT(relativePath string, handlers ...HandlerFunc) IRoutes {
return group.handle("PUT", relativePath, handlers) return group.handle("PUT", relativePath, handlers)
} }
// OPTIONS is a shortcut for router.Handle("OPTIONS", path, handle) // OPTIONS is a shortcut for router.Handle("OPTIONS", path, handle).
func (group *RouterGroup) OPTIONS(relativePath string, handlers ...HandlerFunc) IRoutes { func (group *RouterGroup) OPTIONS(relativePath string, handlers ...HandlerFunc) IRoutes {
return group.handle("OPTIONS", relativePath, handlers) return group.handle("OPTIONS", relativePath, handlers)
} }
// HEAD is a shortcut for router.Handle("HEAD", path, handle) // HEAD is a shortcut for router.Handle("HEAD", path, handle).
func (group *RouterGroup) HEAD(relativePath string, handlers ...HandlerFunc) IRoutes { func (group *RouterGroup) HEAD(relativePath string, handlers ...HandlerFunc) IRoutes {
return group.handle("HEAD", relativePath, handlers) return group.handle("HEAD", relativePath, handlers)
} }
// Any registers a route that matches all the HTTP methods. // Any registers a route that matches all the HTTP methods.
// GET, POST, PUT, PATCH, HEAD, OPTIONS, DELETE, CONNECT, TRACE // GET, POST, PUT, PATCH, HEAD, OPTIONS, DELETE, CONNECT, TRACE.
func (group *RouterGroup) Any(relativePath string, handlers ...HandlerFunc) IRoutes { func (group *RouterGroup) Any(relativePath string, handlers ...HandlerFunc) IRoutes {
group.handle("GET", relativePath, handlers) group.handle("GET", relativePath, handlers)
group.handle("POST", relativePath, handlers) group.handle("POST", relativePath, handlers)

View File

@ -375,25 +375,25 @@ func TestRouterNotFound(t *testing.T) {
router.GET("/", func(c *Context) {}) router.GET("/", func(c *Context) {})
testRoutes := []struct { testRoutes := []struct {
route string route string
code int code int
header string location string
}{ }{
{"/path/", 301, "map[Location:[/path]]"}, // TSR -/ {"/path/", 301, "/path"}, // TSR -/
{"/dir", 301, "map[Location:[/dir/]]"}, // TSR +/ {"/dir", 301, "/dir/"}, // TSR +/
{"", 301, "map[Location:[/]]"}, // TSR +/ {"", 301, "/"}, // TSR +/
{"/PATH", 301, "map[Location:[/path]]"}, // Fixed Case {"/PATH", 301, "/path"}, // Fixed Case
{"/DIR/", 301, "map[Location:[/dir/]]"}, // Fixed Case {"/DIR/", 301, "/dir/"}, // Fixed Case
{"/PATH/", 301, "map[Location:[/path]]"}, // Fixed Case -/ {"/PATH/", 301, "/path"}, // Fixed Case -/
{"/DIR", 301, "map[Location:[/dir/]]"}, // Fixed Case +/ {"/DIR", 301, "/dir/"}, // Fixed Case +/
{"/../path", 301, "map[Location:[/path]]"}, // CleanPath {"/../path", 301, "/path"}, // CleanPath
{"/nope", 404, ""}, // NotFound {"/nope", 404, ""}, // NotFound
} }
for _, tr := range testRoutes { for _, tr := range testRoutes {
w := performRequest(router, "GET", tr.route) w := performRequest(router, "GET", tr.route)
assert.Equal(t, w.Code, tr.code) assert.Equal(t, w.Code, tr.code)
if w.Code != 404 { if w.Code != 404 {
assert.Equal(t, fmt.Sprint(w.Header()), tr.header) assert.Equal(t, fmt.Sprint(w.Header().Get("Location")), tr.location)
} }
} }

View File

@ -96,7 +96,7 @@ type node struct {
priority uint32 priority uint32
} }
// increments priority of the given child and reorders if necessary // increments priority of the given child and reorders if necessary.
func (n *node) incrementChildPrio(pos int) int { func (n *node) incrementChildPrio(pos int) int {
n.children[pos].priority++ n.children[pos].priority++
prio := n.children[pos].priority prio := n.children[pos].priority

View File

@ -47,7 +47,7 @@ func WrapH(h http.Handler) HandlerFunc {
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.
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: "",