lars

package module
v0.0.0-...-132e253 Latest Latest
Warning

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

Go to latest
Published: Mar 16, 2016 License: MIT Imports: 17 Imported by: 0

README

##LARS Project status Build Status Coverage Status Go Report Card GoDoc License

LARS is a fast radix-tree based, zero allocation, HTTP router for Go. view examples

Why Another HTTP Router?

Have you ever been painted into a corner by a framework, ya me too! and I've noticed that allot of routers out there, IMHO, are adding so much functionality that they are turning into Web Frameworks, (which is fine, frameworks are important) however, not at the expense of flexibility and configurability. So with no further ado, introducing LARS an HTTP router that can be your launching pad in creating a framework for your needs. How? Context is an interface see example here, where you can add as little or much as you want or need and most importantly...under your control. ( I will be creating a full example app in the near future that can be used as a starting point for any project. )

Unique Features

  • Context is an interface allowing passing of framework/globals/application specific variables. example
  • Handles mutiple url patterns not supported by many other routers.
    • the route algorithm was written from scratch and is NOT a modification of any other router.
  • Contains helpful logic to help prevent adding bad routes, keeping your url's consistent.
    • i.e. /user/:id and /user/:user_id - the second one will fail to add letting you know that :user_id should be :id
  • Has an uber simple middleware + handler definitions!!! middleware and handlers actually have the exact same definition!
  • Can register custom handlers for use as middleware + handlers; best part is can register one for your custom context and not have to do type casting everywhere see here
  • Full support for standard/native http Handler + HandlerFunc see here
    • When Parsing a form call Context's ParseForm amd ParseMulipartForm functions and the URL params will be added into the Form object, just like query parameters are, so no extra work

Installation

Use go get

go get github.com/go-playground/lars

or to update

go get -u github.com/go-playground/lars

Then import lars package into your code.

import "github.com/go-playground/lars"

Usage

Below is a full example, for a simpler example see here

package main

import (
	"log"
	"net/http"
	"os"
	"time"

	"github.com/go-playground/lars"
)

// This is a contrived example of how I would use in production
// I would break things into separate files but all here for simplicity

// ApplicationGlobals houses all the application info for use.
type ApplicationGlobals struct {
	// DB - some database connection
	Log *log.Logger
	// Translator - some i18n translator
	// JSON - encoder/decoder
	// Schema - gorilla schema
	// .......
}

// Reset gets called just before a new HTTP request starts calling
// middleware + handlers
func (g *ApplicationGlobals) Reset() {
	// DB = new database connection or reset....
	//
	// We don't touch translator + log as they don't change per request
}

// Done gets called after the HTTP request has completed right before
// Context gets put back into the pool
func (g *ApplicationGlobals) Done() {
	// DB.Close()
}

func newGlobals() *ApplicationGlobals {

	logger := log.New(os.Stdout, "INFO: ", log.Ldate|log.Ltime|log.Lshortfile)
	// translator := ...
	// db := ... base db connection or info
	// json := ...
	// schema := ...

	return &ApplicationGlobals{
		Log: logger,
		// Translator: translator,
		// DB: db,
		// JSON: json,
		// schema:schema,
	}
}

// MyContext is a custom context
type MyContext struct {
	*lars.Ctx  // a little dash of Duck Typing....
	AppContext *ApplicationGlobals
}

// Reset overriding
func (mc *MyContext) Reset(w http.ResponseWriter, r *http.Request) {

	// call lars context reset, must be done
	mc.Ctx.Reset(w, r)
	mc.AppContext.Reset()
}

// RequestComplete overriding
func (mc *MyContext) RequestComplete() {
	mc.AppContext.Done()
}

func newContext(l *lars.LARS) lars.Context {
	return &MyContext{
		Ctx:        lars.NewContext(l),
		AppContext: newGlobals(),
	}
}

func main() {

	l := lars.New()
	l.RegisterContext(newContext) // all gets cached in pools for you
	l.Use(Logger)

	l.Get("/", Home)

	users := l.Group("/users")
	users.Get("", Users)

	// you can break it up however you with, just demonstrating that you can
	// have groups of group
	user := users.Group("/:id")
	user.Get("", User)
	user.Get("/profile", UserProfile)

	http.ListenAndServe(":3007", l.Serve())
}

