Lion
Lion is a fast HTTP router for Go with support for middlewares for building modern scalable modular REST APIs.
Lion v2
If you are starting a new project, please consider starting out using the v2
and the new documentation branch. It contains a few breaking changes.
The most important one is that it now uses native http.Handler.
Important
If you are using lion v1, please change your import path to gopkg.in/celrenheit/lion.v1
.
You can get lion via:
go get -u gopkg.in/celrenheit/lion.v1
Features
- Context-Aware: Lion uses the de-facto standard net/Context for storing route params and sharing variables between middlewares and HTTP handlers. It could be integrated in the standard library for Go 1.7 in 2016.
- Modular: You can define your own modules to easily build a scalable architecture
- REST friendly: You can define modules to groups http resources together.
- Host: Match hosts. Each host can get its own content.
- Zero allocations: Lion generates zero garbage*.
Table of contents
Install/Update
$ go get -u gopkg.in/celrenheit/lion.v1
Hello World
package main
import (
"fmt"
"net/http"
"github.com/celrenheit/lion"
"golang.org/x/net/context"
)
func Home(c context.Context, w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Home")
}
func Hello(c context.Context, w http.ResponseWriter, r *http.Request) {
name := lion.Param(c, "name")
fmt.Fprintf(w, "Hello "+name)
}
func main() {
l := lion.Classic()
l.GetFunc("/", Home)
l.GetFunc("/hello/:name", Hello)
l.Run()
}
Try it yourself by running the following command from the current directory:
$ go run examples/hello/hello.go
Getting started with modules and resources
We are going to build a sample products listing REST api (without database handling to keep it simple):
func main() {
l := lion.Classic()
api := l.Group("/api")
api.Module(Products{})
l.Run()
}
type Products struct{}
func (p Products) Base() string {
return "/products"
}
func (p Products) Get(c context.Context, w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Fetching all products")
}
func (p Products) Post(c context.Context, w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Creating a new product")
}
func (p Products) Routes(r *lion.Router) {
r.Resource("/:id", OneProduct{})
}
type OneProduct struct{}
func (p OneProduct) Get(c context.Context, w http.ResponseWriter, r *http.Request) {
id := lion.Param(c, "id")
fmt.Fprintf(w, "Getting product: %s", id)
}
func (p OneProduct) Put(c context.Context, w http.ResponseWriter, r *http.Request) {
id := lion.Param(c, "id")
fmt.Fprintf(w, "Updating article: %s", id)
}
func (p OneProduct) Delete(c context.Context, w http.ResponseWriter, r *http.Request) {
id := lion.Param(c, "id")
fmt.Fprintf(w, "Deleting article: %s", id)
}
Try it yourself. Run:
$ go run examples/modular-hello/modular-hello.go
Open your web browser to http://localhost:3000/api/products or http://localhost:3000/api/products/123. You should see "Fetching all products" or "Getting product: 123".
Handlers
Handlers should implement the Handler interface:
type Handler interface {
ServeHTTPC(context.Context, http.ResponseWriter, *http.Request)
}
Using Handlers
l.Get("/get", get)
l.Post("/post", post)
l.Put("/put", put)
l.Delete("/delete", delete)
Using HandlerFuncs
HandlerFuncs shoud have this function signature:
func handlerFunc(c context.Context, w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hi!")
}
l.GetFunc("/get", handlerFunc)
l.PostFunc("/post", handlerFunc)
l.PutFunc("/put", handlerFunc)
l.DeleteFunc("/delete", handlerFunc)
Using native http.Handler
type nativehandler struct {}
func (_ nativehandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
}
l.GetH("/somepath", nativehandler{})
l.PostH("/somepath", nativehandler{})
l.PutH("/somepath", nativehandler{})
l.DeleteH("/somepath", nativehandler{})
Using native http.Handler with lion.Wrap()
Note: using native http handler you cannot access url params.
func main() {
l := lion.New()
l.Get("/somepath", lion.Wrap(nativehandler{}))
}
Using native http.Handler with lion.WrapFunc()
func getHandlerFunc(w http.ResponseWriter, r *http.Request) {
}
func main() {
l := lion.New()
l.Get("/somepath", lion.WrapFunc(getHandlerFunc))
}
Middlewares
Middlewares should implement the Middleware interface:
type Middleware interface {
ServeNext(Handler) Handler
}
The ServeNext function accepts a Handler and returns a Handler.
You can also use MiddlewareFuncs. For example:
func middlewareFunc(next Handler) Handler {
return next
}
Using Named Middlewares
Named middlewares are designed to be able to reuse a previously defined middleware. For example, if you have a EnsureAuthenticated middleware that check whether a user is logged in.
You can define it once and reuse later in your application.
l := lion.New()
l.Define("EnsureAuthenticated", NewEnsureAuthenticatedMiddleware())
To reuse it later in your application, you can use the UseNamed
method. If it cannot find the named middleware if the current Router instance it will try to find it in the parent router.
If a named middleware is not found it will panic.
api := l.Group("/api")
api.UseNamed("EnsureAuthenticated")
Using Negroni Middlewares
You can use Negroni middlewares you can find a list of third party middlewares here
l := lion.New()
l.UseNegroni(negroni.NewRecovery())
l.Run()
Matching Hosts
You can match a specific or multiple hosts. You can use patterns in the same way they are currently used for routes with only some edge cases.
The main difference is that you will have to use the '$' character instead of ':' to define a parameter.
admin.example.com will match admin.example.com
$username.blog.com will match messi.blog.com
will not match my.awesome.blog.com
*.example.com will match my.admin.example.com
l := New()
api := l.Group("/api")
v1 := api.Subrouter().
Host("v1.example.org")
v1.Get("/", v1Handler)
v2 := api.Subrouter().
Host("v2.example.org")
v2.Get("/", v2Handler)
l.Run()
Resources
You can define a resource to represent a REST, CRUD api resource.
You define global middlewares using Uses() method. For defining custom middlewares for each http method, you have to create a function which name is composed of the http method suffixed by "Middlewares". For example, if you want to define middlewares for the Get method you will have to create a method called: GetMiddlewares().
A resource is defined by the following methods. Everything is optional:
Uses() Middlewares
GetMiddlewares() Middlewares
PostMiddlewares() Middlewares
PutMiddlewares() Middlewares
DeleteMiddlewares() Middlewares
Get(c context.Context, w http.ResponseWriter, r *http.Request)
Post(c context.Context, w http.ResponseWriter, r *http.Request)
Put(c context.Context, w http.ResponseWriter, r *http.Request)
Delete(c context.Context, w http.ResponseWriter, r *http.Request)
Example:
package main
type todolist struct{}
func (t todolist) Uses() lion.Middlewares {
return lion.Middlewares{lion.NewLogger()}
}
func (t todolist) Get(c context.Context, w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "getting todos")
}
func main() {
l := lion.New()
l.Resource("/todos", todolist{})
l.Run()
}
## Modules
Modules are a way to modularize an api which can then define submodules, subresources and custom routes.
A module is defined by the following methods:
Base() string
Routes(*Router)
Requires() []string
package main
type api struct{}
func (t api) Base() string { return "/api" }
func (t api) Routes(r *lion.Router) {
r.Module(v1{})
r.Get("/custom", t.CustomRoute)
}
func (t api) Get(c context.Context, w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "This also a resource accessible at http://localhost:3000/api")
}
func (t api) CustomRoute(c context.Context, w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "This a custom route for this module http://localhost:3000/api/")
}
func main() {
l := lion.New()
l.Module(api{})
l.Run()
}
Examples
Using GET, POST, PUT, DELETE http methods
l := lion.Classic()
l.Get("/get", get)
l.Post("/post", post)
l.Put("/put", put)
l.Delete("/delete", delete)
l.GetFunc("/get", getFunc)
l.PostFunc("/post", postFunc)
l.PutFunc("/put", putFunc)
l.DeleteFunc("/delete", deleteFunc)
l.Run()
Using middlewares
func main() {
l := lion.Classic()
l.Use(lion.NewLogger())
l.UseFunc(someMiddlewareFunc)
l.GetFunc("/hello/:name", Hello)
l.Run()
}
Group routes by a base path
l := lion.Classic()
api := l.Group("/api")
v1 := l.Group("/v1")
v1.GetFunc("/somepath", gettingFromV1)
v2 := l.Group("/v2")
v2.GetFunc("/somepath", gettingFromV2)
l.Run()
Mounting a router into a base path
l := lion.Classic()
sub := lion.New()
sub.GetFunc("/somepath", getting)
l.Mount("/api", sub)
Default middlewares
lion.Classic()
creates a router with default middlewares (Recovery, RealIP, Logger, Static).
If you wish to create a blank router without any middlewares you can use lion.New()
.
func main() {
l := lion.New()
l.Use(lion.NewLogger())
l.GetFunc("/hello/:name", Hello)
l.Run()
}
Custom Middlewares
Custom middlewares should implement the Middleware interface:
type Middleware interface {
ServeNext(Handler) Handler
}
You can also make MiddlewareFuncs to use using .UseFunc()
method.
It has to accept a Handler and return a Handler:
func(next Handler) Handler
Custom Logger example
type logger struct{}
func (*logger) ServeNext(next lion.Handler) lion.Handler {
return lion.HandlerFunc(func(c context.Context, w http.ResponseWriter, r *http.Request) {
start := time.Now()
next.ServeHTTPC(c, w, r)
fmt.Printf("Served %s in %s\n", r.URL.Path, time.Since(start))
})
}
Then in the main function you can use the middleware using:
l := lion.New()
l.Use(&logger{})
l.GetFunc("/hello/:name", Hello)
l.Run()
Benchmarks
Without path cleaning
BenchmarkLion_Param 10000000 164 ns/op 0 B/op 0 allocs/op
BenchmarkLion_Param5 5000000 372 ns/op 0 B/op 0 allocs/op
BenchmarkLion_Param20 1000000 1080 ns/op 0 B/op 0 allocs/op
BenchmarkLion_ParamWrite 10000000 180 ns/op 0 B/op 0 allocs/op
BenchmarkLion_GithubStatic 10000000 160 ns/op 0 B/op 0 allocs/op
BenchmarkLion_GithubParam 5000000 359 ns/op 0 B/op 0 allocs/op
BenchmarkLion_GithubAll 30000 62888 ns/op 0 B/op 0 allocs/op
BenchmarkLion_GPlusStatic 20000000 104 ns/op 0 B/op 0 allocs/op
BenchmarkLion_GPlusParam 10000000 182 ns/op 0 B/op 0 allocs/op
BenchmarkLion_GPlus2Params 5000000 286 ns/op 0 B/op 0 allocs/op
BenchmarkLion_GPlusAll 500000 3227 ns/op 0 B/op 0 allocs/op
BenchmarkLion_ParseStatic 10000000 123 ns/op 0 B/op 0 allocs/op
BenchmarkLion_ParseParam 10000000 145 ns/op 0 B/op 0 allocs/op
BenchmarkLion_Parse2Params 10000000 212 ns/op 0 B/op 0 allocs/op
BenchmarkLion_ParseAll 300000 5242 ns/op 0 B/op 0 allocs/op
BenchmarkLion_StaticAll 50000 37998 ns/op 0 B/op 0 allocs/op
With path cleaning
BenchmarkLion_Param 10000000 227 ns/op 0 B/op 0 allocs/op
BenchmarkLion_Param5 3000000 427 ns/op 0 B/op 0 allocs/op
BenchmarkLion_Param20 1000000 1321 ns/op 0 B/op 0 allocs/op
BenchmarkLion_ParamWrite 5000000 256 ns/op 0 B/op 0 allocs/op
BenchmarkLion_GithubStatic 10000000 214 ns/op 0 B/op 0 allocs/op
BenchmarkLion_GithubParam 3000000 445 ns/op 0 B/op 0 allocs/op
BenchmarkLion_GithubAll 20000 88664 ns/op 0 B/op 0 allocs/op
BenchmarkLion_GPlusStatic 10000000 122 ns/op 0 B/op 0 allocs/op
BenchmarkLion_GPlusParam 5000000 381 ns/op 0 B/op 0 allocs/op
BenchmarkLion_GPlus2Params 5000000 409 ns/op 0 B/op 0 allocs/op
BenchmarkLion_GPlusAll 500000 3952 ns/op 0 B/op 0 allocs/op
BenchmarkLion_ParseStatic 10000000 146 ns/op 0 B/op 0 allocs/op
BenchmarkLion_ParseParam 10000000 187 ns/op 0 B/op 0 allocs/op
BenchmarkLion_Parse2Params 5000000 314 ns/op 0 B/op 0 allocs/op
BenchmarkLion_ParseAll 200000 7857 ns/op 0 B/op 0 allocs/op
BenchmarkLion_StaticAll 30000 56170 ns/op 96 B/op 8 allocs/op
A more in depth benchmark with a comparison with other frameworks is coming soon.
License
https://github.com/celrenheit/lion/blob/master/LICENSE
Todo
Credits