bunrouter

package module
v1.0.1 Latest Latest
Warning

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

Go to latest
Published: Oct 15, 2021 License: MIT Imports: 9 Imported by: 115

README

Fast and flexible HTTP router for Go

build workflow PkgGoDev Documentation Chat

Features:

  • Flexible routing. Routing rules are compatible with httprouter and have proper matching priority.
  • Performant matching. BunRouter is very fast and does zero allocations when matching routes or retrieving parameters.
  • Compatible API. Out-of-the box works with standard http.HandlerFunc and httprouter-like handlers.
  • Middlewares. Extract common operations from your handlers into reusable wrapper functions (middlewares).
  • Error handling. Just return errors from route handlers and handle all of them from a single middleware.
  • Auto-correction. Bunrouter redirects users to the right route in case of extra/missing/double slashes.

Learn:

Examples:

Benchmark results
BenchmarkGin_Param               	16019718	        74.16 ns/op	       0 B/op	       0 allocs/op
BenchmarkHttpRouter_Param        	12560001	        95.04 ns/op	      32 B/op	       1 allocs/op
BenchmarkBunrouter_Param         	50015306	        23.81 ns/op	       0 B/op	       0 allocs/op
BenchmarkGin_Param5              	 8997234	       131.5 ns/op	       0 B/op	       0 allocs/op
BenchmarkHttpRouter_Param5       	 4809441	       261.3 ns/op	     160 B/op	       1 allocs/op
BenchmarkBunrouter_Param5        	10789635	       114.0 ns/op	       0 B/op	       0 allocs/op
BenchmarkGin_Param20             	 3953041	       302.4 ns/op	       0 B/op	       0 allocs/op
BenchmarkHttpRouter_Param20      	 1661373	       743.3 ns/op	     640 B/op	       1 allocs/op
BenchmarkBunrouter_Param20       	 2462354	       482.8 ns/op	       0 B/op	       0 allocs/op
BenchmarkGin_ParamWrite          	 9258986	       128.0 ns/op	       0 B/op	       0 allocs/op
BenchmarkHttpRouter_ParamWrite   	 9908178	       123.0 ns/op	      32 B/op	       1 allocs/op
BenchmarkBunrouter_ParamWrite    	15511226	        70.62 ns/op	       0 B/op	       0 allocs/op
BenchmarkGin_GithubStatic        	12781513	        94.17 ns/op	       0 B/op	       0 allocs/op
BenchmarkHttpRouter_GithubStatic 	30077443	        37.36 ns/op	       0 B/op	       0 allocs/op
BenchmarkBunrouter_GithubStatic  	37160334	        32.41 ns/op	       0 B/op	       0 allocs/op
BenchmarkGin_GithubParam         	 6971791	       169.2 ns/op	       0 B/op	       0 allocs/op
BenchmarkHttpRouter_GithubParam  	 5464755	       217.4 ns/op	      96 B/op	       1 allocs/op
BenchmarkBunrouter_GithubParam   	12047902	       101.2 ns/op	       0 B/op	       0 allocs/op
BenchmarkGin_GithubAll           	   32758	     37382 ns/op	       0 B/op	       0 allocs/op
BenchmarkHttpRouter_GithubAll    	   27324	     43932 ns/op	   13792 B/op	     167 allocs/op
BenchmarkBunrouter_GithubAll     	   57910	     20914 ns/op	       0 B/op	       0 allocs/op
BenchmarkGin_GPlusStatic         	17788194	        69.13 ns/op	       0 B/op	       0 allocs/op
BenchmarkHttpRouter_GPlusStatic  	60191341	        19.84 ns/op	       0 B/op	       0 allocs/op
BenchmarkBunrouter_GPlusStatic   	87114368	        14.06 ns/op	       0 B/op	       0 allocs/op
BenchmarkGin_GPlusParam          	10075399	       119.5 ns/op	       0 B/op	       0 allocs/op
BenchmarkHttpRouter_GPlusParam   	 8272046	       149.2 ns/op	      64 B/op	       1 allocs/op
BenchmarkBunrouter_GPlusParam    	37359979	        32.43 ns/op	       0 B/op	       0 allocs/op
BenchmarkGin_GPlus2Params        	 7375279	       162.9 ns/op	       0 B/op	       0 allocs/op
BenchmarkHttpRouter_GPlus2Params 	 6538942	       186.7 ns/op	      64 B/op	       1 allocs/op
BenchmarkBunrouter_GPlus2Params  	19681939	        61.51 ns/op	       0 B/op	       0 allocs/op
BenchmarkGin_GPlusAll            	  647716	      1752 ns/op	       0 B/op	       0 allocs/op
BenchmarkHttpRouter_GPlusAll     	  590356	      2085 ns/op	     640 B/op	      11 allocs/op
BenchmarkBunrouter_GPlusAll      	 1685287	       712.8 ns/op	       0 B/op	       0 allocs/op
BenchmarkGin_ParseStatic         	14566458	        76.58 ns/op	       0 B/op	       0 allocs/op
BenchmarkHttpRouter_ParseStatic  	52994076	        21.02 ns/op	       0 B/op	       0 allocs/op
BenchmarkBunrouter_ParseStatic   	50583933	        23.83 ns/op	       0 B/op	       0 allocs/op
BenchmarkGin_ParseParam          	13443874	        90.66 ns/op	       0 B/op	       0 allocs/op
BenchmarkHttpRouter_ParseParam   	 8825664	       135.6 ns/op	      64 B/op	       1 allocs/op
BenchmarkBunrouter_ParseParam    	38058278	        31.33 ns/op	       0 B/op	       0 allocs/op
BenchmarkGin_Parse2Params        	10179813	       118.1 ns/op	       0 B/op	       0 allocs/op
BenchmarkHttpRouter_Parse2Params 	 7801735	       152.9 ns/op	      64 B/op	       1 allocs/op
BenchmarkBunrouter_Parse2Params  	23704574	        50.78 ns/op	       0 B/op	       0 allocs/op
BenchmarkGin_ParseAll            	  394884	      3073 ns/op	       0 B/op	       0 allocs/op
BenchmarkHttpRouter_ParseAll     	  410238	      3011 ns/op	     640 B/op	      16 allocs/op
BenchmarkBunrouter_ParseAll      	  810908	      1487 ns/op	       0 B/op	       0 allocs/op
BenchmarkGin_StaticAll           	   50658	     23699 ns/op	       0 B/op	       0 allocs/op
BenchmarkHttpRouter_StaticAll    	  105313	     11518 ns/op	       0 B/op	       0 allocs/op
BenchmarkBunrouter_StaticAll     	   99674	     12188 ns/op	       0 B/op	       0 allocs/op