// Home ...
func Home(c lars.Context) {

	ctx := c.(*MyContext)

	var username string

	// username = ctx.AppContext.DB.find(user by .....)

	ctx.AppContext.Log.Println("Found User")

	c.Response().Write([]byte("Welcome Home " + username))
}

// Users ...
func Users(c lars.Context) {

	ctx := c.(*MyContext)

	ctx.AppContext.Log.Println("In Users Function")

	c.Response().Write([]byte("Users"))
}

// User ...
func User(c lars.Context) {

	ctx := c.(*MyContext)

	id := c.Param("id")

	var username string

	// username = ctx.AppContext.DB.find(user by id.....)

	ctx.AppContext.Log.Println("Found User")

	c.Response().Write([]byte("Welcome " + username + " with id " + id))
}

// UserProfile ...
func UserProfile(c lars.Context) {

	ctx := c.(*MyContext)

	id := c.Param("id")

	var profile string

	// profile = ctx.AppContext.DB.find(user profile by .....)

	ctx.AppContext.Log.Println("Found User Profile")

	c.Response().Write([]byte("Here's your profile " + profile + " user " + id))
}

// Logger ...
func Logger(c lars.Context) {

	start := time.Now()

	c.Next()

	stop := time.Now()
	path := c.Request().URL.Path

	if path == "" {
		path = "/"
	}

	log.Printf("%s %d %s %s", c.Request().Method, c.Response().Status(), path, stop.Sub(start))
}

Native Handler Support

package main

import (
	"log"
	"net/http"
	"time"

	"github.com/go-playground/lars"
)

func main() {

	l := lars.New()
	l.Use(Logger)

	l.Get("/", HelloWorld)

	http.ListenAndServe(":3007", l.Serve())
}

// HelloWorld ...
func HelloWorld(w http.ResponseWriter, r *http.Request) {

	// lars's context! get it and ROCK ON!
	ctx := lars.GetContext(w)

	ctx.Response().Write([]byte("Hello World"))
}

// Logger ...
func Logger(c lars.Context) {

	start := time.Now()

	c.Next()

	stop := time.Now()
	path := c.Request().URL.Path

	if path == "" {
		path = "/"
	}

	log.Printf("%s %d %s %s", c.Request().Method, c.Response().Status(), path, stop.Sub(start))
}

Middleware

There are some pre-defined middlewares within the middleware folder; NOTE: that the middleware inside will comply with the following rule(s):

  • Are completely reusable by the community without modification

Other middleware will be listed under the examples/middleware/... folder for a quick copy/paste modify. as an example a logging or recovery middleware are very application dependent and therefore will be listed under the examples/middleware/...

Benchmarks

Run on MacBook Pro (Retina, 15-inch, Late 2013) 2.6 GHz Intel Core i7 16 GB 1600 MHz DDR3 using Go version go1.6 darwin/amd64

NOTE: you may have noticed the benchmark get a tiny bit slower since turning Context into an interface, but in the real world when using your own Context ( even if only for passing around globals ), there is a single pool that your objects are stored in so the small hit now will save you on the flip side in real world usage.

go test -bench=. -benchmem=true

   githubAPI: 50864 Bytes
   gplusAPI: 3968 Bytes
   parseAPI: 5032 Bytes
   staticAPI: 33856 Bytes

