Skip to content

Commit c282bfe

Browse files
author
Wen Huang
committed
Implement regex caching for CurlyRouter and custom verbs
1 parent 1959514 commit c282bfe

File tree

4 files changed

+130
-5
lines changed

4 files changed

+130
-5
lines changed

curly.go

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,16 @@ import (
99
"regexp"
1010
"sort"
1111
"strings"
12+
"sync"
1213
)
1314

1415
// CurlyRouter expects Routes with paths that contain zero or more parameters in curly brackets.
1516
type CurlyRouter struct{}
1617

18+
var (
19+
regexCache sync.Map // Cache for compiled regex patterns
20+
)
21+
1722
// SelectRoute is part of the Router interface and returns the best match
1823
// for the WebService and its Route for the given Request.
1924
func (c CurlyRouter) SelectRoute(
@@ -113,8 +118,22 @@ func (c CurlyRouter) regularMatchesPathToken(routeToken string, colon int, reque
113118
}
114119
return true, true
115120
}
116-
matched, err := regexp.MatchString(regPart, requestToken)
117-
return (matched && err == nil), false
121+
122+
// Check cache first
123+
if cached, found := regexCache.Load(regPart); found {
124+
regex := cached.(*regexp.Regexp)
125+
matched := regex.MatchString(requestToken)
126+
return matched, false
127+
}
128+
129+
// Compile and cache the regex
130+
regex, err := regexp.Compile(regPart)
131+
if err != nil {
132+
return false, false
133+
}
134+
regexCache.Store(regPart, regex)
135+
matched := regex.MatchString(requestToken)
136+
return matched, false
118137
}
119138

120139
var jsr311Router = RouterJSR311{}

curly_test.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package restful
33
import (
44
"io"
55
"net/http"
6+
"sync"
67
"testing"
78
)
89

@@ -262,3 +263,49 @@ func TestCurly_ISSUE_137_2(t *testing.T) {
262263
}
263264

264265
func curlyDummy(req *Request, resp *Response) { io.WriteString(resp.ResponseWriter, "curlyDummy") }
266+
267+
func TestRegexCaching(t *testing.T) {
268+
// Clear cache before test
269+
regexCache = sync.Map{}
270+
271+
router := CurlyRouter{}
272+
273+
// Test with regex pattern
274+
routeToken := "{id:[0-9]+}"
275+
requestToken := "123"
276+
277+
// First call should cache the regex
278+
matches1, _ := router.regularMatchesPathToken(routeToken, 3, requestToken)
279+
if !matches1 {
280+
t.Error("Expected first call to match")
281+
}
282+
283+
// Verify cache contains the pattern
284+
pattern := "[0-9]+"
285+
_, found := regexCache.Load(pattern)
286+
if !found {
287+
t.Error("Expected pattern to be cached")
288+
}
289+
290+
// Second call should use cached regex
291+
matches2, _ := router.regularMatchesPathToken(routeToken, 3, requestToken)
292+
if !matches2 {
293+
t.Error("Expected second call to match using cache")
294+
}
295+
296+
// Test with different pattern to ensure separate caching
297+
routeToken2 := "{name:[a-z]+}"
298+
requestToken2 := "john"
299+
300+
matches3, _ := router.regularMatchesPathToken(routeToken2, 5, requestToken2)
301+
if !matches3 {
302+
t.Error("Expected name pattern to match")
303+
}
304+
305+
// Verify both patterns are cached
306+
pattern2 := "[a-z]+"
307+
_, found2 := regexCache.Load(pattern2)
308+
if !found2 {
309+
t.Error("Expected name pattern to be cached")
310+
}
311+
}

custom_verb.go

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@ package restful
33
import (
44
"fmt"
55
"regexp"
6+
"sync"
67
)
78

89
var (
9-
customVerbReg = regexp.MustCompile(":([A-Za-z]+)$")
10+
customVerbReg = regexp.MustCompile(":([A-Za-z]+)$")
11+
customVerbCache sync.Map // Cache for compiled custom verb regexes
1012
)
1113

1214
func hasCustomVerb(routeToken string) bool {
@@ -20,7 +22,17 @@ func isMatchCustomVerb(routeToken string, pathToken string) bool {
2022
}
2123

2224
customVerb := rs[1]
23-
specificVerbReg := regexp.MustCompile(fmt.Sprintf(":%s$", customVerb))
25+
regexPattern := fmt.Sprintf(":%s$", customVerb)
26+
27+
// Check cache first
28+
if cached, found := customVerbCache.Load(regexPattern); found {
29+
specificVerbReg := cached.(*regexp.Regexp)
30+
return specificVerbReg.MatchString(pathToken)
31+
}
32+
33+
// Compile and cache the regex
34+
specificVerbReg := regexp.MustCompile(regexPattern)
35+
customVerbCache.Store(regexPattern, specificVerbReg)
2436
return specificVerbReg.MatchString(pathToken)
2537
}
2638

custom_verb_test.go

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
package restful
22

3-
import "testing"
3+
import (
4+
"sync"
5+
"testing"
6+
)
47

58
func TestHasCustomVerb(t *testing.T) {
69
testCase := []struct {
@@ -40,6 +43,7 @@ func TestRemoveCustomVerb(t *testing.T) {
4043
}
4144
}
4245
}
46+
4347
func TestMatchCustomVerb(t *testing.T) {
4448
testCase := []struct {
4549
routeToken string
@@ -60,3 +64,46 @@ func TestMatchCustomVerb(t *testing.T) {
6064
}
6165
}
6266
}
67+
68+
func TestCustomVerbCaching(t *testing.T) {
69+
// Clear cache before test
70+
customVerbCache = sync.Map{}
71+
72+
routeToken := "{userId:regex}:POST"
73+
pathToken := "user123:POST"
74+
75+
// First call should cache the regex
76+
result1 := isMatchCustomVerb(routeToken, pathToken)
77+
if !result1 {
78+
t.Error("Expected first call to match")
79+
}
80+
81+
// Verify cache contains the pattern
82+
pattern := ":POST$"
83+
_, found := customVerbCache.Load(pattern)
84+
if !found {
85+
t.Error("Expected pattern to be cached")
86+
}
87+
88+
// Second call should use cached regex
89+
result2 := isMatchCustomVerb(routeToken, pathToken)
90+
if !result2 {
91+
t.Error("Expected second call to match using cache")
92+
}
93+
94+
// Test with different verb to ensure separate caching
95+
routeToken2 := "{userId:regex}:GET"
96+
pathToken2 := "user123:GET"
97+
98+
result3 := isMatchCustomVerb(routeToken2, pathToken2)
99+
if !result3 {
100+
t.Error("Expected GET verb to match")
101+
}
102+
103+
// Verify both patterns are cached
104+
pattern2 := ":GET$"
105+
_, found2 := customVerbCache.Load(pattern2)
106+
if !found2 {
107+
t.Error("Expected GET pattern to be cached")
108+
}
109+
}

0 commit comments

Comments
 (0)