gorouter

package module
v1.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 12, 2019 License: MIT Imports: 6 Imported by: 58

README

gorouter GoDoc Build Status Go Report Card Coverage Status License Release Awesome

xujiajun/gorouter is a simple and fast HTTP router for Go. It is easy to build RESTful APIs and your web framework.

Motivation

I wanted a simple and fast HTTP GO router, which supports regexp. I prefer to support regexp is because otherwise it will need the logic to check the URL parameter type, thus increasing the program complexity. So I did some searching on Github and found the wonderful julienschmidt/httprouter: it is very fast,unfortunately it does not support regexp. Later I found out about gorilla/mux: it is powerful as well,but a written benchmark shows me that it is somewhat slow. So I tried to develop a new router which both supports regexp and should be fast. Finally I did it and named xujiajun/gorouter. By the way, this is my first GO open source project. It may be the fastest GO HTTP router which supports regexp, and regarding its performance please refer to my latest Benchmarks.

Features

Requirements

  • golang 1.8+

Installation

go get -u github.com/xujiajun/gorouter

Usage

Static routes
package main

import (
	"log"
	"net/http"

	"github.com/xujiajun/gorouter"
)

func main() {
	mux := gorouter.New()
	mux.GET("/", func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte("hello world"))
	})
	log.Fatal(http.ListenAndServe(":8181", mux))
}

URL Parameters
package main

import (
	"log"
	"net/http"

	"github.com/xujiajun/gorouter"
)

func main() {
	mux := gorouter.New()
	//url parameters match
	mux.GET("/user/:id", func(w http.ResponseWriter, r *http.Request) {
		//get one URL parameter
		id := gorouter.GetParam(r, "id")
		//get all URL parameters
		//id := gorouter.GetAllParams(r)
		//fmt.Println(id)
		w.Write([]byte("match user/:id ! get id:" + id))
	})

	log.Fatal(http.ListenAndServe(":8181", mux))
}
Regex Parameters
package main

import (
	"log"
	"net/http"

	"github.com/xujiajun/gorouter"
)

func main() {
	mux := gorouter.New()
	//url regex match
	mux.GET("/user/{id:[0-9]+}", func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte("match user/{id:[0-9]+} !"))
	})

	log.Fatal(http.ListenAndServe(":8181", mux))
}
Routes Groups
package main

import (
	"fmt"
	"log"
	"net/http"

	"github.com/xujiajun/gorouter"
)

func usersHandler(w http.ResponseWriter, r *http.Request) {
	fmt.Fprint(w, "/api/users")
}

func main() {
	mux := gorouter.New()
	mux.Group("/api").GET("/users", usersHandler)

	log.Fatal(http.ListenAndServe(":8181", mux))
}
Reverse Routing
package main

import (
	"fmt"
	"net/http"

	"github.com/xujiajun/gorouter"
)

func main() {
	mux := gorouter.New()

	routeName1 := "user_event"
	mux.GETAndName("/users/:user/events", func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte("/users/:user/events"))
	}, routeName1)

	routeName2 := "repos_owner"
	mux.GETAndName("/repos/{owner:\\w+}/{repo:\\w+}", func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte("/repos/{owner:\\w+}/{repo:\\w+}"))
	}, routeName2)

	params := make(map[string]string)
	params["user"] = "xujiajun"
	fmt.Println(mux.Generate(http.MethodGet, routeName1, params)) // /users/xujiajun/events <nil>

	params = make(map[string]string)
	params["owner"] = "xujiajun"
	params["repo"] = "xujiajun_repo"
	fmt.Println(mux.Generate(http.MethodGet, routeName2, params)) // /repos/xujiajun/xujiajun_repo <nil>
}

Custom NotFoundHandler
package main

import (
	"fmt"
	"log"
	"net/http"

	"github.com/xujiajun/gorouter"
)

func notFoundFunc(w http.ResponseWriter, r *http.Request) {
	w.WriteHeader(http.StatusNotFound)
	fmt.Fprint(w, "404 page !!!")
}

func main() {
	mux := gorouter.New()
	mux.NotFoundFunc(notFoundFunc)
	mux.GET("/", func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte("hello world"))
	})

	log.Fatal(http.ListenAndServe(":8181", mux))
}
Custom PanicHandler
package main

import (
	"fmt"
	"log"
	"net/http"

	"github.com/xujiajun/gorouter"
)

func main() {
	mux := gorouter.New()
	mux.PanicHandler = func(w http.ResponseWriter, req *http.Request, err interface{}) {
		w.WriteHeader(http.StatusInternalServerError)
		fmt.Println("err from recover is :", err)
		fmt.Fprint(w, "received a panic")
	}
	mux.GET("/panic", func(w http.ResponseWriter, r *http.Request) {
		panic("panic")
	})

	log.Fatal(http.ListenAndServe(":8181", mux))
}

