github

celrenheit / lion

  • вторник, 22 марта 2016 г. в 02:12:00
https://github.com/celrenheit/lion

Go
Lion is a fast HTTP router for building modern scalable modular REST APIs in Go



Lion Build Status GoDoc License

Lion is a fast HTTP router for Go with support for middlewares for building modern scalable modular REST APIs.

Lion's Hello World GIF

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.
  • Zero allocations: Lion generates zero garbage*.

Table of contents

Install/Update

$ go get -u github.com/celrenheit/lion

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()
}

// Products module is accessible at url: /api/products
// It handles getting a list of products or creating a new product
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) {
    // Defining a resource for getting, editing and deleting a single product
    r.Resource("/:id", OneProduct{})
}

// OneProduct resource is accessible at url: /api/products/:id
// It handles getting, editing and deleting a single product
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 using 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 using 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
}

You can also use Negroni middlewares by registering them using:

l := lion.New()
l.UseNegroni(negroni.NewRecovery())
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:

// Global middlewares for the resource (Optional)
Uses() Middlewares

// Middlewares for the http methods (Optional)
GetMiddlewares() Middlewares
PostMiddlewares() Middlewares
PutMiddlewares() Middlewares
DeleteMiddlewares() Middlewares


// HandlerFuncs for each HTTP Methods (Optional)
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:

// Required: Base url pattern of the module
Base() string

// Routes accepts a Router instance. This method is used to define the routes of this module.
// Each routes defined are relative to the Base() url pattern
Routes(*Router)

// Optional: Requires named middlewares. Refer to Named Middlewares section
Requires() []string
package main

type api struct{}

// Required: Base url
func (t api) Base() string { return "/api" }

// Required: Here you can declare sub-resources, submodules and custom routes.
func (t api) Routes(r *lion.Router) {
    r.Module(v1{})
    r.Get("/custom", t.CustomRoute)
}

// Optional: Attach Get method to this Module.
// ====> A Module is also a Resource.
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")
}

// Optional: Defining custom routes
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()
    // Registering the module
    l.Module(api{})
    l.Run()
}

Examples

Using GET, POST, PUT, DELETE http methods

l := lion.Classic()

// Using Handlers
l.Get("/get", get)
l.Post("/post", post)
l.Put("/put", put)
l.Delete("/delete", delete)

// Using functions
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()

    // Using middleware
    l.Use(lion.NewLogger())

    // Using middleware functions
    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()

Mouting 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()  {
    // This a no middlewares registered
    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.Clean

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.Clean

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

  • Better static file handling
  • More documentation

Credits