Routing
Patterns
Section titled “Patterns”A pattern is a path made of segments separated by slashes. Each segment is one of three kinds:
| Segment | Matches | Example |
|---|---|---|
| Literal | Only itself | /users/new |
Param, {name} |
Any one segment | /users/{name} |
Regexp param, {name:regexp} |
A segment the whole regexp matches | /users/{id:[0-9]+} |
r.GET("/", home)r.GET("/users/new", newUserForm)r.GET("/users/{id:[0-9]+}", userByID)r.GET("/users/{name}", userByName)r.GET("/repos/{owner}/{repo}/issues/{number:[0-9]+}", issue)A few rules keep matching predictable:
- A regexp matches the whole segment.
{id:[0-9]+}matches42but not42abc: every regexp is anchored as^(?:regexp)$. - A regexp stays within one segment. It can’t contain a slash.
- A trailing slash is ignored.
/usersand/users/are the same route. - An empty segment never matches.
/users//42matches no route onnet/http. fasthttp collapses//before the router sees the path, so there it is/users/42. - Patterns match the decoded path. An escaped slash,
%2F, separates segments like any other slash. - A segment is either text or one param.
/files/{name}.txtis rejected; use/files/{name:.+\.txt}.
Which route wins
Section titled “Which route wins”When more than one pattern could match a segment, the router tries them in this order:
- literals;
- regexp params, in the order they were registered;
- the plain param.
If the preferred branch fails further down the path, the router backtracks and tries the next one. These four routes can live side by side:
r.GET("/users/new", show) // GET /users/newr.GET("/users/{id:[0-9]+}", show) // GET /users/42r.GET("/users/{name}", show) // GET /users/gopherr.GET("/users/{name}/posts", show) // GET /users/new/posts: "new" is a literal, but only the param route has /postsParams
Section titled “Params”Param values are set on the request with Request.SetPathValue, so handlers read them with r.PathValue, as they would with http.ServeMux.
r.Pattern holds the pattern of the route that matched, which is handy for logs and metrics.
r.GET("/repos/{owner}/{repo}", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "%s/%s (route %s)", r.PathValue("owner"), r.PathValue("repo"), r.Pattern)})Middleware registered with Use runs after the params are set, so it can read them too.
Read param values with fastrouter.Param:
r.GET("/repos/{owner}/{repo}", func(ctx *fasthttp.RequestCtx) { fmt.Fprintf(ctx, "%s/%s", fastrouter.Param(ctx, "owner"), fastrouter.Param(ctx, "repo"))})Methods
Section titled “Methods”There is a helper for each standard method: GET, HEAD, POST, PUT, PATCH, DELETE, CONNECT, OPTIONS and TRACE. Each takes a handler func.
Handle takes any http.Handler and any method, including custom ones:
r.Handle("PURGE", "/cache/{key}", purgeHandler)r.Handle(http.MethodGet, "/debug/vars", expvar.Handler())A HEAD request is routed to HEAD handlers only. Register one where you need it.
When nothing matches
Section titled “When nothing matches”| Request | Response |
|---|---|
| No route matches the path | 404 Not Found |
| Routes match the path, but on other methods | 405 Method Not Allowed, with an Allow header listing them |
OPTIONS on a path with routes |
200 OK with an Allow header and an empty body, unless you registered an OPTIONS handler there |
OPTIONS * |
200 OK with an Allow header listing every method the router has routes for |
Replace the 404 and 405 handlers with options. The Allow header is already set when the 405 handler runs:
r := gorouter.New( gorouter.WithNotFound(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { http.Error(w, "no page at "+r.URL.Path, http.StatusNotFound) })), gorouter.WithMethodNotAllowed(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { http.Error(w, "try "+w.Header().Get("Allow"), http.StatusMethodNotAllowed) })),)Invalid patterns
Section titled “Invalid patterns”Registering a route panics with a message that names the problem, so mistakes surface at startup rather than as a mysterious 404. The router panics when a pattern:
- doesn’t start with
/, or has an empty segment; - has an unclosed
{, a param without a name, or an empty regexp; - has a regexp that doesn’t compile;
- mixes text and a param in one segment;
- uses the same param name twice;
- is already registered for that method, or overlaps a mount.
Listing routes
Section titled “Listing routes”Routes returns every registered route in registration order, which helps when printing a route table at startup or testing that a route exists:
for _, route := range r.Routes() { log.Println(route.Method, route.Pattern)}Concurrency
Section titled “Concurrency”Register routes and middleware before you start serving. Once serving, a router is safe for any number of concurrent requests.