Middlewares Chain
package main

import (
	"fmt"
	"log"
	"net/http"

	"github.com/xujiajun/gorouter"
)

type statusRecorder struct {
	http.ResponseWriter
	status int
}

func (rec *statusRecorder) WriteHeader(code int) {
	rec.status = code
	rec.ResponseWriter.WriteHeader(code)
}

//https://upgear.io/blog/golang-tip-wrapping-http-response-writer-for-middleware/
func withStatusRecord(next http.HandlerFunc) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		rec := statusRecorder{w, http.StatusOK}
		next.ServeHTTP(&rec, r)
		log.Printf("response status: %v\n", rec.status)
	}
}

func notFoundFunc(w http.ResponseWriter, r *http.Request) {
	w.WriteHeader(http.StatusNotFound)
	fmt.Fprint(w, "Not found page !")
}

func withLogging(next http.HandlerFunc) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		log.Printf("Logged connection from %s", r.RemoteAddr)
		next.ServeHTTP(w, r)
	}
}

func withTracing(next http.HandlerFunc) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		log.Printf("Tracing request for %s", r.RequestURI)
		next.ServeHTTP(w, r)
	}
}

func main() {
	mux := gorouter.New()
	mux.NotFoundFunc(notFoundFunc)
	mux.Use(withLogging, withTracing, withStatusRecord)
	mux.GET("/", func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte("hello world"))
	})

	log.Fatal(http.ListenAndServe(":8181", mux))
}

Serve static files

package main

import (
	"log"
	"net/http"
	"os"
	
	"github.com/xujiajun/gorouter"
)

//ServeFiles serve static resources
func ServeFiles(w http.ResponseWriter, r *http.Request) {
	wd, err := os.Getwd()
	if err != nil {
		log.Fatal(err)
	}

	dir := wd + "/examples/serveStaticFiles/files"
	http.StripPrefix("/files/", http.FileServer(http.Dir(dir))).ServeHTTP(w, r)
}

func main() {
	mux := gorouter.New()
	mux.GET("/hi", func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte("hi"))
	})
	//defined prefix
	mux2 := mux.Group("/files")
	//http://127.0.0.1:8181/files/demo.txt
	//will match
	mux2.GET("/{filename:[0-9a-zA-Z_.]+}", func(w http.ResponseWriter, r *http.Request) {
		ServeFiles(w, r)
	})

	//http://127.0.0.1:8181/files/a/demo2.txt
	//http://127.0.0.1:8181/files/a/demo.txt
	//will match
	mux2.GET("/{fileDir:[0-9a-zA-Z_.]+}/{filename:[0-9a-zA-Z_.]+}", func(w http.ResponseWriter, r *http.Request) {
		ServeFiles(w, r)
	})

	log.Fatal(http.ListenAndServe(":8181", mux))
}

Detail see serveStaticFiles example

Pattern Rule

The syntax here is modeled after julienschmidt/httprouter and gorilla/mux

Syntax Description Example
:name named parameter /user/:name
{name:regexp} named with regexp parameter /user/{name:[0-9a-zA-Z]+}
:id named with regexp parameter /user/:id

And :id is short for {id:[0-9]+}, :name are short for {name:[0-9a-zA-Z_]+}

if use default regex checks unless you know what you're doing

Benchmarks

the benchmarks code for gorouter be found in the gorouter-bench repository.

go test -bench=.

Benchmark System:
  • Go Version : go1.11.2 darwin/amd64
  • OS: Mac OS X 10.13.6
  • Architecture: x86_64
  • 16 GB 2133 MHz LPDDR3
  • CPU: 3.1 GHz Intel Core i7
Tested routers:

Thanks the author of httprouter: @julienschmidt give me advise about benchmark issues/24

Result:

Given some routing matching syntax differences, divide GithubAPI into two groups:

Using GithubAPI Result:
BenchmarkBeegoMuxRouterWithGithubAPI-8   	   10000	    142398 ns/op	  134752 B/op	    1038 allocs/op
BenchmarkBoneRouterWithGithubAPI-8       	    1000	   2104486 ns/op	  720160 B/op	    8620 allocs/op
BenchmarkTrieMuxRouterWithGithubAPI-8    	   20000	     80845 ns/op	   65856 B/op	     537 allocs/op
BenchmarkHttpRouterWithGithubAPI-8       	   50000	     30169 ns/op	   13792 B/op	     167 allocs/op
BenchmarkGoRouter1WithGithubAPI-8        	   30000	     57793 ns/op	   13832 B/op	     406 allocs/op

