go-playground / lars
- суббота, 2 апреля 2016 г. в 03:14:07
Go
Is a lightweight, fast and extensible zero allocation HTTP router for Go used to create customizable frameworks.
LARS is a fast radix-tree based, zero allocation, HTTP router for Go. view examples
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.
Use go get
go get github.com/go-playground/lars
Below is a simple example, for a full example see here
package main
import (
"net/http"
"github.com/go-playground/lars"
mw "github.com/go-playground/lars/examples/middleware/logging-recovery"
)
func main() {
l := lars.New()
// LoggingAndRecovery is just an example copy paste and modify to your needs
l.Use(mw.LoggingAndRecovery)
l.Get("/", HelloWorld)
http.ListenAndServe(":3007", l.Serve())
}
// HelloWorld ...
func HelloWorld(c lars.Context) {
c.Response().Write([]byte("Hello World"))
// this will also work, Response() complies with http.ResponseWriter interface
fmt.Fprint(c.Response(), "Hello World")
}
l := l.New()
// the matching param will be stored in the Context's params with name "id"
l.Get("/user/:id", UserHandler)
// serve css, js etc.. c.Param(lars.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, and this router allowed it in a previous version, however it nearly cost me in a big app where the dynamic param value say :type actually could have matched another static route and that's just too dangerous, so it is no longer allowed.
l.Use(LoggingAndRecovery)
...
l.Post("/users/add", ...)
// creates a group for user + inherits all middleware registered using l.Use()
user := l.Group("/user/:userid")
user.Get("", ...)
user.Post("", ...)
user.Delete("/delete", ...)
contactInfo := user.Group("/contact-info/:ciid")
contactinfo.Delete("/delete", ...)
// creates a group for others + inherits all middleware registered using l.Use() + adds
// OtherHandler to middleware
others := l.Group("/others", OtherHandler)
// creates a group for admin WITH NO MIDDLEWARE... more can be added using admin.Use()
admin := l.Group("/admin",nil)
admin.Use(SomeAdminSecurityMiddleware)
...
...
// MyContext is a custom context
type MyContext struct {
*lars.Ctx // a little dash of Duck Typing....
}
// RequestStart overriding
func (mc *MyContext) RequestStart(w http.ResponseWriter, r *http.Request) {
mc.Ctx.RequestStart(w, r) // MUST be called!
// do whatever you need to on request start, db connections, variable init...
}
// RequestEnd overriding
func (mc *MyContext) RequestEnd() {
// do whatever you need on request finish, reset variables, db connections...
mc.Ctx.RequestEnd() // MUST be called!
}
// CustomContextFunction is a function that is specific to your applications needs that you added
func (mc *MyContext) CustomContextFunction() {
// do something
}
// newContext is the function that creates your custom context +
// contains lars's default context
func newContext(l *lars.LARS) lars.Context {
return &MyContext{
Ctx: lars.NewContext(l),
}
}
// casts custom context and calls you custom handler so you don;t have to type cast lars.Context everywhere
func castCustomContext(c lars.Context, handler lars.Handler) {
// could do it in all one statement, but in long form for readability
h := handler.(func(*MyContext))
ctx := c.(*MyContext)
h(ctx)
}
func main() {
l := lars.New()
l.RegisterContext(newContext) // all gets cached in pools for you
l.RegisterCustomHandler(func(*MyContext) {}, castCustomContext)
l.Use(Logger)
l.Get("/", Home)
http.ListenAndServe(":3007", l.Serve())
}
// Home ...notice the receiver is *MyContext, castCustomContext handled the type casting for us
// quite the time saver if you ask me.
func Home(c *MyContext) {
c.CustomContextFunction()
...
}
...
// can register multiple handlers, the last is considered the last in the chain and others
// considered middleware, but just for this route and not added to middleware like l.Use() does.
l.Get(/"home", AdditionalHandler, HomeHandler)
// set custom 404 ( not Found ) handler
l.Register404(404Handler)
// 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.SetHandle405MethodNotAllowed(false)
// automatically handle OPTION requests; manually configured
// OPTION handlers take precedence. default true
l.SetAutomaticallyHandleOPTIONS(set bool)
// register custom context
l.RegisterContext(ContextFunc)
// Register custom handler type, see https://github.com/go-playground/lars/blob/master/util.go#L62
// for example handler creation
l.RegisterCustomHandler(interface{}, CustomHandlerFunc)
// NativeChainHandler is used as a helper to create your own custom handlers, or use custom handlers
// that already exist an example usage can be found here
// https://github.com/go-playground/lars/blob/master/util.go#L86, below is an example using nosurf CSRF middleware
l.Use(nosurf.NewPure(lars.NativeChainHandler))
// Context has 2 methods of which you should be aware of ParseForm and ParseMulipartForm, they just call the
// default http functions but provide one more additional feature, they copy the URL params to the request
// Forms variables, just like Query parameters would have been.
// The functions are for convenience and are totally optional.
There are some pre-defined middlewares within the middleware folder; NOTE: that the middleware inside will comply with the following rule(s):
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/...
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
NOTICE: lars uses a custom version of httprouter, benchmarks can be found here
go test -bench=. -benchmem=true
#GithubAPI Routes: 203
LARS: 49040 Bytes
#GPlusAPI Routes: 13
LARS: 3648 Bytes
#ParseAPI Routes: 26
LARS: 6640 Bytes
#Static Routes: 157
LARS: 30128 Bytes
PASS
BenchmarkLARS_Param 20000000 77.1 ns/op 0 B/op 0 allocs/op
BenchmarkLARS_Param5 10000000 134 ns/op 0 B/op 0 allocs/op
BenchmarkLARS_Param20 5000000 320 ns/op 0 B/op 0 allocs/op
BenchmarkLARS_ParamWrite 10000000 142 ns/op 0 B/op 0 allocs/op
BenchmarkLARS_GithubStatic 20000000 96.2 ns/op 0 B/op 0 allocs/op
BenchmarkLARS_GithubParam 10000000 156 ns/op 0 B/op 0 allocs/op
BenchmarkLARS_GithubAll 50000 32952 ns/op 0 B/op 0 allocs/op
BenchmarkLARS_GPlusStatic 20000000 72.2 ns/op 0 B/op 0 allocs/op
BenchmarkLARS_GPlusParam 20000000 98.0 ns/op 0 B/op 0 allocs/op
BenchmarkLARS_GPlus2Params 10000000 127 ns/op 0 B/op 0 allocs/op
BenchmarkLARS_GPlusAll 1000000 1619 ns/op 0 B/op 0 allocs/op
BenchmarkLARS_ParseStatic 20000000 72.8 ns/op 0 B/op 0 allocs/op
BenchmarkLARS_ParseParam 20000000 78.6 ns/op 0 B/op 0 allocs/op
BenchmarkLARS_Parse2Params 20000000 96.9 ns/op 0 B/op 0 allocs/op
BenchmarkLARS_ParseAll 500000 2968 ns/op 0 B/op 0 allocs/op
BenchmarkLARS_StaticAll 100000 22810 ns/op 0 B/op 0 allocs/op
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.