Documentation ¶
Overview ¶
Package hmux provides an HTTP request multiplexer which matches requests to handlers using method- and path-based rules.
Using hmux involves two phases: construction, using a Builder, and request serving, using a Mux.
b := hmux.NewBuilder() b.Get("/", handleIndex) ... mux := b.Build() http.ListenAndServe(addr, mux)
Patterns ¶
Builder rules match methods and paths in request URLs. The path is matched using a pattern string.
A pattern begins with a slash ("/") and contains zero or more segments separated by slashes.
In the simplest case, the pattern matches a single route because each segment is a literal string:
b.Get("/home/about", hmux.ServeFile("about.html"))
A pattern segment may instead contain a parameter, which begins with a colon:
b.Get("/teams/:team/users/:username", serveUser)
This pattern matches many different URL paths:
/teams/llamas/users/bob /teams/45/users/92 ...
A pattern may end with a slash; it only matches URL paths that also end with a slash.
A "wildcard" pattern has a segment containing only * at the end (after the final slash):
b.Get("/lookup/:db/*", handleLookup)
This matches any path beginning with the same prefix of segments:
/lookup/miami/a/b/c /lookup/frankfurt/568739 /lookup/tokyo/ /lookup/ (but not /lookup)
Wildcard patterns are especially useful in conjunction with Builder.Prefix and Builder.ServeFS, which always treat their inputs as wildcard patterns even if they don't have the ending *.
There are two special patterns which don't begin with a slash: "*" and "".
The pattern "*" matches (only) the request URL "*". This is typically used with OPTIONS requests.
The empty pattern ("") matches any request URL.
A Builder does not accept two rules with overlapping methods and the same pattern.
b.Handle("", "/x/:one", h1) b.Get("/x/:two", h2) // panic: pattern is already registered for all methods
To avoid confusion, apart from wildcard patterns and the special pattern "*", asterisks are not allowed in patterns. Additionally, a pattern segment cannot be empty.
b.Get("/a*b", handler) // panic: pattern contains * b.Get("/a//b", handler) // panic: pattern contains empty segment
Literal pattern segments are interpeted as URL-escaped strings. Therefore, to create a pattern which matches a path containing characters reserved for pattern syntax, URL-encode those characters.
b.Get("/%3afoo", handler) // matches the path "/:foo" b.Get("/a/%2a", handler) // matches the path "/a/*"
Routing ¶
A Mux routes requests to the handler registered by the most specific rule that matches the request's path and method. When comparing two rules, the most specific one is the rule with the most specific pattern; if both rules have patterns that are equally specific, then the most specific rule is the one that matches specific methods rather than all methods.
Pattern specificity is defined as a segment-by-segment comparison, starting from the beginning. The types of segments, arranged from most to least specific, are:
- literal ("/a")
- int32 parameter ("/:p:int32")
- int64 parameter ("/:p:int64")
- string parameter ("/:p")
For two patterns having the same segment specificity, a pattern ending with slash is more specific than a pattern ending with a wildcard.
As an example, suppose there are five rules:
b.Get("/x/y", handlerA) b.Get("/x/:p:int32", handlerB) b.Get("/x/:p", handlerC) b.Get("/:p/y", handlerD) b.Handle("", "/x/y", handlerE)
Requests are routed as follows:
GET /x/y handlerA GET /x/3 handlerB GET /x/z handlerC GET /y/y handlerD POST /x/y handlerE
If a request matches the patterns of one or more rules but does not match the methods of any of those rules, the Mux writes an HTTP 405 ("Method Not Allowed") response with an Allow header that lists all of the matching methods.
If there is no matching rule pattern at all, the Mux writes an HTTP 404 ("Not Found") response.
Before routing, if the request path contains any segment that is "" (that is, a double slash), ".", or "..", the Mux writes an HTTP 308 redirect to an equivalent cleaned path. For example, all of these are redirected to /x/y:
/x//y /x/./y /x/y/z/..
This automatic redirection does not apply to CONNECT requests.
Parameters ¶
Pattern segments may specify a type after a second colon:
b.Post("/employees/:username:string", handleUpdateEmployee)
A string parameter matches any URL path segment, and it is also the default type if no parameter type is given.
The other parameter types are int32 and int64. A pattern segment with an integer type matches the corresponding request URL path segment if that segment can be parsed as a decimal integer of that type.
b.Get("/inventory/:itemid:int64/price", handlePrice)
Parameters are passed to HTTP handlers using http.Request.Context. Inside an HTTP handler called by a Mux, parameters are available via RequestParams.
b.Get("/:region/:shard:int64/*", handleLookup) ... func handleLookup(w http.ResponseWriter, r *http.Request) { p := hmux.RequestParams(r) // Suppose we get a URL path of /west/39/alfa/bravo p.Get("region") // "west" p.Int64("shard") // 39 p.Wildcard() // "/alfa/bravo" }
Example (Basics) ¶
package main import ( "fmt" "log" "net/http" "github.com/cespare/hmux" ) func main() { b := hmux.NewBuilder() b.Get("/", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, "Hello, world!") }) b.Get("/hello/:name", func(w http.ResponseWriter, r *http.Request) { name := hmux.RequestParams(r).Get("name") fmt.Fprintf(w, "Hello, %s!\n", name) }) mux := b.Build() log.Fatal(http.ListenAndServe(":5555", mux)) }
Output:
Example (CatchAll) ¶
package main import ( "fmt" "log" "net/http" "github.com/cespare/hmux" ) func staticHandler(msg string) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, msg) } } func main() { b := hmux.NewBuilder() b.Get("/x", staticHandler("x")) // The empty pattern matches all paths and an empty method matches all // methods, so it's possible to construct a rule that catches all // requests as a fallback. This means that hmux's built-in 404 and 405 // handling will never be used. b.Handle("", "", staticHandler("caught!")) mux := b.Build() log.Fatal(http.ListenAndServe(":5555", mux)) }
Output:
Example (FileServing) ¶
package main import ( "fmt" "log" "net/http" "os" "github.com/cespare/hmux" ) func staticHandler(msg string) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, msg) } } func main() { b := hmux.NewBuilder() b.Get("/", staticHandler("Main page")) b.ServeFS("/static/", os.DirFS("static")) mux := b.Build() log.Fatal(http.ListenAndServe(":5555", mux)) }
Output:
Example (NestedMuxes) ¶
package main import ( "fmt" "log" "net/http" "github.com/cespare/hmux" ) func staticHandler(msg string) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, msg) } } func checkUser(h http.Handler) http.Handler { hf := func(w http.ResponseWriter, r *http.Request) { h.ServeHTTP(w, r) } return http.HandlerFunc(hf) } func checkAdmin(h http.Handler) http.Handler { hf := func(w http.ResponseWriter, r *http.Request) { h.ServeHTTP(w, r) } return http.HandlerFunc(hf) } func main() { adminBuilder := hmux.NewBuilder() adminBuilder.Get("/", staticHandler("Hello from the admin page")) adminBuilder.Get("/users", staticHandler("List of all users")) admin := checkAdmin(adminBuilder.Build()) b := hmux.NewBuilder() b.Get("/", staticHandler("Main page")) b.Get("/profile", staticHandler("User profile")) b.Prefix("/admin/", admin) mux := checkUser(b.Build()) log.Fatal(http.ListenAndServe(":5555", mux)) }
Output:
Index ¶
- type Builder
- func (b *Builder) Build() *Mux
- func (b *Builder) Delete(pat string, h http.HandlerFunc)
- func (b *Builder) Get(pat string, h http.HandlerFunc)
- func (b *Builder) Handle(method, pat string, h http.Handler)
- func (b *Builder) Head(pat string, h http.HandlerFunc)
- func (b *Builder) Post(pat string, h http.HandlerFunc)
- func (b *Builder) Prefix(pat string, h http.Handler)
- func (b *Builder) Put(pat string, h http.HandlerFunc)
- func (b *Builder) ServeFS(pat string, fsys fs.FS)
- func (b *Builder) ServeFile(pat, name string)
- type Mux
- type Params
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Builder ¶
type Builder struct {
// contains filtered or unexported fields
}
A Builder constructs a Mux. Rules are added to the Builder by using Handle and related helper methods (Get, Post, and so on). After all the rules have been added, Build creates the Mux which uses those rules to route incoming requests.
A Builder is intended to be used at program initialization and, as such, its methods panic on incorrect use. In particular, any method that registers a pattern (Get, Handle, ServeFile, and so on) panics if the pattern is syntactically invalid or if the rule conflicts with any previously registered rule.
func (*Builder) Build ¶
Build creates a Mux using the current rules in b. The Mux does not share state with b: future changes to b will not affect the built Mux and other Muxes may be built from b later (possibly after adding more rules).
func (*Builder) Delete ¶
func (b *Builder) Delete(pat string, h http.HandlerFunc)
Delete registers a handler for DELETE requests using the given path pattern.
func (*Builder) Get ¶
func (b *Builder) Get(pat string, h http.HandlerFunc)
Get registers a handler for GET requests using the given path pattern.
func (*Builder) Handle ¶
Handle registers a handler for the given HTTP method and path pattern. If method is the empty string, the handler is registered for all HTTP methods.
func (*Builder) Head ¶
func (b *Builder) Head(pat string, h http.HandlerFunc)
Head registers a handler for HEAD requests using the given path pattern.
func (*Builder) Post ¶
func (b *Builder) Post(pat string, h http.HandlerFunc)
Post registers a handler for POST requests using the given path pattern.
func (*Builder) Prefix ¶
Prefix registers a handler at the given prefix pattern. This is similar to calling Handle with method as "" except that the handler is called with a modified request where the matched prefix is removed from the beginning of the path.
For example, suppose this method is called as
b.Prefix("/sub", h)
Then if a request arrives with the path "/sub/x/y", the handler h sees a request with a path "/x/y".
Whether pat ends with * or not, Prefix interprets it as a wildcard pattern. So the example above would be the same whether the pattern had been given as "/sub", "/sub/", or "/sub/*".
The pattern cannot be "" or "*" when calling Prefix.
func (*Builder) Put ¶
func (b *Builder) Put(pat string, h http.HandlerFunc)
Put registers a handler for PUT requests using the given path pattern.
type Mux ¶
type Mux struct {
// contains filtered or unexported fields
}
Mux is an HTTP request multiplexer. It matches the URL path and HTTP method of each incoming request to a list of rules and calls the handler that most closely matches the request. It supplies path-based parameters named by the matched rule via the HTTP request context.
type Params ¶
type Params struct {
// contains filtered or unexported fields
}
Params are URL path segments matched by parameters and wildcards given by rule patterns registered with a Mux.
func RequestParams ¶
RequestParams retrieves the Params previously registered via matching a Mux rule. It returns nil if there are no params in the rule.
func (*Params) Get ¶
Get returns the value of a named parameter. It panics if p does not include a parameter matching the provided name.
For example, if a rule is registered as
mux.Get("/products/:name", handleProduct)
then the product name may be retrieved inside handleProduct with
p.Get("name")
Note that, by construction, a parameter value cannot be empty, so Get never returns the empty string.
func (*Params) Int ¶
Int returns the value of a named integer-typed parameter as an int. It panics if p does not include a parameter matching the provided name or if the parameter exists but does not have an integer type. If the type of the parameter is int64 and the value is larger than the maximum int on the platform, the returned value is truncated (as with any int64-to-int conversion).
For example, if a rule is registered as
mux.Get("/customers/:id:int32", handleCustomer)
then the customer ID may be retrieved as an int inside handleCustomer with
p.Int("id")
func (*Params) Int32 ¶
Int32 returns the value of a named int32-typed parameter. It panics if p does not include a parameter matching the provided name or if the parameter exists but does not have the int32 type.
For example, if a rule is registered as
mux.Get("/customers/:id:int32", handleCustomer)
then the customer ID may be retrieved inside handleCustomer with
p.Int32("id")
func (*Params) Int64 ¶
Int64 returns the value of a named integer-typed parameter as an int64. It panics if p does not include a parameter matching the provided name or if the parameter exists but does not have an integer type.
For example, if a rule is registered as
mux.Get("/posts/:id:int64", handlePost)
then the post ID may be retrieved inside handlePost with
p.Int64("id")
func (*Params) Wildcard ¶
Wildcard returns the path suffix matched by a wildcard rule. It panics if p does not contain a wildcard pattern.
For example, if a rule is registered as
mux.Get("/static/*", handleStatic)
and an incoming GET request for "/static/styles/site.css" matches this rule, then p.Wildcard() gives "styles/site.css".