Comments + IRoutes + IRouter + unexported AbortIndex

This commit is contained in:
Manu Mtz-Almeida 2015-07-02 20:24:54 +02:00
parent 13f57702d4
commit 8f3047814e
8 changed files with 112 additions and 84 deletions

14
auth.go
View File

@ -10,9 +10,7 @@ import (
"strconv" "strconv"
) )
const ( const AuthUserKey = "user"
AuthUserKey = "user"
)
type ( type (
Accounts map[string]string Accounts map[string]string
@ -35,8 +33,9 @@ func (a authPairs) searchCredential(authValue string) (string, bool) {
return "", false return "", false
} }
// Implements a basic Basic HTTP Authorization. It takes as arguments a map[string]string where // BasicAuthForRealm returns a Basic HTTP Authorization middleware. It takes as arguments a map[string]string where
// the key is the user name and the value is the password, as well as the name of the Realm // the key is the user name and the value is the password, as well as the name of the Realm.
// If the realm is empty, "Authorization Required" will be used by default.
// (see http://tools.ietf.org/html/rfc2617#section-1.2) // (see http://tools.ietf.org/html/rfc2617#section-1.2)
func BasicAuthForRealm(accounts Accounts, realm string) HandlerFunc { func BasicAuthForRealm(accounts Accounts, realm string) HandlerFunc {
if realm == "" { if realm == "" {
@ -59,7 +58,7 @@ func BasicAuthForRealm(accounts Accounts, realm string) HandlerFunc {
} }
} }
// Implements a basic Basic HTTP Authorization. It takes as argument a map[string]string where // BasicAuth returns a Basic HTTP Authorization middleware. It takes as argument a map[string]string where
// the key is the user name and the value is the password. // the key is the user name and the value is the password.
func BasicAuth(accounts Accounts) HandlerFunc { func BasicAuth(accounts Accounts) HandlerFunc {
return BasicAuthForRealm(accounts, "") return BasicAuthForRealm(accounts, "")
@ -91,8 +90,7 @@ func authorizationHeader(user, password string) string {
func secureCompare(given, actual string) bool { 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
} else { }
/* 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

@ -18,6 +18,7 @@ import (
"golang.org/x/net/context" "golang.org/x/net/context"
) )
// Content-Type MIME of the most common data formats
const ( const (
MIMEJSON = binding.MIMEJSON MIMEJSON = binding.MIMEJSON
MIMEHTML = binding.MIMEHTML MIMEHTML = binding.MIMEHTML
@ -28,7 +29,7 @@ const (
MIMEMultipartPOSTForm = binding.MIMEMultipartPOSTForm MIMEMultipartPOSTForm = binding.MIMEMultipartPOSTForm
) )
const AbortIndex int8 = math.MaxInt8 / 2 const abortIndex int8 = math.MaxInt8 / 2
// 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.
@ -63,16 +64,18 @@ func (c *Context) reset() {
c.Accepted = nil c.Accepted = nil
} }
// 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.
func (c *Context) Copy() *Context { 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.index = AbortIndex cp.index = abortIndex
cp.handlers = nil cp.handlers = nil
return &cp return &cp
} }
// Returns the main handle's name. For example if the handler is "handleGetUsers()", this // HandlerName returns the main handle's name. For example if the handler is "handleGetUsers()", this
// function will return "main.handleGetUsers" // 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())
@ -93,27 +96,27 @@ func (c *Context) Next() {
} }
} }
// Returns if the currect context was aborted. // IsAborted returns true if the currect context was aborted.
func (c *Context) IsAborted() bool { func (c *Context) IsAborted() bool {
return c.index >= AbortIndex return c.index >= abortIndex
} }
// Stops the system to continue calling the pending handlers in the chain. // Abort stops the system to continue calling the pending handlers in the chain.
// Let's say you have an authorization middleware that validates if the request is authorized // Let's say you have an authorization middleware that validates if the request is authorized
// if the authorization fails (the password does not match). This method (Abort()) should be called // if the authorization fails (the password does not match). This method (Abort()) should be called
// in order to stop the execution of the actual handler. // in order to stop the execution of the actual handler.
func (c *Context) Abort() { func (c *Context) Abort() {
c.index = AbortIndex c.index = abortIndex
} }
// It calls Abort() and writes the headers with the specified status code. // AbortWithStatus calls `Abort()` and writes the headers with the specified status code.
// 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.Writer.WriteHeader(code) c.Writer.WriteHeader(code)
c.Abort() c.Abort()
} }
// It calls AbortWithStatus() and Error() internally. This method stops the chain, writes the status code and // AbortWithError calls `AbortWithStatus()` and `Error()` internally. This method stops the chain, writes the status code and
// pushes the specified error to `c.Errors`. // pushes the specified error to `c.Errors`.
// See Context.Error() for more details. // See Context.Error() for more details.
func (c *Context) AbortWithError(code int, err error) *Error { func (c *Context) AbortWithError(code int, err error) *Error {
@ -371,7 +374,7 @@ func (c *Context) Redirect(code int, location string) {
}) })
} }
// Writes some data into the body stream and updates the HTTP code. // Data writes some data into the body stream and updates the HTTP code.
func (c *Context) Data(code int, contentType string, data []byte) { func (c *Context) Data(code int, contentType string, data []byte) {
c.Render(code, render.Data{ c.Render(code, render.Data{
ContentType: contentType, ContentType: contentType,
@ -379,11 +382,12 @@ func (c *Context) Data(code int, contentType string, data []byte) {
}) })
} }
// Writes the specified file into the body stream in a efficient way. // File writes the specified file into the body stream in a efficient way.
func (c *Context) File(filepath string) { func (c *Context) File(filepath string) {
http.ServeFile(c.Writer, c.Request, filepath) http.ServeFile(c.Writer, c.Request, filepath)
} }
// SSEvent writes a Server-Sent Event into the body stream.
func (c *Context) SSEvent(name string, message interface{}) { func (c *Context) SSEvent(name string, message interface{}) {
c.Render(-1, sse.Event{ c.Render(-1, sse.Event{
Event: name, Event: name,

View File

@ -129,7 +129,7 @@ func TestContextCopy(t *testing.T) {
assert.Nil(t, cp.writermem.ResponseWriter) assert.Nil(t, cp.writermem.ResponseWriter)
assert.Equal(t, &cp.writermem, cp.Writer.(*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.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)
@ -418,7 +418,7 @@ func TestContextIsAborted(t *testing.T) {
c.Next() c.Next()
assert.True(t, c.IsAborted()) assert.True(t, c.IsAborted())
c.Next() c.index++
assert.True(t, c.IsAborted()) assert.True(t, c.IsAborted())
} }
@ -430,7 +430,7 @@ func TestContextAbortWithStatus(t *testing.T) {
c.AbortWithStatus(401) c.AbortWithStatus(401)
c.Writer.WriteHeaderNow() 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)
assert.Equal(t, w.Code, 401) assert.Equal(t, w.Code, 401)
assert.True(t, c.IsAborted()) assert.True(t, c.IsAborted())
@ -482,7 +482,7 @@ func TestContextAbortWithError(t *testing.T) {
c.Writer.WriteHeaderNow() 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)
assert.True(t, c.IsAborted()) assert.True(t, c.IsAborted())
} }

View File

@ -9,6 +9,9 @@ import "log"
func init() { func init() {
log.SetFlags(0) log.SetFlags(0)
} }
// IsDebugging returns true if the framework is running in debug mode.
// Use SetMode(gin.Release) to switch to disable the debug mode.
func IsDebugging() bool { func IsDebugging() bool {
return ginMode == debugCode return ginMode == debugCode
} }
@ -27,7 +30,7 @@ func debugPrint(format string, values ...interface{}) {
} }
} }
func debugPrintWARNING_New() { func debugPrintWARNINGNew() {
debugPrint(`[WARNING] Running in "debug" mode. Switch to "release" mode in production. debugPrint(`[WARNING] Running in "debug" mode. Switch to "release" mode in production.
- using env: export GIN_MODE=release - using env: export GIN_MODE=release
- using code: gin.SetMode(gin.ReleaseMode) - using code: gin.SetMode(gin.ReleaseMode)
@ -35,7 +38,7 @@ func debugPrintWARNING_New() {
`) `)
} }
func debugPrintWARNING_SetHTMLTemplate() { func debugPrintWARNINGSetHTMLTemplate() {
debugPrint(`[WARNING] Since SetHTMLTemplate() is NOT thread-safe. It should only be called debugPrint(`[WARNING] Since SetHTMLTemplate() is NOT thread-safe. It should only be called
at initialization. ie. before any route is registered or the router is listening in a socket: at initialization. ie. before any route is registered or the router is listening in a socket:

33
gin.go
View File

@ -14,6 +14,7 @@ import (
"github.com/gin-gonic/gin/render" "github.com/gin-gonic/gin/render"
) )
// 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")
@ -22,6 +23,7 @@ var default405Body = []byte("405 method not allowed")
type HandlerFunc func(*Context) type HandlerFunc func(*Context)
type HandlersChain []HandlerFunc type HandlersChain []HandlerFunc
// Last returns the last handler in the chain. ie. the last handler is the main own.
func (c HandlersChain) Last() HandlerFunc { func (c HandlersChain) Last() HandlerFunc {
length := len(c) length := len(c)
if length > 0 { if length > 0 {
@ -38,7 +40,8 @@ type (
Handler string Handler string
} }
// Represents the web framework, it wraps the blazing fast httprouter multiplexer and a list of global middlewares. // Engine is the framework's instance, it contains the muxer, middlewares and configuration settings.
// Create an instance of Engine, by using New() or Default()
Engine struct { Engine struct {
RouterGroup RouterGroup
HTMLRender render.HTMLRender HTMLRender render.HTMLRender
@ -78,12 +81,16 @@ type (
} }
) )
var _ RoutesInterface = &Engine{} var _ IRouter = &Engine{}
// Returns a new blank Engine instance without any middleware attached. // New returns a new blank Engine instance without any middleware attached.
// The most basic configuration // By default the configuration is:
// - RedirectTrailingSlash: true
// - RedirectFixedPath: false
// - HandleMethodNotAllowed: false
// - ForwardedByClientIP: true
func New() *Engine { func New() *Engine {
debugPrintWARNING_New() debugPrintWARNINGNew()
engine := &Engine{ engine := &Engine{
RouterGroup: RouterGroup{ RouterGroup: RouterGroup{
Handlers: nil, Handlers: nil,
@ -103,7 +110,7 @@ func New() *Engine {
return engine return engine
} }
// Returns a Engine instance with the Logger and Recovery already attached. // Default returns an Engine instance with the Logger and Recovery middleware already attached.
func Default() *Engine { func Default() *Engine {
engine := New() engine := New()
engine.Use(Recovery(), Logger()) engine.Use(Recovery(), Logger())
@ -134,7 +141,7 @@ func (engine *Engine) LoadHTMLFiles(files ...string) {
func (engine *Engine) SetHTMLTemplate(templ *template.Template) { func (engine *Engine) SetHTMLTemplate(templ *template.Template) {
if len(engine.trees) > 0 { if len(engine.trees) > 0 {
debugPrintWARNING_SetHTMLTemplate() debugPrintWARNINGSetHTMLTemplate()
} }
engine.HTMLRender = render.HTMLProduction{Template: templ} engine.HTMLRender = render.HTMLProduction{Template: templ}
} }
@ -154,7 +161,7 @@ func (engine *Engine) NoMethod(handlers ...HandlerFunc) {
// Attachs a global middleware to the router. ie. the middlewares attached though Use() will be // 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 (engine *Engine) Use(middlewares ...HandlerFunc) routesInterface { func (engine *Engine) Use(middlewares ...HandlerFunc) IRoutes {
engine.RouterGroup.Use(middlewares...) engine.RouterGroup.Use(middlewares...)
engine.rebuild404Handlers() engine.rebuild404Handlers()
engine.rebuild405Handlers() engine.rebuild405Handlers()
@ -193,6 +200,8 @@ func (engine *Engine) addRoute(method, path string, handlers HandlersChain) {
root.addRoute(path, handlers) root.addRoute(path, handlers)
} }
// Routes returns a slice of registered routes, including some useful information, such as:
// the http method, path and the handler name.
func (engine *Engine) Routes() (routes RoutesInfo) { func (engine *Engine) Routes() (routes RoutesInfo) {
for _, tree := range engine.trees { for _, tree := range engine.trees {
routes = iterate("", tree.method, routes, tree.root) routes = iterate("", tree.method, routes, tree.root)
@ -215,7 +224,7 @@ func iterate(path, method string, routes RoutesInfo, root *node) RoutesInfo {
return routes return routes
} }
// The router is attached to a http.Server and starts listening and serving HTTP requests. // Run attaches the router 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 (engine *Engine) Run(addr string) (err error) { func (engine *Engine) Run(addr string) (err error) {
@ -226,7 +235,7 @@ func (engine *Engine) Run(addr string) (err error) {
return return
} }
// The router is attached to a http.Server and starts listening and serving HTTPS 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 undefinitelly unless an error happens. // Note: this method will block the calling goroutine undefinitelly unless an error happens.
func (engine *Engine) RunTLS(addr string, certFile string, keyFile string) (err error) { func (engine *Engine) RunTLS(addr string, certFile string, keyFile string) (err error) {
@ -237,8 +246,8 @@ func (engine *Engine) RunTLS(addr string, certFile string, keyFile string) (err
return return
} }
// The router is attached to a http.Server and starts listening and serving HTTP requests // RunUnix attaches the router 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 (engine *Engine) RunUnix(file string) (err error) { func (engine *Engine) RunUnix(file string) (err error) {
debugPrint("Listening and serving HTTP on unix:/%s", file) debugPrint("Listening and serving HTTP on unix:/%s", file)

View File

@ -23,10 +23,20 @@ type (
http.Flusher http.Flusher
http.CloseNotifier http.CloseNotifier
// Returns the HTTP response status code of the current request.
Status() int Status() int
// Returns the number of bytes already written into the response http body.
// See Written()
Size() int Size() int
// Writes the string into the response body.
WriteString(string) (int, error) WriteString(string) (int, error)
// Returns true if the response body was already written.
Written() bool Written() bool
// Forces to write the http header (status code + headers).
WriteHeaderNow() WriteHeaderNow()
} }

View File

@ -12,30 +12,30 @@ import (
) )
type ( type (
RoutesInterface interface { IRouter interface {
routesInterface IRoutes
Group(string, ...HandlerFunc) *RouterGroup Group(string, ...HandlerFunc) *RouterGroup
} }
routesInterface interface { IRoutes interface {
Use(...HandlerFunc) routesInterface Use(...HandlerFunc) IRoutes
Handle(string, string, ...HandlerFunc) routesInterface Handle(string, string, ...HandlerFunc) IRoutes
Any(string, ...HandlerFunc) routesInterface Any(string, ...HandlerFunc) IRoutes
GET(string, ...HandlerFunc) routesInterface GET(string, ...HandlerFunc) IRoutes
POST(string, ...HandlerFunc) routesInterface POST(string, ...HandlerFunc) IRoutes
DELETE(string, ...HandlerFunc) routesInterface DELETE(string, ...HandlerFunc) IRoutes
PATCH(string, ...HandlerFunc) routesInterface PATCH(string, ...HandlerFunc) IRoutes
PUT(string, ...HandlerFunc) routesInterface PUT(string, ...HandlerFunc) IRoutes
OPTIONS(string, ...HandlerFunc) routesInterface OPTIONS(string, ...HandlerFunc) IRoutes
HEAD(string, ...HandlerFunc) routesInterface HEAD(string, ...HandlerFunc) IRoutes
StaticFile(string, string) routesInterface StaticFile(string, string) IRoutes
Static(string, string) routesInterface Static(string, string) IRoutes
StaticFS(string, http.FileSystem) routesInterface StaticFS(string, http.FileSystem) IRoutes
} }
// 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 (middlewares) // and an array of handlers (middlewares)
RouterGroup struct { RouterGroup struct {
Handlers HandlersChain Handlers HandlersChain
@ -45,15 +45,15 @@ type (
} }
) )
var _ RoutesInterface = &RouterGroup{} var _ IRouter = &RouterGroup{}
// Adds middlewares to the group, see example code in github. // Use adds middlewares to the group, see example code in github.
func (group *RouterGroup) Use(middlewares ...HandlerFunc) routesInterface { func (group *RouterGroup) Use(middlewares ...HandlerFunc) IRoutes {
group.Handlers = append(group.Handlers, middlewares...) group.Handlers = append(group.Handlers, middlewares...)
return group.returnObj() return group.returnObj()
} }
// 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 *RouterGroup) Group(relativePath string, handlers ...HandlerFunc) *RouterGroup { func (group *RouterGroup) Group(relativePath string, handlers ...HandlerFunc) *RouterGroup {
return &RouterGroup{ return &RouterGroup{
@ -63,6 +63,13 @@ func (group *RouterGroup) Group(relativePath string, handlers ...HandlerFunc) *R
} }
} }
func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoutes {
absolutePath := group.calculateAbsolutePath(relativePath)
handlers = group.combineHandlers(handlers)
group.engine.addRoute(httpMethod, absolutePath, handlers)
return group.returnObj()
}
// Handle registers a new request handle and middlewares with the given path and method. // Handle registers a new request handle and middlewares with the given path and method.
// The last handler should be the real handler, the other ones should be middlewares that can and should be shared among different routes. // The last handler should be the real handler, the other ones should be middlewares that can and should be shared among different routes.
// See the example code in github. // See the example code in github.
@ -73,14 +80,7 @@ func (group *RouterGroup) Group(relativePath string, handlers ...HandlerFunc) *R
// This function is intended for bulk loading and to allow the usage of less // This function is intended for bulk loading and to allow the usage of less
// frequently used, non-standardized or custom methods (e.g. for internal // frequently used, non-standardized or custom methods (e.g. for internal
// communication with a proxy). // communication with a proxy).
func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) routesInterface { func (group *RouterGroup) Handle(httpMethod, relativePath string, handlers ...HandlerFunc) IRoutes {
absolutePath := group.calculateAbsolutePath(relativePath)
handlers = group.combineHandlers(handlers)
group.engine.addRoute(httpMethod, absolutePath, handlers)
return group.returnObj()
}
func (group *RouterGroup) Handle(httpMethod, relativePath string, handlers ...HandlerFunc) routesInterface {
if matches, err := regexp.MatchString("^[A-Z]+$", httpMethod); !matches || err != nil { if matches, err := regexp.MatchString("^[A-Z]+$", httpMethod); !matches || err != nil {
panic("http method " + httpMethod + " is not valid") panic("http method " + httpMethod + " is not valid")
} }
@ -88,42 +88,43 @@ func (group *RouterGroup) Handle(httpMethod, relativePath string, handlers ...Ha
} }
// 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) routesInterface { 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) routesInterface { 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) routesInterface { 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) routesInterface { 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) routesInterface { 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) routesInterface { 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) routesInterface { func (group *RouterGroup) HEAD(relativePath string, handlers ...HandlerFunc) IRoutes {
return group.handle("HEAD", relativePath, handlers) return group.handle("HEAD", relativePath, handlers)
} }
func (group *RouterGroup) Any(relativePath string, handlers ...HandlerFunc) routesInterface { // 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 {
group.handle("GET", relativePath, handlers) group.handle("GET", relativePath, handlers)
group.handle("POST", relativePath, handlers) group.handle("POST", relativePath, handlers)
group.handle("PUT", relativePath, handlers) group.handle("PUT", relativePath, handlers)
@ -136,7 +137,9 @@ func (group *RouterGroup) Any(relativePath string, handlers ...HandlerFunc) rout
return group.returnObj() return group.returnObj()
} }
func (group *RouterGroup) StaticFile(relativePath, filepath string) routesInterface { // StaticFile registers a single route in order to server a single file of the local filesystem.
// router.StaticFile("favicon.ico", "./resources/favicon.ico")
func (group *RouterGroup) StaticFile(relativePath, filepath string) IRoutes {
if strings.Contains(relativePath, ":") || strings.Contains(relativePath, "*") { if strings.Contains(relativePath, ":") || strings.Contains(relativePath, "*") {
panic("URL parameters can not be used when serving a static file") panic("URL parameters can not be used when serving a static file")
} }
@ -154,11 +157,13 @@ func (group *RouterGroup) StaticFile(relativePath, filepath string) routesInterf
// To use the operating system's file system implementation, // To use the operating system's file system implementation,
// use : // use :
// router.Static("/static", "/var/www") // router.Static("/static", "/var/www")
func (group *RouterGroup) Static(relativePath, root string) routesInterface { func (group *RouterGroup) Static(relativePath, root string) IRoutes {
return group.StaticFS(relativePath, Dir(root, false)) return group.StaticFS(relativePath, Dir(root, false))
} }
func (group *RouterGroup) StaticFS(relativePath string, fs http.FileSystem) routesInterface { // StaticFS works just like `Static()` but a custom `http.FileSystem` can be used instead.
// Gin by default user: gin.Dir()
func (group *RouterGroup) StaticFS(relativePath string, fs http.FileSystem) IRoutes {
if strings.Contains(relativePath, ":") || strings.Contains(relativePath, "*") { if strings.Contains(relativePath, ":") || strings.Contains(relativePath, "*") {
panic("URL parameters can not be used when serving a static folder") panic("URL parameters can not be used when serving a static folder")
} }
@ -185,7 +190,7 @@ func (group *RouterGroup) createStaticHandler(relativePath string, fs http.FileS
func (group *RouterGroup) combineHandlers(handlers HandlersChain) HandlersChain { func (group *RouterGroup) combineHandlers(handlers HandlersChain) HandlersChain {
finalSize := len(group.Handlers) + len(handlers) finalSize := len(group.Handlers) + len(handlers)
if finalSize >= int(AbortIndex) { if finalSize >= int(abortIndex) {
panic("too many handlers") panic("too many handlers")
} }
mergedHandlers := make(HandlersChain, finalSize) mergedHandlers := make(HandlersChain, finalSize)
@ -198,10 +203,9 @@ func (group *RouterGroup) calculateAbsolutePath(relativePath string) string {
return joinPaths(group.BasePath, relativePath) return joinPaths(group.BasePath, relativePath)
} }
func (group *RouterGroup) returnObj() routesInterface { func (group *RouterGroup) returnObj() IRoutes {
if group.root { if group.root {
return group.engine return group.engine
} else { }
return group return group
} }
}

View File

@ -157,7 +157,7 @@ func TestRouterGroupPipeline(t *testing.T) {
testRoutesInterface(t, v1) testRoutesInterface(t, v1)
} }
func testRoutesInterface(t *testing.T, r RoutesInterface) { func testRoutesInterface(t *testing.T, r IRoutes) {
handler := func(c *Context) {} handler := func(c *Context) {}
assert.Equal(t, r, r.Use(handler)) assert.Equal(t, r, r.Use(handler))