Skip to content

Commit a9fcaf5

Browse files
committed
feat: add support for HTTP QUERY method
The HTTP QUERY method is a means of making a safe, idempotent request that contains content. The primary motivator for this case is graphql, replacing HTTP POST for read only requests, however it is application agnostic. This method is still in draft but since 2025-01-07 it has reached consensus so it should be stable enough to make it unlikely to change this implementation. https://datatracker.ietf.org/doc/draft-ietf-httpbis-safe-method-w-body/
1 parent 68845bd commit a9fcaf5

File tree

4 files changed

+46
-2
lines changed

4 files changed

+46
-2
lines changed

benches/src/header_name.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ fn make_all_known_headers() -> Vec<Vec<u8>> {
6868
// standard_response_headers
6969
b"Accept-Patch".to_vec(),
7070
b"Accept-Ranges".to_vec(),
71+
b"Accept-Query".to_vec(),
7172
b"Access-Control-Allow-Credentials".to_vec(),
7273
b"Access-Control-Allow-Headers".to_vec(),
7374
b"Access-Control-Allow-Methods".to_vec(),
@@ -191,6 +192,7 @@ static ALL_KNOWN_HEADERS: &[&str] = &[
191192
// standard_response_headers
192193
"accept-patch",
193194
"accept-ranges",
195+
"accept-query",
194196
"access-control-allow-credentials",
195197
"access-control-allow-headers",
196198
"access-control-allow-methods",

src/method.rs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ use std::{fmt, str};
3030
///
3131
/// Currently includes 8 variants representing the 8 methods defined in
3232
/// [RFC 7230](https://tools.ietf.org/html/rfc7231#section-4.1), plus PATCH,
33-
/// and an Extension variant for all extensions.
33+
/// QUERY, and an Extension variant for all extensions.
3434
///
3535
/// # Examples
3636
///
@@ -60,6 +60,7 @@ enum Inner {
6060
Trace,
6161
Connect,
6262
Patch,
63+
Query,
6364
// If the extension is short enough, store it inline
6465
ExtensionInline(InlineExtension),
6566
// Otherwise, allocate it
@@ -94,6 +95,9 @@ impl Method {
9495
/// TRACE
9596
pub const TRACE: Method = Method(Trace);
9697

98+
/// TRACE
99+
pub const QUERY: Method = Method(Query);
100+
97101
/// Converts a slice of bytes to an HTTP method.
98102
pub fn from_bytes(src: &[u8]) -> Result<Method, InvalidMethod> {
99103
match src.len() {
@@ -111,6 +115,7 @@ impl Method {
111115
5 => match src {
112116
b"PATCH" => Ok(Method(Patch)),
113117
b"TRACE" => Ok(Method(Trace)),
118+
b"QUERY" => Ok(Method(Query)),
114119
_ => Method::extension_inline(src),
115120
},
116121
6 => match src {
@@ -146,7 +151,7 @@ impl Method {
146151
/// See [the spec](https://tools.ietf.org/html/rfc7231#section-4.2.1)
147152
/// for more words.
148153
pub fn is_safe(&self) -> bool {
149-
matches!(self.0, Get | Head | Options | Trace)
154+
matches!(self.0, Get | Head | Options | Trace | Query)
150155
}
151156

152157
/// Whether a method is considered "idempotent", meaning the request has
@@ -174,6 +179,7 @@ impl Method {
174179
Trace => "TRACE",
175180
Connect => "CONNECT",
176181
Patch => "PATCH",
182+
Query => "QUERY",
177183
ExtensionInline(ref inline) => inline.as_str(),
178184
ExtensionAllocated(ref allocated) => allocated.as_str(),
179185
}
@@ -452,6 +458,7 @@ mod test {
452458
assert!(Method::DELETE.is_idempotent());
453459
assert!(Method::HEAD.is_idempotent());
454460
assert!(Method::TRACE.is_idempotent());
461+
assert!(Method::QUERY.is_idempotent());
455462

456463
assert!(!Method::POST.is_idempotent());
457464
assert!(!Method::CONNECT.is_idempotent());

src/request.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -413,6 +413,28 @@ impl Request<()> {
413413
{
414414
Builder::new().method(Method::TRACE).uri(uri)
415415
}
416+
417+
/// Creates a new `Builder` initialized with a QUERY method and the given URI.
418+
///
419+
/// This method returns an instance of `Builder` which can be used to
420+
/// create a `Request`.
421+
///
422+
/// # Example
423+
///
424+
/// ```
425+
/// # use http::*;
426+
///
427+
/// let request = Request::query("https://www.rust-lang.org/")
428+
/// .body(())
429+
/// .unwrap();
430+
/// ```
431+
pub fn query<T>(uri: T) -> Builder
432+
where
433+
T: TryInto<Uri>,
434+
<T as TryInto<Uri>>::Error: Into<crate::Error>,
435+
{
436+
Builder::new().method(Method::QUERY).uri(uri)
437+
}
416438
}
417439

418440
impl<T> Request<T> {

util/src/main.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,19 @@ standard_headers! {
127127
"#,
128128
"accept-ranges";
129129

130+
r#"
131+
/// Advertises which query formats the server is able to understand.
132+
///
133+
/// Accept-Query should appear in the OPTIONS response for any resource that
134+
/// supports the use of the QUERY method. The presence of the
135+
/// Accept-Query header in response to any method is an implicit indication
136+
/// that QUERY is allowed on the resource identified by the URI. The
137+
/// presence of a specific query document format in this header indicates
138+
/// that that specific format is allowed on the resource identified by the
139+
/// URI.
140+
"#,
141+
"accept-query";
142+
130143
r#"
131144
/// Preflight response indicating if the response to the request can be
132145
/// exposed to the page.

0 commit comments

Comments
 (0)