├── .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 [![GoDoc](https://godoc.org/github.com/squiidz/bone?status.png)](http://godoc.org/github.com/go-zoo/bone) [![Build Status](https://travis-ci.org/go-zoo/bone.svg)](https://travis-ci.org/go-zoo/bone) [![Go Report Card](https://goreportcard.com/badge/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 | ![alt tag](https://c2.staticflickr.com/2/1070/540747396_5542b42cca_z.jpg) 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 |
8 |

TEST

9 |
10 | 11 |
12 |
13 | 14 | 15 | -------------------------------------------------------------------------------- /example/003/example.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/go-zoo/bone" 7 | "github.com/gorilla/mux" 8 | "github.com/julienschmidt/httprouter" 9 | ) 10 | 11 | func main() { 12 | boneSub := bone.New() 13 | gorrilaSub := mux.NewRouter() 14 | httprouterSub := httprouter.New() 15 | 16 | boneSub.GetFunc("/test", func(rw http.ResponseWriter, req *http.Request) { 17 | rw.Write([]byte("Hello from bone mux")) 18 | }) 19 | 20 | gorrilaSub.HandleFunc("/test", func(rw http.ResponseWriter, req *http.Request) { 21 | rw.Write([]byte("Hello from gorilla mux")) 22 | }) 23 | 24 | httprouterSub.GET("/test", func(rw http.ResponseWriter, req *http.Request, _ httprouter.Params) { 25 | rw.Write([]byte("Hello from httprouter mux")) 26 | }) 27 | 28 | muxx := bone.New().Prefix("/api") 29 | 30 | muxx.SubRoute("/bone", boneSub) 31 | muxx.SubRoute("/gorilla", gorrilaSub) 32 | muxx.SubRoute("/http", httprouterSub) 33 | 34 | http.ListenAndServe(":8080", muxx) 35 | } 36 | -------------------------------------------------------------------------------- /example/003/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/go-zoo/bone/example/003 2 | 3 | go 1.9 4 | 5 | require ( 6 | github.com/go-zoo/bone v0.0.0 7 | github.com/gorilla/context v1.1.1 // indirect 8 | github.com/gorilla/mux v1.6.2 9 | github.com/julienschmidt/httprouter v1.2.0 10 | ) 11 | 12 | replace github.com/go-zoo/bone v0.0.0 => ../../ 13 | -------------------------------------------------------------------------------- /example/003/go.sum: -------------------------------------------------------------------------------- 1 | github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8= 2 | github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= 3 | github.com/gorilla/mux v1.6.2 h1:Pgr17XVTNXAk3q/r4CpKzC5xBM/qW1uVLV+IhRZpIIk= 4 | github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= 5 | github.com/julienschmidt/httprouter v1.2.0 h1:TDTW5Yz1mjftljbcKqRcrYhd4XeOoI98t+9HbQbYf7g= 6 | github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= 7 | -------------------------------------------------------------------------------- /example/004/example.go: -------------------------------------------------------------------------------- 1 | // +build go1.7 2 | 3 | package main 4 | 5 | import ( 6 | "context" 7 | "net/http" 8 | "strconv" 9 | 10 | "github.com/go-zoo/bone" 11 | ) 12 | 13 | func main() { 14 | mux := bone.New() 15 | mux.CaseSensitive = true 16 | mux.RegisterValidatorFunc("isNum", func(s string) bool { 17 | if _, err := strconv.Atoi(s); err == nil { 18 | return true 19 | } 20 | return false 21 | }) 22 | 23 | mux.RegisterValidatorFunc("biggerThan1000", func(s string) bool { 24 | if num, err := strconv.Atoi(s); err == nil { 25 | if num >= 1000 { 26 | return true 27 | } 28 | } 29 | return false 30 | }) 31 | 32 | mux.RegisterValidatorFunc("lessThan8", func(s string) bool { 33 | if len(s) < 8 { 34 | return true 35 | } 36 | return false 37 | }) 38 | 39 | mux.RegisterValidator("exist", &exist{ 40 | things: []string{"steve", "john", "fee", "charlotte"}, 41 | }) 42 | 43 | mux.GetFunc("/ctx/:age|isNum|biggerThan1000/:name|lessThan8|exist", rootHandler) 44 | 45 | http.ListenAndServe(":8080", mux) 46 | } 47 | 48 | func rootHandler(rw http.ResponseWriter, req *http.Request) { 49 | ctx := context.WithValue(req.Context(), "var", bone.GetValue(req, "var")) 50 | subHandler(rw, req.WithContext(ctx)) 51 | } 52 | 53 | func subHandler(rw http.ResponseWriter, req *http.Request) { 54 | vars := bone.GetAllValues(req) 55 | age := vars["age"] 56 | name := vars["name"] 57 | rw.Write([]byte(age + " " + name)) 58 | } 59 | 60 | type exist struct { 61 | things []string 62 | } 63 | 64 | func (e *exist) Validate(s string) bool { 65 | for _, thing := range e.things { 66 | if thing == s { 67 | return true 68 | } 69 | } 70 | return false 71 | } 72 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/go-zoo/bone 2 | 3 | go 1.9 4 | -------------------------------------------------------------------------------- /helper.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 | "net/url" 13 | "strings" 14 | ) 15 | 16 | // ListenAndServe wrapper 17 | func (m *Mux) ListenAndServe(port string) error { 18 | return http.ListenAndServe(port, m) 19 | } 20 | 21 | func (m *Mux) parse(rw http.ResponseWriter, req *http.Request) bool { 22 | for _, r := range m.Routes[req.Method] { 23 | ok := r.parse(rw, req) 24 | if ok { 25 | return true 26 | } 27 | } 28 | // If no HEAD method, default to GET 29 | if req.Method == "HEAD" { 30 | for _, r := range m.Routes["GET"] { 31 | ok := r.parse(rw, req) 32 | if ok { 33 | return true 34 | } 35 | } 36 | } 37 | return false 38 | } 39 | 40 | // StaticRoute check if the request path is for Static route 41 | func (m *Mux) staticRoute(rw http.ResponseWriter, req *http.Request) bool { 42 | for _, s := range m.Routes[static] { 43 | if len(req.URL.Path) >= s.Size { 44 | if req.URL.Path[:s.Size] == s.Path { 45 | s.Handler.ServeHTTP(rw, req) 46 | return true 47 | } 48 | } 49 | } 50 | return false 51 | } 52 | 53 | // HandleNotFound handle when a request does not match a registered handler. 54 | func (m *Mux) HandleNotFound(rw http.ResponseWriter, req *http.Request) { 55 | if m.notFound != nil { 56 | m.notFound.ServeHTTP(rw, req) 57 | } else { 58 | http.NotFound(rw, req) 59 | } 60 | } 61 | 62 | // Check if the path don't end with a / 63 | func (m *Mux) validate(rw http.ResponseWriter, req *http.Request) bool { 64 | plen := len(req.URL.Path) 65 | if plen > 1 && req.URL.Path[plen-1:] == "/" { 66 | cleanURL(&req.URL.Path) 67 | rw.Header().Set("Location", req.URL.String()) 68 | rw.WriteHeader(http.StatusFound) 69 | return true 70 | } 71 | // Retry to find a route that match 72 | return m.parse(rw, req) 73 | } 74 | 75 | func valid(path string) bool { 76 | plen := len(path) 77 | if plen > 1 && path[plen-1:] == "/" { 78 | return false 79 | } 80 | return true 81 | } 82 | 83 | // Clean url path 84 | func cleanURL(url *string) { 85 | ulen := len((*url)) 86 | if ulen > 1 { 87 | if (*url)[ulen-1:] == "/" { 88 | *url = (*url)[:ulen-1] 89 | cleanURL(url) 90 | } 91 | } 92 | } 93 | 94 | // GetValue return the key value, of the current *http.Request 95 | func GetValue(req *http.Request, key string) string { 96 | return GetAllValues(req)[key] 97 | } 98 | 99 | // GetRequestRoute returns the route of given Request 100 | func (m *Mux) GetRequestRoute(req *http.Request) string { 101 | cleanURL(&req.URL.Path) 102 | for _, r := range m.Routes[req.Method] { 103 | if r.Atts != 0 { 104 | if r.Atts&SUB != 0 { 105 | return r.Handler.(*Mux).GetRequestRoute(req) 106 | } 107 | if r.Match(req) { 108 | return r.Path 109 | } 110 | } 111 | if req.URL.Path == r.Path { 112 | return r.Path 113 | } 114 | } 115 | 116 | for _, s := range m.Routes[static] { 117 | if len(req.URL.Path) >= s.Size { 118 | if req.URL.Path[:s.Size] == s.Path { 119 | return s.Path 120 | } 121 | } 122 | } 123 | 124 | return "NotFound" 125 | } 126 | 127 | // GetQuery return the key value, of the current *http.Request query 128 | func GetQuery(req *http.Request, key string) []string { 129 | if ok, value := extractQueries(req); ok { 130 | return value[key] 131 | } 132 | return nil 133 | } 134 | 135 | // GetAllQueries return all queries of the current *http.Request 136 | func GetAllQueries(req *http.Request) map[string][]string { 137 | if ok, values := extractQueries(req); ok { 138 | return values 139 | } 140 | return nil 141 | } 142 | 143 | func extractQueries(req *http.Request) (bool, map[string][]string) { 144 | if q, err := url.ParseQuery(req.URL.RawQuery); err == nil { 145 | var queries = make(map[string][]string) 146 | for k, v := range q { 147 | for _, item := range v { 148 | values := strings.Split(item, ",") 149 | queries[k] = append(queries[k], values...) 150 | } 151 | } 152 | return true, queries 153 | } 154 | return false, nil 155 | } 156 | 157 | func (m *Mux) otherMethods(rw http.ResponseWriter, req *http.Request) bool { 158 | for _, met := range method { 159 | if met != req.Method { 160 | for _, r := range m.Routes[met] { 161 | ok := r.exists(rw, req) 162 | if ok { 163 | rw.WriteHeader(http.StatusMethodNotAllowed) 164 | return true 165 | } 166 | } 167 | } 168 | } 169 | return false 170 | } 171 | -------------------------------------------------------------------------------- /helper_15.go: -------------------------------------------------------------------------------- 1 | // +build !go1.7 2 | 3 | /******************************** 4 | *** Multiplexer for Go *** 5 | *** Bone is under MIT license *** 6 | *** Code by CodingFerret *** 7 | *** github.com/go-zoo *** 8 | *********************************/ 9 | 10 | package bone 11 | 12 | import ( 13 | "net/http" 14 | "sync" 15 | ) 16 | 17 | var globalVars = struct { 18 | sync.RWMutex 19 | v map[*http.Request]map[string]string 20 | }{v: make(map[*http.Request]map[string]string)} 21 | 22 | // GetAllValues return the req PARAMs 23 | func GetAllValues(req *http.Request) map[string]string { 24 | globalVars.RLock() 25 | values := globalVars.v[req] 26 | globalVars.RUnlock() 27 | return values 28 | } 29 | 30 | // serveMatchedRequest is an extension point for Route which allows us to conditionally compile for 31 | // go1.7 and = 1 { 72 | switch s[:1] { 73 | case ":": 74 | s = s[1:] 75 | if r.Pattern == nil { 76 | r.Pattern = make(map[int]string) 77 | } 78 | if validators := containsValidators(s); validators != nil { 79 | if r.validators == nil { 80 | r.validators = make(map[string][]string) 81 | } 82 | for _, vali := range validators { 83 | s = s[:validators[0].start] 84 | r.validators[s] = append(r.validators[s], vali.name[1:]) 85 | } 86 | } 87 | r.Pattern[i] = s 88 | r.Atts |= PARAM 89 | case "#": 90 | if r.Compile == nil { 91 | r.Compile = make(map[int]*regexp.Regexp) 92 | r.Tag = make(map[int]string) 93 | } 94 | tmp := strings.Split(s, "^") 95 | r.Tag[i] = tmp[0][1:] 96 | r.Compile[i] = regexp.MustCompile("^" + tmp[1][:len(tmp[1])-1]) 97 | r.Atts |= REGEX 98 | case "*": 99 | r.wildPos = i 100 | r.Atts |= WC 101 | default: 102 | r.Token.raw = append(r.Token.raw, i) 103 | } 104 | } 105 | r.Token.Size++ 106 | } 107 | } 108 | 109 | // Match check if the request match the route Pattern 110 | func (r *Route) Match(req *http.Request) bool { 111 | ok, _ := r.matchAndParse(req) 112 | return ok 113 | } 114 | 115 | // matchAndParse check if the request matches the route Pattern and returns a map of the parsed 116 | // variables if it matches 117 | func (r *Route) matchAndParse(req *http.Request) (bool, map[string]string) { 118 | ss := strings.Split(req.URL.EscapedPath(), "/") 119 | if r.matchRawTokens(&ss) { 120 | if len(ss) == r.Token.Size || r.Atts&WC != 0 { 121 | totalSize := len(r.Pattern) 122 | if r.Atts®EX != 0 { 123 | totalSize += len(r.Compile) 124 | } 125 | 126 | vars := make(map[string]string, totalSize) 127 | for k, v := range r.Pattern { 128 | if validators := r.validators[v]; validators != nil { 129 | for _, vname := range validators { 130 | if !(*r.mux).Validators[vname].Validate(ss[k]) { 131 | return false, nil 132 | } 133 | } 134 | } 135 | vars[v], _ = url.QueryUnescape(ss[k]) 136 | } 137 | 138 | if r.Atts®EX != 0 { 139 | for k, v := range r.Compile { 140 | if !v.MatchString(ss[k]) { 141 | return false, nil 142 | } 143 | vars[r.Tag[k]], _ = url.QueryUnescape(ss[k]) 144 | } 145 | } 146 | 147 | return true, vars 148 | } 149 | } 150 | return false, nil 151 | } 152 | 153 | func (r *Route) parse(rw http.ResponseWriter, req *http.Request) bool { 154 | if r.Atts != 0 { 155 | if r.Atts&SUB != 0 { 156 | if len(req.URL.Path) >= r.Size { 157 | if req.URL.Path[:r.Size] == r.Path { 158 | req.URL.Path = req.URL.Path[r.Size:] 159 | r.Handler.ServeHTTP(rw, req) 160 | return true 161 | } 162 | } 163 | } 164 | 165 | if ok, vars := r.matchAndParse(req); ok { 166 | r.serveMatchedRequest(rw, req, vars) 167 | return true 168 | } 169 | } 170 | if req.URL.Path == r.Path { 171 | r.Handler.ServeHTTP(rw, req) 172 | return true 173 | } 174 | return false 175 | } 176 | 177 | func (r *Route) matchRawTokens(ss *[]string) bool { 178 | if len(*ss) >= r.Token.Size { 179 | for i, v := range r.Token.raw { 180 | if (*ss)[v] != r.Token.Tokens[v] { 181 | if r.Atts&WC != 0 && r.wildPos == i { 182 | return true 183 | } 184 | return false 185 | } 186 | } 187 | return true 188 | } 189 | return false 190 | } 191 | 192 | func (r *Route) exists(rw http.ResponseWriter, req *http.Request) bool { 193 | if r.Atts != 0 { 194 | if r.Atts&SUB != 0 { 195 | if len(req.URL.Path) >= r.Size { 196 | if req.URL.Path[:r.Size] == r.Path { 197 | return true 198 | } 199 | } 200 | } 201 | 202 | if ok, _ := r.matchAndParse(req); ok { 203 | return true 204 | } 205 | } 206 | if req.URL.Path == r.Path { 207 | return true 208 | } 209 | return false 210 | } 211 | 212 | // Get set the route method to Get 213 | func (r *Route) Get() *Route { 214 | r.Method = "GET" 215 | return r 216 | } 217 | 218 | // Post set the route method to Post 219 | func (r *Route) Post() *Route { 220 | r.Method = "POST" 221 | return r 222 | } 223 | 224 | // Put set the route method to Put 225 | func (r *Route) Put() *Route { 226 | r.Method = "PUT" 227 | return r 228 | } 229 | 230 | // Delete set the route method to Delete 231 | func (r *Route) Delete() *Route { 232 | r.Method = "DELETE" 233 | return r 234 | } 235 | 236 | // Head set the route method to Head 237 | func (r *Route) Head() *Route { 238 | r.Method = "HEAD" 239 | return r 240 | } 241 | 242 | // Patch set the route method to Patch 243 | func (r *Route) Patch() *Route { 244 | r.Method = "PATCH" 245 | return r 246 | } 247 | 248 | // Options set the route method to Options 249 | func (r *Route) Options() *Route { 250 | r.Method = "OPTIONS" 251 | return r 252 | } 253 | 254 | func (r *Route) ServeHTTP(rw http.ResponseWriter, req *http.Request) { 255 | if r.Method != "" { 256 | if req.Method == r.Method { 257 | r.Handler.ServeHTTP(rw, req) 258 | return 259 | } 260 | http.NotFound(rw, req) 261 | return 262 | } 263 | r.Handler.ServeHTTP(rw, req) 264 | } 265 | -------------------------------------------------------------------------------- /route_test.go: -------------------------------------------------------------------------------- 1 | package bone 2 | 3 | import ( 4 | "net/http" 5 | "reflect" 6 | "regexp" 7 | "testing" 8 | ) 9 | 10 | func TestNewRoute(t *testing.T) { 11 | type args struct { 12 | url string 13 | h http.Handler 14 | } 15 | tests := []struct { 16 | name string 17 | args args 18 | want *Route 19 | }{ 20 | // TODO: Add test cases. 21 | } 22 | for _, tt := range tests { 23 | t.Run(tt.name, func(t *testing.T) { 24 | if got := NewRoute(nil, tt.args.url, tt.args.h); !reflect.DeepEqual(got, tt.want) { 25 | t.Errorf("NewRoute() = %v, want %v", got, tt.want) 26 | } 27 | }) 28 | } 29 | } 30 | 31 | func TestRoute_save(t *testing.T) { 32 | type fields struct { 33 | Path string 34 | Method string 35 | Size int 36 | Atts int 37 | wildPos int 38 | Token Token 39 | Pattern map[int]string 40 | Compile map[int]*regexp.Regexp 41 | Tag map[int]string 42 | Handler http.Handler 43 | } 44 | tests := []struct { 45 | name string 46 | fields fields 47 | }{ 48 | // TODO: Add test cases. 49 | } 50 | for _, tt := range tests { 51 | t.Run(tt.name, func(t *testing.T) { 52 | r := &Route{ 53 | Path: tt.fields.Path, 54 | Method: tt.fields.Method, 55 | Size: tt.fields.Size, 56 | Atts: tt.fields.Atts, 57 | wildPos: tt.fields.wildPos, 58 | Token: tt.fields.Token, 59 | Pattern: tt.fields.Pattern, 60 | Compile: tt.fields.Compile, 61 | Tag: tt.fields.Tag, 62 | Handler: tt.fields.Handler, 63 | } 64 | r.save() 65 | }) 66 | } 67 | } 68 | 69 | func TestRoute_Match(t *testing.T) { 70 | type fields struct { 71 | Path string 72 | Method string 73 | Size int 74 | Atts int 75 | wildPos int 76 | Token Token 77 | Pattern map[int]string 78 | Compile map[int]*regexp.Regexp 79 | Tag map[int]string 80 | Handler http.Handler 81 | } 82 | type args struct { 83 | req *http.Request 84 | } 85 | tests := []struct { 86 | name string 87 | fields fields 88 | args args 89 | want bool 90 | }{ 91 | // TODO: Add test cases. 92 | } 93 | for _, tt := range tests { 94 | t.Run(tt.name, func(t *testing.T) { 95 | r := &Route{ 96 | Path: tt.fields.Path, 97 | Method: tt.fields.Method, 98 | Size: tt.fields.Size, 99 | Atts: tt.fields.Atts, 100 | wildPos: tt.fields.wildPos, 101 | Token: tt.fields.Token, 102 | Pattern: tt.fields.Pattern, 103 | Compile: tt.fields.Compile, 104 | Tag: tt.fields.Tag, 105 | Handler: tt.fields.Handler, 106 | } 107 | if got := r.Match(tt.args.req); got != tt.want { 108 | t.Errorf("Route.Match() = %v, want %v", got, tt.want) 109 | } 110 | }) 111 | } 112 | } 113 | 114 | func TestRoute_matchAndParse(t *testing.T) { 115 | type fields struct { 116 | Path string 117 | Method string 118 | Size int 119 | Atts int 120 | wildPos int 121 | Token Token 122 | Pattern map[int]string 123 | Compile map[int]*regexp.Regexp 124 | Tag map[int]string 125 | Handler http.Handler 126 | } 127 | type args struct { 128 | req *http.Request 129 | } 130 | tests := []struct { 131 | name string 132 | fields fields 133 | args args 134 | want bool 135 | want1 map[string]string 136 | }{ 137 | // TODO: Add test cases. 138 | } 139 | for _, tt := range tests { 140 | t.Run(tt.name, func(t *testing.T) { 141 | r := &Route{ 142 | Path: tt.fields.Path, 143 | Method: tt.fields.Method, 144 | Size: tt.fields.Size, 145 | Atts: tt.fields.Atts, 146 | wildPos: tt.fields.wildPos, 147 | Token: tt.fields.Token, 148 | Pattern: tt.fields.Pattern, 149 | Compile: tt.fields.Compile, 150 | Tag: tt.fields.Tag, 151 | Handler: tt.fields.Handler, 152 | } 153 | got, got1 := r.matchAndParse(tt.args.req) 154 | if got != tt.want { 155 | t.Errorf("Route.matchAndParse() got = %v, want %v", got, tt.want) 156 | } 157 | if !reflect.DeepEqual(got1, tt.want1) { 158 | t.Errorf("Route.matchAndParse() got1 = %v, want %v", got1, tt.want1) 159 | } 160 | }) 161 | } 162 | } 163 | 164 | func TestRoute_parse(t *testing.T) { 165 | type fields struct { 166 | Path string 167 | Method string 168 | Size int 169 | Atts int 170 | wildPos int 171 | Token Token 172 | Pattern map[int]string 173 | Compile map[int]*regexp.Regexp 174 | Tag map[int]string 175 | Handler http.Handler 176 | } 177 | type args struct { 178 | rw http.ResponseWriter 179 | req *http.Request 180 | } 181 | tests := []struct { 182 | name string 183 | fields fields 184 | args args 185 | want bool 186 | }{ 187 | // TODO: Add test cases. 188 | } 189 | for _, tt := range tests { 190 | t.Run(tt.name, func(t *testing.T) { 191 | r := &Route{ 192 | Path: tt.fields.Path, 193 | Method: tt.fields.Method, 194 | Size: tt.fields.Size, 195 | Atts: tt.fields.Atts, 196 | wildPos: tt.fields.wildPos, 197 | Token: tt.fields.Token, 198 | Pattern: tt.fields.Pattern, 199 | Compile: tt.fields.Compile, 200 | Tag: tt.fields.Tag, 201 | Handler: tt.fields.Handler, 202 | } 203 | if got := r.parse(tt.args.rw, tt.args.req); got != tt.want { 204 | t.Errorf("Route.parse() = %v, want %v", got, tt.want) 205 | } 206 | }) 207 | } 208 | } 209 | 210 | func TestRoute_matchRawTokens(t *testing.T) { 211 | type fields struct { 212 | Path string 213 | Method string 214 | Size int 215 | Atts int 216 | wildPos int 217 | Token Token 218 | Pattern map[int]string 219 | Compile map[int]*regexp.Regexp 220 | Tag map[int]string 221 | Handler http.Handler 222 | } 223 | type args struct { 224 | ss *[]string 225 | } 226 | tests := []struct { 227 | name string 228 | fields fields 229 | args args 230 | want bool 231 | }{ 232 | // TODO: Add test cases. 233 | } 234 | for _, tt := range tests { 235 | t.Run(tt.name, func(t *testing.T) { 236 | r := &Route{ 237 | Path: tt.fields.Path, 238 | Method: tt.fields.Method, 239 | Size: tt.fields.Size, 240 | Atts: tt.fields.Atts, 241 | wildPos: tt.fields.wildPos, 242 | Token: tt.fields.Token, 243 | Pattern: tt.fields.Pattern, 244 | Compile: tt.fields.Compile, 245 | Tag: tt.fields.Tag, 246 | Handler: tt.fields.Handler, 247 | } 248 | if got := r.matchRawTokens(tt.args.ss); got != tt.want { 249 | t.Errorf("Route.matchRawTokens() = %v, want %v", got, tt.want) 250 | } 251 | }) 252 | } 253 | } 254 | 255 | func TestRoute_Get(t *testing.T) { 256 | type fields struct { 257 | Path string 258 | Method string 259 | Size int 260 | Atts int 261 | wildPos int 262 | Token Token 263 | Pattern map[int]string 264 | Compile map[int]*regexp.Regexp 265 | Tag map[int]string 266 | Handler http.Handler 267 | } 268 | tests := []struct { 269 | name string 270 | fields fields 271 | want *Route 272 | }{ 273 | // TODO: Add test cases. 274 | } 275 | for _, tt := range tests { 276 | t.Run(tt.name, func(t *testing.T) { 277 | r := &Route{ 278 | Path: tt.fields.Path, 279 | Method: tt.fields.Method, 280 | Size: tt.fields.Size, 281 | Atts: tt.fields.Atts, 282 | wildPos: tt.fields.wildPos, 283 | Token: tt.fields.Token, 284 | Pattern: tt.fields.Pattern, 285 | Compile: tt.fields.Compile, 286 | Tag: tt.fields.Tag, 287 | Handler: tt.fields.Handler, 288 | } 289 | if got := r.Get(); !reflect.DeepEqual(got, tt.want) { 290 | t.Errorf("Route.Get() = %v, want %v", got, tt.want) 291 | } 292 | }) 293 | } 294 | } 295 | 296 | func TestRoute_Post(t *testing.T) { 297 | type fields struct { 298 | Path string 299 | Method string 300 | Size int 301 | Atts int 302 | wildPos int 303 | Token Token 304 | Pattern map[int]string 305 | Compile map[int]*regexp.Regexp 306 | Tag map[int]string 307 | Handler http.Handler 308 | } 309 | tests := []struct { 310 | name string 311 | fields fields 312 | want *Route 313 | }{ 314 | // TODO: Add test cases. 315 | } 316 | for _, tt := range tests { 317 | t.Run(tt.name, func(t *testing.T) { 318 | r := &Route{ 319 | Path: tt.fields.Path, 320 | Method: tt.fields.Method, 321 | Size: tt.fields.Size, 322 | Atts: tt.fields.Atts, 323 | wildPos: tt.fields.wildPos, 324 | Token: tt.fields.Token, 325 | Pattern: tt.fields.Pattern, 326 | Compile: tt.fields.Compile, 327 | Tag: tt.fields.Tag, 328 | Handler: tt.fields.Handler, 329 | } 330 | if got := r.Post(); !reflect.DeepEqual(got, tt.want) { 331 | t.Errorf("Route.Post() = %v, want %v", got, tt.want) 332 | } 333 | }) 334 | } 335 | } 336 | 337 | func TestRoute_Put(t *testing.T) { 338 | type fields struct { 339 | Path string 340 | Method string 341 | Size int 342 | Atts int 343 | wildPos int 344 | Token Token 345 | Pattern map[int]string 346 | Compile map[int]*regexp.Regexp 347 | Tag map[int]string 348 | Handler http.Handler 349 | } 350 | tests := []struct { 351 | name string 352 | fields fields 353 | want *Route 354 | }{ 355 | // TODO: Add test cases. 356 | } 357 | for _, tt := range tests { 358 | t.Run(tt.name, func(t *testing.T) { 359 | r := &Route{ 360 | Path: tt.fields.Path, 361 | Method: tt.fields.Method, 362 | Size: tt.fields.Size, 363 | Atts: tt.fields.Atts, 364 | wildPos: tt.fields.wildPos, 365 | Token: tt.fields.Token, 366 | Pattern: tt.fields.Pattern, 367 | Compile: tt.fields.Compile, 368 | Tag: tt.fields.Tag, 369 | Handler: tt.fields.Handler, 370 | } 371 | if got := r.Put(); !reflect.DeepEqual(got, tt.want) { 372 | t.Errorf("Route.Put() = %v, want %v", got, tt.want) 373 | } 374 | }) 375 | } 376 | } 377 | 378 | func TestRoute_Delete(t *testing.T) { 379 | type fields struct { 380 | Path string 381 | Method string 382 | Size int 383 | Atts int 384 | wildPos int 385 | Token Token 386 | Pattern map[int]string 387 | Compile map[int]*regexp.Regexp 388 | Tag map[int]string 389 | Handler http.Handler 390 | } 391 | tests := []struct { 392 | name string 393 | fields fields 394 | want *Route 395 | }{ 396 | // TODO: Add test cases. 397 | } 398 | for _, tt := range tests { 399 | t.Run(tt.name, func(t *testing.T) { 400 | r := &Route{ 401 | Path: tt.fields.Path, 402 | Method: tt.fields.Method, 403 | Size: tt.fields.Size, 404 | Atts: tt.fields.Atts, 405 | wildPos: tt.fields.wildPos, 406 | Token: tt.fields.Token, 407 | Pattern: tt.fields.Pattern, 408 | Compile: tt.fields.Compile, 409 | Tag: tt.fields.Tag, 410 | Handler: tt.fields.Handler, 411 | } 412 | if got := r.Delete(); !reflect.DeepEqual(got, tt.want) { 413 | t.Errorf("Route.Delete() = %v, want %v", got, tt.want) 414 | } 415 | }) 416 | } 417 | } 418 | 419 | func TestRoute_Head(t *testing.T) { 420 | type fields struct { 421 | Path string 422 | Method string 423 | Size int 424 | Atts int 425 | wildPos int 426 | Token Token 427 | Pattern map[int]string 428 | Compile map[int]*regexp.Regexp 429 | Tag map[int]string 430 | Handler http.Handler 431 | } 432 | tests := []struct { 433 | name string 434 | fields fields 435 | want *Route 436 | }{ 437 | // TODO: Add test cases. 438 | } 439 | for _, tt := range tests { 440 | t.Run(tt.name, func(t *testing.T) { 441 | r := &Route{ 442 | Path: tt.fields.Path, 443 | Method: tt.fields.Method, 444 | Size: tt.fields.Size, 445 | Atts: tt.fields.Atts, 446 | wildPos: tt.fields.wildPos, 447 | Token: tt.fields.Token, 448 | Pattern: tt.fields.Pattern, 449 | Compile: tt.fields.Compile, 450 | Tag: tt.fields.Tag, 451 | Handler: tt.fields.Handler, 452 | } 453 | if got := r.Head(); !reflect.DeepEqual(got, tt.want) { 454 | t.Errorf("Route.Head() = %v, want %v", got, tt.want) 455 | } 456 | }) 457 | } 458 | } 459 | 460 | func TestRoute_Patch(t *testing.T) { 461 | type fields struct { 462 | Path string 463 | Method string 464 | Size int 465 | Atts int 466 | wildPos int 467 | Token Token 468 | Pattern map[int]string 469 | Compile map[int]*regexp.Regexp 470 | Tag map[int]string 471 | Handler http.Handler 472 | } 473 | tests := []struct { 474 | name string 475 | fields fields 476 | want *Route 477 | }{ 478 | // TODO: Add test cases. 479 | } 480 | for _, tt := range tests { 481 | t.Run(tt.name, func(t *testing.T) { 482 | r := &Route{ 483 | Path: tt.fields.Path, 484 | Method: tt.fields.Method, 485 | Size: tt.fields.Size, 486 | Atts: tt.fields.Atts, 487 | wildPos: tt.fields.wildPos, 488 | Token: tt.fields.Token, 489 | Pattern: tt.fields.Pattern, 490 | Compile: tt.fields.Compile, 491 | Tag: tt.fields.Tag, 492 | Handler: tt.fields.Handler, 493 | } 494 | if got := r.Patch(); !reflect.DeepEqual(got, tt.want) { 495 | t.Errorf("Route.Patch() = %v, want %v", got, tt.want) 496 | } 497 | }) 498 | } 499 | } 500 | 501 | func TestRoute_Options(t *testing.T) { 502 | type fields struct { 503 | Path string 504 | Method string 505 | Size int 506 | Atts int 507 | wildPos int 508 | Token Token 509 | Pattern map[int]string 510 | Compile map[int]*regexp.Regexp 511 | Tag map[int]string 512 | Handler http.Handler 513 | } 514 | tests := []struct { 515 | name string 516 | fields fields 517 | want *Route 518 | }{ 519 | // TODO: Add test cases. 520 | } 521 | for _, tt := range tests { 522 | t.Run(tt.name, func(t *testing.T) { 523 | r := &Route{ 524 | Path: tt.fields.Path, 525 | Method: tt.fields.Method, 526 | Size: tt.fields.Size, 527 | Atts: tt.fields.Atts, 528 | wildPos: tt.fields.wildPos, 529 | Token: tt.fields.Token, 530 | Pattern: tt.fields.Pattern, 531 | Compile: tt.fields.Compile, 532 | Tag: tt.fields.Tag, 533 | Handler: tt.fields.Handler, 534 | } 535 | if got := r.Options(); !reflect.DeepEqual(got, tt.want) { 536 | t.Errorf("Route.Options() = %v, want %v", got, tt.want) 537 | } 538 | }) 539 | } 540 | } 541 | 542 | func TestRoute_ServeHTTP(t *testing.T) { 543 | type fields struct { 544 | Path string 545 | Method string 546 | Size int 547 | Atts int 548 | wildPos int 549 | Token Token 550 | Pattern map[int]string 551 | Compile map[int]*regexp.Regexp 552 | Tag map[int]string 553 | Handler http.Handler 554 | } 555 | type args struct { 556 | rw http.ResponseWriter 557 | req *http.Request 558 | } 559 | tests := []struct { 560 | name string 561 | fields fields 562 | args args 563 | }{ 564 | // TODO: Add test cases. 565 | } 566 | for _, tt := range tests { 567 | t.Run(tt.name, func(t *testing.T) { 568 | r := &Route{ 569 | Path: tt.fields.Path, 570 | Method: tt.fields.Method, 571 | Size: tt.fields.Size, 572 | Atts: tt.fields.Atts, 573 | wildPos: tt.fields.wildPos, 574 | Token: tt.fields.Token, 575 | Pattern: tt.fields.Pattern, 576 | Compile: tt.fields.Compile, 577 | Tag: tt.fields.Tag, 578 | Handler: tt.fields.Handler, 579 | } 580 | r.ServeHTTP(tt.args.rw, tt.args.req) 581 | }) 582 | } 583 | } 584 | -------------------------------------------------------------------------------- /validator.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 | // Validator can be passed to a route to validate the params 11 | type Validator interface { 12 | Validate(string) bool 13 | } 14 | 15 | type validatorFunc struct { 16 | validateFunc func(string) bool 17 | } 18 | 19 | func newValidatorFunc(v func(string) bool) validatorFunc { 20 | return validatorFunc{validateFunc: v} 21 | } 22 | 23 | func (v validatorFunc) Validate(s string) bool { 24 | return v.validateFunc(s) 25 | } 26 | 27 | type validatorInfo struct { 28 | start int 29 | end int 30 | name string 31 | } 32 | 33 | func containsValidators(path string) []validatorInfo { 34 | var index []int 35 | for i, c := range path { 36 | if c == '|' { 37 | index = append(index, i) 38 | } 39 | } 40 | 41 | if len(index) > 0 { 42 | var validators []validatorInfo 43 | for i, pos := range index { 44 | if i+1 == len(index) { 45 | validators = append(validators, validatorInfo{ 46 | start: pos, 47 | end: len(path), 48 | name: path[pos:len(path)], 49 | }) 50 | } else { 51 | validators = append(validators, validatorInfo{ 52 | start: pos, 53 | end: index[i+1], 54 | name: path[pos:index[i+1]], 55 | }) 56 | } 57 | } 58 | return validators 59 | } 60 | return nil 61 | } 62 | --------------------------------------------------------------------------------