Skip to content

Commit

Permalink
Update documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
tigerwill90 committed Apr 4, 2023
1 parent b53d46f commit 7b86e97
Show file tree
Hide file tree
Showing 4 changed files with 234 additions and 271 deletions.
33 changes: 24 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,10 @@
[![Go Report Card](https://goreportcard.com/badge/github.com/tigerwill90/fox)](https://goreportcard.com/report/github.com/tigerwill90/fox)
[![codecov](https://codecov.io/gh/tigerwill90/fox/branch/master/graph/badge.svg?token=09nfd7v0Bl)](https://codecov.io/gh/tigerwill90/fox)
# Fox
Fox is a lightweight high performance HTTP request router for [Go](https://go.dev/). The main difference with other routers is
Fox is a zero allocation, lightweight, high performance HTTP request router for [Go](https://go.dev/). The main difference with other routers is
that it supports **mutation on its routing tree while handling request concurrently**. Internally, Fox use a
[Concurrent Radix Tree](https://github.com/npgall/concurrent-trees/blob/master/documentation/TreeDesign.md) that support **lock-free
reads** while allowing **concurrent writes**.

The router tree is optimized for high-concurrency and high performance reads, and low-concurrency write. Fox has a small memory footprint, and
in many case, it does not do a single heap allocation while handling request.
reads** while allowing **concurrent writes**. The router tree is optimized for high-concurrency and high performance reads, and low-concurrency write.

Fox supports various use cases, but it is especially designed for applications that require changes at runtime to their
routing structure based on user input, configuration changes, or other runtime events.
Expand All @@ -29,8 +26,9 @@ name. Due to Fox design, wildcard route are cheap and scale really well.
**Get the current route:** You can easily retrieve the route of the matched request. This actually makes it easier to integrate
observability middleware like open telemetry (disable by default).

**Only explicit matches:** Inspired from [httprouter](https://github.com/julienschmidt/httprouter), a request can only match
exactly one or no route. As a result there are no unintended matches, and it also encourages good RESTful api design.
**Only explicit matches:** A request can only match exactly one route or no route at all. Fox strikes a balance between routing flexibility,
performance and clarity by enforcing clear priority rules, ensuring that there are no unintended matches and maintaining high performance
even for complex routing pattern.

**Redirect trailing slashes:** Inspired from [httprouter](https://github.com/julienschmidt/httprouter), the router automatically
redirects the client, at no extra cost, if another route match with or without a trailing slash (disable by default).
Expand Down Expand Up @@ -119,7 +117,7 @@ Pattern /users/uuid:{id}
/users/uuid no match
```

### Catch all parameter
#### Catch all parameter
Catch-all parameters can be used to match everything at the end of a route. The placeholder start with `*` followed by a regular
named parameter (e.g. `*{name}`).
```
Expand All @@ -136,6 +134,22 @@ Patter /src/file=*{path}
/src/file=/dir/config.txt match
```

#### Priority rules
Routes are prioritized based on specificity, with static segments taking precedence over wildcard segments.
A wildcard segment (named parameter or catch all) can only overlap with static segments, for the same HTTP method.
For instance, `GET /users/{id}` and `GET /users/{name}/profile` cannot coexist, as the `{id}` and `{name}` segments
are overlapping. These limitations help to minimize the number of branches that need to be evaluated in order to find
the right match, thereby maintaining high-performance routing.

For example, the followings route are allowed:
````
GET /*{filepath}
GET /users/{id}
GET /users/{id}/emails
GET /users/{id}/{actions}
POST /users/{name}/emails
````

#### Warning about params slice
`fox.Params` slice is freed once ServeHTTP returns and may be reused later to save resource. Therefore, if you need to hold `fox.Params`
longer, use the `Clone` methods.
Expand Down Expand Up @@ -518,6 +532,7 @@ The intention behind these choices is that it can serve as a building block for

## Acknowledgements
- [npgall/concurrent-trees](https://github.com/npgall/concurrent-trees): Fox design is largely inspired from Niall Gallagher's Concurrent Trees design.
- [julienschmidt/httprouter](https://github.com/julienschmidt/httprouter): a lot of feature that implements Fox are inspired from Julien Schmidt's router.
- [julienschmidt/httprouter](https://github.com/julienschmidt/httprouter): some feature that implements Fox are inspired from Julien Schmidt's router. Most notably,
this package uses the optimized [httprouter.Cleanpath](https://github.com/julienschmidt/httprouter/blob/master/path.go) function.

## RFC route overlapping enhancement
149 changes: 149 additions & 0 deletions path_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
// Copyright 2013 Julien Schmidt. All rights reserved.
// Based on the path package, Copyright 2009 The Go Authors.
// Use of this source code is governed by a BSD-style license that can be found
// in the LICENSE file.

package fox

import (
"strings"
"testing"
)

type cleanPathTest struct {
path, result string
}

var cleanTests = []cleanPathTest{
// Already clean
{"/", "/"},
{"/abc", "/abc"},
{"/a/b/c", "/a/b/c"},
{"/abc/", "/abc/"},
{"/a/b/c/", "/a/b/c/"},

// missing root
{"", "/"},
{"a/", "/a/"},
{"abc", "/abc"},
{"abc/def", "/abc/def"},
{"a/b/c", "/a/b/c"},

// Remove doubled slash
{"//", "/"},
{"/abc//", "/abc/"},
{"/abc/def//", "/abc/def/"},
{"/a/b/c//", "/a/b/c/"},
{"/abc//def//ghi", "/abc/def/ghi"},
{"//abc", "/abc"},
{"///abc", "/abc"},
{"//abc//", "/abc/"},

// Remove . elements
{".", "/"},
{"./", "/"},
{"/abc/./def", "/abc/def"},
{"/./abc/def", "/abc/def"},
{"/abc/.", "/abc/"},

// Remove .. elements
{"..", "/"},
{"../", "/"},
{"../../", "/"},
{"../..", "/"},
{"../../abc", "/abc"},
{"/abc/def/ghi/../jkl", "/abc/def/jkl"},
{"/abc/def/../ghi/../jkl", "/abc/jkl"},
{"/abc/def/..", "/abc"},
{"/abc/def/../..", "/"},
{"/abc/def/../../..", "/"},
{"/abc/def/../../..", "/"},
{"/abc/def/../../../ghi/jkl/../../../mno", "/mno"},

// Combinations
{"abc/./../def", "/def"},
{"abc//./../def", "/def"},
{"abc/../../././../def", "/def"},
}

func TestPathClean(t *testing.T) {
for _, test := range cleanTests {
if s := CleanPath(test.path); s != test.result {
t.Errorf("CleanPath(%q) = %q, want %q", test.path, s, test.result)
}
if s := CleanPath(test.result); s != test.result {
t.Errorf("CleanPath(%q) = %q, want %q", test.result, s, test.result)
}
}
}

func TestPathCleanMallocs(t *testing.T) {
if testing.Short() {
t.Skip("skipping malloc count in short mode")
}

for _, test := range cleanTests {
test := test
allocs := testing.AllocsPerRun(100, func() { CleanPath(test.result) })
if allocs > 0 {
t.Errorf("CleanPath(%q): %v allocs, want zero", test.result, allocs)
}
}
}

func BenchmarkPathClean(b *testing.B) {
b.ReportAllocs()

for i := 0; i < b.N; i++ {
for _, test := range cleanTests {
CleanPath(test.path)
}
}
}

func genLongPaths() (testPaths []cleanPathTest) {
for i := 1; i <= 1234; i++ {
ss := strings.Repeat("a", i)

correctPath := "/" + ss
testPaths = append(testPaths, cleanPathTest{
path: correctPath,
result: correctPath,
}, cleanPathTest{
path: ss,
result: correctPath,
}, cleanPathTest{
path: "//" + ss,
result: correctPath,
}, cleanPathTest{
path: "/" + ss + "/b/..",
result: correctPath,
})
}
return testPaths
}

func TestPathCleanLong(t *testing.T) {
cleanTests := genLongPaths()

for _, test := range cleanTests {
if s := CleanPath(test.path); s != test.result {
t.Errorf("CleanPath(%q) = %q, want %q", test.path, s, test.result)
}
if s := CleanPath(test.result); s != test.result {
t.Errorf("CleanPath(%q) = %q, want %q", test.result, s, test.result)
}
}
}

func BenchmarkPathCleanLong(b *testing.B) {
cleanTests := genLongPaths()
b.ResetTimer()
b.ReportAllocs()

for i := 0; i < b.N; i++ {
for _, test := range cleanTests {
CleanPath(test.path)
}
}
}
Loading

0 comments on commit 7b86e97

Please sign in to comment.