Using GithubAPI2 Result:
BenchmarkGoRouter2WithGithubAPI2-8       	   30000	     57613 ns/op	   13832 B/op	     406 allocs/op
BenchmarkChiRouterWithGithubAPI2-8       	   10000	    143224 ns/op	  104436 B/op	    1110 allocs/op
BenchmarkMuxRouterWithGithubAPI2-8       	     300	   4450731 ns/op	   61463 B/op	     995 allocs/op
All togther Result:
➜  gorouter git:(master) go test -bench=.
GithubAPI Routes: 203
GithubAPI2 Routes: 203
   BeegoMuxRouter: 111072 Bytes
   BoneRouter: 100992 Bytes
   ChiRouter: 71512 Bytes
   HttpRouter: 37016 Bytes
   trie-mux: 131128 Bytes
   MuxRouter: 1378496 Bytes
   GoRouter1: 83824 Bytes
   GoRouter2: 85584 Bytes
goos: darwin
goarch: amd64
pkg: github.com/xujiajun/gorouter
BenchmarkBeegoMuxRouterWithGithubAPI-8   	   10000	    142398 ns/op	  134752 B/op	    1038 allocs/op
BenchmarkBoneRouterWithGithubAPI-8       	    1000	   2104486 ns/op	  720160 B/op	    8620 allocs/op
BenchmarkTrieMuxRouterWithGithubAPI-8    	   20000	     80845 ns/op	   65856 B/op	     537 allocs/op
BenchmarkHttpRouterWithGithubAPI-8       	   50000	     30169 ns/op	   13792 B/op	     167 allocs/op
BenchmarkGoRouter1WithGithubAPI-8        	   30000	     57793 ns/op	   13832 B/op	     406 allocs/op
BenchmarkGoRouter2WithGithubAPI2-8       	   30000	     57613 ns/op	   13832 B/op	     406 allocs/op
BenchmarkChiRouterWithGithubAPI2-8       	   10000	    143224 ns/op	  104436 B/op	    1110 allocs/op
BenchmarkMuxRouterWithGithubAPI2-8       	     300	   4450731 ns/op	   61463 B/op	     995 allocs/op
PASS
ok  	github.com/xujiajun/gorouter	15.918s

Conclusions:
  • Performance (xujiajun/gorouter,julienschmidt/httprouter and teambition/trie-mux are fast)

  • Memory Consumption (xujiajun/gorouter and julienschmidt/httprouter are fewer)

  • Features (julienschmidt/httprouter not supports regexp,but others support it)

if you want a high performance router which supports regexp, maybe xujiajun/gorouter is good choice.

if you want a high performance router which not supports regexp, maybe julienschmidt/httprouter is good choice.

In the end, as julienschmidt said performance can not be the (only) criterion for choosing a router. Play around a bit with some of the routers, and choose the one you like best.

Contributing

If you'd like to help out with the project. You can put up a Pull Request. Thanks to all contributors.

Author

License

The gorouter is open-sourced software licensed under the MIT Licensed

Acknowledgements

This package is inspired by the following:

Documentation

Overview

Package gorouter is a simple and fast HTTP router for Go. It is easy to build RESTful APIs and your web framework.

Here is the example:

 package main

 import (
	 "log"
	 "net/http"

	 "github.com/xujiajun/gorouter"
 )

 func main() {
	 mux := gorouter.New()
	 //url parameters match
	 mux.GET("/user/{id:[0-9]+}", func(w http.ResponseWriter, r *http.Request) {
		//get one URL parameter
		id := gorouter.GetParam(r, "id")
		//get all URL parameters
		//id := gorouter.GetAllParams(r)
		//fmt.Println(id)
		w.Write([]byte("match user/{id:[0-9]+} ! get id:" + id))
	  })

	  log.Fatal(http.ListenAndServe(":8181", mux))
 }

Here is the syntax:

Syntax	                   Description	                          Example
:name	            named parameter	                        /user/:name
{name:regexp}	    named with regexp parameter	            /user/{name:[0-9a-zA-Z]+}
:id	                named with regexp parameter	            /user/:id

And :id is short for {id:[0-9]+}, :name are short for {name:[0-9a-zA-Z_]+}.

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrGenerateParameters is returned when generating a route with wrong parameters.
	ErrGenerateParameters = errors.New("params contains wrong parameters")

	// ErrNotFoundRoute is returned when generating a route that can not find route in tree.
	ErrNotFoundRoute = errors.New("cannot find route in tree")

	// ErrNotFoundMethod is returned when generating a route that can not find method in tree.
	ErrNotFoundMethod = errors.New("cannot find method in tree")

	// ErrPatternGrammar is returned when generating a route that pattern grammar error.
	ErrPatternGrammar = errors.New("pattern grammar error")
)

Functions

func GetAllParams

