├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── bone.go ├── bone_bench_test.go ├── bone_context_test.go ├── bone_test.go ├── example ├── 001 │ └── example.go ├── 002 │ ├── assets │ │ ├── assets.txt │ │ └── style.css │ ├── example.go │ └── index.html ├── 003 │ ├── example.go │ ├── go.mod │ └── go.sum └── 004 │ └── example.go ├── go.mod ├── helper.go ├── helper_15.go ├── helper_17.go ├── helper_17_test.go ├── helper_test.go ├── mux.go ├── mux_test.go ├── route.go ├── route_test.go └── validator.go /.gitignore: -------------------------------------------------------------------------------- 1 | tags 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - tip 5 | 6 | script: 7 | - go test -v ./... 8 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## Changelog 2 | 3 | #### Update 10 September 2016 4 | 5 | - Add support for go1.7 net.Context 6 | 7 | #### Update 25 September 2015 8 | 9 | - Add support for Sub router 10 | 11 | Example : 12 | ``` go 13 | func main() { 14 | mux := bone.New() 15 | sub := mux.NewRouter() 16 | 17 | sub.GetFunc("/test/example", func(rw http.ResponseWriter, req *http.Request) { 18 | rw.Write([]byte("From sub router !")) 19 | }) 20 | 21 | mux.SubRoute("/api", sub) 22 | 23 | http.ListenAndServe(":8080", mux) 24 | } 25 | 26 | ``` 27 | 28 | 29 | #### Update 26 April 2015 30 | 31 | - Add Support for REGEX parameters, using ` # ` instead of ` : `. 32 | - Add Mux method ` mux.GetFunc(), mux.PostFunc(), etc ... `, takes ` http.HandlerFunc ` instead of ` http.Handler `. 33 | 34 | Example : 35 | ``` go 36 | func main() { 37 | mux.GetFunc("/route/#var^[a-z]$", handler) 38 | } 39 | 40 | func handler(rw http.ResponseWriter, req *http.Request) { 41 | bone.GetValue(req, "var") 42 | } 43 | ``` 44 | 45 | #### Update 29 january 2015 46 | 47 | - Speed improvement for url Parameters, from ```~ 1500 ns/op ``` to ```~ 1000 ns/op ```. 48 | 49 | #### Update 25 december 2014 50 | 51 | After trying to find a way of using the default url.Query() for route parameters, i decide to change the way bone is dealing with this. url.Query() is too slow for good router performance. 52 | So now to get the parameters value in your handler, you need to use 53 | ` bone.GetValue(req, key) ` instead of ` req.Url.Query().Get(key) `. 54 | This change give a big speed improvement for every kind of application using route parameters, like ~80x faster ... 55 | Really sorry for breaking things, but i think it's worth it. 56 | 57 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at piinky05@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Contributing 2 | 3 | 1. Fork it 4 | 2. Create your feature branch (git checkout -b my-new-feature) 5 | 3. Write Tests! 6 | 4. Commit your changes (git commit -am 'Add some feature') 7 | 5. Push to the branch (git push origin my-new-feature) 8 | 6. Create new Pull Request 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 CodingFerret 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 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | bone [](http://godoc.org/github.com/go-zoo/bone) [](https://travis-ci.org/go-zoo/bone) [](https://goreportcard.com/report/go-zoo/bone) 2 | ======= 3 | 4 | ## What is bone ? 5 | 6 | Bone is a lightweight and lightning fast HTTP Multiplexer for Golang. It support : 7 | 8 | - URL Parameters 9 | - REGEX Parameters 10 | - Wildcard routes 11 | - Router Prefix 12 | - Route params validators 13 | - Sub Router, `mux.SubRoute()`, support most standard router (bone, gorilla/mux, httpRouter etc...) 14 | - Http method declaration 15 | - Support for `http.Handler` and `http.HandlerFunc` 16 | - Custom NotFound handler 17 | - Respect the Go standard `http.Handler` interface 18 | 19 |  20 | 21 | ## Speed 22 | 23 | ``` 24 | - BenchmarkBoneMux 10000000 118 ns/op 25 | - BenchmarkZeusMux 100000 144 ns/op 26 | - BenchmarkHttpRouterMux 10000000 134 ns/op 27 | - BenchmarkNetHttpMux 3000000 580 ns/op 28 | - BenchmarkGorillaMux 300000 3333 ns/op 29 | - BenchmarkGorillaPatMux 1000000 1889 ns/op 30 | ``` 31 | 32 | These tests are just for fun, all these routers are great and efficient. 33 | Bone isn't the fastest router for every job. 34 | 35 | ## Example 36 | 37 | ``` go 38 | 39 | package main 40 | 41 | import( 42 | "net/http" 43 | 44 | "github.com/go-zoo/bone" 45 | ) 46 | 47 | func main () { 48 | mux := bone.New() 49 | 50 | mux.RegisterValidatorFunc("isNum", func(s string) bool { 51 | if _, err := strconv.Atoi(s); err == nil { 52 | return true 53 | } 54 | return false 55 | }) 56 | 57 | // mux.Get, Post, etc ... takes http.Handler 58 | // validator for route parameter 59 | mux.Get("/home/:id|isNum", http.HandlerFunc(HomeHandler)) 60 | // multiple parameter 61 | mux.Get("/profil/:id/:var", http.HandlerFunc(ProfilHandler)) 62 | mux.Post("/data", http.HandlerFunc(DataHandler)) 63 | 64 | // Support REGEX Route params 65 | mux.Get("/index/#id^[0-9]$", http.HandlerFunc(IndexHandler)) 66 | 67 | // Handle take http.Handler 68 | mux.Handle("/", http.HandlerFunc(RootHandler)) 69 | 70 | // GetFunc, PostFunc etc ... takes http.HandlerFunc 71 | mux.GetFunc("/test", Handler) 72 | 73 | http.ListenAndServe(":8080", mux) 74 | } 75 | 76 | func Handler(rw http.ResponseWriter, req *http.Request) { 77 | // Get the value of the "id" parameters. 78 | val := bone.GetValue(req, "id") 79 | 80 | rw.Write([]byte(val)) 81 | } 82 | 83 | ``` 84 | 85 | ## Blog Posts 86 | - http://www.peterbe.com/plog/my-favorite-go-multiplexer 87 | - https://harshladha.xyz/my-first-library-in-go-language-hasty-791b8e2b9e69 88 | 89 | ## Libs 90 | - Errors dump for Go : [Trash](https://github.com/go-zoo/trash) 91 | - Middleware Chaining module : [Claw](https://github.com/go-zoo/claw) 92 | -------------------------------------------------------------------------------- /bone.go: -------------------------------------------------------------------------------- 1 | /******************************** 2 | *** Multiplexer for Go *** 3 | *** Bone is under MIT license *** 4 | *** Code by CodingFerret *** 5 | *** github.com/go-zoo *** 6 | *********************************/ 7 | 8 | package bone 9 | 10 | import ( 11 | "net/http" 12 | "strings" 13 | ) 14 | 15 | // Mux have routes and a notFound handler 16 | // Route: all the registred route 17 | // notFound: 404 handler, default http.NotFound if not provided 18 | type Mux struct { 19 | Routes map[string][]*Route 20 | prefix string 21 | notFound http.Handler 22 | Validators map[string]Validator 23 | Serve func(rw http.ResponseWriter, req *http.Request) 24 | CaseSensitive bool 25 | } 26 | 27 | var ( 28 | static = "static" 29 | method = []string{"GET", "POST", "PUT", "DELETE", "HEAD", "PATCH", "OPTIONS"} 30 | ) 31 | 32 | type adapter func(*Mux) *Mux 33 | 34 | // New create a pointer to a Mux instance 35 | func New(adapters ...adapter) *Mux { 36 | m := &Mux{Routes: make(map[string][]*Route), Serve: nil, CaseSensitive: true} 37 | for _, adap := range adapters { 38 | adap(m) 39 | } 40 | if m.Serve == nil { 41 | m.Serve = m.DefaultServe 42 | } 43 | return m 44 | } 45 | 46 | // RegisterValidatorFunc makes the provided function available to the routes register on that mux as a validator 47 | func (m *Mux) RegisterValidatorFunc(name string, validator func(string) bool) { 48 | if m.Validators == nil { 49 | m.Validators = make(map[string]Validator) 50 | } 51 | m.Validators[name] = newValidatorFunc(validator) 52 | } 53 | 54 | // RegisterValidator makes the provided validator available to the routes register on that mux 55 | func (m *Mux) RegisterValidator(name string, validator Validator) { 56 | if m.Validators == nil { 57 | m.Validators = make(map[string]Validator) 58 | } 59 | m.Validators[name] = validator 60 | } 61 | 62 | // Prefix set a default prefix for all routes registred on the router 63 | func (m *Mux) Prefix(p string) *Mux { 64 | m.prefix = strings.TrimSuffix(p, "/") 65 | return m 66 | } 67 | 68 | // DefaultServe is the default http request handler 69 | func (m *Mux) DefaultServe(rw http.ResponseWriter, req *http.Request) { 70 | // Check if a route match 71 | if !m.parse(rw, req) { 72 | // Check if it's a static ressource 73 | if !m.staticRoute(rw, req) { 74 | // Check if the request path doesn't end with / 75 | if !m.validate(rw, req) { 76 | // Check if same route exists for another HTTP method 77 | if !m.otherMethods(rw, req) { 78 | m.HandleNotFound(rw, req) 79 | } 80 | } 81 | } 82 | } 83 | } 84 | 85 | // ServeHTTP pass the request to the serve method of Mux 86 | func (m *Mux) ServeHTTP(rw http.ResponseWriter, req *http.Request) { 87 | if !m.CaseSensitive { 88 | req.URL.Path = strings.ToLower(req.URL.Path) 89 | } 90 | m.Serve(rw, req) 91 | } 92 | -------------------------------------------------------------------------------- /bone_bench_test.go: -------------------------------------------------------------------------------- 1 | // go list -f '{{range .TestImports}}{{.}} {{end}}' github.com/go-zoo/bone | xargs go get 2 | 3 | package bone 4 | 5 | //import ( 6 | // "net/http" 7 | // "net/http/httptest" 8 | // "testing" 9 | // 10 | // "github.com/daryl/zeus" 11 | // "github.com/gorilla/mux" 12 | // "github.com/gorilla/pat" 13 | // "github.com/julienschmidt/httprouter" 14 | // "github.com/ursiform/bear" 15 | //) 16 | // 17 | //// Test the ns/op 18 | //func BenchmarkBoneMux(b *testing.B) { 19 | // request, _ := http.NewRequest("GET", "/sd", nil) 20 | // response := httptest.NewRecorder() 21 | // muxx := New() 22 | // 23 | // muxx.Get("/", http.HandlerFunc(Bench)) 24 | // muxx.Get("/a", http.HandlerFunc(Bench)) 25 | // muxx.Get("/aas", http.HandlerFunc(Bench)) 26 | // muxx.Get("/sd", http.HandlerFunc(Bench)) 27 | // 28 | // for n := 0; n < b.N; n++ { 29 | // muxx.ServeHTTP(response, request) 30 | // } 31 | //} 32 | // 33 | //// Test httprouter ns/op 34 | //func BenchmarkHttpRouterMux(b *testing.B) { 35 | // request, _ := http.NewRequest("GET", "/sd", nil) 36 | // response := httptest.NewRecorder() 37 | // muxx := httprouter.New() 38 | // 39 | // muxx.Handler("GET", "/", http.HandlerFunc(Bench)) 40 | // muxx.Handler("GET", "/a", http.HandlerFunc(Bench)) 41 | // muxx.Handler("GET", "/aas", http.HandlerFunc(Bench)) 42 | // muxx.Handler("GET", "/sd", http.HandlerFunc(Bench)) 43 | // 44 | // for n := 0; n < b.N; n++ { 45 | // muxx.ServeHTTP(response, request) 46 | // } 47 | //} 48 | // 49 | //// Test daryl/zeus ns/op 50 | //func BenchmarkZeusMux(b *testing.B) { 51 | // request, _ := http.NewRequest("GET", "/sd/test", nil) 52 | // response := httptest.NewRecorder() 53 | // muxx := zeus.New() 54 | // 55 | // muxx.GET("/", Bench) 56 | // muxx.GET("/a", Bench) 57 | // muxx.GET("/aas", Bench) 58 | // muxx.GET("/sd/:id", Bench) 59 | // 60 | // for n := 0; n < b.N; n++ { 61 | // muxx.ServeHTTP(response, request) 62 | // } 63 | //} 64 | // 65 | //// Test net/http ns/op 66 | //func BenchmarkNetHttpMux(b *testing.B) { 67 | // request, _ := http.NewRequest("GET", "/sd", nil) 68 | // response := httptest.NewRecorder() 69 | // muxx := http.NewServeMux() 70 | // 71 | // muxx.HandleFunc("/", Bench) 72 | // muxx.HandleFunc("/a", Bench) 73 | // muxx.HandleFunc("/aas", Bench) 74 | // muxx.HandleFunc("/sd", Bench) 75 | // 76 | // for n := 0; n < b.N; n++ { 77 | // muxx.ServeHTTP(response, request) 78 | // } 79 | //} 80 | // 81 | //// Test ursiform/bear ns/op 82 | //func BenchmarkBearMux(b *testing.B) { 83 | // request, _ := http.NewRequest("GET", "/sd", nil) 84 | // response := httptest.NewRecorder() 85 | // muxx := bear.New() 86 | // 87 | // muxx.On("GET", "/", Bench) 88 | // muxx.On("GET", "/a", Bench) 89 | // muxx.On("GET", "/aas", Bench) 90 | // muxx.On("GET", "/sd", Bench) 91 | // 92 | // for n := 0; n < b.N; n++ { 93 | // muxx.ServeHTTP(response, request) 94 | // } 95 | //} 96 | // 97 | //// Test gorilla/mux ns/op 98 | //func BenchmarkGorillaMux(b *testing.B) { 99 | // request, _ := http.NewRequest("GET", "/sd", nil) 100 | // response := httptest.NewRecorder() 101 | // muxx := mux.NewRouter() 102 | // 103 | // muxx.Handle("/", http.HandlerFunc(Bench)) 104 | // muxx.Handle("/a", http.HandlerFunc(Bench)) 105 | // muxx.Handle("/aas", http.HandlerFunc(Bench)) 106 | // muxx.Handle("/sd", http.HandlerFunc(Bench)) 107 | // 108 | // for n := 0; n < b.N; n++ { 109 | // muxx.ServeHTTP(response, request) 110 | // } 111 | //} 112 | // 113 | //// Test gorilla/pat ns/op 114 | //func BenchmarkGorillaPatMux(b *testing.B) { 115 | // request, _ := http.NewRequest("GET", "/sd", nil) 116 | // response := httptest.NewRecorder() 117 | // muxx := pat.New() 118 | // 119 | // muxx.Get("/", Bench) 120 | // muxx.Get("/a", Bench) 121 | // muxx.Get("/aas", Bench) 122 | // muxx.Get("/sd", Bench) 123 | // 124 | // for n := 0; n < b.N; n++ { 125 | // muxx.ServeHTTP(response, request) 126 | // } 127 | //} 128 | // 129 | //func Bench(rw http.ResponseWriter, req *http.Request) { 130 | // rw.Write([]byte("b")) 131 | //} 132 | 133 | /* 134 | ### Result ### 135 | 136 | BenchmarkBoneMux 10000000 124 ns/op 137 | BenchmarkHttpRouterMux 10000000 147 ns/op 138 | BenchmarkZeusMux 10000000 210 ns/op 139 | BenchmarkNetHttpMux 3000000 560 ns/op 140 | BenchmarkGorillaMux 500000 2946 ns/op 141 | BenchmarkGorillaPatMux 1000000 1805 ns/op 142 | 143 | ok github.com/go-zoo/bone 10.997s 144 | */ 145 | -------------------------------------------------------------------------------- /bone_context_test.go: -------------------------------------------------------------------------------- 1 | // +build go1.7 2 | 3 | package bone 4 | 5 | import ( 6 | "context" 7 | "fmt" 8 | "net/http" 9 | "net/http/httptest" 10 | "testing" 11 | ) 12 | 13 | func TestRoutingVariableWithContext(t *testing.T) { 14 | var ( 15 | expected = "variable" 16 | got string 17 | mux = New() 18 | w = httptest.NewRecorder() 19 | ) 20 | 21 | appFn := func(w http.ResponseWriter, r *http.Request) { 22 | got = GetValue(r, "vartest") 23 | } 24 | 25 | middlewareFn := func(w http.ResponseWriter, r *http.Request) { 26 | ctx := context.WithValue(r.Context(), "key", "customValue") 27 | newReq := r.WithContext(ctx) 28 | appFn(w, newReq) 29 | } 30 | 31 | mux.Get("/:vartest", http.HandlerFunc(middlewareFn)) 32 | r, err := http.NewRequest("GET", fmt.Sprintf("/%s", expected), nil) 33 | if err != nil { 34 | t.Fatal(err) 35 | } 36 | mux.ServeHTTP(w, r) 37 | 38 | if got != expected { 39 | t.Fatalf("expected %s, got %s", expected, got) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /bone_test.go: -------------------------------------------------------------------------------- 1 | package bone 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "net/http" 7 | "net/http/httptest" 8 | "reflect" 9 | "testing" 10 | ) 11 | 12 | // Test if the route is valid 13 | func TestRouting(t *testing.T) { 14 | mux := New() 15 | call := false 16 | mux.Get("/a/:id", http.HandlerFunc(func(http.ResponseWriter, *http.Request) { 17 | call = true 18 | })) 19 | 20 | r, _ := http.NewRequest("GET", "/b/123", nil) 21 | w := httptest.NewRecorder() 22 | mux.ServeHTTP(w, r) 23 | 24 | if call { 25 | t.Error("handler should not be called") 26 | } 27 | } 28 | 29 | // Test the custom not handler handler sets 404 error code 30 | func TestNotFoundCustomHandlerSends404(t *testing.T) { 31 | mux := New() 32 | mux.NotFoundFunc(func(rw http.ResponseWriter, req *http.Request) { 33 | rw.WriteHeader(404) 34 | rw.Write([]byte("These are not the droids you're looking for ...")) 35 | }) 36 | 37 | r, _ := http.NewRequest("GET", "/b/123", nil) 38 | w := httptest.NewRecorder() 39 | mux.ServeHTTP(w, r) 40 | 41 | if w.Code != 404 { 42 | t.Errorf("expecting error code 404, got %v", w.Code) 43 | } 44 | } 45 | 46 | // Test if the http method is valid 47 | func TestRoutingMethod(t *testing.T) { 48 | mux := New() 49 | call := false 50 | mux.Get("/t", http.HandlerFunc(func(http.ResponseWriter, *http.Request) { 51 | call = true 52 | })) 53 | 54 | r, _ := http.NewRequest("POST", "/t", nil) 55 | w := httptest.NewRecorder() 56 | mux.ServeHTTP(w, r) 57 | 58 | if call { 59 | t.Error("response to a wrong method") 60 | } 61 | } 62 | 63 | // Test if the mux don't handle by prefix 64 | func TestRoutingPath(t *testing.T) { 65 | mux := New() 66 | call := false 67 | mux.Get("/t", http.HandlerFunc(func(http.ResponseWriter, *http.Request) { 68 | call = true 69 | })) 70 | mux.Get("/t/x", http.HandlerFunc(func(http.ResponseWriter, *http.Request) { 71 | call = false 72 | })) 73 | 74 | r, _ := http.NewRequest("GET", "/t/x", nil) 75 | w := httptest.NewRecorder() 76 | mux.ServeHTTP(w, r) 77 | 78 | if call { 79 | t.Error("response with the wrong path") 80 | } 81 | } 82 | 83 | func TestPrefix(t *testing.T) { 84 | mux := New() 85 | mux.Prefix("/api") 86 | call := false 87 | mux.Get("/t", http.HandlerFunc(func(http.ResponseWriter, *http.Request) { 88 | call = true 89 | })) 90 | mux.Get("/api/t", http.HandlerFunc(func(http.ResponseWriter, *http.Request) { 91 | call = false 92 | })) 93 | 94 | r, _ := http.NewRequest("GET", "/api/t", nil) 95 | w := httptest.NewRecorder() 96 | mux.ServeHTTP(w, r) 97 | 98 | if !call { 99 | t.Error("response with the wrong path") 100 | } 101 | } 102 | 103 | func TestPrefixWithTailSlash(t *testing.T) { 104 | mux := New() 105 | mux.Prefix("/api/") 106 | call := false 107 | mux.Get("/t", http.HandlerFunc(func(http.ResponseWriter, *http.Request) { 108 | call = true 109 | })) 110 | mux.Get("/api/t", http.HandlerFunc(func(http.ResponseWriter, *http.Request) { 111 | call = false 112 | })) 113 | 114 | r, _ := http.NewRequest("GET", "/api/t", nil) 115 | w := httptest.NewRecorder() 116 | mux.ServeHTTP(w, r) 117 | 118 | if !call { 119 | t.Error("response with the wrong path") 120 | } 121 | } 122 | 123 | func TestRoutingVerbs(t *testing.T) { 124 | var ( 125 | methods = []string{"DELETE", "GET", "HEAD", "PUT", "POST", "PATCH", "OPTIONS", "HEAD"} 126 | path = "/" 127 | h = http.HandlerFunc(func(http.ResponseWriter, *http.Request) {}) 128 | ) 129 | for _, meth := range methods { 130 | m := New() 131 | switch meth { 132 | case "DELETE": 133 | m.Delete(path, h) 134 | case "GET": 135 | m.Get(path, h) 136 | case "HEAD": 137 | m.Head(path, h) 138 | case "POST": 139 | m.Post(path, h) 140 | case "PUT": 141 | m.Put(path, h) 142 | case "PATCH": 143 | m.Patch(path, h) 144 | case "OPTIONS": 145 | m.Options(path, h) 146 | } 147 | s := httptest.NewServer(m) 148 | req, err := http.NewRequest(meth, s.URL, nil) 149 | if err != nil { 150 | t.Fatal(err) 151 | } 152 | resp, err := http.DefaultClient.Do(req) 153 | if err != nil { 154 | t.Fatal(err) 155 | } 156 | defer resp.Body.Close() 157 | if resp.StatusCode != 200 { 158 | t.Fatalf("%s: HTTP %d", meth, resp.StatusCode) 159 | } 160 | s.Close() 161 | } 162 | } 163 | 164 | // If no HEAD method, default to GET 165 | func TestHeadToGet(t *testing.T) { 166 | path := "/" 167 | m := New() 168 | m.Get(path, http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { 169 | rw.Write([]byte("text")) 170 | })) 171 | 172 | s := httptest.NewServer(m) 173 | // GET 174 | reqGet, err := http.NewRequest("GET", s.URL, nil) 175 | if err != nil { 176 | t.Fatal(err) 177 | } 178 | resGet, err := http.DefaultClient.Do(reqGet) 179 | if err != nil { 180 | t.Fatal(err) 181 | } 182 | defer resGet.Body.Close() 183 | if resGet.StatusCode != 200 { 184 | t.Fatalf("GET: HTTP %d", resGet.StatusCode) 185 | } 186 | body, _ := ioutil.ReadAll(resGet.Body) 187 | if resGet.Header.Get("Content-Length") != "4" { 188 | t.Fatalf("GET: incorrect Content-Length") 189 | } 190 | if string(body) != "text" { 191 | t.Fatalf("GET: incorrect response body") 192 | } 193 | // HEAD 194 | reqHead, err := http.NewRequest("HEAD", s.URL, nil) 195 | if err != nil { 196 | t.Fatal(err) 197 | } 198 | resHead, err := http.DefaultClient.Do(reqHead) 199 | if err != nil { 200 | t.Fatal(err) 201 | } 202 | defer resHead.Body.Close() 203 | if resHead.StatusCode != 200 { 204 | t.Fatalf("If no HEAD method, default to GET: HTTP %d", resHead.StatusCode) 205 | } 206 | body, _ = ioutil.ReadAll(resHead.Body) 207 | if resGet.Header.Get("Content-Length") != "4" { 208 | t.Fatalf("HEAD: incorrect Content-Length") 209 | } 210 | if len(body) != 0 { 211 | t.Fatalf("HEAD: should not contain response body") 212 | } 213 | 214 | s.Close() 215 | } 216 | 217 | func TestRoutingSlash(t *testing.T) { 218 | mux := New() 219 | call := false 220 | mux.Get("/", http.HandlerFunc(func(http.ResponseWriter, *http.Request) { 221 | call = true 222 | })) 223 | 224 | r, _ := http.NewRequest("GET", "/", nil) 225 | w := httptest.NewRecorder() 226 | mux.ServeHTTP(w, r) 227 | 228 | if !call { 229 | t.Error("root not serve") 230 | } 231 | } 232 | 233 | func TestMultipleRoutingVariables(t *testing.T) { 234 | var ( 235 | expected1 = "variable1" 236 | expected2 = "variable2" 237 | got1 string 238 | got2 string 239 | mux = New() 240 | w = httptest.NewRecorder() 241 | ) 242 | mux.Get("/test/:var1/:var2", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 243 | got1 = GetValue(r, "var1") 244 | got2 = GetValue(r, "var2") 245 | })) 246 | 247 | r, err := http.NewRequest("GET", fmt.Sprintf("/test/%s/%s", expected1, expected2), nil) 248 | if err != nil { 249 | t.Fatal(err) 250 | } 251 | mux.ServeHTTP(w, r) 252 | 253 | if got1 != expected1 { 254 | t.Fatalf("expected %s, got %s", expected1, got1) 255 | } 256 | 257 | if got2 != expected2 { 258 | t.Fatalf("expected %s, got %s", expected2, got2) 259 | } 260 | } 261 | 262 | func TestRoutingVariable(t *testing.T) { 263 | var ( 264 | expected = "variable" 265 | got string 266 | mux = New() 267 | w = httptest.NewRecorder() 268 | ) 269 | mux.Get("/:vartest", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 270 | got = GetValue(r, "vartest") 271 | })) 272 | 273 | r, err := http.NewRequest("GET", fmt.Sprintf("/%s", expected), nil) 274 | if err != nil { 275 | t.Fatal(err) 276 | } 277 | mux.ServeHTTP(w, r) 278 | 279 | if got != expected { 280 | t.Fatalf("expected %s, got %s", expected, got) 281 | } 282 | } 283 | 284 | func TestStaticFile(t *testing.T) { 285 | var data string 286 | mux := New() 287 | mux.Get("/file/", http.HandlerFunc(func(http.ResponseWriter, *http.Request) { 288 | data = "DATA" 289 | })) 290 | 291 | r, _ := http.NewRequest("GET", "/file/", nil) 292 | w := httptest.NewRecorder() 293 | mux.ServeHTTP(w, r) 294 | 295 | if data != "DATA" { 296 | t.Error("Data not serve") 297 | } 298 | } 299 | 300 | func TestStandAloneRoute(t *testing.T) { 301 | valid := false 302 | mux := http.NewServeMux() 303 | 304 | testRoute := NewRoute(nil, "/test", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { 305 | valid = true 306 | })) 307 | mux.Handle("/test", testRoute.Get()) 308 | r, _ := http.NewRequest("GET", "/test", nil) 309 | w := httptest.NewRecorder() 310 | mux.ServeHTTP(w, r) 311 | 312 | if !valid { 313 | t.Error("Route Handler not call") 314 | } 315 | } 316 | 317 | func TestRegexParam(t *testing.T) { 318 | valid := false 319 | mux := New() 320 | 321 | mux.Get("/Regex/#ttt^[a-z]$", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { 322 | valid = true 323 | })) 324 | 325 | r, _ := http.NewRequest("GET", "/Regex/test", nil) 326 | w := httptest.NewRecorder() 327 | mux.ServeHTTP(w, r) 328 | 329 | if !valid { 330 | t.Error("Route Handler not call") 331 | } 332 | } 333 | 334 | func TestRegexParam2(t *testing.T) { 335 | valid := false 336 | mux := New() 337 | 338 | mux.Get("/Regex/#tttt^[a-z]$", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { 339 | valid = true 340 | })) 341 | 342 | r, _ := http.NewRequest("GET", "/Regex/1234", nil) 343 | w := httptest.NewRecorder() 344 | mux.ServeHTTP(w, r) 345 | 346 | if valid { 347 | t.Error("Regex param not valid !") 348 | } 349 | } 350 | 351 | func TestRegexParamMutli(t *testing.T) { 352 | valid := false 353 | mux := New() 354 | 355 | mux.Get("/Regex/#ttt^[a-z]$/#yyy^[0-9]$", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { 356 | valid = true 357 | })) 358 | 359 | r, _ := http.NewRequest("GET", "/Regex/first/2", nil) 360 | w := httptest.NewRecorder() 361 | mux.ServeHTTP(w, r) 362 | 363 | if !valid { 364 | t.Error("Regex multi Params not valid !") 365 | } 366 | } 367 | 368 | func TestMultiParams(t *testing.T) { 369 | valid := false 370 | mux := New() 371 | 372 | mux.Get("/Regex/#num^[a-z]$/:test", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { 373 | valid = true 374 | })) 375 | 376 | r, _ := http.NewRequest("GET", "/Regex/first/second", nil) 377 | w := httptest.NewRecorder() 378 | mux.ServeHTTP(w, r) 379 | 380 | if !valid { 381 | t.Error("Regex multi Params not valid !") 382 | } 383 | } 384 | 385 | func TestWC(t *testing.T) { 386 | valid := false 387 | mux := New() 388 | mux.GetFunc("/test/*", func(rw http.ResponseWriter, req *http.Request) { 389 | valid = true 390 | }) 391 | 392 | req, _ := http.NewRequest("GET", "/test/random/route", nil) 393 | rw := httptest.NewRecorder() 394 | mux.ServeHTTP(rw, req) 395 | 396 | if !valid { 397 | t.Error("WC doesn't work !") 398 | } 399 | } 400 | 401 | func TestSlashRemoving1(t *testing.T) { 402 | mux := New() 403 | mux.GetFunc("/test", func(rw http.ResponseWriter, req *http.Request) { 404 | t.Error("/test got called but it should have been a redirect!") 405 | }) 406 | 407 | req, _ := http.NewRequest("GET", "/test/////", nil) 408 | rw := httptest.NewRecorder() 409 | mux.ServeHTTP(rw, req) 410 | 411 | if rw.Header().Get("Location") != "/test" { 412 | t.Error("Redirect 1 doesn't work") 413 | } 414 | } 415 | 416 | func TestSlashRemovingWithQuery(t *testing.T) { 417 | mux := New() 418 | mux.GetFunc("/test", func(rw http.ResponseWriter, req *http.Request) { 419 | t.Error("/test got called but it should have been a redirect!") 420 | }) 421 | 422 | req, _ := http.NewRequest("GET", "/test/?foo=bar&buzz=bazz", nil) 423 | rw := httptest.NewRecorder() 424 | mux.ServeHTTP(rw, req) 425 | 426 | if rw.Header().Get("Location") != "/test?foo=bar&buzz=bazz" { 427 | t.Error("Redirect 2 doesn't work") 428 | } 429 | } 430 | 431 | func TestSubRouteExtracting(t *testing.T) { 432 | mux := New() 433 | apiMux := New() 434 | result := "" 435 | apiMux.GetFunc("/test", func(rw http.ResponseWriter, req *http.Request) { 436 | result = mux.GetRequestRoute(req) 437 | }) 438 | mux.SubRoute("/api", apiMux) 439 | 440 | req, _ := http.NewRequest("GET", "/api/test", nil) 441 | rw := httptest.NewRecorder() 442 | mux.ServeHTTP(rw, req) 443 | 444 | if result != "/test" { 445 | t.Log(result) 446 | t.Error("SubRouter route extracting not working") 447 | } 448 | } 449 | 450 | func TestNew(t *testing.T) { 451 | type args struct { 452 | adapters []adapter 453 | } 454 | tests := []struct { 455 | name string 456 | args args 457 | want *Mux 458 | }{ 459 | // TODO: Add test cases. 460 | } 461 | for _, tt := range tests { 462 | t.Run(tt.name, func(t *testing.T) { 463 | if got := New(tt.args.adapters...); !reflect.DeepEqual(got, tt.want) { 464 | t.Errorf("New() = %v, want %v", got, tt.want) 465 | } 466 | }) 467 | } 468 | } 469 | 470 | func TestMux_Prefix(t *testing.T) { 471 | type fields struct { 472 | Routes map[string][]*Route 473 | prefix string 474 | notFound http.Handler 475 | Serve func(rw http.ResponseWriter, req *http.Request) 476 | } 477 | type args struct { 478 | p string 479 | } 480 | tests := []struct { 481 | name string 482 | fields fields 483 | args args 484 | want *Mux 485 | }{ 486 | // TODO: Add test cases. 487 | } 488 | for _, tt := range tests { 489 | t.Run(tt.name, func(t *testing.T) { 490 | m := &Mux{ 491 | Routes: tt.fields.Routes, 492 | prefix: tt.fields.prefix, 493 | notFound: tt.fields.notFound, 494 | Serve: tt.fields.Serve, 495 | } 496 | if got := m.Prefix(tt.args.p); !reflect.DeepEqual(got, tt.want) { 497 | t.Errorf("Mux.Prefix() = %v, want %v", got, tt.want) 498 | } 499 | }) 500 | } 501 | } 502 | 503 | func TestMux_DefaultServe(t *testing.T) { 504 | type fields struct { 505 | Routes map[string][]*Route 506 | prefix string 507 | notFound http.Handler 508 | Serve func(rw http.ResponseWriter, req *http.Request) 509 | } 510 | type args struct { 511 | rw http.ResponseWriter 512 | req *http.Request 513 | } 514 | tests := []struct { 515 | name string 516 | fields fields 517 | args args 518 | }{ 519 | // TODO: Add test cases. 520 | } 521 | for _, tt := range tests { 522 | t.Run(tt.name, func(t *testing.T) { 523 | m := &Mux{ 524 | Routes: tt.fields.Routes, 525 | prefix: tt.fields.prefix, 526 | notFound: tt.fields.notFound, 527 | Serve: tt.fields.Serve, 528 | } 529 | m.DefaultServe(tt.args.rw, tt.args.req) 530 | }) 531 | } 532 | } 533 | 534 | func TestMux_ServeHTTP(t *testing.T) { 535 | type fields struct { 536 | Routes map[string][]*Route 537 | prefix string 538 | notFound http.Handler 539 | Serve func(rw http.ResponseWriter, req *http.Request) 540 | } 541 | type args struct { 542 | rw http.ResponseWriter 543 | req *http.Request 544 | } 545 | tests := []struct { 546 | name string 547 | fields fields 548 | args args 549 | }{ 550 | // TODO: Add test cases. 551 | } 552 | for _, tt := range tests { 553 | t.Run(tt.name, func(t *testing.T) { 554 | m := &Mux{ 555 | Routes: tt.fields.Routes, 556 | prefix: tt.fields.prefix, 557 | notFound: tt.fields.notFound, 558 | Serve: tt.fields.Serve, 559 | } 560 | m.ServeHTTP(tt.args.rw, tt.args.req) 561 | }) 562 | } 563 | } 564 | 565 | func TestMux_Handle(t *testing.T) { 566 | type fields struct { 567 | Routes map[string][]*Route 568 | prefix string 569 | notFound http.Handler 570 | Serve func(rw http.ResponseWriter, req *http.Request) 571 | } 572 | type args struct { 573 | path string 574 | handler http.Handler 575 | } 576 | tests := []struct { 577 | name string 578 | fields fields 579 | args args 580 | }{ 581 | // TODO: Add test cases. 582 | } 583 | for _, tt := range tests { 584 | t.Run(tt.name, func(t *testing.T) { 585 | m := &Mux{ 586 | Routes: tt.fields.Routes, 587 | prefix: tt.fields.prefix, 588 | notFound: tt.fields.notFound, 589 | Serve: tt.fields.Serve, 590 | } 591 | m.Handle(tt.args.path, tt.args.handler) 592 | }) 593 | } 594 | } 595 | 596 | func TestMux_HandleFunc(t *testing.T) { 597 | type fields struct { 598 | Routes map[string][]*Route 599 | prefix string 600 | notFound http.Handler 601 | Serve func(rw http.ResponseWriter, req *http.Request) 602 | } 603 | type args struct { 604 | path string 605 | handler http.HandlerFunc 606 | } 607 | tests := []struct { 608 | name string 609 | fields fields 610 | args args 611 | }{ 612 | // TODO: Add test cases. 613 | } 614 | for _, tt := range tests { 615 | t.Run(tt.name, func(t *testing.T) { 616 | m := &Mux{ 617 | Routes: tt.fields.Routes, 618 | prefix: tt.fields.prefix, 619 | notFound: tt.fields.notFound, 620 | Serve: tt.fields.Serve, 621 | } 622 | m.HandleFunc(tt.args.path, tt.args.handler) 623 | }) 624 | } 625 | } 626 | 627 | func TestMux_Get(t *testing.T) { 628 | type fields struct { 629 | Routes map[string][]*Route 630 | prefix string 631 | notFound http.Handler 632 | Serve func(rw http.ResponseWriter, req *http.Request) 633 | } 634 | type args struct { 635 | path string 636 | handler http.Handler 637 | } 638 | tests := []struct { 639 | name string 640 | fields fields 641 | args args 642 | want *Route 643 | }{ 644 | // TODO: Add test cases. 645 | } 646 | for _, tt := range tests { 647 | t.Run(tt.name, func(t *testing.T) { 648 | m := &Mux{ 649 | Routes: tt.fields.Routes, 650 | prefix: tt.fields.prefix, 651 | notFound: tt.fields.notFound, 652 | Serve: tt.fields.Serve, 653 | } 654 | if got := m.Get(tt.args.path, tt.args.handler); !reflect.DeepEqual(got, tt.want) { 655 | t.Errorf("Mux.Get() = %v, want %v", got, tt.want) 656 | } 657 | }) 658 | } 659 | } 660 | 661 | func TestMux_Post(t *testing.T) { 662 | type fields struct { 663 | Routes map[string][]*Route 664 | prefix string 665 | notFound http.Handler 666 | Serve func(rw http.ResponseWriter, req *http.Request) 667 | } 668 | type args struct { 669 | path string 670 | handler http.Handler 671 | } 672 | tests := []struct { 673 | name string 674 | fields fields 675 | args args 676 | want *Route 677 | }{ 678 | // TODO: Add test cases. 679 | } 680 | for _, tt := range tests { 681 | t.Run(tt.name, func(t *testing.T) { 682 | m := &Mux{ 683 | Routes: tt.fields.Routes, 684 | prefix: tt.fields.prefix, 685 | notFound: tt.fields.notFound, 686 | Serve: tt.fields.Serve, 687 | } 688 | if got := m.Post(tt.args.path, tt.args.handler); !reflect.DeepEqual(got, tt.want) { 689 | t.Errorf("Mux.Post() = %v, want %v", got, tt.want) 690 | } 691 | }) 692 | } 693 | } 694 | 695 | func TestMux_Put(t *testing.T) { 696 | type fields struct { 697 | Routes map[string][]*Route 698 | prefix string 699 | notFound http.Handler 700 | Serve func(rw http.ResponseWriter, req *http.Request) 701 | } 702 | type args struct { 703 | path string 704 | handler http.Handler 705 | } 706 | tests := []struct { 707 | name string 708 | fields fields 709 | args args 710 | want *Route 711 | }{ 712 | // TODO: Add test cases. 713 | } 714 | for _, tt := range tests { 715 | t.Run(tt.name, func(t *testing.T) { 716 | m := &Mux{ 717 | Routes: tt.fields.Routes, 718 | prefix: tt.fields.prefix, 719 | notFound: tt.fields.notFound, 720 | Serve: tt.fields.Serve, 721 | } 722 | if got := m.Put(tt.args.path, tt.args.handler); !reflect.DeepEqual(got, tt.want) { 723 | t.Errorf("Mux.Put() = %v, want %v", got, tt.want) 724 | } 725 | }) 726 | } 727 | } 728 | 729 | func TestMux_Delete(t *testing.T) { 730 | type fields struct { 731 | Routes map[string][]*Route 732 | prefix string 733 | notFound http.Handler 734 | Serve func(rw http.ResponseWriter, req *http.Request) 735 | } 736 | type args struct { 737 | path string 738 | handler http.Handler 739 | } 740 | tests := []struct { 741 | name string 742 | fields fields 743 | args args 744 | want *Route 745 | }{ 746 | // TODO: Add test cases. 747 | } 748 | for _, tt := range tests { 749 | t.Run(tt.name, func(t *testing.T) { 750 | m := &Mux{ 751 | Routes: tt.fields.Routes, 752 | prefix: tt.fields.prefix, 753 | notFound: tt.fields.notFound, 754 | Serve: tt.fields.Serve, 755 | } 756 | if got := m.Delete(tt.args.path, tt.args.handler); !reflect.DeepEqual(got, tt.want) { 757 | t.Errorf("Mux.Delete() = %v, want %v", got, tt.want) 758 | } 759 | }) 760 | } 761 | } 762 | 763 | func TestMux_Head(t *testing.T) { 764 | type fields struct { 765 | Routes map[string][]*Route 766 | prefix string 767 | notFound http.Handler 768 | Serve func(rw http.ResponseWriter, req *http.Request) 769 | } 770 | type args struct { 771 | path string 772 | handler http.Handler 773 | } 774 | tests := []struct { 775 | name string 776 | fields fields 777 | args args 778 | want *Route 779 | }{ 780 | // TODO: Add test cases. 781 | } 782 | for _, tt := range tests { 783 | t.Run(tt.name, func(t *testing.T) { 784 | m := &Mux{ 785 | Routes: tt.fields.Routes, 786 | prefix: tt.fields.prefix, 787 | notFound: tt.fields.notFound, 788 | Serve: tt.fields.Serve, 789 | } 790 | if got := m.Head(tt.args.path, tt.args.handler); !reflect.DeepEqual(got, tt.want) { 791 | t.Errorf("Mux.Head() = %v, want %v", got, tt.want) 792 | } 793 | }) 794 | } 795 | } 796 | 797 | func TestMux_Patch(t *testing.T) { 798 | type fields struct { 799 | Routes map[string][]*Route 800 | prefix string 801 | notFound http.Handler 802 | Serve func(rw http.ResponseWriter, req *http.Request) 803 | } 804 | type args struct { 805 | path string 806 | handler http.Handler 807 | } 808 | tests := []struct { 809 | name string 810 | fields fields 811 | args args 812 | want *Route 813 | }{ 814 | // TODO: Add test cases. 815 | } 816 | for _, tt := range tests { 817 | t.Run(tt.name, func(t *testing.T) { 818 | m := &Mux{ 819 | Routes: tt.fields.Routes, 820 | prefix: tt.fields.prefix, 821 | notFound: tt.fields.notFound, 822 | Serve: tt.fields.Serve, 823 | } 824 | if got := m.Patch(tt.args.path, tt.args.handler); !reflect.DeepEqual(got, tt.want) { 825 | t.Errorf("Mux.Patch() = %v, want %v", got, tt.want) 826 | } 827 | }) 828 | } 829 | } 830 | 831 | func TestMux_Options(t *testing.T) { 832 | type fields struct { 833 | Routes map[string][]*Route 834 | prefix string 835 | notFound http.Handler 836 | Serve func(rw http.ResponseWriter, req *http.Request) 837 | } 838 | type args struct { 839 | path string 840 | handler http.Handler 841 | } 842 | tests := []struct { 843 | name string 844 | fields fields 845 | args args 846 | want *Route 847 | }{ 848 | // TODO: Add test cases. 849 | } 850 | for _, tt := range tests { 851 | t.Run(tt.name, func(t *testing.T) { 852 | m := &Mux{ 853 | Routes: tt.fields.Routes, 854 | prefix: tt.fields.prefix, 855 | notFound: tt.fields.notFound, 856 | Serve: tt.fields.Serve, 857 | } 858 | if got := m.Options(tt.args.path, tt.args.handler); !reflect.DeepEqual(got, tt.want) { 859 | t.Errorf("Mux.Options() = %v, want %v", got, tt.want) 860 | } 861 | }) 862 | } 863 | } 864 | 865 | func TestMux_NotFound(t *testing.T) { 866 | type fields struct { 867 | Routes map[string][]*Route 868 | prefix string 869 | notFound http.Handler 870 | Serve func(rw http.ResponseWriter, req *http.Request) 871 | } 872 | type args struct { 873 | handler http.Handler 874 | } 875 | tests := []struct { 876 | name string 877 | fields fields 878 | args args 879 | }{ 880 | // TODO: Add test cases. 881 | } 882 | for _, tt := range tests { 883 | t.Run(tt.name, func(t *testing.T) { 884 | m := &Mux{ 885 | Routes: tt.fields.Routes, 886 | prefix: tt.fields.prefix, 887 | notFound: tt.fields.notFound, 888 | Serve: tt.fields.Serve, 889 | } 890 | m.NotFound(tt.args.handler) 891 | }) 892 | } 893 | } 894 | 895 | func TestMux_register(t *testing.T) { 896 | type fields struct { 897 | Routes map[string][]*Route 898 | prefix string 899 | notFound http.Handler 900 | Serve func(rw http.ResponseWriter, req *http.Request) 901 | } 902 | type args struct { 903 | method string 904 | path string 905 | handler http.Handler 906 | } 907 | tests := []struct { 908 | name string 909 | fields fields 910 | args args 911 | want *Route 912 | }{ 913 | // TODO: Add test cases. 914 | } 915 | for _, tt := range tests { 916 | t.Run(tt.name, func(t *testing.T) { 917 | m := &Mux{ 918 | Routes: tt.fields.Routes, 919 | prefix: tt.fields.prefix, 920 | notFound: tt.fields.notFound, 921 | Serve: tt.fields.Serve, 922 | } 923 | if got := m.register(tt.args.method, tt.args.path, tt.args.handler); !reflect.DeepEqual(got, tt.want) { 924 | t.Errorf("Mux.register() = %v, want %v", got, tt.want) 925 | } 926 | }) 927 | } 928 | } 929 | 930 | func TestMux_SubRoute(t *testing.T) { 931 | type fields struct { 932 | Routes map[string][]*Route 933 | prefix string 934 | notFound http.Handler 935 | Serve func(rw http.ResponseWriter, req *http.Request) 936 | } 937 | type args struct { 938 | path string 939 | router Router 940 | } 941 | tests := []struct { 942 | name string 943 | fields fields 944 | args args 945 | want *Route 946 | }{ 947 | // TODO: Add test cases. 948 | } 949 | for _, tt := range tests { 950 | t.Run(tt.name, func(t *testing.T) { 951 | m := &Mux{ 952 | Routes: tt.fields.Routes, 953 | prefix: tt.fields.prefix, 954 | notFound: tt.fields.notFound, 955 | Serve: tt.fields.Serve, 956 | } 957 | if got := m.SubRoute(tt.args.path, tt.args.router); !reflect.DeepEqual(got, tt.want) { 958 | t.Errorf("Mux.SubRoute() = %v, want %v", got, tt.want) 959 | } 960 | }) 961 | } 962 | } 963 | -------------------------------------------------------------------------------- /example/001/example.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "log" 7 | "net/http" 8 | "time" 9 | 10 | "github.com/go-zoo/bone" 11 | ) 12 | 13 | var ( 14 | mux = bone.New(Serve, Wrap) 15 | ) 16 | 17 | func Wrap(mux *bone.Mux) *bone.Mux { 18 | return mux.Prefix("/api") 19 | } 20 | 21 | func Serve(mux *bone.Mux) *bone.Mux { 22 | mux.Serve = func(rw http.ResponseWriter, req *http.Request) { 23 | tr := time.Now() 24 | mux.DefaultServe(rw, req) 25 | fmt.Println("Serve request from", req.RemoteAddr, "in", time.Since(tr)) 26 | } 27 | return mux 28 | } 29 | 30 | func main() { 31 | // Custom 404 32 | mux.NotFoundFunc(Handler404) 33 | // Handle with any http method, Handle takes http.Handler as argument. 34 | mux.Handle("/index", http.HandlerFunc(homeHandler)) 35 | mux.Handle("/index/:var/info/:test", http.HandlerFunc(varHandler)) 36 | // Get, Post etc... takes http.HandlerFunc as argument. 37 | mux.Post("/home", http.HandlerFunc(homeHandler)) 38 | mux.Get("/home/:var", http.HandlerFunc(varHandler)) 39 | 40 | mux.GetFunc("/test/*", func(rw http.ResponseWriter, req *http.Request) { 41 | rw.Write([]byte(req.RequestURI)) 42 | }) 43 | 44 | // Start Listening 45 | log.Fatal(mux.ListenAndServe(":8080")) 46 | } 47 | 48 | func homeHandler(rw http.ResponseWriter, req *http.Request) { 49 | rw.Write([]byte("WELCOME HOME")) 50 | } 51 | 52 | func varHandler(rw http.ResponseWriter, req *http.Request) { 53 | varr := bone.GetValue(req, "var") 54 | test := bone.GetValue(req, "test") 55 | 56 | var args = struct { 57 | First string 58 | Second string 59 | }{varr, test} 60 | 61 | if err := json.NewEncoder(rw).Encode(&args); err != nil { 62 | panic(err) 63 | } 64 | } 65 | 66 | func Handler404(rw http.ResponseWriter, req *http.Request) { 67 | rw.Write([]byte("These are not the droids you're looking for ...")) 68 | } 69 | -------------------------------------------------------------------------------- /example/002/assets/assets.txt: -------------------------------------------------------------------------------- 1 | assets file -------------------------------------------------------------------------------- /example/002/assets/style.css: -------------------------------------------------------------------------------- 1 | .msg { 2 | color: blue; 3 | } 4 | .block { 5 | background-color: red; 6 | border-radius: 50px; 7 | width: 300px; 8 | height: 300px; 9 | } 10 | -------------------------------------------------------------------------------- /example/002/example.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "io/ioutil" 5 | "net/http" 6 | 7 | "github.com/go-zoo/bone" 8 | ) 9 | 10 | func main() { 11 | mux := bone.New() 12 | 13 | mux.NotFoundFunc(func(rw http.ResponseWriter, req *http.Request) { 14 | rw.WriteHeader(http.StatusTeapot) 15 | }) 16 | 17 | mux.GetFunc("/", defaultHandler) 18 | mux.GetFunc("/reg/#var^[a-z]$/#var2^[0-9]$", ShowVar) 19 | mux.GetFunc("/test", defaultHandler) 20 | mux.Get("/file/", http.StripPrefix("/file/", http.FileServer(http.Dir("assets")))) 21 | 22 | http.ListenAndServe(":8080", mux) 23 | } 24 | 25 | func defaultHandler(rw http.ResponseWriter, req *http.Request) { 26 | file, _ := ioutil.ReadFile("index.html") 27 | rw.Write(file) 28 | } 29 | 30 | func ShowVar(rw http.ResponseWriter, req *http.Request) { 31 | rw.Write([]byte(bone.GetAllValues(req)["var"])) 32 | } 33 | -------------------------------------------------------------------------------- /example/002/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 | 7 |TEST
9 |