Quickstart

Install:

go get github.com/uptrace/bunrouter

Run the example:

package main

import (
	"html/template"
	"log"
	"net/http"

	"github.com/uptrace/bunrouter"
	"github.com/uptrace/bunrouter/extra/reqlog"
)

func main() {
	router := bunrouter.New(
		bunrouter.WithMiddleware(reqlog.NewMiddleware()),
	)

	router.GET("/", indexHandler)

	router.WithGroup("/api", func(g *bunrouter.Group) {
		g.GET("/users/:id", debugHandler)
		g.GET("/users/current", debugHandler)
		g.GET("/users/*path", debugHandler)
	})

	log.Println("listening on http://localhost:9999")
	log.Println(http.ListenAndServe(":9999", router))
}

func indexHandler(w http.ResponseWriter, req bunrouter.Request) error {
	return indexTemplate().Execute(w, nil)
}

func debugHandler(w http.ResponseWriter, req bunrouter.Request) error {
	return bunrouter.JSON(w, bunrouter.H{
		"route":  req.Route(),
		"params": req.Params().Map(),
	})
}

var indexTmpl = `
<html>
  <h1>Welcome</h1>
  <ul>
    <li><a href="/api/users/123">/api/users/123</a></li>
    <li><a href="/api/users/current">/api/users/current</a></li>
    <li><a href="/api/users/foo/bar">/api/users/foo/bar</a></li>
  </ul>
</html>
`

