Browse Source

Cleaning up performance branch

Manu Mtz-Almeida 10 years ago
parent
commit
1f6304ca25
6 changed files with 125 additions and 133 deletions
  1. 0 3
      binding/form_mapping.go
  2. 7 5
      context.go
  3. 102 71
      gin.go
  4. 1 9
      routergroup.go
  5. 5 13
      tree.go
  6. 10 32
      utils.go

+ 0 - 3
binding/form_mapping.go

@@ -6,7 +6,6 @@ package binding
 
 import (
 	"errors"
-	"fmt"
 	"log"
 	"reflect"
 	"strconv"
@@ -27,8 +26,6 @@ func mapForm(ptr interface{}, form map[string][]string) error {
 			inputFieldName = typeField.Name
 		}
 		inputValue, exists := form[inputFieldName]
-		fmt.Println("Field: "+inputFieldName+" Value: ", inputValue)
-
 		if !exists {
 			continue
 		}

+ 7 - 5
context.go

@@ -20,7 +20,6 @@ const AbortIndex = 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.
 type Context struct {
-	Engine    *Engine
 	writermem responseWriter
 	Request   *http.Request
 	Writer    ResponseWriter
@@ -30,6 +29,7 @@ type Context struct {
 	handlers []HandlerFunc
 	index    int8
 
+	Engine   *Engine
 	Keys     map[string]interface{}
 	Errors   errorMsgs
 	accepted []string
@@ -40,10 +40,13 @@ type Context struct {
 /************************************/
 
 func (c *Context) reset() {
-	c.Keys = nil
+	c.Writer = &c.writermem
+	c.Params = c.Params[0:0]
+	c.handlers = nil
 	c.index = -1
-	c.accepted = nil
+	c.Keys = nil
 	c.Errors = c.Errors[0:0]
+	c.accepted = nil
 }
 
 func (c *Context) Copy() *Context {
@@ -114,9 +117,8 @@ func (c *Context) LastError() error {
 	nuErrors := len(c.Errors)
 	if nuErrors > 0 {
 		return errors.New(c.Errors[nuErrors-1].Err)
-	} else {
-		return nil
 	}
+	return nil
 }
 
 /************************************/

+ 102 - 71
gin.go

@@ -27,9 +27,9 @@ type Params []Param
 // ByName returns the value of the first Param which key matches the given name.
 // If no matching Param is found, an empty string is returned.
 func (ps Params) ByName(name string) string {
-	for i := range ps {
-		if ps[i].Key == name {
-			return ps[i].Value
+	for _, entry := range ps {
+		if entry.Key == name {
+			return entry.Value
 		}
 	}
 	return ""
@@ -43,7 +43,7 @@ type (
 
 	// Represents the web framework, it wraps the blazing fast httprouter multiplexer and a list of global middlewares.
 	Engine struct {
-		*RouterGroup
+		RouterGroup
 		HTMLRender  render.Render
 		pool        sync.Pool
 		allNoRoute  []HandlerFunc
@@ -84,16 +84,16 @@ type (
 // The most basic configuration
 func New() *Engine {
 	engine := &Engine{
+		RouterGroup: RouterGroup{
+			Handlers:     nil,
+			absolutePath: "/",
+		},
 		RedirectTrailingSlash:  true,
 		RedirectFixedPath:      true,
 		HandleMethodNotAllowed: true,
 		trees: make(map[string]*node),
 	}
-	engine.RouterGroup = &RouterGroup{
-		Handlers:     nil,
-		absolutePath: "/",
-		engine:       engine,
-	}
+	engine.RouterGroup.engine = engine
 	engine.pool.New = func() interface{} {
 		return engine.allocateContext()
 	}
@@ -109,23 +109,10 @@ func Default() *Engine {
 
 func (engine *Engine) allocateContext() (context *Context) {
 	context = &Context{Engine: engine}
-	context.Writer = &context.writermem
 	context.Input = inputHolder{context: context}
 	return
 }
 
-func (engine *Engine) createContext(w http.ResponseWriter, req *http.Request) *Context {
-	c := engine.pool.Get().(*Context)
-	c.reset()
-	c.writermem.reset(w)
-	c.Request = req
-	return c
-}
-
-func (engine *Engine) reuseContext(c *Context) {
-	engine.pool.Put(c)
-}
-
 func (engine *Engine) LoadHTMLGlob(pattern string) {
 	if IsDebugging() {
 		r := &render.HTMLDebugRender{Glob: pattern}
@@ -177,40 +164,10 @@ func (engine *Engine) rebuild405Handlers() {
 	engine.allNoMethod = engine.combineHandlers(engine.noMethod)
 }
 
-func (engine *Engine) handle404(c *Context) {
-	// set 404 by default, useful for logging
-	c.handlers = engine.allNoRoute
-	c.Writer.WriteHeader(404)
-	c.Next()
-	if !c.Writer.Written() {
-		if c.Writer.Status() == 404 {
-			c.Data(-1, binding.MIMEPlain, default404Body)
-		} else {
-			c.Writer.WriteHeaderNow()
-		}
-	}
-}
-
-func (engine *Engine) handle405(c *Context) {
-	// set 405 by default, useful for logging
-	c.handlers = engine.allNoMethod
-	c.Writer.WriteHeader(405)
-	c.Next()
-	if !c.Writer.Written() {
-		if c.Writer.Status() == 405 {
-			c.Data(-1, binding.MIMEPlain, default405Body)
-		} else {
-			c.Writer.WriteHeaderNow()
-		}
-	}
-}
-
 func (engine *Engine) handle(method, path string, handlers []HandlerFunc) {
 	if path[0] != '/' {
 		panic("path must begin with '/'")
 	}
-
-	//methodCode := codeForHTTPMethod(method)
 	root := engine.trees[method]
 	if root == nil {
 		root = new(node)
@@ -219,33 +176,107 @@ func (engine *Engine) handle(method, path string, handlers []HandlerFunc) {
 	root.addRoute(path, handlers)
 }
 
+func (engine *Engine) Run(addr string) error {
+	debugPrint("Listening and serving HTTP on %s\n", addr)
+	return http.ListenAndServe(addr, engine)
+}
+
+func (engine *Engine) RunTLS(addr string, cert string, key string) error {
+	debugPrint("Listening and serving HTTPS on %s\n", addr)
+	return http.ListenAndServeTLS(addr, cert, key, engine)
+}
+
 // ServeHTTP makes the router implement the http.Handler interface.
 func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
-	c := engine.createContext(w, req)
-	//methodCode := codeForHTTPMethod(req.Method)
-	if root := engine.trees[req.Method]; root != nil {
-		path := req.URL.Path
-		if handlers, params, _ := root.getValue(path, c.Params); handlers != nil {
-			c.handlers = handlers
-			c.Params = params
-			c.Next()
-			c.Writer.WriteHeaderNow()
-			engine.reuseContext(c)
+	context := engine.pool.Get().(*Context)
+	context.writermem.reset(w)
+	context.Request = req
+	context.reset()
+
+	engine.serveHTTPRequest(context)
+
+	engine.pool.Put(context)
+}
+
+func (engine *Engine) serveHTTPRequest(context *Context) {
+	httpMethod := context.Request.Method
+	path := context.Request.URL.Path
+
+	// Find root of the tree for the given HTTP method
+	if root := engine.trees[httpMethod]; root != nil {
+		// Find route in tree
+		handlers, params, tsr := root.getValue(path, context.Params)
+		// Dispatch if we found any handlers
+		if handlers != nil {
+			context.handlers = handlers
+			context.Params = params
+			context.Next()
+			context.writermem.WriteHeaderNow()
 			return
+
+		} else if httpMethod != "CONNECT" && path != "/" {
+			if engine.serveAutoRedirect(context, root, tsr) {
+				return
+			}
 		}
 	}
 
-	// Handle 404
-	engine.handle404(c)
-	engine.reuseContext(c)
+	if engine.HandleMethodNotAllowed {
+		for method, root := range engine.trees {
+			if method != httpMethod {
+				if handlers, _, _ := root.getValue(path, nil); handlers != nil {
+					context.handlers = engine.allNoMethod
+					serveError(context, 405, default405Body)
+					return
+				}
+			}
+		}
+	}
+	context.handlers = engine.allNoMethod
+	serveError(context, 404, default404Body)
 }
 
-func (engine *Engine) Run(addr string) error {
-	debugPrint("Listening and serving HTTP on %s\n", addr)
-	return http.ListenAndServe(addr, engine)
+func (engine *Engine) serveAutoRedirect(c *Context, root *node, tsr bool) bool {
+	req := c.Request
+	path := req.URL.Path
+	code := 301 // Permanent redirect, request with GET method
+	if req.Method != "GET" {
+		code = 307
+	}
+
+	if tsr && engine.RedirectTrailingSlash {
+		if len(path) > 1 && path[len(path)-1] == '/' {
+			req.URL.Path = path[:len(path)-1]
+		} else {
+			req.URL.Path = path + "/"
+		}
+		http.Redirect(c.Writer, req, req.URL.String(), code)
+		return true
+	}
+
+	// Try to fix the request path
+	if engine.RedirectFixedPath {
+		fixedPath, found := root.findCaseInsensitivePath(
+			CleanPath(path),
+			engine.RedirectTrailingSlash,
+		)
+		if found {
+			req.URL.Path = string(fixedPath)
+			http.Redirect(c.Writer, req, req.URL.String(), code)
+			return true
+		}
+	}
+	return false
 }
 
-func (engine *Engine) RunTLS(addr string, cert string, key string) error {
-	debugPrint("Listening and serving HTTPS on %s\n", addr)
-	return http.ListenAndServeTLS(addr, cert, key, engine)
+func serveError(c *Context, code int, defaultMessage []byte) {
+	c.writermem.status = code
+	c.Next()
+	if !c.Writer.Written() {
+		if c.Writer.Status() == code {
+			c.Data(-1, binding.MIMEPlain, defaultMessage)
+		} else {
+			c.Writer.WriteHeaderNow()
+		}
+	}
 }

+ 1 - 9
routergroup.go

@@ -125,13 +125,5 @@ func (group *RouterGroup) combineHandlers(handlers []HandlerFunc) []HandlerFunc
 }
 
 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
+	return joinPaths(group.absolutePath, relativePath)
 }

+ 5 - 13
tree.go

@@ -312,6 +312,7 @@ func (n *node) insertChild(numParams uint8, path string, handlers []HandlerFunc)
 // made if a handle exists with an extra (without the) trailing slash for the
 // given path.
 func (n *node) getValue(path string, po Params) (handlers []HandlerFunc, p Params, tsr bool) {
+	p = po
 walk: // Outer loop for walking the tree
 	for {
 		if len(path) > len(n.path) {
@@ -334,7 +335,6 @@ walk: // Outer loop for walking the tree
 					// trailing slash if a leaf exists for that path.
 					tsr = (path == "/" && n.handlers != nil)
 					return
-
 				}
 
 				// handle wildcard child
@@ -348,12 +348,8 @@ walk: // Outer loop for walking the tree
 					}
 
 					// save param value
-					if p == nil {
-						if cap(po) < int(n.maxParams) {
-							p = make(Params, 0, n.maxParams)
-						} else {
-							p = po[0:0]
-						}
+					if cap(p) < int(n.maxParams) {
+						p = make(Params, 0, n.maxParams)
 					}
 					i := len(p)
 					p = p[:i+1] // expand slice within preallocated capacity
@@ -386,12 +382,8 @@ walk: // Outer loop for walking the tree
 
 				case catchAll:
 					// save param value
-					if p == nil {
-						if cap(po) < int(n.maxParams) {
-							p = make(Params, 0, n.maxParams)
-						} else {
-							p = po[0:0]
-						}
+					if cap(p) < int(n.maxParams) {
+						p = make(Params, 0, n.maxParams)
 					}
 					i := len(p)
 					p = p[:i+1] // expand slice within preallocated capacity

+ 10 - 32
utils.go

@@ -7,23 +7,12 @@ package gin
 import (
 	"encoding/xml"
 	"log"
+	"path"
 	"reflect"
 	"runtime"
 	"strings"
 )
 
-const (
-	methodGET     = iota
-	methodPOST    = iota
-	methodPUT     = iota
-	methodAHEAD   = iota
-	methodOPTIONS = iota
-	methodDELETE  = iota
-	methodCONNECT = iota
-	methodTRACE   = iota
-	methodUnknown = iota
-)
-
 type H map[string]interface{}
 
 // Allows type H to be used with xml.Marshal
@@ -93,25 +82,14 @@ func nameOfFunction(f interface{}) string {
 	return runtime.FuncForPC(reflect.ValueOf(f).Pointer()).Name()
 }
 
-func codeForHTTPMethod(method string) int {
-	switch method {
-	case "GET":
-		return methodGET
-	case "POST":
-		return methodPOST
-	case "PUT":
-		return methodPUT
-	case "AHEAD":
-		return methodAHEAD
-	case "OPTIONS":
-		return methodOPTIONS
-	case "DELETE":
-		return methodDELETE
-	case "TRACE":
-		return methodTRACE
-	case "CONNECT":
-		return methodCONNECT
-	default:
-		return methodUnknown
+func joinPaths(absolutePath, relativePath string) string {
+	if len(relativePath) == 0 {
+		return absolutePath
+	}
+	absolutePath = path.Join(absolutePath, relativePath)
+	appendSlash := lastChar(relativePath) == '/' && lastChar(absolutePath) != '/'
+	if appendSlash {
+		return absolutePath + "/"
 	}
+	return absolutePath
 }