Browse Source

General refactoring

Manu Mtz-Almeida 11 years ago
parent
commit
07a3961941
7 changed files with 223 additions and 180 deletions
  1. 37 25
      context.go
  2. 3 3
      context_test.go
  3. 25 150
      gin.go
  4. 4 0
      mode.go
  5. 1 1
      recovery_test.go
  6. 137 0
      routergroup.go
  7. 16 1
      utils.go

+ 37 - 25
context.go

@@ -71,11 +71,11 @@ type Context struct {
 }
 
 /************************************/
-/********** ROUTES GROUPING *********/
+/********** CONTEXT CREATION ********/
 /************************************/
 
 func (engine *Engine) createContext(w http.ResponseWriter, req *http.Request, params httprouter.Params, handlers []HandlerFunc) *Context {
-	c := engine.cache.Get().(*Context)
+	c := engine.pool.Get().(*Context)
 	c.writermem.reset(w)
 	c.Request = req
 	c.Params = params
@@ -87,9 +87,9 @@ func (engine *Engine) createContext(w http.ResponseWriter, req *http.Request, pa
 	return c
 }
 
-/************************************/
-/****** FLOW AND ERROR MANAGEMENT****/
-/************************************/
+func (engine *Engine) reuseContext(c *Context) {
+	engine.pool.Put(c)
+}
 
 func (c *Context) Copy() *Context {
 	var cp Context = *c
@@ -98,6 +98,10 @@ func (c *Context) Copy() *Context {
 	return &cp
 }
 
+/************************************/
+/*************** FLOW ***************/
+/************************************/
+
 // Next should be used only in the middlewares.
 // It executes the pending handlers in the chain inside the calling handler.
 // See example in github.
@@ -109,25 +113,31 @@ func (c *Context) Next() {
 	}
 }
 
-// Forces the system to do not continue calling the pending handlers.
-// For example, the first handler checks if the request is authorized. If it's not, context.Abort(401) should be called.
-// The rest of pending handlers would never be called for that request.
-func (c *Context) Abort(code int) {
-	if code >= 0 {
-		c.Writer.WriteHeader(code)
-	}
+// Forces the system to do not continue calling the pending handlers in the chain.
+func (c *Context) Abort() {
 	c.index = AbortIndex
 }
 
+// Same than AbortWithStatus() but also writes the specified response status code.
+// For example, the first handler checks if the request is authorized. If it's not, context.AbortWithStatus(401) should be called.
+func (c *Context) AbortWithStatus(code int) {
+	c.Writer.WriteHeader(code)
+	c.Abort()
+}
+
+/************************************/
+/********* ERROR MANAGEMENT *********/
+/************************************/
+
 // Fail is the same as Abort plus an error message.
 // Calling `context.Fail(500, err)` is equivalent to:
 // ```
 // context.Error("Operation aborted", err)
-// context.Abort(500)
+// context.AbortWithStatus(500)
 // ```
 func (c *Context) Fail(code int, err error) {
 	c.Error(err, "Operation aborted")
-	c.Abort(code)
+	c.AbortWithStatus(code)
 }
 
 func (c *Context) ErrorTyped(err error, typ uint32, meta interface{}) {
@@ -146,9 +156,9 @@ func (c *Context) Error(err error, meta interface{}) {
 }
 
 func (c *Context) LastError() error {
-	s := len(c.Errors)
-	if s > 0 {
-		return errors.New(c.Errors[s-1].Err)
+	nuErrors := len(c.Errors)
+	if nuErrors > 0 {
+		return errors.New(c.Errors[nuErrors-1].Err)
 	} else {
 		return nil
 	}
@@ -170,9 +180,9 @@ func (c *Context) Set(key string, item interface{}) {
 // Get returns the value for the given key or an error if the key does not exist.
 func (c *Context) Get(key string) (interface{}, error) {
 	if c.Keys != nil {
-		item, ok := c.Keys[key]
+		value, ok := c.Keys[key]
 		if ok {
-			return item, nil
+			return value, nil
 		}
 	}
 	return nil, errors.New("Key does not exist.")
@@ -182,13 +192,13 @@ func (c *Context) Get(key string) (interface{}, error) {
 func (c *Context) MustGet(key string) interface{} {
 	value, err := c.Get(key)
 	if err != nil || value == nil {
-		log.Panicf("Key %s doesn't exist", key)
+		log.Panicf("Key %s doesn't exist", value)
 	}
 	return value
 }
 
 /************************************/
-/******** ENCOGING MANAGEMENT********/
+/********* PARSING REQUEST **********/
 /************************************/
 
 // This function checks the Content-Type to select a binding engine automatically,
@@ -222,10 +232,14 @@ func (c *Context) BindWith(obj interface{}, b binding.Binding) bool {
 	return true
 }
 
+/************************************/
+/******** RESPONSE RENDERING ********/
+/************************************/
+
 func (c *Context) Render(code int, render render.Render, obj ...interface{}) {
 	if err := render.Render(c.Writer, code, obj...); err != nil {
 		c.ErrorTyped(err, ErrorTypeInternal, obj)
-		c.Abort(500)
+		c.AbortWithStatus(500)
 	}
 }
 
@@ -267,9 +281,7 @@ func (c *Context) Data(code int, contentType string, data []byte) {
 	if len(contentType) > 0 {
 		c.Writer.Header().Set("Content-Type", contentType)
 	}
-	if code >= 0 {
-		c.Writer.WriteHeader(code)
-	}
+	c.Writer.WriteHeader(code)
 	c.Writer.Write(data)
 }
 

+ 3 - 3
context_test.go

@@ -232,13 +232,13 @@ func TestBadAbortHandlersChain(t *testing.T) {
 		c.Next()
 		stepsPassed += 1
 		// after check and abort
-		c.Abort(409)
+		c.AbortWithStatus(409)
 	})
 	r.Use(func(c *Context) {
 		stepsPassed += 1
 		c.Next()
 		stepsPassed += 1
-		c.Abort(403)
+		c.AbortWithStatus(403)
 	})
 
 	// RUN
@@ -260,7 +260,7 @@ func TestAbortHandlersChain(t *testing.T) {
 	r := New()
 	r.Use(func(context *Context) {
 		stepsPassed += 1
-		context.Abort(409)
+		context.AbortWithStatus(409)
 	})
 	r.Use(func(context *Context) {
 		stepsPassed += 1

+ 25 - 150
gin.go

@@ -5,13 +5,11 @@
 package gin
 
 import (
-	"fmt"
 	"github.com/gin-gonic/gin/render"
 	"github.com/julienschmidt/httprouter"
 	"html/template"
 	"math"
 	"net/http"
-	"path"
 	"sync"
 )
 
@@ -28,28 +26,19 @@ const (
 type (
 	HandlerFunc func(*Context)
 
-	// Used internally to configure router, a RouterGroup is associated with a prefix
-	// and an array of handlers (middlewares)
-	RouterGroup struct {
-		Handlers []HandlerFunc
-		prefix   string
-		parent   *RouterGroup
-		engine   *Engine
-	}
-
 	// Represents the web framework, it wraps the blazing fast httprouter multiplexer and a list of global middlewares.
 	Engine struct {
 		*RouterGroup
-		HTMLRender   render.Render
-		cache        sync.Pool
-		finalNoRoute []HandlerFunc
-		noRoute      []HandlerFunc
-		router       *httprouter.Router
+		HTMLRender render.Render
+		pool       sync.Pool
+		allNoRoute []HandlerFunc
+		noRoute    []HandlerFunc
+		router     *httprouter.Router
 	}
 )
 
 func (engine *Engine) handle404(w http.ResponseWriter, req *http.Request) {
-	c := engine.createContext(w, req, nil, engine.finalNoRoute)
+	c := engine.createContext(w, req, nil, engine.allNoRoute)
 	// set 404 by default, useful for logging
 	c.Writer.WriteHeader(404)
 	c.Next()
@@ -60,17 +49,21 @@ func (engine *Engine) handle404(w http.ResponseWriter, req *http.Request) {
 			c.Writer.WriteHeaderNow()
 		}
 	}
-	engine.cache.Put(c)
+	engine.reuseContext(c)
 }
 
 // Returns a new blank Engine instance without any middleware attached.
 // The most basic configuration
 func New() *Engine {
 	engine := &Engine{}
-	engine.RouterGroup = &RouterGroup{nil, "/", nil, engine}
+	engine.RouterGroup = &RouterGroup{
+		Handlers:     nil,
+		absolutePath: "/",
+		engine:       engine,
+	}
 	engine.router = httprouter.New()
 	engine.router.NotFound = engine.handle404
-	engine.cache.New = func() interface{} {
+	engine.pool.New = func() interface{} {
 		c := &Context{Engine: engine}
 		c.Writer = &c.writermem
 		return c
@@ -86,7 +79,7 @@ func Default() *Engine {
 }
 
 func (engine *Engine) LoadHTMLGlob(pattern string) {
-	if gin_mode == debugCode {
+	if IsDebugging() {
 		render.HTMLDebug.AddGlob(pattern)
 		engine.HTMLRender = render.HTMLDebug
 	} else {
@@ -96,7 +89,7 @@ func (engine *Engine) LoadHTMLGlob(pattern string) {
 }
 
 func (engine *Engine) LoadHTMLFiles(files ...string) {
-	if gin_mode == debugCode {
+	if IsDebugging() {
 		render.HTMLDebug.AddFiles(files...)
 		engine.HTMLRender = render.HTMLDebug
 	} else {
@@ -114,151 +107,33 @@ func (engine *Engine) SetHTMLTemplate(templ *template.Template) {
 // Adds handlers for NoRoute. It return a 404 code by default.
 func (engine *Engine) NoRoute(handlers ...HandlerFunc) {
 	engine.noRoute = handlers
-	engine.finalNoRoute = engine.combineHandlers(engine.noRoute)
+	engine.rebuild404Handlers()
 }
 
 func (engine *Engine) Use(middlewares ...HandlerFunc) {
 	engine.RouterGroup.Use(middlewares...)
-	engine.finalNoRoute = engine.combineHandlers(engine.noRoute)
+	engine.rebuild404Handlers()
+}
+
+func (engine *Engine) rebuild404Handlers() {
+	engine.allNoRoute = engine.combineHandlers(engine.noRoute)
 }
 
 // ServeHTTP makes the router implement the http.Handler interface.
-func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
-	engine.router.ServeHTTP(w, req)
+func (engine *Engine) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
+	engine.router.ServeHTTP(writer, request)
 }
 
 func (engine *Engine) Run(addr string) {
-	if gin_mode == debugCode {
-		fmt.Println("[GIN-debug] Listening and serving HTTP on " + addr)
-	}
+	debugPrint("Listening and serving HTTP on %s", addr)
 	if err := http.ListenAndServe(addr, engine); err != nil {
 		panic(err)
 	}
 }
 
 func (engine *Engine) RunTLS(addr string, cert string, key string) {
-	if gin_mode == debugCode {
-		fmt.Println("[GIN-debug] Listening and serving HTTPS on " + addr)
-	}
+	debugPrint("Listening and serving HTTPS on %s", addr)
 	if err := http.ListenAndServeTLS(addr, cert, key, engine); err != nil {
 		panic(err)
 	}
 }
-
-/************************************/
-/********** ROUTES GROUPING *********/
-/************************************/
-
-// Adds middlewares to the group, see example code in github.
-func (group *RouterGroup) Use(middlewares ...HandlerFunc) {
-	group.Handlers = append(group.Handlers, middlewares...)
-}
-
-// 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(component string, handlers ...HandlerFunc) *RouterGroup {
-	prefix := group.pathFor(component)
-
-	return &RouterGroup{
-		Handlers: group.combineHandlers(handlers),
-		parent:   group,
-		prefix:   prefix,
-		engine:   group.engine,
-	}
-}
-
-func (group *RouterGroup) pathFor(p string) string {
-	joined := path.Join(group.prefix, p)
-	// Append a '/' if the last component had one, but only if it's not there already
-	if len(p) > 0 && p[len(p)-1] == '/' && joined[len(joined)-1] != '/' {
-		return joined + "/"
-	}
-	return joined
-}
-
-// 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.
-//
-// For GET, POST, PUT, PATCH and DELETE requests the respective shortcut
-// functions can be used.
-//
-// 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(method, p string, handlers []HandlerFunc) {
-	p = group.pathFor(p)
-	handlers = group.combineHandlers(handlers)
-	if gin_mode == debugCode {
-		nuHandlers := len(handlers)
-		name := funcName(handlers[nuHandlers-1])
-		fmt.Printf("[GIN-debug] %-5s %-25s --> %s (%d handlers)\n", method, p, name, nuHandlers)
-	}
-	group.engine.router.Handle(method, p, func(w http.ResponseWriter, req *http.Request, params httprouter.Params) {
-		c := group.engine.createContext(w, req, params, handlers)
-		c.Next()
-		c.Writer.WriteHeaderNow()
-		group.engine.cache.Put(c)
-	})
-}
-
-// POST is a shortcut for router.Handle("POST", path, handle)
-func (group *RouterGroup) POST(path string, handlers ...HandlerFunc) {
-	group.Handle("POST", path, handlers)
-}
-
-// GET is a shortcut for router.Handle("GET", path, handle)
-func (group *RouterGroup) GET(path string, handlers ...HandlerFunc) {
-	group.Handle("GET", path, handlers)
-}
-
-// DELETE is a shortcut for router.Handle("DELETE", path, handle)
-func (group *RouterGroup) DELETE(path string, handlers ...HandlerFunc) {
-	group.Handle("DELETE", path, handlers)
-}
-
-// PATCH is a shortcut for router.Handle("PATCH", path, handle)
-func (group *RouterGroup) PATCH(path string, handlers ...HandlerFunc) {
-	group.Handle("PATCH", path, handlers)
-}
-
-// PUT is a shortcut for router.Handle("PUT", path, handle)
-func (group *RouterGroup) PUT(path string, handlers ...HandlerFunc) {
-	group.Handle("PUT", path, handlers)
-}
-
-// OPTIONS is a shortcut for router.Handle("OPTIONS", path, handle)
-func (group *RouterGroup) OPTIONS(path string, handlers ...HandlerFunc) {
-	group.Handle("OPTIONS", path, handlers)
-}
-
-// HEAD is a shortcut for router.Handle("HEAD", path, handle)
-func (group *RouterGroup) HEAD(path string, handlers ...HandlerFunc) {
-	group.Handle("HEAD", path, handlers)
-}
-
-// Static serves files from the given file system root.
-// Internally a http.FileServer is used, therefore http.NotFound is used instead
-// of the Router's NotFound handler.
-// To use the operating system's file system implementation,
-// use :
-//     router.Static("/static", "/var/www")
-func (group *RouterGroup) Static(p, root string) {
-	prefix := group.pathFor(p)
-	p = path.Join(p, "/*filepath")
-	fileServer := http.StripPrefix(prefix, http.FileServer(http.Dir(root)))
-	group.GET(p, func(c *Context) {
-		fileServer.ServeHTTP(c.Writer, c.Request)
-	})
-	group.HEAD(p, func(c *Context) {
-		fileServer.ServeHTTP(c.Writer, c.Request)
-	})
-}
-
-func (group *RouterGroup) combineHandlers(handlers []HandlerFunc) []HandlerFunc {
-	s := len(group.Handlers) + len(handlers)
-	h := make([]HandlerFunc, 0, s)
-	h = append(h, group.Handlers...)
-	h = append(h, handlers...)
-	return h
-}

+ 4 - 0
mode.go

@@ -42,6 +42,10 @@ func Mode() string {
 	return mode_name
 }
 
+func IsDebugging() bool {
+	return gin_mode == debugCode
+}
+
 func init() {
 	value := os.Getenv(GIN_MODE)
 	if len(value) == 0 {

+ 1 - 1
recovery_test.go

@@ -39,7 +39,7 @@ func TestPanicWithAbort(t *testing.T) {
 	r := New()
 	r.Use(Recovery())
 	r.GET("/recovery", func(c *Context) {
-		c.Abort(400)
+		c.AbortWithStatus(400)
 		panic("Oupps, Houston, we have a problem")
 	})
 

+ 137 - 0
routergroup.go

@@ -0,0 +1,137 @@
+// 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 (
+	"github.com/julienschmidt/httprouter"
+	"net/http"
+	"path"
+)
+
+// Used internally to configure router, a RouterGroup is associated with a prefix
+// and an array of handlers (middlewares)
+type RouterGroup struct {
+	Handlers     []HandlerFunc
+	absolutePath string
+	engine       *Engine
+}
+
+// Adds middlewares to the group, see example code in github.
+func (group *RouterGroup) Use(middlewares ...HandlerFunc) {
+	group.Handlers = append(group.Handlers, middlewares...)
+}
+
+// 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{
+		Handlers:     group.combineHandlers(handlers),
+		absolutePath: group.calculateAbsolutePath(relativePath),
+		engine:       group.engine,
+	}
+}
+
+// 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.
+//
+// For GET, POST, PUT, PATCH and DELETE requests the respective shortcut
+// functions can be used.
+//
+// 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 []HandlerFunc) {
+	absolutePath := group.calculateAbsolutePath(relativePath)
+	handlers = group.combineHandlers(handlers)
+	if IsDebugging() {
+		nuHandlers := len(handlers)
+		handlerName := nameOfFuncion(handlers[nuHandlers-1])
+		debugPrint("%-5s %-25s --> %s (%d handlers)\n", httpMethod, absolutePath, handlerName, nuHandlers)
+	}
+
+	group.engine.router.Handle(httpMethod, absolutePath, func(w http.ResponseWriter, req *http.Request, params httprouter.Params) {
+		context := group.engine.createContext(w, req, params, handlers)
+		context.Next()
+		context.Writer.WriteHeaderNow()
+		group.engine.reuseContext(context)
+	})
+}
+
+// POST is a shortcut for router.Handle("POST", path, handle)
+func (group *RouterGroup) POST(relativePath string, handlers ...HandlerFunc) {
+	group.Handle("POST", relativePath, handlers)
+}
+
+// GET is a shortcut for router.Handle("GET", path, handle)
+func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) {
+	group.Handle("GET", relativePath, handlers)
+}
+
+// DELETE is a shortcut for router.Handle("DELETE", path, handle)
+func (group *RouterGroup) DELETE(relativePath string, handlers ...HandlerFunc) {
+	group.Handle("DELETE", relativePath, handlers)
+}
+
+// PATCH is a shortcut for router.Handle("PATCH", path, handle)
+func (group *RouterGroup) PATCH(relativePath string, handlers ...HandlerFunc) {
+	group.Handle("PATCH", relativePath, handlers)
+}
+
+// PUT is a shortcut for router.Handle("PUT", path, handle)
+func (group *RouterGroup) PUT(relativePath string, handlers ...HandlerFunc) {
+	group.Handle("PUT", relativePath, handlers)
+}
+
+// OPTIONS is a shortcut for router.Handle("OPTIONS", path, handle)
+func (group *RouterGroup) OPTIONS(relativePath string, handlers ...HandlerFunc) {
+	group.Handle("OPTIONS", relativePath, handlers)
+}
+
+// HEAD is a shortcut for router.Handle("HEAD", path, handle)
+func (group *RouterGroup) HEAD(relativePath string, handlers ...HandlerFunc) {
+	group.Handle("HEAD", relativePath, handlers)
+}
+
+// Static serves files from the given file system root.
+// Internally a http.FileServer is used, therefore http.NotFound is used instead
+// of the Router's NotFound handler.
+// To use the operating system's file system implementation,
+// use :
+//     router.Static("/static", "/var/www")
+func (group *RouterGroup) Static(relativePath, root string) {
+	handler := group.createStaticHandler(relativePath, root)
+	group.GET(relativePath, handler)
+	group.HEAD(relativePath, handler)
+}
+
+func (group *RouterGroup) createStaticHandler(relativePath, root string) func(*Context) {
+	absolutePath := group.calculateAbsolutePath(relativePath)
+	absolutePath = path.Join(absolutePath, "/*filepath")
+	fileServer := http.StripPrefix(absolutePath, http.FileServer(http.Dir(root)))
+	return func(c *Context) {
+		fileServer.ServeHTTP(c.Writer, c.Request)
+	}
+}
+
+func (group *RouterGroup) combineHandlers(handlers []HandlerFunc) []HandlerFunc {
+	finalSize := len(group.Handlers) + len(handlers)
+	mergedHandlers := make([]HandlerFunc, 0, finalSize)
+	mergedHandlers = append(mergedHandlers, group.Handlers...)
+	mergedHandlers = append(mergedHandlers, handlers...)
+	return mergedHandlers
+}
+
+func (group *RouterGroup) calculateAbsolutePath(relativePath string) string {
+	if len(relativePath) == 0 {
+		return group.absolutePath
+	}
+	absolutePath := path.Join(group.absolutePath, relativePath)
+	appendSlash := lastChar(relativePath) == '/' && lastChar(absolutePath) != '/'
+	if appendSlash {
+		return absolutePath + "/"
+	}
+	return absolutePath
+}

+ 16 - 1
utils.go

@@ -6,6 +6,7 @@ package gin
 
 import (
 	"encoding/xml"
+	"fmt"
 	"reflect"
 	"runtime"
 	"strings"
@@ -46,6 +47,12 @@ func filterFlags(content string) string {
 	return content
 }
 
+func debugPrint(format string, values ...interface{}) {
+	if IsDebugging() {
+		fmt.Printf("[GIN-debug] "+format, values)
+	}
+}
+
 func chooseData(custom, wildcard interface{}) interface{} {
 	if custom == nil {
 		if wildcard == nil {
@@ -69,6 +76,14 @@ func parseAccept(accept string) []string {
 	return parts
 }
 
-func funcName(f interface{}) string {
+func lastChar(str string) uint8 {
+	size := len(str)
+	if size == 0 {
+		panic("The length of the string can't be 0")
+	}
+	return str[size-1]
+}
+
+func nameOfFuncion(f interface{}) string {
 	return runtime.FuncForPC(reflect.ValueOf(f).Pointer()).Name()
 }