rte

package module
v0.0.2 Latest Latest
Warning

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

Go to latest
Published: Mar 9, 2019 License: MIT Imports: 5 Imported by: 0

README

rte - routing table extraordinaire

Build Status Go Report Card GoDoc Coverage Status

Simple, opinionated, performant routing.

  • Intuitive, legible interface; encourages treating routing configuration as data to be passed around and manipulated.
  • Extracted path variables are matched to type signature; handlers get to business logic faster, code is more explicit, and programming errors are surfaced before request handling time.
  • Fast AF -- completely avoids the heap during request handling; markedly faster than any other router in the go-http-routing-benchmark suite.
package main

import (
    "fmt"
    "github.com/jwilner/rte"
    "log"
    "net/http"
)

func main() {
    log.Fatal(http.ListenAndServe(":8080", rte.Must(rte.Routes(
        "/foo", rte.Routes(
            "POST", func(w http.ResponseWriter, r *http.Request) {
                // POST /foo
            },
            "/:id", rte.Routes(
                "GET", func(w http.ResponseWriter, r *http.Request, id string) {
                    // GET /foo/:id
                },
                "PUT", func(w http.ResponseWriter, r *http.Request, id string) {
                    // PUT /foo/:id
                },
                "DELETE", func(w http.ResponseWriter, r *http.Request, id string) {
                    // DELETE /foo/:id
                },
                rte.MethodAny, func(w http.ResponseWriter, r *http.Request, id string) {
                    // Match any other HTTP method on /foo/:id (e.g. to serve a 405)
                },
                "POST /bar", func(w http.ResponseWriter, r *http.Request, id string) {
                    // POST /foo/:id/bar
                },
            ),
        ),
    ))))
}

Usage

rte.Route values are passed to rte.Must or rte.New, which constructs an *rte.Table. There are plenty of examples here, but also check out the go docs.

rte.Route

The core value is the simple rte.Route struct:

route := rte.Route{Method: "GET", Path: "/health", Handler: healthHandler}

You can construct and manipulate them as you would any struct; the handler can be a standard http.Handler, http.HandlerFunc, or one of:

func(http.ResponseWriter, *http.Request, string)
func(http.ResponseWriter, *http.Request, string, string)
func(http.ResponseWriter, *http.Request, string, string, string)
func(http.ResponseWriter, *http.Request, [N]string) // where N is a number between 1 and 8

If the handler has string parameters, RTE injects any variables indicted w/in the path into function signature. For signatures of 4 or more, only array signatures are provided; arrays, rather than slices, are used to avoid heap allocations -- and to be explicit. It's a configuration error if the number of path variables doesn't match the number of function parameters (an exception is made for zero string parameter functions -- they can be used with any number of path variables).

Each struct can also be assigned middleware behavior:

route.Middleware = func(w http.ResponseWriter, r *http.Request, next http.Handler) {
    _, _ = fmt.Println(w, "Before request")
    next.ServeHttp(w, r)
    _, _ = fmt.Println(w, "After request")
}
rte.Routes constructor

When it's useful, there's an overloaded variadic constructor:

routes := rte.Routes(
    "GET /health", healthHandler,
    "GET /foo/:foo_id", func(w http.ResponseWriter, r *http.Request, fooID string) {
        _, _ = fmt.Fprintf(w, "fooID: %v", fooID)
    },
)

It can be used to construct hierarchical routes:

routes := rte.Routes(
    "/foo", rte.Routes(
        "POST", func(w http.ResponseWriter, r *http.Request) {
            // POST /foo
        },
        "/:foo_id", rte.Routes(
            "GET", func(w http.ResponseWriter, r *http.Request, fooID string) {
                // GET /foo/:foo_id
            },
            "PUT", func(w http.ResponseWriter, r *http.Request, fooID string) {
                // PUT /foo/:foo_id
            },
        ), myMiddleware
    )
)

The above is exactly equivalent to:

