Fox
A high-performance HTTP router for building reverse proxies and API gateways, with first-class support for mutating routes at runtime
About Fox
This large README is shown as a plain-text preview. Read the full README at the source
# Fox
<img align="right" width="159px" src="https://raw.githubusercontent.com/fox-toolkit/fox/refs/heads/static/fox_logo.png">
[](https://pkg.go.dev/github.com/fox-toolkit/fox)
[](https://github.com/fox-toolkit/fox/actions?query=workflow%3Atests)
[](https://codecov.io/gh/fox-toolkit/fox)


> [!NOTE]
> This repository has been transferred from `github.com/tigerwill90/fox` to `github.com/fox-toolkit/fox`.
> Existing users should update their imports and `go.mod` accordingly.
Fox is a lightweight and high performance HTTP request router for [Go](https://go.dev/), designed for building reverse proxies,
API gateways, or other applications that require managing routes at runtime based on configuration changes or external events.
It is also well-suited for general use cases such as microservices and REST APIs, though it focuses on routing and does not include
convenience helpers found in full-featured frameworks, such as automatic binding, content negotiation, file uploads, cookies, etc.
Fox supports **mutation on its routing tree while handling requests concurrently**. Internally, it uses a Radix Tree that supports
**lock-free** reads while allowing a concurrent writer, and is optimized for high-concurrency reads and low-concurrency writes.
The router supports complex routing patterns, enforces clear priority rules, and performs strict validation to prevent misconfigurations.
## Disclaimer
The current API is not yet stabilized. Breaking changes may occur before `v1.0.0` and will be noted on the release note.
## Features
**Runtime updates:** Register, update and delete route handler safely at any time without impact on performance.
**Flexible routing:** 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 patterns. Supported features include named parameters,
suffix and infix catch-all, regexp constraints, hostname matching, method and method-less routes, and route matchers.
**Trailing slash handling:** Automatically handle trailing slash inconsistencies by either ignoring them, redirecting to
the canonical path, or enforcing strict matching based on your needs.
**Path normalization:** Automatically handle requests with extra slashes or dot segments by either
serving the normalized path directly or redirecting to the canonical form.
**Automatic OPTIONS replies:** Fox has built-in native support for [OPTIONS requests](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/OPTIONS).
**Client IP Derivation:** Accurately determine the "real" client IP address using best practices tailored to your network topology.
**Growing middleware ecosystem:** Fox's middleware ecosystem is still limited, but most standard `http.Handler` middleware should be compatible.
---
* [Getting started](#getting-started)
* [Install](#install)
* [Basic example](#basic-example)
* [Named parameters](#named-parameters)
* [Named wildcards](#named-wildcards-catch-all)
* [Route matchers](#route-matchers)
* [Method-less routes](#method-less-routes)
* [Hostname validation & restrictions](#hostname-validation--restrictions)
* [Path encoding](#path-encoding)
* [Priority rules](#priority-rules)
* [Hostname routing](#hostname-routing)
* [Warning about context](#warning-about-context)
* [Concurrency](#concurrency)
* [Managing routes at runtime](#managing-routes-at-runtime)
* [ACID Transaction](#acid-transaction)
* [Managed read-write transaction](#managed-read-write-transaction)
* [Unmanaged read-write transaction](#unmanaged-read-write-transaction)
* [Managed read-only transaction](#managed-read-only-transaction)
* [Middleware](#middleware)
* [Official middlewares](#official-middlewares)
* [Working with http.Handler](#working-with-httphandler)
* [Handling OPTIONS Requests and CORS Automatically](#handling-options-requests-and-cors-automatically)
* [Resolving Client IP](#resolving-client-ip)
* [Benchmark](#benchmark)
* [Road to v1](#road-to-v1)
* [Contributions](#contributions)
* [License](#license)
---
## Getting started
#### Install
With a [correctly configured](https://go.dev/doc/install#testing) Go toolchain:
```shell
go get -u github.com/fox-toolkit/fox
```
This library requires [Go 1.26.0](https://tip.golang.org/doc/go1.26) or above. Per [the Go release policy](https://go.dev/doc/devel/release#policy),
it only supports the two most recent major releases of Go, i.e. 1.26 and 1.25.
#### Basic example
````go
package main
import (
"log"
"net/http"
"net/http/httputil"
"net/url"
"github.com/fox-toolkit/fox"
)
func proxy(backend string) fox.HandlerFunc {
target, err := url.Parse(backend)
if err != nil {
panic(err)
}
rp := &httputil.ReverseProxy{
Rewrite: func(r *httputil.ProxyRequest) {
r.SetURL(target)
r.SetXForwarded()
r.Out.Host = r.In.Host
},
}
return func(c *fox.Context) {
rp.ServeHTTP(c.Writer(), c.Request())
}
}
func main() {
f := fox.MustRouter(fox.WithAllowRegexpParam(true))
f.MustAdd(fox.MethodAny, "api.example.com/orders/{id:[0-9]+}", proxy("http://orders.internal"))
f.MustAdd(fox.MethodAny, "api.example.com/orders/{ref}", proxy("http://legacy.internal"))
f.MustAdd(fox.MethodAny, "api.example.com/files/+{path}/preview", proxy("http://imaging.internal"))
f.MustAdd(fox.MethodAny, "api.example.com/files/+{path}", proxy("http://storage.internal"))
f.MustAdd([]string{http.MethodGet, http.MethodHead}, "www.example.com/*{path}", proxy("http://frontend.internal"))
f.MustAdd(fox.MethodGet, "/health", func(c *fox.Context) {
_ = c.String(http.StatusOK, "ok")
})
log.Fatalln(http.ListenAndServe(":8080", f))
}
````
#### Named parameters
Routes can include named parameters using curly braces `{name}` to match exactly one non-empty route segment. The matching
segments are recorded as [Param](https://pkg.go.dev/github.com/fox-toolkit/fox#Param) and accessible via the
[Context](https://pkg.go.dev/github.com/fox-toolkit/fox#Context). Named parameters are supported anywhere in
the route, but only one parameter is allowed per segment (or hostname label) and must appear at the end of the segment.
````
Pattern /avengers/{name}
/avengers/ironman matches
/avengers/thor matches
/avengers/hulk/angry no matches
/avengers/ no matches
Pattern /users/uuid:{id}
/users/uuid:123 matches
/users/uuid: no matches
Pattern /users/uuid:{id}/config
/users/uuid:123/config matches
/users/uuid:/config no matches
Pattern {sub}.example.com/avengers
first.example.com/avengers matches
example.com/avengers no matches
````
Named parameters can include regular expression using the syntax `{name:regexp}`. Regular expressions cannot
contain capturing groups, but can use non-capturing groups `(?:pattern)` instead. Regexp support is opt-in via
`fox.WithAllowRegexpParam(true)` option.
````
Pattern /products/{name:[A-Za-z]+}
/products/laptop matches
/products/123 no matches
````
#### Named Wildcards (Catch-all)
Named wildcard start with a plus sign `+` followed by a name `{param}` and match any sequence of characters
including slashes, but cannot match an empty string. The matching segments are also accessible via
[Context](https://pkg.go.dev/github.com/fox-toolkit/fox#Context). Catch-all parameters are supported anywhere in the route,
but only one parameter is allowed per segment (or hostname label) and must appear at the end of the segment.
Consecutive catch-all parameter are not allowed.
````
Pattern /src/+{filepath}
/src/conf.txt matches
/src/dir/config.txt matches
/src/ no matches
Pattern /src/file=+{path}
/src/file=config.txt matches
/src/file=/dir/config.txt matches
/src/file= no matches
Pattern: /assets/+{path}/thumbnail
/assets/images/thumbnail matches
/assets/photos/2021/thumbnail matches
/assets//thumbnail no matches
Pattern +{sub}.example.com/avengers
first.example.com/avengers matches
first.second.example.com/avengers matches
example.com/avengers no matches
````
Optional named wildcard start with an asterisk `*` followed by a name `{param}` and match any sequence of characters
**including empty** strings. Unlike `+{param}`, optional wildcards can only be used as a suffix.
````
Pattern /src/*{filepath}
/src/conf.txt matches
/src/dir/config.txt matches
/src/ matches
Pattern /src/file=*{path}
/src/file=config.txt matches
/src/file=/dir/config.txt matches
/src/file= matches
````
Named wildcards can include a regular expression constraint using the syntax `+{name:regexp}`. Regular expressions cannot
contain capturing groups, but can use non-capturing groups `(?:pattern)` instead. Optional wildcards (`*{param}`) do not
support regular expressions. Regexp support is opt-in via `fox.WithAllowRegexpParam(true)` option.
````
Pattern /src/+{filepath:[A-Za-z/]+\.json}
/src/dir/config.json matches
/src/dir/config.txt no matches
````
#### Route matchers
Route matchers enable routing decisions based on request properties beyond methods, hostname and path. Multiple routes can share
the same pattern and methods and be differentiated by query parameters, headers, client IP, or custom criteria.
````go
f.MustAdd(fox.MethodAny, "api.example.com/checkout/+{path}", proxy("http://checkout-v2.internal"), fox.WithHeaderMatcher("X-Canary", "on"))
f.MustAdd(fox.MethodAny, "api.example.com/checkout/+{path}", proxy("http://checkout-v2.internal"), fox.WithClientIPMatcher("10.0.0.0/8"))
f.MustAdd(fox.MethodAny, "api.example.com/checkout/+{path}", proxy("http://checkout.internal"))
````
Built-in matchers include `fox.WithQueryMatcher`, `fox.WithQueryRegexpMatcher`, `fox.WithHeaderMatcher`, `fox.WithHeaderRegexpMatcher`,
`fox.WithSchemeMatcher` and `fox.WithClientIPMatcher`. Multiple matchers on a route use AND logic. Routes without matchers serve as fallbacks.
For custom matching logic, implement the `fox.Matcher` interface and use `fox.WithMatcher`. See [Priority rules](#priority-rules) for matcher
evaluation order.
#### Method-less routes
Routes can be registered without specifying an HTTP method to match any method. The constant `fox.MethodAny` is
a convenience placeholder equivalent to an empty method set (nil or empty slice).
````go
// Handle any method on /health
f.MustAdd(fox.MethodAny, "/health", HealthHandler)
// Forward all requests to a backend service
f.MustAdd(fox.MethodAny, "/api/*{any}", ProxyHandler)
````
Routes registered with a specific HTTP method always take precedence over method-less routes. This allows defining method-specific
behavior while falling back to a generic handler for other methods.
````go
// Specific handler for GET requests
f.MustAdd(fox.MethodGet, "/resource", GetHandler)
// All other methods handled here
f.MustAdd(fox.MethodAny, "/resource", FallbackHandler)
````
#### Hostname validation & restrictions
Hostnames are validated to conform to the [LDH (letters, digits, hyphens) rule](https://datatracker.ietf.org/doc/html/rfc3696.html#section-2)
(lowercase only) and SRV-like "underscore labels". Wildcard segments within hostnames, such as `{sub}.example.com/`, are exempt from LDH validation
since they act as placeholders rather than actual domain labels. As such, they do not count toward the hard limit of 63 characters per label,
nor the 253-character limit for the full hostname. Internationalized domain names (IDNs) should be specified using an ASCII
(Punycode) representation.
#### Path encoding
Fox matches requests against a canonical routing path. Percent-encoded
[unreserved characters](https://datatracker.ietf.org/doc/html/rfc3986#section-2.3) (`A-Z a-z 0-9 - . _ ~`) are
decoded, while every other escape sequence is normalized to uppercase hex (`%2f` becomes `%2F`) and stays
distinct from its decoded form, so `/foo%2Fbar` and `/foo/bar` are different routing paths. Characters that must
be escaped in a path, like `é` or `{`, are percent-encoded.
#### Priority rules
The router is designed to balance routing flexibility, performance, and predictability. Internally, it uses a radix tree to
store routes efficiently. When a request arrives, Fox evaluates routes in the following order:
1. **Hostname matching**
- Routes with hostnames are evaluated before path-only routes
2. **Pattern matching** (longest match, most specific first)
- Static segments
- Named parameters with regex constraints
- Named parameters without constraints
- Catch-all parameters with regex constraints
- Catch-all parameters without constraints
- Infix catch-all are evaluated before suffix catch-all (e.g., `/bucket/+{path}/meta` before `/bucket/+{path}`)
- At the same level, multiple regex-constrained parameters are evaluated in registration order
3. **Method matching**
- Routes with specific methods are evaluated before method-less routes
4. **Matcher evaluation** (for routes sharing the same pattern and overlapping methods)
- Routes with matchers are evaluated before routes without
- Among routes with matchers, higher priority is evaluated first (configurable via `fox.WithMatchersPriority`, or defaults to the number of matchers)
- Routes with equal matchers priority may be evaluated in any order
If a match candidate fails to complete the full route, including matchers, Fox returns to the last decision point and tries the next available
alternative following the same priority order.
##### Hostname routing
The router can transition instantly and transparently from path-only mode to hostname-prioritized mode without any
additional configuration or action. If any route with a hostname is registered, the router automatically switches to
prioritize hostname matching. Conversely, if no hostname-specific routes are registered, the router reverts to
path-priority mode.
- If the router has no routes registered with hostnames, the router will perform a path-based lookup only.
- If the router includes at least one route with a hostname, the router will prioritize lookup based
on the request host and path. If no match is found, the router will then fall back to a path-only lookup.
Hostname matching is **case-insensitive**, so requests to `Example.COM`, `example.com`, and `EXAMPLE.COM` will all match a route registered for `example.com`.
#### Warning about context
The `fox.Context` instance is freed once the request handler function returns to optimize resource allocation.
If you need to retain `fox.Context` beyond the scope of the handler, use the `fox.Context.Clone` methods.
````go
func Hello(c *fox.Context) {
cc := c.Clone()
go func() {
time.Sleep(2 * time.Second)
log.Println(cc.Param("name")) // Safe
}()
_ = c.String(http.StatusOK, "Hello %s\n", c.Param("name"))
}
````
## Concurrency
Fox implements an **immutable radix tree** with copy-on-write semantics, which support lock-free reads while allowing
a single concurrent writer. Mutations follow a three-phase pattern: first, descend recursively through the tree to
locate the insertion point; then as the call stack unwinds, copy each node along the modified path back to the root and finally,
update the root in a **single atomic operation**. The result is a shallow copy of the tree, where unmodified branches
are shared between the old and new tree. Multiple mutations can be applied in a single transaction, where each cloned node is cached
to avoid copying it more than once.
### Other key points
- Routing requests is lock-free (reading thread never block, even while writes are ongoing)
- The router always sees a consistent version of the tree while routing request
- Reading threads do not block writing threads (adding, updating or removing a handler can be done concurrently)
- Writing threads block each other but never block reading threads
As such threads that route requests should never encounter latency due to ongoing writes or other concurrent readers.
### Managing routes at runtime
#### Routing mutation
In this example, the `/upstreams` endpoints allow to dynamically add, update and delete proxy upstreams, reusing the
`proxy` helper from the [basic example](#basic-example). Thanks to Fox's design, those actions are perfectly safe and
may be executed concurrently.
````go
package main
import (
"encoding/json"
"log"
"net/http"
"github.com/fox-toolkit/fox"
)
type Upstream struct {
Pattern string `json:"pattern"`
Backend string `json:"backend"`
}
func UpsertUpstream(c *fox.Context) {
var up Upstream
if err := json.NewDecoder(c.Request().Body).Decode(&up); err != nil {
http.Error(c.Writer(), err.Error(), http.StatusBadRequest)
return
}
if err := c.Router().Updates(func(txn *fox.Txn) error {
if txn.Has(fox.MethodAny, up.Pattern) {
_, err := txn.Update(fox.MethodAny, up.Pattern, proxy(up.Backend))
return err
}
_, err := txn.Add(fox.MethodAny, up.Pattern, proxy(up.Backend))
return err
}); err != nil {
http.Error(c.Writer(), err.Error(), http.StatusUnprocessableEntity)
return
}
_ = c.String(http.StatusOK, "upstream saved\n")
}
func DeleteUpstream(c *fox.Context) {
var up Upstream
if err := json.NewDecoder(c.Request().Body).Decode(&up); err != nil {
http.Error(c.Writer(), err.Error(), http.StatusBadRequest)
return
}
if _, err := c.Router().Delete(fox.MethodAny, up.Pattern); err != nil {
http.Error(c.Writer(), err.Error(), http.StatusUnprocessableEntity)
return
}
_ = c.String(http.StatusOK, "upstream deleted\n")
}
func main() {
f := fox.MustRouter()
f.MustAdd(fox.MethodPut, "/upstreams", UpsertUpstream)
f.MustAdd(fox.MethodDelete, "/upstreams", DeleteUpstream)
log.Fatalln(http.ListenAndServe(":8080", f))
}
````
#### ACID Transaction
Fox supports read-write and read-only transactions (with Atomicity, Consistency, and Isolation; Durability is not supported
as transactions are in memory). Thread that route requests always see a consistent version of the routing tree and are
fully isolated from an ongoing transaction until committed. Read-only transactions capture a point-in-time snapshot of
the tree, ensuring they do not observe any ongoing or committed changes made after their creation.
#### Managed read-write transaction
````go
// Updates executes a function within the context of a read-write managed transaction. If no error is returned
// from the function then the transaction is committed. If an error is returned then the entire transaction is
// aborted.
if err := f.Updates(func(txn *fox.Txn) error {
if _, err := txn.Update(fox.MethodAny, "api.example.com/checkout/+{path}", proxy("http://checkout-v2.internal")); err != nil {
return err
}
// Iter returns a collection of range iterators for traversing registered routes.
it := txn.Iter()
// When Iter() is called on a write transaction, it creates a point-in-time snapshot of the transaction state.
// It means that writing on the current transaction while iterating is allowed, but the mutation will not be
// observed in the result returned by PatternPrefix (or any other iterator).
for route := range it.PatternPrefix("beta.example.com/checkout") {Frequently Asked Questions
What is Fox?
Fox is a Web Frameworks library for the Go programming language. A high-performance HTTP router for building reverse proxies and API gateways, with first-class support for mutating routes at runtime
How do I install Fox?
Install Fox with the Go module system using `go get fox-toolkit/fox`. Check the repository for the current installation instructions.
What category does Fox belong to?
Fox is listed under Web Frameworks, specifically Routers.