func indexTemplate() *template.Template {
	return template.Must(template.New("index").Parse(indexTmpl))
}

See the documentation for details.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func CleanPath

func CleanPath(p string) string

CleanPath is the URL version of path.Clean, it returns a canonical URL path for p, eliminating . and .. elements.

The following rules are applied iteratively until no further processing can be done:

  1. Replace multiple slashes with a single slash.
  2. Eliminate each . path name element (the current directory).
  3. Eliminate each inner .. path name element (the parent directory) along with the non-.. element that precedes it.
  4. Eliminate .. elements that begin a rooted path: that is, replace "/.." by "/" at the beginning of a path.

If the result of this process is an empty string, "/" is returned

func JSON

func JSON(w http.ResponseWriter, value interface{}) error

JSON marshals the value as JSON and writes it to the response writer.

Don't hesitate to copy-paste this function to your project and customize it as necessary.

func Version

func Version() string

Version is the current release version.

Types

type CompatGroup

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

CompatGroup is like Group, but it works with http.HandlerFunc instead of bunrouter handler.

func (CompatGroup) DELETE

func (g CompatGroup) DELETE(path string, handler http.HandlerFunc)

func (CompatGroup) GET

func (g CompatGroup) GET(path string, handler http.HandlerFunc)

func (CompatGroup) HEAD

func (g CompatGroup) HEAD(path string, handler http.HandlerFunc)

func (CompatGroup) Handle

func (g CompatGroup) Handle(method string, path string, handler http.HandlerFunc)

func (CompatGroup) NewGroup

func (g CompatGroup) NewGroup(path string, opts ...GroupOption) *CompatGroup

func (CompatGroup) OPTIONS

func (g CompatGroup) OPTIONS(path string, handler http.HandlerFunc)

func (CompatGroup) PATCH

func (g CompatGroup) PATCH(path string, handler http.HandlerFunc)

func (CompatGroup) POST

func (g CompatGroup) POST(path string, handler http.HandlerFunc)

func (CompatGroup) PUT

func (g CompatGroup) PUT(path string, handler http.HandlerFunc)

func (CompatGroup) WithGroup

func (g CompatGroup) WithGroup(path string, fn func(g *CompatGroup))

func (CompatGroup) WithMiddleware

func (g CompatGroup) WithMiddleware(middleware MiddlewareFunc) *CompatGroup

type CompatRouter

type CompatRouter struct {
	*Router
	*CompatGroup
}

type Group

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

Group is a group of routes and middlewares.

func (*Group) Compat

func (g *Group) Compat() *CompatGroup

func (*Group) DELETE

func (g *Group) DELETE(path string, handler HandlerFunc)

Syntactic sugar for Handle("DELETE", path, handler)

func (*Group) GET

func (g *Group) GET(path string, handler HandlerFunc)

Syntactic sugar for Handle("GET", path, handler)

func (*Group) HEAD

func (g *Group) HEAD(path string, handler HandlerFunc)

Syntactic sugar for Handle("HEAD", path, handler)

func (*Group) Handle

func (g *Group) Handle(meth string, path string, handler HandlerFunc)

Path elements starting with : indicate a wildcard in the path. A wildcard will only match on a single path segment. That is, the pattern `/post/:postid` will match on `/post/1` or `/post/1/`, but not `/post/1/2`.

A path element starting with * is a catch-all, whose value will be a string containing all text in the URL matched by the wildcards. For example, with a pattern of `/images/*path` and a requested URL `images/abc/def`, path would contain `abc/def`.

Routing Rule Priority

The priority rules in the router are simple.

1. Static path segments take the highest priority. If a segment and its subtree are able to match the URL, that match is returned.