routes := []rte.Route {
    {
        Method: "POST", Path: "/foo",
        Handler: func(http.ResponseWriter, *http.Request) {
            // POST /foo
        },
    },
    {
        Method: "GET", Path: "/foo/:foo_id",
        Handler: func(http.ResponseWriter, *http.Request, id string) {
            // GET /foo/:foo_id
        },
        Middleware: myMiddleware
    },
    {
        Method: "PUT", Path: "/foo/:foo_id",
        Handler: func(w http.ResponseWriter, r *http.Request, id string) {
            // PUT /foo/:foo_id
        },
        Middleware: myMiddleware
    },
}

See examples_test.go or go docs for more examples.

Compiling the routing table

Zero or more routes are combined to create a Table handler using either New or Must:

var routes []rte.Route
tbl, err := rte.New(routes) // errors on misconfiguration
tbl = rte.Must(routes) // panics on misconfiguration

If you're dynamically constructing your routes, the returned rte.Error type helps you recover from misconfiguration.

*rte.Table satisfies the standard http.Handler interface and can be used with standard Go http utilities.

Extras

RTE provides a few basic values and functions to help with common patterns. Many of these functions take in a []Route and return a new, potentially modified []Route, in keeping with the design principles.

MethodAny

RTE performs wildcard matching in paths with the : syntax; it can also perform wildcard matching of methods via the use of rte.MethodAny. You can use rte.MethodAny anywhere you would a normal HTTP method; it will match any requests to the path that don't match an explicit, static method:

rte.Routes(
    // handles GETs to /
    "GET /", func(http.ResponseWriter, *http.Request){},
    // handles POST PUT, OPTIONS, etc. to /
    rte.MethodAny+" /", func(http.ResponseWriter, *http.Request){},
)
DefaultMethod

rte.DefaultMethod adds a rte.MethodAny handler to every path; useful if you want to serve 405s for all routes.

reflect.DeepEqual(
    rte.DefaultMethod(
        hndlr405,
        []rte.Route {
            {Method: "GET", Path: "/foo", Handler: fooHandler},
            {Method: "POST", Path: "/foo", Handler: postFooHandler},
            {Method: "GET", Path: "/bar", Handler: barHandler},
        },
    ),
    []rte.Route {
        {Method: "GET", Path: "/foo", Handler: fooHandler},
        {Method: rte.MethodAny, Path: "/foo", Handler: hndlr405},
        {Method: "POST", Path: "/foo", Handler: postFooHandler},
        {Method: "GET", Path: "/bar", Handler: barHandler},
        {Method: rte.MethodAny, Path: "/bar", Handler: hnldr405},
    },
)
Wrap

rte.Wrap adds middleware behavior to every contained path; if a middleware is already set, the new middleware will be wrapped around it -- so that the stack will have the new middleware at the top, the old middleware in the middle, and the handler at the bottom.

reflect.DeepEqual(
    rte.Wrap(
        myMiddleware,
        []rte.Route {
            {Method: "GET", Path: "/foo", Handler: myHandler},
        },
    ),
    []rte.Route {
        {Method: "GET", Path: "/foo", Handler: myHandler, Middleware: myMiddleware},
    },
)
OptTrailingSlash

OptTrailingSlash makes each handler also match its slashed or not-slashed version.

reflect.DeepEqual(
    rte.OptTrailingSlash(
        []rte.Route {
            {Method: "GET", Path: "/foo", Handler: myHandler},
            {Method: "POST", Path: "/bar/", Handler: barHandler},
        },
    ),
    []rte.Route {
        {Method: "GET", Path: "/foo", Handler: myHandler},
        {Method: "GET", Path: "/foo/", Handler: myHandler},
        {Method: "POST", Path: "/bar/", Handler: barHandler},
        {Method: "POST", Path: "/bar", Handler: barHandler},
    },
)
more

Check out the go docs for still more extras.

Trade-offs

It's important to note that RTE uses a fixed size array of strings for path variables paired with generated code to avoid heap allocations; currently, this number is fixed at 8, which means that RTE does not support routes with more than eight path variables (doing so will cause an error or panic). The author is deeply skeptical that anyone actually really needs more than eight path variables; that said, it's a design goal to provide support for higher numbers once the right packaging technique is found.

