router

package module
v1.0.0 Latest Latest
Warning

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

Go to latest
Published: May 4, 2020 License: MIT Imports: 6 Imported by: 7

README

gowww router GoDoc Build Coverage Go Report Status Stable

Package router provides a lightning fast HTTP router.

Features

  • Extreme performance: sub-microsecond routing in most cases
  • Full compatibility with the http.Handler interface
  • Generic: no magic methods, bring your own handlers
  • Path parameters, regular expressions and wildcards
  • Smart prioritized routes
  • Zero memory allocations during serving (but for parameters)
  • Respecting the principle of least surprise
  • Tested and used in production

Installing

  1. Get package:

    go get -u github.com/gowww/router
    
  2. Import it in your code:

    import "github.com/gowww/router"
    

Usage

  1. Make a new router:

    rt := router.New()
    
  2. Make a route:

    rt.Handle("GET", "/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    	fmt.Fprint(w, "Hello")
    }))
    

    Remember that HTTP methods are case-sensitive and uppercase by convention (RFC 7231 4.1).
    So you can directly use the built-in shortcuts for standard HTTP methods: Router.Get, Router.Post, Router.Put, Router.Patch and Router.Delete.

  3. Give the router to the server:

    http.ListenAndServe(":8080", rt)
    
Parameters
Named

A named parameter begins with : and matches any value until the next / or end of path.

To retrieve the value (stored in request's context), ask Parameter.
It will return the value as a string.

Example, with a parameter id:

rt.Get("/users/:id", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
	id := router.Parameter(r, "id")
	fmt.Fprintf(w, "Page of user #%s", id)
}))
No surprise

A parameter can be used on the same level as a static route, without conflict:

rt.Get("/users/all", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
	fmt.Fprint(w, "All users page")
}))

rt.Get("/users/:id", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
	id := router.Parameter(r, "id")
	fmt.Fprintf(w, "Page of user #%s", id)
}))
Regular expressions

If a parameter must match an exact pattern (digits only, for example), you can also set a regular expression constraint just after the parameter name and another ::

rt.Get(`/users/:id:^\d+$`, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
	id := router.Parameter(r, "id")
	fmt.Fprintf(w, "Page of user #%s", id)
}))

If you don't need to retrieve the parameter value but only use a regular expression, you can omit the parameter name:

rt.Get(`/shows/::^prison-break-s06-.+`, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
	fmt.Fprint(w, "Prison Break S06 — Coming soon…")
}))

Don't forget that regular expressions can significantly reduce performance.

No surprise

A parameter with a regular expression can be used on the same level as a simple parameter, without conflict:

rt.Get(`/users/:id:^\d+$`, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
	id := router.Parameter(r, "id")
	fmt.Fprintf(w, "Page of user #%s", id)
}))

rt.Get("/users/:name", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
	name := router.Parameter(r, "name")
	fmt.Fprintf(w, "Page of %s", name)
}))
Wildcard

A trailing slash in a route path is significant.
It behaves like a wildcard by matching the beginning of the request path.
The rest of the request path becomes the parameter value of *:

rt.Get("/files/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
	filepath := router.Parameter(r, "*")
	fmt.Fprintf(w, "Get file %s", filepath)
}))

Note that a trailing slash in a request path is always trimmed and the client redirected.
For example, a request for /files/ will be redirected to /files and will never match a /files/ route.
In other words, /files and /files/ are two different routes.

No surprise

Deeper route paths with the same prefix as the wildcard will take precedence, without conflict:

// Will match:
// 	/files/one
// 	/files/two
// 	...
rt.Get("/files/:name", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {kv
	name := router.Parameter(r, "name")
	fmt.Fprintf(w, "Get root file #%s", name)
}))

// Will match:
// 	/files/one/...
// 	/files/two/...
// 	...
rt.Get("/files/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
	filepath := router.Parameter(r, "*")
	fmt.Fprintf(w, "Get file %s", filepath)
}))

// Will match:
// 	/files/movies/one
// 	/files/movies/two
// 	...
rt.Get("/files/movies/:name", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
	name := router.Parameter(r, "name")
	fmt.Fprintf(w, "Get movie #%s", name)
}))
Static files

For serving static files, like for other routes, just bring your own handler.

Example, with the standard net/http.FileServer:

rt.Get("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("static"))))
Custom "not found" handler

When a request match no route, the response status is set to 404 and an empty body is sent by default.

But you can set your own "not found" handler.
In this case, it's up to you to set the response status code (normally 404):

rt.NotFoundHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
	http.NotFound(w, r)
})

Documentation

Overview

Package router provides a lightning fast HTTP router.

Example
package main

import (
	"fmt"
	"github.com/gowww/router"
	"net/http"
)

func main() {
	rt := router.New()

	// File server
	rt.Get("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("static"))))

	// Static route
	rt.Get("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprint(w, "Hello")
	}))

	// Path parameter
	rt.Get("/users/:name", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintf(w, "Get user %s", router.Parameter(r, "name"))
	}))

	// Path parameter with regular expression
	rt.Get(`users/:id:^\d+$`, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintf(w, "Page of user #%s", router.Parameter(r, "id"))
	}))

	// Path parameter + Trailing slash for wildcard
	rt.Post("/users/:id/files/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintf(w, "Post file %s to user %s", router.Parameter(r, "*"), router.Parameter(r, "id"))
	}))

	// Custom "not found"
	rt.NotFoundHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		http.NotFound(w, r)
	})

	http.ListenAndServe(":8080", rt)
}
Output:

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func Parameter

func Parameter(r *http.Request, key string) string

Parameter returns the value of path parameter. Result is empty if parameter doesn't exist.

Types

type Router

type Router struct {
	NotFoundHandler http.Handler
	// contains filtered or unexported fields
}

The Router is the main structure of this package.

func New

func New() *Router

New returns a fresh rounting unit.

func (*Router) Delete

func (rt *Router) Delete(path string, handler http.Handler)

Delete makes a route for DELETE method.

func (*Router) Get

func (rt *Router) Get(path string, handler http.Handler)

Get makes a route for GET method.

func (*Router) Handle

func (rt *Router) Handle(method, path string, handler http.Handler)

Handle adds a route with method, path and handler.

func (*Router) Patch

func (rt *Router) Patch(path string, handler http.Handler)

Patch makes a route for PATCH method.

func (*Router) Post

func (rt *Router) Post(path string, handler http.Handler)

Post makes a route for POST method.

func (*Router) Put

func (rt *Router) Put(path string, handler http.Handler)

Put makes a route for PUT method.

func (*Router) ServeHTTP

func (rt *Router) ServeHTTP(w http.ResponseWriter, r *http.Request)

func (*Router) String

func (rt *Router) String() (s string)

Notes

Bugs

  • If the client has no route for "/" and this split occurs on the first level because of the 2nd byte (just after the leading "/"), the isRoot flag of the parent node ("/") is false. It's not a problem because it has no handler and will never match a request, but it's not clean. A first solution would be to let makeChild know the current level in tree, but... All that for this?

Jump to

Keyboard shortcuts

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