{"id":49319303,"url":"https://github.com/fox-toolkit/fox","last_synced_at":"2026-04-26T17:00:38.129Z","repository":{"id":63820158,"uuid":"514251462","full_name":"fox-toolkit/fox","owner":"fox-toolkit","description":"A high-performance Go HTTP router for building reverse proxies and API gateways, including use cases like ingress controllers","archived":false,"fork":false,"pushed_at":"2026-04-21T14:58:32.000Z","size":8647,"stargazers_count":11,"open_issues_count":0,"forks_count":0,"subscribers_count":4,"default_branch":"master","last_synced_at":"2026-04-21T16:42:52.460Z","etag":null,"topics":["fox","go","golang","http-router","http-server","middleware","mux","performance","router","server"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/fox-toolkit.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2022-07-15T11:52:31.000Z","updated_at":"2026-04-21T14:58:34.000Z","dependencies_parsed_at":"2024-02-14T09:38:07.250Z","dependency_job_id":"0872d593-dcc9-45f1-9e34-73b030fecd5e","html_url":"https://github.com/fox-toolkit/fox","commit_stats":{"total_commits":35,"total_committers":3,"mean_commits":"11.666666666666666","dds":"0.34285714285714286","last_synced_commit":"eea87e74f78fccae69743631c27e41d54c4b6d60"},"previous_names":["fox-toolkit/fox"],"tags_count":52,"template":false,"template_full_name":null,"purl":"pkg:github/fox-toolkit/fox","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fox-toolkit%2Ffox","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fox-toolkit%2Ffox/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fox-toolkit%2Ffox/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fox-toolkit%2Ffox/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/fox-toolkit","download_url":"https://codeload.github.com/fox-toolkit/fox/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fox-toolkit%2Ffox/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32305039,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-26T09:34:17.070Z","status":"ssl_error","status_checked_at":"2026-04-26T09:34:00.993Z","response_time":129,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["fox","go","golang","http-router","http-server","middleware","mux","performance","router","server"],"created_at":"2026-04-26T17:00:31.908Z","updated_at":"2026-04-26T17:00:38.114Z","avatar_url":"https://github.com/fox-toolkit.png","language":"Go","funding_links":[],"categories":["Web Frameworks"],"sub_categories":["Routers"],"readme":"# Fox\n\n\u003cimg align=\"right\" width=\"159px\" src=\"https://raw.githubusercontent.com/fox-toolkit/fox/refs/heads/static/fox_logo.png\"\u003e\n\n[![Go Reference](https://pkg.go.dev/badge/github.com/fox-toolkit/fox.svg)](https://pkg.go.dev/github.com/fox-toolkit/fox)\n[![tests](https://github.com/fox-toolkit/fox/actions/workflows/tests.yaml/badge.svg)](https://github.com/fox-toolkit/fox/actions?query=workflow%3Atests)\n[![Go Report Card](https://goreportcard.com/badge/github.com/fox-toolkit/fox)](https://goreportcard.com/report/github.com/fox-toolkit/fox)\n[![codecov](https://codecov.io/gh/fox-toolkit/fox/graph/badge.svg?token=09nfd7v0Bl)](https://codecov.io/gh/fox-toolkit/fox)\n![GitHub release (latest SemVer)](https://img.shields.io/github/v/release/fox-toolkit/fox)\n![GitHub go.mod Go version](https://img.shields.io/github/go-mod/go-version/fox-toolkit/fox)\n\n\u003e [!NOTE]\n\u003e This repository has been transferred from `github.com/tigerwill90/fox` to `github.com/fox-toolkit/fox`.\n\u003e Existing users should update their imports and `go.mod` accordingly.\n\nFox is a lightweight and high performance HTTP request router for [Go](https://go.dev/), designed for building reverse proxies,\nAPI gateways, or other applications that require managing routes at runtime based on configuration changes or external events.\nIt is also well-suited for general use cases such as microservices and REST APIs, though it focuses on routing and does not include \nconvenience helpers found in full-featured frameworks, such as automatic binding, content negotiation, file uploads, cookies, etc.\n\nFox supports **mutation on its routing tree while handling requests concurrently**. Internally, it uses a Radix Tree that supports\n**lock-free** reads while allowing a concurrent writer, and is optimized for high-concurrency reads and low-concurrency writes.\nThe router supports complex routing patterns, enforces clear priority rules, and performs strict validation to prevent misconfigurations.\n\n## Disclaimer\nThe current API is not yet stabilized. Breaking changes may occur before `v1.0.0` and will be noted on the release note.\n\n## Features\n**Runtime updates:** Register, update and delete route handler safely at any time without impact on performance.\n\n**Flexible routing:** Fox strikes a balance between routing flexibility, performance and clarity by enforcing clear priority rules, ensuring that\nthere are no unintended matches and maintaining high performance even for complex routing patterns. Supported features include named parameters,\nsuffix and infix catch-all, regexp constraints, hostname matching, method and method-less routes, route matchers, and sub-routers.\n\n**Trailing slash handling:** Automatically handle trailing slash inconsistencies by either ignoring them, redirecting to \nthe canonical path, or enforcing strict matching based on your needs.\n\n**Path correction:** Automatically handle malformed paths with extra slashes or dots by either serving the cleaned path directly or redirecting to the canonical form.\n\n**Automatic OPTIONS replies:** Fox has built-in native support for [OPTIONS requests](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/OPTIONS).\n\n**Client IP Derivation:** Accurately determine the \"real\" client IP address using best practices tailored to your network topology.\n\n**Growing middleware ecosystem:** Fox's middleware ecosystem is still limited, but standard `http.Handler` middleware are fully compatible. Contributions are welcome!\n\n---\n* [Getting started](#getting-started)\n  * [Install](#install)\n  * [Basic example](#basic-example)\n  * [Named parameters](#named-parameters)\n  * [Named wildcards](#named-wildcards-catch-all)\n  * [Route matchers](#route-matchers)\n  * [Method-less routes](#method-less-routes)\n  * [Sub-Routers](#sub-routers)\n  * [Hostname validation \u0026 restrictions](#hostname-validation--restrictions)\n  * [Path encoding](#path-encoding)\n  * [Priority rules](#priority-rules)\n    * [Hostname routing](#hostname-routing)\n  * [Warning about context](#warning-about-context)\n* [Concurrency](#concurrency)\n  * [Managing routes at runtime](#managing-routes-at-runtime)\n  * [ACID Transaction](#acid-transaction)\n  * [Managed read-write transaction](#managed-read-write-transaction)\n  * [Unmanaged read-write transaction](#unmanaged-read-write-transaction)\n  * [Managed read-only transaction](#managed-read-only-transaction)\n* [Middleware](#middleware)\n  * [Official middlewares](#official-middlewares)\n* [Working with http.Handler](#working-with-httphandler)\n* [Handling OPTIONS Requests and CORS Automatically](#handling-options-requests-and-cors-automatically)\n* [Resolving Client IP](#resolving-client-ip)\n* [Benchmark](#benchmark)\n* [Road to v1](#road-to-v1)\n* [Contributions](#contributions)\n* [License](#license)\n---\n\n## Getting started\n#### Install\nWith a [correctly configured](https://go.dev/doc/install#testing) Go toolchain:\n```shell\ngo get -u github.com/fox-toolkit/fox\n```\nThis 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),\nit only supports the two most recent major releases of Go, i.e. 1.26 and 1.25.\n\n#### Basic example\n````go\npackage main\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"log\"\n\t\"net/http\"\n\n\t\"github.com/fox-toolkit/fox\"\n)\n\nfunc HelloServer(c *fox.Context) {\n\t_ = c.String(http.StatusOK, fmt.Sprintf(\"Hello %s\\n\", c.Param(\"name\")))\n}\n\nfunc main() {\n\tf := fox.MustRouter(fox.DefaultOptions())\n\n\tf.MustAdd([]string{http.MethodHead, http.MethodGet}, \"/hello/{name}\", HelloServer)\n\n\tif err := http.ListenAndServe(\":8080\", f); err != nil \u0026\u0026 !errors.Is(err, http.ErrServerClosed) {\n\t\tlog.Fatalln(err)\n\t}\n}\n````\n\n#### Named parameters\nRoutes can include named parameters using curly braces `{name}` to match exactly one non-empty route segment. The matching\nsegments are recorded as [Param](https://pkg.go.dev/github.com/fox-toolkit/fox#Param) and accessible via the \n[Context](https://pkg.go.dev/github.com/fox-toolkit/fox#Context). Named parameters are supported anywhere in \nthe route, but only one parameter is allowed per segment (or hostname label) and must appear at the end of the segment.\n\n````\nPattern /avengers/{name}\n\n/avengers/ironman               matches\n/avengers/thor                  matches\n/avengers/hulk/angry            no matches\n/avengers/                      no matches\n\nPattern /users/uuid:{id}\n\n/users/uuid:123                 matches\n/users/uuid:                    no matches\n\nPattern /users/uuid:{id}/config\n\n/users/uuid:123/config          matches\n/users/uuid:/config             no matches\n\nPattern {sub}.example.com/avengers\n\nfirst.example.com/avengers      matches\nexample.com/avengers           no matches\n````\n\nNamed parameters can include regular expression using the syntax `{name:regexp}`. Regular expressions cannot \ncontain capturing groups, but can use non-capturing groups `(?:pattern)` instead. Regexp support is opt-in via\n`fox.AllowRegexpParam(true)` option.\n\n````\nPattern /products/{name:[A-Za-z]+}\n\n/products/laptop        matches\n/products/123           no matches\n````\n\n#### Named Wildcards (Catch-all)\nNamed wildcard start with a plus sign `+` followed by a name `{param}` and match any sequence of characters\nincluding slashes, but cannot match an empty string. The matching segments are also accessible via\n[Context](https://pkg.go.dev/github.com/fox-toolkit/fox#Context). Catch-all parameters are supported anywhere in the route,\nbut only one parameter is allowed per segment (or hostname label) and must appear at the end of the segment.\nConsecutive catch-all parameter are not allowed.\n\n````\nPattern /src/+{filepath}\n\n/src/conf.txt                      matches\n/src/dir/config.txt                 matches\n/src/                              no matches\n\nPattern /src/file=+{path}\n\n/src/file=config.txt                 matches\n/src/file=/dir/config.txt            matches\n/src/file=                          no matches\n\nPattern: /assets/+{path}/thumbnail\n\n/assets/images/thumbnail           matches\n/assets/photos/2021/thumbnail      matches\n/assets//thumbnail                 no matches\n\nPattern +{sub}.example.com/avengers\n\nfirst.example.com/avengers          matches\nfirst.second.example.com/avengers   matches\nexample.com/avengers               no matches\n````\n\nOptional named wildcard start with an asterisk `*` followed by a name `{param}` and match any sequence of characters\n**including empty** strings. Unlike `+{param}`, optional wildcards can only be used as a suffix.\n\n````\nPattern /src/*{filepath}\n\n/src/conf.txt                      matches\n/src/dir/config.txt                 matches\n/src/                              matches\n\nPattern /src/file=*{path}\n\n/src/file=config.txt                 matches\n/src/file=/dir/config.txt            matches\n/src/file=                          matches\n````\n\nNamed wildcards can include a regular expression constraint using the syntax `+{name:regexp}`. Regular expressions cannot\ncontain capturing groups, but can use non-capturing groups `(?:pattern)` instead. Optional wildcards (`*{param}`) do not\nsupport regular expressions. Regexp support is opt-in via `fox.AllowRegexpParam(true)` option.\n\n````\nPattern /src/+{filepath:[A-Za-z/]+\\.json}\n\n/src/dir/config.json            matches\n/src/dir/config.txt             no matches\n````\n\n#### Route matchers\n\nRoute matchers enable routing decisions based on request properties beyond methods, hostname and path. Multiple routes can share\nthe same pattern and methods and be differentiated by query parameters, headers, client IP, or custom criteria.\n\n````go\nf.MustAdd(fox.MethodGet, \"/api/users\", V1Handler, fox.WithHeaderMatcher(\"X-API-Version\", \"v1\"))\nf.MustAdd(fox.MethodGet, \"/api/users\", V2Handler, fox.WithHeaderMatcher(\"X-API-Version\", \"v2\"))\nf.MustAdd(fox.MethodGet, \"/api/users\", V1Handler) // Fallback route\n````\n\nBuilt-in matchers include `fox.WithQueryMatcher`, `fox.WithQueryRegexpMatcher`, `fox.WithHeaderMatcher`, `fox.WithHeaderRegexpMatcher`,\n`WithSchemeMatcher` and `fox.WithClientIPMatcher`. Multiple matchers on a route use AND logic. Routes without matchers serve as fallbacks.\nFor custom matching logic, implement the `fox.Matcher` interface and use `fox.WithMatcher`. See [Priority rules](#priority-rules) for matcher\nevaluation order.\n\n#### Method-less routes\n\nRoutes can be registered without specifying an HTTP method to match any method. The constant `fox.MethodAny` is\na convenience placeholder equivalent to an empty method set (nil or empty slice).\n\n````go\n// Handle any method on /health\nf.MustAdd(fox.MethodAny, \"/health\", HealthHandler)\n// Forward all requests to a backend service\nf.MustAdd(fox.MethodAny, \"/api/*{any}\", ProxyHandler)\n````\n\nRoutes registered with a specific HTTP method always take precedence over method-less routes. This allows defining method-specific\nbehavior while falling back to a generic handler for other methods.\n````go\n// Specific handler for GET requests\nf.MustAdd(fox.MethodGet, \"/resource\", GetHandler)\n// All other methods handled here\nf.MustAdd(fox.MethodAny, \"/resource\", FallbackHandler)\n````\n\n#### Sub-Routers\nFox provides a composable routing API where routers can be mounted as regular routes, each with its own middleware and configuration.\n\n```go\napi := fox.MustRouter(fox.WithMiddleware(AuthMiddleware()))\napi.MustAdd([]string{http.MethodHead, http.MethodGet}, \"/\", HelloHandler)\napi.MustAdd([]string{http.MethodHead, http.MethodGet}, \"/users\", ListUser)\napi.MustAdd([]string{http.MethodHead, http.MethodGet}, \"/users/{id}\", GetUser)\napi.MustAdd(fox.MethodPost, \"/users\", CreateUser)\n\nf := fox.MustRouter(fox.DefaultOptions())\nf.MustAdd([]string{http.MethodHead, http.MethodGet}, \"/*{filepath}\", fox.WrapH(http.FileServer(http.Dir(\"./public/\"))))\nf.MustAdd(fox.MethodAny, \"/api*{mount}\", fox.Sub(api))\n```\n\nRequests matching the prefix are delegated to the mounted router with the remaining path.\n\nUse cases include:\n- Applying middleware, matchers or other configuration to a route prefix\n- Managing entire route subtree at runtime (e.g. insert, update, or delete via the parent router)\n- Organizing routes into groups with shared configuration\n\n#### Hostname validation \u0026 restrictions\n\nHostnames are validated to conform to the [LDH (letters, digits, hyphens) rule](https://datatracker.ietf.org/doc/html/rfc3696.html#section-2)\n(lowercase only) and SRV-like \"underscore labels\". Wildcard segments within hostnames, such as `{sub}.example.com/`, are exempt from LDH validation\nsince they act as placeholders rather than actual domain labels. As such, they do not count toward the hard limit of 63 characters per label,\nnor the 253-character limit for the full hostname. Internationalized domain names (IDNs) should be specified using an ASCII\n(Punycode) representation.\n\n#### Path encoding\n\nFox matches requests against the canonical encoded path, equivalent to `url.URL.EscapedPath()` with percent-encoded hex\nsequences normalized to uppercase (e.g. `%2f` becomes `%2F`). Encoded and decoded forms are not interchangeable so a request\nfor `/foo%2Fbar` will not match a pattern registered as `/foo/bar`. Patterns containing literal characters that require\nencoding must be registered in their encoded form (e.g. `/foo%20bar`, not `/foo bar`).\n\n#### Priority rules\n\nThe router is designed to balance routing flexibility, performance, and predictability. Internally, it uses a radix tree to\nstore routes efficiently. When a request arrives, Fox evaluates routes in the following order:\n\n1. **Hostname matching**\n    - Routes with hostnames are evaluated before path-only routes\n\n2. **Pattern matching** (longest match, most specific first)\n    - Static segments\n    - Named parameters with regex constraints\n    - Named parameters without constraints\n    - Catch-all parameters with regex constraints\n    - Catch-all parameters without constraints\n    - Infix catch-all are evaluated before suffix catch-all (e.g., `/bucket/+{path}/meta` before `/bucket/+{path}`)\n    - At the same level, multiple regex-constrained parameters are evaluated in registration order\n\n3. **Method matching**\n    - Routes with specific methods are evaluated before method-less routes\n\n4. **Matcher evaluation** (for routes sharing the same pattern and overlapping methods)\n    - Routes with matchers are evaluated before routes without\n    - Among routes with matchers, higher priority is evaluated first (configurable via `fox.WithMatcherPriority`, or defaults to the number of matchers)\n    - Routes with equal matchers priority may be evaluated in any order\n\nIf a match candidate fails to complete the full route, including matchers, Fox returns to the last decision point and tries the next available\nalternative following the same priority order.\n\n##### Hostname routing\n\nThe router can transition instantly and transparently from path-only mode to hostname-prioritized mode without any \nadditional configuration or action. If any route with a hostname is registered, the router automatically switches to \nprioritize hostname matching. Conversely, if no hostname-specific routes are registered, the router reverts to \npath-priority mode.\n\n- If the router has no routes registered with hostnames, the router will perform a path-based lookup only.\n- If the router includes at least one route with a hostname, the router will prioritize lookup based \non the request host and path. If no match is found, the router will then fall back to a path-only lookup.\n\nHostname matching is **case-insensitive**, so requests to `Example.COM`, `example.com`, and `EXAMPLE.COM` will all match a route registered for `example.com`.\n\n#### Warning about context\nThe `fox.Context` instance is freed once the request handler function returns to optimize resource allocation.\nIf you need to retain `fox.Context` beyond the scope of the handler, use the `fox.Context.Clone` methods.\n````go\nfunc Hello(c *fox.Context) {\n    cc := c.Clone()\n    go func() {\n        time.Sleep(2 * time.Second)\n        log.Println(cc.Param(\"name\")) // Safe\n    }()\n    _ = c.String(http.StatusOK, \"Hello %s\\n\", c.Param(\"name\"))\n}\n````\n\n## Concurrency\nFox implements an **immutable radix tree** with copy-on-write semantics, which support lock-free reads while allowing\na single concurrent writer. Mutations follow a three-phase pattern: first, descend recursively through the tree to\nlocate the insertion point; then as the call stack unwinds, copy each node along the modified path back to the root and finally,\nupdate the root in a **single atomic operation**. The result is a shallow copy of the tree, where unmodified branches\nare shared between the old and new tree. Multiple mutations can be applied in a single transaction, where each cloned node is cached \nto avoid copying it more than once.\n\n### Other key points\n\n- Routing requests is lock-free (reading thread never block, even while writes are ongoing)\n- The router always sees a consistent version of the tree while routing request\n- Reading threads do not block writing threads (adding, updating or removing a handler can be done concurrently)\n- Writing threads block each other but never block reading threads\n\nAs such threads that route requests should never encounter latency due to ongoing writes or other concurrent readers.\n\n### Managing routes at runtime\n#### Routing mutation\nIn this example, the handler for `routes/{action}` allows to dynamically register, update and delete handler for the\ngiven route and method. Thanks to Fox's design, those actions are perfectly safe and may be executed concurrently.\n\n````go\npackage main\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"log\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/fox-toolkit/fox\"\n)\n\ntype Data struct {\n\tPattern string   `json:\"pattern\"`\n\tMethods []string `json:\"methods\"`\n\tText    string   `json:\"text\"`\n}\n\nfunc Action(c *fox.Context) {\n\tdata := new(Data)\n\tif err := json.NewDecoder(c.Request().Body).Decode(data); err != nil {\n\t\thttp.Error(c.Writer(), err.Error(), http.StatusBadRequest)\n\t\treturn\n\t}\n\n\tvar err error\n\taction := c.Param(\"action\")\n\tswitch action {\n\tcase \"add\":\n\t\t_, err = c.Router().Add(data.Methods, data.Pattern, func(c *fox.Context) {\n\t\t\t_ = c.String(http.StatusOK, data.Text)\n\t\t})\n\tcase \"update\":\n\t\t_, err = c.Router().Update(data.Methods, data.Pattern, func(c *fox.Context) {\n\t\t\t_ = c.String(http.StatusOK, data.Text)\n\t\t})\n\tcase \"delete\":\n\t\t_, err = c.Router().Delete(data.Methods, data.Pattern)\n\tdefault:\n\t\thttp.Error(c.Writer(), fmt.Sprintf(\"action %q is not allowed\", action), http.StatusBadRequest)\n\t\treturn\n\t}\n\tif err != nil {\n\t\thttp.Error(c.Writer(), err.Error(), http.StatusConflict)\n\t\treturn\n\t}\n\n\t_ = c.String(http.StatusOK, fmt.Sprintf(\"%s route [%s] %s: success\\n\", action, strings.Join(data.Methods, \",\"), data.Pattern))\n}\n\nfunc main() {\n\tf := fox.MustRouter(fox.DefaultOptions())\n\n\tf.MustAdd(fox.MethodPost, \"/routes/{action}\", Action)\n\n\tif err := http.ListenAndServe(\":8080\", f); err != nil \u0026\u0026 !errors.Is(err, http.ErrServerClosed) {\n\t\tlog.Fatalln(err)\n\t}\n}\n````\n\n#### ACID Transaction\nFox supports read-write and read-only transactions (with Atomicity, Consistency, and Isolation; Durability is not supported \nas transactions are in memory). Thread that route requests always see a consistent version of the routing tree and are \nfully isolated from an ongoing transaction until committed. Read-only transactions capture a point-in-time snapshot of \nthe tree, ensuring they do not observe any ongoing or committed changes made after their creation.\n\n#### Managed read-write transaction\n````go\n// Updates executes a function within the context of a read-write managed transaction. If no error is returned\n// from the function then the transaction is committed. If an error is returned then the entire transaction is\n// aborted.\nif err := f.Updates(func(txn *fox.Txn) error {\n\tif _, err := txn.Add(fox.MethodGet, \"example.com/hello/{name}\", Handler); err != nil {\n\t\treturn err\n\t}\n\n\t// Iter returns a collection of range iterators for traversing registered routes.\n\tit := txn.Iter()\n\t// When Iter() is called on a write transaction, it creates a point-in-time snapshot of the transaction state.\n\t// It means that writing on the current transaction while iterating is allowed, but the mutation will not be\n\t// observed in the result returned by PatternPrefix (or any other iterator).\n\tfor route := range it.PatternPrefix(\"tmp.example.com/\") {\n\t\tif _, err := txn.Delete(slices.Collect(route.Methods()), route.Pattern()); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}); err != nil {\n\tlog.Printf(\"transaction aborted: %s\", err)\n}\n````\n\n#### Managed read-only transaction\n````go\n_ = f.View(func(txn *fox.Txn) error {\n\tif txn.Has(fox.MethodGet, \"/foo\") {\n\t\tif txn.Has(fox.MethodGet, \"/bar\") {\n\t\t\t// do something\n\t\t}\n\t}\n\treturn nil\n})\n````\n\n#### Unmanaged read-write transaction\n````go\n// Txn create an unmanaged read-write or read-only transaction.\ntxn := f.Txn(true)\ndefer txn.Abort()\n\nif _, err := txn.Add(fox.MethodGet, \"example.com/hello/{name}\", Handler); err != nil {\n\tlog.Printf(\"error inserting route: %s\", err)\n\treturn\n}\n\n// Iter returns a collection of range iterators for traversing registered routes.\nit := txn.Iter()\n// When Iter() is called on a write transaction, it creates a point-in-time snapshot of the transaction state.\n// It means that writing on the current transaction while iterating is allowed, but the mutation will not be\n// observed in the result returned by PatternPrefix (or any other iterator).\nfor route := range it.PatternPrefix(\"tmp.example.com/\") {\n\tif _, err := txn.Delete(slices.Collect(route.Methods()), route.Pattern()); err != nil {\n\t\tlog.Printf(\"error deleting route: %s\", err)\n\t\treturn\n\t}\n}\n// Finalize the transaction\ntxn.Commit()\n````\n\n## Middleware\nMiddlewares can be registered globally using the `fox.WithMiddleware` option. The example below demonstrates how \nto create and apply automatically a simple logging middleware to all routes (including 404, 405, etc...).\n\n````go\npackage main\n\nimport (\n\t\"log\"\n\t\"net/http\"\n\t\"time\"\n\n\t\"github.com/fox-toolkit/fox\"\n)\n\nfunc Logger(next fox.HandlerFunc) fox.HandlerFunc {\n\treturn func(c *fox.Context) {\n\t\tstart := time.Now()\n\t\tnext(c)\n\t\tlog.Printf(\"route: %s, latency: %s, status: %d, size: %d\",\n\t\t\tc.Pattern(),\n\t\t\ttime.Since(start),\n\t\t\tc.Writer().Status(),\n\t\t\tc.Writer().Size(),\n\t\t)\n\t}\n}\n\nfunc main() {\n\tf := fox.MustRouter(fox.WithMiddleware(Logger))\n\n\tf.MustAdd(fox.MethodGet, \"/\", func(c *fox.Context) {\n\t\t_ = c.String(http.StatusOK, \"Hello World\")\n\t})\n\n\tlog.Fatalln(http.ListenAndServe(\":8080\", f))\n}\n````\n\nAdditionally, `fox.WithMiddlewareFor` option provide a more fine-grained control over where a middleware is applied, such as\nonly for 404 or 405 handlers. Possible scopes include `fox.RouteHandler` (regular routes), `fox.NoRouteHandler`, `fox.NoMethodHandler`, \n`fox.RedirectSlashHandler`, `fox.RedirectPathHandler`, `fox.OptionsHandler` and any combination of these.\n\n````go\nf  := fox.MustRouter(\n\tfox.WithMiddlewareFor(fox.RouteHandler, Logger),\n\tfox.WithMiddlewareFor(fox.NoRouteHandler|fox.NoMethodHandler, SpecialLogger),\n)\n````\n\nFinally, it's also possible to attaches middleware on a per-route basis. Note that route-specific middleware must be explicitly reapplied \nwhen updating a route. If not, any middleware will be removed, and the route will fall back to using only global middleware (if any).\n\n````go\nf := fox.MustRouter(\n\tfox.WithMiddleware(fox.Logger(slog.NewTextHandler(os.Stdout, nil))),\n)\nf.MustAdd(fox.MethodGet, \"/\", SomeHandler, fox.WithMiddleware(foxtimeout.Middleware(2*time.Second)))\nf.MustAdd(fox.MethodGet, \"/foo\", SomeOtherHandler)\n````\n\n### Official middlewares\n* [fox-toolkit/oteltracing](https://github.com/fox-toolkit/oteltracing): Distributed tracing with [OpenTelemetry](https://opentelemetry.io/)\n* [fox-toolkit/timeout](https://github.com/fox-toolkit/timeout): Better `http.TimeoutHandler` middleware.\n* [fox-toolkit/waf](https://github.com/fox-toolkit/waf): Coraza WAF middleware (experimental).\n\n## Working with http.Handler\nFox itself implements the `http.Handler` interface which make easy to chain any compatible middleware before the router. Moreover, the router\nprovides convenient `fox.WrapF`, `fox.WrapH` and `fox.WrapM` adapter to be use with `http.Handler`.\n\nThe route parameters can be accessed by the wrapped handler through the request `context.Context` when the adapters are used.\n\nWrapping an `http.Handler`\n````go\narticles := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\tparams := fox.ParamsFromContext(r.Context())\n\t// Article id: 80\n\t// Matched route: /articles/{id}\n\t_, _ = fmt.Fprintf(w, \"Article id: %s\\nMatched route: %s\\n\", params.Get(\"id\"), r.Pattern)\n})\n\nf := fox.MustRouter()\nf.MustAdd(fox.MethodGet, \"/articles/{id}\", fox.WrapH(articles))\n````\n\nWrapping any standard `http.Handler` middleware\n````go\ncorsMw, _ := cors.NewMiddleware(cors.Config{\n\tOrigins:        []string{\"https://example.com\"},\n\tMethods:        []string{http.MethodGet, http.MethodPost, http.MethodPut},\n\tRequestHeaders: []string{\"Authorization\"},\n})\n\nf := fox.MustRouter(\n\tfox.WithMiddlewareFor(fox.RouteHandler|fox.OptionsHandler, fox.WrapM(corsMw.Wrap)),\n)\n````\n\n## Handling OPTIONS Requests and CORS Automatically\nThe `WithAutoOptions` setting or the `WithOptionsHandler` registration enable automatic responses to [OPTIONS requests](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Methods/OPTIONS).\nThis feature is particularly useful for handling Cross-Origin Resource Sharing (CORS) preflight requests.\n\nWhen automatic OPTIONS responses is enabled, Fox distinguishes between regular OPTIONS requests and CORS preflight requests:\n- **Regular OPTIONS requests:** The router responds with the `Allow` header populated with all HTTP methods registered for the matched resource. If no route matches, the `NoRoute` handler is called.\n- **CORS preflight requests:** The router responds to every preflight request by calling the OPTIONS handler, regardless of whether the resource exists.\n\nTo customize how OPTIONS requests are handled (e.g. adding CORS headers), you may register a middleware for the `fox.OptionsHandler` scope\nor provide a custom handler via `WithOptionsHandler`. Note that registered routes with the OPTIONS method always take precedence over automatic replies.\n\n````go\npackage main\n\nimport (\n\t\"errors\"\n\t\"log\"\n\t\"net/http\"\n\n\t\"github.com/jub0bs/cors\"\n\t\"github.com/fox-toolkit/fox\"\n)\n\nfunc main() {\n\tcorsMw, err := cors.NewMiddleware(cors.Config{\n\t\tOrigins:        []string{\"https://example.com\"},\n\t\tMethods:        []string{http.MethodGet, http.MethodPost},\n\t\tRequestHeaders: []string{\"Authorization\"},\n\t})\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\tcorsMw.SetDebug(true) // turn debug mode on (optional)\n\n\tf := fox.MustRouter(\n\t\tfox.WithAutoOptions(true), // let Fox automatically handle OPTIONS requests\n\t\tfox.WithMiddlewareFor(fox.RouteHandler|fox.OptionsHandler, fox.WrapM(corsMw.Wrap)),\n\t)\n\n\tf.MustAdd(fox.MethodGet, \"/api/users\", ListUsers)\n\tf.MustAdd(fox.MethodPost, \"/api/users\", CreateUsers)\n\n\tif err := http.ListenAndServe(\":8080\", f); !errors.Is(err, http.ErrServerClosed) {\n\t\tlog.Fatal(err)\n\t}\n}\n````\n\nAlternatively, you can use a sub-router to apply CORS only to a specific section of your API.\n\n````go\npackage main\n\nimport (\n\t\"log\"\n\t\"net/http\"\n\n\t\"github.com/jub0bs/cors\"\n\t\"github.com/fox-toolkit/fox\"\n)\n\nfunc main() {\n\tcorsMw, err := cors.NewMiddleware(cors.Config{\n\t\tOrigins:        []string{\"https://example.com\"},\n\t\tMethods:        []string{http.MethodHead, http.MethodGet, http.MethodPost},\n\t\tRequestHeaders: []string{\"Authorization\"},\n\t})\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\tcorsMw.SetDebug(true) // turn debug mode on (optional)\n\n\tf := fox.MustRouter()\n\tf.MustAdd([]string{http.MethodHead, http.MethodGet}, \"/*{filepath}\", fox.WrapH(http.FileServer(http.Dir(\"./public/\"))))\n\n\tapi := fox.MustRouter(\n\t\tfox.WithAutoOptions(true), // let Fox automatically handle OPTIONS requests\n\t\tfox.WithMiddlewareFor(fox.RouteHandler|fox.OptionsHandler, fox.WrapM(corsMw.Wrap)),\n\t)\n\tapi.MustAdd([]string{http.MethodHead, http.MethodGet}, \"/users\", ListUsers)\n\tapi.MustAdd(fox.MethodPost, \"/users\", CreateUser)\n\n\tf.MustAdd(fox.MethodAny, \"/api*{any}\", fox.Sub(api)) // note: Method-less route\n}\n````\n\nThe CORS protocol is complex and security-sensitive. We do **NOT** recommend implementing CORS handling manually. Instead,\nconsider using [jub0bs/cors](https://github.com/jub0bs/cors), which performs extensive validation before allowing middleware creation, helping you avoid common pitfalls.\n\n## Resolving Client IP\nThe `WithClientIPResolver` option allows you to set up strategies to resolve the client IP address based on your \nuse case and network topology. Accurately determining the client IP is hard, particularly in environments with proxies or \nload balancers. For example, the leftmost IP in the `X-Forwarded-For` header is commonly used and is often regarded as the \n\"closest to the client\" and \"most real,\" but it can be easily spoofed. Therefore, you should absolutely avoid using it \nfor any security-related purposes, such as request throttling.\n\nThe resolver used must be chosen and tuned for your network configuration. This should result in a resolver never returning \nan error and if it does, it should be treated as an application issue or a misconfiguration, rather than defaulting to an \nuntrustworthy IP.\n\nThe sub-package `github.com/fox-toolkit/fox/clientip` provides a set of best practices resolvers that should cover most use cases.\n\n````go\npackage main\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/fox-toolkit/fox\"\n\t\"github.com/fox-toolkit/fox/clientip\"\n)\n\nfunc main() {\n\tresolver, err := clientip.NewRightmostNonPrivate(clientip.XForwardedForKey)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tf := fox.MustRouter(\n\t\tfox.DefaultOptions(),\n\t\tfox.WithClientIPResolver(\n\t\t\tresolver,\n\t\t),\n\t)\n\n\tf.MustAdd(fox.MethodGet, \"/foo/bar\", func(c *fox.Context) {\n\t\tipAddr, err := c.ClientIP()\n\t\tif err != nil {\n\t\t\t// If the current resolver is not able to derive the client IP, an error\n\t\t\t// will be returned rather than falling back on an untrustworthy IP. It\n\t\t\t// should be treated as an application issue or a misconfiguration.\n\t\t\tpanic(err)\n\t\t}\n\t\tfmt.Println(ipAddr.String())\n\t})\n}\n````\n\nIt is also possible to create a chain with multiple resolvers that attempt to derive the client IP, stopping when the first one succeeds.\n\n````go\nresolver, _ := clientip.NewLeftmostNonPrivate(clientip.ForwardedKey, 10)\nf := fox.MustRouter(\n\tfox.DefaultOptions(),\n\tfox.WithClientIPResolver(\n\t\t// A common use for this is if a server is both directly connected to the\n\t\t// internet and expecting a header to check.\n\t\tclientip.NewChain(\n\t\t\tresolver,\n\t\t\tclientip.NewRemoteAddr(),\n\t\t),\n\t),\n)\n````\n\nNote that there is no \"sane\" default strategy, so calling `Context.ClientIP` without a resolver configured will return \nan `ErrNoClientIPResolver`.\n\nSee this [blog post](https://adam-p.ca/blog/2022/03/x-forwarded-for/) for general guidance on choosing a strategy that fit your needs.\n\n## Benchmark\nThe primary goal of Fox is to be a lightweight, high performance router which allow routes modification at runtime.\nThe following benchmarks attempt to compare Fox to various popular alternatives, including both fully-featured web frameworks\nand lightweight request routers. These benchmarks are based on the [julienschmidt/go-http-routing-benchmark](https://github.com/julienschmidt/go-http-routing-benchmark) \nrepository.\n\nPlease note that these benchmarks should not be taken too seriously, as the comparison may not be entirely fair due to \nthe differences in feature sets offered by each framework. Performance should be evaluated in the context of your specific \nuse case and requirements. While Fox aims to excel in performance, it's important to consider the trade-offs and \nfunctionality provided by different web frameworks and routers when making your selection.\n\n### Config\n```\nGOOS:   Darwin\nGOARCH: arm64\nGO:     1.26\nCPU:    Apple M4 Max\n```\n### Static Routes\nIt is just a collection of random static paths inspired by the structure of the Go directory. It might not be a realistic URL-structure.\n\n**GOMAXPROCS: 1**\n```\nBenchmarkHttpRouter_StaticAll     624214              3800 ns/op               0 B/op          0 allocs/op\nBenchmarkHttpTreeMux_StaticAll    475593              5077 ns/op               0 B/op          0 allocs/op\nBenchmarkGin_StaticAll            437480              5461 ns/op               0 B/op          0 allocs/op\nBenchmarkEcho_StaticAll           298819              7452 ns/op               0 B/op          0 allocs/op\nBenchmarkFox_StaticAll            292527              8102 ns/op               0 B/op          0 allocs/op\nBenchmarkStdMux_StaticAll         186615             12339 ns/op               0 B/op          0 allocs/op\nBenchmarkChi_StaticAll             63331             37896 ns/op           57776 B/op        314 allocs/op\nBenchmarkBeego_StaticAll           42171             57150 ns/op           55264 B/op        471 allocs/op\nBenchmarkGorillaMux_StaticAll       8614            293244 ns/op          133137 B/op       1099 allocs/op\nBenchmarkPat_StaticAll              5626            414071 ns/op          602832 B/op      12559 allocs/op\nBenchmarkMartini_StaticAll          4375            562980 ns/op          129210 B/op       2031 allocs/op\nBenchmarkTraffic_StaticAll          3708            646128 ns/op          749842 B/op      14444 allocs/op\n```\nIn this benchmark, Fox performs as well as `Gin` and `Echo` which are both Radix Tree based routers. An interesting fact is\nthat [HttpTreeMux](https://github.com/dimfeld/httptreemux) also support [adding route while serving request concurrently](https://github.com/dimfeld/httptreemux#concurrency).\nHowever, it takes a slightly different approach, by using an optional `RWMutex` that may not scale as well as Fox under heavy load. The next\ntest compare `HttpTreeMux` with and without the `*SafeAddRouteFlag` (concurrent reads and writes) and `Fox` in parallel benchmark.\n\n**GOMAXPROCS: 16**\n```\nBenchmarkFox_StaticAllParallel-16                3309738               739.3 ns/op             0 B/op          0 allocs/op\nBenchmarkHttpTreeMux_StaticAllParallel-16         100099             23991 ns/op               0 B/op          0 allocs/op\n```\nAs you can see, this benchmark highlight the cost of using higher synchronisation primitive like `RWMutex` to be able to register new route while handling requests.\n\n### Micro Benchmarks\nThe following benchmarks measure the cost of some very basic operations.\n\nIn the first benchmark, only a single route, containing a parameter, is loaded into the routers. Then a request for a URL \nmatching this pattern is made and the router has to call the respective registered handler function. End.\n\n**GOMAXPROCS: 1**\n```\nBenchmarkEcho_Param             100000000               21.37 ns/op            0 B/op          0 allocs/op\nBenchmarkGin_Param              100000000               23.43 ns/op            0 B/op          0 allocs/op\nBenchmarkFox_Param              89916406                26.83 ns/op            0 B/op          0 allocs/op\nBenchmarkHttpRouter_Param       79691304                30.77 ns/op           32 B/op          1 allocs/op\nBenchmarkHttpTreeMux_Param      15789637               152.6 ns/op           352 B/op          3 allocs/op\nBenchmarkBeego_Param             7728720               314.2 ns/op           352 B/op          3 allocs/op\nBenchmarkPat_Param               7304704               316.0 ns/op           472 B/op          8 allocs/op\nBenchmarkChi_Param               7349187               327.5 ns/op           704 B/op          4 allocs/op\nBenchmarkGorillaMux_Param        4107637               583.5 ns/op          1152 B/op          8 allocs/op\nBenchmarkTraffic_Param           2434245               944.6 ns/op          1808 B/op         19 allocs/op\nBenchmarkMartini_Param           2001694              1215 ns/op            1096 B/op         12 allocs/op\n```\nSame as before, but now with multiple parameters, all in the same single route. The intention is to see how the routers scale with the number of parameters.\n\n**GOMAXPROCS: 1**\n```\nBenchmarkGin_Param5             60474345                41.07 ns/op            0 B/op          0 allocs/op\nBenchmarkEcho_Param5            54142920                43.99 ns/op            0 B/op          0 allocs/op\nBenchmarkFox_Param5             38424334                62.01 ns/op            0 B/op          0 allocs/op\nBenchmarkHttpRouter_Param5      29863018                80.66 ns/op          160 B/op          1 allocs/op\nBenchmarkHttpTreeMux_Param5      7086744               338.2 ns/op           576 B/op          6 allocs/op\nBenchmarkBeego_Param5            5765439               412.4 ns/op           352 B/op          3 allocs/op\nBenchmarkChi_Param5              5436216               444.1 ns/op           704 B/op          4 allocs/op\nBenchmarkPat_Param5              3452257               707.6 ns/op           776 B/op         23 allocs/op\nBenchmarkGorillaMux_Param5       2546830               934.7 ns/op          1216 B/op          8 allocs/op\nBenchmarkMartini_Param5          1664374              1436 ns/op            1256 B/op         13 allocs/op\nBenchmarkTraffic_Param5          1617499              1445 ns/op            2176 B/op         26 allocs/op\n\nBenchmarkEcho_Param20           20749047               116.1 ns/op             0 B/op          0 allocs/op\nBenchmarkGin_Param20            20870449               117.1 ns/op             0 B/op          0 allocs/op\nBenchmarkFox_Param20            10336230               234.9 ns/op             0 B/op          0 allocs/op\nBenchmarkHttpRouter_Param20      8866296               269.5 ns/op           704 B/op          1 allocs/op\nBenchmarkBeego_Param20           2399250               995.7 ns/op           352 B/op          3 allocs/op\nBenchmarkChi_Param20             1392295              1735 ns/op            2504 B/op          9 allocs/op\nBenchmarkHttpTreeMux_Param20     1300804              1843 ns/op            3144 B/op         13 allocs/op\nBenchmarkGorillaMux_Param20      1000000              2065 ns/op            3272 B/op         13 allocs/op\nBenchmarkMartini_Param20          868604              2716 ns/op            3568 B/op         18 allocs/op\nBenchmarkPat_Param20              695614              3433 ns/op            3992 B/op         75 allocs/op\nBenchmarkTraffic_Param20          453583              5186 ns/op            7664 B/op         52 allocs/op\n```\n\nNow let's see how expensive it is to access a parameter. The handler function reads the value (by the name of the parameter, e.g. with a map \nlookup; depends on the router) and writes it to `/dev/null`\n\n**GOMAXPROCS: 1**\n```\nBenchmarkGin_ParamWrite                 81540667                27.21 ns/op            0 B/op          0 allocs/op\nBenchmarkFox_ParamWrite                 69228854                34.00 ns/op            0 B/op          0 allocs/op\nBenchmarkHttpRouter_ParamWrite          69800920                34.05 ns/op           32 B/op          1 allocs/op\nBenchmarkEcho_ParamWrite                46682539                49.16 ns/op            8 B/op          1 allocs/op\nBenchmarkHttpTreeMux_ParamWrite         15320574               160.0 ns/op           352 B/op          3 allocs/op\nBenchmarkChi_ParamWrite                  7458879               323.0 ns/op           704 B/op          4 allocs/op\nBenchmarkBeego_ParamWrite                7340652               327.7 ns/op           360 B/op          4 allocs/op\nBenchmarkPat_ParamWrite                  4767104               501.8 ns/op           896 B/op         12 allocs/op\nBenchmarkGorillaMux_ParamWrite           4066573               592.5 ns/op          1152 B/op          8 allocs/op\nBenchmarkTraffic_ParamWrite              2079380              1148 ns/op            2232 B/op         23 allocs/op\nBenchmarkMartini_ParamWrite              1760973              1355 ns/op            1144 B/op         15 allocs/op\n```\n\nIn those micro benchmarks, we can see that `Fox` scale really well, even with long wildcard routes. Like `Gin`, this router reuse the\ndata structure (e.g. `fox.Context` slice) containing the matching parameters in order to remove completely heap allocation. \n\n### Github\nFinally, this benchmark executes a request for each GitHub API route (203 routes).\n\n**GOMAXPROCS: 1**\n```\nBenchmarkGin_GithubAll            286442              8405 ns/op               0 B/op          0 allocs/op\nBenchmarkEcho_GithubAll           207840             11112 ns/op               0 B/op          0 allocs/op\nBenchmarkHttpRouter_GithubAll     179312             13219 ns/op           14240 B/op        171 allocs/op\nBenchmarkFox_GithubAll            170670             14033 ns/op               0 B/op          0 allocs/op\nBenchmarkHttpTreeMux_GithubAll     58237             41668 ns/op           67648 B/op        691 allocs/op\nBenchmarkChi_GithubAll             30548             78991 ns/op          130817 B/op        740 allocs/op\nBenchmarkBeego_GithubAll           30604             79720 ns/op           73121 B/op        629 allocs/op\nBenchmarkTraffic_GithubAll          2248           1071896 ns/op          837296 B/op      14315 allocs/op\nBenchmarkPat_GithubAll              2174           1104015 ns/op         1834945 B/op      28773 allocs/op\nBenchmarkGorillaMux_GithubAll       1900           1272217 ns/op          230339 B/op       1620 allocs/op\nBenchmarkMartini_GithubAll          1728           1375748 ns/op          236943 B/op       2805 allocs/op\n```\n\n## Road to v1\n- [x] [Update route syntax](https://github.com/fox-toolkit/fox/pull/10#issue-1643728309) @v0.6.0\n- [x] [Route overlapping](https://github.com/fox-toolkit/fox/pull/9#issue-1642887919) @v0.7.0\n- [x] [Route overlapping (catch-all and params)](https://github.com/fox-toolkit/fox/pull/24#issue-1784686061) @v0.10.0\n- [x] [Ignore trailing slash](https://github.com/fox-toolkit/fox/pull/32), [Builtin Logger Middleware](https://github.com/fox-toolkit/fox/pull/33), [Client IP Derivation](https://github.com/fox-toolkit/fox/pull/33) @v0.14.0\n- [x] [Support infix wildcard](https://github.com/fox-toolkit/fox/pull/46), [Support hostname routing](https://github.com/fox-toolkit/fox/pull/48), [Support ACID transaction](https://github.com/fox-toolkit/fox/pull/49) @v0.18.0\n- [x] [Support regexp params](https://github.com/fox-toolkit/fox/pull/68) @v0.25.0\n- [x] [Support route matchers](https://github.com/fox-toolkit/fox/pull/69), [Support SubRouter](https://github.com/fox-toolkit/fox/pull/70), [Method-less tree](https://github.com/fox-toolkit/fox/pull/71) @v0.26.0\n- [x] Programmatic error handling\n- [ ] Improving performance and stabilizing API\n\n## Contributions\nThis project aims to provide a lightweight, high-performance router that is easy to use and hard to misuse, designed for building API gateways and reverse proxies.\nFeatures are chosen carefully with an emphasis on composability, and each addition is evaluated against this core mission. The router exposes a relatively low-level API,\nallowing it to serve as a building block for implementing your own \"batteries included\" frameworks. Feature requests and PRs along these lines are welcome. \n\n## License\n\nFox is licensed under the **Apache License 2.0**. See [`LICENSE.txt`](./LICENSE.txt) for details.\n\nThe [**Fox logo**](https://github.com/fox-toolkit/fox/blob/static/fox_logo.png) is licensed separately under [**CC BY-NC-ND 4.0**](https://creativecommons.org/licenses/by-nc-nd/4.0/?ref=chooser-v1). \nSee [`LICENSE-fox-logo.txt`](https://github.com/fox-toolkit/fox/blob/static/LICENSE-fox-logo.txt) for details.\n\n## Acknowledgements\n- [hashicorp/go-immutable-radix](https://github.com/hashicorp/go-immutable-radix): Fox Tree design is inspired by Hashicorp's Immutable Radix Tree.\n- [realclientip/realclientip-go](https://github.com/realclientip/realclientip-go): Fox uses a derivative version of Adam Pritchard's `realclientip-go` library. \nSee his insightful [blog post](https://adam-p.ca/blog/2022/03/x-forwarded-for/) on the topic for more details.\n- The router API is influenced by popular routers such as [Gin](https://github.com/gin-gonic/gin) and [Echo](https://github.com/labstack/echo).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffox-toolkit%2Ffox","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffox-toolkit%2Ffox","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffox-toolkit%2Ffox/lists"}