hmux

package module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 17, 2022 License: MIT Imports: 10 Imported by: 1

README

hmux

Go Reference

NOTE: this package is still under active development and has not yet reached version 1.0.

This package provides an HTTP request multiplexer which matches requests to handlers using method- and path-based rules.

Here's a simple example:

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, "index")
	})
	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("localhost:8888", mux))
}

If this server is running, then:

$ curl localhost:8888/hello/alice
Hello, alice!

Consult the documentation for the details about this package and more examples.

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

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 NewBuilder

func NewBuilder() *Builder

NewBuilder creates a new Builder.

func (*Builder) Build

func (b *Builder) Build() *Mux

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

func (b *Builder) Handle(method, pat string, h http.Handler)

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

func (b *Builder) Prefix(pat string, h http.Handler)

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.

func (*Builder) ServeFS added in v0.1.0

func (b *Builder) ServeFS(pat string, fsys fs.FS)

ServeFS serves files from fsys at a prefix pattern.

Like Prefix, the pattern prefix is removed from the beginining of the path before lookup in fsys.

func (*Builder) ServeFile added in v0.1.0

func (b *Builder) ServeFile(pat, name string)

ServeFile registers GET and HEAD handlers for the given pattern that serve the named file using http.ServeFile.

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.

func (*Mux) ServeHTTP

func (m *Mux) ServeHTTP(w http.ResponseWriter, r *http.Request)

ServeHTTP implements the http.Handler interface.

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

func RequestParams(r *http.Request) *Params

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

func (p *Params) Get(name string) string

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

func (p *Params) Int(name string) 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

func (p *Params) Int32(name string) 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

func (p *Params) Int64(name string) 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

func (p *Params) Wildcard() string

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".

Directories

Path Synopsis
example module

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL