├── .hound.yml ├── mux ├── doc.go ├── route.go ├── benchmark_test.go ├── example_test.go ├── tree_test.go ├── tree.go ├── node.go └── node_test.go ├── path ├── doc.go ├── benchmark_test.go ├── path.go └── path_test.go ├── context ├── doc.go ├── context.go ├── context_test.go ├── param.go └── param_test.go ├── middleware ├── doc.go ├── middleware_test.go ├── collection.go ├── middleware.go └── collection_test.go ├── website ├── src │ ├── static │ │ ├── img │ │ │ ├── logo.png │ │ │ ├── fasthttp.png │ │ │ ├── gopher_files.png │ │ │ ├── gopher_http2.png │ │ │ ├── gopher_search.png │ │ │ ├── apple-touch-icon.png │ │ │ ├── gopher_lowmemory.png │ │ │ ├── gopher_middleware.png │ │ │ ├── gpher_multidomain.png │ │ │ ├── gopher_authentication.png │ │ │ ├── android-chrome-192x192.png │ │ │ ├── android-chrome-512x512.png │ │ │ └── gopher.svg │ │ ├── favicon │ │ │ ├── favicon.ico │ │ │ ├── favicon-16x16.png │ │ │ └── favicon-32x32.png │ │ ├── benchmarks │ │ │ ├── fasthttp │ │ │ │ ├── benchmark.png │ │ │ │ ├── concurrency.png │ │ │ │ ├── benchmark_alloc.png │ │ │ │ ├── benchmark_latency.png │ │ │ │ ├── concurrency_alloc.png │ │ │ │ ├── benchmark-pipeline.png │ │ │ │ ├── concurrency-pipeline.png │ │ │ │ ├── concurrency_latency.png │ │ │ │ ├── cpubound_benchmark.png │ │ │ │ └── cpubound_concurrency.png │ │ │ └── nethttp │ │ │ │ ├── benchmark.png │ │ │ │ ├── concurrency.png │ │ │ │ ├── benchmark_alloc.png │ │ │ │ ├── benchmark-pipeline.png │ │ │ │ ├── benchmark_latency.png │ │ │ │ ├── concurrency_alloc.png │ │ │ │ ├── cpubound_benchmark.png │ │ │ │ ├── concurrency-pipeline.png │ │ │ │ ├── concurrency_latency.png │ │ │ │ └── cpubound_concurrency.png │ │ ├── site.webmanifest │ │ └── css │ │ │ └── custom.css │ ├── .gitignore │ ├── package.json │ ├── sidebars.json │ ├── pages │ │ └── en │ │ │ ├── users.js │ │ │ ├── help.js │ │ │ └── index.js │ ├── core │ │ └── Footer.js │ ├── siteConfig.js │ └── README.md └── docs │ ├── installation.md │ ├── http2.md │ ├── sub-router.md │ ├── apphandler.md │ ├── basic-example.md │ ├── static-files.md │ ├── panic.md │ ├── https.md │ ├── routing.md │ ├── basic-authentication.md │ ├── multidomain.md │ ├── benchmark.md │ └── middleware.md ├── .travis.yml ├── go.mod ├── route.go ├── .gitignore ├── .github ├── workflows │ ├── test.yml │ └── website-publish.yml ├── FUNDING.yml ├── CONTRIBUTING.md └── CODE_OF_CONDUCT.md ├── go.sum ├── tree.go ├── LICENSE.md ├── doc.go ├── route_test.go ├── mocks_test.go ├── README.md ├── router.go ├── benchmark_test.go ├── nethttp.go ├── fasthttp.go ├── example_test.go └── fasthttp_test.go /.hound.yml: -------------------------------------------------------------------------------- 1 | go: 2 | enabled: true 3 | -------------------------------------------------------------------------------- /mux/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package mux provide route tree 3 | */ 4 | package mux 5 | -------------------------------------------------------------------------------- /path/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package path provide path utils 3 | */ 4 | package path 5 | -------------------------------------------------------------------------------- /context/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package context provide router context 3 | */ 4 | package context 5 | -------------------------------------------------------------------------------- /middleware/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package middleware provide router middleware 3 | */ 4 | package middleware 5 | -------------------------------------------------------------------------------- /website/src/static/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vardius/gorouter/HEAD/website/src/static/img/logo.png -------------------------------------------------------------------------------- /website/src/static/img/fasthttp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vardius/gorouter/HEAD/website/src/static/img/fasthttp.png -------------------------------------------------------------------------------- /website/src/static/favicon/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vardius/gorouter/HEAD/website/src/static/favicon/favicon.ico -------------------------------------------------------------------------------- /website/src/static/img/gopher_files.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vardius/gorouter/HEAD/website/src/static/img/gopher_files.png -------------------------------------------------------------------------------- /website/src/static/img/gopher_http2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vardius/gorouter/HEAD/website/src/static/img/gopher_http2.png -------------------------------------------------------------------------------- /website/src/static/img/gopher_search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vardius/gorouter/HEAD/website/src/static/img/gopher_search.png -------------------------------------------------------------------------------- /website/src/static/img/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vardius/gorouter/HEAD/website/src/static/img/apple-touch-icon.png -------------------------------------------------------------------------------- /website/src/static/img/gopher_lowmemory.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vardius/gorouter/HEAD/website/src/static/img/gopher_lowmemory.png -------------------------------------------------------------------------------- /mux/route.go: -------------------------------------------------------------------------------- 1 | package mux 2 | 3 | // Route is an handler aware route interface 4 | type Route interface { 5 | Handler() interface{} 6 | } 7 | -------------------------------------------------------------------------------- /website/src/static/favicon/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vardius/gorouter/HEAD/website/src/static/favicon/favicon-16x16.png -------------------------------------------------------------------------------- /website/src/static/favicon/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vardius/gorouter/HEAD/website/src/static/favicon/favicon-32x32.png -------------------------------------------------------------------------------- /website/src/static/img/gopher_middleware.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vardius/gorouter/HEAD/website/src/static/img/gopher_middleware.png -------------------------------------------------------------------------------- /website/src/static/img/gpher_multidomain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vardius/gorouter/HEAD/website/src/static/img/gpher_multidomain.png -------------------------------------------------------------------------------- /website/src/static/img/gopher_authentication.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vardius/gorouter/HEAD/website/src/static/img/gopher_authentication.png -------------------------------------------------------------------------------- /website/src/static/img/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vardius/gorouter/HEAD/website/src/static/img/android-chrome-192x192.png -------------------------------------------------------------------------------- /website/src/static/img/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vardius/gorouter/HEAD/website/src/static/img/android-chrome-512x512.png -------------------------------------------------------------------------------- /website/src/static/benchmarks/fasthttp/benchmark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vardius/gorouter/HEAD/website/src/static/benchmarks/fasthttp/benchmark.png -------------------------------------------------------------------------------- /website/src/static/benchmarks/nethttp/benchmark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vardius/gorouter/HEAD/website/src/static/benchmarks/nethttp/benchmark.png -------------------------------------------------------------------------------- /website/src/static/benchmarks/nethttp/concurrency.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vardius/gorouter/HEAD/website/src/static/benchmarks/nethttp/concurrency.png -------------------------------------------------------------------------------- /website/src/static/benchmarks/fasthttp/concurrency.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vardius/gorouter/HEAD/website/src/static/benchmarks/fasthttp/concurrency.png -------------------------------------------------------------------------------- /website/src/static/benchmarks/fasthttp/benchmark_alloc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vardius/gorouter/HEAD/website/src/static/benchmarks/fasthttp/benchmark_alloc.png -------------------------------------------------------------------------------- /website/src/static/benchmarks/nethttp/benchmark_alloc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vardius/gorouter/HEAD/website/src/static/benchmarks/nethttp/benchmark_alloc.png -------------------------------------------------------------------------------- /website/src/static/benchmarks/fasthttp/benchmark_latency.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vardius/gorouter/HEAD/website/src/static/benchmarks/fasthttp/benchmark_latency.png -------------------------------------------------------------------------------- /website/src/static/benchmarks/fasthttp/concurrency_alloc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vardius/gorouter/HEAD/website/src/static/benchmarks/fasthttp/concurrency_alloc.png -------------------------------------------------------------------------------- /website/src/static/benchmarks/nethttp/benchmark-pipeline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vardius/gorouter/HEAD/website/src/static/benchmarks/nethttp/benchmark-pipeline.png -------------------------------------------------------------------------------- /website/src/static/benchmarks/nethttp/benchmark_latency.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vardius/gorouter/HEAD/website/src/static/benchmarks/nethttp/benchmark_latency.png -------------------------------------------------------------------------------- /website/src/static/benchmarks/nethttp/concurrency_alloc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vardius/gorouter/HEAD/website/src/static/benchmarks/nethttp/concurrency_alloc.png -------------------------------------------------------------------------------- /website/src/static/benchmarks/nethttp/cpubound_benchmark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vardius/gorouter/HEAD/website/src/static/benchmarks/nethttp/cpubound_benchmark.png -------------------------------------------------------------------------------- /website/src/static/benchmarks/fasthttp/benchmark-pipeline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vardius/gorouter/HEAD/website/src/static/benchmarks/fasthttp/benchmark-pipeline.png -------------------------------------------------------------------------------- /website/src/static/benchmarks/fasthttp/concurrency-pipeline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vardius/gorouter/HEAD/website/src/static/benchmarks/fasthttp/concurrency-pipeline.png -------------------------------------------------------------------------------- /website/src/static/benchmarks/fasthttp/concurrency_latency.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vardius/gorouter/HEAD/website/src/static/benchmarks/fasthttp/concurrency_latency.png -------------------------------------------------------------------------------- /website/src/static/benchmarks/fasthttp/cpubound_benchmark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vardius/gorouter/HEAD/website/src/static/benchmarks/fasthttp/cpubound_benchmark.png -------------------------------------------------------------------------------- /website/src/static/benchmarks/fasthttp/cpubound_concurrency.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vardius/gorouter/HEAD/website/src/static/benchmarks/fasthttp/cpubound_concurrency.png -------------------------------------------------------------------------------- /website/src/static/benchmarks/nethttp/concurrency-pipeline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vardius/gorouter/HEAD/website/src/static/benchmarks/nethttp/concurrency-pipeline.png -------------------------------------------------------------------------------- /website/src/static/benchmarks/nethttp/concurrency_latency.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vardius/gorouter/HEAD/website/src/static/benchmarks/nethttp/concurrency_latency.png -------------------------------------------------------------------------------- /website/src/static/benchmarks/nethttp/cpubound_concurrency.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vardius/gorouter/HEAD/website/src/static/benchmarks/nethttp/cpubound_concurrency.png -------------------------------------------------------------------------------- /website/src/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | 3 | node_modules 4 | build 5 | 6 | lib/core/metadata.js 7 | lib/core/MetadataBlog.js 8 | 9 | /translated_docs 10 | /build/ 11 | /node_modules 12 | /i18n/* 13 | 14 | yarn.lock 15 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | go: 3 | - "1.15" 4 | - tip 5 | script: 6 | - go build 7 | - go test ./... -v -race -cover -coverprofile=coverage.txt -covermode=atomic 8 | - go test -bench=. -run=^$ -cpu=4 -benchmem 9 | after_script: 10 | - bash <(curl -s https://codecov.io/bash) 11 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/vardius/gorouter/v4 2 | 3 | go 1.20 4 | 5 | require github.com/valyala/fasthttp v1.50.0 6 | 7 | require ( 8 | github.com/andybalholm/brotli v1.0.6 // indirect 9 | github.com/klauspost/compress v1.17.2 // indirect 10 | github.com/valyala/bytebufferpool v1.0.0 // indirect 11 | ) 12 | -------------------------------------------------------------------------------- /route.go: -------------------------------------------------------------------------------- 1 | package gorouter 2 | 3 | type route struct { 4 | handler interface{} 5 | } 6 | 7 | func newRoute(h interface{}) *route { 8 | if h == nil { 9 | panic("Handler can not be nil.") 10 | } 11 | 12 | return &route{ 13 | handler: h, 14 | } 15 | } 16 | 17 | func (r *route) Handler() interface{} { 18 | // returns already cached computed handler 19 | return r.handler 20 | } 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.dll 4 | *.so 5 | *.dylib 6 | 7 | # Test binary, build with `go test -c` 8 | *.test 9 | 10 | # Output of the go coverage tool, specifically when used with LiteIDE 11 | *.out 12 | 13 | # Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 14 | .glide/ 15 | 16 | .vscode 17 | .idea 18 | 19 | vendor/ 20 | 21 | .history/ 22 | -------------------------------------------------------------------------------- /website/src/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "examples": "docusaurus-examples", 4 | "start": "docusaurus-start", 5 | "build": "docusaurus-build", 6 | "publish-gh-pages": "docusaurus-publish", 7 | "write-translations": "docusaurus-write-translations", 8 | "version": "docusaurus-version", 9 | "rename-version": "docusaurus-rename-version" 10 | }, 11 | "devDependencies": { 12 | "docusaurus": "^1.14.4" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /context/context.go: -------------------------------------------------------------------------------- 1 | package context 2 | 3 | import ( 4 | "context" 5 | ) 6 | 7 | type key struct{} 8 | 9 | // WithParams stores params in context 10 | func WithParams(ctx context.Context, params Params) context.Context { 11 | return context.WithValue(ctx, key{}, params) 12 | } 13 | 14 | // Parameters extracts the request Params ctx, if present. 15 | func Parameters(ctx context.Context) (Params, bool) { 16 | params, ok := ctx.Value(key{}).(Params) 17 | return params, ok 18 | } 19 | -------------------------------------------------------------------------------- /website/docs/installation.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: installation 3 | title: Installation 4 | sidebar_label: Installation 5 | --- 6 | 7 | Package **gorouter** provides request router with middleware. 8 | 9 | ## Installation 10 | 11 | Install the [gorouter](https://github.com/vardius/gorouter) package by calling the following command: 12 | 13 | ```bash 14 | go get -u github.com/vardius/gorouter 15 | ``` 16 | 17 | Import package as follow 18 | 19 | ```go 20 | import "github.com/vardius/gorouter/v4" 21 | ``` 22 | -------------------------------------------------------------------------------- /website/src/static/site.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "name":"vardius/gorouter", 3 | "short_name":"gorouter", 4 | "theme_color":"#ffffff", 5 | "background_color":"#ffffff", 6 | "display":"standalone", 7 | "icons":[ 8 | {"src":"/img/android-chrome-192x192.png","sizes":"192x192","type":"image/png"}, 9 | {"src":"/img/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}, 10 | {"src": "img/apple-touch-icon.png","sizes": "48x48","type": "image/png"}, 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /context/context_test.go: -------------------------------------------------------------------------------- 1 | package context 2 | 3 | import ( 4 | "net/http" 5 | "testing" 6 | ) 7 | 8 | func TestContext(t *testing.T) { 9 | req, err := http.NewRequest("GET", "/x", nil) 10 | if err != nil { 11 | t.Fatal(err) 12 | } 13 | 14 | p := Param{"test", "test"} 15 | params := Params{p} 16 | 17 | req = req.WithContext(WithParams(req.Context(), params)) 18 | cParams, ok := Parameters(req.Context()) 19 | if !ok { 20 | t.Fatal("Error while getting context") 21 | } 22 | 23 | if params.Value("test") != cParams.Value("test") { 24 | t.Error("Request returned invalid context") 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /context/param.go: -------------------------------------------------------------------------------- 1 | package context 2 | 3 | type ( 4 | // Param object to hold request parameter 5 | Param struct { 6 | Key string 7 | Value string 8 | } 9 | // Params slice returned from request context 10 | Params []Param 11 | ) 12 | 13 | // Value of the request parameter by name 14 | func (p Params) Value(key string) string { 15 | for i := range p { 16 | if p[i].Key == key { 17 | return p[i].Value 18 | } 19 | } 20 | return "" 21 | } 22 | 23 | // Set key value pair at index 24 | func (p Params) Set(index uint8, key string, value string) { 25 | p[index].Value = value 26 | p[index].Key = key 27 | } 28 | -------------------------------------------------------------------------------- /website/src/sidebars.json: -------------------------------------------------------------------------------- 1 | { 2 | "docs": { 3 | "Quick Start": ["installation", "basic-example"], 4 | "Router": ["routing", "middleware", "sub-router"], 5 | "Examples": [ 6 | { 7 | "type": "subcategory", 8 | "label": "Authentication", 9 | "ids": [ 10 | "basic-authentication" 11 | ] 12 | }, 13 | { 14 | "type": "subcategory", 15 | "label": "Serving Files", 16 | "ids": ["static-files"] 17 | }, 18 | "https", 19 | "http2", 20 | "multidomain", 21 | "panic", 22 | "apphandler" 23 | ], 24 | "Benchmark": ["benchmark"] 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a golang project 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go 3 | 4 | name: Go 5 | 6 | on: 7 | push: 8 | branches: [ "master" ] 9 | pull_request: 10 | branches: [ "master" ] 11 | 12 | jobs: 13 | 14 | build: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v3 18 | 19 | - name: Set up Go 20 | uses: actions/setup-go@v4 21 | with: 22 | go-version: '1.20' 23 | 24 | - name: Build 25 | run: go build -v ./... 26 | 27 | - name: Test 28 | run: go test -v ./... 29 | -------------------------------------------------------------------------------- /website/src/static/css/custom.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2017-present, Facebook, Inc. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | /* your custom css */ 9 | 10 | @media only screen and (min-device-width: 360px) and (max-device-width: 736px) { 11 | } 12 | 13 | @media only screen and (min-width: 1024px) { 14 | } 15 | 16 | @media only screen and (max-width: 1023px) { 17 | } 18 | 19 | @media only screen and (min-width: 1400px) { 20 | } 21 | 22 | @media only screen and (min-width: 1500px) { 23 | } 24 | 25 | .codeSample { 26 | text-align: initial !important; 27 | } 28 | -------------------------------------------------------------------------------- /path/benchmark_test.go: -------------------------------------------------------------------------------- 1 | package path 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func BenchmarkTrimSlash(b *testing.B) { 8 | path := "/pl/blog/comments/123/new/" 9 | 10 | b.ResetTimer() 11 | b.RunParallel(func(pb *testing.PB) { 12 | for pb.Next() { 13 | p := TrimSlash(path) 14 | 15 | if p != "pl/blog/comments/123/new" { 16 | b.Fatalf("%s", p) 17 | } 18 | } 19 | }) 20 | } 21 | 22 | func BenchmarkGetPart(b *testing.B) { 23 | path := "pl/blog/comments/123/new" 24 | 25 | b.ResetTimer() 26 | b.RunParallel(func(pb *testing.PB) { 27 | for pb.Next() { 28 | p, _ := GetPart(path) 29 | 30 | if p != "pl" { 31 | b.Fatalf("%s", p) 32 | } 33 | } 34 | }) 35 | } 36 | -------------------------------------------------------------------------------- /middleware/middleware_test.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestMiddleware_WithPriority(t *testing.T) { 8 | type test struct { 9 | name string 10 | middleware Middleware 11 | priority uint 12 | } 13 | tests := []test{ 14 | {"Zero", mockMiddleware("Zero"), 0}, 15 | {"Positive", mockMiddleware("Positive"), 1}, 16 | {"Positive Large", mockMiddleware("Positive Large"), 999}, 17 | } 18 | for _, tt := range tests { 19 | t.Run(tt.name, func(t *testing.T) { 20 | m := WithPriority(tt.middleware, tt.priority) 21 | if got := m.Priority(); got != tt.priority { 22 | t.Errorf("Priority() = %v, want %v", got, tt.priority) 23 | } 24 | }) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI= 2 | github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= 3 | github.com/klauspost/compress v1.17.2 h1:RlWWUY/Dr4fL8qk9YG7DTZ7PDgME2V4csBXA8L/ixi4= 4 | github.com/klauspost/compress v1.17.2/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= 5 | github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= 6 | github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= 7 | github.com/valyala/fasthttp v1.50.0 h1:H7fweIlBm0rXLs2q0XbalvJ6r0CUPFWK3/bB4N13e9M= 8 | github.com/valyala/fasthttp v1.50.0/go.mod h1:k2zXd82h/7UZc3VOdJ2WaUqt1uZ/XpXAfE9i+HBC3lA= 9 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [vardius] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with a single custom sponsorship URL 13 | -------------------------------------------------------------------------------- /middleware/collection.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "sort" 5 | ) 6 | 7 | // Collection is a slice of handler wrappers functions 8 | type Collection []Middleware 9 | 10 | // NewCollection provides new middleware 11 | func NewCollection(ms ...Middleware) Collection { 12 | return ms 13 | } 14 | 15 | // Merge merges another middleware 16 | func (c Collection) Merge(m Collection) Collection { 17 | return append(c, m...) 18 | } 19 | 20 | // Compose returns middleware composed to single WrapperFunc 21 | func (c Collection) Compose(h Handler) Handler { 22 | if h == nil { 23 | return nil 24 | } 25 | 26 | for i := range c { 27 | h = c[len(c)-1-i].Wrap(h) 28 | } 29 | 30 | return h 31 | } 32 | 33 | // Sort sorts collection by priority 34 | func (c Collection) Sort() Collection { 35 | sort.SliceStable(c, func(i, j int) bool { 36 | return c[i].Priority() < c[j].Priority() 37 | }) 38 | 39 | return c 40 | } 41 | -------------------------------------------------------------------------------- /.github/workflows/website-publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish website to GitHub Pages 2 | on: 3 | # Trigger the workflow on push or pull request, 4 | # but only for the master branch 5 | push: 6 | branches: 7 | - master 8 | jobs: 9 | publish: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Copy Files 13 | uses: actions/checkout@v2 14 | - name: Setup Git 15 | run: | 16 | git config --global user.name "$GITHUB_ACTOR" 17 | git config --global user.email "$GITHUB_ACTOR@users.noreply.github.com" 18 | - name: install 19 | run: cd website/src && yarn install 20 | - name: build 21 | run: cd website/src && yarn build 22 | - name: Publish website 23 | env: 24 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 25 | run: cd website/src && GIT_USER="$GITHUB_ACTOR:$GITHUB_TOKEN" CURRENT_BRANCH=master yarn run publish-gh-pages 26 | -------------------------------------------------------------------------------- /tree.go: -------------------------------------------------------------------------------- 1 | package gorouter 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/vardius/gorouter/v4/mux" 7 | ) 8 | 9 | func allowed(t mux.Tree, method, path string) (allow string) { 10 | if path == "*" { 11 | // tree roots should be http method nodes only 12 | for _, root := range t { 13 | if root.Name() == http.MethodOptions { 14 | continue 15 | } 16 | if len(allow) == 0 { 17 | allow = root.Name() 18 | } else { 19 | allow += ", " + root.Name() 20 | } 21 | } 22 | } else { 23 | // tree roots should be http method nodes only 24 | for _, root := range t { 25 | if root.Name() == method || root.Name() == http.MethodOptions { 26 | continue 27 | } 28 | 29 | if route, _ := root.Tree().MatchRoute(path); route != nil { 30 | if len(allow) == 0 { 31 | allow = root.Name() 32 | } else { 33 | allow += ", " + root.Name() 34 | } 35 | } 36 | } 37 | } 38 | if len(allow) > 0 { 39 | allow += ", " + http.MethodOptions 40 | } 41 | return allow 42 | } 43 | -------------------------------------------------------------------------------- /context/param_test.go: -------------------------------------------------------------------------------- 1 | package context 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestParamValue(t *testing.T) { 8 | key := "key" 9 | value := "value" 10 | 11 | p := Param{key, value} 12 | params := Params{p} 13 | 14 | if params.Value(key) != value { 15 | t.Error("Invalid parameter value") 16 | } 17 | } 18 | 19 | func TestParamsSet(t *testing.T) { 20 | p := make(Params, 5) 21 | type args struct { 22 | index uint8 23 | key string 24 | value string 25 | } 26 | tests := []struct { 27 | name string 28 | p Params 29 | args args 30 | }{ 31 | {"one", p, args{0, "one", "one"}}, 32 | {"two", p, args{0, "two", "two"}}, 33 | {"three", p, args{0, "three", "three"}}, 34 | {"four", p, args{0, "four", "four"}}, 35 | {"five", p, args{0, "five", "five"}}, 36 | } 37 | for _, tt := range tests { 38 | t.Run(tt.name, func(t *testing.T) { 39 | tt.p.Set(tt.args.index, tt.args.key, tt.args.value) 40 | 41 | if p.Value(tt.args.key) != tt.args.value { 42 | t.Error("Invalid parameter value") 43 | } 44 | }) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017-present Rafał Lorenz 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 | -------------------------------------------------------------------------------- /website/docs/http2.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: http2 3 | title: HTTP2 4 | sidebar_label: HTTP2 5 | --- 6 | 7 | ## [The Go Blog - HTTP/2 Server Push](https://blog.golang.org/h2push) 8 | 9 | ## Pusher 10 | 11 | 12 | ```go 13 | package main 14 | 15 | import ( 16 | "log" 17 | "net/http" 18 | 19 | "golang.org/x/net/http2" 20 | "github.com/vardius/gorouter/v4" 21 | ) 22 | 23 | func Pusher(w http.ResponseWriter, r *http.Request) { 24 | if pusher, ok := w.(http.Pusher); ok { 25 | // Push is supported. 26 | options := &http.PushOptions{ 27 | Header: http.Header{ 28 | "Accept-Encoding": r.Header["Accept-Encoding"], 29 | }, 30 | } 31 | if err := pusher.Push("/script.js", options); err != nil { 32 | log.Printf("Failed to push: %v", err) 33 | } 34 | } 35 | // ... 36 | } 37 | 38 | func main() { 39 | router := gorouter.New() 40 | router.GET("/", http.HandlerFunc(Pusher)) 41 | 42 | http2.ConfigureServer(router, &http2.Server{}) 43 | log.Fatal(router.ListenAndServeTLS("router.crt", "router.key")) 44 | } 45 | ``` 46 | 47 | HTTP/2 implementation for fasthttp is [under construction...](https://github.com/fasthttp/http2) 48 | -------------------------------------------------------------------------------- /middleware/middleware.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | // Handler represents wrapped function 4 | type Handler interface{} 5 | 6 | // Middleware wraps Handler 7 | type Middleware interface { 8 | // Wrap Handler with middleware 9 | Wrap(Handler) Handler 10 | // Priority provides a value for sorting Collection, lower values come first 11 | Priority() uint 12 | } 13 | 14 | // WrapperFunc is an adapter to allow the use of 15 | // handler wrapper functions as middleware functions. 16 | type WrapperFunc func(Handler) Handler 17 | 18 | // Wrap implements Wrapper interface 19 | func (f WrapperFunc) Wrap(h Handler) Handler { 20 | return f(h) 21 | } 22 | 23 | // Priority provides a value for sorting Collection, lower values come first 24 | func (f WrapperFunc) Priority() (priority uint) { 25 | return 26 | } 27 | 28 | // Middleware is a slice of handler wrappers functions 29 | type sortableMiddleware struct { 30 | Middleware 31 | priority uint 32 | } 33 | 34 | // Priority provides a value for sorting Collection, lower values come first 35 | func (m *sortableMiddleware) Priority() uint { 36 | return m.priority 37 | } 38 | 39 | // WithPriority provides new Middleware with priority 40 | func WithPriority(middleware Middleware, priority uint) Middleware { 41 | return &sortableMiddleware{ 42 | Middleware: middleware, 43 | priority: priority, 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /website/docs/sub-router.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: sub-router 3 | title: Mounting Sub-Router 4 | sidebar_label: Mounting Sub-Router 5 | --- 6 | 7 | When having multiple instance of a router you might want to mount one as a sub router of another under some route path, still keeping all middleware. 8 | 9 | It doesn't have to be [gorouter](github.com/vardius/gorouter). You can mount other routers as well as long they implement `http.Handler` interface. 10 | 11 | ## Mount 12 | 13 | 14 | 15 | ```go 16 | package main 17 | 18 | import ( 19 | "log" 20 | "net/http" 21 | 22 | "github.com/vardius/gorouter/v4" 23 | ) 24 | 25 | func main() { 26 | router := gorouter.New() 27 | subrouter := gorouter.New() 28 | 29 | router.Mount("/{param}", subrouter) 30 | 31 | log.Fatal(http.ListenAndServe(":8080", router)) 32 | } 33 | ``` 34 | 35 | ```go 36 | package main 37 | 38 | import ( 39 | "log" 40 | 41 | "github.com/valyala/fasthttp" 42 | "github.com/vardius/gorouter/v4" 43 | ) 44 | 45 | func main() { 46 | router := gorouter.NewFastHTTPRouter() 47 | subrouter := gorouter.NewFastHTTPRouter() 48 | 49 | router.Mount("/{param}", subrouter) 50 | 51 | log.Fatal(fasthttp.ListenAndServe(":8080", router.HandleFastHTTP)) 52 | } 53 | ``` 54 | 55 | 56 | Given example will result in all routes of a `subrouter` being available under paths prefixed with a mount path. -------------------------------------------------------------------------------- /path/path.go: -------------------------------------------------------------------------------- 1 | package path 2 | 3 | import "strings" 4 | 5 | // TrimSlash trims '/' URL path 6 | func TrimSlash(path string) string { 7 | pathLen := len(path) 8 | if pathLen > 0 && path[0] == '/' { 9 | path = path[1:] 10 | pathLen-- 11 | } 12 | 13 | if pathLen > 0 && path[pathLen-1] == '/' { 14 | path = path[:pathLen-1] 15 | } 16 | 17 | return path 18 | } 19 | 20 | // GetPart returns first path part and next path as a second argument 21 | func GetPart(path string) (part string, nextPath string) { 22 | if j := strings.IndexByte(path, '/'); j > 0 { 23 | part = path[:j] 24 | nextPath = path[j+1:] 25 | } else { 26 | part = path 27 | nextPath = "" 28 | } 29 | 30 | return 31 | } 32 | 33 | // GetNameFromPart gets node name from path part 34 | func GetNameFromPart(pathPart string) (name string, exp string) { 35 | name = pathPart 36 | 37 | if pathPart[0] == '{' { 38 | name = pathPart[1 : len(pathPart)-1] 39 | 40 | if parts := strings.Split(name, ":"); len(parts) == 2 { 41 | name = parts[0] 42 | exp = parts[1] 43 | } 44 | 45 | if name == "" { 46 | panic("Empty wildcard name") 47 | } 48 | 49 | return 50 | } 51 | 52 | return 53 | } 54 | 55 | func StripLeadingSlashes(path string, stripSlashes int) string { 56 | for stripSlashes > 0 && len(path) > 0 { 57 | n := strings.IndexByte(path[1:], '/') 58 | if n < 0 { 59 | path = path[:0] 60 | break 61 | } 62 | path = path[n+1:] 63 | stripSlashes-- 64 | } 65 | return path 66 | } 67 | -------------------------------------------------------------------------------- /website/src/pages/en/users.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2017-present, Facebook, Inc. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | const React = require("react"); 9 | 10 | const CompLibrary = require("../../core/CompLibrary.js"); 11 | 12 | const Container = CompLibrary.Container; 13 | 14 | class Users extends React.Component { 15 | render() { 16 | const { config: siteConfig } = this.props; 17 | if ((siteConfig.users || []).length === 0) { 18 | return null; 19 | } 20 | 21 | const editUrl = `${siteConfig.repoUrl}/edit/master/website/siteConfig.js`; 22 | const showcase = siteConfig.users.map(user => ( 23 | 24 | {user.caption} 25 | 26 | )); 27 | 28 | return ( 29 |
30 | 31 |
32 |
33 |

