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 ¶
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 )
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 /
type Table ¶
Table manages the routing table and a default handler
func Must ¶
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 ¶
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 ¶
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 ¶
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