Builtin HTTP routing in Go

Table of contents

Routing is a core component of building web services of all kinds. With the release of go 1.22, the routing capabilities of the standard library net/http package has gained powerful new features that remove the need for third-party routing tools for many applications.

Basic pattern matching

A pattern is a string with the format METHOD HOST/PATH, used to match HTTP requests to handlers on an http.ServeMux. Everything except the first slash between HOST and PATH is optional. Omitted parts match any value, for example the pattern /foo will match all request methods and hosts for path /foo, while the pattern POST sample.com/foo will only match a POST request to path /foo on host sample.com.

If a pattern ends with a slash /, it will match any path prefixed by the pattern. The pattern /img will only patch requests for the path /img, but the pattern /img/ will match requests for any path starting with /img/, like /img/1.jpg or /img/logos/square/large.png. It should be noted that a path with and without trailing slash are treated as separate: if both patterns /img and /img/ are registered at the same time, they will work as expected. If only pattern /img/ is registered, then a request for path /img will redirect to /img/ instead.

The trailing slash behavior is also true for the pattern /, which will match requests for any path, making it suitable for a 404 Not Found page.

Limiting matches

Pattern matching can be limited using the special {$} marker. It can be used in a pattern to mark the end of a request path, so that only paths are matched that end where the marker in the pattern is. For example, the pattern /{$} will only match the literal path /. This can also be used to disable the trailing slash behavior for other patterns, for example the pattern /img/{$} will only match the literal path /img/.

Matching precedence

When multiple patterns would match a request, the more specific one takes precedence. A pattern is considered more specific if it matches less requests than the other one(s). For example, if both patterns /static/ and /static/img/ are registered at the same time, the second one will match all paths starting with /static/img/, while the first one will match all other paths starting with /static/.

Extracting path variables

The last missing piece for common routing needs is the ability to extract path segments as variables. Suppose you wanted to construct a handler that serves blog posts at a generic url like /post/15, where 15 is the id of the blog post to show. A pattern to match this use case would look like this: /post/{id}. The {id} part of the pattern signals that the path segment at this position should be extracted as a variable by name id. The value for the actual path can then be retrieved using the *http.Request object that was passed to the handler:

http.HandleFunc("/post/{id}", func(w http.ResponseWriter, r *http.Request) {
  id := r.PathValue("id")
})

It is important to note that this syntax will only match one path segment - it will neither match multiple path segments like /post/15/comments. To catch all path segments until the end of the path, the special ... syntax can be appended to the variable name. Using this, the pattern /static/{file...} with a request for path /static/img/one.png would provide value img/one.png when calling r.Pathvalue("file").




With these additions to the standard library routing functionality, the gap to popular third-party routing packages like gorilla/mux has been reduced significantly, and many applications may not need to rely on packages outside the standard library anymore at all. While the most common needs are now covered with the pattern matching additions, more advanced needs like matching partial path segments or regular expressions still require the use of a more complex routing module.

More articles

Operating postgres clusters on Kubernetes

Automating backups and failover with the kubegres operator

Writing kubernetes manifests by hand

Writing valid and secure object definitions

PHP output buffering

Controlling what gets sent and when

The downsides of source-available software licenses

And how it differs from real open-source licenses

Configure linux debian to boot into a fullscreen application

Running kiosk-mode applications with confidence

How to use ansible with vagrant environments

Painlessly connect vagrant infrastructure and ansible playbooks