gorouter

package module
v1.1.0 Latest Latest
Warning

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

Go to latest
Published: Nov 9, 2018 License: MIT Imports: 6 Imported by: 0

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, that’s why I prefer to support regexp,if a router does not support,it will increase the logic to judge the type of parameter from the URL,it will increase the program complexity,so I searched the github,i found the router julienschmidt/httprouter,it is very fast router,but it is a pity that it does not support regexp,later i found gorilla/mux,it is powerful as well,but a written benchmark show me that it is slow,so i tried to develop a new router which supports regexp and it should be a fast router.Finally i did it and named xujiajun/gorouter, By the way, it is my first GO open source project.It maybe the fastest GO http router which support regexp, about its performance see my latest Benchmarks.

Features

Requirements

  • golang 1.8+

Installation

go get 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 (
	"github.com/xujiajun/gorouter"
	"log"
	"net/http"
)

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 (
	"github.com/xujiajun/gorouter"
	"log"
	"net/http"
)

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"
	"github.com/xujiajun/gorouter"
	"log"
	"net/http"
)

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"
	"github.com/xujiajun/gorouter"
	"net/http"
)

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"
	"github.com/xujiajun/gorouter"
	"log"
	"net/http"
)

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"
	"github.com/xujiajun/gorouter"
	"log"
	"net/http"
)

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"
	"github.com/xujiajun/gorouter"
	"log"
	"net/http"
)

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 (
	"github.com/xujiajun/gorouter"
	"log"
	"net/http"
	"os"
)

//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_]+}

Benchmarks

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
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 is fast)

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

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

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

if you want a high performance router which not support 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.

Author

License

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

Acknowledgements

This package is inspired by the following:

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func GetAllParams

func GetAllParams(r *http.Request) paramsMapType

GetAllParams return all route params stored in r.

func GetParam

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

GetParam return route param stored in r.

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 (router *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 (router *Router) DELETEAndName(path string, handle http.HandlerFunc, routeName string)

DELETEAndName is short for `DELETE` and Named routeName

func (*Router) GET

func (router *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 (router *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 (router *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 (router *Router) Group(prefix string) *Router

Group define routes groups If there is a path prefix that use `prefix`

func (*Router) Handle

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

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

func (*Router) HandleNotFound

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

HandleNotFound registers a handler when the request route is not found

func (*Router) Match

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

Match check if the request match the route Pattern

func (*Router) NotFoundFunc

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

NotFoundFunc registers a handler when the request route is not found

func (*Router) PATCH

func (router *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 (router *Router) PATCHAndName(path string, handle http.HandlerFunc, routeName string)

PATCHAndName is short for `PATCH` and Named routeName

func (*Router) POST

func (router *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 (router *Router) POSTAndName(path string, handle http.HandlerFunc, routeName string)

POSTAndName is short for `POST` and Named routeName

func (*Router) PUT

func (router *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 (router *Router) PUTAndName(path string, handle http.HandlerFunc, routeName string)

PUTAndName is short for `PUT` and Named routeName

func (*Router) ServeHTTP

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

ServeHTTP makes the router implement the http.Handler interface.

func (*Router) Use

func (router *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 (tree *Tree) Add(pattern string, handle http.HandlerFunc, middleware ...MiddlewareType)

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

func (*Tree) Find

func (tree *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