PASS
BenchmarkLARS_GithubStatic-8	20000000	       107 ns/op	       0 B/op	       0 allocs/op
BenchmarkLARS_GithubParam-8 	10000000	       159 ns/op	       0 B/op	       0 allocs/op
BenchmarkLARS_GithubAll-8   	   30000	     38937 ns/op	       0 B/op	       0 allocs/op
BenchmarkLARS_GPlusStatic-8 	20000000	        76.5 ns/op	       0 B/op	       0 allocs/op
BenchmarkLARS_GPlusParam-8  	20000000	       105 ns/op	       0 B/op	       0 allocs/op
BenchmarkLARS_GPlus2Params-8	10000000	       146 ns/op	       0 B/op	       0 allocs/op
BenchmarkLARS_GPlusAll-8    	 1000000	      1947 ns/op	       0 B/op	       0 allocs/op
BenchmarkLARS_ParseStatic-8 	20000000	        97.7 ns/op	       0 B/op	       0 allocs/op
BenchmarkLARS_ParseParam-8  	10000000	       120 ns/op	       0 B/op	       0 allocs/op
BenchmarkLARS_Parse2Params-8	10000000	       130 ns/op	       0 B/op	       0 allocs/op
BenchmarkLARS_ParseAll-8    	  300000	      3879 ns/op	       0 B/op	       0 allocs/op
BenchmarkLARS_StaticAll-8   	   50000	     24417 ns/op	       0 B/op	       0 allocs/op
BenchmarkLARS_Param-8       	20000000	        94.1 ns/op	       0 B/op	       0 allocs/op
BenchmarkLARS_Param5-8      	10000000	       160 ns/op	       0 B/op	       0 allocs/op
BenchmarkLARS_Param20-8     	 3000000	       405 ns/op	       0 B/op	       0 allocs/op
BenchmarkLARS_ParamWrite-8  	20000000	        95.2 ns/op	       0 B/op	       0 allocs/op

Package Versioning

I'm jumping on the vendoring bandwagon, you should vendor this package as I will not be creating different version with gopkg.in like allot of my other libraries.

Why? because my time is spread pretty thin maintaining all of the libraries I have + LIFE, it is so freeing not to worry about it and will help me keep pouring out bigger and better things for you the community.

This package is inspired by the following

License

This project is licensed unter MIT, for more information look into the LICENSE file. Copyright (c) 2016 Go Playground

Documentation

Index

Constants

View Source
const (
	// CONNECT HTTP method
	CONNECT = "CONNECT"
	// DELETE HTTP method
	DELETE = "DELETE"
	// GET HTTP method
	GET = "GET"
	// HEAD HTTP method
	HEAD = "HEAD"
	// OPTIONS HTTP method
	OPTIONS = "OPTIONS"
	// PATCH HTTP method
	PATCH = "PATCH"
	// POST HTTP method
	POST = "POST"
	// PUT HTTP method
	PUT = "PUT"
	// TRACE HTTP method
	TRACE = "TRACE"

	ApplicationJSON                  = "application/json"
	ApplicationJSONCharsetUTF8       = ApplicationJSON + "; " + CharsetUTF8
	ApplicationJavaScript            = "application/javascript"
	ApplicationJavaScriptCharsetUTF8 = ApplicationJavaScript + "; " + CharsetUTF8
	ApplicationXML                   = "application/xml"
	ApplicationXMLCharsetUTF8        = ApplicationXML + "; " + CharsetUTF8
	ApplicationForm                  = "application/x-www-form-urlencoded"
	ApplicationProtobuf              = "application/protobuf"
	ApplicationMsgpack               = "application/msgpack"
	TextHTML                         = "text/html"
	TextHTMLCharsetUTF8              = TextHTML + "; " + CharsetUTF8
	TextPlain                        = "text/plain"
	TextPlainCharsetUTF8             = TextPlain + "; " + CharsetUTF8
	MultipartForm                    = "multipart/form-data"
	OctetStream                      = "application/octet-stream"

	CharsetUTF8 = "charset=utf-8"

	AcceptedLanguage   = "Accept-Language"
	AcceptEncoding     = "Accept-Encoding"
	Authorization      = "Authorization"
	ContentDisposition = "Content-Disposition"
	ContentEncoding    = "Content-Encoding"
	ContentLength      = "Content-Length"
	ContentType        = "Content-Type"
	Location           = "Location"
	Upgrade            = "Upgrade"
	Vary               = "Vary"
	WWWAuthenticate    = "WWW-Authenticate"
	XForwardedFor      = "X-Forwarded-For"
	XRealIP            = "X-Real-Ip"

	Gzip = "gzip"

	WildcardParam = "*wildcard"
)

HTTP Constant Terms and Variables

Variables

View Source
var NativeChainHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {

	c := GetContext(w)
	b := c.BaseContext()

	if b.index+1 < len(b.handlers) {
		c.Next()
	}
})

