Merge branch 'master' of https://github.com/dahankzter/httprouter into dahankzter-master

Conflicts:
	router_test.go
diff --git a/.travis.yml b/.travis.yml
index 2439ae0..b0cf782 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,4 +1,6 @@
 language: go
 go:
+  - 1.1
   - 1.2
+  - 1.3
   - tip
diff --git a/README.md b/README.md
index 03b7f0f..082e4cf 100644
--- a/README.md
+++ b/README.md
@@ -1,30 +1,53 @@
 # HttpRouter [![Build Status](https://travis-ci.org/julienschmidt/httprouter.png?branch=master)](https://travis-ci.org/julienschmidt/httprouter) [![GoDoc](http://godoc.org/github.com/julienschmidt/httprouter?status.png)](http://godoc.org/github.com/julienschmidt/httprouter)
 
-HttpRouter is a high performance HTTP request router
-(also called *multiplexer* or just *mux* for short).
+HttpRouter is a lightweight high performance HTTP request router
+(also called *multiplexer* or just *mux* for short) for [Go](http://golang.org/).
 
 In contrast to the default mux of Go's net/http package, this router supports
 variables in the routing pattern and matches against the request method.
 It also scales better.
 
 The router is optimized for best performance and a small memory footprint.
-It scales well even with very long pathes and a large number of routes.
+It scales well even with very long paths and a large number of routes.
 A compressing dynamic trie (radix tree) structure is used for efficient matching.
 
 ## Features
-**Zero Garbage:** The matching and dispatching process generates zero bytes of garbage. In fact, the only heap allocations that are made, is by building the map containing the key-value pairs for variables. If the request path contains no variables, not a single heap allocation is necessary.
+**Zero Garbage:** The matching and dispatching process generates zero bytes of
+garbage. In fact, the only heap allocations that are made, is by building the
+slice of the key-value pairs for path parameters. If the request path contains
+no parameters, not a single heap allocation is necessary.
 
-**Best Performance:** [Benchmarks speak for themselves](https://github.com/julienschmidt/go-http-routing-benchmark). See below for technical details of the implementation.
+**Best Performance:** [Benchmarks speak for themselves](https://github.com/julienschmidt/go-http-routing-benchmark).
+See below for technical details of the implementation.
 
-**Variables in your routing pattern:** Stop parsing the requested URL path, just give the path segment a name and the router delivers the value to you. Because of the design of the router, pattern variables are very cheap.
+**Parameters in your routing pattern:** Stop parsing the requested URL path,
+just give the path segment a name and the router delivers the dynamic value to
+you. Because of the design of the router, path parameters are very cheap.
 
-**Only explicit matches:** With other routers / muxes, like [http.ServeMux](http://golang.org/pkg/net/http/#ServeMux), a requested URL path could match multiple patterns. Therefore they have some awkward pattern priority rules, like *longest match* or *first registered, first matched*. By design of this router, a request can only match exactly one or no route. As a result, there are also no unintended matches, which makes it great for SEO. 
+**Only explicit matches:** With other routers, like [http.ServeMux](http://golang.org/pkg/net/http/#ServeMux),
+a requested URL path could match multiple patterns. Therefore they have some
+awkward pattern priority rules, like *longest match* or *first registered,
+first matched*. By design of this router, a request can only match exactly one
+or no route. As a result, there are also no unintended matches, which makes it
+great for SEO and improves the user experience.
 
-**Stop caring about trailing slashes:** Choose the URL style you like, the router automatically redirects the client if a trailing slash is missing or if there is one extra. Of course it only does so, if the new path has a handler. If you don't like it, you can turn off this behavior.
+**Stop caring about trailing slashes:** Choose the URL style you like, the
+router automatically redirects the client if a trailing slash is missing or if
+there is one extra. Of course it only does so, if the new path has a handler.
+If you don't like it, you can turn off this behavior.
 
-**No more server crashes:** You can set a PanicHandler to deal with panics occouring during handling a HTTP request. The router then recovers and lets the PanicHandler log what happened and deliver a nice error page.
+**Path auto-correction:** Besides detecting the missing or additional trailing
+slash at no extra cost, the router can also fix wrong cases and remove
+superfluous path elements (like `../` or `//`).
+Is [CAPTAIN CAPS LOCK](http://www.urbandictionary.com/define.php?term=Captain+Caps+Lock) one of your users?
+HttpRouter can help him by making a case-insensitive look-up and redirecting him
+to the correct URL.
 
-Of course you can also set a custom NotFound handler and serve files.
+**No more server crashes:** You can set a PanicHandler to deal with panics
+occurring during handling a HTTP request. The router then recovers and lets the
+PanicHandler log what happened and deliver a nice error page.
+
+Of course you can also set a **custom NotFound handler** and **serve static files**.
 
 ## Usage
 This is just a quick introduction, view the [GoDoc](http://godoc.org/github.com/julienschmidt/httprouter) for details.
@@ -40,12 +63,12 @@
     "log"
 )
 
-func Index(w http.ResponseWriter, r *http.Request, _ map[string]string) {
+func Index(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
     fmt.Fprint(w, "Welcome!\n")
 }
 
-func Hello(w http.ResponseWriter, r *http.Request, vars map[string]string) {
-    fmt.Fprintf(w, "hello, %s!\n", vars["name"])
+func Hello(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
+    fmt.Fprintf(w, "hello, %s!\n", ps.ByName("name"))
 }
 
 func main() {
@@ -53,13 +76,15 @@
     router.GET("/", Index)
     router.GET("/hello/:name", Hello)
 
-    log.Fatal(http.ListenAndServe(":12345", router))
+    log.Fatal(http.ListenAndServe(":8080", router))
 }
 ```
 
 ### Named parameters
 As you can see, `:name` is a *named parameter*.
-The values are passed in a map, therefore the value of `:name` is available in `vars["name"]`.
+The values are accessible via `httprouter.Params`, which is just a slice of `httprouter.Param`s.
+You can get the value of a parameter either by its index in the slice, or by using the `ByName(name)` method:
+`:name` can be retrived by `ByName("name")`.
 
 Named parameters only match a single path segment:
 ```
@@ -71,10 +96,11 @@
  /user/                    no match
 ```
 
-**Note:** Since this router has only explicit matches, you can not register static routes and paramters for the same path segment. For example you can not register the patterns `/user/new` and `/user/:user` at the same time.
+**Note:** Since this router has only explicit matches, you can not register static routes and parameters for the same path segment. For example you can not register the patterns `/user/new` and `/user/:user` for the same request method at the same time. The routing of different request methods is independent from each other.
 
-### Catch-All routes
-The second type are *catch-all* routes and have the form `*name`. Like the name suggest, they match everything.
+### Catch-All parameters
+The second type are *catch-all* parameters and have the form `*name`.
+Like the name suggests, they match everything.
 Therefore they must always be at the **end** of the pattern:
 ```
 Pattern: /src/*filepath
@@ -85,33 +111,52 @@
 ```
 
 ## How does it work?
-The router relies on a tree structure which makes heavy use of *common prefixes*, it is basically a *compact* [*prefix tree*](http://en.wikipedia.org/wiki/Trie) (or just [*Radix tree*](http://en.wikipedia.org/wiki/Radix_tree)). Nodes with a common prefix also share a common parent. Here is a short example what the routing tree could look like:
+The router relies on a tree structure which makes heavy use of *common prefixes*,
+it is basically a *compact* [*prefix tree*](http://en.wikipedia.org/wiki/Trie)
+(or just [*Radix tree*](http://en.wikipedia.org/wiki/Radix_tree)).
+Nodes with a common prefix also share a common parent. Here is a short example
+what the routing tree for the `GET` request method could look like:
 
 ```
-Priority   Path             Handles
-9          \                map[GET:<1>]
-3          ├s               map[]
-2          |├earch\         map[GET:<2>, POST:<3>,]
-1          |└upport\        map[GET:<4>]
-2          ├blog\           map[GET:<5>]
-1          |    └:post      map[]
-1          |         └\     map[GET:<6>]
-2          ├about-us\       map[GET:<7>]
-1          |        └team\  map[GET:<8>]
-1          └contact\        map[GET:<9>]
+Priority   Path             Handle
+9          \                *<1>
+3          ├s               nil
+2          |├earch\         *<2>
+1          |└upport\        *<3>
+2          ├blog\           *<4>
+1          |    └:post      nil
+1          |         └\     *<5>
+2          ├about-us\       *<6>
+1          |        └team\  *<7>
+1          └contact\        *<8>
 ```
-Every `<num>` represents the memory address of a handler function. If you follow a path trough the tree from the root to the leaf, you get the complete route path, e.g `\search\`, for which a GET- and a POST-handler function are registered.
+Every `*<num>` represents the memory address of a handler function (a pointer).
+If you follow a path trough the tree from the root to the leaf, you get the
+complete route path, e.g `\blog\:post\`, where `:post` is just a placeholder
+([*parameter*](#named-parameters)) for an actual post name. Unlike hash-maps, a
+tree structure also allows us to use dynamic parts like the `:post` parameter,
+since we actually match against the routing patterns instead of just comparing
+hashes. [As benchmarks show](https://github.com/julienschmidt/go-http-routing-benchmark),
+this works very well and efficient.
 
-Since URL pathes have a hierarchical structure and make use only of a limited set of characters, it is very likely that there are a lot of common prefixes. This allows us to easiely reduce the routing into ever smaller problems.
+Since URL paths have a hierarchical structure and make use only of a limited set
+of characters (byte values), it is very likely that there are a lot of common
+prefixes. This allows us to easily reduce the routing into ever smaller problems.
+Moreover the router manages a separate tree for every request method.
+For one thing it is more space efficient than holding a method->handle map in
+every single node, for another thing is also allows us to greatly reduce the
+routing problem before even starting the look-up in the prefix-tree.
 
-Unlike hash-maps, a tree structure also allows us to use dynamic parts, e.g. the `:post` above, which is just a placeholder for a post name, since we actually match against the routing patterns instead of just comparing hashes. [As benchmarks show](https://github.com/julienschmidt/go-http-routing-benchmark), hash-map based routers also don't scale very well.
-
-For even better scalability, the child nodes on each tree level are ordered by priority, where the priority is just the number of handles registered in sub nodes (children, grandchildren, and so on..).
+For even better scalability, the child nodes on each tree level are ordered by
+priority, where the priority is just the number of handles registered in sub
+nodes (children, grandchildren, and so on..).
 This helps in two ways:
 
-1. Nodes which are part of the most routing pathes are evaluated first. This helps to make as much routes as possible to be reachable as fast as possible.
-2. It is some sort of cost compensation. The longest reachable path (highest cost) can always be evaluated first. The follwing scheme visualizes the tree structure. Nodes are evaluated from top to bottom and from left to right.
-
+1. Nodes which are part of the most routing paths are evaluated first. This
+helps to make as much routes as possible to be reachable as fast as possible.
+2. It is some sort of cost compensation. The longest reachable path (highest
+cost) can always be evaluated first. The following scheme visualizes the tree
+structure. Nodes are evaluated from top to bottom and from left to right.
 
 ```
 ├------------
@@ -123,10 +168,31 @@
 └-
 ```
 
-## Where can I find Middleware *X*?
-This package just provides a very efficient request router with a few extra features. The router is just a [http.Handler](http://golang.org/pkg/net/http/#Handler), you can chain any http.Handler compatible middleware before the router, for example the [Gorilla handlers](http://www.gorillatoolkit.org/pkg/handlers). Or you could [just write your own](http://justinas.org/writing-http-middleware-in-go/), it's very easy!
 
-Here is a quick example: Does your server serve multiple domains / hosts? You want to use subdomains?
+## Why doesn't this work with http.Handler?
+**It does!** The router itself implements the http.Handler interface.
+Moreover the router provides convenient [adapters for http.Handler](http://godoc.org/github.com/julienschmidt/httprouter#Router.Handler)s and [http.HandlerFunc](http://godoc.org/github.com/julienschmidt/httprouter#Router.HandlerFunc)s
+which allows them to be used as a [httprouter.Handle](http://godoc.org/github.com/julienschmidt/httprouter#Router.Handle) when registering a route.
+The only disadvantage is, that no parameter values can be retrieved when a
+http.Handler or http.HandlerFunc is used, since there is no efficient way to
+pass the values with the existing function parameters.
+Therefore [httprouter.Handle](http://godoc.org/github.com/julienschmidt/httprouter#Router.Handle) has a third function parameter.
+
+Just try it out for yourself, the usage of HttpRouter is very straightforward. The package is compact and minimalistic, but also probably one of the easiest routers to set up.
+
+
+## Where can I find Middleware *X*?
+This package just provides a very efficient request router with a few extra
+features. The router is just a [http.Handler](http://golang.org/pkg/net/http/#Handler),
+you can chain any http.Handler compatible middleware before the router,
+for example the [Gorilla handlers](http://www.gorillatoolkit.org/pkg/handlers).
+Or you could [just write your own](http://justinas.org/writing-http-middleware-in-go/),
+it's very easy!
+
+Alternatively, you could try [a framework building upon HttpRouter](#web-frameworks-building-upon-httprouter).
+
+Here is a quick example: Does your server serve multiple domains / hosts?
+You want to use sub-domains?
 Define a router per host!
 ```go
 // We need an object that implements the http.Handler interface.
@@ -147,7 +213,7 @@
 }
 
 func main() {
-	// Initialze a router as usual 
+	// Initialize a router as usual
 	router := httprouter.New()
 	router.GET("/", Index)
 	router.GET("/hello/:name", Hello)
@@ -161,3 +227,8 @@
 	log.Fatal(http.ListenAndServe(":12345", hs))
 }
 ```
+
+## Web Frameworks building upon HttpRouter
+If the HttpRouter is a bit too minimalistic for you, you might try one of the following more high-level 3rd-party web frameworks building upon the HttpRouter package:
+* [Gin](https://github.com/gin-gonic/gin): Features a martini-like API with much better performance
+* [Hikaru](https://github.com/najeira/hikaru): Supports standalone and Google AppEngine
diff --git a/path.go b/path.go
index b040085..486134d 100644
--- a/path.go
+++ b/path.go
@@ -19,6 +19,7 @@
 //
 // If the result of this process is an empty string, "/" is returned
 func CleanPath(p string) string {
+	// Turn empty string into "/"
 	if p == "" {
 		return "/"
 	}
@@ -102,11 +103,6 @@
 		w++
 	}
 
-	// Turn empty string into "/"
-	if w == 0 {
-		return "/"
-	}
-
 	if buf == nil {
 		return p[:w]
 	}
diff --git a/router.go b/router.go
index 1c31bdb..a6be43c 100644
--- a/router.go
+++ b/router.go
@@ -15,12 +15,12 @@
 //      "log"
 //  )
 //
-//  func Index(w http.ResponseWriter, r *http.Request, _ map[string]string) {
+//  func Index(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
 //      fmt.Fprint(w, "Welcome!\n")
 //  }
 //
-//  func Hello(w http.ResponseWriter, r *http.Request, vars map[string]string) {
-//      fmt.Fprintf(w, "hello, %s!\n", vars["name"])
+//  func Hello(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
+//      fmt.Fprintf(w, "hello, %s!\n", ps.ByName("name"))
 //  }
 //
 //  func main() {
@@ -28,7 +28,7 @@
 //      router.GET("/", Index)
 //      router.GET("/hello/:name", Hello)
 //
-//      log.Fatal(http.ListenAndServe(":12345", router))
+//      log.Fatal(http.ListenAndServe(":8080", router))
 //  }
 //
 // The router matches incoming requests by the request method and the path.
@@ -38,15 +38,13 @@
 // register handles, for all other methods router.Handle can be used.
 //
 // The registered path, against which the router matches incoming requests, can
-// contain two types of wildcards:
+// contain two types of parameters:
 //  Syntax    Type
-//  :name     Parameter
-//  *name     CatchAll
-// The value of wildcards is saved in a map as vars["name"] = value. The map is
-// passed to the Handle func as a parameter.
+//  :name     named parameter
+//  *name     catch-all parameter
 //
-// Parameters are variable path segments. They match anything until the next '/'
-// or the path end:
+// Named parameters are dynamic path segments. They match anything until the
+// next '/' or the path end:
 //  Path: /blog/:category/:post
 //
 //  Requests:
@@ -55,9 +53,9 @@
 //   /blog/go/                           no match
 //   /blog/go/request-routers/comments   no match
 //
-// CatchAll wildcards match anything until the path end, including the directory
-// index (the '/'' before the CatchAll). Since they match anything until the end,
-// CatchAll wildcards must always be the final path element.
+// Catch-all parameters match anything until the path end, including the
+// directory index (the '/' before the catch-all). Since they match anything
+// until the end, catch-all paramerters must always be the final path element.
 //  Path: /files/*filepath
 //
 //  Requests:
@@ -66,6 +64,16 @@
 //   /files/templates/article.html       match: filepath="/templates/article.html"
 //   /files                              no match, but the router would redirect
 //
+// The value of parameters is saved as a slice of the Param struct, consisting
+// each of a key and a value. The slice is passed to the Handle func as a third
+// parameter.
+// There are two ways to retrieve the value of a parameter:
+//  // by the name of the parameter
+//  user := ps.ByName("user") // defined by :user or *user
+//
+//  // by the index of the parameter. This way you can also get the name (key)
+//  thirdKey   := ps[2].Key   // the name of the 3rd parameter
+//  thirdValue := ps[2].Value // the value of the 3rd parameter
 package httprouter
 
 import (
@@ -75,42 +83,60 @@
 // Handle is a function that can be registered to a route to handle HTTP
 // requests. Like http.HandlerFunc, but has a third parameter for the values of
 // wildcards (variables).
-type Handle func(http.ResponseWriter, *http.Request, map[string]string)
+type Handle func(http.ResponseWriter, *http.Request, Params)
 
-// NotFound is the default HTTP handler func for routes that can't be matched
-// with an existing route.
-// NotFound tries to redirect to a canonical URL generated with CleanPath.
-// Otherwise the request is delegated to http.NotFound.
-func NotFound(w http.ResponseWriter, req *http.Request) {
-	if req.Method != "CONNECT" {
-		path := req.URL.Path
-		if cp := CleanPath(path); cp != path && cp != req.Referer() {
-			http.Redirect(w, req, cp, http.StatusMovedPermanently)
-			return
+// Param is a single URL parameter, consisting of a key and a value.
+type Param struct {
+	Key   string
+	Value string
+}
+
+// Params is a Param-slice, as returned by the router.
+// The slice is ordered, the first URL parameter is also the first slice value.
+// It is therefore safe to read values by the index.
+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
 		}
 	}
-
-	http.NotFound(w, req)
+	return ""
 }
 
 // Router is a http.Handler which can be used to dispatch requests to different
 // handler functions via configurable routes
 type Router struct {
-	node
+	trees map[string]*node
 
-	// Enables automatic redirection if the current route can't be matched but
+	// Enables automatic redirection if the current route can't be matched but a
 	// handler for the path with (without) the trailing slash exists.
 	// For example if /foo/ is requested but a route only exists for /foo, the
-	// client is redirected to /foo with http status code 301.
+	// client is redirected to /foo with http status code 301 for GET requests
+	// and 307 for all other request methods.
 	RedirectTrailingSlash bool
 
-	// Configurable handler func which is used when no matching route is found.
-	// Default is the NotFound func of this package.
+	// If enabled, the router tries to fix the current request path, if no
+	// handle is registered for it.
+	// First superfluous path elements like ../ or // are removed.
+	// Afterwards the router does a case-insensitive lookup of the cleaned path.
+	// If a handle can be found for this route, the router makes a  redirection
+	// to the corrected path with status code 301 for GET requests and 307 for
+	// all other request methods.
+	// For example /FOO and /..//Foo could be redirected to /foo.
+	// RedirectTrailingSlash is independent of this option.
+	RedirectFixedPath bool
+
+	// Configurable http.HandlerFunc which is called when no matching route is
+	// found. If it is not set, http.NotFound is used.
 	NotFound http.HandlerFunc
 
-	// Handler func to handle panics recovered from http handlers.
+	// Function to handle panics recovered from http handlers.
 	// It should be used to generate a error page and return the http error code
-	// "500 - Internal Server Error".
+	// 500 (Internal Server Error).
 	// The handler can be used to keep your server from crashing because of
 	// unrecovered panics.
 	PanicHandler func(http.ResponseWriter, *http.Request, interface{})
@@ -119,13 +145,12 @@
 // Make sure the Router conforms with the http.Handler interface
 var _ http.Handler = New()
 
-// New returnes a new initialized Router.
-// The router can be configured to also match the requested HTTP method or the
-// requested Host.
+// New returns a new initialized Router.
+// Path auto-correction, including trailing slashes, is enabled by default.
 func New() *Router {
 	return &Router{
 		RedirectTrailingSlash: true,
-		NotFound:              NotFound,
+		RedirectFixedPath:     true,
 	}
 }
 
@@ -171,14 +196,35 @@
 	if path[0] != '/' {
 		panic("path must begin with '/'")
 	}
-	r.addRoute(method, path, handle)
+
+	if r.trees == nil {
+		r.trees = make(map[string]*node)
+	}
+
+	root := r.trees[method]
+	if root == nil {
+		root = new(node)
+		r.trees[method] = root
+	}
+
+	root.addRoute(path, handle)
 }
 
-// HandlerFunc is an adapter which allows the usage of a http.HandlerFunc as a
+// Handler is an adapter which allows the usage of an http.Handler as a
+// request handle.
+func (r *Router) Handler(method, path string, handler http.Handler) {
+	r.Handle(method, path,
+		func(w http.ResponseWriter, req *http.Request, _ Params) {
+			handler.ServeHTTP(w, req)
+		},
+	)
+}
+
+// HandlerFunc is an adapter which allows the usage of an http.HandlerFunc as a
 // request handle.
 func (r *Router) HandlerFunc(method, path string, handler http.HandlerFunc) {
 	r.Handle(method, path,
-		func(w http.ResponseWriter, req *http.Request, _ map[string]string) {
+		func(w http.ResponseWriter, req *http.Request, _ Params) {
 			handler(w, req)
 		},
 	)
@@ -201,8 +247,8 @@
 
 	fileServer := http.FileServer(root)
 
-	r.GET(path, func(w http.ResponseWriter, req *http.Request, vars map[string]string) {
-		req.URL.Path = vars["filepath"]
+	r.GET(path, func(w http.ResponseWriter, req *http.Request, ps Params) {
+		req.URL.Path = ps.ByName("filepath")
 		fileServer.ServeHTTP(w, req)
 	})
 }
@@ -213,29 +259,64 @@
 	}
 }
 
-// Make the router implement the http.Handler interface.
+// Lookup allows the manual lookup of a method + path combo.
+// This is e.g. useful to build a framework around this router.
+func (r *Router) Lookup(method, path string) (Handle, Params, bool) {
+	if root := r.trees[method]; root != nil {
+		return root.getValue(path)
+	}
+	return nil, nil, false
+}
+
+// ServeHTTP makes the router implement the http.Handler interface.
 func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
 	if r.PanicHandler != nil {
 		defer r.recv(w, req)
 	}
 
-	path := req.URL.Path
+	if root := r.trees[req.Method]; root != nil {
+		path := req.URL.Path
 
-	if handle, vars, tsr := r.getValue(req.Method, path); handle != nil {
-		handle(w, req, vars)
-	} else if tsr && r.RedirectTrailingSlash && path != "/" {
-		if path[len(path)-1] == '/' {
-			path = path[:len(path)-1]
-		} else {
-			path = path + "/"
+		if handle, ps, tsr := root.getValue(path); handle != nil {
+			handle(w, req, ps)
+			return
+		} else if req.Method != "CONNECT" && path != "/" {
+			code := 301 // Permanent redirect, request with GET method
+			if req.Method != "GET" {
+				// Temporary redirect, request with same method
+				// As of Go 1.3, Go does not support status code 308.
+				code = 307
+			}
+
+			if tsr && r.RedirectTrailingSlash {
+				if path[len(path)-1] == '/' {
+					req.URL.Path = path[:len(path)-1]
+				} else {
+					req.URL.Path = path + "/"
+				}
+				http.Redirect(w, req, req.URL.String(), code)
+				return
+			}
+
+			// Try to fix the request path
+			if r.RedirectFixedPath {
+				fixedPath, found := root.findCaseInsensitivePath(
+					CleanPath(path),
+					r.RedirectTrailingSlash,
+				)
+				if found {
+					req.URL.Path = string(fixedPath)
+					http.Redirect(w, req, req.URL.String(), code)
+					return
+				}
+			}
 		}
-		http.Redirect(w, req, path, http.StatusMovedPermanently)
-		return
-	} else { // Handle 404
-		if r.NotFound != nil {
-			r.NotFound(w, req)
-		} else {
-			http.NotFound(w, req)
-		}
+	}
+
+	// Handle 404
+	if r.NotFound != nil {
+		r.NotFound(w, req)
+	} else {
+		http.NotFound(w, req)
 	}
 }
diff --git a/router_test.go b/router_test.go
index bb8aa02..08dab29 100644
--- a/router_test.go
+++ b/router_test.go
@@ -29,15 +29,31 @@
 
 func (m *mockResponseWriter) WriteHeader(int) {}
 
+func TestParams(t *testing.T) {
+	ps := Params{
+		Param{"param1", "value1"},
+		Param{"param2", "value2"},
+		Param{"param3", "value3"},
+	}
+	for i := range ps {
+		if val := ps.ByName(ps[i].Key); val != ps[i].Value {
+			t.Errorf("Wrong value for %s: Got %s; Want %s", ps[i].Key, val, ps[i].Value)
+		}
+	}
+	if val := ps.ByName("noKey"); val != "" {
+		t.Errorf("Expected empty string for not found key; got: %s", val)
+	}
+}
+
 func TestRouter(t *testing.T) {
 	router := New()
 
 	routed := false
-	router.Handle("GET", "/user/:name", func(w http.ResponseWriter, r *http.Request, vars map[string]string) {
+	router.Handle("GET", "/user/:name", func(w http.ResponseWriter, r *http.Request, ps Params) {
 		routed = true
-		want := map[string]string{"name": "gopher"}
-		if !reflect.DeepEqual(vars, want) {
-			t.Fatalf("wrong wildcard values: want %v, got %v", want, vars)
+		want := Params{Param{"name", "gopher"}}
+		if !reflect.DeepEqual(ps, want) {
+			t.Fatalf("wrong wildcard values: want %v, got %v", want, ps)
 		}
 	})
 
@@ -51,28 +67,39 @@
 	}
 }
 
+type handlerStruct struct {
+	handeled *bool
+}
+
+func (h handlerStruct) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+	*h.handeled = true
+}
+
 func TestRouterAPI(t *testing.T) {
-	var get, head, post, put, patch, delete, handlerFunc bool
+	var get, head, post, put, patch, delete, handler, handlerFunc bool
+
+	httpHandler := handlerStruct{&handler}
 
 	router := New()
-	router.GET("/GET", func(w http.ResponseWriter, r *http.Request, _ map[string]string) {
+	router.GET("/GET", func(w http.ResponseWriter, r *http.Request, _ Params) {
 		get = true
 	})
-	router.HEAD("/GET", func(w http.ResponseWriter, r *http.Request, _ map[string]string) {
+	router.HEAD("/GET", func(w http.ResponseWriter, r *http.Request, _ Params) {
 		head = true
 	})
-	router.POST("/POST", func(w http.ResponseWriter, r *http.Request, _ map[string]string) {
+	router.POST("/POST", func(w http.ResponseWriter, r *http.Request, _ Params) {
 		post = true
 	})
-	router.PUT("/PUT", func(w http.ResponseWriter, r *http.Request, _ map[string]string) {
+	router.PUT("/PUT", func(w http.ResponseWriter, r *http.Request, _ Params) {
 		put = true
 	})
-	router.PATCH("/PATCH", func(w http.ResponseWriter, r *http.Request, _ map[string]string) {
+	router.PATCH("/PATCH", func(w http.ResponseWriter, r *http.Request, _ Params) {
 		patch = true
 	})
-	router.DELETE("/DELETE", func(w http.ResponseWriter, r *http.Request, _ map[string]string) {
+	router.DELETE("/DELETE", func(w http.ResponseWriter, r *http.Request, _ Params) {
 		delete = true
 	})
+	router.Handler("GET", "/Handler", httpHandler)
 	router.HandlerFunc("GET", "/HandlerFunc", func(w http.ResponseWriter, r *http.Request) {
 		handlerFunc = true
 	})
@@ -115,6 +142,12 @@
 		t.Error("routing DELETE failed")
 	}
 
+	r, _ = http.NewRequest("GET", "/Handler", nil)
+	router.ServeHTTP(w, r)
+	if !handler {
+		t.Error("routing Handler failed")
+	}
+
 	r, _ = http.NewRequest("GET", "/HandlerFunc", nil)
 	router.ServeHTTP(w, r)
 	if !handlerFunc {
@@ -133,26 +166,27 @@
 }
 
 func TestRouterNotFound(t *testing.T) {
-	handlerFunc := func(_ http.ResponseWriter, _ *http.Request, _ map[string]string) {}
+	handlerFunc := func(_ http.ResponseWriter, _ *http.Request, _ Params) {}
 
 	router := New()
 	router.GET("/path", handlerFunc)
 	router.GET("/dir/", handlerFunc)
 
 	testRoutes := []struct {
-		route   string
-		handler http.HandlerFunc
-		code    int
-		header  string
+		route  string
+		code   int
+		header string
 	}{
-		{"/path/", NotFound, 301, "map[Location:[/path]]"},   // TSR -/
-		{"/dir", NotFound, 301, "map[Location:[/dir/]]"},     // TSR +/
-		{"/../path", NotFound, 301, "map[Location:[/path]]"}, // CleanPath
-		{"/nope", NotFound, 404, ""},                         // NotFound
-		{"/nope", nil, 404, ""},                              // NotFound
+		{"/path/", 301, "map[Location:[/path]]"},   // TSR -/
+		{"/dir", 301, "map[Location:[/dir/]]"},     // TSR +/
+		{"/PATH", 301, "map[Location:[/path]]"},    // Fixed Case
+		{"/DIR/", 301, "map[Location:[/dir/]]"},    // Fixed Case
+		{"/PATH/", 301, "map[Location:[/path]]"},   // Fixed Case -/
+		{"/DIR", 301, "map[Location:[/dir/]]"},     // Fixed Case +/
+		{"/../path", 301, "map[Location:[/path]]"}, // CleanPath
+		{"/nope", 404, ""},                         // NotFound
 	}
 	for _, tr := range testRoutes {
-		router.NotFound = tr.handler
 		r, _ := http.NewRequest("GET", tr.route, nil)
 		w := httptest.NewRecorder()
 		router.ServeHTTP(w, r)
@@ -160,6 +194,38 @@
 			t.Errorf("NotFound handling route %s failed: Code=%d, Header=%v", tr.route, w.Code, w.Header())
 		}
 	}
+
+	// Test custom not found handler
+	var notFound bool
+	router.NotFound = func(rw http.ResponseWriter, r *http.Request) {
+		rw.WriteHeader(404)
+		notFound = true
+	}
+	r, _ := http.NewRequest("GET", "/nope", nil)
+	w := httptest.NewRecorder()
+	router.ServeHTTP(w, r)
+	if !(w.Code == 404 && notFound == true) {
+		t.Errorf("Custom NotFound handler failed: Code=%d, Header=%v", w.Code, w.Header())
+	}
+
+	// Test other method than GET (want 307 instead of 301)
+	router.PATCH("/path", handlerFunc)
+	r, _ = http.NewRequest("PATCH", "/path/", nil)
+	w = httptest.NewRecorder()
+	router.ServeHTTP(w, r)
+	if !(w.Code == 307 && fmt.Sprint(w.Header()) == "map[Location:[/path]]") {
+		t.Errorf("Custom NotFound handler failed: Code=%d, Header=%v", w.Code, w.Header())
+	}
+
+	// Test special case where no node for the prefix "/" exists
+	router = New()
+	router.GET("/a", handlerFunc)
+	r, _ = http.NewRequest("GET", "/", nil)
+	w = httptest.NewRecorder()
+	router.ServeHTTP(w, r)
+	if !(w.Code == 404) {
+		t.Errorf("NotFound handling route / failed: Code=%d", w.Code)
+	}
 }
 
 func TestRouterPanicHandler(t *testing.T) {
@@ -170,7 +236,7 @@
 		panicHandled = true
 	}
 
-	router.Handle("PUT", "/user/:name", func(_ http.ResponseWriter, _ *http.Request, _ map[string]string) {
+	router.Handle("PUT", "/user/:name", func(_ http.ResponseWriter, _ *http.Request, _ Params) {
 		panic("oops!")
 	})
 
@@ -190,6 +256,58 @@
 	}
 }
 
+func TestRouterLookup(t *testing.T) {
+	routed := false
+	wantHandle := func(_ http.ResponseWriter, _ *http.Request, _ Params) {
+		routed = true
+	}
+	wantParams := Params{Param{"name", "gopher"}}
+
+	router := New()
+
+	// try empty router first
+	handle, _, tsr := router.Lookup("GET", "/nope")
+	if handle != nil {
+		t.Fatalf("Got handle for unregistered pattern: %v", handle)
+	}
+	if tsr {
+		t.Error("Got wrong TSR recommendation!")
+	}
+
+	// insert route and try again
+	router.GET("/user/:name", wantHandle)
+
+	handle, params, tsr := router.Lookup("GET", "/user/gopher")
+	if handle == nil {
+		t.Fatal("Got no handle!")
+	} else {
+		handle(nil, nil, nil)
+		if !routed {
+			t.Fatal("Routing failed!")
+		}
+	}
+
+	if !reflect.DeepEqual(params, wantParams) {
+		t.Fatalf("Wrong parameter values: want %v, got %v", wantParams, params)
+	}
+
+	handle, _, tsr = router.Lookup("GET", "/user/gopher/")
+	if handle != nil {
+		t.Fatalf("Got handle for unregistered pattern: %v", handle)
+	}
+	if !tsr {
+		t.Error("Got no TSR recommendation!")
+	}
+
+	handle, _, tsr = router.Lookup("GET", "/nope")
+	if handle != nil {
+		t.Fatalf("Got handle for unregistered pattern: %v", handle)
+	}
+	if tsr {
+		t.Error("Got wrong TSR recommendation!")
+	}
+}
+
 type mockFileSystem struct {
 	opened bool
 }
diff --git a/tree.go b/tree.go
index 3509038..933b5cb 100644
--- a/tree.go
+++ b/tree.go
@@ -4,6 +4,11 @@
 
 package httprouter
 
+import (
+	"strings"
+	"unicode"
+)
+
 func min(a, b int) int {
 	if a <= b {
 		return a
@@ -11,6 +16,20 @@
 	return b
 }
 
+func countParams(path string) uint8 {
+	var n uint
+	for i := 0; i < len(path); i++ {
+		if path[i] != ':' && path[i] != '*' {
+			continue
+		}
+		n++
+	}
+	if n >= 255 {
+		return 255
+	}
+	return uint8(n)
+}
+
 type nodeType uint8
 
 const (
@@ -21,11 +40,12 @@
 
 type node struct {
 	path      string
-	indices   []byte
-	children  []*node
 	wildChild bool
 	nType     nodeType
-	handle    map[string]Handle
+	maxParams uint8
+	indices   []byte
+	children  []*node
+	handle    Handle
 	priority  uint32
 }
 
@@ -51,29 +71,45 @@
 
 // addRoute adds a node with the given handle to the path.
 // Not concurrency-safe!
-func (n *node) addRoute(method, path string, handle Handle) {
+func (n *node) addRoute(path string, handle Handle) {
 	n.priority++
+	numParams := countParams(path)
+
 	// non-empty tree
-	if len(n.path) != 0 {
-	OUTER:
+	if len(n.path) > 0 || len(n.children) > 0 {
+	WALK:
 		for {
+			// Update maxParams of the current node
+			if numParams > n.maxParams {
+				n.maxParams = numParams
+			}
+
 			// Find the longest common prefix.
 			// This also implies that the commom prefix contains no ':' or '*'
 			// since the existing key can't contain this chars.
 			i := 0
-			for j := min(len(path), len(n.path)); i < j && path[i] == n.path[i]; i++ {
+			for max := min(len(path), len(n.path)); i < max && path[i] == n.path[i]; i++ {
 			}
 
 			// Split edge
 			if i < len(n.path) {
-				n.children = []*node{&node{
+				child := node{
 					path:      n.path[i:],
+					wildChild: n.wildChild,
 					indices:   n.indices,
 					children:  n.children,
 					handle:    n.handle,
-					wildChild: n.wildChild,
 					priority:  n.priority - 1,
-				}}
+				}
+
+				// Update maxParams (max of all children)
+				for i := range child.children {
+					if child.children[i].maxParams > child.maxParams {
+						child.maxParams = child.children[i].maxParams
+					}
+				}
+
+				n.children = []*node{&child}
 				n.indices = []byte{n.path[i]}
 				n.path = path[:i]
 				n.handle = nil
@@ -88,11 +124,17 @@
 					n = n.children[0]
 					n.priority++
 
+					// Update maxParams of the child node
+					if numParams > n.maxParams {
+						n.maxParams = numParams
+					}
+					numParams--
+
 					// Check if the wildcard matches
 					if len(path) >= len(n.path) && n.path == path[:len(n.path)] {
 						// check for longer wildcard, e.g. :name and :names
 						if len(n.path) >= len(path) || path[len(n.path)] == '/' {
-							continue OUTER
+							continue WALK
 						}
 					}
 
@@ -101,11 +143,11 @@
 
 				c := path[0]
 
-				// param
+				// slash after param
 				if n.nType == param && c == '/' && len(n.children) == 1 {
 					n = n.children[0]
 					n.priority++
-					continue OUTER
+					continue WALK
 				}
 
 				// Check if a child with the next path byte exists
@@ -113,134 +155,138 @@
 					if c == index {
 						i = n.incrementChildPrio(i)
 						n = n.children[i]
-						continue OUTER
+						continue WALK
 					}
 				}
 
 				// Otherwise insert it
 				if c != ':' && c != '*' {
 					n.indices = append(n.indices, c)
-					child := &node{}
+					child := &node{
+						maxParams: numParams,
+					}
 					n.children = append(n.children, child)
-
 					n.incrementChildPrio(len(n.indices) - 1)
 					n = child
 				}
-				n.insertChild(method, path, handle)
+				n.insertChild(numParams, path, handle)
 				return
 
 			} else if i == len(path) { // Make node a (in-path) leaf
-				if n.handle == nil {
-					n.handle = map[string]Handle{
-						method: handle,
-					}
-				} else {
-					if n.handle[method] != nil {
-						panic("a Handle is already registered for this method at this path")
-					}
-					n.handle[method] = handle
+				if n.handle != nil {
+					panic("a Handle is already registered for this path")
 				}
+				n.handle = handle
 			}
 			return
 		}
 	} else { // Empty tree
-		n.insertChild(method, path, handle)
+		n.insertChild(numParams, path, handle)
 	}
 }
 
-func (n *node) insertChild(method, path string, handle Handle) {
+func (n *node) insertChild(numParams uint8, path string, handle Handle) {
 	var offset int
 
 	// find prefix until first wildcard (beginning with ':'' or '*'')
-	for i, j := 0, len(path); i < j; i++ {
-		if b := path[i]; b == ':' || b == '*' {
-			// Check if this Node existing children which would be
-			// unreachable if we insert the wildcard here
-			if len(n.children) > 0 {
-				panic("wildcard route conflicts with existing children")
-			}
+	for i, max := 0, len(path); numParams > 0; i++ {
+		c := path[i]
+		if c != ':' && c != '*' {
+			continue
+		}
 
-			// find wildcard end (either '/'' or path end)
-			k := i + 1
-			for k < j && path[k] != '/' {
-				k++
-			}
+		// Check if this Node existing children which would be
+		// unreachable if we insert the wildcard here
+		if len(n.children) > 0 {
+			panic("wildcard route conflicts with existing children")
+		}
 
-			if k-i == 1 {
-				panic("wildcards must be named with a non-empty name")
-			}
+		// find wildcard end (either '/' or path end)
+		end := i + 1
+		for end < max && path[end] != '/' {
+			end++
+		}
 
-			if b == ':' { // param
-				// split path at the beginning of the wildcard
-				if i > 0 {
-					n.path = path[offset:i]
-					offset = i
-				}
+		if end-i < 2 {
+			panic("wildcards must be named with a non-empty name")
+		}
 
-				child := &node{
-					nType: param,
-				}
-				n.children = []*node{child}
-				n.wildChild = true
-				n = child
-				n.priority++
-
-				// if the path doesn't end with the wildcard, then there will be
-				// another non-wildcard subpath starting with '/'
-				if k < j {
-					n.path = path[offset:k]
-					offset = k
-
-					child := &node{}
-					n.children = []*node{child}
-					n = child
-					n.priority++
-				}
-
-			} else { // catchAll
-				if len(path) != k {
-					panic("catchAlls are only allowed at the end of the path")
-				}
-
-				// currently fixed width 1 for '/'
-				i--
-				if path[i] != '/' {
-					panic("no / before catchAll")
-				}
-
+		if c == ':' { // param
+			// split path at the beginning of the wildcard
+			if i > 0 {
 				n.path = path[offset:i]
-
-				// first node: catchAll node with empty path
-				child := &node{
-					wildChild: true,
-					nType:     catchAll,
-				}
-				n.children = []*node{child}
-				n.indices = []byte{path[i]}
-				n = child
-				n.priority++
-
-				// second node: node holding the variable
-				child = &node{
-					path: path[i:],
-					handle: map[string]Handle{
-						method: handle,
-					},
-					nType:    catchAll,
-					priority: 1,
-				}
-				n.children = []*node{child}
-
-				return
+				offset = i
 			}
+
+			child := &node{
+				nType:     param,
+				maxParams: numParams,
+			}
+			n.children = []*node{child}
+			n.wildChild = true
+			n = child
+			n.priority++
+			numParams--
+
+			// if the path doesn't end with the wildcard, then there
+			// will be another non-wildcard subpath starting with '/'
+			if end < max {
+				n.path = path[offset:end]
+				offset = end
+
+				child := &node{
+					maxParams: numParams,
+					priority:  1,
+				}
+				n.children = []*node{child}
+				n = child
+			}
+
+		} else { // catchAll
+			if end != max || numParams > 1 {
+				panic("catch-all routes are only allowed at the end of the path")
+			}
+
+			if len(n.path) > 0 && n.path[len(n.path)-1] == '/' {
+				panic("catch-all conflicts with existing handle for the path segment root")
+			}
+
+			// currently fixed width 1 for '/'
+			i--
+			if path[i] != '/' {
+				panic("no / before catch-all")
+			}
+
+			n.path = path[offset:i]
+
+			// first node: catchAll node with empty path
+			child := &node{
+				wildChild: true,
+				nType:     catchAll,
+				maxParams: 1,
+			}
+			n.children = []*node{child}
+			n.indices = []byte{path[i]}
+			n = child
+			n.priority++
+
+			// second node: node holding the variable
+			child = &node{
+				path:      path[i:],
+				nType:     catchAll,
+				maxParams: 1,
+				handle:    handle,
+				priority:  1,
+			}
+			n.children = []*node{child}
+
+			return
 		}
 	}
 
 	// insert remaining path part and handle to the leaf
 	n.path = path[offset:]
-	n.handle = map[string]Handle{
-		method: handle,
-	}
+	n.handle = handle
 }
 
 // Returns the handle registered with the given path (key). The values of
@@ -248,15 +294,98 @@
 // If no handle can be found, a TSR (trailing slash redirect) recommendation is
 // made if a handle exists with an extra (without the) trailing slash for the
 // given path.
-func (n *node) getValue(method, path string) (handle Handle, vars map[string]string, tsr bool) {
-	// Walk tree nodes
-OUTER:
-	for len(path) >= len(n.path) && path[:len(n.path)] == n.path {
-		path = path[len(n.path):]
+func (n *node) getValue(path string) (handle Handle, p Params, tsr bool) {
+walk: // Outer loop for walking the tree
+	for {
+		if len(path) > len(n.path) {
+			if path[:len(n.path)] == n.path {
+				path = path[len(n.path):]
+				// If this node does not have a wildcard (param or catchAll)
+				// child,  we can just look up the next child node and continue
+				// to walk down the tree
+				if !n.wildChild {
+					c := path[0]
+					for i, index := range n.indices {
+						if c == index {
+							n = n.children[i]
+							continue walk
+						}
+					}
 
-		if len(path) == 0 {
-			// Check if this node has a handle registered for the given node
-			if handle = n.handle[method]; handle != nil {
+					// Nothing found.
+					// We can recommend to redirect to the same URL without a
+					// trailing slash if a leaf exists for that path.
+					tsr = (path == "/" && n.handle != nil)
+					return
+
+				}
+
+				// handle wildcard child
+				n = n.children[0]
+				switch n.nType {
+				case param:
+					// find param end (either '/' or path end)
+					end := 0
+					for end < len(path) && path[end] != '/' {
+						end++
+					}
+
+					// save param value
+					if p == nil {
+						// lazy allocation
+						p = make(Params, 0, n.maxParams)
+					}
+					i := len(p)
+					p = p[:i+1] // expand slice within preallocated capacity
+					p[i].Key = n.path[1:]
+					p[i].Value = path[:end]
+
+					// we need to go deeper!
+					if end < len(path) {
+						if len(n.children) > 0 {
+							path = path[end:]
+							n = n.children[0]
+							continue walk
+						}
+
+						// ... but we can't
+						tsr = (len(path) == end+1)
+						return
+					}
+
+					if handle = n.handle; handle != nil {
+						return
+					} else if len(n.children) == 1 {
+						// No handle found. Check if a handle for this path + a
+						// trailing slash exists for TSR recommendation
+						n = n.children[0]
+						tsr = (n.path == "/" && n.handle != nil)
+					}
+
+					return
+
+				case catchAll:
+					// save param value
+					if p == nil {
+						// lazy allocation
+						p = make(Params, 0, n.maxParams)
+					}
+					i := len(p)
+					p = p[:i+1] // expand slice within preallocated capacity
+					p[i].Key = n.path[2:]
+					p[i].Value = path
+
+					handle = n.handle
+					return
+
+				default:
+					panic("Unknown node type")
+				}
+			}
+		} else if path == n.path {
+			// We should have reached the node containing the handle.
+			// Check if this node has a handle registered.
+			if handle = n.handle; handle != nil {
 				return
 			}
 
@@ -265,99 +394,141 @@
 			for i, index := range n.indices {
 				if index == '/' {
 					n = n.children[i]
-					tsr = (n.path == "/" && n.handle[method] != nil) ||
-						(n.nType == catchAll && n.children[0].handle[method] != nil)
+					tsr = (n.path == "/" && n.handle != nil) ||
+						(n.nType == catchAll && n.children[0].handle != nil)
 					return
 				}
 			}
 
-			// TODO: handle HTTP Error 405 - Method Not Allowed
-			// Return available methods
-
 			return
+		}
 
-		} else if n.wildChild {
-			n = n.children[0]
+		// Nothing found. We can recommend to redirect to the same URL with an
+		// extra trailing slash if a leaf exists for that path
+		tsr = (path == "/") ||
+			(len(n.path) == len(path)+1 && n.path[len(path)] == '/' &&
+				path == n.path[:len(n.path)-1] && n.handle != nil)
+		return
+	}
+}
 
-			switch n.nType {
-			case param:
-				// find param end (either '/'' or path end)
-				k := 0
-				for k < len(path) && path[k] != '/' {
-					k++
-				}
+// Makes a case-insensitive lookup of the given path and tries to find a handler.
+// It can optionally also fix trailing slashes.
+// It returns the case-corrected path and a bool indicating wether the lookup
+// was successful.
+func (n *node) findCaseInsensitivePath(path string, fixTrailingSlash bool) (ciPath []byte, found bool) {
+	ciPath = make([]byte, 0, len(path)+1) // preallocate enough memory
 
-				// save param value
-				if vars == nil {
-					vars = map[string]string{
-						n.path[1:]: path[:k],
+	// Outer loop for walking the tree
+	for len(path) >= len(n.path) && strings.ToLower(path[:len(n.path)]) == strings.ToLower(n.path) {
+		path = path[len(n.path):]
+		ciPath = append(ciPath, n.path...)
+
+		if len(path) > 0 {
+			// If this node does not have a wildcard (param or catchAll) child,
+			// we can just look up the next child node and continue to walk down
+			// the tree
+			if !n.wildChild {
+				r := unicode.ToLower(rune(path[0]))
+				for i, index := range n.indices {
+					// must use recursive approach since both index and
+					// ToLower(index) could exist. We must check both.
+					if r == unicode.ToLower(rune(index)) {
+						out, found := n.children[i].findCaseInsensitivePath(path, fixTrailingSlash)
+						if found {
+							return append(ciPath, out...), true
+						}
 					}
-				} else {
-					vars[n.path[1:]] = path[:k]
 				}
 
-				// we need to go deeper!
-				if k < len(path) {
-					if len(n.children) > 0 {
-						path = path[k:]
+				// Nothing found. We can recommend to redirect to the same URL
+				// without a trailing slash if a leaf exists for that path
+				found = (fixTrailingSlash && path == "/" && n.handle != nil)
+				return
+
+			} else {
+				n = n.children[0]
+
+				switch n.nType {
+				case param:
+					// find param end (either '/' or path end)
+					k := 0
+					for k < len(path) && path[k] != '/' {
+						k++
+					}
+
+					// add param value to case insensitive path
+					ciPath = append(ciPath, path[:k]...)
+
+					// we need to go deeper!
+					if k < len(path) {
+						if len(n.children) > 0 {
+							path = path[k:]
+							n = n.children[0]
+							continue
+						} else { // ... but we can't
+							if fixTrailingSlash && len(path) == k+1 {
+								return ciPath, true
+							}
+							return
+						}
+					}
+
+					if n.handle != nil {
+						return ciPath, true
+					} else if fixTrailingSlash && len(n.children) == 1 {
+						// No handle found. Check if a handle for this path + a
+						// trailing slash exists
 						n = n.children[0]
-						continue
-					} else { // ... but we can't
-						tsr = (len(path) == k+1)
+						if n.path == "/" && n.handle != nil {
+							return append(ciPath, '/'), true
+						}
+					}
+					return
+
+				case catchAll:
+					return append(ciPath, path...), true
+
+				default:
+					panic("Unknown node type")
+				}
+			}
+		} else {
+			// We should have reached the node containing the handle.
+			// Check if this node has a handle registered.
+			if n.handle != nil {
+				return ciPath, true
+			}
+
+			// No handle found.
+			// Try to fix the path by adding a trailing slash
+			if fixTrailingSlash {
+				for i, index := range n.indices {
+					if index == '/' {
+						n = n.children[i]
+						if (n.path == "/" && n.handle != nil) ||
+							(n.nType == catchAll && n.children[0].handle != nil) {
+							return append(ciPath, '/'), true
+						}
 						return
 					}
 				}
-
-				if handle = n.handle[method]; handle != nil {
-					return
-				} else if len(n.children) == 1 {
-					// No handle found. Check if a handle for this path + a
-					// trailing slash exists for TSR recommendation
-					n = n.children[0]
-					tsr = (n.path == "/" && n.handle[method] != nil)
-				}
-
-				// TODO: handle HTTP Error 405 - Method Not Allowed
-				// Return available methods
-
-				return
-
-			case catchAll:
-				// save catchAll value
-				if vars == nil {
-					vars = map[string]string{
-						n.path[2:]: path,
-					}
-				} else {
-					vars[n.path[2:]] = path
-				}
-
-				handle = n.handle[method]
-				return
-
-			default:
-				panic("Unknown node type")
 			}
-
-		} else {
-			c := path[0]
-
-			for i, index := range n.indices {
-				if c == index {
-					n = n.children[i]
-					continue OUTER
-				}
-			}
-
-			// Nothing found. We can recommend to redirect to the same URL
-			// without a trailing slash if a leaf exists for that path
-			tsr = (path == "/" && n.handle[method] != nil)
 			return
 		}
 	}
 
-	// Nothing found. We can recommend to redirect to the same URL with an extra
-	// trailing slash if a leaf exists for that path
-	tsr = (len(path)+1 == len(n.path) && n.path[len(path)] == '/' && n.handle[method] != nil) || (path == "/")
+	// Nothing found.
+	// Try to fix the path by adding / removing a trailing slash
+	if fixTrailingSlash {
+		if path == "/" {
+			return ciPath, true
+		}
+		if len(path)+1 == len(n.path) && n.path[len(path)] == '/' &&
+			strings.ToLower(path) == strings.ToLower(n.path[:len(path)]) &&
+			n.handle != nil {
+			return append(ciPath, n.path...), true
+		}
+	}
 	return
 }
diff --git a/tree_test.go b/tree_test.go
index d30c074..cf4d170 100644
--- a/tree_test.go
+++ b/tree_test.go
@@ -8,11 +8,12 @@
 	"fmt"
 	"net/http"
 	"reflect"
+	"strings"
 	"testing"
 )
 
 func printChildren(n *node, prefix string) {
-	fmt.Printf(" %02d %s%s[%d] %v %t \r\n", n.priority, prefix, n.path, len(n.children), n.handle, n.wildChild)
+	fmt.Printf(" %02d:%02d %s%s[%d] %v %t %d \r\n", n.priority, n.maxParams, prefix, n.path, len(n.children), n.handle, n.wildChild, n.nType)
 	for l := len(n.path); l > 0; l-- {
 		prefix += " "
 	}
@@ -25,7 +26,7 @@
 var fakeHandlerValue string
 
 func fakeHandler(val string) Handle {
-	return func(http.ResponseWriter, *http.Request, map[string]string) {
+	return func(http.ResponseWriter, *http.Request, Params) {
 		fakeHandlerValue = val
 	}
 }
@@ -34,12 +35,12 @@
 	path       string
 	nilHandler bool
 	route      string
-	vars       map[string]string
+	ps         Params
 }
 
 func checkRequests(t *testing.T, tree *node, requests testRequests) {
 	for _, request := range requests {
-		handler, vars, _ := tree.getValue("GET", request.path)
+		handler, ps, _ := tree.getValue(request.path)
 
 		if handler == nil {
 			if !request.nilHandler {
@@ -54,8 +55,8 @@
 			}
 		}
 
-		if !reflect.DeepEqual(vars, request.vars) {
-			t.Errorf("vars mismatch for route '%s'", request.path)
+		if !reflect.DeepEqual(ps, request.ps) {
+			t.Errorf("Params mismatch for route '%s'", request.path)
 		}
 	}
 }
@@ -65,7 +66,10 @@
 	for i := range n.children {
 		prio += checkPriorities(t, n.children[i])
 	}
-	prio += uint32(len(n.handle))
+
+	if n.handle != nil {
+		prio++
+	}
 
 	if n.priority != prio {
 		t.Errorf(
@@ -77,6 +81,37 @@
 	return prio
 }
 
+func checkMaxParams(t *testing.T, n *node) uint8 {
+	var maxParams uint8
+	for i := range n.children {
+		params := checkMaxParams(t, n.children[i])
+		if params > maxParams {
+			maxParams = params
+		}
+	}
+	if n.nType != static && !n.wildChild {
+		maxParams++
+	}
+
+	if n.maxParams != maxParams {
+		t.Errorf(
+			"maxParams mismatch for node '%s': is %d, should be %d",
+			n.path, n.maxParams, maxParams,
+		)
+	}
+
+	return maxParams
+}
+
+func TestCountParams(t *testing.T) {
+	if countParams("/path/:param1/static/*catch-all") != 2 {
+		t.Fail()
+	}
+	if countParams(strings.Repeat("/:param", 256)) != 255 {
+		t.Fail()
+	}
+}
+
 func TestTreeAddAndGet(t *testing.T) {
 	tree := &node{}
 
@@ -92,7 +127,7 @@
 		"/doc/go1.html",
 	}
 	for _, route := range routes {
-		tree.addRoute("GET", route, fakeHandler(route))
+		tree.addRoute(route, fakeHandler(route))
 	}
 
 	//printChildren(tree, "")
@@ -110,6 +145,7 @@
 	})
 
 	checkPriorities(t, tree)
+	checkMaxParams(t, tree)
 }
 
 func TestTreeWildcard(t *testing.T) {
@@ -128,29 +164,34 @@
 		"/doc/",
 		"/doc/go_faq.html",
 		"/doc/go1.html",
+		"/info/:user/public",
+		"/info/:user/project/:project",
 	}
 	for _, route := range routes {
-		tree.addRoute("GET", route, fakeHandler(route))
+		tree.addRoute(route, fakeHandler(route))
 	}
 
 	//printChildren(tree, "")
 
 	checkRequests(t, tree, testRequests{
 		{"/", false, "/", nil},
-		{"/cmd/test/", false, "/cmd/:tool/", map[string]string{"tool": "test"}},
-		{"/cmd/test", true, "", map[string]string{"tool": "test"}},
-		{"/cmd/test/3", false, "/cmd/:tool/:sub", map[string]string{"tool": "test", "sub": "3"}},
-		{"/src/", false, "/src/*filepath", map[string]string{"filepath": "/"}},
-		{"/src/some/file.png", false, "/src/*filepath", map[string]string{"filepath": "/some/file.png"}},
+		{"/cmd/test/", false, "/cmd/:tool/", Params{Param{"tool", "test"}}},
+		{"/cmd/test", true, "", Params{Param{"tool", "test"}}},
+		{"/cmd/test/3", false, "/cmd/:tool/:sub", Params{Param{"tool", "test"}, Param{"sub", "3"}}},
+		{"/src/", false, "/src/*filepath", Params{Param{"filepath", "/"}}},
+		{"/src/some/file.png", false, "/src/*filepath", Params{Param{"filepath", "/some/file.png"}}},
 		{"/search/", false, "/search/", nil},
-		{"/search/someth!ng+in+ünìcodé", false, "/search/:query", map[string]string{"query": "someth!ng+in+ünìcodé"}},
-		{"/search/someth!ng+in+ünìcodé/", true, "", map[string]string{"query": "someth!ng+in+ünìcodé"}},
-		{"/user_gopher", false, "/user_:name", map[string]string{"name": "gopher"}},
-		{"/user_gopher/about", false, "/user_:name/about", map[string]string{"name": "gopher"}},
-		{"/files/js/inc/framework.js", false, "/files/:dir/*filepath", map[string]string{"dir": "js", "filepath": "/inc/framework.js"}},
+		{"/search/someth!ng+in+ünìcodé", false, "/search/:query", Params{Param{"query", "someth!ng+in+ünìcodé"}}},
+		{"/search/someth!ng+in+ünìcodé/", true, "", Params{Param{"query", "someth!ng+in+ünìcodé"}}},
+		{"/user_gopher", false, "/user_:name", Params{Param{"name", "gopher"}}},
+		{"/user_gopher/about", false, "/user_:name/about", Params{Param{"name", "gopher"}}},
+		{"/files/js/inc/framework.js", false, "/files/:dir/*filepath", Params{Param{"dir", "js"}, Param{"filepath", "/inc/framework.js"}}},
+		{"/info/gordon/public", false, "/info/:user/public", Params{Param{"user", "gordon"}}},
+		{"/info/gordon/project/go", false, "/info/:user/project/:project", Params{Param{"user", "gordon"}, Param{"project", "go"}}},
 	})
 
 	checkPriorities(t, tree)
+	checkMaxParams(t, tree)
 }
 
 func catchPanic(testFunc func()) (recv interface{}) {
@@ -172,7 +213,7 @@
 
 	for _, route := range routes {
 		recv := catchPanic(func() {
-			tree.addRoute("GET", route.path, nil)
+			tree.addRoute(route.path, nil)
 		})
 
 		if route.conflict {
@@ -236,7 +277,7 @@
 	}
 	for _, route := range routes {
 		recv := catchPanic(func() {
-			tree.addRoute("GET", route, fakeHandler(route))
+			tree.addRoute(route, fakeHandler(route))
 		})
 		if recv != nil {
 			t.Fatalf("panic inserting route '%s': %v", route, recv)
@@ -244,7 +285,7 @@
 
 		// Add again
 		recv = catchPanic(func() {
-			tree.addRoute("GET", route, nil)
+			tree.addRoute(route, nil)
 		})
 		if recv == nil {
 			t.Fatalf("no panic while inserting duplicate route '%s", route)
@@ -256,9 +297,9 @@
 	checkRequests(t, tree, testRequests{
 		{"/", false, "/", nil},
 		{"/doc/", false, "/doc/", nil},
-		{"/src/some/file.png", false, "/src/*filepath", map[string]string{"filepath": "/some/file.png"}},
-		{"/search/someth!ng+in+ünìcodé", false, "/search/:query", map[string]string{"query": "someth!ng+in+ünìcodé"}},
-		{"/user_gopher", false, "/user_:name", map[string]string{"name": "gopher"}},
+		{"/src/some/file.png", false, "/src/*filepath", Params{Param{"filepath", "/some/file.png"}}},
+		{"/search/someth!ng+in+ünìcodé", false, "/search/:query", Params{Param{"query", "someth!ng+in+ünìcodé"}}},
+		{"/user_gopher", false, "/user_:name", Params{Param{"name", "gopher"}}},
 	})
 }
 
@@ -273,7 +314,7 @@
 	}
 	for _, route := range routes {
 		recv := catchPanic(func() {
-			tree.addRoute("GET", route, nil)
+			tree.addRoute(route, nil)
 		})
 		if recv == nil {
 			t.Fatalf("no panic while inserting route with empty wildcard name '%s", route)
@@ -290,6 +331,14 @@
 	testRoutes(t, routes)
 }
 
+func TestTreeCatchAllConflictRoot(t *testing.T) {
+	routes := []testRoute{
+		{"/", false},
+		{"/*filepath", true},
+	}
+	testRoutes(t, routes)
+}
+
 /*func TestTreeDuplicateWildcard(t *testing.T) {
 	tree := &node{}
 
@@ -325,10 +374,11 @@
 		"/doc/go1.html",
 		"/no/a",
 		"/no/b",
+		"/api/hello/:name",
 	}
 	for _, route := range routes {
 		recv := catchPanic(func() {
-			tree.addRoute("GET", route, fakeHandler(route))
+			tree.addRoute(route, fakeHandler(route))
 		})
 		if recv != nil {
 			t.Fatalf("panic inserting route '%s': %v", route, recv)
@@ -351,7 +401,7 @@
 		"/doc/",
 	}
 	for _, route := range tsrRoutes {
-		handler, _, tsr := tree.getValue("GET", route)
+		handler, _, tsr := tree.getValue(route)
 		if handler != nil {
 			t.Fatalf("non-nil handler for TSR route '%s", route)
 		} else if !tsr {
@@ -365,9 +415,10 @@
 		"/no/",
 		"/_",
 		"/_/",
+		"/api/world/abc",
 	}
 	for _, route := range noTsrRoutes {
-		handler, _, tsr := tree.getValue("GET", route)
+		handler, _, tsr := tree.getValue(route)
 		if handler != nil {
 			t.Fatalf("non-nil handler for No-TSR route '%s", route)
 		} else if tsr {
@@ -375,3 +426,134 @@
 		}
 	}
 }
+
+func TestTreeFindCaseInsensitivePath(t *testing.T) {
+	tree := &node{}
+
+	routes := [...]string{
+		"/hi",
+		"/b/",
+		"/ABC/",
+		"/search/:query",
+		"/cmd/:tool/",
+		"/src/*filepath",
+		"/x",
+		"/x/y",
+		"/y/",
+		"/y/z",
+		"/0/:id",
+		"/0/:id/1",
+		"/1/:id/",
+		"/1/:id/2",
+		"/aa",
+		"/a/",
+		"/doc",
+		"/doc/go_faq.html",
+		"/doc/go1.html",
+		"/doc/go/away",
+		"/no/a",
+		"/no/b",
+	}
+
+	for _, route := range routes {
+		recv := catchPanic(func() {
+			tree.addRoute(route, fakeHandler(route))
+		})
+		if recv != nil {
+			t.Fatalf("panic inserting route '%s': %v", route, recv)
+		}
+	}
+
+	// Check out == in for all registered routes
+	// With fixTrailingSlash = true
+	for _, route := range routes {
+		out, found := tree.findCaseInsensitivePath(route, true)
+		if !found {
+			t.Errorf("Route '%s' not found!", route)
+		} else if string(out) != route {
+			t.Errorf("Wrong result for route '%s': %s", route, string(out))
+		}
+	}
+	// With fixTrailingSlash = false
+	for _, route := range routes {
+		out, found := tree.findCaseInsensitivePath(route, false)
+		if !found {
+			t.Errorf("Route '%s' not found!", route)
+		} else if string(out) != route {
+			t.Errorf("Wrong result for route '%s': %s", route, string(out))
+		}
+	}
+
+	tests := []struct {
+		in    string
+		out   string
+		found bool
+		slash bool
+	}{
+		{"/HI", "/hi", true, false},
+		{"/HI/", "/hi", true, true},
+		{"/B", "/b/", true, true},
+		{"/B/", "/b/", true, false},
+		{"/abc", "/ABC/", true, true},
+		{"/abc/", "/ABC/", true, false},
+		{"/aBc", "/ABC/", true, true},
+		{"/aBc/", "/ABC/", true, false},
+		{"/abC", "/ABC/", true, true},
+		{"/abC/", "/ABC/", true, false},
+		{"/SEARCH/QUERY", "/search/QUERY", true, false},
+		{"/SEARCH/QUERY/", "/search/QUERY", true, true},
+		{"/CMD/TOOL/", "/cmd/TOOL/", true, false},
+		{"/CMD/TOOL", "/cmd/TOOL/", true, true},
+		{"/SRC/FILE/PATH", "/src/FILE/PATH", true, false},
+		{"/x/Y", "/x/y", true, false},
+		{"/x/Y/", "/x/y", true, true},
+		{"/X/y", "/x/y", true, false},
+		{"/X/y/", "/x/y", true, true},
+		{"/X/Y", "/x/y", true, false},
+		{"/X/Y/", "/x/y", true, true},
+		{"/Y/", "/y/", true, false},
+		{"/Y", "/y/", true, true},
+		{"/Y/z", "/y/z", true, false},
+		{"/Y/z/", "/y/z", true, true},
+		{"/Y/Z", "/y/z", true, false},
+		{"/Y/Z/", "/y/z", true, true},
+		{"/y/Z", "/y/z", true, false},
+		{"/y/Z/", "/y/z", true, true},
+		{"/Aa", "/aa", true, false},
+		{"/Aa/", "/aa", true, true},
+		{"/AA", "/aa", true, false},
+		{"/AA/", "/aa", true, true},
+		{"/aA", "/aa", true, false},
+		{"/aA/", "/aa", true, true},
+		{"/A/", "/a/", true, false},
+		{"/A", "/a/", true, true},
+		{"/DOC", "/doc", true, false},
+		{"/DOC/", "/doc", true, true},
+		{"/NO", "", false, true},
+		{"/DOC/GO", "", false, true},
+	}
+	// With fixTrailingSlash = true
+	for _, test := range tests {
+		out, found := tree.findCaseInsensitivePath(test.in, true)
+		if found != test.found || (found && (string(out) != test.out)) {
+			t.Errorf("Wrong result for '%s': got %s, %t; want %s, %t",
+				test.in, string(out), found, test.out, test.found)
+			return
+		}
+	}
+	// With fixTrailingSlash = false
+	for _, test := range tests {
+		out, found := tree.findCaseInsensitivePath(test.in, false)
+		if test.slash {
+			if found { // test needs a trailingSlash fix. It must not be found!
+				t.Errorf("Found without fixTrailingSlash: %s; got %s", test.in, string(out))
+			}
+		} else {
+			if found != test.found || (found && (string(out) != test.out)) {
+				t.Errorf("Wrong result for '%s': got %s, %t; want %s, %t",
+					test.in, string(out), found, test.out, test.found)
+				return
+			}
+		}
+	}
+}