Who is Using This?

34 |

This project is used by many folks

35 |
36 |
{showcase}
37 |

Are you using this project?

38 | 39 | Add your company 40 | 41 |
42 |
43 |
44 | ); 45 | } 46 | } 47 | 48 | module.exports = Users; 49 | -------------------------------------------------------------------------------- /mux/benchmark_test.go: -------------------------------------------------------------------------------- 1 | package mux 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func BenchmarkMux(b *testing.B) { 8 | root := NewNode("GET", 0) 9 | 10 | lang := NewNode("{lang:en|pl}", root.MaxParamsSize()) 11 | blog := NewNode("blog", lang.MaxParamsSize()) 12 | search := NewNode("search", blog.MaxParamsSize()) 13 | 14 | page := NewNode("page", blog.MaxParamsSize()) 15 | pageID := NewNode(`{pageId:[^/]+}`, page.MaxParamsSize()) 16 | 17 | posts := NewNode("posts", blog.MaxParamsSize()) 18 | postsID := NewNode(`{postsId:[^/]+}`, posts.MaxParamsSize()) 19 | 20 | comments := NewNode("comments", blog.MaxParamsSize()) 21 | commentID := NewNode(`{commentId:\d+}`, comments.MaxParamsSize()) 22 | commentNew := NewNode("new", commentID.MaxParamsSize()) 23 | 24 | root.WithChildren(root.Tree().withNode(lang).sort()) 25 | lang.WithChildren(lang.Tree().withNode(blog).sort()) 26 | blog.WithChildren(blog.Tree().withNode(search).sort()) 27 | blog.WithChildren(blog.Tree().withNode(page).sort()) 28 | blog.WithChildren(blog.Tree().withNode(posts).sort()) 29 | blog.WithChildren(blog.Tree().withNode(comments).sort()) 30 | page.WithChildren(page.Tree().withNode(pageID).sort()) 31 | posts.WithChildren(posts.Tree().withNode(postsID).sort()) 32 | comments.WithChildren(comments.Tree().withNode(commentID).sort()) 33 | commentID.WithChildren(commentID.Tree().withNode(commentNew).sort()) 34 | 35 | root.WithChildren(root.Tree().Compile()) 36 | 37 | b.ResetTimer() 38 | b.RunParallel(func(pb *testing.PB) { 39 | for pb.Next() { 40 | root.Tree().MatchRoute("pl/blog/comments/123/new") 41 | } 42 | }) 43 | } 44 | -------------------------------------------------------------------------------- /website/docs/apphandler.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: apphandler 3 | title: App Handler 4 | sidebar_label: App Handler 5 | --- 6 | 7 | ## Use custom handler type in your application 8 | 9 | 10 | 11 | 12 | ```go 13 | package main 14 | 15 | import ( 16 | "errors" 17 | "fmt" 18 | "log" 19 | "net/http" 20 | 21 | "github.com/vardius/gorouter/v4" 22 | ) 23 | 24 | type AppHandlerFunc func(http.ResponseWriter, *http.Request) error 25 | 26 | // ServeHTTP calls f(w, r) and handles error 27 | func (f AppHandlerFunc) ServeHTTP(w http.ResponseWriter, r *http.Request) { 28 | if err := f(w, r); err != nil { 29 | w.WriteHeader(http.StatusInternalServerError) 30 | _, _ = fmt.Fprint(w, err.Error()) 31 | } 32 | } 33 | 34 | func Index(w http.ResponseWriter, r *http.Request) error { 35 | return errors.New("I am app handler which can return error") 36 | } 37 | 38 | func main() { 39 | router := gorouter.New() 40 | router.GET("/", AppHandlerFunc(Index)) 41 | 42 | log.Fatal(http.ListenAndServe(":8080", router)) 43 | } 44 | ``` 45 | 46 | ```go 47 | package main 48 | 49 | import ( 50 | "errors" 51 | "log" 52 | 53 | "github.com/valyala/fasthttp" 54 | "github.com/vardius/gorouter/v4" 55 | ) 56 | 57 | type AppHandlerFunc func(ctx *fasthttp.RequestCtx) error 58 | 59 | // HandleFastHTTP calls f(ctx) and handles error 60 | func (f AppHandlerFunc) HandleFastHTTP(ctx *fasthttp.RequestCtx) { 61 | if err := f(ctx); err != nil { 62 | ctx.SetBody([]byte(err.Error())) 63 | ctx.SetStatusCode(fasthttp.StatusInternalServerError) 64 | } 65 | } 66 | 67 | func index(_ *fasthttp.RequestCtx) error { 68 | return errors.New("I am app handler which can return error") 69 | } 70 | 71 | func main() { 72 | router := gorouter.NewFastHTTPRouter() 73 | router.GET("/", AppHandlerFunc(index)) 74 | 75 | log.Fatal(fasthttp.ListenAndServe(":8080", router.HandleFastHTTP)) 76 | } 77 | ``` 78 | 79 | -------------------------------------------------------------------------------- /website/docs/basic-example.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: basic-example 3 | title: Basic example 4 | sidebar_label: Basic example 5 | --- 6 | 7 | [gorouter](github.com/vardius/gorouter) supports following http implementations: 8 | 9 | - [net/http](https://golang.org/pkg/net/http/) 10 | - [fasthttp](https://github.com/valyala/fasthttp) 11 | 12 | 13 | 14 | ```go 15 | package main 16 | 17 | import ( 18 | "fmt" 19 | "log" 20 | "net/http" 21 | 22 | "github.com/vardius/gorouter/v4" 23 | "github.com/vardius/gorouter/v4/context" 24 | ) 25 | 26 | func index(w http.ResponseWriter, _ *http.Request) { 27 | if _, err := fmt.Fprint(w, "Welcome!\n"); err != nil { 28 | panic(err) 29 | } 30 | } 31 | 32 | func hello(w http.ResponseWriter, r *http.Request) { 33 | params, _ := context.Parameters(r.Context()) 34 | if _, err := fmt.Fprintf(w, "hello, %s!\n", params.Value("name")); err != nil { 35 | panic(err) 36 | } 37 | } 38 | 39 | func main() { 40 | router := gorouter.New() 41 | router.GET("/", http.HandlerFunc(index)) 42 | router.GET("/hello/{name}", http.HandlerFunc(hello)) 43 | 44 | log.Fatal(http.ListenAndServe(":8080", router)) 45 | } 46 | ``` 47 | 48 | ```go 49 | package main 50 | 51 | import ( 52 | "fmt" 53 | "log" 54 | 55 | "github.com/valyala/fasthttp" 56 | "github.com/vardius/gorouter/v4" 57 | "github.com/vardius/gorouter/v4/context" 58 | ) 59 | 60 | func index(_ *fasthttp.RequestCtx) { 61 | fmt.Print("Welcome!\n") 62 | } 63 | 64 | func hello(ctx *fasthttp.RequestCtx) { 65 | params := ctx.UserValue("params").(context.Params) 66 | fmt.Printf("Hello, %s!\n", params.Value("name")) 67 | } 68 | 69 | func main() { 70 | router := gorouter.NewFastHTTPRouter() 71 | router.GET("/", index) 72 | router.GET("/hello/{name}", hello) 73 | 74 | log.Fatal(fasthttp.ListenAndServe(":8080", router.HandleFastHTTP)) 75 | } 76 | ``` 77 | 78 | -------------------------------------------------------------------------------- /mux/example_test.go: -------------------------------------------------------------------------------- 1 | package mux 2 | 3 | import "fmt" 4 | 5 | func Example() { 6 | root := NewNode("GET", 0) 7 | 8 | lang := NewNode("{lang:en|pl}", root.MaxParamsSize()) 9 | blog := NewNode("blog", lang.MaxParamsSize()) 10 | 11 | search := NewNode("search", blog.MaxParamsSize()) 12 | searchAuthor := NewNode("author", search.MaxParamsSize()) 13 | 14 | page := NewNode("page", blog.MaxParamsSize()) 15 | pageID := NewNode(`{pageId:[^/]+}`, page.MaxParamsSize()) 16 | 17 | posts := NewNode("posts", blog.MaxParamsSize()) 18 | postsID := NewNode(`{postsId:[^/]+}`, posts.MaxParamsSize()) 19 | 20 | comments := NewNode("comments", blog.MaxParamsSize()) 21 | commentID := NewNode(`{commentId:\d+}`, comments.MaxParamsSize()) 22 | commentNew := NewNode("new", commentID.MaxParamsSize()) 23 | 24 | root.WithChildren(root.Tree().withNode(lang).sort()) 25 | lang.WithChildren(lang.Tree().withNode(blog).sort()) 26 | blog.WithChildren(blog.Tree().withNode(search).sort()) 27 | blog.WithChildren(blog.Tree().withNode(page).sort()) 28 | blog.WithChildren(blog.Tree().withNode(posts).sort()) 29 | blog.WithChildren(blog.Tree().withNode(comments).sort()) 30 | search.WithChildren(search.Tree().withNode(searchAuthor).sort()) 31 | page.WithChildren(page.Tree().withNode(pageID).sort()) 32 | posts.WithChildren(posts.Tree().withNode(postsID).sort()) 33 | comments.WithChildren(comments.Tree().withNode(commentID).sort()) 34 | commentID.WithChildren(commentID.Tree().withNode(commentNew).sort()) 35 | 36 | fmt.Printf("Raw tree:\n") 37 | fmt.Print(root.Tree().PrettyPrint()) 38 | 39 | root.WithChildren(root.Tree().Compile()) 40 | 41 | fmt.Printf("Compiled tree:\n") 42 | fmt.Print(root.Tree().PrettyPrint()) 43 | 44 | // Output: 45 | // Raw tree: 46 | // {lang:en|pl} 47 | // blog 48 | // page 49 | // {pageId:[^/]+} 50 | // posts 51 | // {postsId:[^/]+} 52 | // search 53 | // author 54 | // comments 55 | // {commentId:\d+} 56 | // new 57 | // Compiled tree: 58 | // {lang:en|pl} 59 | // blog 60 | // page 61 | // {pageId:[^/]+} 62 | // posts 63 | // {postsId:[^/]+} 64 | // search/author 65 | // comments 66 | // {commentId:\d+} 67 | // new 68 | } 69 | -------------------------------------------------------------------------------- /website/docs/static-files.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: static-files 3 | title: Static Files 4 | sidebar_label: Static Files 5 | --- 6 | 7 | ## Static Files 8 | 9 | 10 | 11 | ```go 12 | package main 13 | 14 | import ( 15 | "fmt" 16 | "log" 17 | "net/http" 18 | 19 | "github.com/vardius/gorouter/v4" 20 | "github.com/vardius/gorouter/v4/context" 21 | ) 22 | 23 | func index(w http.ResponseWriter, r *http.Request) { 24 | fmt.Fprint(w, "Welcome!\n") 25 | } 26 | 27 | func hello(w http.ResponseWriter, r *http.Request) { 28 | params, _ := context.Parameters(r.Context()) 29 | fmt.Fprintf(w, "hello, %s!\n", params.Value("name")) 30 | } 31 | 32 | func main() { 33 | router := gorouter.New() 34 | router.GET("/", http.HandlerFunc(index)) 35 | router.GET("/hello/{name}", http.HandlerFunc(hello)) 36 | 37 | // If route not found and the request method equals Get 38 | // router will serve files from directory 39 | // third parameter decide if prefix should be striped 40 | router.ServeFiles(http.Dir("static"), "static", false) 41 | 42 | log.Fatal(http.ListenAndServe(":8080", router)) 43 | } 44 | ``` 45 | 46 | ```go 47 | package main 48 | 49 | import ( 50 | "fmt" 51 | "log" 52 | 53 | "github.com/valyala/fasthttp" 54 | "github.com/vardius/gorouter/v4" 55 | ) 56 | 57 | func index(_ *fasthttp.RequestCtx) { 58 | fmt.Print("Welcome!\n") 59 | } 60 | 61 | func hello(ctx *fasthttp.RequestCtx) { 62 | params := ctx.UserValue("params").(context.Params) 63 | fmt.Printf("Hello, %s!\n", params.Value("name")) 64 | } 65 | 66 | func main() { 67 | router := gorouter.NewFastHTTPRouter() 68 | router.GET("/", index) 69 | router.GET("/hello/{name}", hello) 70 | 71 | // If route not found and the request method equals Get 72 | // router will serve files from directory 73 | // Will serve files from /var/www/static with path /static/* 74 | // because strip 1 slash (/static/favicon.ico -> /favicon.ico) 75 | router.ServeFiles("/var/www/static", 1) 76 | 77 | log.Fatal(fasthttp.ListenAndServe(":8080", router.HandleFastHTTP)) 78 | } 79 | ``` 80 | 81 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package gorouter provide request router with middleware 3 | 4 | # Router 5 | 6 | The router determines how to handle http request. 7 | GoRouter uses a routing tree. Once one branch of the tree matches, only routes inside that branch are considered, 8 | not any routes after that branch. When instantiating router, the root node of tree is created. 9 | 10 | # Route types 11 | 12 | - Static `/hello` (will match requests matching given route) 13 | 14 | - Named `/{name}` (will match requests matching given route scheme) 15 | 16 | - Regexp `/{name:[a-z]+}` (will match requests matching given route scheme and its regexp) 17 | 18 | # Wildcards 19 | 20 | The values of *named parameter* or *regexp parameters* are accessible via *request context* 21 | `params, ok := context.Parameters(req.Context())`. 22 | You can get the value of a parameter either by its index in the slice, or by using the `params.Value(name)` method: 23 | `:name` or `/{name:[a-z]+}` can be retrieved by `params.Value("name")`. 24 | 25 | # Defining Routes 26 | 27 | A full route definition contain up to three parts: 28 | 29 | 1. HTTP method under which route will be available 30 | 31 | 2. The URL path route. This is matched against the URL passed to the router, 32 | and can contain named wildcard placeholders *(e.g. {placeholder})* to match dynamic parts in the URL. 33 | 34 | 3. `http.HandlerFunc`, which tells the router to handle matched requests to the router with handler. 35 | 36 | Take the following example: 37 | 38 | import "github.com/vardius/gorouter/v4/context" 39 | 40 | router.GET("/hello/{name:r([a-z]+)go}", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 41 | params, _ := context.Parameters(r.Context()) 42 | fmt.Fprintf(w, "hello, %s!\n", params.Value("name")) 43 | })) 44 | 45 | In this case, the route is matched by `/hello/rxxxxxgo` for example, 46 | because the `:name` wildcard matches the regular expression wildcard given (`r([a-z]+)go`). However, 47 | `/hello/foo` does not match, because "foo" fails the *name* wildcard. When using wildcards, 48 | these are returned in the map from request context. The part of the path that the wildcard matched (e.g. *rxxxxxgo*) is used as value. 49 | */ 50 | package gorouter 51 | -------------------------------------------------------------------------------- /website/docs/panic.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: panic 3 | title: Panic Recovery 4 | sidebar_label: Panic Recovery 5 | --- 6 | 7 | ## Recover Middleware 8 | 9 | 10 | 11 | ```go 12 | package main 13 | 14 | import ( 15 | "fmt" 16 | "log" 17 | "net/http" 18 | 19 | "github.com/vardius/gorouter/v4" 20 | ) 21 | 22 | func recoverMiddleware(next http.Handler) http.Handler { 23 | fn := func(w http.ResponseWriter, r *http.Request) { 24 | defer func() { 25 | if rcv := recover(); rcv != nil { 26 | http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 27 | } 28 | }() 29 | 30 | next.ServeHTTP(w, r) 31 | } 32 | 33 | return http.HandlerFunc(fn) 34 | } 35 | 36 | func Index(w http.ResponseWriter, r *http.Request) { 37 | fmt.Fprint(w, "Ok!\n") 38 | } 39 | 40 | func WithError(w http.ResponseWriter, r *http.Request) { 41 | panic("panic recover") 42 | } 43 | 44 | func main() { 45 | router := gorouter.New(recoverMiddleware) 46 | router.GET("/", http.HandlerFunc(Index)) 47 | router.GET("/panic", http.HandlerFunc(WithError)) 48 | 49 | log.Fatal(http.ListenAndServe(":8080", router)) 50 | } 51 | ``` 52 | 53 | ```go 54 | package main 55 | 56 | import ( 57 | "fmt" 58 | "log" 59 | 60 | "github.com/valyala/fasthttp" 61 | "github.com/vardius/gorouter/v4" 62 | ) 63 | 64 | func recoverMiddleware(next fasthttp.RequestHandler) fasthttp.RequestHandler { 65 | fn := func(ctx *fasthttp.RequestCtx) { 66 | defer func() { 67 | if rcv := recover(); rcv != nil { 68 | ctx.Error(fasthttp.StatusMessage(fasthttp.StatusInternalServerError), fasthttp.StatusInternalServerError) 69 | } 70 | }() 71 | 72 | next(ctx) 73 | } 74 | 75 | return fn 76 | } 77 | 78 | func index(_ *fasthttp.RequestCtx) { 79 | fmt.Print("Welcome!\n") 80 | } 81 | 82 | func withError(ctx *fasthttp.RequestCtx) { 83 | panic("panic recover") 84 | } 85 | 86 | func main() { 87 | router := gorouter.NewFastHTTPRouter() 88 | router.GET("/", index) 89 | router.GET("/panic", withError) 90 | 91 | log.Fatal(fasthttp.ListenAndServe(":8080", router.HandleFastHTTP)) 92 | } 93 | ``` 94 | 95 | -------------------------------------------------------------------------------- /website/src/pages/en/help.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2017-present, Facebook, Inc. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | const React = require("react"); 9 | 10 | const CompLibrary = require("../../core/CompLibrary.js"); 11 | 12 | const Container = CompLibrary.Container; 13 | const GridBlock = CompLibrary.GridBlock; 14 | 15 | function Help(props) { 16 | const { config: siteConfig, language = "" } = props; 17 | const { baseUrl, docsUrl } = siteConfig; 18 | const docsPart = `${docsUrl ? `${docsUrl}/` : ""}`; 19 | const langPart = `${language ? `${language}/` : ""}`; 20 | const docUrl = doc => `${baseUrl}${docsPart}${langPart}${doc}`; 21 | 22 | const supportLinks = [ 23 | { 24 | content: `Learn more using the [documentation on this site.](${docUrl( 25 | "installation.html" 26 | )})`, 27 | title: "Browse Docs" 28 | }, 29 | { 30 | content: 31 | "[Ask questions](https://github.com/vardius/gorouter/issues) about the documentation and project", 32 | title: "Join the community" 33 | }, 34 | { 35 | content: 36 | "Find out [what's new](https://github.com/vardius/gorouter/watchers) with this project", 37 | title: "Stay up to date" 38 | }, 39 | { 40 | content: 41 | "Consider support this project by [sponsoring](https://github.com/sponsors/vardius)", 42 | title: "Support" 43 | }, 44 | { 45 | content: 46 | "Contribute by [forking](https://github.com/vardius/gorouter/network/members) and improving this project", 47 | title: "Contribute" 48 | } 49 | ]; 50 | 51 | return ( 52 |
53 | 54 |
55 |
56 |