func GetAllParams(r *http.Request) paramsMapType

GetAllParams returns all route params stored in http.Request.

func GetParam

func GetParam(r *http.Request, key string) string

GetParam returns route param stored in http.request.

Types

type MiddlewareType added in v1.0.1

type MiddlewareType func(next http.HandlerFunc) http.HandlerFunc

MiddlewareType is a public type that is used for middleware

type Node

type Node struct {
	// contains filtered or unexported fields
}

Node records any URL params, and executes an end handler.

func NewNode

func NewNode(key string, depth int) *Node

NewNode returns a newly initialized Node object that implements the Node

type Parameters added in v1.1.0

type Parameters struct {
	// contains filtered or unexported fields
}

Parameters records some parameters

type Router

type Router struct {

	// PanicHandler for handling panic.
	PanicHandler func(w http.ResponseWriter, req *http.Request, err interface{})
	// contains filtered or unexported fields
}

Router is a simple HTTP route multiplexer that parses a request path, records any URL params, and executes an end handler.

func New

func New() *Router

New returns a newly initialized Router object that implements the Router

func (*Router) DELETE

func (r *Router) DELETE(path string, handle http.HandlerFunc)

DELETE adds the route `path` that matches a DELETE http method to execute the `handle` http.HandlerFunc.

func (*Router) DELETEAndName added in v1.1.0

func (r *Router) DELETEAndName(path string, handle http.HandlerFunc, routeName string)

DELETEAndName is short for `DELETE` and Named routeName

func (*Router) GET

func (r *Router) GET(path string, handle http.HandlerFunc)

GET adds the route `path` that matches a GET http method to execute the `handle` http.HandlerFunc.

func (*Router) GETAndName added in v1.1.0

func (r *Router) GETAndName(path string, handle http.HandlerFunc, routeName string)

GETAndName is short for `GET` and Named routeName

func (*Router) Generate added in v1.1.0

func (r *Router) Generate(method string, routeName string, params map[string]string) (string, error)

Generate returns reverse routing by method, routeName and params

func (*Router) Group

func (r *Router) Group(prefix string) *Router

Group define routes groups if there is a path prefix that uses `prefix`

func (*Router) Handle

func (r *Router) Handle(method string, path string, handle http.HandlerFunc)

Handle registers a new request handler with the given path and method.

func (*Router) HandleNotFound

func (r *Router) HandleNotFound(w http.ResponseWriter, req *http.Request, middleware []MiddlewareType)

HandleNotFound registers a handler when the request route is not found

func (*Router) Match

func (r *Router) Match(requestUrl string, path string) bool

Match checks if the request matches the route pattern

func (*Router) NotFoundFunc

func (r *Router) NotFoundFunc(handler http.HandlerFunc)

NotFoundFunc registers a handler when the request route is not found

func (*Router) PATCH

func (r *Router) PATCH(path string, handle http.HandlerFunc)

PATCH adds the route `path` that matches a PATCH http method to execute the `handle` http.HandlerFunc.

func (*Router) PATCHAndName added in v1.1.0

func (r *Router) PATCHAndName(path string, handle http.HandlerFunc, routeName string)

PATCHAndName is short for `PATCH` and Named routeName

func (*Router) POST

func (r *Router) POST(path string, handle http.HandlerFunc)

POST adds the route `path` that matches a POST http method to execute the `handle` http.HandlerFunc.

func (*Router) POSTAndName added in v1.1.0

func (r *Router) POSTAndName(path string, handle http.HandlerFunc, routeName string)

POSTAndName is short for `POST` and Named routeName

func (*Router) PUT

func (r *Router) PUT(path string, handle http.HandlerFunc)

PUT adds the route `path` that matches a PUT http method to execute the `handle` http.HandlerFunc.

func (*Router) PUTAndName added in v1.1.0

func (r *Router) PUTAndName(path string, handle http.HandlerFunc, routeName string)

PUTAndName is short for `PUT` and Named routeName

func (*Router) ServeHTTP

func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request)

ServeHTTP makes the router implement the http.Handler interface.

func (*Router) Use

func (r *Router) Use(middleware ...MiddlewareType)

Use appends a middleware handler to the middleware stack.

type Tree

type Tree struct {
	// contains filtered or unexported fields
}

Tree records node

func NewTree

func NewTree() *Tree

NewTree returns a newly initialized Tree object that implements the Tree

func (*Tree) Add

func (t *Tree) Add(pattern string, handle http.HandlerFunc, middleware ...MiddlewareType)

Add use `pattern` 、handle、middleware stack as node register to tree

func (*Tree) Find

func (t *Tree) Find(pattern string, isRegex bool) (nodes []*Node)

Find returns nodes that the request match the route pattern

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL