Giter Site home page Giter Site logo

lion's Introduction

Lion Build Status GoDoc License Go Report Card

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

// 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(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) {
	// 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(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()

// Group by /api basepath
api := l.Group("/api")

// Specific to v1
v1 := api.Subrouter().
	Host("v1.example.org")

v1.Get("/", v1Handler)

// Specific to v2
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:

// 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(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:

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

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()  {
	// 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(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

  • Support for Go 1.7 context
  • Host matching
  • Automatic OPTIONS handler
  • Modules
    • JWT Auth module
  • Better static file handling
  • More documentation

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

lion's People

Contributors

akagi201 avatar celrenheit avatar ttacon avatar vrischmann avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

lion's Issues

router fails for named param routes

Just an FYI,

if you have defined the routes:
GET /hello/contact/named => handler1
GET /hello/contact/:dest => handler2

and then do a request..
GET /hello/contact/nameddd it would be expected that the route would match /hello/contact/:dest with the param nameddd, but instead, it returns a 404. This problem grows as you try to compose subrouters.

btw, this does work in the current version of http://github.com/pressly/chi but unfortunately uses recursion which means it uses a few more stack frames for finding the route. I am in the process of updating the tree findNode() method in chi to use a trampoline function and removing the recursive calls.

compare your method https://github.com/celrenheit/lion/blob/master/tree.go#L138-L209 to https://github.com/pressly/chi/blob/master/tree.go#L181-L253

Multiple resource types

Currently there is only one form of resources (RESTful).
This is a small proposal to extend resource to multiple type.

RESTful

router.Resource("/users", myResource)
HTTP method Path Struct's method name
GET /users Get
Head /users Head
POST /users Post
PUT /users Put
PATCH /users Patch
DELETE /users Delete
CONNECT /users Connect

Resourceful

router.Resource("/users", myResource)
HTTP method Path Struct's method name
GET /users Index
POST /users Create
GET /users/:id Show
PUT or PATCH /users/:id Update
DELETE /users/:id Destroy

Hopefully this could be customized.
I currently have not decided the best way to specify differents types.
The resource could implement an interface or by embedding a type.

Feedback are welcome

Bug on URL param's

In following code, I'm found some problens on parameters:

package main

import (
    "fmt"
    "net/http"

    "github.com/celrenheit/lion"
    "golang.org/x/net/context"
)

func Handler(c context.Context, w http.ResponseWriter, r *http.Request) {
    dns := lion.Param(c, "DNS")
    fmt.Fprintf(w, "DNS: "+dns)
}

func Handler2(c context.Context, w http.ResponseWriter, r *http.Request) {
    dns := lion.Param(c, "Anything")
    dnsDiscography := lion.Param(c, "DNSDiscography")

    fmt.Fprintf(w, "DNS: "+dns+" DNSDiscography: "+dnsDiscography)
}

func Handler3(c context.Context, w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "I'm here!")
}

func main() {
    l := lion.Classic()
    l.GetFunc("/artistas/:Anything/discografia/:DNSDiscography/", Handler2)
    l.GetFunc("/artistas/:DNS/", Handler)
    l.GetFunc("/artistas/", Handler3)
    l.Run()
}

Results:

$ curl http://localhost:3000/artistas/test/
DNS:

$ curl http://localhost:3000/artistas/test/discografia/testing/
DNS: test DNSDiscography: testing

If I change Handler to get "Anything" param without change the route, it's get "DNS" from route. Following code and resutls:

package main

import (
    "fmt"
    "net/http"

    "github.com/celrenheit/lion"
    "golang.org/x/net/context"
)

func Handler(c context.Context, w http.ResponseWriter, r *http.Request) {
    dns := lion.Param(c, "Anything")
    fmt.Fprintf(w, "DNS: "+dns)
}

func Handler2(c context.Context, w http.ResponseWriter, r *http.Request) {
    dns := lion.Param(c, "Anything")
    dnsDiscography := lion.Param(c, "DNSDiscography")

    fmt.Fprintf(w, "DNS: "+dns+" DNSDiscography: "+dnsDiscography)
}

func Handler3(c context.Context, w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "I'm here!")
}

func main() {
    l := lion.Classic()
    l.GetFunc("/artistas/:Anything/discografia/:DNSDiscography/", Handler2)
    l.GetFunc("/artistas/:DNS/", Handler)
    l.GetFunc("/artistas/", Handler3)
    l.Run()
}

Results:

$ curl http://localhost:3000/artistas/test/
DNS: test

$ curl http://localhost:3000/artistas/test/discografia/testing/
DNS: test DNSDiscography: testing

