mux

package module
v0.0.4 Latest Latest
Warning

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

Go to latest
Published: Mar 19, 2020 License: BSD-2-Clause Imports: 7 Imported by: 0

README

mux

GoDoc todo.sr.ht

Package mux is a Go HTTP multiplexer that provided typed route parameters.

import (
	"code.soquee.net/mux"
)

License

The package may be used under the terms of the BSD 2-Clause License a copy of which may be found in the LICENSE file.

Unless you explicitly state otherwise, any contribution submitted for inclusion in the work by you shall be licensed as above, without any additional terms or conditions.

Documentation

Overview

Package mux is a fast and safe HTTP request multiplexer.

The multiplexer in this package is capable of routing based on request method and a fixed rooted path (/favicon.ico) or subtree (/images/) which may include typed path parameters and wildcards.

serveMux := mux.New(
	mux.Handle(http.MethodGet, "/profile/{username string}", http.NotFoundHandler()),
	mux.HandleFunc(http.MethodGet, "/profile", http.RedirectHandler("/profile/me", http.StatusPermanentRedirect)),
	mux.Handle(http.MethodPost, "/logout", logoutHandler()),
)

URL Parameters

Routes registered on the multiplexer may contain variable path parameters that comprise an optional name, followed by a type.

/user/{id int}/edit

Valid types include:

int    eg. -1, 1 (int64 in Go)
uint   eg. 0, 1 (uint64 in Go)
float  eg. 1, 1.123, -1.123 (float64 in Go)
string eg. anything ({string} is the same as {})
path   eg. files/123.png (must be the last path component)

All numeric types are 64 bits wide. Parameters of type "path" match the remainder of the input path and therefore may only appear as the final component of a route:

/file/{p path}

Two paths with different typed variable parameters (including static routes) in the same position are not allowed. Attempting to register any two of the following routes will panic:

/user/{a int}/new
/user/{b int}/edit
/user/{float}/edit
/user/{b string}/edit
/user/me

This is to prevent a common class of bug where a static route conflicts with a path parameter and it is not clear which should be selected. For example, if the route /users/new and /users/{username string} could be registered at the same time and someone attempts to register the user "new", it might make the new user page impossible to visit, or break the new users profile. Disallowing conflicting routes keeps things simple and eliminates this class of issues.

When a route is matched, the value of each named path parameter is stored on the request context. To retrieve the value of named path parameters from within a handler, the Param function can be used.

pinfo := mux.Param(req, "username")
fmt.Println("Got username:", pinfo.Raw)

For more information, see the ParamInfo type and the examples.

Normalization

It's common to normalize routes on HTTP servers. For example, a username may need to match the Username Case Mapped profile of PRECIS (RFC 8265), or the name of an identifier may need to always be lower cased. This can be tedious and error prone even if you have enough information to figure out what path components need to be replaced, and many HTTP routers don't even give you enough information to match path components to their original route parameters. To make this easier, this package provides the Path and WithParam functions. WithParam is used to attach a new context to the context tree with replacement values for existing route parameters, and Path is used to re-render the path from the original route using the request context. If the resulting path is different from req.URL.Path, a redirect can be issued or some other corrective action can be applied.

serveMux := mux.New(
	mux.HandleFunc(http.MethodGet, "/profile/{username string}", func(w http.ResponseWriter, r *http.Request) {
		username := mux.Param(r, "username")
		// golang.org/x/text/secure/precis
		normalized, err := precis.UsernameCaseMapped.String(username.Raw)
		if err != nil {
				…
		}

		if normalized != username.Raw {
			r = mux.WithParam(r, username.Name, normalized)
			newPath, err := mux.Path(r)
			if err != nil {
				…
			}
			http.Redirect(w, r, newPath, http.StatusPermanentRedirect)
			return
		}

		fmt.Fprintln(w, "The canonical username is:", username.Raw)
	}),
)

For more information, see the examples.

Example (Normalization)
package main

import (
	"fmt"
	"io"
	"net/http"
	"net/http/httptest"
	"os"
	"strings"

	"code.soquee.net/mux"
)

func main() {
	serveMux := mux.New(
		mux.HandleFunc("GET", "/profile/{username string}/personal", func(w http.ResponseWriter, r *http.Request) {
			username := mux.Param(r, "username")
			// You probably want to use the Username Case Mapped profile from the
			// golang.org/x/text/secure/precis package instead.
			normalized := strings.ToLower(username.Raw)

			// If the username is not canonical, redirect.
			if normalized != username.Raw {
				r = mux.WithParam(r, username.Name, normalized)
				newPath, err := mux.Path(r)
				if err != nil {
					panic(fmt.Errorf("mux_test: error creating canonicalized path: %w", err))
				}
				http.Redirect(w, r, newPath, http.StatusPermanentRedirect)
				return
			}

			// Show the users profile.
			fmt.Fprintf(w, "Profile for the user %q", username.Raw)
		}),
	)

	server := httptest.NewServer(serveMux)
	defer server.Close()

	resp, err := http.Get(server.URL + "/profile/Me/personal")
	if err != nil {
		panic(err)
	}
	defer resp.Body.Close()

	io.Copy(os.Stdout, resp.Body)
}
Output:

Profile for the user "me"
Example (Path)
package main

import (
	"crypto/sha256"
	"fmt"
	"io"
	"net/http"
	"net/http/httptest"
	"os"

	"code.soquee.net/mux"
)

func main() {
	serveMux := mux.New(
		mux.HandleFunc("GET", "/sha256/{wildcard path}", func(w http.ResponseWriter, r *http.Request) {
			val := mux.Param(r, "wildcard")
			sum := sha256.Sum256([]byte(val.Raw))
			fmt.Fprintf(w, "the hash of %q is %x", val.Raw, sum)
		}),
	)

	server := httptest.NewServer(serveMux)
	resp, err := http.Get(server.URL + "/sha256/a/b")
	if err != nil {
		panic(err)
	}
	defer resp.Body.Close()

	io.Copy(os.Stdout, resp.Body)
}
Output:

the hash of "a/b" is c14cddc033f64b9dea80ea675cf280a015e672516090a5626781153dc68fea11

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func Path added in v0.0.4

func Path(r *http.Request) (string, error)

Path returns the request path by applying the route parameters found in the context to the route used to match the given request. This value may be different from r.URL.Path if some form of normalization has been applied to a route parameter, in which case the user may choose to issue a redirect to the canonical path.

func WithParam added in v0.0.4

func WithParam(r *http.Request, name, val string) *http.Request

WithParam returns a shallow copy of r with a new context that shadows the given route parameter. If the parameter does not exist, the original request is returned unaltered.

Because WithParam is used to normalize request parameters after the route has already been resolved, all replaced parameters are of type string.

Types

type Option

type Option func(*ServeMux)

Option is used to configure a ServeMux.

func Handle

func Handle(method, r string, h http.Handler) Option

Handle registers the handler for the given pattern. If a handler already exists for pattern, Handle panics.

func HandleFunc

func HandleFunc(method, r string, h http.HandlerFunc) Option

HandleFunc registers the handler for the given pattern. If a handler already exists for pattern, Handle panics.

func MethodNotAllowed added in v0.0.2

func MethodNotAllowed(h http.Handler) Option

MethodNotAllowed sets the default handler to call when a path is matched to a route, but there is no handler registered for the specific method.

By default, http.Error with http.StatusMethodNotAllowed is used.

func NotFound

func NotFound(h http.Handler) Option

NotFound sets the handler to use when a request does not have a registered route.

If the provided handler does not set the status code, it is set to 404 (Page Not Found) by default instead of 200. If the provided handler explicitly sets the status by calling "http.ResponseWriter".WriteHeader, that status code is used instead.

func Options added in v0.0.2

func Options(f func([]string) http.Handler) Option

Options changes the ServeMux's default OPTIONS request handling behavior. If you do not want options handling by default, set f to "nil".

Registering handlers for OPTIONS requests on a specific path always overrides the default handler.

type ParamInfo added in v0.0.2

type ParamInfo struct {
	// The parsed value of the parameter (for example int64(10))
	// If and only if no such parameter existed on the route, Value will be nil.
	Value interface{}
	// The raw value of the parameter (for example "10")
	Raw string
	// The name of the route component that the parameter was matched against (for
	// example "name" in "{name int}")
	Name string
	// Type type of the route component that the parameter was matched against
	// (for example "int" in "{name int}")
	Type string
	// contains filtered or unexported fields
}

ParamInfo represents a route parameter and related metadata.

func Param

func Param(r *http.Request, name string) ParamInfo

Param returns the named route parameter from the requests context.

type ServeMux

type ServeMux struct {
	// contains filtered or unexported fields
}

ServeMux is an HTTP request multiplexer. It matches the URL of each incoming request against a list of registered patterns and calls the handler for the pattern that most closely matches the URL.

func New

func New(opts ...Option) *ServeMux

New allocates and returns a new ServeMux.

func (*ServeMux) Handler

func (mux *ServeMux) Handler(r *http.Request) (http.Handler, *http.Request)

Handler returns the handler to use for the given request, consulting r.URL.Path. It always returns a non-nil handler and request.

The path used is unchanged for CONNECT requests.

If there is no registered handler that applies to the request, Handler returns a page not found handler. If a new request is returned it uses a context that contains any route parameters that were matched against the request path.

func (*ServeMux) ServeHTTP

func (mux *ServeMux) ServeHTTP(w http.ResponseWriter, r *http.Request)

ServeHTTP dispatches the request to the handler whose pattern most closely matches the request URL.

Jump to

Keyboard shortcuts

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