NativeChainHandler is used in native handler chains example using nosurf crsf middleware nosurf.NewPure(lars.NativeChainHandlerFunc)

Functions

This section is empty.

Types

type Context

type Context interface {
	context.Context
	Request() *http.Request
	Response() *Response
	WebSocket() *websocket.Conn
	Param(name string) string
	ParseForm() error
	ParseMultipartForm(maxMemory int64) error
	Set(key string, value interface{})
	Get(key string) (value interface{}, exists bool)
	Next()
	Reset(w http.ResponseWriter, r *http.Request)
	RequestComplete()
	ClientIP() (clientIP string)
	AcceptedLanguages(lowercase bool) []string
	HandlerName() string
	Stream(step func(w io.Writer) bool)
	Attachment(r io.Reader, filename string) (err error)
	Inline(r io.Reader, filename string) (err error)
	BaseContext() *Ctx
}

Context is the context interface type

func GetContext

func GetContext(w http.ResponseWriter) Context

GetContext is a helper method for retrieving the Context object from the ResponseWriter when using native go hanlders. NOTE: this will panic if fed an http.ResponseWriter not provided by lars's chaining.

type ContextFunc

type ContextFunc func(l *LARS) Context

ContextFunc is the function to run when creating a new context

type Ctx

type Ctx struct {
	context.Context
	// contains filtered or unexported fields
}

Ctx encapsulates the http request, response context

func NewContext

func NewContext(l *LARS) *Ctx

NewContext returns a new default lars Context object.

func (*Ctx) AcceptedLanguages

func (c *Ctx) AcceptedLanguages(lowercase bool) []string

AcceptedLanguages returns an array of accepted languages denoted by the Accept-Language header sent by the browser NOTE: some stupid browsers send in locales lowercase when all the rest send it properly

func (*Ctx) Attachment

func (c *Ctx) Attachment(r io.Reader, filename string) (err error)

Attachment is a helper method for returning an attachement file to be downloaded, if you with to open inline see function

func (*Ctx) BaseContext

func (c *Ctx) BaseContext() *Ctx

BaseContext returns the underlying context object LARS uses internally. used when overriding the context object

func (*Ctx) ClientIP

func (c *Ctx) ClientIP() (clientIP string)

ClientIP implements a best effort algorithm to return the real client IP, it parses X-Real-IP and X-Forwarded-For in order to work properly with reverse-proxies such us: nginx or haproxy.

func (*Ctx) Get

func (c *Ctx) Get(key string) (value interface{}, exists bool)

Get returns the value for the given key, ie: (value, true). If the value does not exists it returns (nil, false)

func (*Ctx) HandlerName

func (c *Ctx) HandlerName() string

HandlerName returns the current Contexts final handler's name

func (*Ctx) Inline

func (c *Ctx) Inline(r io.Reader, filename string) (err error)

Inline is a helper method for returning a file inline to be rendered/opened by the browser

func (*Ctx) Next

func (c *Ctx) Next()

Next should be used only inside middleware. It executes the pending handlers in the chain inside the calling handler. See example in github.

func (*Ctx) Param

func (c *Ctx) Param(name string) string

Param returns the value of the first Param which key matches the given name. If no matching Param is found, an empty string is returned.

func (*Ctx) ParseForm

func (c *Ctx) ParseForm() error

ParseForm calls the underlying http.Request ParseForm but also adds the URL params to the request Form as if they were defined as query params i.e. ?id=13&ok=true but does not add the params to the http.Request.URL.RawQuery for SEO purposes

func (*Ctx) ParseMultipartForm

func (c *Ctx) ParseMultipartForm(maxMemory int64) error

ParseMultipartForm calls the underlying http.Request ParseMultipartForm but also adds the URL params to the request Form as if they were defined as query params i.e. ?id=13&ok=true but does not add the params to the http.Request.URL.RawQuery for SEO purposes

func (*Ctx) Request

func (c *Ctx) Request() *http.Request

Request returns context assotiated *http.Request.

func (*Ctx) RequestComplete

func (c *Ctx) RequestComplete()

RequestComplete fires after request completes and just before the *Ctx object gets put back into the pool. Used to close DB connections and such on a custom context

func (*Ctx) Reset

