Skip to content

Commit 2e96e51

Browse files
committed
feat(response): introduce trailers support
1 parent bb681ad commit 2e96e51

2 files changed

Lines changed: 69 additions & 5 deletions

File tree

src/async_impl/response.rs

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ pub struct Response {
3030
// Boxed to save space (11 words to 1 word), and it's not accessed
3131
// frequently internally.
3232
url: Box<Url>,
33+
trailers: Option<HeaderMap>,
3334
}
3435

3536
impl Response {
@@ -48,6 +49,7 @@ impl Response {
4849
Response {
4950
res,
5051
url: Box::new(url),
52+
trailers: None,
5153
}
5254
}
5355

@@ -93,6 +95,11 @@ impl Response {
9395
Body::size_hint(self.res.body()).exact()
9496
}
9597

98+
/// Get the trailers of this `Response`.
99+
pub fn trailers(&mut self) -> Option<&HeaderMap> {
100+
self.trailers.as_ref()
101+
}
102+
96103
/// Retrieve the cookies contained in the response.
97104
///
98105
/// Note that invalid 'Set-Cookie' headers will be ignored.
@@ -313,16 +320,22 @@ impl Response {
313320
/// # }
314321
/// ```
315322
pub async fn chunk(&mut self) -> crate::Result<Option<Bytes>> {
323+
use http_body::Frame;
316324
use http_body_util::BodyExt;
317325

318326
// loop to ignore unrecognized frames
319327
loop {
320328
if let Some(res) = self.res.body_mut().frame().await {
321329
let frame = res.map_err(crate::error::decode)?;
322-
if let Ok(buf) = frame.into_data() {
323-
return Ok(Some(buf));
330+
match frame.into_trailers().map_err(Frame::into_data) {
331+
Ok(trailers) => {
332+
self.trailers = Some(trailers);
333+
}
334+
Err(Ok(buf)) => {
335+
return Ok(Some(buf));
336+
}
337+
_ => continue,
324338
}
325-
// else continue
326339
} else {
327340
return Ok(None);
328341
}
@@ -468,6 +481,7 @@ impl<T: Into<Body>> From<http::Response<T>> for Response {
468481
Response {
469482
res,
470483
url: Box::new(url),
484+
trailers: None,
471485
}
472486
}
473487
}

tests/client.rs

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,18 @@
22
#![cfg(not(feature = "rustls-tls-manual-roots-no-provider"))]
33
mod support;
44

5+
use bytes::Bytes;
6+
use http_body_util::{BodyExt, Full};
57
use support::server;
68

7-
use http::header::{CONTENT_LENGTH, CONTENT_TYPE, TRANSFER_ENCODING};
9+
use http::{
10+
header::{self, CONTENT_LENGTH, CONTENT_TYPE, TRANSFER_ENCODING},
11+
StatusCode,
12+
};
813
#[cfg(feature = "json")]
914
use std::collections::HashMap;
1015

11-
use reqwest::Client;
16+
use reqwest::{Body, Client};
1217
use tokio::io::AsyncWriteExt;
1318

1419
#[tokio::test]
@@ -528,3 +533,48 @@ async fn error_has_url() {
528533
let err = reqwest::get(u).await.unwrap_err();
529534
assert_eq!(err.url().map(AsRef::as_ref), Some(u), "{err:?}");
530535
}
536+
537+
#[tokio::test]
538+
async fn response_trailers() {
539+
let server = server::http(move |req| async move {
540+
assert_eq!(req.uri().path(), "/trailers");
541+
542+
let body = Full::new(Bytes::from("HelloWorld!")).with_trailers(async move {
543+
let mut trailers = http::HeaderMap::new();
544+
trailers.insert("chunky-trailer", "custom-value".parse().unwrap());
545+
Some(Ok(trailers))
546+
});
547+
let mut resp = http::Response::new(Body::wrap(body));
548+
resp.headers_mut().insert(
549+
header::TRAILER,
550+
header::HeaderValue::from_static("chunky-trailer"),
551+
);
552+
resp.headers_mut().insert(
553+
header::TRANSFER_ENCODING,
554+
header::HeaderValue::from_static("chunked"),
555+
);
556+
557+
resp
558+
});
559+
560+
let mut res = reqwest::Client::new()
561+
.get(format!("http://{}/trailers", server.addr()))
562+
.header(header::TE, "trailers")
563+
.send()
564+
.await
565+
.expect("Failed to get response");
566+
567+
assert_eq!(res.status(), StatusCode::OK);
568+
569+
// Read the body using chunk() to preserve response ownership
570+
let mut body_content = Vec::new();
571+
while let Some(chunk) = res.chunk().await.expect("Failed to read chunk") {
572+
body_content.extend_from_slice(&chunk);
573+
}
574+
575+
let body = String::from_utf8(body_content).expect("Invalid UTF-8");
576+
assert_eq!(body, "HelloWorld!");
577+
578+
let trailers = res.trailers().expect("Expected trailers but got None");
579+
assert_eq!(trailers.get("chunky-trailer").unwrap(), "custom-value");
580+
}

0 commit comments

Comments
 (0)