2. Wildcards take second priority. For a particular wildcard to match, that wildcard and its subtree must match the URL.

3. Finally, a catch-all rule will match when the earlier path segments have matched, and none of the static or wildcard conditions have matched. Catch-all rules must be at the end of a pattern.

So with the following patterns, we'll see certain matches:

router = bunrouter.New()
router.GET("/:page", pageHandler)
router.GET("/:year/:month/:post", postHandler)
router.GET("/:year/:month", archiveHandler)
router.GET("/images/*path", staticHandler)
router.GET("/favicon.ico", staticHandler)

/abc will match /:page
/2014/05 will match /:year/:month
/2014/05/really-great-blog-post will match /:year/:month/:post
/images/CoolImage.gif will match /images/*path
/images/2014/05/MayImage.jpg will also match /images/*path, with all the text after /images stored in the variable path.
/favicon.ico will match /favicon.ico

Trailing Slashes

The router has special handling for paths with trailing slashes. If a pattern is added to the router with a trailing slash, any matches on that pattern without a trailing slash will be redirected to the version with the slash. If a pattern does not have a trailing slash, matches on that pattern with a trailing slash will be redirected to the version without.

The trailing slash flag is only stored once for a pattern. That is, if a pattern is added for a method with a trailing slash, all other methods for that pattern will also be considered to have a trailing slash, regardless of whether or not it is specified for those methods too.

router = bunrouter.New()
router.GET("/about", pageHandler)
router.GET("/posts/", postIndexHandler)
router.POST("/posts", postFormHandler)

GET /about will match normally.
GET /about/ will redirect to /about.
GET /posts will redirect to /posts/.
GET /posts/ will match normally.
POST /posts will redirect to /posts/, because the GET method used a trailing slash.

func (*Group) NewGroup

func (g *Group) NewGroup(path string, opts ...GroupOption) *Group

NewGroup adds a sub-group to this group.

func (*Group) OPTIONS

func (g *Group) OPTIONS(path string, handler HandlerFunc)

Syntactic sugar for Handle("OPTIONS", path, handler)

func (*Group) PATCH

func (g *Group) PATCH(path string, handler HandlerFunc)

Syntactic sugar for Handle("PATCH", path, handler)

func (*Group) POST

func (g *Group) POST(path string, handler HandlerFunc)

Syntactic sugar for Handle("POST", path, handler)

func (*Group) PUT

func (g *Group) PUT(path string, handler HandlerFunc)

Syntactic sugar for Handle("PUT", path, handler)

func (*Group) Verbose

func (g *Group) Verbose() *VerboseGroup

func (*Group) WithGroup

func (g *Group) WithGroup(path string, fn func(g *Group))

func (*Group) WithMiddleware

func (g *Group) WithMiddleware(middleware MiddlewareFunc) *Group

type GroupOption

type GroupOption interface {
	Option
	// contains filtered or unexported methods
}

func WithGroup

func WithGroup(fn func(g *Group)) GroupOption

WithGroup calls the fn with the current Group.

func WithHandler

func WithHandler(fn HandlerFunc) GroupOption

WithHandler is like WithMiddleware, but the handler can't modify the request.

func WithMiddleware

func WithMiddleware(fn MiddlewareFunc) GroupOption

WithMiddleware adds a middleware handler to the Group's middleware stack.

type H

type H map[string]interface{}

type HandlerFunc

type HandlerFunc func(w http.ResponseWriter, req Request) error

func HTTPHandler

func HTTPHandler(handler http.Handler) HandlerFunc

func HTTPHandlerFunc

func HTTPHandlerFunc(handler http.HandlerFunc) HandlerFunc

type MiddlewareFunc

type MiddlewareFunc func(next HandlerFunc) HandlerFunc

type Option

type Option interface {
	// contains filtered or unexported methods
}

func WithMethodNotAllowedHandler

func WithMethodNotAllowedHandler(handler HandlerFunc) Option

MethodNotAllowedHandler is called when a route matches, but that route does not have a handler for the requested method. The default handler just writes the status code http.StatusMethodNotAllowed.

func WithNotFoundHandler

func WithNotFoundHandler(handler HandlerFunc) Option

WithNotFoundHandler is called when there is no a matching pattern. The default NotFoundHandler is http.NotFound.

type Param

type Param struct {
	Key   string
	Value string
}

type Params

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

func ParamsFromContext

func ParamsFromContext(ctx context.Context) Params

func (Params) ByName

func (ps Params) ByName(name string) string

func (Params) Get

func (ps Params) Get(name string) (string, bool)

func (Params) Int32

func (ps Params) Int32(name string) (int32, error)

func (Params) Int64

func (ps Params) Int64(name string) (int64, error)

func (Params) Map

func (ps Params) Map() map[string]string

func (Params) Route

func (ps Params) Route() string

func (Params) Slice

func (ps Params) Slice() []Param

func (Params) Uint32

func (ps Params) Uint32(name string) (uint32, error)

func (Params) Uint64

func (ps Params) Uint64(name string) (uint64, error)

type Request

type Request struct {
	*http.Request
	// contains filtered or unexported fields
}

func NewRequest

func NewRequest(req *http.Request) Request

func (Request) Param

func (req Request) Param(key string) string

func (Request) Params

func (req Request) Params() Params

func (Request) Route

func (req Request) Route() string

func (Request) WithContext

func (req Request) WithContext(ctx context.Context) Request

type Router

type Router struct {
	Group
	// contains filtered or unexported fields
}

func New

func New(opts ...Option) *Router

func (*Router) Compat

func (r *Router) Compat() *CompatRouter

func (*Router) ServeHTTP

func (t *Router) ServeHTTP(w http.ResponseWriter, req *http.Request)

func (*Router) Verbose

func (r *Router) Verbose() *VerboseRouter

type VerboseGroup

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

VerboseGroup is like Group, but it works with VerboseHandlerFunc instead of bunrouter handler.

func (VerboseGroup) DELETE

func (g VerboseGroup) DELETE(path string, handler VerboseHandlerFunc)

func (VerboseGroup) GET

func (g VerboseGroup) GET(path string, handler VerboseHandlerFunc)

func (VerboseGroup) HEAD

func (g VerboseGroup) HEAD(path string, handler VerboseHandlerFunc)

func (VerboseGroup) Handle

func (g VerboseGroup) Handle(method string, path string, handler VerboseHandlerFunc)

func (VerboseGroup) NewGroup

func (g VerboseGroup) NewGroup(path string, opts ...GroupOption) *VerboseGroup

func (VerboseGroup) OPTIONS

func (g VerboseGroup) OPTIONS(path string, handler VerboseHandlerFunc)

func (VerboseGroup) PATCH

func (g VerboseGroup) PATCH(path string, handler VerboseHandlerFunc)

func (VerboseGroup) POST

func (g VerboseGroup) POST(path string, handler VerboseHandlerFunc)

func (VerboseGroup) PUT

func (g VerboseGroup) PUT(path string, handler VerboseHandlerFunc)

func (VerboseGroup) WithGroup

func (g VerboseGroup) WithGroup(path string, fn func(g *VerboseGroup))

func (VerboseGroup) WithMiddleware

func (g VerboseGroup) WithMiddleware(middleware MiddlewareFunc) *VerboseGroup

type VerboseHandlerFunc

type VerboseHandlerFunc func(w http.ResponseWriter, req *http.Request, ps Params)

type VerboseRouter

type VerboseRouter struct {
	*Router
	*VerboseGroup
}

Directories

Path Synopsis
example
all-in-one Module
basic Module
basic-compat Module
basic-verbose Module
file-server Module
opentelemetry Module
panic-recover Module
rate-limiting Module
extra
basicauth Module
bunroutergzip Module
bunrouterotel Module
reqlog Module

Jump to

Keyboard shortcuts

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