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

16
auth.go
View File

@ -10,9 +10,7 @@ import (
"strconv"
)
const (
AuthUserKey = "user"
)
const AuthUserKey = "user"
type (
Accounts map[string]string
@ -35,8 +33,9 @@ func (a authPairs) searchCredential(authValue string) (string, bool) {
return "", false
}
// Implements a basic Basic HTTP Authorization. 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
// 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.
// If the realm is empty, "Authorization Required" will be used by default.
// (see http://tools.ietf.org/html/rfc2617#section-1.2)
func BasicAuthForRealm(accounts Accounts, realm string) HandlerFunc {
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.
func BasicAuth(accounts Accounts) HandlerFunc {
return BasicAuthForRealm(accounts, "")
@ -91,8 +90,7 @@ func authorizationHeader(user, password string) string {
func secureCompare(given, actual string) bool {
if subtle.ConstantTimeEq(int32(len(given)), int32(len(actual))) == 1 {
return subtle.ConstantTimeCompare([]byte(given), []byte(actual)) == 1
} else {
/* Securely compare actual to itself to keep constant time, but always return false */
return subtle.ConstantTimeCompare([]byte(actual), []byte(actual)) == 1 && false
}
/* Securely compare actual to itself to keep constant time, but always return false */
return subtle.ConstantTimeCompare([]byte(actual), []byte(actual)) == 1 && false
}

View File

@ -18,6 +18,7 @@ import (
"golang.org/x/net/context"
)
// Content-Type MIME of the most common data formats
const (
MIMEJSON = binding.MIMEJSON
MIMEHTML = binding.MIMEHTML
@ -28,7 +29,7 @@ const (
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,
// 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
}
// 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 {
var cp Context = *c
cp.writermem.ResponseWriter = nil
cp.Writer = &cp.writermem
cp.index = AbortIndex
cp.index = abortIndex
cp.handlers = nil
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"
func (c *Context) HandlerName() string {
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 {
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
// 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.
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).
func (c *Context) AbortWithStatus(code int) {
c.Writer.WriteHeader(code)
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`.
// See Context.Error() for more details.
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) {
c.Render(code, render.Data{
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) {
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{}) {
c.Render(-1, sse.Event{
Event: name,

View File

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

View File

@ -9,6 +9,9 @@ import "log"
func init() {
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 {
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.
- using env: export GIN_MODE=release
- 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
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"
)
// Framework's version
const Version = "v1.0rc2"
var default404Body = []byte("404 page not found")
@ -22,6 +23,7 @@ var default405Body = []byte("405 method not allowed")
type HandlerFunc func(*Context)
type HandlersChain []HandlerFunc
// Last returns the last handler in the chain. ie. the last handler is the main own.
func (c HandlersChain) Last() HandlerFunc {
length := len(c)
if length > 0 {
@ -38,7 +40,8 @@ type (
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 {
RouterGroup
HTMLRender render.HTMLRender
@ -78,12 +81,16 @@ type (
}
)
var _ RoutesInterface = &Engine{}
var _ IRouter = &Engine{}
// Returns a new blank Engine instance without any middleware attached.
// The most basic configuration
// New returns a new blank Engine instance without any middleware attached.
// By default the configuration is:
// - RedirectTrailingSlash: true
// - RedirectFixedPath: false
// - HandleMethodNotAllowed: false
// - ForwardedByClientIP: true
func New() *Engine {
debugPrintWARNING_New()
debugPrintWARNINGNew()
engine := &Engine{
RouterGroup: RouterGroup{
Handlers: nil,
@ -103,7 +110,7 @@ func New() *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 {
engine := New()
engine.Use(Recovery(), Logger())
@ -134,7 +141,7 @@ func (engine *Engine) LoadHTMLFiles(files ...string) {
func (engine *Engine) SetHTMLTemplate(templ *template.Template) {
if len(engine.trees) > 0 {
debugPrintWARNING_SetHTMLTemplate()
debugPrintWARNINGSetHTMLTemplate()
}
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
// 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.
func (engine *Engine) Use(middlewares ...HandlerFunc) routesInterface {
func (engine *Engine) Use(middlewares ...HandlerFunc) IRoutes {
engine.RouterGroup.Use(middlewares...)
engine.rebuild404Handlers()
engine.rebuild405Handlers()
@ -193,6 +200,8 @@ func (engine *Engine) addRoute(method, path string, handlers HandlersChain) {
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) {
for _, tree := range engine.trees {
routes = iterate("", tree.method, routes, tree.root)
@ -215,7 +224,7 @@ func iterate(path, method string, routes RoutesInfo, root *node) RoutesInfo {
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)
// Note: this method will block the calling goroutine undefinitelly unless an error happens.
func (engine *Engine) Run(addr string) (err error) {
@ -226,7 +235,7 @@ func (engine *Engine) Run(addr string) (err error) {
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)
// 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) {
@ -237,8 +246,8 @@ func (engine *Engine) RunTLS(addr string, certFile string, keyFile string) (err
return
}
// The router is attached to a http.Server and starts listening and serving HTTP requests
// through the specified unix socket (ie. a file)
// RunUnix attaches the router to a http.Server and starts listening and serving HTTP requests
// through the specified unix socket (ie. a file).
// Note: this method will block the calling goroutine undefinitelly unless an error happens.
func (engine *Engine) RunUnix(file string) (err error) {
debugPrint("Listening and serving HTTP on unix:/%s", file)

View File

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

View File

@ -12,30 +12,30 @@ import (
)
type (
RoutesInterface interface {
routesInterface
IRouter interface {
IRoutes
Group(string, ...HandlerFunc) *RouterGroup
}
routesInterface interface {
Use(...HandlerFunc) routesInterface
IRoutes interface {
Use(...HandlerFunc) IRoutes
Handle(string, string, ...HandlerFunc) routesInterface
Any(string, ...HandlerFunc) routesInterface
GET(string, ...HandlerFunc) routesInterface
POST(string, ...HandlerFunc) routesInterface
DELETE(string, ...HandlerFunc) routesInterface
PATCH(string, ...HandlerFunc) routesInterface
PUT(string, ...HandlerFunc) routesInterface
OPTIONS(string, ...HandlerFunc) routesInterface
HEAD(string, ...HandlerFunc) routesInterface
Handle(string, string, ...HandlerFunc) IRoutes
Any(string, ...HandlerFunc) IRoutes
GET(string, ...HandlerFunc) IRoutes
POST(string, ...HandlerFunc) IRoutes
DELETE(string, ...HandlerFunc) IRoutes
PATCH(string, ...HandlerFunc) IRoutes
PUT(string, ...HandlerFunc) IRoutes
OPTIONS(string, ...HandlerFunc) IRoutes
HEAD(string, ...HandlerFunc) IRoutes
StaticFile(string, string) routesInterface
Static(string, string) routesInterface
StaticFS(string, http.FileSystem) routesInterface
StaticFile(string, string) IRoutes
Static(string, string) IRoutes
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)
RouterGroup struct {
Handlers HandlersChain
@ -45,15 +45,15 @@ type (
}
)
var _ RoutesInterface = &RouterGroup{}
var _ IRouter = &RouterGroup{}
// Adds middlewares to the group, see example code in github.
func (group *RouterGroup) Use(middlewares ...HandlerFunc) routesInterface {
// Use adds middlewares to the group, see example code in github.
func (group *RouterGroup) Use(middlewares ...HandlerFunc) IRoutes {
group.Handlers = append(group.Handlers, middlewares...)
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.
func (group *RouterGroup) Group(relativePath string, handlers ...HandlerFunc) *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.
// 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.
@ -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
// frequently used, non-standardized or custom methods (e.g. for internal
// communication with a proxy).
func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) routesInterface {
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 {
func (group *RouterGroup) Handle(httpMethod, relativePath string, handlers ...HandlerFunc) IRoutes {
if matches, err := regexp.MatchString("^[A-Z]+$", httpMethod); !matches || err != nil {
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)
func (group *RouterGroup) POST(relativePath string, handlers ...HandlerFunc) routesInterface {
func (group *RouterGroup) POST(relativePath string, handlers ...HandlerFunc) IRoutes {
return group.handle("POST", relativePath, handlers)
}
// 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)
}
// 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)
}
// 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)
}
// 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)
}
// 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)
}
// 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)
}
func (group *RouterGroup) Any(relativePath string, handlers ...HandlerFunc) routesInterface {
// GET, POST, PUT, PATCH, HEAD, OPTIONS, DELETE, CONNECT, TRACE
// Any registers a route that matches all the HTTP methods.
// 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("POST", relativePath, handlers)
group.handle("PUT", relativePath, handlers)
@ -136,7 +137,9 @@ func (group *RouterGroup) Any(relativePath string, handlers ...HandlerFunc) rout
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, "*") {
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,
// use :
// 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))
}
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, "*") {
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 {
finalSize := len(group.Handlers) + len(handlers)
if finalSize >= int(AbortIndex) {
if finalSize >= int(abortIndex) {
panic("too many handlers")
}
mergedHandlers := make(HandlersChain, finalSize)
@ -198,10 +203,9 @@ func (group *RouterGroup) calculateAbsolutePath(relativePath string) string {
return joinPaths(group.BasePath, relativePath)
}
func (group *RouterGroup) returnObj() routesInterface {
func (group *RouterGroup) returnObj() IRoutes {
if group.root {
return group.engine
} else {
return group
}
return group
}

View File

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