Performance

Modern Go routers place a lot of emphasis on speed. There's plenty of room for skepticism about this attitude, as most web application will be IO bound. Nonetheless, this is the barrier for entry to the space these days. To this end, RTE completely avoids performing zero heap allocations while serving requests and uses a relatively optimized data structure (a compressed trie) to route requests.

Benchmarks are drawn from this fork of go-http-routing-benchmark (which appears unmaintained). The benchmarks were run on a 2013 MB Pro with a 2.6 GHz i5 and 8 GB ram. For fun, RTE is compared to some of the most popular Go router layers below.

Single Param Micro Benchmark Reps ns/op B/op allocs/op
RTE 20000000 64.0 0 0
Gin 20000000 79.4 0 0
Echo 20000000 105 0 0
HttpRouter 10000000 138 32 1
Beego 1000000 1744 352 3
GorillaMux 300000 3775 1280 10
Martini 200000 6891 1072 10
Five Param Micro Benchmark Reps ns/op B/op allocs/op
RTE 20000000 116 0 0
Gin 10000000 137 0 0
Echo 5000000 253 0 0
HttpRouter 3000000 416 160 1
Beego 1000000 2036 352 3
GorillaMux 300000 5194 1344 10
Martini 200000 8091 1232 11
Github API with 1 Param Reps ns/op B/op allocs/op
RTE 10000000 156 0 0
Gin 10000000 184 0 0
Echo 5000000 266 0 0
HttpRouter 5000000 296 96 1
Beego 1000000 2018 352 3
GorillaMux 200000 10949 1296 10
Martini 100000 14957 1152 11
Google Plus API with 1 Param Reps ns/op B/op allocs/op
RTE 20000000 89.1 0 0
Gin 20000000 123 0 0
Echo 10000000 143 0 0
HttpRouter 10000000 185 64 1
Beego 1000000 1488 352 3
GorillaMux 300000 4053 1280 10
Martini 200000 6375 1072 10
Google Plus API with 2 Params Reps ns/op B/op allocs/op
RTE 10000000 139 0 0
Gin 10000000 141 0 0
HttpRouter 5000000 225 64 1
Echo 10000000 237 0 0
Beego 1000000 1646 352 3
GorillaMux 200000 8675 1296 10
Martini 100000 13586 1200 13

Design principles

simplicity

RTE attempts to follow Golang's lead by avoiding features which complicate routing behavior when a viable alternative is available to the user. For example, RTE chooses not to allow users to specify path variables with types -- e.g. {foo_id:int} -- or catch-all paths -- e.g. a /foo/* matching /foo/bar/blah. Supporting either of those features would introduce complicated precedence behavior, and simple alternatives exist for users.

Additionally, RTE aims to have defined behavior in all circumstances and to document and prove that behavior with unit tests.

limiting state space

Many modern routers coordinate by mutating a common data structure -- unsurprisingly, usually called a Router. In larger applications that pass around the routers and subrouters, setting different flags and options in different locations, the state of the router can be at best hard to reason about and at worst undefined -- it is not uncommon for certain feature combinations to fail or have unexpected results.

By centering on the simple rte.Route and not exposing any mutable state, RTE keeps its interface simple to understand, while also simplifying its own internals. Because most routing libraries focus on the mutable router object, they do not have explicit finalization, and thus their internal logic must remain open to modification at any time -- RTE does not have this problem.

When a routing feature is necessary, it will usually be added as an helper method orthogonal to the rest of the API. For example, rather than providing a method OptTrailingSlash(enabled bool) on *rte.Table and pushing complexity into the routing logic, RTE provides the pure function rte.OptTrailingSlash(routes []rte.Route) []rte.Route, which adds the new rte.Routes necessary to optionally match trailing slashes, while the routing logic remains unchanged.

reflect.DeepEqual(
    rte.OptTrailingSlash(
        []Route{
            {Method: "GET", Path: "/foo", Handler: myHandler},
        },
    ),
    []Route {
        {Method: "GET", Path: "/foo", Handler: myHandler},
        {Method: "GET", Path: "/foo/", Handler: myHandler},
    }
)

Development

Check out the Makefile for dev entrypoints.

TLDR:

  • make test
  • make test-cover
  • make gen (regenerates internal code)
  • make check (requires golint -- install with go get -u golang.org/x/lint/golint)

CI

Travis builds. In addition to tests, the build is gated on golint and whether the checked-in generated code matches the code as currently generated.

Documentation

Overview

Package rte provides simple, performant routing. - Define individual routes with `rte.Func` and generated siblings - Combine them into a table with `rte.Must` or `rte.New`

Index

Examples

Constants

View Source
const (
	// ErrTypeMethodEmpty means a route was missing a method
	ErrTypeMethodEmpty = iota + 1
	// ErrTypeNilHandler means a route had a nil handler
	ErrTypeNilHandler
	// ErrTypePathEmpty means a path was empty
	ErrTypePathEmpty
	// ErrTypeNoInitialSlash means the path was missing the initial slash
	ErrTypeNoInitialSlash
	// ErrTypeInvalidSegment means there was an invalid segment within a path
	ErrTypeInvalidSegment
	// ErrTypeOutOfRange indicates that there are more variables in the path than this version of RTE can handle
	ErrTypeOutOfRange
	// ErrTypeDuplicateHandler means more than one handler was provided for the same method and path.
	ErrTypeDuplicateHandler
	// ErrTypeConversionFailure means that the provided value can't be converted to a handler
	ErrTypeConversionFailure
	// ErrTypeParamCountMismatch means the handler doesn't match the number of variables in the path
	ErrTypeParamCountMismatch
	// ErrTypeConflictingRoutes is returned when a route would be obscured by a wildcard.
	ErrTypeConflictingRoutes
)
View Source
const (
	// MethodAny can be provided used as a method within a route to handle scenarios when the path but not
	// the method are matched.
	//
	// E.g., serve gets on '/foo/:foo_id' and return a 405 for everything else (405 handler can also access path vars):
	// 		_ = rte.Must(rte.Routes(
	// 			"GET /foo/:foo_id", handlerGet,
	// 			rte.MethodAny + " /foo/:foo_id", handler405,
	// 		))
	MethodAny = "~"
)

Variables

This section is empty.

Functions

This section is empty.

Types

type Middleware

type Middleware interface {
	Handle(w http.ResponseWriter, r *http.Request, next http.Handler)
}

Middleware is shorthand for a function which can handle or modify a request, optionally invoke the next handler (or not), and modify (or set) a response.

func Compose

func Compose(mw Middleware, mws ...Middleware) Middleware

Compose combines one or more middlewares into a single middleware. The composed middleware will proceed left to right through the middleware (and exit right to left).

func RecoveryMiddleware

func RecoveryMiddleware(log interface{ Println(...interface{}) }) Middleware

RecoveryMiddleware returns a middleware which converts any panics into 500 status http errors and stops the panic. If a non-nil log is provided, any panic will be logged.

type MiddlewareFunc

type MiddlewareFunc func(w http.ResponseWriter, r *http.Request, next http.Handler)

MiddlewareFunc is an adapter type permitting regular functions to be used as Middleware

Example
package main

import (
	"fmt"
	"github.com/jwilner/rte"
	"net/http"
	"net/http/httptest"
)

func main() {
	mw := rte.MiddlewareFunc(func(w http.ResponseWriter, r *http.Request, next http.Handler) {
		_, _ = fmt.Fprintln(w, "Hi!")
		next.ServeHTTP(w, r)
		_, _ = fmt.Fprintln(w, "Goodbye!")
	})

	tbl := rte.Must(rte.Routes(
		"GET /hello/:name", func(w http.ResponseWriter, r *http.Request, name string) {
			_, _ = fmt.Fprintf(w, "How are you, %v?\n", name)
		}, mw,
	))

	w := httptest.NewRecorder()
	tbl.ServeHTTP(w, httptest.NewRequest("GET", "/hello/bob", nil))

	fmt.Print(w.Body.String())

}
Output:

