gemux

package module
v0.2.4 Latest Latest
Warning

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

Go to latest
Published: Jun 28, 2020 License: MIT Imports: 3 Imported by: 0

README

Good Enough Multiplexer (gemux)

pkg.go.dev

gemux is the good enough multiplexer. It aims to provide functionality that is good enough for the majority of HTTP services, with a focus on a small and easy to test codebase, fair performance, and no dependencies outside the standard library.

Usage

package main

func main() {
    mux := new(gemux.ServeMux)

    mux.Handle("/", http.MethodGet, http.HandlerFunc(healthHandler))
    mux.Handle("/posts", http.MethodGet, http.HandlerFunc(getPostsHandler))
    mux.Handle("/posts", http.MethodPost, http.HandlerFunc(createPostHandler))
    mux.Handle("/posts/*", http.MethodDelete, http.HandlerFunc(deletePostHandler))
    mux.Handle("/posts/*/comments", http.MethodPost, http.HandlerFunc(createCommentHandler))
    mux.Handle("/posts/*/comments", http.MethodGet, http.HandlerFunc(getCommentsHandler))
    mux.Handle("/posts/*/comments/*", http.MethodDelete, http.HandlerFunc(deleteCommentHandler))

    http.ListenAndServe(":8080", mux)
}

Features

Strict Path Based Routing (with wildcards)

Route strictly based on paths, but allow wildcards for path parameters such as a resource ID.

mux.Handle("/users", http.MethodPost, http.HandlerFunc(getUsersHandler))
mux.Handle("/posts", http.MethodPost, http.HandlerFunc(createPostHandler))
mux.Handle("/posts/*", http.MethodGet, http.HandlerFunc(getPostHandler))
Strict Method Based Routing (with wildcards)

Route based on methods, and allow wildcard methods if you need to write your own method multiplexer, or want to match on any method.

mux.Handle("/users", http.MethodGet, http.HandlerFunc(createPostHandler)) // implement your own method muxer
mux.Handle("/posts", "*", http.HandlerFunc(createPostHandler)) // implement your own method muxer
Context Path Parameters

Extract path wildcard values via the request context.

func getPostHandler(w http.ResponseWriter, r *http.Request) {
    rawPostID := gemux.PathParameter(r.Context(), 0)
    postID, err := strconv.ParseInt(rawPostID, 10, 64)
    if err != nil {
        http.Error(w, err.Error(), http.StatusBadRequest)
        return
    }

    // ...
}
Custom Error Handlers

Create custom error handlers for when a route or method isn't found.

mux := new(gemux.ServeMux)
mux.NotFoundHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    w.WriteHeader(http.StatusNotFound)
    json.NewEncoder(w).Encode(map[string]string{"error": "not found"})
})
mux.MethodNotAllowedHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    w.WriteHeader(http.StatusMethodNotAllowed)
    json.NewEncoder(w).Encode(map[string]string{"error": "method not allowed"})
})

Benchmarks

Performed on a Dell XPS 13 with an Intel(R) Core(TM) i7-8550U CPU @ 1.80GHz. gemux is fast enough that it's performance impact is negligible for most HTTP services.

goos: linux
goarch: amd64
pkg: github.com/fharding1/gemux
BenchmarkServeHTTP/one_static_path-8         	 7071818	       159 ns/op
BenchmarkServeHTTP/one_wildcard_path-8       	 2432485	       516 ns/op
BenchmarkServeHTTP/one_wildcard_path_and_method-8         	 2541954	       453 ns/op
BenchmarkServeHTTP/short_path_with_many_routes-8          	 6360070	       187 ns/op
BenchmarkServeHTTP/very_deep_static_path-8                	 1856644	       644 ns/op
BenchmarkServeHTTP/very_deep_wildcard_path-8              	  499780	      2262 ns/op
BenchmarkHandle/one_static_path-8                         	 6987087	       170 ns/op
BenchmarkHandle/one_wildcard_path-8                       	 8136362	       151 ns/op
BenchmarkHandle/one_wildcard_path_and_method-8            	 8723088	       139 ns/op
BenchmarkHandle/short_path_with_many_routes-8             	  191238	      6234 ns/op
BenchmarkHandle/very_deep_static_path-8                   	 1626010	       694 ns/op
BenchmarkHandle/very_deep_wildcard_path-8                 	 2015136	       616 ns/op
PASS
ok  	github.com/fharding1/gemux	18.140s

Documentation

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func MethodNotAllowedHandler

func MethodNotAllowedHandler() http.Handler

MethodNotAllowedHandler returns a simple request handler that replies to each request with a "405 method not allowed" reply and writes the 405 status code.

func PathParameter added in v0.2.0

func PathParameter(ctx context.Context, n int) string

PathParameter returns the nth path parameter from the request context. It returns an empty string if no value exists at the given index.

Example
mux := new(ServeMux)

handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
	ctx := r.Context()
	fmt.Fprintln(w, PathParameter(ctx, 0))
	fmt.Fprintln(w, PathParameter(ctx, 1))
})

mux.Handle("/foo/*/bar/*", http.MethodGet, handler)

rw := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/foo/test/bar/92", nil)
mux.ServeHTTP(rw, req)
fmt.Println(rw.Body.String())
Output:

test
92

Types

type ServeMux

type ServeMux struct {

	// NotFoundHandler is called when there is no path corresponding to
	// the request URL. If NotFoundHandler is nil, http.NotFoundHandler
	// will be used.
	NotFoundHandler http.Handler

	// MethodNotAllowedHandler is called when there is no method corresponding
	// to the request URL. If MethodNotAllowedHandler is nil, MethodNotAllowedHandler
	// will be used.
	MethodNotAllowedHandler http.Handler
	// contains filtered or unexported fields
}

ServeMux is an HTTP request multiplexer. It matches the URL and method of the incoming request against a list of registered routes, and calls the matching route.

Example
mux := new(ServeMux)

mux.Handle("/", http.MethodGet, stringHandler("health check"))
mux.Handle("/posts", http.MethodGet, stringHandler("create post"))
mux.Handle("/posts", http.MethodGet, stringHandler("get posts"))
mux.Handle("/posts/*", http.MethodGet, stringHandler("get post"))
mux.Handle("/posts/*/comments", http.MethodGet, stringHandler("get post comments"))

rw := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/", nil)
mux.ServeHTTP(rw, req)
fmt.Println(rw.Body.String())

rw = httptest.NewRecorder()
req, _ = http.NewRequest("GET", "/posts/4", nil)
mux.ServeHTTP(rw, req)
fmt.Println(rw.Body.String())

rw = httptest.NewRecorder()
req, _ = http.NewRequest("GET", "/posts/5/comments", nil)
mux.ServeHTTP(rw, req)
fmt.Println(rw.Body.String())
Output:

health check
get post
get post comments

func (*ServeMux) Handle

func (mux *ServeMux) Handle(pattern string, method string, handler http.Handler)

Handle registers a handler for the given pattern and method on the muxer. The pattern should be the exact URL to match, with the exception of wildcards ("*"), which can be used for a single segment of a path (split on "/") to match anything. A wildcard method of "*" can also be used to match any method.

func (*ServeMux) ServeHTTP

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

ServeHTTP dispatches the request to the handler whose pattern and method matches the request URL and method.

Jump to

Keyboard shortcuts

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