func (c *Ctx) Reset(w http.ResponseWriter, r *http.Request)

Reset resets the Context to it's default request state

func (*Ctx) Response

func (c *Ctx) Response() *Response

Response returns http.ResponseWriter.

func (*Ctx) Set

func (c *Ctx) Set(key string, value interface{})

Set is used to store a new key/value pair exclusivelly for thisContext. It also lazy initializes c.Keys if it was not used previously.

func (*Ctx) Stream

func (c *Ctx) Stream(step func(w io.Writer) bool)

Stream provides HTTP Streaming

func (*Ctx) WebSocket

func (c *Ctx) WebSocket() *websocket.Conn

WebSocket returns context's assotiated *websocket.Conn.

type CustomHandlerFunc

type CustomHandlerFunc func(Context, Handler)

CustomHandlerFunc wraped by HandlerFunc and called where you can type cast both Context and Handler and call Handler

type Handler

type Handler interface{}

Handler is the type used in registering handlers. NOTE: these handlers may get wrapped by the HandlerFunc type internally.

type HandlerFunc

type HandlerFunc func(Context)

HandlerFunc is the internal handler type used for middleware and handlers

type HandlersChain

type HandlersChain []HandlerFunc

HandlersChain is an array of HanderFunc handlers to run

type IRouteGroup

type IRouteGroup interface {
	IRoutes
	Group(prefix string, middleware ...Handler) IRouteGroup
}

IRouteGroup interface for router group

type IRoutes

type IRoutes interface {
	Use(...Handler)
	Any(string, ...Handler)
	Get(string, ...Handler)
	Post(string, ...Handler)
	Delete(string, ...Handler)
	Patch(string, ...Handler)
	Put(string, ...Handler)
	Options(string, ...Handler)
	Head(string, ...Handler)
	Connect(string, ...Handler)
	Trace(string, ...Handler)
	WebSocket(string, Handler)
}

IRoutes interface for routes

type LARS

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

LARS is the main routing instance

func New

func New() *LARS

New Creates and returns a new lars instance

func (*LARS) Any

func (g *LARS) Any(path string, h ...Handler)

Any adds a route & handler to the router for all HTTP methods.

func (*LARS) Connect

func (g *LARS) Connect(path string, h ...Handler)

Connect adds a CONNECT route & handler to the router.

func (*LARS) Delete

func (g *LARS) Delete(path string, h ...Handler)

Delete adds a DELETE route & handler to the router.

func (*LARS) Get

func (g *LARS) Get(path string, h ...Handler)

Get adds a GET route & handler to the router.

func (*LARS) GetRouteMap

func (l *LARS) GetRouteMap() []*RouteMap

GetRouteMap returns an array of all registered routes

func (*LARS) Group

func (g *LARS) Group(prefix string, middleware ...Handler) IRouteGroup

Group creates a new sub router with prefix. It inherits all properties from the parent. Passing middleware overrides parent middleware but still keeps the root level middleware intact.

func (*LARS) Head

func (g *LARS) Head(path string, h ...Handler)

Head adds a HEAD route & handler to the router.

func (*LARS) Match

func (g *LARS) Match(methods []string, path string, h ...Handler)

Match adds a route & handler to the router for multiple HTTP methods provided.

func (*LARS) Options

func (g *LARS) Options(path string, h ...Handler)

Options adds an OPTIONS route & handler to the router.

func (*LARS) Patch

func (g *LARS) Patch(path string, h ...Handler)

Patch adds a PATCH route & handler to the router.

func (*LARS) Post

func (g *LARS) Post(path string, h ...Handler)

Post adds a POST route & handler to the router.

func (*LARS) Put

func (g *LARS) Put(path string, h ...Handler)

Put adds a PUT route & handler to the router.

func (*LARS) Register404

func (l *LARS) Register404(notFound ...Handler)

Register404 alows for overriding of the not found handler function. NOTE: this is run after not finding a route even after redirecting with the trailing slash

func (*LARS) RegisterContext

func (l *LARS) RegisterContext(fn ContextFunc)

RegisterContext registers a custom Context function for creation and resetting of a global object passed per http request

func (*LARS) RegisterCustomHandler