Hi!
How are you, bob?
Goodbye!

func (MiddlewareFunc) Handle

func (f MiddlewareFunc) Handle(w http.ResponseWriter, r *http.Request, next http.Handler)

Handle applies wrapping behavior to a request handler

type Route

type Route struct {
	Method, Path string
	Handler      interface{}
	Middleware   Middleware
}

Route is data for routing to a handler

func DefaultMethod

func DefaultMethod(hndlr interface{}, routes []Route) []Route

DefaultMethod adds a default method handler to any paths without one.

Example
package main

import (
	"fmt"
	"github.com/jwilner/rte"
	"net/http"
	"net/http/httptest"
)

func main() {
	hndlr := func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusMethodNotAllowed)
		_, _ = fmt.Fprintf(w, "%v %v not allowed", r.Method, r.URL.Path)
	}
	routes := rte.DefaultMethod(hndlr, rte.Routes(
		"GET /foo", func(w http.ResponseWriter, r *http.Request) {
			_, _ = fmt.Fprintf(w, "GET /foo succeeded")
		},
		"POST /bar", func(w http.ResponseWriter, r *http.Request) {
			_, _ = fmt.Fprintf(w, "POST /bar succeeded")
		},
	))

	for _, r := range routes {
		fmt.Printf("%v\n", r)
	}

	tbl := rte.Must(routes)
	{
		w := httptest.NewRecorder()
		tbl.ServeHTTP(w, httptest.NewRequest("GET", "/foo", nil))
		fmt.Println(w.Body.String())
	}
	{
		w := httptest.NewRecorder()
		tbl.ServeHTTP(w, httptest.NewRequest("PRETEND", "/foo", nil))
		fmt.Println(w.Body.String())
	}

}
Output:

GET /foo
~ /foo
POST /bar
~ /bar
GET /foo succeeded
PRETEND /foo not allowed

func OptTrailingSlash

func OptTrailingSlash(routes []Route) []Route

OptTrailingSlash ensures that the provided routes will perform the same regardless of whether or not they have a trailing slash.

Example
package main

import (
	"fmt"
	"github.com/jwilner/rte"
	"net/http"
)

func main() {
	routes := rte.OptTrailingSlash(rte.Routes(
		"GET /", func(w http.ResponseWriter, r *http.Request) {
			_, _ = fmt.Fprintln(w, "hello world!")
		},
		"GET /:name", func(w http.ResponseWriter, r *http.Request, name string) {
			_, _ = fmt.Fprintf(w, "hello %v!\n", name)
		},
	))

	for _, r := range routes {
		fmt.Printf("%v\n", r)
	}

}
Output:

GET /
GET /:name
GET /:name/

func Prefix

func Prefix(prefix string, routes []Route) []Route

Prefix adds the given prefix to all of the contained routes; no verification is performed of e.g. leading slashes

Example
package main

import (
	"fmt"
	"github.com/jwilner/rte"
	"net/http"
)

func main() {
	routes := rte.Prefix("/hello", rte.Routes(
		"GET /", func(w http.ResponseWriter, r *http.Request) {
			_, _ = fmt.Fprintln(w, "hello")
		},
	))

	for _, r := range routes {
		fmt.Printf("%v\n", r)
	}

}
Output:

GET /hello/

func Routes

func Routes(is ...interface{}) []Route

Routes is a vanity constructor for constructing literal routing tables. It enforces types at runtime. An invocation can be zero or more combinations. Each combination can be one of: - nil - "METHOD", handler - "METHOD PATH", handler - "PATH", handler - "", handler - "METHOD", handler, middleware - "METHOD PATH", handler, middleware - "PATH", handler, middleware - "", handler, middleware - Route - []Route - "PATH", []Route (identical to rte.Prefix("PATH", routes)) - "PATH", []Route, middleware (identical to rte.Wrap(rte.Prefix("PATH", routes), middleware))

Example
package main

import (
	"fmt"
	"github.com/jwilner/rte"
	"net/http"
)

