826 lines
19 KiB
Go
826 lines
19 KiB
Go
package router
|
|
|
|
import (
|
|
"net"
|
|
"testing"
|
|
|
|
assert "git.sharkk.net/Go/Assert"
|
|
"github.com/valyala/fasthttp"
|
|
"github.com/valyala/fasthttp/fasthttputil"
|
|
)
|
|
|
|
// newHandler creates a simple Handler from a function
|
|
func newHandler(fn func(ctx Ctx, params []string)) Handler {
|
|
return fn
|
|
}
|
|
|
|
// performRequest is a helper function to test the router
|
|
func performRequest(r *Router, method, path string) (*fasthttp.RequestCtx, bool) {
|
|
ctx := &fasthttp.RequestCtx{}
|
|
ctx.Request.Header.SetMethod(method)
|
|
ctx.Request.SetRequestURI(path)
|
|
|
|
handler, params, found := r.Lookup(method, path)
|
|
if found {
|
|
handler.Serve(ctx, params)
|
|
}
|
|
return ctx, found
|
|
}
|
|
|
|
func TestRootPath(t *testing.T) {
|
|
r := New()
|
|
r.Get("/", func(ctx Ctx, params []string) {
|
|
// No-op for testing
|
|
})
|
|
|
|
_, found := performRequest(r, "GET", "/")
|
|
assert.True(t, found)
|
|
}
|
|
|
|
func TestStaticPath(t *testing.T) {
|
|
r := New()
|
|
r.Get("/users/all", func(ctx Ctx, params []string) {
|
|
// No-op for testing
|
|
})
|
|
|
|
_, found := performRequest(r, "GET", "/users/all")
|
|
assert.True(t, found)
|
|
}
|
|
|
|
func TestSingleParameter(t *testing.T) {
|
|
r := New()
|
|
|
|
called := false
|
|
r.Get("/users/[id]", func(ctx Ctx, params []string) {
|
|
called = true
|
|
assert.Equal(t, params[0], "123")
|
|
})
|
|
|
|
_, found := performRequest(r, "GET", "/users/123")
|
|
assert.True(t, found)
|
|
assert.True(t, called)
|
|
}
|
|
|
|
func TestMultipleParameters(t *testing.T) {
|
|
r := New()
|
|
|
|
called := false
|
|
r.Get("/users/[id]/posts/[postId]", func(ctx Ctx, params []string) {
|
|
called = true
|
|
assert.Equal(t, params[0], "123")
|
|
assert.Equal(t, params[1], "456")
|
|
})
|
|
|
|
_, found := performRequest(r, "GET", "/users/123/posts/456")
|
|
assert.True(t, found)
|
|
assert.True(t, called)
|
|
}
|
|
|
|
func TestNonExistentPath(t *testing.T) {
|
|
r := New()
|
|
r.Get("/users/[id]", func(ctx Ctx, params []string) {
|
|
// No-op for testing
|
|
})
|
|
|
|
_, found := performRequest(r, "GET", "/posts/123")
|
|
assert.False(t, found)
|
|
}
|
|
|
|
func TestWrongMethod(t *testing.T) {
|
|
r := New()
|
|
r.Get("/users/[id]", func(ctx Ctx, params []string) {
|
|
// No-op for testing
|
|
})
|
|
|
|
_, found := performRequest(r, "POST", "/users/123")
|
|
assert.False(t, found)
|
|
}
|
|
|
|
func TestTrailingSlash(t *testing.T) {
|
|
r := New()
|
|
|
|
called := false
|
|
r.Get("/users/[id]", func(ctx Ctx, params []string) {
|
|
called = true
|
|
assert.Equal(t, params[0], "123")
|
|
})
|
|
|
|
_, found := performRequest(r, "GET", "/users/123/")
|
|
assert.True(t, found)
|
|
assert.True(t, called)
|
|
}
|
|
|
|
func TestDifferentMethods(t *testing.T) {
|
|
r := New()
|
|
|
|
handler := func(ctx Ctx, params []string) {}
|
|
|
|
r.Get("/test", handler)
|
|
r.Post("/test", handler)
|
|
r.Put("/test", handler)
|
|
r.Patch("/test", handler)
|
|
r.Delete("/test", handler)
|
|
|
|
methods := []string{"GET", "POST", "PUT", "PATCH", "DELETE"}
|
|
for _, method := range methods {
|
|
t.Run(method, func(t *testing.T) {
|
|
_, found := performRequest(r, method, "/test")
|
|
assert.True(t, found)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestWildcardPath(t *testing.T) {
|
|
r := New()
|
|
|
|
t.Run("simple wildcard", func(t *testing.T) {
|
|
called := false
|
|
err := r.Get("/files/*path", func(ctx Ctx, params []string) {
|
|
called = true
|
|
assert.Equal(t, params[0], "docs/report.pdf")
|
|
})
|
|
assert.Nil(t, err)
|
|
|
|
_, found := performRequest(r, "GET", "/files/docs/report.pdf")
|
|
assert.True(t, found)
|
|
assert.True(t, called)
|
|
})
|
|
|
|
t.Run("wildcard with empty path", func(t *testing.T) {
|
|
called := false
|
|
err := r.Get("/download/*filepath", func(ctx Ctx, params []string) {
|
|
called = true
|
|
assert.Equal(t, params[0], "")
|
|
})
|
|
assert.Nil(t, err)
|
|
|
|
_, found := performRequest(r, "GET", "/download/")
|
|
assert.True(t, found)
|
|
assert.True(t, called)
|
|
})
|
|
|
|
t.Run("wildcard with parameter", func(t *testing.T) {
|
|
called := false
|
|
err := r.Get("/users/[id]/*action", func(ctx Ctx, params []string) {
|
|
called = true
|
|
assert.Equal(t, params[0], "123")
|
|
assert.Equal(t, params[1], "settings/profile/avatar")
|
|
})
|
|
assert.Nil(t, err)
|
|
|
|
_, found := performRequest(r, "GET", "/users/123/settings/profile/avatar")
|
|
assert.True(t, found)
|
|
assert.True(t, called)
|
|
})
|
|
|
|
t.Run("multiple wildcards not allowed", func(t *testing.T) {
|
|
err := r.Get("/api/*version/*path", func(ctx Ctx, params []string) {})
|
|
assert.NotNil(t, err)
|
|
})
|
|
|
|
t.Run("non-last wildcard not allowed", func(t *testing.T) {
|
|
err := r.Get("/api/*version/users", func(ctx Ctx, params []string) {})
|
|
assert.NotNil(t, err)
|
|
})
|
|
}
|
|
|
|
// Middleware Tests
|
|
func TestMiddleware(t *testing.T) {
|
|
t.Run("global middleware", func(t *testing.T) {
|
|
r := New()
|
|
|
|
// Track middleware execution
|
|
executed := false
|
|
|
|
r.Use(func(next Handler) Handler {
|
|
return newHandler(func(ctx Ctx, params []string) {
|
|
executed = true
|
|
next.Serve(ctx, params)
|
|
})
|
|
})
|
|
|
|
r.Get("/test", func(ctx Ctx, params []string) {})
|
|
|
|
_, found := performRequest(r, "GET", "/test")
|
|
assert.True(t, found)
|
|
assert.True(t, executed)
|
|
})
|
|
|
|
t.Run("multiple middleware", func(t *testing.T) {
|
|
r := New()
|
|
|
|
// Track middleware execution order
|
|
order := []int{}
|
|
|
|
r.Use(func(next Handler) Handler {
|
|
return newHandler(func(ctx Ctx, params []string) {
|
|
order = append(order, 1)
|
|
next.Serve(ctx, params)
|
|
order = append(order, 4)
|
|
})
|
|
})
|
|
|
|
r.Use(func(next Handler) Handler {
|
|
return newHandler(func(ctx Ctx, params []string) {
|
|
order = append(order, 2)
|
|
next.Serve(ctx, params)
|
|
order = append(order, 3)
|
|
})
|
|
})
|
|
|
|
r.Get("/test", func(ctx Ctx, params []string) {
|
|
order = append(order, 0)
|
|
})
|
|
|
|
_, found := performRequest(r, "GET", "/test")
|
|
assert.True(t, found)
|
|
|
|
// Check middleware execution order (first middleware wraps second)
|
|
assert.Equal(t, len(order), 5)
|
|
assert.Equal(t, order[0], 1) // First middleware enter
|
|
assert.Equal(t, order[1], 2) // Second middleware enter
|
|
assert.Equal(t, order[2], 0) // Handler
|
|
assert.Equal(t, order[3], 3) // Second middleware exit
|
|
assert.Equal(t, order[4], 4) // First middleware exit
|
|
})
|
|
|
|
t.Run("route-specific middleware", func(t *testing.T) {
|
|
r := New()
|
|
|
|
executed := false
|
|
|
|
middleware := func(next Handler) Handler {
|
|
return newHandler(func(ctx Ctx, params []string) {
|
|
executed = true
|
|
next.Serve(ctx, params)
|
|
})
|
|
}
|
|
|
|
r.WithMiddleware(middleware).Get("/test", func(ctx Ctx, params []string) {})
|
|
|
|
_, found := performRequest(r, "GET", "/test")
|
|
assert.True(t, found)
|
|
assert.True(t, executed)
|
|
})
|
|
}
|
|
|
|
// Group Tests
|
|
func TestGroup(t *testing.T) {
|
|
t.Run("simple group", func(t *testing.T) {
|
|
r := New()
|
|
|
|
// Create API group
|
|
api := r.Group("/api")
|
|
api.Get("/users", func(ctx Ctx, params []string) {})
|
|
|
|
_, found := performRequest(r, "GET", "/api/users")
|
|
assert.True(t, found)
|
|
})
|
|
|
|
t.Run("nested groups", func(t *testing.T) {
|
|
r := New()
|
|
|
|
// Create nested groups
|
|
api := r.Group("/api")
|
|
v1 := api.Group("/v1")
|
|
v1.Get("/users", func(ctx Ctx, params []string) {})
|
|
|
|
_, found := performRequest(r, "GET", "/api/v1/users")
|
|
assert.True(t, found)
|
|
})
|
|
|
|
t.Run("group middleware", func(t *testing.T) {
|
|
r := New()
|
|
executed := false
|
|
|
|
// Create group with middleware
|
|
api := r.Group("/api")
|
|
api.Use(func(next Handler) Handler {
|
|
return newHandler(func(ctx Ctx, params []string) {
|
|
executed = true
|
|
next.Serve(ctx, params)
|
|
})
|
|
})
|
|
|
|
api.Get("/users", func(ctx Ctx, params []string) {})
|
|
|
|
_, found := performRequest(r, "GET", "/api/users")
|
|
assert.True(t, found)
|
|
assert.True(t, executed)
|
|
})
|
|
|
|
t.Run("nested group middleware", func(t *testing.T) {
|
|
r := New()
|
|
order := []int{}
|
|
|
|
// Create group with middleware
|
|
api := r.Group("/api")
|
|
api.Use(func(next Handler) Handler {
|
|
return newHandler(func(ctx Ctx, params []string) {
|
|
order = append(order, 1)
|
|
next.Serve(ctx, params)
|
|
})
|
|
})
|
|
|
|
// Create nested group with additional middleware
|
|
v1 := api.Group("/v1")
|
|
v1.Use(func(next Handler) Handler {
|
|
return newHandler(func(ctx Ctx, params []string) {
|
|
order = append(order, 2)
|
|
next.Serve(ctx, params)
|
|
})
|
|
})
|
|
|
|
v1.Get("/users", func(ctx Ctx, params []string) {
|
|
order = append(order, 3)
|
|
})
|
|
|
|
_, found := performRequest(r, "GET", "/api/v1/users")
|
|
assert.True(t, found)
|
|
|
|
// Check middleware execution order
|
|
assert.Equal(t, len(order), 3)
|
|
assert.Equal(t, order[0], 1) // First middleware (from api group)
|
|
assert.Equal(t, order[1], 2) // Second middleware (from v1 group)
|
|
assert.Equal(t, order[2], 3) // Handler
|
|
})
|
|
|
|
t.Run("route-specific middleware in group", func(t *testing.T) {
|
|
r := New()
|
|
order := []int{}
|
|
|
|
// Create group with middleware
|
|
api := r.Group("/api")
|
|
api.Use(func(next Handler) Handler {
|
|
return newHandler(func(ctx Ctx, params []string) {
|
|
order = append(order, 1)
|
|
next.Serve(ctx, params)
|
|
})
|
|
})
|
|
|
|
// Add route with specific middleware
|
|
api.WithMiddleware(func(next Handler) Handler {
|
|
return newHandler(func(ctx Ctx, params []string) {
|
|
order = append(order, 2)
|
|
next.Serve(ctx, params)
|
|
})
|
|
}).Get("/users", func(ctx Ctx, params []string) {
|
|
order = append(order, 3)
|
|
})
|
|
|
|
_, found := performRequest(r, "GET", "/api/users")
|
|
assert.True(t, found)
|
|
|
|
// Check middleware execution order
|
|
assert.Equal(t, len(order), 3)
|
|
assert.Equal(t, order[0], 1) // Group middleware
|
|
assert.Equal(t, order[1], 2) // Route-specific middleware
|
|
assert.Equal(t, order[2], 3) // Handler
|
|
})
|
|
}
|
|
|
|
// Tests for standard handlers
|
|
func TestStandardHandlers(t *testing.T) {
|
|
r := New()
|
|
|
|
handlerCalled := false
|
|
standardHandler := func(ctx *fasthttp.RequestCtx) {
|
|
handlerCalled = true
|
|
}
|
|
|
|
r.Get("/standard", StandardHandler(standardHandler))
|
|
|
|
_, found := performRequest(r, "GET", "/standard")
|
|
assert.True(t, found)
|
|
assert.True(t, handlerCalled)
|
|
}
|
|
|
|
// Test complete HTTP handler chain
|
|
func TestHandlerChain(t *testing.T) {
|
|
r := New()
|
|
|
|
handlerCalled := false
|
|
r.Get("/complete", func(ctx Ctx, params []string) {
|
|
handlerCalled = true
|
|
})
|
|
|
|
_, found := performRequest(r, "GET", "/complete")
|
|
assert.True(t, found)
|
|
assert.True(t, handlerCalled)
|
|
}
|
|
|
|
// Test advanced routes with multiple parameters
|
|
func TestAdvancedRoutes(t *testing.T) {
|
|
r := New()
|
|
|
|
called := false
|
|
r.Get("/api/[version]/users/[id]/profiles/[profile]", func(ctx Ctx, params []string) {
|
|
called = true
|
|
assert.Equal(t, len(params), 3)
|
|
assert.Equal(t, params[0], "v1")
|
|
assert.Equal(t, params[1], "123")
|
|
assert.Equal(t, params[2], "basic")
|
|
})
|
|
|
|
_, found := performRequest(r, "GET", "/api/v1/users/123/profiles/basic")
|
|
assert.True(t, found)
|
|
assert.True(t, called)
|
|
|
|
wildcardCalled := false
|
|
r.Get("/files/[type]", func(ctx Ctx, params []string) {
|
|
wildcardCalled = true
|
|
assert.Equal(t, params[0], "pdf")
|
|
})
|
|
|
|
_, found = performRequest(r, "GET", "/files/pdf")
|
|
assert.True(t, found)
|
|
assert.True(t, wildcardCalled)
|
|
}
|
|
|
|
// Test 404 handling
|
|
func TestNotFoundHandling(t *testing.T) {
|
|
r := New()
|
|
|
|
r.Get("/exists", func(ctx Ctx, params []string) {
|
|
ctx.SetStatusCode(fasthttp.StatusOK)
|
|
})
|
|
|
|
ln := fasthttputil.NewInmemoryListener()
|
|
defer ln.Close()
|
|
|
|
go fasthttp.Serve(ln, r.Handler())
|
|
|
|
req := fasthttp.AcquireRequest()
|
|
resp := fasthttp.AcquireResponse()
|
|
defer fasthttp.ReleaseRequest(req)
|
|
defer fasthttp.ReleaseResponse(resp)
|
|
|
|
req.SetRequestURI("http://example.com/not-exists")
|
|
req.Header.SetMethod("GET")
|
|
|
|
client := &fasthttp.HostClient{
|
|
Addr: "example.com",
|
|
Dial: func(addr string) (net.Conn, error) {
|
|
return ln.Dial()
|
|
},
|
|
}
|
|
|
|
err := client.Do(req, resp)
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, resp.StatusCode(), fasthttp.StatusNotFound)
|
|
}
|
|
|
|
// Benchmarks
|
|
func BenchmarkRouterLookup(b *testing.B) {
|
|
r := New()
|
|
handler := func(ctx Ctx, params []string) {}
|
|
|
|
// Setup routes for benchmarking
|
|
r.Get("/", handler)
|
|
r.Get("/users/all", handler)
|
|
r.Get("/users/[id]", handler)
|
|
r.Get("/users/[id]/posts/[postId]", handler)
|
|
|
|
b.Run("root", func(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
r.Lookup("GET", "/")
|
|
}
|
|
})
|
|
|
|
b.Run("static", func(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
r.Lookup("GET", "/users/all")
|
|
}
|
|
})
|
|
|
|
b.Run("single_param", func(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
r.Lookup("GET", "/users/123")
|
|
}
|
|
})
|
|
|
|
b.Run("multi_param", func(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
r.Lookup("GET", "/users/123/posts/456")
|
|
}
|
|
})
|
|
|
|
b.Run("not_found", func(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
r.Lookup("GET", "/nonexistent/path")
|
|
}
|
|
})
|
|
}
|
|
|
|
func BenchmarkParallelLookup(b *testing.B) {
|
|
r := New()
|
|
handler := func(ctx Ctx, params []string) {}
|
|
|
|
r.Get("/users/[id]", handler)
|
|
r.Get("/posts/[id]/comments", handler)
|
|
r.Get("/products/[category]/[id]", handler)
|
|
|
|
b.RunParallel(func(pb *testing.PB) {
|
|
i := 0
|
|
for pb.Next() {
|
|
switch i % 3 {
|
|
case 0:
|
|
r.Lookup("GET", "/users/123")
|
|
case 1:
|
|
r.Lookup("GET", "/posts/456/comments")
|
|
case 2:
|
|
r.Lookup("GET", "/products/electronics/789")
|
|
}
|
|
i++
|
|
}
|
|
})
|
|
}
|
|
|
|
func BenchmarkWildcardLookup(b *testing.B) {
|
|
r := New()
|
|
handler := func(ctx Ctx, params []string) {}
|
|
|
|
// Setup routes for benchmarking
|
|
r.Get("/files/*path", handler)
|
|
r.Get("/users/[id]/*action", handler)
|
|
|
|
b.Run("simple_wildcard", func(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
r.Lookup("GET", "/files/documents/reports/2024/q1.pdf")
|
|
}
|
|
})
|
|
|
|
b.Run("wildcard_with_param", func(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
r.Lookup("GET", "/users/123/settings/profile/avatar")
|
|
}
|
|
})
|
|
}
|
|
|
|
func BenchmarkMiddleware(b *testing.B) {
|
|
passthrough := func(next Handler) Handler {
|
|
return newHandler(func(ctx Ctx, params []string) {
|
|
next.Serve(ctx, params)
|
|
})
|
|
}
|
|
|
|
b.Run("no_middleware", func(b *testing.B) {
|
|
r := New()
|
|
r.Get("/test", func(ctx Ctx, params []string) {})
|
|
|
|
dummyCtx := &fasthttp.RequestCtx{}
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
h, params, _ := r.Lookup("GET", "/test")
|
|
h.Serve(dummyCtx, params)
|
|
}
|
|
})
|
|
|
|
b.Run("one_middleware", func(b *testing.B) {
|
|
r := New()
|
|
r.Use(passthrough)
|
|
r.Get("/test", func(ctx Ctx, params []string) {})
|
|
|
|
dummyCtx := &fasthttp.RequestCtx{}
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
h, params, _ := r.Lookup("GET", "/test")
|
|
h.Serve(dummyCtx, params)
|
|
}
|
|
})
|
|
|
|
b.Run("five_middleware", func(b *testing.B) {
|
|
r := New()
|
|
for i := 0; i < 5; i++ {
|
|
r.Use(passthrough)
|
|
}
|
|
r.Get("/test", func(ctx Ctx, params []string) {})
|
|
|
|
dummyCtx := &fasthttp.RequestCtx{}
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
h, params, _ := r.Lookup("GET", "/test")
|
|
h.Serve(dummyCtx, params)
|
|
}
|
|
})
|
|
}
|
|
|
|
func BenchmarkGroups(b *testing.B) {
|
|
handler := func(ctx Ctx, params []string) {}
|
|
|
|
b.Run("flat_route", func(b *testing.B) {
|
|
r := New()
|
|
r.Get("/api/v1/users", handler)
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
r.Lookup("GET", "/api/v1/users")
|
|
}
|
|
})
|
|
|
|
b.Run("grouped_route", func(b *testing.B) {
|
|
r := New()
|
|
api := r.Group("/api")
|
|
v1 := api.Group("/v1")
|
|
v1.Get("/users", handler)
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
r.Lookup("GET", "/api/v1/users")
|
|
}
|
|
})
|
|
|
|
b.Run("grouped_route_with_middleware", func(b *testing.B) {
|
|
r := New()
|
|
api := r.Group("/api")
|
|
api.Use(func(next Handler) Handler {
|
|
return newHandler(func(ctx Ctx, params []string) {
|
|
next.Serve(ctx, params)
|
|
})
|
|
})
|
|
v1 := api.Group("/v1")
|
|
v1.Get("/users", handler)
|
|
|
|
dummyCtx := &fasthttp.RequestCtx{}
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
h, params, _ := r.Lookup("GET", "/api/v1/users")
|
|
h.Serve(dummyCtx, params)
|
|
}
|
|
})
|
|
}
|
|
|
|
func BenchmarkFasthttpServer(b *testing.B) {
|
|
// Create a local listener
|
|
ln := fasthttputil.NewInmemoryListener()
|
|
defer ln.Close()
|
|
|
|
r := New()
|
|
|
|
r.Get("/users", func(ctx Ctx, params []string) {})
|
|
r.Get("/users/all", func(ctx Ctx, params []string) {})
|
|
r.Get("/users/[id]", func(ctx Ctx, params []string) {})
|
|
|
|
// Start the server
|
|
go fasthttp.Serve(ln, r.Handler())
|
|
|
|
// Create a client
|
|
client := &fasthttp.HostClient{
|
|
Addr: "example.com",
|
|
Dial: func(addr string) (net.Conn, error) {
|
|
return ln.Dial()
|
|
},
|
|
}
|
|
|
|
b.Run("root", func(b *testing.B) {
|
|
req := fasthttp.AcquireRequest()
|
|
resp := fasthttp.AcquireResponse()
|
|
defer fasthttp.ReleaseRequest(req)
|
|
defer fasthttp.ReleaseResponse(resp)
|
|
|
|
req.SetRequestURI("http://example.com/")
|
|
req.Header.SetMethod("GET")
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
client.Do(req, resp)
|
|
}
|
|
})
|
|
|
|
b.Run("static_path", func(b *testing.B) {
|
|
req := fasthttp.AcquireRequest()
|
|
resp := fasthttp.AcquireResponse()
|
|
defer fasthttp.ReleaseRequest(req)
|
|
defer fasthttp.ReleaseResponse(resp)
|
|
|
|
req.SetRequestURI("http://example.com/users/all")
|
|
req.Header.SetMethod("GET")
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
client.Do(req, resp)
|
|
}
|
|
})
|
|
|
|
b.Run("param_path", func(b *testing.B) {
|
|
req := fasthttp.AcquireRequest()
|
|
resp := fasthttp.AcquireResponse()
|
|
defer fasthttp.ReleaseRequest(req)
|
|
defer fasthttp.ReleaseResponse(resp)
|
|
|
|
req.SetRequestURI("http://example.com/users/123")
|
|
req.Header.SetMethod("GET")
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
client.Do(req, resp)
|
|
}
|
|
})
|
|
}
|
|
|
|
func BenchmarkComparison(b *testing.B) {
|
|
// Custom router setup
|
|
customRouter := New()
|
|
handler := func(ctx Ctx, params []string) {}
|
|
|
|
customRouter.Get("/", handler)
|
|
customRouter.Get("/users/all", handler)
|
|
customRouter.Get("/users/[id]", handler)
|
|
customRouter.Get("/users/[id]/posts/[postId]", handler)
|
|
|
|
// Simple fasthttp router setup (similar to http.ServeMux)
|
|
routes := map[string]fasthttp.RequestHandler{
|
|
"/": func(ctx *fasthttp.RequestCtx) {},
|
|
"/users/all": func(ctx *fasthttp.RequestCtx) {},
|
|
"/users/": func(ctx *fasthttp.RequestCtx) {}, // Best equivalent for dynamic routes
|
|
}
|
|
|
|
muxHandler := func(ctx *fasthttp.RequestCtx) {
|
|
path := string(ctx.Path())
|
|
if handler, ok := routes[path]; ok {
|
|
handler(ctx)
|
|
return
|
|
}
|
|
// Try prefix match (like http.ServeMux does)
|
|
for pattern, handler := range routes {
|
|
if len(pattern) > 0 && pattern[len(pattern)-1] == '/' && len(path) >= len(pattern) && path[:len(pattern)] == pattern {
|
|
handler(ctx)
|
|
return
|
|
}
|
|
}
|
|
ctx.SetStatusCode(fasthttp.StatusNotFound)
|
|
}
|
|
|
|
// Root path
|
|
b.Run("root_path", func(b *testing.B) {
|
|
b.Run("custom", func(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
customRouter.Lookup("GET", "/")
|
|
}
|
|
})
|
|
b.Run("mux", func(b *testing.B) {
|
|
ctx := &fasthttp.RequestCtx{}
|
|
ctx.Request.Header.SetMethod("GET")
|
|
ctx.Request.SetRequestURI("/")
|
|
for i := 0; i < b.N; i++ {
|
|
muxHandler(ctx)
|
|
}
|
|
})
|
|
})
|
|
|
|
// Static path
|
|
b.Run("static_path", func(b *testing.B) {
|
|
b.Run("custom", func(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
customRouter.Lookup("GET", "/users/all")
|
|
}
|
|
})
|
|
b.Run("mux", func(b *testing.B) {
|
|
ctx := &fasthttp.RequestCtx{}
|
|
ctx.Request.Header.SetMethod("GET")
|
|
ctx.Request.SetRequestURI("/users/all")
|
|
for i := 0; i < b.N; i++ {
|
|
muxHandler(ctx)
|
|
}
|
|
})
|
|
})
|
|
|
|
// Dynamic path
|
|
b.Run("dynamic_path", func(b *testing.B) {
|
|
b.Run("custom", func(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
customRouter.Lookup("GET", "/users/123")
|
|
}
|
|
})
|
|
b.Run("mux", func(b *testing.B) {
|
|
ctx := &fasthttp.RequestCtx{}
|
|
ctx.Request.Header.SetMethod("GET")
|
|
ctx.Request.SetRequestURI("/users/123")
|
|
for i := 0; i < b.N; i++ {
|
|
muxHandler(ctx)
|
|
}
|
|
})
|
|
})
|
|
|
|
// Not found
|
|
b.Run("not_found", func(b *testing.B) {
|
|
b.Run("custom", func(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
customRouter.Lookup("GET", "/nonexistent/path")
|
|
}
|
|
})
|
|
b.Run("mux", func(b *testing.B) {
|
|
ctx := &fasthttp.RequestCtx{}
|
|
ctx.Request.Header.SetMethod("GET")
|
|
ctx.Request.SetRequestURI("/nonexistent/path")
|
|
for i := 0; i < b.N; i++ {
|
|
muxHandler(ctx)
|
|
}
|
|
})
|
|
})
|
|
}
|