Need help?

57 |
58 |

This project is maintained by a dedicated group of people.

59 | 60 |
61 |
62 |
63 | ); 64 | } 65 | 66 | module.exports = Help; 67 | -------------------------------------------------------------------------------- /route_test.go: -------------------------------------------------------------------------------- 1 | package gorouter 2 | 3 | import ( 4 | "net/http" 5 | "net/http/httptest" 6 | "testing" 7 | 8 | "github.com/vardius/gorouter/v4/context" 9 | "github.com/vardius/gorouter/v4/middleware" 10 | ) 11 | 12 | func TestRouter(t *testing.T) { 13 | handler := http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { 14 | if _, err := w.Write([]byte("4")); err != nil { 15 | t.Fatal(err) 16 | } 17 | }) 18 | 19 | buildMiddlewareFunc := func(body string) middleware.Middleware { 20 | fn := func(h middleware.Handler) middleware.Handler { 21 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 22 | if _, err := w.Write([]byte(body)); err != nil { 23 | t.Fatal(err) 24 | } 25 | h.(http.Handler).ServeHTTP(w, r) 26 | }) 27 | } 28 | 29 | return middleware.WrapperFunc(fn) 30 | } 31 | 32 | m1 := buildMiddlewareFunc("1") 33 | m2 := buildMiddlewareFunc("2") 34 | m3 := buildMiddlewareFunc("3") 35 | 36 | r := newRoute(handler) 37 | m := middleware.NewCollection(m1, m2, m3) 38 | h := m.Compose(r.Handler()) 39 | 40 | w := httptest.NewRecorder() 41 | req, err := http.NewRequest("GET", "/", nil) 42 | if err != nil { 43 | t.Fatal(err) 44 | } 45 | 46 | h.(http.Handler).ServeHTTP(w, req) 47 | 48 | if w.Body.String() != "1234" { 49 | t.Errorf("The router doesn't work correctly. Expected 1234, Actual: %s", w.Body.String()) 50 | } 51 | } 52 | 53 | func TestParams(t *testing.T) { 54 | param := context.Param{Key: "key", Value: "value"} 55 | params := context.Params{param} 56 | 57 | if params.Value("key") != "value" { 58 | t.Error("Invalid params value") 59 | } 60 | } 61 | 62 | func TestInvalidParams(t *testing.T) { 63 | param := context.Param{Key: "key", Value: "value"} 64 | params := context.Params{param} 65 | 66 | if params.Value("invalid_key") != "" { 67 | t.Error("Invalid params value") 68 | } 69 | } 70 | 71 | func TestNilHandler(t *testing.T) { 72 | panicked := false 73 | defer func() { 74 | if rcv := recover(); rcv != nil { 75 | panicked = true 76 | } 77 | }() 78 | 79 | r := newRoute(nil) 80 | if h := r.Handler(); h != nil { 81 | t.Error("Handler should be equal nil") 82 | } 83 | 84 | if panicked != true { 85 | t.Error("Router should panic if handler is nil") 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /mocks_test.go: -------------------------------------------------------------------------------- 1 | package gorouter 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "net/http" 7 | "net/http/httptest" 8 | "testing" 9 | 10 | "github.com/valyala/fasthttp" 11 | ) 12 | 13 | type testLogger struct { 14 | t *testing.T 15 | } 16 | 17 | func (t testLogger) Printf(format string, args ...interface{}) { 18 | t.t.Logf(format, args...) 19 | } 20 | 21 | type mockHandler struct { 22 | served bool 23 | } 24 | 25 | func (mh *mockHandler) ServeHTTP(_ http.ResponseWriter, _ *http.Request) { 26 | mh.served = true 27 | } 28 | 29 | func (mh *mockHandler) HandleFastHTTP(_ *fasthttp.RequestCtx) { 30 | mh.served = true 31 | } 32 | 33 | type mockFileSystem struct { 34 | opened bool 35 | } 36 | 37 | func (mfs *mockFileSystem) Open(_ string) (http.File, error) { 38 | mfs.opened = true 39 | return nil, errors.New("") 40 | } 41 | 42 | func mockMiddleware(body string) MiddlewareFunc { 43 | fn := func(h http.Handler) http.Handler { 44 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 45 | if _, err := w.Write([]byte(body)); err != nil { 46 | panic(err) 47 | } 48 | h.ServeHTTP(w, r) 49 | }) 50 | } 51 | 52 | return fn 53 | } 54 | 55 | func mockServeHTTP(h http.Handler, method, path string) error { 56 | w := httptest.NewRecorder() 57 | req, err := http.NewRequest(method, path, nil) 58 | if err != nil { 59 | return err 60 | } 61 | 62 | h.ServeHTTP(w, req) 63 | 64 | return nil 65 | } 66 | 67 | func mockFastHTTPMiddleware(body string) FastHTTPMiddlewareFunc { 68 | fn := func(h fasthttp.RequestHandler) fasthttp.RequestHandler { 69 | return func(ctx *fasthttp.RequestCtx) { 70 | if _, err := fmt.Fprint(ctx, body); err != nil { 71 | panic(err) 72 | } 73 | 74 | h(ctx) 75 | } 76 | } 77 | 78 | return fn 79 | } 80 | 81 | func mockHandleFastHTTP(h fasthttp.RequestHandler, method, path string) error { 82 | ctx := &fasthttp.RequestCtx{} 83 | ctx.Request.Header.SetMethod(method) 84 | ctx.URI().SetPath(path) 85 | 86 | h(ctx) 87 | 88 | return nil 89 | } 90 | 91 | func checkIfHasRootRoute(t *testing.T, r interface{}, method string) { 92 | switch v := r.(type) { 93 | case *router: 94 | case *fastHTTPRouter: 95 | if rootRoute := v.tree.Find(method); rootRoute == nil { 96 | t.Error("Route not found") 97 | } 98 | default: 99 | t.Error("Unsupported type") 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /website/docs/https.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: https 3 | title: HTTPS 4 | sidebar_label: HTTPS 5 | --- 6 | 7 | ## Autocert 8 | 9 | 10 | ```go 11 | package main 12 | 13 | import ( 14 | "crypto/tls" 15 | "fmt" 16 | "net/http" 17 | "time" 18 | "log" 19 | 20 | "github.com/caarlos0/env" 21 | "github.com/vardius/gorouter/v4" 22 | "golang.org/x/crypto/acme/autocert" 23 | ) 24 | 25 | type config struct { 26 | Host string `env:"HOST" envDefault:"0.0.0.0"` 27 | Port int `env:"PORT" envDefault:"3000"` 28 | CertDirCache string `env:"CERT_DIR_CACHE"` 29 | } 30 | 31 | var localHostAddresses = map[string]bool{ 32 | "0.0.0.0": true, 33 | "localhost": true, 34 | } 35 | 36 | func index(w http.ResponseWriter, r *http.Request) { 37 | fmt.Fprint(w, "Hello!\n") 38 | } 39 | 40 | func main() { 41 | cfg := config{} 42 | env.Parse(&cfg) 43 | 44 | router := gorouter.New() 45 | router.GET("/", http.HandlerFunc(index)) 46 | 47 | srv := setupServer(&cfg, router) 48 | 49 | log.Fatal(srv.ListenAndServeTLS("", "")) 50 | } 51 | 52 | func setupServer(cfg *config, router gorouter.Router) *http.Server { 53 | srv := &http.Server{ 54 | Addr: fmt.Sprintf("%s:%d", cfg.Host, cfg.Port), 55 | ReadTimeout: 5 * time.Second, 56 | WriteTimeout: 10 * time.Second, 57 | IdleTimeout: 120 * time.Second, 58 | Handler: router, 59 | } 60 | 61 | // for localhost do not use autocert 62 | if localHostAddresses[cfg.Host] { 63 | return srv 64 | } 65 | 66 | certManager := autocert.Manager{ 67 | Prompt: autocert.AcceptTOS, 68 | HostPolicy: autocert.HostWhitelist(cfg.Host), 69 | Cache: autocert.DirCache(cfg.CertDirCache), 70 | } 71 | 72 | tlsConfig := &tls.Config{ 73 | MinVersion: tls.VersionTLS12, 74 | CurvePreferences: []tls.CurveID{tls.CurveP521, tls.CurveP384, tls.CurveP256}, 75 | PreferServerCipherSuites: true, 76 | CipherSuites: []uint16{ 77 | tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, 78 | tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, 79 | tls.TLS_RSA_WITH_AES_256_GCM_SHA384, 80 | tls.TLS_RSA_WITH_AES_256_CBC_SHA, 81 | }, 82 | GetCertificate: certManager.GetCertificate, 83 | } 84 | 85 | srv.TLSConfig = tlsConfig 86 | srv.TLSNextProto = make(map[string]func(*http.Server, *tls.Conn, http.Handler), 0) 87 | 88 | return srv 89 | } 90 | ``` 91 | 92 | ```go 93 | Example coming soon... 94 | ``` 95 | -------------------------------------------------------------------------------- /website/docs/routing.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: routing 3 | title: Routing 4 | sidebar_label: Routing 5 | --- 6 | 7 | The router determines how to handle that request. GoRouter uses a routing tree. Once one branch of the tree matches, only routes inside that branch are considered, not any routes after that branch. When instantiating router, the root node of router tree is created. 8 | ### Route types 9 | - Static `/hello` 10 | will match requests matching given route 11 | - Named `/{name}` 12 | will match requests matching given route scheme 13 | - Regexp `/{name:[a-z]+}` 14 | will match requests matching given route scheme and its regexp 15 | #### Wildcards 16 | The values of *named parameter* or *regexp parameters* are accessible via *request context* `params, ok := gorouter.FromContext(req.Context())`. You can get the value of a parameter either by its index in the slice, or by using the `params.Value(name)` method: `{name}` or `/{name:[a-z]+}` can be retrived by `params.Value("name")`. 17 | ### Defining Routes 18 | A full route definition contain up to three parts: 19 | 1. HTTP method under which route will be available 20 | 2. The URL path route. This is matched against the URL passed to the router, and can contain named wildcard placeholders *(e.g. :placeholders)* to match dynamic parts in the URL. 21 | 3. `http.HandleFunc`, which tells the router to handle matched requests to the router with handler. 22 | Take the following example: 23 | 24 | 25 | 26 | ```go 27 | import "github.com/vardius/gorouter/v4/context" 28 | 29 | router.GET("/hello/{name:r([a-z]+)go}", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 30 | params, _ := context.Parameters(r.Context()) 31 | fmt.Fprintf(w, "hello, %s!\n", params.Value("name")) 32 | })) 33 | ``` 34 | 35 | ```go 36 | import "github.com/vardius/gorouter/v4/context" 37 | 38 | router.GET("/hello/{name:r([a-z]+)go}", http.HandlerFunc(func(ctx *fasthttp.RequestCtx) { 39 | params := ctx.UserValue("params").(context.Params) 40 | fmt.Printf("hello, %s!\n", params.Value("name")) 41 | })) 42 | ``` 43 | 44 | 45 | In this case, the route is matched by `/hello/rxxxxxgo` for example, because the `{name}` wildcard matches the regular expression wildcard given (`r([a-z]+)go`). However, `/hello/foo` does not match, because "foo" fails the *name* wildcard. When using wildcards, these are returned in the map from request context. The part of the path that the wildcard matched (e.g. *rxxxxxgo*) is used as value. -------------------------------------------------------------------------------- /mux/tree_test.go: -------------------------------------------------------------------------------- 1 | package mux 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestTreeMatch(t *testing.T) { 8 | root := NewNode("GET", 0) 9 | 10 | lang := NewNode("{lang:en|pl}", root.MaxParamsSize()) 11 | blog := NewNode("blog", lang.MaxParamsSize()) 12 | 13 | search := NewNode("search", blog.MaxParamsSize()) 14 | searchAuthor := NewNode("author", search.MaxParamsSize()) 15 | 16 | page := NewNode("page", blog.MaxParamsSize()) 17 | pageID := NewNode(`{pageId:[^/]+}`, page.MaxParamsSize()) 18 | 19 | posts := NewNode("posts", blog.MaxParamsSize()) 20 | postsID := NewNode(`{postsId:[^/]+}`, posts.MaxParamsSize()) 21 | 22 | comments := NewNode("comments", blog.MaxParamsSize()) 23 | commentID := NewNode(`{commentId:\d+}`, comments.MaxParamsSize()) 24 | commentNew := NewNode("new", commentID.MaxParamsSize()) 25 | 26 | root.WithChildren(root.Tree().withNode(lang).sort()) 27 | lang.WithChildren(lang.Tree().withNode(blog).sort()) 28 | blog.WithChildren(blog.Tree().withNode(search).sort()) 29 | blog.WithChildren(blog.Tree().withNode(page).sort()) 30 | blog.WithChildren(blog.Tree().withNode(posts).sort()) 31 | blog.WithChildren(blog.Tree().withNode(comments).sort()) 32 | search.WithChildren(search.Tree().withNode(searchAuthor).sort()) 33 | page.WithChildren(page.Tree().withNode(pageID).sort()) 34 | posts.WithChildren(posts.Tree().withNode(postsID).sort()) 35 | comments.WithChildren(comments.Tree().withNode(commentID).sort()) 36 | commentID.WithChildren(commentID.Tree().withNode(commentNew).sort()) 37 | 38 | root.WithChildren(root.Tree().Compile()) 39 | 40 | route, _ := root.Tree().MatchRoute("pl/blog/comments/123/new") 41 | 42 | if route != commentNew.Route() { 43 | t.Fatalf("route did not match expected %s (%s)", "pl/blog/comments/123/new", commentNew.Name()) 44 | } 45 | } 46 | 47 | func TestTreeFindNode(t *testing.T) { 48 | blog := NewNode("blog", 0) 49 | 50 | search := NewNode("search", blog.MaxParamsSize()) 51 | page := NewNode("page", blog.MaxParamsSize()) 52 | posts := NewNode("posts", blog.MaxParamsSize()) 53 | 54 | blog.WithChildren(blog.Tree().withNode(search).sort()) 55 | blog.WithChildren(blog.Tree().withNode(page).sort()) 56 | blog.WithChildren(blog.Tree().withNode(posts).sort()) 57 | 58 | blog.WithChildren(blog.Tree().Compile()) 59 | 60 | tests := []struct { 61 | name string 62 | input string 63 | expected Node 64 | }{ 65 | {"Find existing node 1", "search", search}, 66 | {"Find existing node 2", "page", page}, 67 | {"Find existing node 3", "posts", posts}, 68 | {"Find non-existing node", "comments", nil}, 69 | {"Find with empty name", "", nil}, 70 | } 71 | 72 | for _, tt := range tests { 73 | t.Run(tt.name, func(t *testing.T) { 74 | result := blog.Tree().Find(tt.input) 75 | if result != tt.expected { 76 | t.Errorf("expected %v, got %v", tt.expected, result) 77 | } 78 | }) 79 | } 80 | } 81 | 82 | -------------------------------------------------------------------------------- /path/path_test.go: -------------------------------------------------------------------------------- 1 | package path 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestTrim(t *testing.T) { 8 | type args struct { 9 | path string 10 | } 11 | tests := []struct { 12 | name string 13 | args args 14 | want string 15 | }{ 16 | {"empty", args{""}, ""}, 17 | {"/", args{"/"}, ""}, 18 | {"/x", args{"/x"}, "x"}, 19 | {"/x/", args{"/x/"}, "x"}, 20 | {"/x/y", args{"/x/y"}, "x/y"}, 21 | {"/x/y/", args{"/x/y/"}, "x/y"}, 22 | {"/{name}", args{"/{name}"}, "{name}"}, 23 | {"/{name}/", args{"/{name}/"}, "{name}"}, 24 | {"/x/{name}", args{"/x/{name}"}, "x/{name}"}, 25 | {"/x/{name}/", args{"/x/{name}/"}, "x/{name}"}, 26 | {"/x/{name}/y", args{"/x/{name}/y"}, "x/{name}/y"}, 27 | {"/x/{name}/y/", args{"/x/{name}/y/"}, "x/{name}/y"}, 28 | } 29 | for _, tt := range tests { 30 | t.Run(tt.name, func(t *testing.T) { 31 | if got := TrimSlash(tt.args.path); got != tt.want { 32 | t.Errorf("[%s] Trim() = %v, want %v", tt.name, got, tt.want) 33 | } 34 | }) 35 | } 36 | } 37 | 38 | func TestGetPart(t *testing.T) { 39 | type args struct { 40 | path string 41 | } 42 | tests := []struct { 43 | name string 44 | args args 45 | wantPart string 46 | wantNextPath string 47 | }{ 48 | {"empty", args{""}, "", ""}, 49 | {"/", args{"/"}, "/", ""}, 50 | {"x", args{"x"}, "x", ""}, 51 | {"x/y", args{"x/y"}, "x", "y"}, 52 | {"x/y/z", args{"x/y/z"}, "x", "y/z"}, 53 | } 54 | for _, tt := range tests { 55 | t.Run(tt.name, func(t *testing.T) { 56 | gotPart, gotNextPath := GetPart(tt.args.path) 57 | if gotPart != tt.wantPart { 58 | t.Errorf("GetPart() gotPart = %v, want %v", gotPart, tt.wantPart) 59 | } 60 | if gotNextPath != tt.wantNextPath { 61 | t.Errorf("GetPart() gotNextPath = %v, want %v", gotNextPath, tt.wantNextPath) 62 | } 63 | }) 64 | } 65 | } 66 | 67 | func TestGetNameFromPart(t *testing.T) { 68 | type args struct { 69 | pathPart string 70 | } 71 | tests := []struct { 72 | name string 73 | args args 74 | want string 75 | }{ 76 | {"x", args{"x"}, "x"}, 77 | {"{name}", args{"{name}"}, "name"}, 78 | {"{name:(w+)", args{"{name:(w+)"}, "name"}, 79 | } 80 | for _, tt := range tests { 81 | t.Run(tt.name, func(t *testing.T) { 82 | got, _ := GetNameFromPart(tt.args.pathPart) 83 | if got != tt.want { 84 | t.Errorf("GetNameFromPart() got = %v, want %v", got, tt.want) 85 | } 86 | }) 87 | } 88 | } 89 | 90 | func TestStripLeadingSlashes(t *testing.T) { 91 | tests := []struct { 92 | name string 93 | path string 94 | stripSlashes int 95 | want string 96 | }{ 97 | {"slashesCount = 0", "/foo/bar", 0, "/foo/bar"}, 98 | {"slashesCount = 1", "/foo/bar", 1, "/bar"}, 99 | {"slashesCount = 2", "/foo/bar", 2, ""}, 100 | } 101 | for _, tt := range tests { 102 | t.Run(tt.name, func(t *testing.T) { 103 | if got := StripLeadingSlashes(tt.path, tt.stripSlashes); got != tt.want { 104 | t.Errorf("StripLeadingSlashes() = %v, want %v", got, tt.want) 105 | } 106 | }) 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | We are open to, and grateful for, any contributions made by the community. By contributing to **gorouter**, you agree to abide by the [code of conduct](https://github.com/vardius/gorouter/blob/master/CODE_OF_CONDUCT.md). 4 | 5 | ## Reporting Issues and Asking Questions 6 | 7 | Before opening an issue, please search the [issue tracker](https://github.com/vardius/gorouter/issues) to make sure your issue hasn't already been reported. 8 | 9 | ### Bugs and Improvements 10 | 11 | We use the issue tracker to keep track of bugs and improvements to **gorouter** itself, its examples, and the documentation. We encourage you to open issues to discuss improvements, architecture, theory, internal implementation, etc. If a topic has been discussed before, we will ask you to join the previous discussion. 12 | 13 | Any pull requests that involve breaking changes should be made against the `next` branch. 14 | 15 | ### Getting Help 16 | 17 | For support or usage questions like “how do I do X with **gorouter**” and “my code doesn't work”, we encourage you to post an issue. 18 | 19 | Please be considerate when doing this as this is not the primary purpose of the issue tracker. 20 | 21 | ## Development 22 | 23 | Visit the [issue tracker](https://github.com/vardius/gorouter/issues) to find a list of open issues that need attention. 24 | 25 | Fork, then clone the repo: 26 | 27 | ```bash 28 | git clone https://github.com/your-username/gorouter.git 29 | # or 30 | go get github.com/your-username/gorouter 31 | ``` 32 | 33 | [Dep](https://golang.github.io/dep/docs/introduction.html) is used to managing dependencies. Install instructions can be found [here](https://golang.github.io/dep/docs/installation.html). 34 | 35 | ```bash 36 | # In the src dir get the dependencies by running 37 | dep ensure 38 | ``` 39 | 40 | ### Docs 41 | 42 | Improvements to the documentation are always welcome. In the docs we abide by typographic rules, so instead of ' you should use '. Same goes for “ ” and dashes (—) where appropriate. These rules only apply to the text, not to code blocks. 43 | 44 | ### Sending a Pull Request 45 | 46 | For non-trivial changes, please open an issue with a proposal for a new feature or refactoring before starting on the work. We don't want you to waste your efforts on a pull request that we won't want to accept. 47 | 48 | On the other hand, sometimes the best way to start a conversation *is* to send a pull request. Use your best judgement! 49 | 50 | In general, the contribution workflow looks like this: 51 | 52 | * Open a new issue in the [Issue tracker](https://github.com/vardius/gorouter/issues). 53 | * Fork the repo. 54 | * Create a new feature branch based off the `master` branch. 55 | * Make sure all tests pass and there are no linting errors. 56 | * Submit a pull request, referencing any issues it addresses. 57 | 58 | Please try to keep your pull request focused in scope and avoid including unrelated commits. 59 | 60 | After you have submitted your pull request, we'll try to get back to you as soon as possible. We may suggest some changes or improvements. 61 | 62 | Thank you for contributing! 63 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 🍃 gorouter 2 | ================ 3 | [![Build Status](https://travis-ci.com/vardius/gorouter.svg?branch=master)](https://travis-ci.com/vardius/gorouter) 4 | [![Go Report Card](https://goreportcard.com/badge/github.com/vardius/gorouter)](https://goreportcard.com/report/github.com/vardius/gorouter) 5 | [![codecov](https://codecov.io/gh/vardius/gorouter/branch/master/graph/badge.svg)](https://codecov.io/gh/vardius/gorouter) 6 | [![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fvardius%2Fgorouter.svg?type=shield)](https://app.fossa.io/projects/git%2Bgithub.com%2Fvardius%2Fgorouter?ref=badge_shield) 7 | [![](https://godoc.org/github.com/vardius/gorouter?status.svg)](https://pkg.go.dev/github.com/vardius/gorouter) 8 | [![license](https://img.shields.io/github/license/mashape/apistatus.svg)](https://github.com/vardius/gorouter/blob/master/LICENSE.md) 9 | 10 | logo 11 | 12 | Go Server/API micro framework, HTTP request router, multiplexer, mux. 13 | 14 | 📖 ABOUT 15 | ================================================== 16 | Contributors: 17 | 18 | * [Rafał Lorenz](https://rafallorenz.com) 19 | 20 | Want to contribute ? Feel free to send pull requests! 21 | 22 | Have problems, bugs, feature ideas? 23 | We are using the github [issue tracker](https://github.com/vardius/gorouter/issues) to manage them. 24 | 25 | ## 📚 Documentation 26 | 27 | For **documentation** (_including examples_), **visit [rafallorenz.com/gorouter](https://rafallorenz.com/gorouter)** 28 | 29 | For **GoDoc** reference, **visit [pkg.go.dev](https://pkg.go.dev/github.com/vardius/gorouter)** 30 | 31 | ## 🚅 Benchmark 32 | 33 | ```go 34 | ➜ gorouter git:(master) ✗ go test -bench=. -cpu=4 -benchmem 35 | test 36 | goos: darwin 37 | goarch: amd64 38 | pkg: github.com/vardius/gorouter/v4 39 | BenchmarkNetHTTP-4 65005786 17.9 ns/op 0 B/op 0 allocs/op 40 | BenchmarkFastHTTP-4 69810878 16.5 ns/op 0 B/op 0 allocs/op 41 | PASS 42 | ok github.com/vardius/gorouter/v4 3.808s 43 | ``` 44 | 45 | 👉 **[Click here](https://rafallorenz.com/gorouter/docs/benchmark)** to see all benchmark results. 46 | 47 | ## Features 48 | - Routing System 49 | - Middleware System 50 | - Authentication 51 | - Fast HTTP 52 | - Serving Files 53 | - Multidomain 54 | - HTTP2 Support 55 | - Low memory usage 56 | - [Documentation](https://rafallorenz.com/gorouter/) 57 | 58 | 🚏 HOW TO USE 59 | ================================================== 60 | 61 | - [Basic example](https://rafallorenz.com/gorouter/docs/basic-example) 62 | - [net/http](https://rafallorenz.com/gorouter/docs/basic-example#nethttp) 63 | - [valyala/fasthttp](https://rafallorenz.com/gorouter/docs/basic-example#fasthttp) 64 | 65 | ## 🖥️ API example setup 66 | 67 | - **[Go Server/API boilerplate](https://github.com/vardius/go-api-boilerplate)** using best practices DDD CQRS ES. 68 | 69 | 📜 [License](LICENSE.md) 70 | ------- 71 | 72 | This package is released under the MIT license. See the complete license in the package: 73 | 74 | [![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fvardius%2Fgorouter.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2Fvardius%2Fgorouter?ref=badge_large) 75 | -------------------------------------------------------------------------------- /.github/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 vardius@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 | -------------------------------------------------------------------------------- /website/docs/basic-authentication.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: basic-authentication 3 | title: Basic Authentication 4 | sidebar_label: Basic Authentication 5 | --- 6 | 7 | ## Basic Authentication 8 | 9 | 10 | 11 | ```go 12 | package main 13 | 14 | import ( 15 | "fmt" 16 | "log" 17 | "net/http" 18 | "crypto/subtle" 19 | 20 | "github.com/vardius/gorouter/v4" 21 | ) 22 | 23 | var ( 24 | requiredUser = []byte("gordon") 25 | requiredPassword = []byte("secret!") 26 | ) 27 | 28 | func BasicAuth(next http.Handler) http.Handler { 29 | fn := func(w http.ResponseWriter, r *http.Request) { 30 | // Get the Basic Authentication credentials 31 | user, password, hasAuth := r.BasicAuth() 32 | 33 | if !hasAuth || subtle.ConstantTimeCompare(requiredUser, []byte(user)) != 1 || subtle.ConstantTimeCompare(requiredPassword, []byte(password)) != 1 { 34 | w.Header().Set("WWW-Authenticate", "Basic realm=Restricted") 35 | http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized) 36 | return 37 | } 38 | 39 | next.ServeHTTP(w, r) 40 | } 41 | 42 | return http.HandlerFunc(fn) 43 | } 44 | 45 | func index(w http.ResponseWriter, r *http.Request) { 46 | fmt.Fprint(w, "Not protected!\n") 47 | } 48 | 49 | func protected(w http.ResponseWriter, r *http.Request) { 50 | fmt.Fprint(w, "Protected!\n") 51 | } 52 | 53 | func main() { 54 | router := gorouter.New() 55 | router.GET("/", http.HandlerFunc(index)) 56 | router.GET("/protected", http.HandlerFunc(protected)) 57 | 58 | router.USE("GET", "/protected", BasicAuth) 59 | 60 | log.Fatal(http.ListenAndServe(":8080", router)) 61 | } 62 | ``` 63 | 64 | ```go 65 | package main 66 | 67 | import ( 68 | "crypto/subtle" 69 | "bytes" 70 | "encoding/base64" 71 | "fmt" 72 | "log" 73 | 74 | "github.com/valyala/fasthttp" 75 | "github.com/vardius/gorouter/v4" 76 | ) 77 | 78 | var ( 79 | basicAuthPrefix = []byte("Basic ") 80 | requiredUser = []byte("gordon") 81 | requiredPassword = []byte("secret!") 82 | ) 83 | 84 | func BasicAuth(next fasthttp.RequestHandler) fasthttp.RequestHandler { 85 | fn := func(ctx *fasthttp.RequestCtx) { 86 | // Get the Basic Authentication credentials 87 | auth := ctx.Request.Header.Peek("Authorization") 88 | if bytes.HasPrefix(auth, basicAuthPrefix) { 89 | // Check credentials 90 | payload, err := base64.StdEncoding.DecodeString(string(auth[len(basicAuthPrefix):])) 91 | if err == nil { 92 | pair := bytes.SplitN(payload, []byte(":"), 2) 93 | if len(pair) == 2 && subtle.ConstantTimeCompare(requiredUser, pair[0]) == 1 && subtle.ConstantTimeCompare(requiredPassword, pair[1]) == 1 { 94 | // Delegate request to the given handle 95 | next(ctx) 96 | return 97 | } 98 | } 99 | } 100 | 101 | // Request Basic Authentication otherwise 102 | ctx.Response.Header.Set("WWW-Authenticate", "Basic realm=Restricted") 103 | ctx.Error(fasthttp.StatusMessage(fasthttp.StatusUnauthorized), fasthttp.StatusUnauthorized) 104 | } 105 | 106 | return fn 107 | } 108 | 109 | func index(_ *fasthttp.RequestCtx) { 110 | fmt.Print("Not Protected!\n") 111 | } 112 | 113 | func protected(_ *fasthttp.RequestCtx) { 114 | fmt.Print("Protected!\n") 115 | } 116 | 117 | func main() { 118 | router := gorouter.NewFastHTTPRouter() 119 | router.GET("/", index) 120 | router.GET("/protected", protected) 121 | 122 | router.USE("GET", "/protected", BasicAuth) 123 | 124 | log.Fatal(fasthttp.ListenAndServe(":8080", router.HandleFastHTTP)) 125 | } 126 | ``` 127 | 128 | -------------------------------------------------------------------------------- /website/src/core/Footer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2017-present, Facebook, Inc. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | const React = require("react"); 9 | 10 | class Footer extends React.Component { 11 | docUrl(doc, language) { 12 | const baseUrl = this.props.config.baseUrl; 13 | const docsUrl = this.props.config.docsUrl; 14 | const docsPart = `${docsUrl ? `${docsUrl}/` : ""}`; 15 | const langPart = `${language ? `${language}/` : ""}`; 16 | return `${baseUrl}${docsPart}${langPart}${doc}`; 17 | } 18 | 19 | pageUrl(doc, language) { 20 | const baseUrl = this.props.config.baseUrl; 21 | return baseUrl + (language ? `${language}/` : "") + doc; 22 | } 23 | 24 | render() { 25 | return ( 26 | 96 | ); 97 | } 98 | } 99 | 100 | module.exports = Footer; 101 | -------------------------------------------------------------------------------- /website/docs/multidomain.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: multidomain 3 | title: Multidomain 4 | sidebar_label: Multidomain 5 | --- 6 | 7 | ## HostSwitch 8 | 9 | 10 | 11 | ```go 12 | import ( 13 | "fmt" 14 | "log" 15 | "net/http" 16 | 17 | "github.com/vardius/gorouter/v4" 18 | "github.com/vardius/gorouter/v4/context" 19 | ) 20 | 21 | // We need an object that implements the http.Handler interface. 22 | // Therefore we need a type for which we implement the ServeHTTP method. 23 | // We just use a map here, in which we map host names (with port) to http.Handlers 24 | type HostSwitch map[string]http.Handler 25 | 26 | // Implement the ServerHTTP method on our new type 27 | func (hs HostSwitch) ServeHTTP(w http.ResponseWriter, r *http.Request) { 28 | // Check if a http.Handler is registered for the given host. 29 | // If yes, use it to handle the request. 30 | if handler := hs[r.Host]; handler != nil { 31 | handler.ServeHTTP(w, r) 32 | } else { 33 | // Handle host names for wich no handler is registered 34 | http.Error(w, "Forbidden", 403) // Or Redirect? 35 | } 36 | } 37 | 38 | func index(w http.ResponseWriter, _ *http.Request) { 39 | if _, err := fmt.Fprint(w, "Welcome!\n"); err != nil { 40 | panic(err) 41 | } 42 | } 43 | 44 | func hello(w http.ResponseWriter, r *http.Request) { 45 | params, _ := context.Parameters(r.Context()) 46 | if _, err := fmt.Fprintf(w, "hello, %s!\n", params.Value("name")); err != nil { 47 | panic(err) 48 | } 49 | } 50 | 51 | func main() { 52 | // Initialize a router as usual 53 | router := gorouter.New() 54 | router.GET("/", http.HandlerFunc(Index)) 55 | router.GET("/hello/{name}", http.HandlerFunc(Hello)) 56 | 57 | // Make a new HostSwitch and insert the router (our http handler) 58 | // for example.com and port 12345 59 | hs := make(HostSwitch) 60 | hs["example.com:12345"] = router 61 | 62 | // Use the HostSwitch to listen and serve on port 12345 63 | log.Fatal(http.ListenAndServe(":12345", hs)) 64 | } 65 | ``` 66 | 67 | ```go 68 | package main 69 | 70 | import ( 71 | "fmt" 72 | "log" 73 | 74 | "github.com/valyala/fasthttp" 75 | "github.com/vardius/gorouter/v4" 76 | "github.com/vardius/gorouter/v4/context" 77 | ) 78 | 79 | // HostSwitch is the host-handler map 80 | // We need an object that implements the fasthttp.RequestHandler interface. 81 | // We just use a map here, in which we map host names (with port) to fasthttp.RequestHandlers 82 | type HostSwitch map[string]fasthttp.RequestHandler 83 | 84 | // CheckHost Implement a CheckHost method on our new type 85 | func (hs HostSwitch) CheckHost(ctx *fasthttp.RequestCtx) { 86 | // Check if a http.Handler is registered for the given host. 87 | // If yes, use it to handle the request. 88 | if handler := hs[string(ctx.Host())]; handler != nil { 89 | handler(ctx) 90 | } else { 91 | // Handle host names for wich no handler is registered 92 | ctx.Error("Forbidden", 403) // Or Redirect? 93 | } 94 | } 95 | 96 | func index(_ *fasthttp.RequestCtx) { 97 | fmt.Print("Welcome!\n") 98 | } 99 | 100 | func hello(ctx *fasthttp.RequestCtx) { 101 | params := ctx.UserValue("params").(context.Params) 102 | fmt.Printf("Hello, %s!\n", params.Value("name")) 103 | } 104 | 105 | func main() { 106 | router := gorouter.NewFastHTTPRouter() 107 | router.GET("/", index) 108 | router.GET("/hello/{name}", hello) 109 | 110 | // Make a new HostSwitch and insert the router (our http handler) 111 | // for example.com and port 12345 112 | hs := make(HostSwitch) 113 | hs["example.com:12345"] = router.HandleFastHTTP 114 | 115 | // Use the HostSwitch to listen and serve on port 12345 116 | log.Fatal(fasthttp.ListenAndServe(":12345", hs.CheckHost)) 117 | } 118 | ``` 119 | 120 | -------------------------------------------------------------------------------- /middleware/collection_test.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "net/http" 5 | "net/http/httptest" 6 | "testing" 7 | ) 8 | 9 | func mockMiddleware(body string) Middleware { 10 | fn := func(h Handler) Handler { 11 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 12 | if _, err := w.Write([]byte(body)); err != nil { 13 | panic(err) 14 | } 15 | h.(http.Handler).ServeHTTP(w, r) 16 | }) 17 | } 18 | 19 | return WrapperFunc(fn) 20 | } 21 | 22 | func TestCollection(t *testing.T) { 23 | middlewareFactory := func(body string, priority uint) Middleware { 24 | fn := func(h Handler) Handler { 25 | return func() string { return body + h.(func() string)() } 26 | } 27 | 28 | return WithPriority(WrapperFunc(fn), priority) 29 | } 30 | type test struct { 31 | name string 32 | m Collection 33 | output string 34 | sortedOutput string 35 | } 36 | tests := []test{ 37 | {"Empty", NewCollection(), "h", "h"}, 38 | {"Single middleware", NewCollection(middlewareFactory("0", 0)), "0h", "0h"}, 39 | {"Multiple unsorted middleware", NewCollection(middlewareFactory("3", 3), middlewareFactory("1", 1), middlewareFactory("2", 2)), "312h", "123h"}, 40 | {"Multiple unsorted middleware 2", NewCollection(middlewareFactory("2", 2), middlewareFactory("1", 1), middlewareFactory("3", 3)), "213h", "123h"}, 41 | {"Multiple unsorted middleware 3", NewCollection(middlewareFactory("1", 1), middlewareFactory("3", 3), middlewareFactory("2", 2)), "132h", "123h"}, 42 | {"Multiple sorted middleware", NewCollection(middlewareFactory("1", 1), middlewareFactory("2", 2), middlewareFactory("3", 3)), "123h", "123h"}, 43 | } 44 | for _, tt := range tests { 45 | t.Run(tt.name, func(t *testing.T) { 46 | m := NewCollection(tt.m...) 47 | h := m.Compose(func() string { return "h" }) 48 | 49 | result := h.(func() string)() 50 | 51 | if h.(func() string)() != tt.output { 52 | t.Errorf("NewCollection: h() = %v, want %v", result, tt.output) 53 | } 54 | 55 | h = m.Sort().Compose(func() string { return "h" }) 56 | 57 | result = h.(func() string)() 58 | 59 | if h.(func() string)() != tt.sortedOutput { 60 | t.Errorf("NewCollection: h() = %v, want %v", result, tt.sortedOutput) 61 | } 62 | }) 63 | } 64 | } 65 | 66 | func TestWithPriority(t *testing.T) { 67 | m1 := WithPriority(mockMiddleware("1"), 3) 68 | m2 := WithPriority(mockMiddleware("2"), 2) 69 | m3 := WithPriority(mockMiddleware("3"), 1) 70 | fn := http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { 71 | if _, err := w.Write([]byte("4")); err != nil { 72 | t.Fatal(err) 73 | } 74 | }) 75 | 76 | m := NewCollection(m1, m2, m3) 77 | h := m.Sort().Compose(fn).(http.Handler) 78 | 79 | w := httptest.NewRecorder() 80 | r, err := http.NewRequest("GET", "/", nil) 81 | if err != nil { 82 | t.Fatal(err) 83 | } 84 | 85 | h.ServeHTTP(w, r) 86 | 87 | if w.Body.String() != "3214" { 88 | t.Error("The order is incorrect") 89 | } 90 | } 91 | 92 | func TestMerge(t *testing.T) { 93 | m1 := mockMiddleware("1") 94 | m2 := mockMiddleware("2") 95 | m3 := mockMiddleware("3") 96 | fn := http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { 97 | if _, err := w.Write([]byte("4")); err != nil { 98 | t.Fatal(err) 99 | } 100 | }) 101 | 102 | m := NewCollection(m1) 103 | m = m.Merge(NewCollection(m2, m3)) 104 | h := m.Compose(fn).(http.Handler) 105 | 106 | w := httptest.NewRecorder() 107 | r, err := http.NewRequest("GET", "/", nil) 108 | if err != nil { 109 | t.Fatal(err) 110 | } 111 | 112 | h.ServeHTTP(w, r) 113 | 114 | if w.Body.String() != "1234" { 115 | t.Errorf("The order is incorrect expected: 1234 actual: %s", w.Body.String()) 116 | } 117 | } 118 | 119 | func TestCompose(t *testing.T) { 120 | m := NewCollection(mockMiddleware("1")) 121 | h := m.Compose(nil) 122 | 123 | if h != nil { 124 | t.Fail() 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /website/src/siteConfig.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2017-present, Facebook, Inc. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | // See https://docusaurus.io/docs/site-config for all the possible 9 | // site configuration options. 10 | 11 | // List of projects/orgs using your project for the users page. 12 | const users = [ 13 | // { 14 | // caption: "go-api-boilerplate", 15 | // // You will need to prepend the image path with your baseUrl 16 | // // if it is not '/', like: '/test-site/img/image.jpg'. 17 | // image: "/img/undraw_open_source.svg", 18 | // infoLink: "https://github.com/vardius/go-api-boilerplate", 19 | // pinned: true 20 | // } 21 | ]; 22 | 23 | const siteConfig = { 24 | title: "gorouter", // Title for your website. 25 | tagline: 26 | "Go Server/API micro framework, HTTP request router, multiplexer, mux", 27 | url: "https://rafallorenz.com/", // Your website URL 28 | baseUrl: "/gorouter/", // Base URL for your project */ 29 | // For github.io type URLs, you would set the url and baseUrl like: 30 | // url: 'https://facebook.github.io', 31 | // baseUrl: '/test-site/', 32 | 33 | // Used for publishing and more 34 | projectName: "gorouter", 35 | organizationName: "vardius", 36 | // For top-level user or org sites, the organization is still the same. 37 | // e.g., for the https://JoelMarcey.github.io site, it would be set like... 38 | // organizationName: 'JoelMarcey' 39 | 40 | // For no header links in the top nav bar -> headerLinks: [], 41 | headerLinks: [ 42 | { doc: "installation", label: "Docs" }, 43 | { page: "help", label: "Help" }, 44 | // { page: "users", label: "Users" }, 45 | { href: "https://github.com/vardius/gorouter", label: "GitHub" } 46 | // { blog: true, label: 'Blog'}, 47 | // { languages: true }, 48 | // { search: true } 49 | ], 50 | 51 | docsSideNavCollapsible: true, 52 | 53 | // If you have users set above, you add it here: 54 | users, 55 | 56 | /* path to images for header/footer */ 57 | headerIcon: "img/apple-touch-icon.png", 58 | footerIcon: "img/apple-touch-icon.png", 59 | favicon: "favicon/favicon.ico", 60 | 61 | /* Colors for website */ 62 | colors: { 63 | primaryColor: "#3e4f4e", 64 | secondaryColor: "#6e8d7c" 65 | }, 66 | 67 | /* Custom fonts for website */ 68 | /* 69 | fonts: { 70 | myFont: [ 71 | "Times New Roman", 72 | "Serif" 73 | ], 74 | myOtherFont: [ 75 | "-apple-system", 76 | "system-ui" 77 | ] 78 | }, 79 | */ 80 | 81 | // This copyright info is used in /core/Footer.js and blog RSS/Atom feeds. 82 | copyright: `Copyright © ${new Date().getFullYear()} Rafał Lorenz`, 83 | 84 | highlight: { 85 | // Highlight.js theme to use for syntax highlighting in code blocks. 86 | theme: "darcula" 87 | }, 88 | 89 | // Add custom scripts here that would be placed in