高性能高扩展golang http路由库erouter

3,545 阅读5分钟

erouter

Go Report Card
GoDoc

erouter是高性能高扩展http路由库,具有零内存复制、严格路由匹配顺序、代码复制度低、组路由、中间件功能、默认参数、常量匹配、变量匹配、通配符匹配、变量校验匹配、通配符校验匹配、基于Host路由这些特点功能。

设计说明

基于eudore框架路由分离,修改中间件机制并移除MVC。

RouterRadix

RouterRadix使用基数树实现,具有零内存复制、严格路由匹配顺序、组路由、中间件功能、默认参数、常量匹配、变量匹配、通配符匹配功能。

example:

package main

import "log"
import "net/http"
import "github.com/eudore/erouter"

func main() {
	router := erouter.NewRouterRadix()
	router.AddMiddleware("ANY", "", func(h erouter.Handler) erouter.Handler {
		return func(w http.ResponseWriter, r *http.Request, p erouter.Params) {
			log.Printf("%s %s route: %s", r.Method, r.URL.Path, p.GetParam("route"))
			h(w, r, p)
		}
	})
	router.Any("/*", func(w http.ResponseWriter, r *http.Request, p erouter.Params) {
		w.Write([]byte("hello\n"))
	})
	router.Get("/api/*action version=v0", func(w http.ResponseWriter, _ *http.Request, p erouter.Params) {
		w.Write([]byte("access api " + p.GetParam("version") +": " + p.GetParam("action") + "\n"))
	})
	router.Get("/api/v1/*action version=v1", func(w http.ResponseWriter, _ *http.Request, p erouter.Params) {
		w.Write([]byte("access api " + p.GetParam("version") +": " + p.GetParam("action") + "\n"))
	})
	apiv2 := router.Group("/api/v2 version=v2")
	apiv2.Any("/*action", func(w http.ResponseWriter, _ *http.Request, p erouter.Params) {
		w.Write([]byte("access api " + p.GetParam("version") +": " + p.GetParam("action") + "\n"))
	})
	http.ListenAndServe(":8080", router)
}

测试命令:

curl 127.0.0.1:8080/get
curl 127.0.0.1:8080/api/getuser
curl 127.0.0.1:8080/api/v1/getuser
curl 127.0.0.1:8080/api/v2/getuser

RouterFull

RouterFull基于RouterRadix扩展,实现变量校验匹配、通配符校验匹配功能。

用法:在正常变量和通配符后,使用'|'符号分割,后为校验规则,isnum是校验函数;min:100为动态检验函数,min是动态校验函数名称,':'后为参数;如果为'^'开头为正则校验,并且要使用'$'作为结尾。

注意: 正则表达式不要使用空格,会导致参数切割错误,使用\u002代替空格。

:num|isnum
:num|min:100
:num|^0.*$
*num|isnum
*num|min:100
*num|^0.*$

example:

package main

import "net/http"
import "github.com/eudore/erouter"

func main() {
	router := erouter.NewRouterFull()
	router.Any("/*", func(w http.ResponseWriter, r *http.Request, p erouter.Params) {
		w.Write([]byte("hello\n"))
	})
	router.Get("/:num|^0.*$", func(w http.ResponseWriter, r *http.Request, p erouter.Params) {
		w.Write([]byte("first char is '0', num is: " + p.GetParam("num") + "\n"))
	})
	router.Get("/:num|min:100", func(w http.ResponseWriter, r *http.Request, p erouter.Params) {
		w.Write([]byte("num great 100, num is: " + p.GetParam("num") + "\n"))
	})
	router.Get("/:num|isnum", func(w http.ResponseWriter, r *http.Request, p erouter.Params) {
		w.Write([]byte("num is: " + p.GetParam("num") + "\n"))
	})
	router.Get("/*var|^E.*$", func(w http.ResponseWriter, r *http.Request, p erouter.Params) {
		w.Write([]byte("first char is 'E', var is: " + p.GetParam("var") + "\n"))
	})
	http.ListenAndServe(":8080", router)
}

测试命令:

curl 127.0.0.1:8080/get
curl 127.0.0.1:8080/012
curl 127.0.0.1:8080/123
curl 127.0.0.1:8080/12
curl 127.0.0.1:8080/Erouter/123

RouterHost

RouterHost基于Host匹配,通过选择Host对应子路由器执行注册和匹配,实现基于Host路由功能。

当前使用遍历匹配,Host匹配函数为path.Match,未来匹配规则仅保留'*'通配符和常量。

用法:需要给Host路由器注册域名规则下的子路由器,注册路由时使用host参数匹配注册的路由器添加路由,匹配时使用请求的Host来匹配注册的路由。

example:

package main

import "net/http"
import "github.com/eudore/erouter"

func main() {
	router := erouter.NewRouterHost().(*erouter.RouterHost)
	router.RegisterHost("*.example.com", erouter.NewRouterRadix())
	router.Any("/*", func(w http.ResponseWriter, r *http.Request, p erouter.Params) {
		w.Write([]byte("hello\n"))
	})
	router.Get("/* host=*.example.com", func(w http.ResponseWriter, r *http.Request, p erouter.Params) {
		w.Write([]byte("host is " + r.Host + ", match host: " + p.GetParam("host") + "\n"))
	})
	http.ListenAndServe(":8080", router)
}

测试命令:

curl 127.0.0.1:8080
curl -XPUT 127.0.0.1:8080
curl -H 'Host: www.example.com' 127.0.0.1:8080
curl -H 'Host: www.example.com' -XPUT 127.0.0.1:8080
curl -H 'Host: www.example.com' -Xput 127.0.0.1:8080