func (l *LARS) RegisterCustomHandler(customType interface{}, fn CustomHandlerFunc)

RegisterCustomHandler registers a custom handler that gets wrapped by HandlerFunc

func (*LARS) Serve

func (l *LARS) Serve() http.Handler

Serve returns an http.Handler to be used.

func (*LARS) SetHandle405MethodNotAllowed

func (l *LARS) SetHandle405MethodNotAllowed(set bool)

SetHandle405MethodNotAllowed tells lars whether to handle the http 405 Method Not Allowed status code

func (*LARS) SetRedirectTrailingSlash

func (l *LARS) SetRedirectTrailingSlash(set bool)

SetRedirectTrailingSlash tells lars whether to try and fix a URL by trying to find it lowercase -> with or without slash -> 404

func (*LARS) Trace

func (g *LARS) Trace(path string, h ...Handler)

Trace adds a TRACE route & handler to the router.

func (*LARS) Use

func (g *LARS) Use(m ...Handler)

Use adds a middleware handler to the group middleware chain.

func (*LARS) WebSocket

func (g *LARS) WebSocket(path string, h Handler)

WebSocket adds a websocket route

type Param

type Param struct {
	Key   string
	Value string
}

Param is a single URL parameter, consisting of a key and a value.

type Params

type Params []Param

Params is a Param-slice, as returned by the router. The slice is ordered, the first URL parameter is also the first slice value. It is therefore safe to read values by the index.

type Response

type Response struct {
	http.ResponseWriter
	// contains filtered or unexported fields
}

Response struct contains methods and to capture extra data about the http request and more efficiently reset underlying writer object... it does comply with the http.ResponseWriter interface

func (*Response) CloseNotify

func (r *Response) CloseNotify() <-chan bool

CloseNotify wraps response writer's CloseNotify function.

func (*Response) Committed

func (r *Response) Committed() bool

Committed returns whether the *Response header has already been written to and if has been committed to this return.

func (*Response) Flush

func (r *Response) Flush()

Flush wraps response writer's Flush function.

func (*Response) Header

func (r *Response) Header() http.Header

Header returns the header map that will be sent by WriteHeader. Changing the header after a call to WriteHeader (or Write) has no effect unless the modified headers were declared as trailers by setting the "Trailer" header before the call to WriteHeader (see example). To suppress implicit *Response headers, set their value to nil.

func (*Response) Hijack

func (r *Response) Hijack() (net.Conn, *bufio.ReadWriter, error)

Hijack wraps response writer's Hijack function.

func (*Response) SetWriter

func (r *Response) SetWriter(w http.ResponseWriter)

SetWriter sets the provided writer as the new *Response http.ResponseWriter

func (*Response) Size

func (r *Response) Size() int64

Size returns the number of bytes written in the *Response

func (*Response) Status

func (r *Response) Status() int

Status returns the *Response's current http status code.

func (*Response) Write

func (r *Response) Write(b []byte) (n int, err error)

Write writes the data to the connection as part of an HTTP reply. If WriteHeader has not yet been called, Write calls WriteHeader(http.StatusOK) before writing the data. If the Header does not contain a Content-Type line, Write adds a Content-Type set to the result of passing the initial 512 bytes of written data to DetectContentType.

func (*Response) WriteHeader

func (r *Response) WriteHeader(code int)

WriteHeader sends an HTTP *Response header with status code. If WriteHeader is not called explicitly, the first call to Write will trigger an implicit WriteHeader(http.StatusOK). Thus explicit calls to WriteHeader are mainly used to send error codes.

func (*Response) WriteString

func (r *Response) WriteString(s string) (n int, err error)

WriteString write string to ResponseWriter

func (*Response) Writer

func (r *Response) Writer() http.ResponseWriter

Writer return the *Response's http.ResponseWriter object. Usually only used when creating middleware.

type RouteMap

type RouteMap struct {
	Depth   int    `json:"depth"`
	Path    string `json:"path"`
	Method  string `json:"method"`
	Handler string `json:"handler"`
}

RouteMap contains a single routes full path and other information

type Router

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

Router contains the tree information and methods to traverse it

Directories

Path Synopsis
examples

Jump to

Keyboard shortcuts

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