Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add RegexpCompileFunc to override regexp.Compile #731

Merged
merged 2 commits into from
Apr 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion mux.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ var (
ErrMethodMismatch = errors.New("method is not allowed")
// ErrNotFound is returned when no route match is found.
ErrNotFound = errors.New("no matching route was found")
// RegexpCompileFunc aliases regexp.Compile and enables overriding it.
// Do not run this function from `init()` in importable packages.
// Changing this value is not safe for concurrent use.
RegexpCompileFunc = regexp.Compile
)

// NewRouter returns a new router instance.
Expand Down Expand Up @@ -524,7 +528,7 @@ func mapFromPairsToRegex(pairs ...string) (map[string]*regexp.Regexp, error) {
}
m := make(map[string]*regexp.Regexp, length/2)
for i := 0; i < length; i += 2 {
regex, err := regexp.Compile(pairs[i+1])
regex, err := RegexpCompileFunc(pairs[i+1])
if err != nil {
return nil, err
}
Expand Down
4 changes: 2 additions & 2 deletions regexp.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ func newRouteRegexp(tpl string, typ regexpType, options routeRegexpOptions) (*ro

// Append variable name and compiled pattern.
varsN[i/2] = name
varsR[i/2], err = regexp.Compile(fmt.Sprintf("^%s$", patt))
varsR[i/2], err = RegexpCompileFunc(fmt.Sprintf("^%s$", patt))
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -125,7 +125,7 @@ func newRouteRegexp(tpl string, typ regexpType, options routeRegexpOptions) (*ro
reverse.WriteByte('/')
}
// Compile full regexp.
reg, errCompile := regexp.Compile(pattern.String())
reg, errCompile := RegexpCompileFunc(pattern.String())
if errCompile != nil {
return nil, errCompile
}
Expand Down
66 changes: 66 additions & 0 deletions route_test.go
AlexVulaj marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package mux

import (
"net/http"
"regexp"
"sync"
"testing"
)

var testNewRouterMu sync.Mutex
var testHandler = http.NotFoundHandler()

func BenchmarkNewRouter(b *testing.B) {
testNewRouterMu.Lock()
defer testNewRouterMu.Unlock()

// Set the RegexpCompileFunc to the default regexp.Compile.
RegexpCompileFunc = regexp.Compile

b.ReportAllocs()
b.ResetTimer()

for i := 0; i < b.N; i++ {
testNewRouter(b, testHandler)
}
}

func BenchmarkNewRouterRegexpFunc(b *testing.B) {
testNewRouterMu.Lock()
defer testNewRouterMu.Unlock()

// We preallocate the size to 8.
cache := make(map[string]*regexp.Regexp, 8)

// Override the RegexpCompileFunc to reuse compiled expressions
// from the `cache` map. Real world caches should have eviction
// policies or some sort of approach to limit memory use.
RegexpCompileFunc = func(expr string) (*regexp.Regexp, error) {
if regex, ok := cache[expr]; ok {
return regex, nil
}

regex, err := regexp.Compile(expr)
if err != nil {
return nil, err
}

cache[expr] = regex
return regex, nil
}

b.ReportAllocs()
b.ResetTimer()

for i := 0; i < b.N; i++ {
testNewRouter(b, testHandler)
}
}

func testNewRouter(_ testing.TB, handler http.Handler) {
r := NewRouter()
// A route with a route variable:
r.Handle("/metrics/{type}", handler)
r.Queries("orgID", "{orgID:[0-9]*?}")
r.Host("{subdomain}.domain.com")
}