/pure

:non-potable_water: Is a fast radix-tree based HTTP router that sticks to the native implementations of Go's "net/http" package

Primary LanguageGoMIT LicenseMIT

##Pure Project status Build Status Coverage Status Go Report Card GoDoc License Gitter

Pure is a fast radix-tree based HTTP router that sticks to the native implimentations of Go's "net/http" package; in essence, keeping the handler implimentations 'pure' by using Go 1.7's "context" package.

Why Another HTTP Router?

I initially created lars, which I still maintain, that wraps the native implimentation, think of this package as a Go pure implimentation of lars

Key & Unique Features

  • It sticks to Go's native implimentations while providing helper functions for convenience
  • Fast & Efficient - pure uses a custom version of httprouter's radix tree, so incredibly fast and efficient.

Installation

Use go get

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

Usage

package main

import (
	"net/http"

	"github.com/go-playground/pure"
	mw "github.com/go-playground/pure/examples/middleware/logging-recovery"
)

func main() {

	p := pure.New()
	p.Use(mw.LoggingAndRecovery(true))

	p.Get("/", helloWorld)

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

func helloWorld(w http.ResponseWriter, r *http.Request) {
	w.Write([]byte("Hello World"))
}

RequestVars

This is an interface that is used to pass request scoped variables and functions using context.Context. It is implimented in this way because retrieving values from context isn't the fastest, and so using this the router can store multiple pieces of information while reducing lookup time to a single stored RequestVars.

Currently only the URL/SEO params are stored on the RequestVars but if/when more is added they can merely be added to the RequestVars and there will be no additional lookup time.

URL Params

p := p.New()

// the matching param will be stored in the context's params with name "id"
l.Get("/user/:id", UserHandler)

// extract params like so
rv := pure.ReqestVars(r) // done this way so only have to extract from context once, read above
rv.URLParam(paramname)

// serve css, js etc.. pure.RequestVars(r).URLParam(pure.WildcardParam) will return the remaining path if 
// you need to use it in a custom handler...
l.Get("/static/*", http.FileServer(http.Dir("static/"))) 

...

Note: Since this router has only explicit matches, you can not register static routes and parameters for the same path segment. For example you can not register the patterns /user/new and /user/:user for the same request method at the same time. The routing of different request methods is independent from each other. I was initially against this, however it nearly cost me in a large web application where the dynamic param value say :type actually could have matched another static route and that's just too dangerous and so it is not allowed.

Groups

p.Use(LoggingAndRecovery, Gzip...)
...
p.Post("/users/add", ...)

// creates a group for user + inherits all middleware registered using p.Use()
user := p.Group("/user/:userid")
user.Get("", ...)
user.Post("", ...)
user.Delete("/delete", ...)

contactInfo := user.Group("/contact-info/:cid")
contactinfo.Delete("/delete", ...)

// creates a group for others + inherits all middleware registered using p.Use() + adds 
// OtherHandler to middleware
others := p.Group("/others", OtherHandler)

// creates a group for admin WITH NO MIDDLEWARE... more can be added using admin.Use()
admin := p.Group("/admin", nil)
admin.Use(SomeAdminSecurityMiddleware)
...

Decoding Body

currently JSON, XML, FORM, Multipart Form and url.Values are support out of the box.

	// second argument denotes yes or no I would like URL query parameter fields
	// to be included. i.e. 'id' and 'id2' in route '/user/:id?id2=val' should it be included.
	if err := pure.Decode(r, true, maxBytes, &user); err != nil {
		log.Println(err)
	}

Misc

// set custom 404 ( not Found ) handler
l.Register404(404Handler, middleware_like_logging)

// Redirect to or from ending slash if route not found, default is true
l.SetRedirectTrailingSlash(true)

// Handle 405 ( Method Not allowed ), default is false
l.RegisterMethodNotAllowed(middleware)

// automatically handle OPTION requests; manually configured
// OPTION handlers take precedence. default false
l.RegisterAutomaticOPTIONS(middleware)

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 LoddingAndRecovery middleware is 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.7.3 darwin/amd64

NOTICE: pure uses a custom version of httprouter's radix tree, benchmarks can be found here the slowdown is with the use of the context package, as you can see when no SEO params are defined, and therefore no need to store anything in the context, it is faster than even lars.

go test -bench=. -benchmem=true
#GithubAPI Routes: 203
   Pure: 37560 Bytes

#GPlusAPI Routes: 13
   Pure: 2808 Bytes

#ParseAPI Routes: 26
   Pure: 5072 Bytes

#Static Routes: 157
   Pure: 21224 Bytes

BenchmarkPure_Param        	10000000	       156 ns/op	     240 B/op	       1 allocs/op
BenchmarkPure_Param5       	10000000	       199 ns/op	     240 B/op	       1 allocs/op
BenchmarkPure_Param20      	 5000000	       349 ns/op	     240 B/op	       1 allocs/op
BenchmarkPure_ParamWrite   	10000000	       209 ns/op	     240 B/op	       1 allocs/op
BenchmarkPure_GithubStatic 	30000000	        45.4 ns/op	       0 B/op	       0 allocs/op
BenchmarkPure_GithubParam  	10000000	       219 ns/op	     240 B/op	       1 allocs/op
BenchmarkPure_GithubAll    	   30000	     40244 ns/op	   40082 B/op	     167 allocs/op
BenchmarkPure_GPlusStatic  	50000000	        30.4 ns/op	       0 B/op	       0 allocs/op
BenchmarkPure_GPlusParam   	10000000	       173 ns/op	     240 B/op	       1 allocs/op
BenchmarkPure_GPlus2Params 	10000000	       190 ns/op	     240 B/op	       1 allocs/op
BenchmarkPure_GPlusAll     	 1000000	      2108 ns/op	    2640 B/op	      11 allocs/op
BenchmarkPure_ParseStatic  	50000000	        29.9 ns/op	       0 B/op	       0 allocs/op
BenchmarkPure_ParseParam   	10000000	       154 ns/op	     240 B/op	       1 allocs/op
BenchmarkPure_Parse2Params 	10000000	       167 ns/op	     240 B/op	       1 allocs/op
BenchmarkPure_ParseAll     	  500000	      3209 ns/op	    3840 B/op	      16 allocs/op
BenchmarkPure_StaticAll    	  200000	      9881 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.

I am open versioning with gopkg.in should anyone request it, but this should be stable going forward.

Licenses

  • MIT License (MIT), Copyright (c) 2016 Dean Karn
  • BSD License, Copyright (c) 2013 Julien Schmidt. All rights reserved.