Benchmark

使用GithubApi进行Benchmark性能测试,Erouter匹配性能仅有httprouter的60%,但是具有严格路由匹配顺序、易扩展重写和代码复杂度低的特点。

测试命令:

go get github.com/eudore/web-framework-benchmark
go test -bench=router github.com/eudore/web-framework-benchmark

测试结果:

goos: linux
goarch: amd64
pkg: github.com/eudore/web-framework-benchmark
BenchmarkHttprouterStatic-2        	   50000	     25518 ns/op	    1949 B/op	     157 allocs/op
BenchmarkHttprouterGitHubAPI-2     	   30000	     57961 ns/op	   16571 B/op	     370 allocs/op
BenchmarkHttprouterGplusAPI-2      	  500000	      2747 ns/op	     813 B/op	      24 allocs/op
BenchmarkHttprouterParseAPI-2      	  300000	      3886 ns/op	     963 B/op	      42 allocs/op
BenchmarkErouterRadixStatic-2      	   30000	     44147 ns/op	    2412 B/op	     157 allocs/op
BenchmarkErouterRadixGitHubAPI-2   	   20000	     63756 ns/op	    2501 B/op	     203 allocs/op
BenchmarkErouterRadixGplusAPI-2    	  500000	      2653 ns/op	     173 B/op	      13 allocs/op
BenchmarkErouterRadixParseAPI-2    	  300000	      4523 ns/op	     323 B/op	      26 allocs/op
BenchmarkErouterFullStatic-2       	   30000	     43923 ns/op	    2413 B/op	     157 allocs/op
BenchmarkErouterFullGitHubAPI-2    	   20000	     65698 ns/op	    2503 B/op	     203 allocs/op
BenchmarkErouterFullGplusAPI-2     	  500000	      2582 ns/op	     173 B/op	      13 allocs/op
BenchmarkErouterFullParseAPI-2     	  300000	      5990 ns/op	     323 B/op	      26 allocs/op
PASS
ok  	github.com/eudore/web-framework-benchmark	19.934s

Api

列出了主要使用的方法,具体参考文档

Router接口定义:

type (
	// Params读写请求处理中的参数。
	Params interface {
		GetParam(string) string
		AddParam(string, string)
		SetParam(string, string)
	}
	// Erouter处理一个请求的方法,在http.HandlerFunc基础上增加了Parmas。
	Handler func(http.ResponseWriter, *http.Request, Params)
	// 定义请求处理中间件函数,通过传入处理然后返回一个处理,使用装饰器组装处理请求。
	Middleware func(Handler) Handler
	// The route is directly registered by default. Other methods can be directly registered using the RouterRegister interface.
	//
	// 路由默认直接注册的方法,其他方法可以使用RouterRegister接口直接注册。
	RouterMethod interface {
		Group(string) RouterMethod
		AddHandler(string, string, Handler) RouterMethod
		AddMiddleware(string, string, ...Middleware) RouterMethod
		NotFound(Handler)
		MethodNotAllowed(Handler)
		Any(string, Handler)
		Delete(string, Handler)
		Get(string, Handler)
		Head(string, Handler)
		Options(string, Handler)
		Patch(string, Handler)
		Post(string, Handler)
		Put(string, Handler)
	}
	// Router core interface, performing routing, middleware registration, and processing http requests.
	//
	// 路由器核心接口,执行路由、中间件的注册和处理http请求。
	RouterCore interface {
		RegisterMiddleware(string, string, []Middleware)
		RegisterHandler(string, string, Handler)
		ServeHTTP(http.ResponseWriter, *http.Request)
	}
	// The router interface needs to implement two methods: the router method and the router core.
	//
	// 路由器接口,需要实现路由器方法、路由器核心两个接口。
	Router interface {
		RouterCore
		RouterMethod
	}
)

NewRouter

当前拥有三种实现,每种路由器都实现了Router接口。

func NewRouterRadix() Router
func NewRouterFull() Router
func NewRouterHost() Router
router1 := erouter.NewRouterRadix()
router2 := erouter.NewRouterFull()
router3 := erouter.NewRouterHost()

Group

func Group(path string) RouterMethod

Group实现路由器分组。

router := erouter.NewRouterRadix()
apiv1 := router.Group("/api/v1 version=v1")
apiv1.Get("/*", ...)

AddHandler

func AddHandler(method string, path string, handler Handler) RouterMethod

AddHandler用于添加新路由。

router := erouter.NewRouterRadix()
router.AddHandle("GET", "/*", ...)

AddMiddleware

func AddMiddleware(method string, path string, midds ...Middleware) RouterMethod

AddMiddleware给当前路由方法添加处理中间件。

router := erouter.NewRouterRadix()
router.AddMiddleware("ANY", "", func(h erouter.Handler) erouter.Handler {
	return func(w http.ResponseWriter, r *http.Request, p erouter.Params) {
		// befor执行
		...
		// 调用next处理汇总函数
		h(w, r, p)
		// after执行
		...
	}
})

NotFound

func NotFound(Handler)

设置路由器404处理。

MethodNotAllowed

func MethodNotAllowed(Handler)

设置路由器405处理。

Any

func Any(path string, handler Handler)

注册Any方法,相当于AddHandler的方法为"ANY"。

Any方法的集合为erouter.RouterAllMethod,扩展新方法Radix和Full不支持。

Get

func Get(path string, handler Handler)

注册Get方法,相当于AddHandler的方法为"GET",post、put等方法函数类似。

router := erouter.NewRouterRadix()
router.Get("/*", func(w http.ResponseWriter, r *http.Request, p erouter.Params) {
	// 执行处理
})