├── .gitignore ├── bear-examples_test.go ├── bear.go ├── LICENSE ├── tree.go ├── context.go ├── README.md ├── handlerfunc.go ├── mux.go └── bear-tests_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | *.prof 25 | 26 | # Mac 27 | .DS_Store 28 | -------------------------------------------------------------------------------- /bear-examples_test.go: -------------------------------------------------------------------------------- 1 | package bear_test 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | 7 | "github.com/ursiform/bear" 8 | ) 9 | 10 | func ExampleMux_On_error() { 11 | mux := bear.New() 12 | handlerOne := func(http.ResponseWriter, *http.Request) {} 13 | handlerTwo := func(http.ResponseWriter, *http.Request) {} 14 | if err := mux.On("GET", "/foo/", handlerOne); err != nil { 15 | fmt.Println(err) 16 | } else if err := mux.On("*", "/foo/", handlerTwo); err != nil { 17 | fmt.Println(err) 18 | } 19 | // Output: bear: GET /foo/ exists, ignoring 20 | } 21 | -------------------------------------------------------------------------------- /bear.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Afshin Darian. All rights reserved. 2 | // Use of this source code is governed by The MIT License 3 | // that can be found in the LICENSE file. 4 | 5 | // Package bear provides HTTP multiplexing with dynamic URL components and 6 | // request contexts to form the nucleus of a middleware-based web service. 7 | package bear 8 | 9 | import "regexp" 10 | 11 | const ( 12 | asterisk = "*" 13 | dynamic = "\x00" 14 | empty = "" 15 | lasterisk = "*/" 16 | slash = "/" 17 | slashr = '/' 18 | wildcard = "\x00\x00" 19 | ) 20 | 21 | var ( 22 | dyn = regexp.MustCompile(`\{(\w+)\}`) 23 | dbl = regexp.MustCompile(`[\/]{2,}`) 24 | verbs = [8]string{ 25 | "CONNECT", 26 | "DELETE", 27 | "GET", 28 | "HEAD", 29 | "OPTIONS", 30 | "POST", 31 | "PUT", 32 | "TRACE", 33 | } 34 | ) 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015-2016 Afshin Darian 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /tree.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Afshin Darian. All rights reserved. 2 | // Use of this source code is governed by The MIT License 3 | // that can be found in the LICENSE file. 4 | 5 | package bear 6 | 7 | import ( 8 | "fmt" 9 | "strings" 10 | ) 11 | 12 | type tree struct { 13 | children map[string]*tree 14 | handlers []HandlerFunc 15 | name string 16 | pattern string 17 | } 18 | 19 | func parsePattern(s string) (pattern string, components []string, last int) { 20 | if slashr != s[0] { 21 | s = slash + s // start with slash 22 | } 23 | if slashr != s[len(s)-1] { 24 | s = s + slash // end with slash 25 | } 26 | pattern = dbl.ReplaceAllString(s, slash) 27 | components = strings.SplitAfter(pattern, slash) 28 | components = components[1 : len(components)-1] 29 | last = len(components) - 1 30 | return pattern, components, last 31 | } 32 | 33 | func (tr *tree) set(verb string, pattern string, handlers []HandlerFunc, 34 | wildcards *bool, err *error) { 35 | if pattern == slash || pattern == empty { 36 | if nil != tr.handlers { 37 | *err = fmt.Errorf("bear: %s %s exists, ignoring", verb, pattern) 38 | return 39 | } 40 | tr.pattern = slash 41 | tr.handlers = handlers 42 | return 43 | } 44 | if nil == tr.children { 45 | tr.children = make(map[string]*tree) 46 | } 47 | current := &tr.children 48 | pattern, components, last := parsePattern(pattern) 49 | for index, component := range components { 50 | var ( 51 | match []string = dyn.FindStringSubmatch(component) 52 | key string = component 53 | name string 54 | ) 55 | if 0 < len(match) { 56 | key, name = dynamic, match[1] 57 | } else if key == lasterisk { 58 | key, name = wildcard, asterisk 59 | *wildcards = true 60 | } 61 | if nil == (*current)[key] { 62 | (*current)[key] = &tree{ 63 | children: make(map[string]*tree), name: name} 64 | } 65 | if index == last { 66 | if nil != (*current)[key].handlers { 67 | *err = fmt.Errorf("bear: %s %s exists, ignoring", verb, pattern) 68 | return 69 | } 70 | (*current)[key].pattern = pattern 71 | (*current)[key].handlers = handlers 72 | return 73 | } else if key == wildcard { 74 | *err = fmt.Errorf("bear: %s %s wildcard (%s) token must be last", 75 | verb, pattern, asterisk) 76 | return 77 | } 78 | current = &(*current)[key].children 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /context.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Afshin Darian. All rights reserved. 2 | // Use of this source code is governed by The MIT License 3 | // that can be found in the LICENSE file. 4 | 5 | package bear 6 | 7 | import "net/http" 8 | 9 | // Context is state of each request. 10 | type Context struct { 11 | // Params is a map of string keys with string values that is populated 12 | // by the dynamic URL parameters (if any). 13 | // Wildcard params are accessed by using an asterisk: Params["*"] 14 | Params map[string]string 15 | handler int 16 | mux *Mux 17 | // Request is the same as the *http.Request that all handlers receive 18 | // and is referenced in Context for convenience. 19 | Request *http.Request 20 | // ResponseWriter is the same as the http.ResponseWriter that all handlers 21 | // receive and is referenced in Context for convenience. 22 | ResponseWriter http.ResponseWriter 23 | state map[string]interface{} 24 | tree *tree 25 | } 26 | 27 | // Get allows retrieving a state value (interface{}) 28 | func (ctx *Context) Get(key string) interface{} { 29 | if nil == ctx.state { 30 | return nil 31 | } 32 | return ctx.state[key] 33 | } 34 | 35 | // Next calls the next middleware (if any) that was registered as a handler for 36 | // a particular request pattern. 37 | func (ctx *Context) Next() { 38 | always := len(ctx.mux.always) 39 | handlers := len(ctx.tree.handlers) 40 | ctx.handler++ 41 | if always > 0 && ctx.handler < always { 42 | index := ctx.handler 43 | ctx.mux.always[index](ctx.ResponseWriter, ctx.Request, ctx) 44 | return 45 | } 46 | if ctx.handler-always < handlers { 47 | index := ctx.handler - always 48 | ctx.tree.handlers[index](ctx.ResponseWriter, ctx.Request, ctx) 49 | } 50 | } 51 | 52 | func (ctx *Context) param(key string, value string, capacity int) { 53 | if nil == ctx.Params { 54 | ctx.Params = make(map[string]string, capacity) 55 | } 56 | ctx.Params[key] = value[:len(value)-1] 57 | } 58 | 59 | // Set allows setting an arbitrary value (interface{}) to a string key 60 | // to allow one middleware to pass information to the next. 61 | // It returns a pointer to the current Context to allow chaining. 62 | func (ctx *Context) Set(key string, value interface{}) *Context { 63 | if nil == ctx.state { 64 | ctx.state = make(map[string]interface{}) 65 | } 66 | ctx.state[key] = value 67 | return ctx 68 | } 69 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # *b*ear: *e*mbeddable *a*pplication *r*outer 2 | 3 | [![Coverage status](https://coveralls.io/repos/ursiform/bear/badge.svg)](https://coveralls.io/r/ursiform/bear) 4 | 5 | [![API documentation](https://godoc.org/github.com/ursiform/bear?status.svg)](https://godoc.org/github.com/ursiform/bear) 6 | 7 | [`bear.Mux`](#type-mux) is an HTTP multiplexer. It uses a tree structure for fast routing, supports dynamic parameters, middleware, 8 | and accepts both native [`http.HandlerFunc`](http://golang.org/pkg/net/http/#HandlerFunc) or [`bear.HandlerFunc`](https://godoc.org/github.com/ursiform/bear#HandlerFunc), which accepts an extra [`*Context`](https://godoc.org/github.com/ursiform/bear#Context) argument 9 | that allows storing state (using the [`Get()`](https://godoc.org/github.com/ursiform/bear#Context.Get) and [`Set()`](https://godoc.org/github.com/ursiform/bear#Context.Set) methods) and calling the [`Next()`](https://godoc.org/github.com/ursiform/bear#Context.Next) middleware. 10 | 11 | ## Install 12 | ``` 13 | go get -u github.com/ursiform/bear 14 | ``` 15 | 16 | ## Quick start 17 | ### Create `main.go` in a new folder 18 | ```go 19 | package main 20 | 21 | import ( 22 | "fmt" 23 | "github.com/ursiform/bear" 24 | "log" 25 | "net/http" 26 | ) 27 | 28 | func logRequest(res http.ResponseWriter, req *http.Request, ctx *bear.Context) { 29 | log.Printf("%s %s\n", req.Method, req.URL.Path) 30 | ctx.Next() 31 | } 32 | func notFound(res http.ResponseWriter, req *http.Request, ctx *bear.Context) { 33 | res.Header().Set("Content-Type", "text/plain") 34 | res.WriteHeader(http.StatusNotFound) 35 | res.Write([]byte("Sorry, not found!\n")) 36 | } 37 | func one(res http.ResponseWriter, req *http.Request, ctx *bear.Context) { 38 | ctx.Set("one", "set in func one").Next() // Set() allows chaining 39 | } 40 | func two(res http.ResponseWriter, req *http.Request, ctx *bear.Context) { 41 | ctx.Set("two", "set in func two").Next() 42 | } 43 | func three(res http.ResponseWriter, req *http.Request, ctx *bear.Context) { 44 | greet := fmt.Sprintf("Hello, %s!\n", ctx.Params["user"]) 45 | first := ctx.Get("one").(string) // assert type: interface{} as string 46 | second := ctx.Get("two").(string) // assert type: interface{} as string 47 | state := fmt.Sprintf("state one: %s\nstate two: %s\n", first, second) 48 | res.Header().Set("Content-Type", "text/plain") 49 | res.Write([]byte(greet + state)) 50 | } 51 | func main() { 52 | mux := bear.New() 53 | mux.Always(logRequest) // log each incoming request 54 | mux.On("GET", "/hello/{user}", one, two, three) // dynamic URL param {user} 55 | mux.On("*", "/*", notFound) // wildcard method + path 56 | http.ListenAndServe(":1337", mux) 57 | } 58 | ``` 59 | ###Build and start a server 60 | ``` 61 | $ go build -o ./server && ./server 62 | ``` 63 | ###Test using `curl` 64 | ``` 65 | $ curl http://localhost:1337/hello/world 66 | Hello, world! 67 | state one: set in func one 68 | state two: set in func two 69 | $ curl http://localhost:1337/hello/world/foo 70 | Sorry, not found! 71 | ``` 72 | ###Check server log output 73 | ``` 74 | 2016/02/06 15:19:50 GET /hello/world 75 | 2016/02/06 15:20:00 GET /hello/world/foo 76 | ``` 77 | ## Test 78 | go test -cover github.com/ursiform/bear 79 | 80 | ## API 81 | 82 | [![API documentation](https://godoc.org/github.com/ursiform/bear?status.svg)](https://godoc.org/github.com/ursiform/bear) 83 | 84 | ## License 85 | [MIT License](LICENSE) 86 | -------------------------------------------------------------------------------- /handlerfunc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Afshin Darian. All rights reserved. 2 | // Use of this source code is governed by The MIT License 3 | // that can be found in the LICENSE file. 4 | 5 | package bear 6 | 7 | import ( 8 | "fmt" 9 | "net/http" 10 | ) 11 | 12 | // HandlerFunc is similar to http.HandlerFunc, except it requires 13 | // an extra argument for the *Context of a request. 14 | type HandlerFunc func(http.ResponseWriter, *http.Request, *Context) 15 | 16 | // handlerize takes one of handler formats that Mux accepts. 17 | // It returns a HandlerFunc, a flag indicating whether the HandlerFunc 18 | // can be follwed by other handlers, and any error that may have arisen in 19 | // conversion. 20 | func handlerize(function interface{}) (HandlerFunc, bool, error) { 21 | followable := true 22 | unfollowable := false 23 | switch function.(type) { 24 | case HandlerFunc: 25 | handler := function.(HandlerFunc) 26 | if handler == nil { 27 | return nil, unfollowable, fmt.Errorf("nil middleware") 28 | } else { 29 | return HandlerFunc(handler), followable, nil 30 | } 31 | case func(*Context): 32 | handler := function.(func(*Context)) 33 | if handler == nil { 34 | return nil, unfollowable, fmt.Errorf("nil middleware") 35 | } else { 36 | handler := HandlerFunc( 37 | func(_ http.ResponseWriter, _ *http.Request, ctx *Context) { 38 | handler(ctx) 39 | }) 40 | return handler, followable, nil 41 | } 42 | case func(http.ResponseWriter, *http.Request, *Context): 43 | handler := function.(func(http.ResponseWriter, *http.Request, *Context)) 44 | if handler == nil { 45 | return nil, unfollowable, fmt.Errorf("nil middleware") 46 | } else { 47 | return HandlerFunc(handler), followable, nil 48 | } 49 | case http.HandlerFunc: 50 | handler := function.(http.HandlerFunc) 51 | if handler == nil { 52 | return nil, unfollowable, fmt.Errorf("nil middleware") 53 | } else { 54 | handler := HandlerFunc( 55 | func(res http.ResponseWriter, req *http.Request, _ *Context) { 56 | handler(res, req) 57 | }) 58 | return handler, unfollowable, nil 59 | } 60 | case func(http.ResponseWriter, *http.Request): 61 | handler := function.(func(http.ResponseWriter, *http.Request)) 62 | if handler == nil { 63 | return nil, unfollowable, fmt.Errorf("nil middleware") 64 | } else { 65 | handler := HandlerFunc( 66 | func(res http.ResponseWriter, req *http.Request, _ *Context) { 67 | handler(res, req) 68 | }) 69 | return handler, unfollowable, nil 70 | } 71 | default: 72 | err := fmt.Errorf( 73 | "handler must match: %s, %s, or %s", 74 | "http.HandlerFunc", "bear.HandlerFunc", "func(*Context)") 75 | return nil, unfollowable, err 76 | } 77 | } 78 | 79 | func handlerizeLax( 80 | verb string, pattern string, functions []interface{}) ([]HandlerFunc, error) { 81 | var handlers []HandlerFunc 82 | unreachable := false 83 | for _, function := range functions { 84 | if unreachable { 85 | err := fmt.Errorf("bear: %s %s has unreachable middleware", verb, pattern) 86 | return nil, err 87 | } 88 | if handler, followable, err := handlerize(function); err != nil { 89 | return nil, fmt.Errorf("bear: %s %s: %s", verb, pattern, err) 90 | } else { 91 | if !followable { 92 | unreachable = true 93 | } 94 | handlers = append(handlers, handler) 95 | } 96 | } 97 | return handlers, nil 98 | } 99 | 100 | func handlerizeStrict(functions []interface{}) ([]HandlerFunc, error) { 101 | var handlers []HandlerFunc 102 | for _, function := range functions { 103 | switch function.(type) { 104 | case HandlerFunc: 105 | handler := function.(HandlerFunc) 106 | if handler == nil { 107 | return nil, fmt.Errorf("bear: nil middleware") 108 | } else { 109 | handlers = append(handlers, HandlerFunc(handler)) 110 | } 111 | case func(http.ResponseWriter, *http.Request, *Context): 112 | handler := function.(func(http.ResponseWriter, *http.Request, *Context)) 113 | if handler == nil { 114 | return nil, fmt.Errorf("bear: nil middleware") 115 | } else { 116 | handlers = append(handlers, HandlerFunc(handler)) 117 | } 118 | default: 119 | return nil, fmt.Errorf( 120 | "bear: handler must be a bear.HandlerFunc or match its signature") 121 | } 122 | } 123 | return handlers, nil 124 | } 125 | -------------------------------------------------------------------------------- /mux.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Afshin Darian. All rights reserved. 2 | // Use of this source code is governed by The MIT License 3 | // that can be found in the LICENSE file. 4 | 5 | package bear 6 | 7 | import ( 8 | "fmt" 9 | "net/http" 10 | "strings" 11 | ) 12 | 13 | // Mux is an HTTP multiplexer. It uses a tree structure for fast routing, 14 | // supports dynamic parameters, middleware, and accepts both native 15 | // http.HandlerFunc or bear.HandlerFunc, which accepts an extra *Context 16 | // argument that allows storing state (using the Get() and Set() methods) and 17 | // calling the Next() middleware. 18 | type Mux struct { 19 | trees [8]*tree // pointers to a tree for each HTTP verb 20 | always []HandlerFunc // list of handlers that run for all requests 21 | wild [8]bool // true if a tree has wildcard (requires back-references) 22 | } 23 | 24 | func parsePath(s string) (components []string, last int) { 25 | start, offset := 0, 0 26 | if slashr == s[0] { 27 | start = 1 28 | } 29 | if slashr == s[len(s)-1] { 30 | offset = 1 31 | } 32 | components = strings.SplitAfter(s, slash) 33 | if start == 1 || offset == 1 { 34 | components = components[start : len(components)-offset] 35 | } 36 | last = len(components) - 1 37 | if offset == 0 { 38 | components[last] = components[last] + slash 39 | } 40 | return components, last 41 | } 42 | 43 | // Always adds one or more handlers that will run before every single request. 44 | // Multiple calls to Always will append the current list of Always handlers with 45 | // the newly added handlers. 46 | // 47 | // Handlers must be either bear.HandlerFunc functions or functions that match 48 | // the bear.HandlerFunc signature and they should call (*Context).Next to 49 | // continue the response life cycle. 50 | func (mux *Mux) Always(handlers ...interface{}) error { 51 | if functions, err := handlerizeStrict(handlers); err != nil { 52 | return err 53 | } else { 54 | mux.always = append(mux.always, functions...) 55 | return err 56 | } 57 | } 58 | 59 | // On adds HTTP verb handler(s) for a URL pattern. The handler argument(s) 60 | // should either be http.HandlerFunc or bear.HandlerFunc or conform to the 61 | // signature of one of those two. NOTE: if http.HandlerFunc (or a function 62 | // conforming to its signature) is used no other handlers can *follow* it, i.e. 63 | // it is not middleware. 64 | // 65 | // It returns an error if it fails, but does not panic. Verb strings are 66 | // uppercase HTTP methods. There is a special verb "*" which can be used to 67 | // answer *all* HTTP methods. It is not uncommon for the verb "*" to return 68 | // errors, because a path may already have a listener associated with one HTTP 69 | // verb before the "*" verb is called. For example, this common and useful 70 | // pattern will return an error that can safely be ignored (see error example). 71 | // 72 | // Pattern strings are composed of tokens that are separated by "/" characters. 73 | // There are three kinds of tokens: 74 | // 75 | // 1. static path strings: "/foo/bar/baz/etc" 76 | // 77 | // 2. dynamically populated parameters "/foo/{bar}/baz" (where "bar" will be 78 | // populated in the *Context.Params) 79 | // 80 | // 3. wildcard tokens "/foo/bar/*" where * has to be the final token. 81 | // Parsed URL params are available in handlers via the Params map of the 82 | // *Context argument. 83 | // 84 | // Notes: 85 | // 86 | // 1. A trailing slash / is always implied, even when not explicit. 87 | // 88 | // 2. Wildcard (*) patterns are only matched if no other (more specific) 89 | // pattern matches. If multiple wildcard rules match, the most specific takes 90 | // precedence. 91 | // 92 | // 3. Wildcard patterns do *not* match empty strings: a request to /foo/bar will 93 | // not match the pattern "/foo/bar/*". The only exception to this is the root 94 | // wildcard pattern "/*" which will match the request path / if no root 95 | // handler exists. 96 | func (mux *Mux) On(verb string, pattern string, handlers ...interface{}) error { 97 | if verb == asterisk { 98 | errors := []string{} 99 | for _, verb := range verbs { 100 | if err := mux.On(verb, pattern, handlers...); err != nil { 101 | errors = append(errors, err.Error()) 102 | } 103 | } 104 | if 0 == len(errors) { 105 | return nil 106 | } 107 | return fmt.Errorf(strings.Join(errors, "\n")) 108 | } 109 | tr, wildcards := mux.tree(verb) 110 | if nil == tr { 111 | return fmt.Errorf("bear: %s isn't a valid HTTP verb", verb) 112 | } 113 | if functions, err := handlerizeLax(verb, pattern, handlers); err != nil { 114 | return err 115 | } else { 116 | tr.set(verb, pattern, functions, wildcards, &err) 117 | return err 118 | } 119 | } 120 | 121 | // ServeHTTP allows a Mux instance to conform to the http.Handler interface. 122 | func (mux *Mux) ServeHTTP(res http.ResponseWriter, req *http.Request) { 123 | tr, wildcards := mux.tree(req.Method) 124 | if nil == tr { // if req.Method is not found in HTTP verbs 125 | http.NotFound(res, req) 126 | return 127 | } 128 | // root is a special case because it is the top node in the tree 129 | if req.URL.Path == slash || req.URL.Path == empty { 130 | if nil != tr.handlers { // root match 131 | (&Context{ 132 | handler: -1, 133 | mux: mux, 134 | tree: tr, 135 | ResponseWriter: res, 136 | Request: req, 137 | }).Next() 138 | return 139 | } else if wild := tr.children[wildcard]; nil != wild { 140 | // root level wildcard pattern match 141 | (&Context{ 142 | handler: -1, 143 | mux: mux, 144 | tree: wild, 145 | ResponseWriter: res, 146 | Request: req, 147 | }).Next() 148 | return 149 | } 150 | http.NotFound(res, req) 151 | return 152 | } 153 | var key string 154 | components, last := parsePath(req.URL.Path) 155 | capacity := last + 1 // maximum number of params possible for this request 156 | context := &Context{ 157 | handler: -1, 158 | mux: mux, 159 | Request: req, 160 | ResponseWriter: res} 161 | current := &tr.children 162 | // If no wildcards: simpler, slightly faster logic (this if *always* returns). 163 | if !*wildcards { 164 | for index, component := range components { 165 | key = component 166 | if nil == *current { 167 | http.NotFound(res, req) 168 | return 169 | } else if nil == (*current)[key] { 170 | if nil == (*current)[dynamic] { 171 | http.NotFound(res, req) 172 | return 173 | } else { 174 | key = dynamic 175 | context.param((*current)[key].name, component, capacity) 176 | } 177 | } 178 | if index == last { 179 | if nil == (*current)[key].handlers { 180 | http.NotFound(res, req) 181 | } else { 182 | context.tree = (*current)[key] 183 | context.Next() 184 | } 185 | return 186 | } 187 | current = &(*current)[key].children 188 | } 189 | } 190 | // If wildcards exist, more involved logic. 191 | wild := tr.children[wildcard] 192 | for index, component := range components { 193 | key = component 194 | if nil == (*current)[key] { 195 | if nil == (*current)[dynamic] && nil == (*current)[wildcard] { 196 | if nil == wild { // there's no wildcard up the tree 197 | http.NotFound(res, req) 198 | } else { // wildcard pattern match 199 | context.tree = wild 200 | context.Next() 201 | } 202 | return 203 | } else { 204 | if nil != (*current)[wildcard] { 205 | // i.e. there is a more proximate wildcard 206 | wild = (*current)[wildcard] 207 | context.param(asterisk, 208 | strings.Join(components[index:], empty), capacity) 209 | } 210 | if nil != (*current)[dynamic] { 211 | key = dynamic 212 | context.param((*current)[key].name, component, capacity) 213 | } else { // wildcard pattern match 214 | context.tree = wild 215 | context.Next() 216 | return 217 | } 218 | } 219 | } 220 | if index == last { 221 | if nil == (*current)[key].handlers { 222 | http.NotFound(res, req) 223 | } else { // non-wildcard pattern match 224 | context.tree = (*current)[key] 225 | context.Next() 226 | } 227 | return 228 | } 229 | current = &(*current)[key].children 230 | if nil != (*current)[wildcard] { 231 | wild = (*current)[wildcard] // there's a more proximate wildcard 232 | context.param(asterisk, 233 | strings.Join(components[index:], empty), capacity) 234 | } 235 | } 236 | } 237 | 238 | func (mux *Mux) tree(name string) (*tree, *bool) { 239 | switch name { 240 | case "CONNECT": 241 | return mux.trees[0], &mux.wild[0] 242 | case "DELETE": 243 | return mux.trees[1], &mux.wild[1] 244 | case "GET": 245 | return mux.trees[2], &mux.wild[2] 246 | case "HEAD": 247 | return mux.trees[3], &mux.wild[3] 248 | case "OPTIONS": 249 | return mux.trees[4], &mux.wild[4] 250 | case "POST": 251 | return mux.trees[5], &mux.wild[5] 252 | case "PUT": 253 | return mux.trees[6], &mux.wild[6] 254 | case "TRACE": 255 | return mux.trees[7], &mux.wild[7] 256 | default: 257 | return nil, nil 258 | } 259 | } 260 | 261 | // New returns a pointer to a Mux instance 262 | func New() *Mux { 263 | mux := new(Mux) 264 | mux.trees = [8]*tree{{}, {}, {}, {}, {}, {}, {}, {}} 265 | mux.wild = [8]bool{false, false, false, false, false, false, false, false} 266 | return mux 267 | 268 | } 269 | -------------------------------------------------------------------------------- /bear-tests_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Afshin Darian. All rights reserved. 2 | // Use of this source code is governed by The MIT License 3 | // that can be found in the LICENSE file. 4 | 5 | package bear 6 | 7 | import ( 8 | "net/http" 9 | "net/http/httptest" 10 | "reflect" 11 | "testing" 12 | ) 13 | 14 | type tester func(*testing.T) 15 | 16 | // Generate tests for param requests using HandlerFunc. 17 | func paramBearTest( 18 | label string, method string, path string, 19 | pattern string, want map[string]string) tester { 20 | return func(t *testing.T) { 21 | var ( 22 | mux = New() 23 | req *http.Request 24 | res *httptest.ResponseRecorder 25 | ) 26 | req, _ = http.NewRequest(method, path, nil) 27 | res = httptest.NewRecorder() 28 | handler := func(_ http.ResponseWriter, _ *http.Request, ctx *Context) { 29 | if !reflect.DeepEqual(want, ctx.Params) { 30 | t.Errorf( 31 | "%s %s (%s) %s got %v want %v", 32 | method, path, pattern, label, ctx.Params, want) 33 | } 34 | } 35 | mux.On(method, pattern, HandlerFunc(handler)) 36 | mux.ServeHTTP(res, req) 37 | if res.Code != http.StatusOK { 38 | t.Errorf( 39 | "%s %s (%s) %s got %d want %d", 40 | method, path, pattern, label, res.Code, http.StatusOK) 41 | } 42 | } 43 | } 44 | 45 | // Generate tests for param requests using anonymous HandlerFunc 46 | // compatible functions. 47 | func paramBearAnonTest( 48 | label string, method string, path string, 49 | pattern string, want map[string]string) tester { 50 | return func(t *testing.T) { 51 | var ( 52 | mux = New() 53 | req *http.Request 54 | res *httptest.ResponseRecorder 55 | ) 56 | req, _ = http.NewRequest(method, path, nil) 57 | res = httptest.NewRecorder() 58 | handler := func(_ http.ResponseWriter, _ *http.Request, ctx *Context) { 59 | if !reflect.DeepEqual(want, ctx.Params) { 60 | t.Errorf( 61 | "%s %s (%s) %s got %v want %v", 62 | method, path, pattern, label, ctx.Params, want) 63 | } 64 | } 65 | mux.On(method, pattern, handler) 66 | mux.ServeHTTP(res, req) 67 | if res.Code != http.StatusOK { 68 | t.Errorf( 69 | "%s %s (%s) %s got %d want %d", 70 | method, path, pattern, label, res.Code, http.StatusOK) 71 | } 72 | } 73 | } 74 | 75 | // Generate tests for requests (i.e. no *Context) using http.HandlerFunc. 76 | func simpleHTTPTest( 77 | label string, method string, path string, pattern string, want int) tester { 78 | return func(t *testing.T) { 79 | var ( 80 | mux = New() 81 | req *http.Request 82 | res *httptest.ResponseRecorder 83 | ) 84 | req, _ = http.NewRequest(method, path, nil) 85 | res = httptest.NewRecorder() 86 | handler := func(http.ResponseWriter, *http.Request) {} 87 | mux.On(method, pattern, http.HandlerFunc(handler)) 88 | mux.ServeHTTP(res, req) 89 | if res.Code != want { 90 | t.Errorf( 91 | "%s %s (%s) %s got %d want %d", 92 | method, path, pattern, label, res.Code, want) 93 | } 94 | } 95 | } 96 | 97 | // Generate tests for param AND no-param requests (i.e. no *Context) using 98 | // anonymous http.HandlerFunc compatible func. 99 | func simpleHTTPAnonTest( 100 | label string, method string, path string, pattern string, want int) tester { 101 | return func(t *testing.T) { 102 | var ( 103 | mux = New() 104 | req *http.Request 105 | res *httptest.ResponseRecorder 106 | ) 107 | req, _ = http.NewRequest(method, path, nil) 108 | res = httptest.NewRecorder() 109 | mux.On(method, pattern, func(http.ResponseWriter, *http.Request) {}) 110 | mux.ServeHTTP(res, req) 111 | if res.Code != want { 112 | t.Errorf( 113 | "%s %s (%s) %s got %d want %d", 114 | method, path, pattern, label, res.Code, want) 115 | } 116 | } 117 | } 118 | 119 | // Generate tests for simple no-param requests using HandlerFunc. 120 | func simpleBearTest( 121 | label string, method string, path string, pattern string, want int) tester { 122 | return func(t *testing.T) { 123 | var ( 124 | mux = New() 125 | req *http.Request 126 | res *httptest.ResponseRecorder 127 | ) 128 | req, _ = http.NewRequest(method, path, nil) 129 | res = httptest.NewRecorder() 130 | handler := func(http.ResponseWriter, *http.Request, *Context) {} 131 | mux.On(method, pattern, HandlerFunc(handler)) 132 | mux.ServeHTTP(res, req) 133 | if res.Code != want { 134 | t.Errorf( 135 | "%s %s (%s) %s got %d want %d", 136 | method, path, pattern, label, res.Code, want) 137 | } 138 | } 139 | } 140 | 141 | // Generate tests for simple no-param requests using anonymous functions that 142 | // only accept a *Context param. 143 | func simpleContextTest( 144 | label string, method string, path string, pattern string, want int) tester { 145 | return func(t *testing.T) { 146 | var ( 147 | mux = New() 148 | req *http.Request 149 | res *httptest.ResponseRecorder 150 | ) 151 | req, _ = http.NewRequest(method, path, nil) 152 | res = httptest.NewRecorder() 153 | handler := func(*Context) {} 154 | mux.On(method, pattern, handler) 155 | mux.ServeHTTP(res, req) 156 | if res.Code != want { 157 | t.Errorf( 158 | "%s %s (%s) %s got %d want %d", 159 | method, path, pattern, label, res.Code, want) 160 | } 161 | } 162 | } 163 | 164 | // Generate tests for simple no-param requests using anonymous HandlerFunc 165 | // compatible functions. 166 | func simpleBearAnonTest(label, method, path, pattern string, want int) tester { 167 | return func(t *testing.T) { 168 | var ( 169 | mux = New() 170 | req *http.Request 171 | res *httptest.ResponseRecorder 172 | ) 173 | req, _ = http.NewRequest(method, path, nil) 174 | res = httptest.NewRecorder() 175 | handler := func(http.ResponseWriter, *http.Request, *Context) {} 176 | mux.On(method, pattern, handler) 177 | mux.ServeHTTP(res, req) 178 | if res.Code != want { 179 | t.Errorf( 180 | "%s %s (%s) %s got %d want %d", 181 | method, path, pattern, label, res.Code, want) 182 | } 183 | } 184 | } 185 | 186 | func TestBadHandler(t *testing.T) { 187 | mux := New() 188 | handlerBad := func(res http.ResponseWriter) {} 189 | if err := mux.On("*", "*", handlerBad); nil == err { 190 | t.Errorf("handlerBad should not be accepted") 191 | } 192 | } 193 | 194 | func TestBadVerbOn(t *testing.T) { 195 | mux := New() 196 | verb := "BLUB" 197 | handler := func(http.ResponseWriter, *http.Request) {} 198 | if err := mux.On(verb, "*", handler); nil == err { 199 | t.Errorf("%s should not be accepted", verb) 200 | } 201 | } 202 | 203 | func TestBadVerbServe(t *testing.T) { 204 | var ( 205 | method = "BLUB" 206 | mux = New() 207 | pattern = "/" 208 | path = "/" 209 | want = http.StatusNotFound 210 | req *http.Request 211 | res *httptest.ResponseRecorder 212 | ) 213 | handler := func(res http.ResponseWriter, req *http.Request) {} 214 | mux.On("*", pattern, handler) 215 | req, _ = http.NewRequest(method, path, nil) 216 | res = httptest.NewRecorder() 217 | mux.ServeHTTP(res, req) 218 | if status := res.Code; status != want { 219 | t.Errorf("%s %s got %d want %d", method, path, res.Code, want) 220 | } 221 | } 222 | 223 | func TestDuplicateFailure(t *testing.T) { 224 | var ( 225 | handler HandlerFunc 226 | mux = New() 227 | pattern = "/foo/{bar}" 228 | ) 229 | handler = HandlerFunc( 230 | func(http.ResponseWriter, *http.Request, *Context) {}) 231 | for _, verb := range verbs { 232 | if err := mux.On(verb, pattern, handler); err != nil { 233 | t.Error(err) 234 | } else if err := mux.On(verb, pattern, handler); err == nil { 235 | t.Errorf( 236 | "%s %s addition must fail because it is a duplicate", 237 | verb, pattern) 238 | } 239 | } 240 | } 241 | 242 | func TestDuplicateFailureRoot(t *testing.T) { 243 | var ( 244 | handler HandlerFunc 245 | mux = New() 246 | pattern = "/" 247 | ) 248 | handler = HandlerFunc( 249 | func(http.ResponseWriter, *http.Request, *Context) {}) 250 | for _, verb := range verbs { 251 | if err := mux.On(verb, pattern, handler); err != nil { 252 | t.Error(err) 253 | } else if err := mux.On(verb, pattern, handler); err == nil { 254 | t.Errorf( 255 | "%s %s addition must fail because it is a duplicate", 256 | verb, pattern) 257 | } 258 | } 259 | } 260 | 261 | func TestMiddleware(t *testing.T) { 262 | var ( 263 | middlewares int = 4 264 | mux *Mux = New() 265 | params map[string]string 266 | path string = "/foo/BAR/baz/QUX" 267 | pattern string = "/foo/{bar}/baz/{qux}" 268 | state map[string]interface{} 269 | stateOne = "one" 270 | stateTwo = "two" 271 | stateThree = "three" 272 | stateNil = "nope" 273 | ) 274 | params = map[string]string{"bar": "BAR", "qux": "QUX"} 275 | state = map[string]interface{}{"one": 1, "two": 2, "three": 3} 276 | run := func(method string) { 277 | var ( 278 | req *http.Request 279 | res *httptest.ResponseRecorder 280 | visited int 281 | ) 282 | one := func(_ http.ResponseWriter, _ *http.Request, ctx *Context) { 283 | visited++ 284 | if ctx.Get(stateNil) != nil { 285 | t.Errorf( 286 | "%s %s (%s) got %v want %v", 287 | method, path, pattern, ctx.Get(stateNil), nil) 288 | } 289 | ctx.Set("one", 1).Next() 290 | } 291 | two := func(_ http.ResponseWriter, _ *http.Request, ctx *Context) { 292 | visited++ 293 | ctx.Set("two", 2).Next() 294 | } 295 | three := func(ctx *Context) { 296 | visited++ 297 | ctx.Set("three", 3).Next() 298 | } 299 | last := func(_ http.ResponseWriter, _ *http.Request, ctx *Context) { 300 | visited++ 301 | if !reflect.DeepEqual(params, ctx.Params) { 302 | t.Errorf( 303 | "%s %s (%s) got %v want %v", 304 | method, path, pattern, ctx.Params, params) 305 | } 306 | if ctx.Get(stateOne) != state[stateOne] { 307 | t.Errorf( 308 | "%s %s (%s) got %v want %v", 309 | method, path, pattern, ctx.Get(stateOne), state[stateOne]) 310 | } 311 | if ctx.Get(stateTwo) != state[stateTwo] { 312 | t.Errorf( 313 | "%s %s (%s) got %v want %v", 314 | method, path, pattern, ctx.Get(stateTwo), state[stateTwo]) 315 | } 316 | if ctx.Get(stateThree) != state[stateThree] { 317 | t.Errorf( 318 | "%s %s (%s) got %v want %v", 319 | method, path, pattern, ctx.Get(stateThree), state[stateThree]) 320 | } 321 | } 322 | req, _ = http.NewRequest(method, path, nil) 323 | res = httptest.NewRecorder() 324 | mux.On(method, pattern, one, two, three, last) 325 | mux.ServeHTTP(res, req) 326 | if visited != middlewares { 327 | t.Errorf( 328 | "%s %s (%s) expected %d middlewares, visited %d", 329 | method, path, pattern, middlewares, visited) 330 | } 331 | } 332 | for _, verb := range verbs { 333 | run(verb) 334 | } 335 | } 336 | 337 | func TestMiddlewareRejectionA(t *testing.T) { 338 | mux := New() 339 | one := func(http.ResponseWriter, *http.Request, *Context) {} 340 | two := func(http.ResponseWriter, *http.Request) {} 341 | last := func(http.ResponseWriter, *http.Request, *Context) {} 342 | if err := mux.On("*", "*", one, two, last); err == nil { 343 | t.Errorf("middleware with wrong signature was accepted") 344 | } 345 | } 346 | 347 | func TestMiddlewareRejectionB(t *testing.T) { 348 | mux := New() 349 | one := http.HandlerFunc(func(http.ResponseWriter, *http.Request) {}) 350 | two := HandlerFunc( 351 | func(http.ResponseWriter, *http.Request, *Context) {}) 352 | last := HandlerFunc( 353 | func(http.ResponseWriter, *http.Request, *Context) {}) 354 | if err := mux.On("*", "*", one, two, last); err == nil { 355 | t.Errorf("middleware with wrong signature was accepted") 356 | } 357 | } 358 | 359 | func TestMiddlewareRejectionC(t *testing.T) { 360 | mux := New() 361 | one := http.HandlerFunc(func(http.ResponseWriter, *http.Request) {}) 362 | two := http.HandlerFunc(func(http.ResponseWriter, *http.Request) {}) 363 | if err := mux.On("*", "*", one, two); err == nil { 364 | t.Errorf("middleware with wrong signature was accepted") 365 | } 366 | } 367 | 368 | func TestMiddlewareRejectionD(t *testing.T) { 369 | mux := New() 370 | one := http.HandlerFunc(func(http.ResponseWriter, *http.Request) {}) 371 | two := func(http.ResponseWriter, *http.Request) {} 372 | if err := mux.On("*", "*", one, two); err == nil { 373 | t.Errorf("middleware with wrong signature was accepted") 374 | } 375 | } 376 | 377 | func TestMiddlewareRejectionE(t *testing.T) { 378 | mux := New() 379 | var ( 380 | one HandlerFunc 381 | two http.HandlerFunc 382 | three func(http.ResponseWriter, *http.Request) 383 | four func(http.ResponseWriter, *http.Request, *Context) 384 | five func(*Context) 385 | ) 386 | if err := mux.On("GET", "/", one); err == nil { 387 | t.Errorf("nil middleware was accepted") 388 | } 389 | if err := mux.On("GET", "/", two); err == nil { 390 | t.Errorf("nil middleware was accepted") 391 | } 392 | if err := mux.On("GET", "/", three); err == nil { 393 | t.Errorf("nil middleware was accepted") 394 | } 395 | if err := mux.On("GET", "/", four); err == nil { 396 | t.Errorf("nil middleware was accepted") 397 | } 398 | if err := mux.On("GET", "/", five); err == nil { 399 | t.Errorf("nil middleware was accepted") 400 | } 401 | } 402 | 403 | func TestNoHandlers(t *testing.T) { 404 | var ( 405 | mux *Mux = New() 406 | path string = "/foo/bar" 407 | req *http.Request 408 | res *httptest.ResponseRecorder 409 | want int = http.StatusNotFound 410 | ) 411 | for _, verb := range verbs { 412 | req, _ = http.NewRequest(verb, path, nil) 413 | res = httptest.NewRecorder() 414 | mux.ServeHTTP(res, req) 415 | if res.Code != want { 416 | t.Errorf("%s %s got %d want %d", verb, path, res.Code, want) 417 | } 418 | } 419 | } 420 | 421 | func TestNotFoundCustom(t *testing.T) { 422 | var ( 423 | method string = "GET" 424 | mux *Mux = New() 425 | pathFound string = "/foo/bar" 426 | pathLost string = "/foo/bar/baz" 427 | patternFound string = "/foo/bar" 428 | patternLost string = "/*" 429 | req *http.Request 430 | res *httptest.ResponseRecorder 431 | wantFound int = http.StatusOK 432 | wantLost int = http.StatusTeapot 433 | ) 434 | handlerFound := http.HandlerFunc(func(http.ResponseWriter, *http.Request) { 435 | res.WriteHeader(http.StatusOK) 436 | res.Write([]byte("found!")) 437 | }) 438 | handlerLost := http.HandlerFunc(func(http.ResponseWriter, *http.Request) { 439 | res.WriteHeader(http.StatusTeapot) 440 | res.Write([]byte("not found!")) 441 | }) 442 | // Test found to make sure wildcard doesn't overtake everything. 443 | req, _ = http.NewRequest(method, pathFound, nil) 444 | res = httptest.NewRecorder() 445 | mux.On(method, patternFound, handlerFound) 446 | mux.ServeHTTP(res, req) 447 | if res.Code != wantFound { 448 | t.Errorf( 449 | "%s %s (%s) got %d want %d", 450 | method, pathFound, patternFound, res.Code, wantFound) 451 | } 452 | // Test lost to make sure wildcard can gets non-pattern-matching paths. 453 | req, _ = http.NewRequest(method, pathLost, nil) 454 | res = httptest.NewRecorder() 455 | mux.On(method, patternLost, handlerLost) 456 | mux.ServeHTTP(res, req) 457 | if res.Code != wantLost { 458 | t.Errorf( 459 | "%s %s (%s) got %d want %d", 460 | method, pathLost, patternLost, res.Code, wantLost) 461 | } 462 | } 463 | 464 | func TestNotFoundNoParams(t *testing.T) { 465 | var ( 466 | path = "/foo/bar" 467 | pattern = "/foo" 468 | want = http.StatusNotFound 469 | ) 470 | for _, verb := range verbs { 471 | simpleHTTPTest( 472 | "http.HandlerFunc", 473 | verb, path, pattern, want)(t) 474 | simpleHTTPAnonTest( 475 | "anonymous http.HandlerFunc", 476 | verb, path, pattern, want)(t) 477 | simpleBearTest( 478 | "HandlerFunc", 479 | verb, path, pattern, want)(t) 480 | simpleBearAnonTest( 481 | "anonymous http.HandlerFunc", 482 | verb, path, pattern, want)(t) 483 | simpleContextTest( 484 | "anonymous *Context func", 485 | verb, path, pattern, want)(t) 486 | } 487 | } 488 | 489 | func TestNotFoundParams(t *testing.T) { 490 | var ( 491 | path = "/foo/BAR/baz" 492 | pattern = "/foo/{bar}/baz/{qux}" 493 | want = http.StatusNotFound 494 | ) 495 | for _, verb := range verbs { 496 | simpleHTTPTest( 497 | "http.HandlerFunc", 498 | verb, path, pattern, want)(t) 499 | simpleHTTPAnonTest( 500 | "anonymous http.HandlerFunc", 501 | verb, path, pattern, want)(t) 502 | simpleBearTest( 503 | "HandlerFunc", 504 | verb, path, pattern, want)(t) 505 | simpleBearAnonTest( 506 | "anonymous http.HandlerFunc", 507 | verb, path, pattern, want)(t) 508 | simpleContextTest( 509 | "anonymous *Context func", 510 | verb, path, pattern, want)(t) 511 | } 512 | } 513 | 514 | func TestNotFoundRoot(t *testing.T) { 515 | var ( 516 | method string = "GET" 517 | mux *Mux = New() 518 | path string = "/" 519 | req *http.Request 520 | res *httptest.ResponseRecorder 521 | want int = http.StatusNotFound 522 | ) 523 | req, _ = http.NewRequest(method, path, nil) 524 | res = httptest.NewRecorder() 525 | mux.ServeHTTP(res, req) 526 | if res.Code != want { 527 | t.Errorf("%s %s got %d want %d", method, path, res.Code, want) 528 | } 529 | } 530 | 531 | func TestNotFoundWildA(t *testing.T) { 532 | var ( 533 | path = "/foo" 534 | pattern = "/foo/*" 535 | want = http.StatusNotFound 536 | ) 537 | for _, verb := range verbs { 538 | simpleHTTPTest( 539 | "http.HandlerFunc", 540 | verb, path, pattern, want)(t) 541 | simpleHTTPAnonTest( 542 | "anonymous http.HandlerFunc", 543 | verb, path, pattern, want)(t) 544 | simpleBearTest( 545 | "HandlerFunc", 546 | verb, path, pattern, want)(t) 547 | simpleBearAnonTest( 548 | "anonymous http.HandlerFunc", 549 | verb, path, pattern, want)(t) 550 | simpleContextTest( 551 | "anonymous *Context func", 552 | verb, path, pattern, want)(t) 553 | } 554 | } 555 | 556 | func TestNotFoundWildB(t *testing.T) { 557 | var ( 558 | method string = "GET" 559 | mux *Mux = New() 560 | path string = "/bar/baz" 561 | patternOne string = "/foo/*" 562 | patternTwo string = "/bar" 563 | req *http.Request 564 | res *httptest.ResponseRecorder 565 | want int = http.StatusNotFound 566 | ) 567 | handler := func(http.ResponseWriter, *http.Request) {} 568 | mux.On(method, patternOne, handler) 569 | mux.On(method, patternTwo, handler) 570 | req, _ = http.NewRequest(method, path, nil) 571 | res = httptest.NewRecorder() 572 | mux.ServeHTTP(res, req) 573 | if res.Code != want { 574 | t.Errorf("%s %s got %d want %d", method, path, res.Code, want) 575 | } 576 | } 577 | 578 | func TestOKMultiRuleParams(t *testing.T) { 579 | var ( 580 | method string = "GET" 581 | mux *Mux = New() 582 | path string = "/bar/baz" 583 | patternOne string = "/foo/*" 584 | patternTwo string = "/bar/baz" 585 | req *http.Request 586 | res *httptest.ResponseRecorder 587 | want int = http.StatusOK 588 | ) 589 | handler := func(http.ResponseWriter, *http.Request) {} 590 | mux.On(method, patternOne, handler) 591 | mux.On(method, patternTwo, handler) 592 | req, _ = http.NewRequest(method, path, nil) 593 | res = httptest.NewRecorder() 594 | mux.ServeHTTP(res, req) 595 | if res.Code != want { 596 | t.Errorf("%s %s got %d want %d", method, path, res.Code, want) 597 | } 598 | } 599 | 600 | func TestOKNoParams(t *testing.T) { 601 | var ( 602 | path = "/foo/bar" 603 | pattern = "/foo/bar" 604 | want = http.StatusOK 605 | ) 606 | for _, verb := range verbs { 607 | simpleHTTPTest( 608 | "http.HandlerFunc", 609 | verb, path, pattern, want)(t) 610 | simpleHTTPAnonTest( 611 | "anonymous http.HandlerFunc", 612 | verb, path, pattern, want)(t) 613 | simpleBearTest( 614 | "HandlerFunc", 615 | verb, path, pattern, want)(t) 616 | simpleBearAnonTest( 617 | "anonymous http.HandlerFunc", 618 | verb, path, pattern, want)(t) 619 | } 620 | } 621 | 622 | func TestOKParams(t *testing.T) { 623 | var ( 624 | path = "/foo/BAR/baz/QUX" 625 | pattern = "/foo/{bar}/baz/{qux}" 626 | want map[string]string 627 | ) 628 | want = map[string]string{"bar": "BAR", "qux": "QUX"} 629 | for _, verb := range verbs { 630 | simpleHTTPTest( 631 | "http.HandlerFunc", 632 | verb, path, pattern, http.StatusOK)(t) 633 | simpleHTTPAnonTest( 634 | "anonymous http.HandlerFunc", 635 | verb, path, pattern, http.StatusOK)(t) 636 | paramBearTest( 637 | "HandlerFunc", 638 | verb, path, pattern, want)(t) 639 | paramBearAnonTest( 640 | "anonymous http.HandlerFunc", 641 | verb, path, pattern, want)(t) 642 | } 643 | } 644 | 645 | func TestOKParamsTrailingSlash(t *testing.T) { 646 | var ( 647 | path = "/foo/BAR/baz/QUX/" 648 | pattern = "/foo/{bar}/baz/{qux}" 649 | want map[string]string 650 | ) 651 | want = map[string]string{"bar": "BAR", "qux": "QUX"} 652 | for _, verb := range verbs { 653 | simpleHTTPTest( 654 | "http.HandlerFunc", 655 | verb, path, pattern, http.StatusOK)(t) 656 | simpleHTTPAnonTest( 657 | "anonymous http.HandlerFunc", 658 | verb, path, pattern, http.StatusOK)(t) 659 | paramBearTest( 660 | "HandlerFunc", 661 | verb, path, pattern, want)(t) 662 | paramBearAnonTest( 663 | "anonymous http.HandlerFunc", 664 | verb, path, pattern, want)(t) 665 | } 666 | } 667 | 668 | func TestOKRoot(t *testing.T) { 669 | var ( 670 | path = "/" 671 | pattern = "/" 672 | want = http.StatusOK 673 | ) 674 | for _, verb := range verbs { 675 | simpleHTTPTest( 676 | "http.HandlerFunc", 677 | verb, path, pattern, want)(t) 678 | simpleHTTPAnonTest( 679 | "anonymous http.HandlerFunc", 680 | verb, path, pattern, want)(t) 681 | simpleBearTest( 682 | "HandlerFunc", 683 | verb, path, pattern, want)(t) 684 | simpleBearAnonTest( 685 | "anonymous http.HandlerFunc", 686 | verb, path, pattern, want)(t) 687 | } 688 | } 689 | 690 | func TestOKWildRoot(t *testing.T) { 691 | var ( 692 | path = "/" 693 | pattern = "*" 694 | want = http.StatusOK 695 | ) 696 | for _, verb := range verbs { 697 | simpleHTTPTest( 698 | "http.HandlerFunc", 699 | verb, path, pattern, want)(t) 700 | simpleHTTPAnonTest( 701 | "anonymous http.HandlerFunc", 702 | verb, path, pattern, want)(t) 703 | simpleBearTest( 704 | "HandlerFunc", 705 | verb, path, pattern, want)(t) 706 | simpleBearAnonTest( 707 | "anonymous http.HandlerFunc", 708 | verb, path, pattern, want)(t) 709 | } 710 | } 711 | 712 | func TestSanitizePatternPrefixSuffix(t *testing.T) { 713 | var ( 714 | method = "GET" 715 | mux = New() 716 | pattern = "foo/{bar}/*" 717 | path = "/foo/ABC/baz" 718 | want string = "/foo/{bar}/*/" 719 | req *http.Request 720 | res *httptest.ResponseRecorder 721 | ) 722 | handler := func(res http.ResponseWriter, _ *http.Request, ctx *Context) { 723 | res.WriteHeader(http.StatusOK) 724 | res.Write([]byte(ctx.tree.pattern)) 725 | } 726 | mux.On(method, pattern, handler) 727 | req, _ = http.NewRequest(method, path, nil) 728 | res = httptest.NewRecorder() 729 | mux.ServeHTTP(res, req) 730 | if body := res.Body.String(); body != want { 731 | t.Errorf("%s %s (%s) got %s want %s", method, path, pattern, body, want) 732 | } 733 | } 734 | 735 | func TestSanitizePatternDoubleSlash(t *testing.T) { 736 | var ( 737 | method = "GET" 738 | mux = New() 739 | pattern = "///foo///{bar}////*//" 740 | path = "/foo/ABC/baz" 741 | want string = "/foo/{bar}/*/" 742 | req *http.Request 743 | res *httptest.ResponseRecorder 744 | ) 745 | handler := func(res http.ResponseWriter, _ *http.Request, ctx *Context) { 746 | res.WriteHeader(http.StatusOK) 747 | res.Write([]byte(ctx.tree.pattern)) 748 | } 749 | mux.On(method, pattern, handler) 750 | req, _ = http.NewRequest(method, path, nil) 751 | res = httptest.NewRecorder() 752 | mux.ServeHTTP(res, req) 753 | if body := res.Body.String(); body != want { 754 | t.Errorf("%s %s (%s) got %s want %s", method, path, pattern, body, want) 755 | } 756 | } 757 | 758 | func TestUnreachableA(t *testing.T) { 759 | mux := New() 760 | one := func(http.ResponseWriter, *http.Request, *Context) {} 761 | two := func(http.ResponseWriter, *http.Request) {} 762 | three := func(http.ResponseWriter, *http.Request, *Context) {} 763 | err := mux.On("*", "*", one, two, three) 764 | if nil == err { 765 | t.Errorf("unreachable A") 766 | } 767 | } 768 | 769 | func TestUnreachableB(t *testing.T) { 770 | mux := New() 771 | one := func(http.ResponseWriter, *http.Request, *Context) {} 772 | two := func(http.ResponseWriter, *http.Request) {} 773 | three := func(*Context) {} 774 | err := mux.On("*", "*", one, two, three) 775 | if nil == err { 776 | t.Errorf("unreachable B") 777 | } 778 | } 779 | 780 | func TestWildcardCompeting(t *testing.T) { 781 | var ( 782 | method string = "GET" 783 | mux *Mux = New() 784 | patternOne string = "/*" 785 | pathOne string = "/bar/baz" 786 | wantOne string = "bar/baz" 787 | patternTwo string = "/foo/*" 788 | pathTwo string = "/foo/baz" 789 | wantTwo string = "baz" 790 | patternThree string = "/foo/bar/*" 791 | pathThree string = "/foo/bar/bar/baz" 792 | wantThree string = "bar/baz" 793 | req *http.Request 794 | res *httptest.ResponseRecorder 795 | ) 796 | handler := func(res http.ResponseWriter, _ *http.Request, ctx *Context) { 797 | res.WriteHeader(http.StatusOK) 798 | res.Write([]byte(ctx.Params["*"])) 799 | } 800 | mux.On(method, patternOne, handler) 801 | mux.On(method, patternTwo, handler) 802 | mux.On(method, patternThree, handler) 803 | req, _ = http.NewRequest(method, pathOne, nil) 804 | res = httptest.NewRecorder() 805 | mux.ServeHTTP(res, req) 806 | if body := res.Body.String(); body != wantOne { 807 | t.Errorf( 808 | "%s %s (%s) got %s want %s", 809 | method, pathOne, patternOne, body, wantOne) 810 | } 811 | req, _ = http.NewRequest(method, pathTwo, nil) 812 | res = httptest.NewRecorder() 813 | mux.ServeHTTP(res, req) 814 | if body := res.Body.String(); body != wantTwo { 815 | t.Errorf( 816 | "%s %s (%s) got %s want %s", 817 | method, pathTwo, patternTwo, body, wantTwo) 818 | } 819 | req, _ = http.NewRequest(method, pathThree, nil) 820 | res = httptest.NewRecorder() 821 | mux.ServeHTTP(res, req) 822 | if body := res.Body.String(); body != wantThree { 823 | t.Errorf( 824 | "%s %s (%s) got %s want %s", 825 | method, pathThree, patternThree, body, wantThree) 826 | } 827 | } 828 | 829 | func TestWildcardMethod(t *testing.T) { 830 | var ( 831 | mux = New() 832 | path = "/foo/bar" 833 | pattern = "/foo/bar" 834 | req *http.Request 835 | res *httptest.ResponseRecorder 836 | want int = http.StatusOK 837 | ) 838 | mux.On( 839 | "*", 840 | pattern, 841 | func(http.ResponseWriter, *http.Request, *Context) {}) 842 | for _, verb := range verbs { 843 | req, _ = http.NewRequest(verb, path, nil) 844 | res = httptest.NewRecorder() 845 | mux.ServeHTTP(res, req) 846 | if res.Code != want { 847 | t.Errorf( 848 | "%s %s (%s) got %d want %d", 849 | verb, path, pattern, res.Code, want) 850 | } 851 | } 852 | } 853 | 854 | func TestWildcardMethodWarning(t *testing.T) { 855 | var ( 856 | mux = New() 857 | pattern = "/foo/bar/" 858 | verb string = "GET" 859 | ) 860 | handler := func(http.ResponseWriter, *http.Request, *Context) {} 861 | if err := mux.On(verb, pattern, handler); err != nil { 862 | t.Errorf("%s %s should have registered", verb, pattern) 863 | } 864 | if err := mux.On("*", pattern, handler); err == nil { 865 | t.Errorf("* %s should have registered, but with an error", pattern) 866 | } 867 | } 868 | 869 | func TestWildcardNotLast(t *testing.T) { 870 | var ( 871 | mux = New() 872 | pattern = "/foo/*/bar" 873 | ) 874 | handler := func(res http.ResponseWriter, req *http.Request) {} 875 | err := mux.On("*", pattern, handler) 876 | if err == nil { 877 | t.Errorf( 878 | "wildcard pattern (%s) with non-final wildcard was accepted", 879 | pattern) 880 | } 881 | } 882 | 883 | func TestWildcardParams(t *testing.T) { 884 | var ( 885 | method = "GET" 886 | mux = New() 887 | pattern = "/foo/{bar}/*" 888 | path = "/foo/ABC/baz" 889 | want string = "ABC" 890 | req *http.Request 891 | res *httptest.ResponseRecorder 892 | ) 893 | handler := func(res http.ResponseWriter, _ *http.Request, ctx *Context) { 894 | res.WriteHeader(http.StatusOK) 895 | res.Write([]byte(ctx.Params["bar"])) 896 | } 897 | mux.On(method, pattern, handler) 898 | req, _ = http.NewRequest(method, path, nil) 899 | res = httptest.NewRecorder() 900 | mux.ServeHTTP(res, req) 901 | if body := res.Body.String(); body != want { 902 | t.Errorf("%s %s (%s) got %s want %s", method, path, pattern, body, want) 903 | } 904 | } 905 | 906 | func TestAlways(t *testing.T) { 907 | var ( 908 | mux *Mux = New() 909 | pattern = "/foo" 910 | method = "GET" 911 | path = "/foo" 912 | req *http.Request 913 | res *httptest.ResponseRecorder 914 | keyOne = "one" 915 | stateOne = 1 916 | keyTwo = "two" 917 | stateTwo = 2 918 | ) 919 | one := func(_ http.ResponseWriter, _ *http.Request, ctx *Context) { 920 | ctx.Set(keyOne, stateOne).Next() 921 | } 922 | two := func(_ http.ResponseWriter, _ *http.Request, ctx *Context) { 923 | ctx.Set(keyTwo, stateTwo).Next() 924 | } 925 | three := func(res http.ResponseWriter, _ *http.Request, ctx *Context) { 926 | first := reflect.DeepEqual(ctx.Get(keyOne), stateOne) 927 | second := reflect.DeepEqual(ctx.Get(keyTwo), stateTwo) 928 | if !first || !second { 929 | t.Errorf("Always middlewares did not execute before On middleware") 930 | } 931 | } 932 | mux.Always(one) 933 | mux.Always(HandlerFunc(two)) 934 | mux.On(method, pattern, three) 935 | req, _ = http.NewRequest(method, path, nil) 936 | res = httptest.NewRecorder() 937 | mux.ServeHTTP(res, req) 938 | } 939 | 940 | func TestAlwaysBeforeNotFound(t *testing.T) { 941 | var ( 942 | mux *Mux = New() 943 | pattern = "/foo" 944 | method = "GET" 945 | path = "/bar" 946 | req *http.Request 947 | res *httptest.ResponseRecorder 948 | keyOne = "one" 949 | stateOne = 1 950 | ) 951 | always := func(_ http.ResponseWriter, _ *http.Request, ctx *Context) { 952 | ctx.Set(keyOne, stateOne).Next() 953 | } 954 | handler := func(_ http.ResponseWriter, _ *http.Request, _ *Context) { 955 | t.Errorf("handler should not be fired because path != pattern") 956 | } 957 | notFound := func(_ http.ResponseWriter, _ *http.Request, ctx *Context) { 958 | first := reflect.DeepEqual(ctx.Get(keyOne), stateOne) 959 | if !first { 960 | t.Errorf("Always middleware did not execute before notFound") 961 | } 962 | } 963 | mux.Always(always) 964 | mux.On(method, pattern, handler) 965 | mux.On("*", "/*", notFound) 966 | req, _ = http.NewRequest(method, path, nil) 967 | res = httptest.NewRecorder() 968 | mux.ServeHTTP(res, req) 969 | } 970 | 971 | func TestAlwaysRejection(t *testing.T) { 972 | var ( 973 | mux *Mux = New() 974 | one HandlerFunc 975 | two http.HandlerFunc 976 | three func(http.ResponseWriter, *http.Request) 977 | four func(http.ResponseWriter, *http.Request, *Context) 978 | ) 979 | if err := mux.Always(one); err == nil { 980 | t.Errorf("Always requires non-nil handler") 981 | } 982 | if err := mux.Always(two); err == nil { 983 | t.Errorf("Always requires non-nil handler") 984 | } 985 | if err := mux.Always(three); err == nil { 986 | t.Errorf("Always requires non-nil handler") 987 | } 988 | if err := mux.Always(four); err == nil { 989 | t.Errorf("Always requires non-nil handler") 990 | } 991 | } 992 | --------------------------------------------------------------------------------