Skip to content

Commit 29010be

Browse files
authored
feat: add support for the HTTP QUERY method (RFC 10008) (#1187)
RFC 10008 defines QUERY, a safe and idempotent HTTP method that carries the query as request content, filling the gap between GET and POST. - Add the MethodQuery constant. - Add the Request.Query(url) helper, matching the existing verb helpers. - Send the request body for QUERY (isPayloadSupported), since QUERY always carries content. - Classify QUERY as idempotent (enables automatic retries) and read-only (enables hedging), per its RFC 10008 safe and idempotent semantics. - Add tests for the helper and the classification. * docs: add early-adoption note to Request.Query (review)
1 parent ea5b6cf commit 29010be

4 files changed

Lines changed: 60 additions & 2 deletions

File tree

client.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,9 @@ const (
4747

4848
// MethodTrace is the HTTP TRACE method.
4949
MethodTrace = "TRACE"
50+
51+
// MethodQuery is the HTTP QUERY method.
52+
MethodQuery = "QUERY"
5053
)
5154

5255
const (

hedging.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -284,7 +284,7 @@ func (ht *Hedging) RoundTrip(req *http.Request) (*http.Response, error) {
284284
// isReadOnlyMethod reports whether the HTTP method is read-only (safe for hedging).
285285
func isReadOnlyMethod(method string) bool {
286286
switch method {
287-
case MethodGet, MethodHead, MethodOptions, MethodTrace:
287+
case MethodGet, MethodHead, MethodOptions, MethodTrace, MethodQuery:
288288
return true
289289
default:
290290
return false

query_test.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package resty
2+
3+
import (
4+
"io"
5+
"net/http"
6+
"testing"
7+
)
8+
9+
func TestQuery(t *testing.T) {
10+
ts := createTestServer(func(w http.ResponseWriter, r *http.Request) {
11+
if r.Method != MethodQuery {
12+
t.Errorf("expected method %q, got %q", MethodQuery, r.Method)
13+
}
14+
body, _ := io.ReadAll(r.Body)
15+
w.Header().Set("Content-Type", "application/json")
16+
w.WriteHeader(http.StatusOK)
17+
_, _ = w.Write(body)
18+
})
19+
defer ts.Close()
20+
21+
resp, err := dcnl().R().
22+
SetHeader("Content-Type", "application/sql").
23+
SetBody("select * from users limit 10").
24+
Query(ts.URL + "/")
25+
26+
assertError(t, err)
27+
assertEqual(t, http.StatusOK, resp.StatusCode())
28+
assertEqual(t, "select * from users limit 10", resp.String())
29+
30+
logResponse(t, resp)
31+
}
32+
33+
func TestQueryMethodClassification(t *testing.T) {
34+
// QUERY is safe and idempotent per RFC 10008, so it must be treated as
35+
// read-only (hedging) and idempotent (automatic retries).
36+
assertEqual(t, true, isReadOnlyMethod(MethodQuery))
37+
38+
r := dcnl().R()
39+
r.Method = MethodQuery
40+
assertEqual(t, true, r.isIdempotent())
41+
}

request.go

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1438,6 +1438,18 @@ func (r *Request) Trace(url string) (*Response, error) {
14381438
return r.Execute(MethodTrace, url)
14391439
}
14401440

1441+
// Query method does QUERY HTTP request. QUERY is a safe, idempotent method that
1442+
// carries the query as request content; it's defined in section 2 of [RFC 10008].
1443+
//
1444+
// NOTE:
1445+
// - RFC is in the early stage of adoption in the industry; server side and browsers
1446+
// are yet to add support. You may run into unexpected failures due to infrastructure/environment.
1447+
//
1448+
// [RFC 10008]: https://datatracker.ietf.org/doc/html/rfc10008.html#section-2
1449+
func (r *Request) Query(url string) (*Response, error) {
1450+
return r.Execute(MethodQuery, url)
1451+
}
1452+
14411453
// Send method performs the HTTP request using the method and URL already defined
14421454
// for current [Request].
14431455
//
@@ -1800,7 +1812,8 @@ func (r *Request) isPayloadSupported() bool {
18001812
return true
18011813
}
18021814

1803-
if r.Method == MethodPost || r.Method == MethodPut || r.Method == MethodPatch {
1815+
// QUERY (RFC 10008) always carries the query as request content.
1816+
if r.Method == MethodPost || r.Method == MethodPut || r.Method == MethodPatch || r.Method == MethodQuery {
18041817
return true
18051818
}
18061819

@@ -1853,6 +1866,7 @@ var idempotentMethods = map[string]struct{}{
18531866
MethodHead: {},
18541867
MethodOptions: {},
18551868
MethodPut: {},
1869+
MethodQuery: {},
18561870
MethodTrace: {},
18571871
}
18581872

0 commit comments

Comments
 (0)