func main() {
	routes := rte.Routes(
		"/my-resource", rte.Routes(
			"POST", func(w http.ResponseWriter, r *http.Request) {
				// create
			},
			"/:id", rte.Routes(
				"GET", func(w http.ResponseWriter, r *http.Request, id string) {
					// read
				},
				"PUT", func(w http.ResponseWriter, r *http.Request, id string) {
					// update
				},
				"DELETE", func(w http.ResponseWriter, r *http.Request, id string) {
					// delete
				},
				rte.MethodAny, func(w http.ResponseWriter, r *http.Request, id string) {
					// serve a 405
				},
			),
		),
	)

	for _, r := range routes {
		fmt.Printf("%v\n", r)
	}

}
Output:

POST /my-resource
GET /my-resource/:id
PUT /my-resource/:id
DELETE /my-resource/:id
~ /my-resource/:id
Example (Second)
mw := stringMW("abc")
rts := rte.Routes(
	nil,

	"GET", func(w http.ResponseWriter, r *http.Request) {},
	"GET /", func(w http.ResponseWriter, r *http.Request) {},
	"/", func(w http.ResponseWriter, r *http.Request) {},
	"", func(w http.ResponseWriter, r *http.Request) {},

	"GET", func(w http.ResponseWriter, r *http.Request) {}, mw,
	"GET /", func(w http.ResponseWriter, r *http.Request) {}, mw,
	"/", func(w http.ResponseWriter, r *http.Request) {}, mw,
	"", func(w http.ResponseWriter, r *http.Request) {}, mw,

	rte.Route{Method: "OPTIONS", Path: "/bob"},
	[]rte.Route{
		{Method: "OPTIONS", Path: "/bob"},
		{Method: "BLAH", Path: "/jane"},
	},

	"/pre", []rte.Route{
		{Method: "GET", Path: "/bob"},
		{Method: "POST", Path: "/bob/hi"},
	},
	"/pre2", []rte.Route{
		{Method: "GET", Path: "/bob"},
		{Method: "POST", Path: "/bob/hi"},
	}, mw,
)

for _, r := range rts {
	fmt.Println(r)
}
Output:

GET <nil>
GET /
<nil> /
<nil> <nil>
GET <nil>
GET /
<nil> /
<nil> <nil>
OPTIONS /bob
OPTIONS /bob
BLAH /jane
GET /pre/bob
POST /pre/bob/hi
GET /pre2/bob
POST /pre2/bob/hi
Example (Third)
package main

import (
	"fmt"
	"github.com/jwilner/rte"
	"net/http"
	"net/http/httptest"
)

func main() {
	rts := rte.Routes(
		"GET /boo", func(w http.ResponseWriter, r *http.Request) {
			_, _ = fmt.Fprintln(w, "boo")
		},
	)

	var withSlashRedirects []rte.Route
	for _, route := range rts {
		target := route.Path

		c := route
		c.Path += "/"
		c.Handler = func(w http.ResponseWriter, r *http.Request) {
			http.Redirect(w, r, target, http.StatusMovedPermanently)
		}

		withSlashRedirects = append(withSlashRedirects, route, c)
	}

	tbl := rte.Must(withSlashRedirects)

	w := httptest.NewRecorder()
	tbl.ServeHTTP(w, httptest.NewRequest("GET", "/boo/", nil))
	fmt.Printf("%v %v", w.Code, w.Header().Get("Location"))

}
Output:

301 /boo

func Wrap

func Wrap(mw Middleware, routes []Route) []Route

Wrap registers a middleware across all provide routes. If a middleware is already set, that middleware will be invoked second.

Example
// applied to the one
m1 := stringMW("and this is m1")
// applied to both
m2 := stringMW("this is m2")

tbl := rte.Must(rte.Wrap(m2, rte.Routes(
	"GET /", func(w http.ResponseWriter, r *http.Request) {
		_, _ = fmt.Fprintf(w, "handling GET /\n")
	}, m1,
	"POST /", func(w http.ResponseWriter, r *http.Request) {
		_, _ = fmt.Fprintf(w, "handling POST /\n")
	},
)))