I not sure about it, but I thing which /artistas/:Anything/... is match before /artistas/:DNS/ and :Anything overwrite :DNS param.

If I switch positions of url's, I found this results:

package main

import (
    "fmt"
    "net/http"

    "github.com/celrenheit/lion"
    "golang.org/x/net/context"
)

func Handler(c context.Context, w http.ResponseWriter, r *http.Request) {
    dns := lion.Param(c, "DNS")
    fmt.Fprintf(w, "DNS: "+dns)
}

func Handler2(c context.Context, w http.ResponseWriter, r *http.Request) {
    dns := lion.Param(c, "Anything")
    dnsDiscography := lion.Param(c, "DNSDiscography")

    fmt.Fprintf(w, "DNS: "+dns+" DNSDiscography: "+dnsDiscography)
}

func Handler3(c context.Context, w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "I'm here!")
}

func main() {
    l := lion.Classic()
    l.GetFunc("/artistas/:DNS/", Handler)
    l.GetFunc("/artistas/:Anything/discografia/:DNSDiscography/", Handler2)
    l.GetFunc("/artistas/", Handler3)
    l.Run()
}

Results:

$ curl http://localhost:3000/artistas/test/
DNS: test

$ curl http://localhost:3000/artistas/test/discografia/testing/
DNS:  DNSDiscography: testing

In this case, if I try to get DNS on Handler2 it's get "Anything" from url. Bellow code and results:

package main

import (
    "fmt"
    "net/http"

    "github.com/celrenheit/lion"
    "golang.org/x/net/context"
)

func Handler(c context.Context, w http.ResponseWriter, r *http.Request) {
    dns := lion.Param(c, "DNS")
    fmt.Fprintf(w, "DNS: "+dns)
}

func Handler2(c context.Context, w http.ResponseWriter, r *http.Request) {
    dns := lion.Param(c, "DNS")
    dnsDiscography := lion.Param(c, "DNSDiscography")

    fmt.Fprintf(w, "DNS: "+dns+" DNSDiscography: "+dnsDiscography)
}

func Handler3(c context.Context, w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "I'm here!")
}

func main() {
    l := lion.Classic()
    l.GetFunc("/artistas/:DNS/", Handler)
    l.GetFunc("/artistas/:Anything/discografia/:DNSDiscography/", Handler2)
    l.GetFunc("/artistas/", Handler3)
    l.Run()
}
$ curl http://localhost:3000/artistas/test/
DNS: test

$ curl http://localhost:3000/artistas/test/discografia/testing/
DNS: test DNSDiscography: testing

"color" dependancy

go get github.com/celrenheit/lion
# github.com/celrenheit/lion
../../celrenheit/lion/middlewares.go:20: undefined: color.FgHiMagenta
../../celrenheit/lion/middlewares.go:24: undefined: color.FgHiBlue
../../celrenheit/lion/middlewares.go:25: undefined: color.FgHiGreen

might want to vendor, or add to the docs

label: question Serving static files

@celrenheit What would be the simplest form to serve static files with Lion? I'm attempting to get the following folders "css", "js", "img", and "templates" all within a root folder named "public".
for _, dir := range []string{"js", "css", "img", "templates"} { lion.ServeFiles(fmt.Sprintf("/%s/", dir)) }

Mount bug fix


func main() {
	l := lion.New(middleware.Classic())

	sub := lion.New()
	//normal return
	sub.GET("/", func(ctx lion.Context) {
		ctx.Write([]byte("/"))
	})

	//404 page not found ?
	sub.GET("/a", func(ctx lion.Context) {
		ctx.Write([]byte("a"))
	})
	//normal return
	sub.GET("/b", func(ctx lion.Context) {
		ctx.Write([]byte("b"))
	})

	//404 page not found ?
	sub.GET("/c", func(ctx lion.Context) {
		ctx.Write([]byte("c"))
	})
	//normal return
	sub.GET("/d", func(ctx lion.Context) {
		ctx.Write([]byte("d"))
	})

	l.Mount("/api", sub)

	l.Run()
}

@celrenheit

Contextual rendering

Implementation of a contextual renderer:

  • Render JSON
  • Render XML
  • Render String
  • Render File
  • Attachment
  • Redirect

I would like your feedback on what is your preferred way of handling responses and status codes.

Currently, it works like this (1):

func render(c Context) {
	c.WithStatus(http.StatusOK).
		JSON(data)
}

But I might consider the following way (2):

func render(c Context) {
	c.JSON(http.StatusOK, data)
}

Please, add a comment to this issue with "1" for the first or "2" for the second.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.