From 1f6304ca259f5d8b7e419c1f56b70a66e4fc37dd Mon Sep 17 00:00:00 2001 From: Manu Mtz-Almeida Date: Tue, 7 Apr 2015 12:22:38 +0200 Subject: [PATCH] Cleaning up performance branch --- binding/form_mapping.go | 3 - context.go | 12 +-- gin.go | 177 +++++++++++++++++++++++----------------- routergroup.go | 10 +-- tree.go | 18 ++-- utils.go | 42 +++------- 6 files changed, 127 insertions(+), 135 deletions(-) diff --git a/binding/form_mapping.go b/binding/form_mapping.go index e406245f..3284b106 100644 --- a/binding/form_mapping.go +++ b/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 } diff --git a/context.go b/context.go index b42c739d..b515691c 100644 --- a/context.go +++ b/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 } /************************************/ diff --git a/gin.go b/gin.go index c8b8106c..82931a33 100644 --- a/gin.go +++ b/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,27 +176,6 @@ func (engine *Engine) handle(method, path string, handlers []HandlerFunc) { root.addRoute(path, handlers) } -// 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) - return - } - } - - // Handle 404 - engine.handle404(c) - engine.reuseContext(c) -} - func (engine *Engine) Run(addr string) error { debugPrint("Listening and serving HTTP on %s\n", addr) return http.ListenAndServe(addr, engine) @@ -249,3 +185,98 @@ 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) { + 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 + } + } + } + + 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) 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 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() + } + } +} diff --git a/routergroup.go b/routergroup.go index 3d58512f..9b51221c 100644 --- a/routergroup.go +++ b/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) } diff --git a/tree.go b/tree.go index 195fa694..9cd04fe8 100644 --- a/tree.go +++ b/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 diff --git a/utils.go b/utils.go index de9c302d..20ba5a8a 100644 --- a/utils.go +++ b/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 }