{
	w := httptest.NewRecorder()
	tbl.ServeHTTP(w, httptest.NewRequest("GET", "/", nil))
	fmt.Print(w.Body.String())
}

{
	w := httptest.NewRecorder()
	tbl.ServeHTTP(w, httptest.NewRequest("POST", "/", nil))
	fmt.Print(w.Body.String())
}
Output:

this is m2
and this is m1
handling GET /
this is m2
handling POST /

func (Route) String

func (r Route) String() string

type Table

type Table struct {
	Default http.Handler
	// contains filtered or unexported fields
}

Table manages the routing table and a default handler

func Must

func Must(routes []Route) *Table

Must builds routes into a Table and panics if there's an error

Example
package main

import (
	"fmt"
	"github.com/jwilner/rte"
	"net/http"
)

func main() {
	defer func() {
		p := recover()
		fmt.Printf("panicked! %v\n", p)
	}()

	_ = rte.Must(rte.Routes(
		"GET /hello/:name", func(w http.ResponseWriter, r *http.Request, a, b string) {
		},
	))

}
Output:

panicked! route 0 "GET /hello/:name": path and handler have different numbers of parameters

func New

func New(routes []Route) (*Table, error)

New builds routes into a Table or returns an error

Example
package main

import (
	"fmt"
	"github.com/jwilner/rte"
	"net/http"
)

func main() {
	_, err := rte.New(rte.Routes(
		"GET /hello/:name", func(w http.ResponseWriter, r *http.Request, a, b string) {
		},
	))

	fmt.Printf("errored! %v", err)

}
Output:

errored! route 0 "GET /hello/:name": path and handler have different numbers of parameters

func (*Table) ServeHTTP

func (t *Table) ServeHTTP(w http.ResponseWriter, r *http.Request)
Example
package main

import (
	"fmt"
	"github.com/jwilner/rte"
	"net/http"
	"net/http/httptest"
)

func main() {
	tbl := rte.Must(rte.Routes(
		"GET /hello/:name", func(w http.ResponseWriter, r *http.Request, name string) {
			_, _ = fmt.Fprintf(w, "Hello %v!\n", name)
		},
	))

	w := httptest.NewRecorder()
	tbl.ServeHTTP(w, httptest.NewRequest("GET", "/hello/bob", nil))

	fmt.Print(w.Body.String())

}
Output:

Hello bob!

func (*Table) Vars

func (t *Table) Vars(r *http.Request) ([]string, bool)

Vars reparses the request URI and returns any matched variables and whether or not there was a route matched.

Example
package main

import (
	"fmt"
	"github.com/jwilner/rte"
	"net/http"
	"net/http/httptest"
)

func main() {
	var tbl *rte.Table
	tbl = rte.Must(rte.Routes(
		"GET /:a/:b", func(w http.ResponseWriter, r *http.Request) { // zero params can match any path
			vars, _ := tbl.Vars(r)
			for _, v := range vars {
				_, _ = fmt.Println(v)
			}
		},
	))

	tbl.ServeHTTP(httptest.NewRecorder(), httptest.NewRequest("GET", "/abc/def", nil))

}
Output:

abc
def

type TableError

type TableError struct {
	Type, Idx int
	Route     Route
	Msg       string
}

TableError encapsulates table construction errors

Example
package main

import (
	"fmt"
	"github.com/jwilner/rte"
	"net/http"
)

func main() {
	_, err := rte.New(rte.Routes(
		"GET /hello", func(w http.ResponseWriter, r *http.Request) {
		},
		"GET /hello/:name", func(w http.ResponseWriter, r *http.Request, a, b string) {
		},
	))

	_, _ = fmt.Printf("%v", err.(*rte.TableError).Route)

}
Output:

GET /hello/:name

func (*TableError) Error

func (e *TableError) Error() string

Directories

Path Synopsis
internal
funcs
Code generated by cmd/rte-gen/gen.go DO NOT EDIT.
Code generated by cmd/rte-gen/gen.go DO NOT EDIT.

Jump to

Keyboard shortcuts

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