Lion
Lion is a fast HTTP router for building modern scalable modular REST APIs written in Go (golang).
Features
- Go1-like guarantee: The API will not change in Lion v2.x (once released).
- Modular: You can define your own modules to easily build a scalable architecture
- RESTful: You can define modules to groups http resources together.
- Subdomains: Select which subdomains, hosts a router can match. You can specify it a param or a wildcard e.g.
*.example.org
. More infos here. - Near zero garbage: Lion generates near zero garbage*. The allocations generated comes from the net/http.Request.WithContext() works.
It makes a shallow copy of the request.
Table of contents
Install/Update
Lion requires Go 1.7+:
$ go get -u github.com/celrenheit/lion
Next versions of Lion will support the latest Go version and the previous one.
For example, when Go 1.8 is out, Lion will support Go 1.7 and Go 1.8.
Hello World
package main
import (
"fmt"
"net/http"
"github.com/celrenheit/lion"
"context"
)
func Home(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Home")
}
func Hello(w http.ResponseWriter, r *http.Request) {
name := lion.Param(r, "name")
fmt.Fprintf(w, "Hello %s!",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(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Fetching all products")
}
func (p Products) Post(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(w http.ResponseWriter, r *http.Request) {
id := lion.Param(r, "id")
fmt.Fprintf(w, "Getting product: %s", id)
}
func (p OneProduct) Put(w http.ResponseWriter, r *http.Request) {
id := lion.Param(r, "id")
fmt.Fprintf(w, "Updating article: %s", id)
}
func (p OneProduct) Delete(w http.ResponseWriter, r *http.Request) {
id := lion.Param(r, "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".
Using net/http.Handler
Handlers should implement the native net/http.Handler:
l.Get("/get", get)
l.Post("/post", post)
l.Put("/put", put)
l.Delete("/delete", delete)
Using net/http.HandlerFunc
You can use native net/http.Handler (func(w http.ResponseWriter, r *http.Request)
):
func myHandlerFunc(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hi!")
}
l.GetFunc("/get", myHandlerFunc)
l.PostFunc("/post", myHandlerFunc)
l.PutFunc("/put", myHandlerFunc)
l.DeleteFunc("/delete", myHandlerFunc)
Middlewares
Middlewares should implement the Middleware interface:
type Middleware interface {
ServeNext(http.Handler) http.Handler
}
The ServeNext function accepts a http.Handler and returns a http.Handler.
You can also use MiddlewareFuncs which are basically just func(http.Handler) http.Handler
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 Third-Party Middlewares
Negroni
In v1, negroni was supported directly using UseNegroni.
It still works but you will have to use .UseNext and pass it a negroni.HandlerFunc:
func(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc)
This way if you prefer to use this kind of middleware, you can.
You can use Negroni middlewares
you can find a list of third party middlewares here
l := lion.New()
l.UseNext(negroni.NewRecovery().ServeHTTP)
l.Run()
Matching Subdomains/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(w http.ResponseWriter, r *http.Request)
Post(w http.ResponseWriter, r *http.Request)
Put(w http.ResponseWriter, r *http.Request)
Delete(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(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(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "This also a resource accessible at http://localhost:3000/api")
}
func (t api) CustomRoute(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(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
TODO: Update this when v2 is released.
Contributing
Want to contribute to Lion ? Awesome! Feel free to submit an issue or a pull request.
Here are some ways you can help:
- Report bugs
- Share a middleware or a module
- Improve code/documentation
- Propose new features
- and more...
License
The project is licensed under the MIT license available here: https://github.com/celrenheit/lion/blob/master/LICENSE.
The benchmarks present in bench_test.go is licensed under a BSD-style license available here: https://github.com/julienschmidt/go-http-routing-benchmark/blob/master/LICENSE.
Todo
Credits
Lion v1 was inspired by pressly/chi.
If Lion is not the right http router for you, check out chi.
Parts of Lion taken for other projects:
- Negroni
- Static and Recovery middlewares are taken from Negroni
- Goji
- RealIP middleware is taken from goji
- Gin
- ResponseWriter and Logger inspiration are inspired by gin