Skip to content

Commit 36631d3

Browse files
committed
💥 Drop regexp based router for better performance
1 parent ddc4708 commit 36631d3

File tree

5 files changed

+44
-53
lines changed

5 files changed

+44
-53
lines changed

application.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,24 @@ package vox
22

33
import (
44
"net/http"
5+
6+
"github.com/aisk/route122"
57
)
68

79
// An Application is a container which includes middlewares and config, and implemented the GO's net/http.Handler interface https://golang.org/pkg/net/http/#Handler.
810
type Application struct {
11+
router *route122.Router
912
middlewares []Handler
1013
configs map[string]string
1114
}
1215

1316
// New returns a new vox Application.
1417
func New() *Application {
1518
app := &Application{
16-
middlewares: []Handler{logging, respond},
17-
configs: map[string]string{},
19+
router: route122.New(),
20+
configs: map[string]string{},
1821
}
22+
app.middlewares = []Handler{logging, app.routeHandler, respond}
1923
return app
2024
}
2125

go.mod

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,12 @@
11
module github.com/aisk/vox
22

3-
go 1.12
3+
go 1.22
4+
5+
toolchain go1.23.5
6+
7+
require github.com/aisk/route122 v0.0.0-20251129140018-16cd6cc48089
8+
9+
require (
10+
golang.org/x/net v0.35.0 // indirect
11+
golang.org/x/text v0.22.0 // indirect
12+
)

go.sum

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
github.com/aisk/route122 v0.0.0-20251129140018-16cd6cc48089 h1:FN73eBiN0dKR4WI1RjxiKssNVD+MMJAz4r/+BxrER5A=
2+
github.com/aisk/route122 v0.0.0-20251129140018-16cd6cc48089/go.mod h1:wV8INfN/2tGkuHol+j0Fjrz3iZijZrMJkB+A8LoZQNU=
3+
golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
4+
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
5+
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
6+
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=

route.go

Lines changed: 20 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,30 @@
11
package vox
22

3-
import (
4-
"regexp"
5-
)
6-
7-
// Route will register a new path handler to a given path.
8-
func (app *Application) Route(method string, path string, handler Handler) {
9-
// TODO: support string based path
10-
r := routeToRegexp(path)
11-
app.middlewares = append(app.middlewares, func(ctx *Context, req *Request, res *Response) {
12-
if match(req, method, r) {
13-
handler(ctx, req, res)
14-
return
3+
// routeHandler handles route matching and parameter extraction
4+
func (app *Application) routeHandler(ctx *Context, req *Request, res *Response) {
5+
match, found := app.router.Match(req.Method, "", req.URL.Path)
6+
if found {
7+
for k, v := range match.Params {
8+
req.Params[k] = v
159
}
16-
ctx.Next()
17-
})
10+
if h, ok := match.Handler.(Handler); ok {
11+
h(ctx, req, res)
12+
}
13+
}
14+
ctx.Next()
1815
}
1916

20-
func match(req *Request, method string, path *regexp.Regexp) bool {
21-
if req.Method != method && method != "*" {
22-
// TODO(asaka): ignore case?
23-
return false
24-
}
25-
match := path.FindStringSubmatch(req.URL.Path)
26-
if match == nil {
27-
return false
17+
// Route will register a new path handler to a given path.
18+
func (app *Application) Route(method string, path string, handler Handler) {
19+
var err error
20+
if method == "*" {
21+
err = app.router.Handle(path, handler)
22+
} else {
23+
err = app.router.Handle(method+" "+path, handler)
2824
}
29-
for i, name := range path.SubexpNames() {
30-
if i == 0 || name == "" {
31-
continue
32-
}
33-
req.Params[name] = match[i]
25+
if err != nil {
26+
panic(err)
3427
}
35-
return true
3628
}
3729

3830
// Get register a new path handler for GET method.
@@ -74,10 +66,3 @@ func (app *Application) Options(path string, handler Handler) {
7466
func (app *Application) Trace(path string, handler Handler) {
7567
app.Route("TRACE", path, handler)
7668
}
77-
78-
func routeToRegexp(path string) *regexp.Regexp {
79-
replaced := regexp.MustCompile(`{(?P<param>\w+)}`).ReplaceAllStringFunc(path, func(s string) string {
80-
return "(?P<" + s[1:len(s)-1] + ">[-_.\\p{Lu}\\p{Ll}0-9]+)"
81-
})
82-
return regexp.MustCompile("^" + replaced + "$")
83-
}

route_test.go

Lines changed: 2 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ func TestMatchAnyMethod(t *testing.T) {
4848
func TestRouteWithParams(t *testing.T) {
4949
app := New()
5050
app.SetConfig("logging:disable", "true")
51-
app.Route("GET", `/(?P<first>\w+)/\w+/(?P<second>\w+)`, func(ctx *Context, req *Request, res *Response) {
51+
app.Route("GET", "/{first}/xxxxx/{second}", func(ctx *Context, req *Request, res *Response) {
5252
res.Body = "Hello Vox!"
5353
if req.Params["first"] != "foo" {
5454
t.Fail()
@@ -114,23 +114,10 @@ func TestRouteFallthrough(t *testing.T) {
114114
}
115115
}
116116

117-
func TestRouteToRegexp(t *testing.T) {
118-
fn := func(input, expect string) {
119-
result := routeToRegexp(input)
120-
if result.String() != expect {
121-
t.Errorf("expect %s, got %s", expect, result)
122-
}
123-
}
124-
fn("/xxx/foo", "^/xxx/foo$")
125-
fn(`/xxx/(?P<name>\w+)`, `^/xxx/(?P<name>\w+)$`)
126-
fn(`/xxx/{name}`, `^/xxx/(?P<name>[-_.\p{Lu}\p{Ll}0-9]+)$`)
127-
fn(`/xxx/{name2}`, `^/xxx/(?P<name2>[-_.\p{Lu}\p{Ll}0-9]+)$`)
128-
}
129-
130117
func TestRouteWithUnicodeParams(t *testing.T) {
131118
app := New()
132119
app.SetConfig("logging:disable", "true")
133-
app.Route("GET", `/{first}/\w+/{second}`, func(ctx *Context, req *Request, res *Response) {
120+
app.Route("GET", "/{first}/xxxxx/{second}", func(ctx *Context, req *Request, res *Response) {
134121
res.Body = "Hello Vox!"
135122
if req.Params["first"] != "éèçà" {
136123
t.Fail()

0 commit comments

Comments
 (0)