Skip to content
Merged
Show file tree
Hide file tree
Changes from 13 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
8 changes: 8 additions & 0 deletions app.go
Original file line number Diff line number Diff line change
Expand Up @@ -599,6 +599,7 @@ const (
methodOptions
methodTrace
methodPatch
methodQuery
)

// HTTP methods enabled by default
Expand All @@ -612,6 +613,7 @@ var DefaultMethods = []string{
methodOptions: MethodOptions,
methodTrace: MethodTrace,
methodPatch: MethodPatch,
methodQuery: MethodQuery,
}

// httpReadResponse - Used for test mocking http.ReadResponse
Expand Down Expand Up @@ -1082,6 +1084,12 @@ func (app *App) Patch(path string, handler any, handlers ...any) Router {
return app.Add([]string{MethodPatch}, path, handler, handlers...)
}

// Query registers a route for QUERY methods that performs a safe, idempotent
// query with a request body.
func (app *App) Query(path string, handler any, handlers ...any) Router {
return app.Add([]string{MethodQuery}, path, handler, handlers...)
}

// Add allows you to specify multiple HTTP methods to register a route.
// The provided handlers are executed in order, starting with `handler` and then the variadic `handlers`.
func (app *App) Add(methods []string, path string, handler any, handlers ...any) Router {
Expand Down
12 changes: 11 additions & 1 deletion app_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1298,6 +1298,9 @@ func Test_App_Methods(t *testing.T) {
app.Trace("/:john?/:doe?", dummyHandler)
testStatus200(t, app, "/john/doe", MethodTrace)

app.Query("/:john?/:doe?", dummyHandler)
testStatus200(t, app, "/john/doe", MethodQuery)

app.Get("/:john?/:doe?", dummyHandler)
testStatus200(t, app, "/john/doe", MethodGet)

Expand Down Expand Up @@ -1767,6 +1770,9 @@ func Test_App_Group(t *testing.T) {
grp.Trace("/TRACE", dummyHandler)
testStatus200(t, app, "/test/TRACE", MethodTrace)

grp.Query("/QUERY", dummyHandler)
testStatus200(t, app, "/test/QUERY", MethodQuery)

grp.All("/ALL", dummyHandler)
testStatus200(t, app, "/test/ALL", MethodPost)

Expand Down Expand Up @@ -1804,7 +1810,8 @@ func Test_App_RouteChain(t *testing.T) {
Connect(dummyHandler).
Options(dummyHandler).
Trace(dummyHandler).
Patch(dummyHandler)
Patch(dummyHandler).
Query(dummyHandler)

testStatus200(t, app, "/test", MethodGet)
testStatus200(t, app, "/test", MethodHead)
Expand All @@ -1815,6 +1822,7 @@ func Test_App_RouteChain(t *testing.T) {
testStatus200(t, app, "/test", MethodOptions)
testStatus200(t, app, "/test", MethodTrace)
testStatus200(t, app, "/test", MethodPatch)
testStatus200(t, app, "/test", MethodQuery)

register.RouteChain("/v1").Get(dummyHandler).Post(dummyHandler)

Expand Down Expand Up @@ -2603,6 +2611,7 @@ func Test_App_Stack(t *testing.T) {
app.Get("/path1", testEmptyHandler)
app.Get("/path2", testEmptyHandler)
app.Post("/path3", testEmptyHandler)
app.Query("/path4", testEmptyHandler)

app.startupProcess()

Expand All @@ -2618,6 +2627,7 @@ func Test_App_Stack(t *testing.T) {
require.Len(t, stack[app.methodInt(MethodConnect)], 1)
require.Len(t, stack[app.methodInt(MethodOptions)], 1)
require.Len(t, stack[app.methodInt(MethodTrace)], 1)
require.Len(t, stack[app.methodInt(MethodQuery)], 2)
}

// go test -run Test_App_HandlersCount
Expand Down
5 changes: 5 additions & 0 deletions client/request.go
Original file line number Diff line number Diff line change
Expand Up @@ -664,6 +664,11 @@ func (r *Request) Patch(url string) (*Response, error) {
return r.SetURL(url).SetMethod(fiber.MethodPatch).Send()
}

// Query sends a QUERY request to the given URL.
func (r *Request) Query(url string) (*Response, error) {
return r.SetURL(url).SetMethod(fiber.MethodQuery).Send()
}

// Custom sends a request with a custom HTTP method to the given URL.
func (r *Request) Custom(url, method string) (*Response, error) {
return r.SetURL(url).SetMethod(method).Send()
Expand Down
28 changes: 28 additions & 0 deletions client/request_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1067,6 +1067,34 @@ func Test_Request_Patch(t *testing.T) {
}
}

func Test_Request_Query(t *testing.T) {
t.Parallel()

app, ln, start := createHelperServer(t)

app.Query("/", func(c fiber.Ctx) error {
return c.Send(c.Body())
})

go start()
time.Sleep(100 * time.Millisecond)

client := New().SetDial(ln)

for range 5 {
resp, err := AcquireRequest().
SetClient(client).
SetRawBody([]byte("query body")).
Query("http://example.com")

require.NoError(t, err)
require.Equal(t, fiber.StatusOK, resp.StatusCode())
require.Equal(t, "query body", resp.String())

resp.Close()
}
}

func Test_Request_Header_With_Server(t *testing.T) {
t.Parallel()
handler := func(c fiber.Ctx) error {
Expand Down
1 change: 1 addition & 0 deletions constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const (
MethodConnect = "CONNECT" // RFC 7231, 4.3.6
MethodOptions = "OPTIONS" // RFC 7231, 4.3.7
MethodTrace = "TRACE" // RFC 7231, 4.3.8
MethodQuery = "QUERY" // RFC 10008
methodUse = "USE"
)

Expand Down
1 change: 1 addition & 0 deletions docs/api/constants.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ const (
MethodConnect = "CONNECT" // RFC 7231, 4.3.6
MethodOptions = "OPTIONS" // RFC 7231, 4.3.7
MethodTrace = "TRACE" // RFC 7231, 4.3.8
MethodQuery = "QUERY" // RFC 10008
methodUse = "USE"
)
```
Expand Down
8 changes: 8 additions & 0 deletions docs/client/request.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,14 @@ func (r *Request) Head(url string) (*Response, error)
func (r *Request) Options(url string) (*Response, error)
```

### Query

**Query** sends a QUERY request. It sets the URL and method to QUERY, then sends the request.

```go title="Signature"
func (r *Request) Query(url string) (*Response, error)
```

### Custom

**Custom** sends a request using a custom HTTP method. For example, you can use this to send a TRACE or CONNECT request.
Expand Down
1 change: 1 addition & 0 deletions docs/partials/routing/handler.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ func (app *App) Connect(path string, handler any, handlers ...any) Router
func (app *App) Options(path string, handler any, handlers ...any) Router
func (app *App) Trace(path string, handler any, handlers ...any) Router
func (app *App) Patch(path string, handler any, handlers ...any) Router
func (app *App) Query(path string, handler any, handlers ...any) Router

// Add registers the same handlers on multiple methods at once.
// The handlers run in order, starting with `handler` and then the variadic `handlers`.
Expand Down
10 changes: 10 additions & 0 deletions domain.go
Original file line number Diff line number Diff line change
Expand Up @@ -527,6 +527,12 @@ func (d *domainRouter) Patch(path string, handler any, handlers ...any) Router {
return d.Add([]string{MethodPatch}, path, handler, handlers...)
}

// Query registers a route for QUERY methods.
// The handler only executes when the request hostname matches the domain pattern.
func (d *domainRouter) Query(path string, handler any, handlers ...any) Router {
return d.Add([]string{MethodQuery}, path, handler, handlers...)
}

// Add allows you to specify multiple HTTP methods to register a route.
// The handler only executes when the request hostname matches the domain pattern.
func (d *domainRouter) Add(methods []string, path string, handler any, handlers ...any) Router {
Expand Down Expand Up @@ -674,6 +680,10 @@ func (r *domainRegistering) Patch(handler any, handlers ...any) Register {
return r.Add([]string{MethodPatch}, handler, handlers...)
}

func (r *domainRegistering) Query(handler any, handlers ...any) Register {
return r.Add([]string{MethodQuery}, handler, handlers...)
}

func (r *domainRegistering) Add(methods []string, handler any, handlers ...any) Register {
converted := collectHandlers("domain", append([]any{handler}, handlers...)...)
wrapped := r.domain.wrapHandlers(converted)
Expand Down
6 changes: 5 additions & 1 deletion domain_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,7 @@ func Test_Domain_HTTPMethods(t *testing.T) {
{method: MethodPut, reg: func(r Router, p string, h any, hs ...any) Router { return r.Put(p, h, hs...) }},
{method: MethodDelete, reg: func(r Router, p string, h any, hs ...any) Router { return r.Delete(p, h, hs...) }},
{method: MethodPatch, reg: func(r Router, p string, h any, hs ...any) Router { return r.Patch(p, h, hs...) }},
{method: MethodQuery, reg: func(r Router, p string, h any, hs ...any) Router { return r.Query(p, h, hs...) }},
{method: MethodOptions, reg: func(r Router, p string, h any, hs ...any) Router { return r.Options(p, h, hs...) }},
{method: MethodConnect, reg: func(r Router, p string, h any, hs ...any) Router { return r.Connect(p, h, hs...) }},
{method: MethodTrace, reg: func(r Router, p string, h any, hs ...any) Router { return r.Trace(p, h, hs...) }},
Expand Down Expand Up @@ -1058,8 +1059,11 @@ func Test_Domain_RouteChainAllMethods(t *testing.T) {
rc.Patch(func(c Ctx) error {
return c.SendString("patch")
})
rc.Query(func(c Ctx) error {
return c.SendString("query")
})

for _, method := range []string{MethodPut, MethodDelete, MethodPatch, MethodOptions} {
for _, method := range []string{MethodPut, MethodDelete, MethodPatch, MethodOptions, MethodQuery} {
req := httptest.NewRequest(method, "/test", http.NoBody)
req.Host = "api.example.com"
resp, err := app.Test(req)
Expand Down
6 changes: 6 additions & 0 deletions group.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,12 @@ func (grp *Group) Patch(path string, handler any, handlers ...any) Router {
return grp.Add([]string{MethodPatch}, path, handler, handlers...)
}

// Query registers a route for QUERY methods that performs a safe, idempotent
// query with a request body.
func (grp *Group) Query(path string, handler any, handlers ...any) Router {
return grp.Add([]string{MethodQuery}, path, handler, handlers...)
}

// Add allows you to specify multiple HTTP methods to register a route.
// The provided handlers are executed in order, starting with `handler` and then the variadic `handlers`.
func (grp *Group) Add(methods []string, path string, handler any, handlers ...any) Router {
Expand Down
5 changes: 4 additions & 1 deletion helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -935,6 +935,8 @@ func (app *App) methodInt(s string) int {
return methodTrace
case MethodPatch:
return methodPatch
case MethodQuery:
return methodQuery
default:
return -1
}
Expand All @@ -954,7 +956,8 @@ func IsMethodSafe(m string) bool {
case MethodGet,
MethodHead,
MethodOptions,
MethodTrace:
MethodTrace,
MethodQuery:
return true
default:
return false
Expand Down
9 changes: 9 additions & 0 deletions middleware/cache/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -1290,6 +1290,15 @@ func defaultKeyGenerator(c fiber.Ctx, cfg *Config) string {
buf = append(buf, canonicalCookieSubset(c, cfg.KeyCookies)...)
}

if c.Method() == fiber.MethodQuery {
// RFC 10008 only requires the cache key to incorporate the request content,
// not that it be hashed. boundKeySegment keeps small bodies verbatim (no
// hashing on the hot path) and hashes only bodies larger than
// maxKeyDimensionSegmentLength, which still bounds the key length.
buf = append(buf, '|', 'b', '=')
buf = append(buf, boundKeySegment(utils.UnsafeString(c.Request().Body()))...)
}

result := string(buf)

// Reset buffer and return to pool, but discard if it grew too large
Expand Down
Loading
Loading