Codebase list golang-github-julienschmidt-httprouter / 9acf884
New upstream snapshot. Debian Janitor 2 years ago
9 changed file(s) with 794 addition(s) and 514 deletion(s). Raw diff Collapse all Expand all
88 - 1.12.x
99 - 1.13.x
1010 - master
11 matrix:
12 allow_failures:
13 - go: master
14 fast_finish: true
1115 before_install:
1216 - go get github.com/mattn/goveralls
1317 script:
155155 if r.Header.Get("Access-Control-Request-Method") != "" {
156156 // Set CORS headers
157157 header := w.Header()
158 header.Set("Access-Control-Allow-Methods", r.Header.Get("Allow"))
158 header.Set("Access-Control-Allow-Methods", header.Get("Allow"))
159159 header.Set("Access-Control-Allow-Origin", "*")
160160 }
161161
0 golang-github-julienschmidt-httprouter (1.3.0-2) UNRELEASED; urgency=medium
0 golang-github-julienschmidt-httprouter (1.3.0+git20200921.1.fe77dd0-1) UNRELEASED; urgency=medium
11
22 * Remove constraints unnecessary since stretch:
33 + Build-Depends: Drop versioned constraint on dh-golang.
4 * New upstream snapshot.
45
5 -- Debian Janitor <janitor@jelmer.uk> Tue, 15 Jun 2021 18:03:45 -0000
6 -- Debian Janitor <janitor@jelmer.uk> Sat, 02 Apr 2022 05:33:19 -0000
67
78 golang-github-julienschmidt-httprouter (1.3.0-1) unstable; urgency=medium
89
1818 //
1919 // If the result of this process is an empty string, "/" is returned
2020 func CleanPath(p string) string {
21 const stackBufSize = 128
22
2123 // Turn empty string into "/"
2224 if p == "" {
2325 return "/"
2426 }
2527
28 // Reasonably sized buffer on stack to avoid allocations in the common case.
29 // If a larger buffer is required, it gets allocated dynamically.
30 buf := make([]byte, 0, stackBufSize)
31
2632 n := len(p)
27 var buf []byte
2833
2934 // Invariants:
3035 // reading from path; r is index of next byte to process.
3641
3742 if p[0] != '/' {
3843 r = 0
39 buf = make([]byte, n+1)
44
45 if n+1 > stackBufSize {
46 buf = make([]byte, n+1)
47 } else {
48 buf = buf[:n+1]
49 }
4050 buf[0] = '/'
4151 }
4252
4353 trailing := n > 1 && p[n-1] == '/'
4454
4555 // A bit more clunky without a 'lazybuf' like the path package, but the loop
46 // gets completely inlined (bufApp). So in contrast to the path package this
47 // loop has no expensive function calls (except 1x make)
56 // gets completely inlined (bufApp calls).
57 // So in contrast to the path package this loop has no expensive function
58 // calls (except make, if needed).
4859
4960 for r < n {
5061 switch {
6879 // can backtrack
6980 w--
7081
71 if buf == nil {
82 if len(buf) == 0 {
7283 for w > 1 && p[w] != '/' {
7384 w--
7485 }
8091 }
8192
8293 default:
83 // real path element.
84 // add slash if needed
94 // Real path element.
95 // Add slash if needed
8596 if w > 1 {
8697 bufApp(&buf, p, w, '/')
8798 w++
8899 }
89100
90 // copy element
101 // Copy element
91102 for r < n && p[r] != '/' {
92103 bufApp(&buf, p, w, p[r])
93104 w++
96107 }
97108 }
98109
99 // re-append trailing slash
110 // Re-append trailing slash
100111 if trailing && w > 1 {
101112 bufApp(&buf, p, w, '/')
102113 w++
103114 }
104115
105 if buf == nil {
116 // If the original string was not modified (or only shortened at the end),
117 // return the respective substring of the original string.
118 // Otherwise return a new string from the buffer.
119 if len(buf) == 0 {
106120 return p[:w]
107121 }
108122 return string(buf[:w])
109123 }
110124
111 // internal helper to lazily create a buffer if necessary
125 // Internal helper to lazily create a buffer if necessary.
126 // Calls to this function get inlined.
112127 func bufApp(buf *[]byte, s string, w int, c byte) {
113 if *buf == nil {
128 b := *buf
129 if len(b) == 0 {
130 // No modification of the original string so far.
131 // If the next character is the same as in the original string, we do
132 // not yet have to allocate a buffer.
114133 if s[w] == c {
115134 return
116135 }
117136
118 *buf = make([]byte, len(s))
119 copy(*buf, s[:w])
137 // Otherwise use either the stack buffer, if it is large enough, or
138 // allocate a new buffer on the heap, and copy all previous characters.
139 if l := len(s); l > cap(b) {
140 *buf = make([]byte, len(s))
141 } else {
142 *buf = (*buf)[:l]
143 }
144 b = *buf
145
146 copy(b, s[:w])
120147 }
121 (*buf)[w] = c
148 b[w] = c
122149 }
55 package httprouter
66
77 import (
8 "runtime"
8 "strings"
99 "testing"
1010 )
1111
12 var cleanTests = []struct {
12 type cleanPathTest struct {
1313 path, result string
14 }{
14 }
15
16 var cleanTests = []cleanPathTest{
1517 // Already clean
1618 {"/", "/"},
1719 {"/abc", "/abc"},
7880 if testing.Short() {
7981 t.Skip("skipping malloc count in short mode")
8082 }
81 if runtime.GOMAXPROCS(0) > 1 {
82 t.Log("skipping AllocsPerRun checks; GOMAXPROCS>1")
83 return
84 }
8583
8684 for _, test := range cleanTests {
85 test := test
8786 allocs := testing.AllocsPerRun(100, func() { CleanPath(test.result) })
8887 if allocs > 0 {
8988 t.Errorf("CleanPath(%q): %v allocs, want zero", test.result, allocs)
9089 }
9190 }
9291 }
92
93 func BenchmarkPathClean(b *testing.B) {
94 b.ReportAllocs()
95
96 for i := 0; i < b.N; i++ {
97 for _, test := range cleanTests {
98 CleanPath(test.path)
99 }
100 }
101 }
102
103 func genLongPaths() (testPaths []cleanPathTest) {
104 for i := 1; i <= 1234; i++ {
105 ss := strings.Repeat("a", i)
106
107 correctPath := "/" + ss
108 testPaths = append(testPaths, cleanPathTest{
109 path: correctPath,
110 result: correctPath,
111 }, cleanPathTest{
112 path: ss,
113 result: correctPath,
114 }, cleanPathTest{
115 path: "//" + ss,
116 result: correctPath,
117 }, cleanPathTest{
118 path: "/" + ss + "/b/..",
119 result: correctPath,
120 })
121 }
122 return testPaths
123 }
124
125 func TestPathCleanLong(t *testing.T) {
126 cleanTests := genLongPaths()
127
128 for _, test := range cleanTests {
129 if s := CleanPath(test.path); s != test.result {
130 t.Errorf("CleanPath(%q) = %q, want %q", test.path, s, test.result)
131 }
132 if s := CleanPath(test.result); s != test.result {
133 t.Errorf("CleanPath(%q) = %q, want %q", test.result, s, test.result)
134 }
135 }
136 }
137
138 func BenchmarkPathCleanLong(b *testing.B) {
139 cleanTests := genLongPaths()
140 b.ResetTimer()
141 b.ReportAllocs()
142
143 for i := 0; i < b.N; i++ {
144 for _, test := range cleanTests {
145 CleanPath(test.path)
146 }
147 }
148 }
3333 // The router matches incoming requests by the request method and the path.
3434 // If a handle is registered for this path and method, the router delegates the
3535 // request to that function.
36 // For the methods GET, POST, PUT, PATCH and DELETE shortcut functions exist to
36 // For the methods GET, POST, PUT, PATCH, DELETE and OPTIONS shortcut functions exist to
3737 // register handles, for all other methods router.Handle can be used.
3838 //
3939 // The registered path, against which the router matches incoming requests, can
7979 "context"
8080 "net/http"
8181 "strings"
82 "sync"
8283 )
8384
8485 // Handle is a function that can be registered to a route to handle HTTP
8586 // requests. Like http.HandlerFunc, but has a third parameter for the values of
86 // wildcards (variables).
87 // wildcards (path variables).
8788 type Handle func(http.ResponseWriter, *http.Request, Params)
8889
8990 // Param is a single URL parameter, consisting of a key and a value.
100101 // ByName returns the value of the first Param which key matches the given name.
101102 // If no matching Param is found, an empty string is returned.
102103 func (ps Params) ByName(name string) string {
103 for i := range ps {
104 if ps[i].Key == name {
105 return ps[i].Value
104 for _, p := range ps {
105 if p.Key == name {
106 return p.Value
106107 }
107108 }
108109 return ""
120121 return p
121122 }
122123
124 // MatchedRoutePathParam is the Param name under which the path of the matched
125 // route is stored, if Router.SaveMatchedRoutePath is set.
126 var MatchedRoutePathParam = "$matchedRoutePath"
127
128 // MatchedRoutePath retrieves the path of the matched route.
129 // Router.SaveMatchedRoutePath must have been enabled when the respective
130 // handler was added, otherwise this function always returns an empty string.
131 func (ps Params) MatchedRoutePath() string {
132 return ps.ByName(MatchedRoutePathParam)
133 }
134
123135 // Router is a http.Handler which can be used to dispatch requests to different
124136 // handler functions via configurable routes
125137 type Router struct {
126138 trees map[string]*node
127139
140 paramsPool sync.Pool
141 maxParams uint16
142
143 // If enabled, adds the matched route path onto the http.Request context
144 // before invoking the handler.
145 // The matched route path is only added to handlers of routes that were
146 // registered when this option was enabled.
147 SaveMatchedRoutePath bool
148
128149 // Enables automatic redirection if the current route can't be matched but a
129150 // handler for the path with (without) the trailing slash exists.
130151 // For example if /foo/ is requested but a route only exists for /foo, the
131152 // client is redirected to /foo with http status code 301 for GET requests
132 // and 307 for all other request methods.
153 // and 308 for all other request methods.
133154 RedirectTrailingSlash bool
134155
135156 // If enabled, the router tries to fix the current request path, if no
137158 // First superfluous path elements like ../ or // are removed.
138159 // Afterwards the router does a case-insensitive lookup of the cleaned path.
139160 // If a handle can be found for this route, the router makes a redirection
140 // to the corrected path with status code 301 for GET requests and 307 for
161 // to the corrected path with status code 301 for GET requests and 308 for
141162 // all other request methods.
142163 // For example /FOO and /..//Foo could be redirected to /foo.
143164 // RedirectTrailingSlash is independent of this option.
197218 }
198219 }
199220
221 func (r *Router) getParams() *Params {
222 ps, _ := r.paramsPool.Get().(*Params)
223 *ps = (*ps)[0:0] // reset slice
224 return ps
225 }
226
227 func (r *Router) putParams(ps *Params) {
228 if ps != nil {
229 r.paramsPool.Put(ps)
230 }
231 }
232
233 func (r *Router) saveMatchedRoutePath(path string, handle Handle) Handle {
234 return func(w http.ResponseWriter, req *http.Request, ps Params) {
235 if ps == nil {
236 psp := r.getParams()
237 ps = (*psp)[0:1]
238 ps[0] = Param{Key: MatchedRoutePathParam, Value: path}
239 handle(w, req, ps)
240 r.putParams(psp)
241 } else {
242 ps = append(ps, Param{Key: MatchedRoutePathParam, Value: path})
243 handle(w, req, ps)
244 }
245 }
246 }
247
200248 // GET is a shortcut for router.Handle(http.MethodGet, path, handle)
201249 func (r *Router) GET(path string, handle Handle) {
202250 r.Handle(http.MethodGet, path, handle)
241289 // frequently used, non-standardized or custom methods (e.g. for internal
242290 // communication with a proxy).
243291 func (r *Router) Handle(method, path string, handle Handle) {
292 varsCount := uint16(0)
293
294 if method == "" {
295 panic("method must not be empty")
296 }
244297 if len(path) < 1 || path[0] != '/' {
245298 panic("path must begin with '/' in path '" + path + "'")
299 }
300 if handle == nil {
301 panic("handle must not be nil")
302 }
303
304 if r.SaveMatchedRoutePath {
305 varsCount++
306 handle = r.saveMatchedRoutePath(path, handle)
246307 }
247308
248309 if r.trees == nil {
258319 }
259320
260321 root.addRoute(path, handle)
322
323 // Update maxParams
324 if paramsCount := countParams(path); paramsCount+varsCount > r.maxParams {
325 r.maxParams = paramsCount + varsCount
326 }
327
328 // Lazy-init paramsPool alloc func
329 if r.paramsPool.New == nil && r.maxParams > 0 {
330 r.paramsPool.New = func() interface{} {
331 ps := make(Params, 0, r.maxParams)
332 return &ps
333 }
334 }
261335 }
262336
263337 // Handler is an adapter which allows the usage of an http.Handler as a
318392 // the same path with an extra / without the trailing slash should be performed.
319393 func (r *Router) Lookup(method, path string) (Handle, Params, bool) {
320394 if root := r.trees[method]; root != nil {
321 return root.getValue(path)
395 handle, ps, tsr := root.getValue(path, r.getParams)
396 if handle == nil {
397 r.putParams(ps)
398 return nil, nil, tsr
399 }
400 if ps == nil {
401 return handle, nil, tsr
402 }
403 return handle, *ps, tsr
322404 }
323405 return nil, nil, false
324406 }
346428 continue
347429 }
348430
349 handle, _, _ := r.trees[method].getValue(path)
431 handle, _, _ := r.trees[method].getValue(path, nil)
350432 if handle != nil {
351433 // Add request method to list of allowed methods
352434 allowed = append(allowed, method)
370452 // return as comma separated list
371453 return strings.Join(allowed, ", ")
372454 }
373 return
455
456 return allow
374457 }
375458
376459 // ServeHTTP makes the router implement the http.Handler interface.
382465 path := req.URL.Path
383466
384467 if root := r.trees[req.Method]; root != nil {
385 if handle, ps, tsr := root.getValue(path); handle != nil {
386 handle(w, req, ps)
468 if handle, ps, tsr := root.getValue(path, r.getParams); handle != nil {
469 if ps != nil {
470 handle(w, req, *ps)
471 r.putParams(ps)
472 } else {
473 handle(w, req, nil)
474 }
387475 return
388476 } else if req.Method != http.MethodConnect && path != "/" {
389 code := 301 // Permanent redirect, request with GET method
477 // Moved Permanently, request with GET method
478 code := http.StatusMovedPermanently
390479 if req.Method != http.MethodGet {
391 // Temporary redirect, request with same method
392 // As of Go 1.3, Go does not support status code 308.
393 code = 307
480 // Permanent Redirect, request with same method
481 code = http.StatusPermanentRedirect
394482 }
395483
396484 if tsr && r.RedirectTrailingSlash {
410498 r.RedirectTrailingSlash,
411499 )
412500 if found {
413 req.URL.Path = string(fixedPath)
501 req.URL.Path = fixedPath
414502 http.Redirect(w, req, req.URL.String(), code)
415503 return
416504 }
163163 }
164164 }
165165
166 func TestRouterRoot(t *testing.T) {
167 router := New()
166 func TestRouterInvalidInput(t *testing.T) {
167 router := New()
168
169 handle := func(_ http.ResponseWriter, _ *http.Request, _ Params) {}
170
168171 recv := catchPanic(func() {
169 router.GET("noSlashRoot", nil)
172 router.Handle("", "/", handle)
173 })
174 if recv == nil {
175 t.Fatal("registering empty method did not panic")
176 }
177
178 recv = catchPanic(func() {
179 router.GET("", handle)
180 })
181 if recv == nil {
182 t.Fatal("registering empty path did not panic")
183 }
184
185 recv = catchPanic(func() {
186 router.GET("noSlashRoot", handle)
170187 })
171188 if recv == nil {
172189 t.Fatal("registering path not beginning with '/' did not panic")
190 }
191
192 recv = catchPanic(func() {
193 router.GET("/", nil)
194 })
195 if recv == nil {
196 t.Fatal("registering nil handler did not panic")
173197 }
174198 }
175199
394418 code int
395419 location string
396420 }{
397 {"/path/", 301, "/path"}, // TSR -/
398 {"/dir", 301, "/dir/"}, // TSR +/
399 {"", 301, "/"}, // TSR +/
400 {"/PATH", 301, "/path"}, // Fixed Case
401 {"/DIR/", 301, "/dir/"}, // Fixed Case
402 {"/PATH/", 301, "/path"}, // Fixed Case -/
403 {"/DIR", 301, "/dir/"}, // Fixed Case +/
404 {"/../path", 301, "/path"}, // CleanPath
405 {"/nope", 404, ""}, // NotFound
421 {"/path/", http.StatusMovedPermanently, "/path"}, // TSR -/
422 {"/dir", http.StatusMovedPermanently, "/dir/"}, // TSR +/
423 {"", http.StatusMovedPermanently, "/"}, // TSR +/
424 {"/PATH", http.StatusMovedPermanently, "/path"}, // Fixed Case
425 {"/DIR/", http.StatusMovedPermanently, "/dir/"}, // Fixed Case
426 {"/PATH/", http.StatusMovedPermanently, "/path"}, // Fixed Case -/
427 {"/DIR", http.StatusMovedPermanently, "/dir/"}, // Fixed Case +/
428 {"/../path", http.StatusMovedPermanently, "/path"}, // CleanPath
429 {"/nope", http.StatusNotFound, ""}, // NotFound
406430 }
407431 for _, tr := range testRoutes {
408432 r, _ := http.NewRequest(http.MethodGet, tr.route, nil)
409433 w := httptest.NewRecorder()
410434 router.ServeHTTP(w, r)
411 if !(w.Code == tr.code && (w.Code == 404 || fmt.Sprint(w.Header().Get("Location")) == tr.location)) {
435 if !(w.Code == tr.code && (w.Code == http.StatusNotFound || fmt.Sprint(w.Header().Get("Location")) == tr.location)) {
412436 t.Errorf("NotFound handling route %s failed: Code=%d, Header=%v", tr.route, w.Code, w.Header().Get("Location"))
413437 }
414438 }
416440 // Test custom not found handler
417441 var notFound bool
418442 router.NotFound = http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
419 rw.WriteHeader(404)
443 rw.WriteHeader(http.StatusNotFound)
420444 notFound = true
421445 })
422446 r, _ := http.NewRequest(http.MethodGet, "/nope", nil)
423447 w := httptest.NewRecorder()
424448 router.ServeHTTP(w, r)
425 if !(w.Code == 404 && notFound == true) {
449 if !(w.Code == http.StatusNotFound && notFound == true) {
426450 t.Errorf("Custom NotFound handler failed: Code=%d, Header=%v", w.Code, w.Header())
427451 }
428452
429 // Test other method than GET (want 307 instead of 301)
453 // Test other method than GET (want 308 instead of 301)
430454 router.PATCH("/path", handlerFunc)
431455 r, _ = http.NewRequest(http.MethodPatch, "/path/", nil)
432456 w = httptest.NewRecorder()
433457 router.ServeHTTP(w, r)
434 if !(w.Code == 307 && fmt.Sprint(w.Header()) == "map[Location:[/path]]") {
458 if !(w.Code == http.StatusPermanentRedirect && fmt.Sprint(w.Header()) == "map[Location:[/path]]") {
435459 t.Errorf("Custom NotFound handler failed: Code=%d, Header=%v", w.Code, w.Header())
436460 }
437461
441465 r, _ = http.NewRequest(http.MethodGet, "/", nil)
442466 w = httptest.NewRecorder()
443467 router.ServeHTTP(w, r)
444 if !(w.Code == 404) {
468 if !(w.Code == http.StatusNotFound) {
445469 t.Errorf("NotFound handling route / failed: Code=%d", w.Code)
446470 }
447471 }
494518
495519 // insert route and try again
496520 router.GET("/user/:name", wantHandle)
497
498521 handle, params, _ := router.Lookup(http.MethodGet, "/user/gopher")
499522 if handle == nil {
500523 t.Fatal("Got no handle!")
504527 t.Fatal("Routing failed!")
505528 }
506529 }
507
508530 if !reflect.DeepEqual(params, wantParams) {
509531 t.Fatalf("Wrong parameter values: want %v, got %v", wantParams, params)
532 }
533 routed = false
534
535 // route without param
536 router.GET("/user", wantHandle)
537 handle, params, _ = router.Lookup(http.MethodGet, "/user")
538 if handle == nil {
539 t.Fatal("Got no handle!")
540 } else {
541 handle(nil, nil, nil)
542 if !routed {
543 t.Fatal("Routing failed!")
544 }
545 }
546 if params != nil {
547 t.Fatalf("Wrong parameter values: want %v, got %v", nil, params)
510548 }
511549
512550 handle, _, tsr = router.Lookup(http.MethodGet, "/user/gopher/")
571609 }
572610 }
573611
612 func TestRouterMatchedRoutePath(t *testing.T) {
613 route1 := "/user/:name"
614 routed1 := false
615 handle1 := func(_ http.ResponseWriter, req *http.Request, ps Params) {
616 route := ps.MatchedRoutePath()
617 if route != route1 {
618 t.Fatalf("Wrong matched route: want %s, got %s", route1, route)
619 }
620 routed1 = true
621 }
622
623 route2 := "/user/:name/details"
624 routed2 := false
625 handle2 := func(_ http.ResponseWriter, req *http.Request, ps Params) {
626 route := ps.MatchedRoutePath()
627 if route != route2 {
628 t.Fatalf("Wrong matched route: want %s, got %s", route2, route)
629 }
630 routed2 = true
631 }
632
633 route3 := "/"
634 routed3 := false
635 handle3 := func(_ http.ResponseWriter, req *http.Request, ps Params) {
636 route := ps.MatchedRoutePath()
637 if route != route3 {
638 t.Fatalf("Wrong matched route: want %s, got %s", route3, route)
639 }
640 routed3 = true
641 }
642
643 router := New()
644 router.SaveMatchedRoutePath = true
645 router.Handle(http.MethodGet, route1, handle1)
646 router.Handle(http.MethodGet, route2, handle2)
647 router.Handle(http.MethodGet, route3, handle3)
648
649 w := new(mockResponseWriter)
650 r, _ := http.NewRequest(http.MethodGet, "/user/gopher", nil)
651 router.ServeHTTP(w, r)
652 if !routed1 || routed2 || routed3 {
653 t.Fatal("Routing failed!")
654 }
655
656 w = new(mockResponseWriter)
657 r, _ = http.NewRequest(http.MethodGet, "/user/gopher/details", nil)
658 router.ServeHTTP(w, r)
659 if !routed2 || routed3 {
660 t.Fatal("Routing failed!")
661 }
662
663 w = new(mockResponseWriter)
664 r, _ = http.NewRequest(http.MethodGet, "/", nil)
665 router.ServeHTTP(w, r)
666 if !routed3 {
667 t.Fatal("Routing failed!")
668 }
669 }
670
574671 type mockFileSystem struct {
575672 opened bool
576673 }
+384
-370
tree.go less more
1616 return b
1717 }
1818
19 const maxParamCount uint8 = ^uint8(0)
20
21 func countParams(path string) uint8 {
19 func longestCommonPrefix(a, b string) int {
20 i := 0
21 max := min(len(a), len(b))
22 for i < max && a[i] == b[i] {
23 i++
24 }
25 return i
26 }
27
28 // Search for a wildcard segment and check the name for invalid characters.
29 // Returns -1 as index, if no wildcard was found.
30 func findWildcard(path string) (wilcard string, i int, valid bool) {
31 // Find start
32 for start, c := range []byte(path) {
33 // A wildcard starts with ':' (param) or '*' (catch-all)
34 if c != ':' && c != '*' {
35 continue
36 }
37
38 // Find end and check for invalid characters
39 valid = true
40 for end, c := range []byte(path[start+1:]) {
41 switch c {
42 case '/':
43 return path[start : start+1+end], start, valid
44 case ':', '*':
45 valid = false
46 }
47 }
48 return path[start:], start, valid
49 }
50 return "", -1, false
51 }
52
53 func countParams(path string) uint16 {
2254 var n uint
23 for i := 0; i < len(path); i++ {
24 if path[i] != ':' && path[i] != '*' {
25 continue
26 }
27 n++
28 }
29 if n >= uint(maxParamCount) {
30 return maxParamCount
31 }
32
33 return uint8(n)
55 for i := range []byte(path) {
56 switch path[i] {
57 case ':', '*':
58 n++
59 }
60 }
61 return uint16(n)
3462 }
3563
3664 type nodeType uint8
4472
4573 type node struct {
4674 path string
75 indices string
4776 wildChild bool
4877 nType nodeType
49 maxParams uint8
5078 priority uint32
51 indices string
5279 children []*node
5380 handle Handle
5481 }
5582
56 // increments priority of the given child and reorders if necessary
83 // Increments priority of the given child and reorders if necessary
5784 func (n *node) incrementChildPrio(pos int) int {
58 n.children[pos].priority++
59 prio := n.children[pos].priority
60
61 // adjust position (move to front)
85 cs := n.children
86 cs[pos].priority++
87 prio := cs[pos].priority
88
89 // Adjust position (move to front)
6290 newPos := pos
63 for newPos > 0 && n.children[newPos-1].priority < prio {
64 // swap node positions
65 n.children[newPos-1], n.children[newPos] = n.children[newPos], n.children[newPos-1]
66
67 newPos--
68 }
69
70 // build new index char string
91 for ; newPos > 0 && cs[newPos-1].priority < prio; newPos-- {
92 // Swap node positions
93 cs[newPos-1], cs[newPos] = cs[newPos], cs[newPos-1]
94 }
95
96 // Build new index char string
7197 if newPos != pos {
72 n.indices = n.indices[:newPos] + // unchanged prefix, might be empty
73 n.indices[pos:pos+1] + // the index char we move
74 n.indices[newPos:pos] + n.indices[pos+1:] // rest without char at 'pos'
98 n.indices = n.indices[:newPos] + // Unchanged prefix, might be empty
99 n.indices[pos:pos+1] + // The index char we move
100 n.indices[newPos:pos] + n.indices[pos+1:] // Rest without char at 'pos'
75101 }
76102
77103 return newPos
82108 func (n *node) addRoute(path string, handle Handle) {
83109 fullPath := path
84110 n.priority++
85 numParams := countParams(path)
86
87 // non-empty tree
88 if len(n.path) > 0 || len(n.children) > 0 {
89 walk:
90 for {
91 // Update maxParams of the current node
92 if numParams > n.maxParams {
93 n.maxParams = numParams
94 }
95
96 // Find the longest common prefix.
97 // This also implies that the common prefix contains no ':' or '*'
98 // since the existing key can't contain those chars.
99 i := 0
100 max := min(len(path), len(n.path))
101 for i < max && path[i] == n.path[i] {
102 i++
103 }
104
105 // Split edge
106 if i < len(n.path) {
107 child := node{
108 path: n.path[i:],
109 wildChild: n.wildChild,
110 nType: static,
111 indices: n.indices,
112 children: n.children,
113 handle: n.handle,
114 priority: n.priority - 1,
115 }
116
117 // Update maxParams (max of all children)
118 for i := range child.children {
119 if child.children[i].maxParams > child.maxParams {
120 child.maxParams = child.children[i].maxParams
121 }
122 }
123
124 n.children = []*node{&child}
111
112 // Empty tree
113 if n.path == "" && n.indices == "" {
114 n.insertChild(path, fullPath, handle)
115 n.nType = root
116 return
117 }
118
119 walk:
120 for {
121 // Find the longest common prefix.
122 // This also implies that the common prefix contains no ':' or '*'
123 // since the existing key can't contain those chars.
124 i := longestCommonPrefix(path, n.path)
125
126 // Split edge
127 if i < len(n.path) {
128 child := node{
129 path: n.path[i:],
130 wildChild: n.wildChild,
131 nType: static,
132 indices: n.indices,
133 children: n.children,
134 handle: n.handle,
135 priority: n.priority - 1,
136 }
137
138 n.children = []*node{&child}
139 // []byte for proper unicode char conversion, see #65
140 n.indices = string([]byte{n.path[i]})
141 n.path = path[:i]
142 n.handle = nil
143 n.wildChild = false
144 }
145
146 // Make new node a child of this node
147 if i < len(path) {
148 path = path[i:]
149
150 if n.wildChild {
151 n = n.children[0]
152 n.priority++
153
154 // Check if the wildcard matches
155 if len(path) >= len(n.path) && n.path == path[:len(n.path)] &&
156 // Adding a child to a catchAll is not possible
157 n.nType != catchAll &&
158 // Check for longer wildcard, e.g. :name and :names
159 (len(n.path) >= len(path) || path[len(n.path)] == '/') {
160 continue walk
161 } else {
162 // Wildcard conflict
163 pathSeg := path
164 if n.nType != catchAll {
165 pathSeg = strings.SplitN(pathSeg, "/", 2)[0]
166 }
167 prefix := fullPath[:strings.Index(fullPath, pathSeg)] + n.path
168 panic("'" + pathSeg +
169 "' in new path '" + fullPath +
170 "' conflicts with existing wildcard '" + n.path +
171 "' in existing prefix '" + prefix +
172 "'")
173 }
174 }
175
176 idxc := path[0]
177
178 // '/' after param
179 if n.nType == param && idxc == '/' && len(n.children) == 1 {
180 n = n.children[0]
181 n.priority++
182 continue walk
183 }
184
185 // Check if a child with the next path byte exists
186 for i, c := range []byte(n.indices) {
187 if c == idxc {
188 i = n.incrementChildPrio(i)
189 n = n.children[i]
190 continue walk
191 }
192 }
193
194 // Otherwise insert it
195 if idxc != ':' && idxc != '*' {
125196 // []byte for proper unicode char conversion, see #65
126 n.indices = string([]byte{n.path[i]})
127 n.path = path[:i]
128 n.handle = nil
129 n.wildChild = false
130 }
131
132 // Make new node a child of this node
133 if i < len(path) {
134 path = path[i:]
135
136 if n.wildChild {
137 n = n.children[0]
138 n.priority++
139
140 // Update maxParams of the child node
141 if numParams > n.maxParams {
142 n.maxParams = numParams
143 }
144 numParams--
145
146 // Check if the wildcard matches
147 if len(path) >= len(n.path) && n.path == path[:len(n.path)] &&
148 // Adding a child to a catchAll is not possible
149 n.nType != catchAll &&
150 // Check for longer wildcard, e.g. :name and :names
151 (len(n.path) >= len(path) || path[len(n.path)] == '/') {
152 continue walk
153 } else {
154 // Wildcard conflict
155 var pathSeg string
156 if n.nType == catchAll {
157 pathSeg = path
158 } else {
159 pathSeg = strings.SplitN(path, "/", 2)[0]
160 }
161 prefix := fullPath[:strings.Index(fullPath, pathSeg)] + n.path
162 panic("'" + pathSeg +
163 "' in new path '" + fullPath +
164 "' conflicts with existing wildcard '" + n.path +
165 "' in existing prefix '" + prefix +
166 "'")
167 }
168 }
169
170 c := path[0]
171
172 // slash after param
173 if n.nType == param && c == '/' && len(n.children) == 1 {
174 n = n.children[0]
175 n.priority++
176 continue walk
177 }
178
179 // Check if a child with the next path byte exists
180 for i := 0; i < len(n.indices); i++ {
181 if c == n.indices[i] {
182 i = n.incrementChildPrio(i)
183 n = n.children[i]
184 continue walk
185 }
186 }
187
188 // Otherwise insert it
189 if c != ':' && c != '*' {
190 // []byte for proper unicode char conversion, see #65
191 n.indices += string([]byte{c})
192 child := &node{
193 maxParams: numParams,
194 }
195 n.children = append(n.children, child)
196 n.incrementChildPrio(len(n.indices) - 1)
197 n = child
198 }
199 n.insertChild(numParams, path, fullPath, handle)
200 return
201
202 } else if i == len(path) { // Make node a (in-path) leaf
203 if n.handle != nil {
204 panic("a handle is already registered for path '" + fullPath + "'")
205 }
206 n.handle = handle
207 }
197 n.indices += string([]byte{idxc})
198 child := &node{}
199 n.children = append(n.children, child)
200 n.incrementChildPrio(len(n.indices) - 1)
201 n = child
202 }
203 n.insertChild(path, fullPath, handle)
208204 return
209205 }
210 } else { // Empty tree
211 n.insertChild(numParams, path, fullPath, handle)
212 n.nType = root
213 }
214 }
215
216 func (n *node) insertChild(numParams uint8, path, fullPath string, handle Handle) {
217 var offset int // already handled bytes of the path
218
219 // find prefix until first wildcard (beginning with ':'' or '*'')
220 for i, max := 0, len(path); numParams > 0; i++ {
221 c := path[i]
222 if c != ':' && c != '*' {
223 continue
224 }
225
226 // find wildcard end (either '/' or path end)
227 end := i + 1
228 for end < max && path[end] != '/' {
229 switch path[end] {
230 // the wildcard name must not contain ':' and '*'
231 case ':', '*':
232 panic("only one wildcard per path segment is allowed, has: '" +
233 path[i:] + "' in path '" + fullPath + "'")
234 default:
235 end++
236 }
237 }
238
239 // check if this Node existing children which would be
206
207 // Otherwise add handle to current node
208 if n.handle != nil {
209 panic("a handle is already registered for path '" + fullPath + "'")
210 }
211 n.handle = handle
212 return
213 }
214 }
215
216 func (n *node) insertChild(path, fullPath string, handle Handle) {
217 for {
218 // Find prefix until first wildcard
219 wildcard, i, valid := findWildcard(path)
220 if i < 0 { // No wilcard found
221 break
222 }
223
224 // The wildcard name must not contain ':' and '*'
225 if !valid {
226 panic("only one wildcard per path segment is allowed, has: '" +
227 wildcard + "' in path '" + fullPath + "'")
228 }
229
230 // Check if the wildcard has a name
231 if len(wildcard) < 2 {
232 panic("wildcards must be named with a non-empty name in path '" + fullPath + "'")
233 }
234
235 // Check if this node has existing children which would be
240236 // unreachable if we insert the wildcard here
241237 if len(n.children) > 0 {
242 panic("wildcard route '" + path[i:end] +
238 panic("wildcard segment '" + wildcard +
243239 "' conflicts with existing children in path '" + fullPath + "'")
244240 }
245241
246 // check if the wildcard has a name
247 if end-i < 2 {
248 panic("wildcards must be named with a non-empty name in path '" + fullPath + "'")
249 }
250
251 if c == ':' { // param
252 // split path at the beginning of the wildcard
242 // param
243 if wildcard[0] == ':' {
253244 if i > 0 {
254 n.path = path[offset:i]
255 offset = i
256 }
257
245 // Insert prefix before the current wildcard
246 n.path = path[:i]
247 path = path[i:]
248 }
249
250 n.wildChild = true
258251 child := &node{
259 nType: param,
260 maxParams: numParams,
252 nType: param,
253 path: wildcard,
261254 }
262255 n.children = []*node{child}
263 n.wildChild = true
264256 n = child
265257 n.priority++
266 numParams--
267
268 // if the path doesn't end with the wildcard, then there
258
259 // If the path doesn't end with the wildcard, then there
269260 // will be another non-wildcard subpath starting with '/'
270 if end < max {
271 n.path = path[offset:end]
272 offset = end
273
261 if len(wildcard) < len(path) {
262 path = path[len(wildcard):]
274263 child := &node{
275 maxParams: numParams,
276 priority: 1,
264 priority: 1,
277265 }
278266 n.children = []*node{child}
279267 n = child
280 }
281
282 } else { // catchAll
283 if end != max || numParams > 1 {
284 panic("catch-all routes are only allowed at the end of the path in path '" + fullPath + "'")
285 }
286
287 if len(n.path) > 0 && n.path[len(n.path)-1] == '/' {
288 panic("catch-all conflicts with existing handle for the path segment root in path '" + fullPath + "'")
289 }
290
291 // currently fixed width 1 for '/'
292 i--
293 if path[i] != '/' {
294 panic("no / before catch-all in path '" + fullPath + "'")
295 }
296
297 n.path = path[offset:i]
298
299 // first node: catchAll node with empty path
300 child := &node{
301 wildChild: true,
302 nType: catchAll,
303 maxParams: 1,
304 }
305 // update maxParams of the parent node
306 if n.maxParams < 1 {
307 n.maxParams = 1
308 }
309 n.children = []*node{child}
310 n.indices = string(path[i])
311 n = child
312 n.priority++
313
314 // second node: node holding the variable
315 child = &node{
316 path: path[i:],
317 nType: catchAll,
318 maxParams: 1,
319 handle: handle,
320 priority: 1,
321 }
322 n.children = []*node{child}
323
268 continue
269 }
270
271 // Otherwise we're done. Insert the handle in the new leaf
272 n.handle = handle
324273 return
325274 }
326 }
327
328 // insert remaining path part and handle to the leaf
329 n.path = path[offset:]
275
276 // catchAll
277 if i+len(wildcard) != len(path) {
278 panic("catch-all routes are only allowed at the end of the path in path '" + fullPath + "'")
279 }
280
281 if len(n.path) > 0 && n.path[len(n.path)-1] == '/' {
282 panic("catch-all conflicts with existing handle for the path segment root in path '" + fullPath + "'")
283 }
284
285 // Currently fixed width 1 for '/'
286 i--
287 if path[i] != '/' {
288 panic("no / before catch-all in path '" + fullPath + "'")
289 }
290
291 n.path = path[:i]
292
293 // First node: catchAll node with empty path
294 child := &node{
295 wildChild: true,
296 nType: catchAll,
297 }
298 n.children = []*node{child}
299 n.indices = string('/')
300 n = child
301 n.priority++
302
303 // Second node: node holding the variable
304 child = &node{
305 path: path[i:],
306 nType: catchAll,
307 handle: handle,
308 priority: 1,
309 }
310 n.children = []*node{child}
311
312 return
313 }
314
315 // If no wildcard was found, simply insert the path and handle
316 n.path = path
330317 n.handle = handle
331318 }
332319
335322 // If no handle can be found, a TSR (trailing slash redirect) recommendation is
336323 // made if a handle exists with an extra (without the) trailing slash for the
337324 // given path.
338 func (n *node) getValue(path string) (handle Handle, p Params, tsr bool) {
339 walk: // outer loop for walking the tree
325 func (n *node) getValue(path string, params func() *Params) (handle Handle, ps *Params, tsr bool) {
326 walk: // Outer loop for walking the tree
340327 for {
341 if len(path) > len(n.path) {
342 if path[:len(n.path)] == n.path {
343 path = path[len(n.path):]
328 prefix := n.path
329 if len(path) > len(prefix) {
330 if path[:len(prefix)] == prefix {
331 path = path[len(prefix):]
332
344333 // If this node does not have a wildcard (param or catchAll)
345 // child, we can just look up the next child node and continue
334 // child, we can just look up the next child node and continue
346335 // to walk down the tree
347336 if !n.wildChild {
348 c := path[0]
349 for i := 0; i < len(n.indices); i++ {
350 if c == n.indices[i] {
337 idxc := path[0]
338 for i, c := range []byte(n.indices) {
339 if c == idxc {
351340 n = n.children[i]
352341 continue walk
353342 }
358347 // trailing slash if a leaf exists for that path.
359348 tsr = (path == "/" && n.handle != nil)
360349 return
361
362 }
363
364 // handle wildcard child
350 }
351
352 // Handle wildcard child
365353 n = n.children[0]
366354 switch n.nType {
367355 case param:
368 // find param end (either '/' or path end)
356 // Find param end (either '/' or path end)
369357 end := 0
370358 for end < len(path) && path[end] != '/' {
371359 end++
372360 }
373361
374 // save param value
375 if p == nil {
376 // lazy allocation
377 p = make(Params, 0, n.maxParams)
378 }
379 i := len(p)
380 p = p[:i+1] // expand slice within preallocated capacity
381 p[i].Key = n.path[1:]
382 p[i].Value = path[:end]
383
384 // we need to go deeper!
362 // Save param value
363 if params != nil {
364 if ps == nil {
365 ps = params()
366 }
367 // Expand slice within preallocated capacity
368 i := len(*ps)
369 *ps = (*ps)[:i+1]
370 (*ps)[i] = Param{
371 Key: n.path[1:],
372 Value: path[:end],
373 }
374 }
375
376 // We need to go deeper!
385377 if end < len(path) {
386378 if len(n.children) > 0 {
387379 path = path[end:]
400392 // No handle found. Check if a handle for this path + a
401393 // trailing slash exists for TSR recommendation
402394 n = n.children[0]
403 tsr = (n.path == "/" && n.handle != nil)
395 tsr = (n.path == "/" && n.handle != nil) || (n.path == "" && n.indices == "/")
404396 }
405397
406398 return
407399
408400 case catchAll:
409 // save param value
410 if p == nil {
411 // lazy allocation
412 p = make(Params, 0, n.maxParams)
413 }
414 i := len(p)
415 p = p[:i+1] // expand slice within preallocated capacity
416 p[i].Key = n.path[2:]
417 p[i].Value = path
401 // Save param value
402 if params != nil {
403 if ps == nil {
404 ps = params()
405 }
406 // Expand slice within preallocated capacity
407 i := len(*ps)
408 *ps = (*ps)[:i+1]
409 (*ps)[i] = Param{
410 Key: n.path[2:],
411 Value: path,
412 }
413 }
418414
419415 handle = n.handle
420416 return
423419 panic("invalid node type")
424420 }
425421 }
426 } else if path == n.path {
422 } else if path == prefix {
427423 // We should have reached the node containing the handle.
428424 // Check if this node has a handle registered.
429425 if handle = n.handle; handle != nil {
430426 return
431427 }
432428
429 // If there is no handle for this route, but this route has a
430 // wildcard child, there must be a handle for this path with an
431 // additional trailing slash
433432 if path == "/" && n.wildChild && n.nType != root {
434433 tsr = true
435434 return
437436
438437 // No handle found. Check if a handle for this path + a
439438 // trailing slash exists for trailing slash recommendation
440 for i := 0; i < len(n.indices); i++ {
441 if n.indices[i] == '/' {
439 for i, c := range []byte(n.indices) {
440 if c == '/' {
442441 n = n.children[i]
443442 tsr = (len(n.path) == 1 && n.handle != nil) ||
444443 (n.nType == catchAll && n.children[0].handle != nil)
445444 return
446445 }
447446 }
448
449447 return
450448 }
451449
452450 // Nothing found. We can recommend to redirect to the same URL with an
453451 // extra trailing slash if a leaf exists for that path
454452 tsr = (path == "/") ||
455 (len(n.path) == len(path)+1 && n.path[len(path)] == '/' &&
456 path == n.path[:len(n.path)-1] && n.handle != nil)
453 (len(prefix) == len(path)+1 && prefix[len(path)] == '/' &&
454 path == prefix[:len(prefix)-1] && n.handle != nil)
457455 return
458456 }
459457 }
462460 // It can optionally also fix trailing slashes.
463461 // It returns the case-corrected path and a bool indicating whether the lookup
464462 // was successful.
465 func (n *node) findCaseInsensitivePath(path string, fixTrailingSlash bool) (ciPath []byte, found bool) {
466 return n.findCaseInsensitivePathRec(
463 func (n *node) findCaseInsensitivePath(path string, fixTrailingSlash bool) (fixedPath string, found bool) {
464 const stackBufSize = 128
465
466 // Use a static sized buffer on the stack in the common case.
467 // If the path is too long, allocate a buffer on the heap instead.
468 buf := make([]byte, 0, stackBufSize)
469 if l := len(path) + 1; l > stackBufSize {
470 buf = make([]byte, 0, l)
471 }
472
473 ciPath := n.findCaseInsensitivePathRec(
467474 path,
468 make([]byte, 0, len(path)+1), // preallocate enough memory for new path
469 [4]byte{}, // empty rune buffer
475 buf, // Preallocate enough memory for new path
476 [4]byte{}, // Empty rune buffer
470477 fixTrailingSlash,
471478 )
472 }
473
474 // shift bytes in array by n bytes left
479
480 return string(ciPath), ciPath != nil
481 }
482
483 // Shift bytes in array by n bytes left
475484 func shiftNRuneBytes(rb [4]byte, n int) [4]byte {
476485 switch n {
477486 case 0:
487496 }
488497 }
489498
490 // recursive case-insensitive lookup function used by n.findCaseInsensitivePath
491 func (n *node) findCaseInsensitivePathRec(path string, ciPath []byte, rb [4]byte, fixTrailingSlash bool) ([]byte, bool) {
499 // Recursive case-insensitive lookup function used by n.findCaseInsensitivePath
500 func (n *node) findCaseInsensitivePathRec(path string, ciPath []byte, rb [4]byte, fixTrailingSlash bool) []byte {
492501 npLen := len(n.path)
493502
494 walk: // outer loop for walking the tree
503 walk: // Outer loop for walking the tree
495504 for len(path) >= npLen && (npLen == 0 || strings.EqualFold(path[1:npLen], n.path[1:])) {
496 // add common prefix to result
497
505 // Add common prefix to result
498506 oldPath := path
499507 path = path[npLen:]
500508 ciPath = append(ciPath, n.path...)
504512 // we can just look up the next child node and continue to walk down
505513 // the tree
506514 if !n.wildChild {
507 // skip rune bytes already processed
515 // Skip rune bytes already processed
508516 rb = shiftNRuneBytes(rb, npLen)
509517
510518 if rb[0] != 0 {
511 // old rune not finished
512 for i := 0; i < len(n.indices); i++ {
513 if n.indices[i] == rb[0] {
519 // Old rune not finished
520 idxc := rb[0]
521 for i, c := range []byte(n.indices) {
522 if c == idxc {
514523 // continue with child node
515524 n = n.children[i]
516525 npLen = len(n.path)
518527 }
519528 }
520529 } else {
521 // process a new rune
530 // Process a new rune
522531 var rv rune
523532
524 // find rune start
525 // runes are up to 4 byte long,
526 // -4 would definitely be another rune
533 // Find rune start.
534 // Runes are up to 4 byte long,
535 // -4 would definitely be another rune.
527536 var off int
528537 for max := min(npLen, 3); off < max; off++ {
529538 if i := npLen - off; utf8.RuneStart(oldPath[i]) {
533542 }
534543 }
535544
536 // calculate lowercase bytes of current rune
545 // Calculate lowercase bytes of current rune
537546 lo := unicode.ToLower(rv)
538547 utf8.EncodeRune(rb[:], lo)
539548
540 // skip already processed bytes
549 // Skip already processed bytes
541550 rb = shiftNRuneBytes(rb, off)
542551
543 for i := 0; i < len(n.indices); i++ {
544 // lowercase matches
545 if n.indices[i] == rb[0] {
552 idxc := rb[0]
553 for i, c := range []byte(n.indices) {
554 // Lowercase matches
555 if c == idxc {
546556 // must use a recursive approach since both the
547557 // uppercase byte and the lowercase byte might exist
548558 // as an index
549 if out, found := n.children[i].findCaseInsensitivePathRec(
559 if out := n.children[i].findCaseInsensitivePathRec(
550560 path, ciPath, rb, fixTrailingSlash,
551 ); found {
552 return out, true
561 ); out != nil {
562 return out
553563 }
554564 break
555565 }
556566 }
557567
558 // if we found no match, the same for the uppercase rune,
568 // If we found no match, the same for the uppercase rune,
559569 // if it differs
560570 if up := unicode.ToUpper(rv); up != lo {
561571 utf8.EncodeRune(rb[:], up)
562572 rb = shiftNRuneBytes(rb, off)
563573
564 for i, c := 0, rb[0]; i < len(n.indices); i++ {
565 // uppercase matches
566 if n.indices[i] == c {
567 // continue with child node
574 idxc := rb[0]
575 for i, c := range []byte(n.indices) {
576 // Uppercase matches
577 if c == idxc {
578 // Continue with child node
568579 n = n.children[i]
569580 npLen = len(n.path)
570581 continue walk
575586
576587 // Nothing found. We can recommend to redirect to the same URL
577588 // without a trailing slash if a leaf exists for that path
578 return ciPath, (fixTrailingSlash && path == "/" && n.handle != nil)
589 if fixTrailingSlash && path == "/" && n.handle != nil {
590 return ciPath
591 }
592 return nil
579593 }
580594
581595 n = n.children[0]
582596 switch n.nType {
583597 case param:
584 // find param end (either '/' or path end)
585 k := 0
586 for k < len(path) && path[k] != '/' {
587 k++
588 }
589
590 // add param value to case insensitive path
591 ciPath = append(ciPath, path[:k]...)
592
593 // we need to go deeper!
594 if k < len(path) {
598 // Find param end (either '/' or path end)
599 end := 0
600 for end < len(path) && path[end] != '/' {
601 end++
602 }
603
604 // Add param value to case insensitive path
605 ciPath = append(ciPath, path[:end]...)
606
607 // We need to go deeper!
608 if end < len(path) {
595609 if len(n.children) > 0 {
596 // continue with child node
610 // Continue with child node
597611 n = n.children[0]
598612 npLen = len(n.path)
599 path = path[k:]
613 path = path[end:]
600614 continue
601615 }
602616
603617 // ... but we can't
604 if fixTrailingSlash && len(path) == k+1 {
605 return ciPath, true
606 }
607 return ciPath, false
618 if fixTrailingSlash && len(path) == end+1 {
619 return ciPath
620 }
621 return nil
608622 }
609623
610624 if n.handle != nil {
611 return ciPath, true
625 return ciPath
612626 } else if fixTrailingSlash && len(n.children) == 1 {
613627 // No handle found. Check if a handle for this path + a
614628 // trailing slash exists
615629 n = n.children[0]
616630 if n.path == "/" && n.handle != nil {
617 return append(ciPath, '/'), true
618 }
619 }
620 return ciPath, false
631 return append(ciPath, '/')
632 }
633 }
634 return nil
621635
622636 case catchAll:
623 return append(ciPath, path...), true
637 return append(ciPath, path...)
624638
625639 default:
626640 panic("invalid node type")
629643 // We should have reached the node containing the handle.
630644 // Check if this node has a handle registered.
631645 if n.handle != nil {
632 return ciPath, true
646 return ciPath
633647 }
634648
635649 // No handle found.
636650 // Try to fix the path by adding a trailing slash
637651 if fixTrailingSlash {
638 for i := 0; i < len(n.indices); i++ {
639 if n.indices[i] == '/' {
652 for i, c := range []byte(n.indices) {
653 if c == '/' {
640654 n = n.children[i]
641655 if (len(n.path) == 1 && n.handle != nil) ||
642656 (n.nType == catchAll && n.children[0].handle != nil) {
643 return append(ciPath, '/'), true
644 }
645 return ciPath, false
646 }
647 }
648 }
649 return ciPath, false
657 return append(ciPath, '/')
658 }
659 return nil
660 }
661 }
662 }
663 return nil
650664 }
651665 }
652666
654668 // Try to fix the path by adding / removing a trailing slash
655669 if fixTrailingSlash {
656670 if path == "/" {
657 return ciPath, true
671 return ciPath
658672 }
659673 if len(path)+1 == npLen && n.path[len(path)] == '/' &&
660674 strings.EqualFold(path[1:], n.path[1:len(path)]) && n.handle != nil {
661 return append(ciPath, n.path...), true
662 }
663 }
664 return ciPath, false
665 }
675 return append(ciPath, n.path...)
676 }
677 }
678 return nil
679 }
1212 "testing"
1313 )
1414
15 func printChildren(n *node, prefix string) {
16 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)
17 for l := len(n.path); l > 0; l-- {
18 prefix += " "
19 }
20 for _, child := range n.children {
21 printChildren(child, prefix)
22 }
23 }
15 // func printChildren(n *node, prefix string) {
16 // fmt.Printf(" %02d %s%s[%d] %v %t %d \r\n", n.priority, prefix, n.path, len(n.children), n.handle, n.wildChild, n.nType)
17 // for l := len(n.path); l > 0; l-- {
18 // prefix += " "
19 // }
20 // for _, child := range n.children {
21 // printChildren(child, prefix)
22 // }
23 // }
2424
2525 // Used as a workaround since we can't compare functions or their addresses
2626 var fakeHandlerValue string
3838 ps Params
3939 }
4040
41 func getParams() *Params {
42 ps := make(Params, 0, 20)
43 return &ps
44 }
45
4146 func checkRequests(t *testing.T, tree *node, requests testRequests) {
4247 for _, request := range requests {
43 handler, ps, _ := tree.getValue(request.path)
44
45 if handler == nil {
48 handler, psp, _ := tree.getValue(request.path, getParams)
49
50 switch {
51 case handler == nil:
4652 if !request.nilHandler {
4753 t.Errorf("handle mismatch for route '%s': Expected non-nil handle", request.path)
4854 }
49 } else if request.nilHandler {
55 case request.nilHandler:
5056 t.Errorf("handle mismatch for route '%s': Expected nil handle", request.path)
51 } else {
57 default:
5258 handler(nil, nil, nil)
5359 if fakeHandlerValue != request.route {
5460 t.Errorf("handle mismatch for route '%s': Wrong handle (%s != %s)", request.path, fakeHandlerValue, request.route)
5561 }
62 }
63
64 var ps Params
65 if psp != nil {
66 ps = *psp
5667 }
5768
5869 if !reflect.DeepEqual(ps, request.ps) {
8192 return prio
8293 }
8394
84 func checkMaxParams(t *testing.T, n *node) uint8 {
85 var maxParams uint8
86 for i := range n.children {
87 params := checkMaxParams(t, n.children[i])
88 if params > maxParams {
89 maxParams = params
90 }
91 }
92 if n.nType > root && !n.wildChild {
93 maxParams++
94 }
95
96 if n.maxParams != maxParams {
97 t.Errorf(
98 "maxParams mismatch for node '%s': is %d, should be %d",
99 n.path, n.maxParams, maxParams,
100 )
101 }
102
103 return maxParams
104 }
105
10695 func TestCountParams(t *testing.T) {
10796 if countParams("/path/:param1/static/*catch-all") != 2 {
10897 t.Fail()
10998 }
110 if countParams(strings.Repeat("/:param", 256)) != 255 {
99 if countParams(strings.Repeat("/:param", 256)) != 256 {
111100 t.Fail()
112101 }
113102 }
132121 tree.addRoute(route, fakeHandler(route))
133122 }
134123
135 //printChildren(tree, "")
124 // printChildren(tree, "")
136125
137126 checkRequests(t, tree, testRequests{
138127 {"/a", false, "/a", nil},
149138 })
150139
151140 checkPriorities(t, tree)
152 checkMaxParams(t, tree)
153141 }
154142
155143 func TestTreeWildcard(t *testing.T) {
175163 tree.addRoute(route, fakeHandler(route))
176164 }
177165
178 //printChildren(tree, "")
166 // printChildren(tree, "")
179167
180168 checkRequests(t, tree, testRequests{
181169 {"/", false, "/", nil},
195183 })
196184
197185 checkPriorities(t, tree)
198 checkMaxParams(t, tree)
199186 }
200187
201188 func catchPanic(testFunc func()) (recv interface{}) {
215202 func testRoutes(t *testing.T, routes []testRoute) {
216203 tree := &node{}
217204
218 for _, route := range routes {
205 for i := range routes {
206 route := routes[i]
219207 recv := catchPanic(func() {
220208 tree.addRoute(route.path, nil)
221209 })
229217 }
230218 }
231219
232 //printChildren(tree, "")
220 // printChildren(tree, "")
233221 }
234222
235223 func TestTreeWildcardConflict(t *testing.T) {
279267 "/search/:query",
280268 "/user_:name",
281269 }
282 for _, route := range routes {
270 for i := range routes {
271 route := routes[i]
283272 recv := catchPanic(func() {
284273 tree.addRoute(route, fakeHandler(route))
285274 })
296285 }
297286 }
298287
299 //printChildren(tree, "")
288 // printChildren(tree, "")
300289
301290 checkRequests(t, tree, testRequests{
302291 {"/", false, "/", nil},
316305 "/cmd/:/",
317306 "/src/*",
318307 }
319 for _, route := range routes {
308 for i := range routes {
309 route := routes[i]
320310 recv := catchPanic(func() {
321311 tree.addRoute(route, nil)
322312 })
349339 tree := &node{}
350340 var route = "/cmd/*filepath"
351341 tree.addRoute(route, fakeHandler(route))
352 checkMaxParams(t, tree)
353342 }
354343
355344 func TestTreeDoubleWildcard(t *testing.T) {
361350 "/:foo*bar",
362351 }
363352
364 for _, route := range routes {
353 for i := range routes {
354 route := routes[i]
365355 tree := &node{}
366356 recv := catchPanic(func() {
367357 tree.addRoute(route, nil)
372362 }
373363 }
374364 }
375
376 /*func TestTreeDuplicateWildcard(t *testing.T) {
377 tree := &node{}
378
379 routes := [...]string{
380 "/:id/:name/:id",
381 }
382 for _, route := range routes {
383 ...
384 }
385 }*/
386365
387366 func TestTreeTrailingSlashRedirect(t *testing.T) {
388367 tree := &node{}
412391 "/no/a",
413392 "/no/b",
414393 "/api/hello/:name",
415 }
416 for _, route := range routes {
394 "/vendor/:x/*y",
395 }
396 for i := range routes {
397 route := routes[i]
417398 recv := catchPanic(func() {
418399 tree.addRoute(route, fakeHandler(route))
419400 })
422403 }
423404 }
424405
425 //printChildren(tree, "")
406 // printChildren(tree, "")
426407
427408 tsrRoutes := [...]string{
428409 "/hi/",
439420 "/admin/config/",
440421 "/admin/config/permissions/",
441422 "/doc/",
423 "/vendor/x",
442424 }
443425 for _, route := range tsrRoutes {
444 handler, _, tsr := tree.getValue(route)
426 handler, _, tsr := tree.getValue(route, nil)
445427 if handler != nil {
446428 t.Fatalf("non-nil handler for TSR route '%s", route)
447429 } else if !tsr {
458440 "/api/world/abc",
459441 }
460442 for _, route := range noTsrRoutes {
461 handler, _, tsr := tree.getValue(route)
443 handler, _, tsr := tree.getValue(route, nil)
462444 if handler != nil {
463445 t.Fatalf("non-nil handler for No-TSR route '%s", route)
464446 } else if tsr {
477459 t.Fatalf("panic inserting test route: %v", recv)
478460 }
479461
480 handler, _, tsr := tree.getValue("/")
462 handler, _, tsr := tree.getValue("/", nil)
481463 if handler != nil {
482464 t.Fatalf("non-nil handler")
483465 } else if tsr {
487469
488470 func TestTreeFindCaseInsensitivePath(t *testing.T) {
489471 tree := &node{}
472
473 longPath := "/l" + strings.Repeat("o", 128) + "ng"
474 lOngPath := "/l" + strings.Repeat("O", 128) + "ng/"
490475
491476 routes := [...]string{
492477 "/hi",
521506 "/w/♭/", // 3 byte, last byte differs
522507 "/w/𠜎", // 4 byte
523508 "/w/𠜏/", // 4 byte
524 }
525
526 for _, route := range routes {
509 longPath,
510 }
511
512 for i := range routes {
513 route := routes[i]
527514 recv := catchPanic(func() {
528515 tree.addRoute(route, fakeHandler(route))
529516 })
534521
535522 // Check out == in for all registered routes
536523 // With fixTrailingSlash = true
537 for _, route := range routes {
524 for i := range routes {
525 route := routes[i]
538526 out, found := tree.findCaseInsensitivePath(route, true)
539527 if !found {
540528 t.Errorf("Route '%s' not found!", route)
541 } else if string(out) != route {
542 t.Errorf("Wrong result for route '%s': %s", route, string(out))
529 } else if out != route {
530 t.Errorf("Wrong result for route '%s': %s", route, out)
543531 }
544532 }
545533 // With fixTrailingSlash = false
546 for _, route := range routes {
534 for i := range routes {
535 route := routes[i]
547536 out, found := tree.findCaseInsensitivePath(route, false)
548537 if !found {
549538 t.Errorf("Route '%s' not found!", route)
550 } else if string(out) != route {
551 t.Errorf("Wrong result for route '%s': %s", route, string(out))
539 } else if out != route {
540 t.Errorf("Wrong result for route '%s': %s", route, out)
552541 }
553542 }
554543
613602 {"/w/♭", "/w/♭/", true, true},
614603 {"/w/𠜎/", "/w/𠜎", true, true},
615604 {"/w/𠜏", "/w/𠜏/", true, true},
605 {lOngPath, longPath, true, true},
616606 }
617607 // With fixTrailingSlash = true
618608 for _, test := range tests {
619609 out, found := tree.findCaseInsensitivePath(test.in, true)
620 if found != test.found || (found && (string(out) != test.out)) {
610 if found != test.found || (found && (out != test.out)) {
621611 t.Errorf("Wrong result for '%s': got %s, %t; want %s, %t",
622 test.in, string(out), found, test.out, test.found)
612 test.in, out, found, test.out, test.found)
623613 return
624614 }
625615 }
628618 out, found := tree.findCaseInsensitivePath(test.in, false)
629619 if test.slash {
630620 if found { // test needs a trailingSlash fix. It must not be found!
631 t.Errorf("Found without fixTrailingSlash: %s; got %s", test.in, string(out))
621 t.Errorf("Found without fixTrailingSlash: %s; got %s", test.in, out)
632622 }
633623 } else {
634 if found != test.found || (found && (string(out) != test.out)) {
624 if found != test.found || (found && (out != test.out)) {
635625 t.Errorf("Wrong result for '%s': got %s, %t; want %s, %t",
636 test.in, string(out), found, test.out, test.found)
626 test.in, out, found, test.out, test.found)
637627 return
638628 }
639629 }
652642
653643 // normal lookup
654644 recv := catchPanic(func() {
655 tree.getValue("/test")
645 tree.getValue("/test", nil)
656646 })
657647 if rs, ok := recv.(string); !ok || rs != panicMsg {
658648 t.Fatalf("Expected panic '"+panicMsg+"', got '%v'", recv)
681671 {"/conooo/xxx", "ooo", `/con:tact`, `:tact`},
682672 }
683673
684 for _, conflict := range conflicts {
674 for i := range conflicts {
675 conflict := conflicts[i]
676
685677 // I have to re-create a 'tree', because the 'tree' will be
686678 // in an inconsistent state when the loop recovers from the
687679 // panic which threw by 'addRoute' function.
692684 "/who/foo/hello",
693685 }
694686
695 for _, route := range routes {
687 for i := range routes {
688 route := routes[i]
696689 tree.addRoute(route, fakeHandler(route))
697690 }
698691