Skip to content

Commit 31b4180

Browse files
authored
feat(http1): Add support for sending HTTP/1.1 Chunked Trailer Fields (#3375)
Closes #2719
1 parent 0f2929b commit 31b4180

File tree

8 files changed

+611
-31
lines changed

8 files changed

+611
-31
lines changed

src/proto/h1/conn.rs

+36-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use std::time::Duration;
88

99
use crate::rt::{Read, Write};
1010
use bytes::{Buf, Bytes};
11-
use http::header::{HeaderValue, CONNECTION};
11+
use http::header::{HeaderValue, CONNECTION, TE};
1212
use http::{HeaderMap, Method, Version};
1313
use httparse::ParserConfig;
1414

@@ -75,6 +75,7 @@ where
7575
// We assume a modern world where the remote speaks HTTP/1.1.
7676
// If they tell us otherwise, we'll downgrade in `read_head`.
7777
version: Version::HTTP_11,
78+
allow_trailer_fields: false,
7879
},
7980
_marker: PhantomData,
8081
}
@@ -264,6 +265,13 @@ where
264265
self.state.reading = Reading::Body(Decoder::new(msg.decode));
265266
}
266267

268+
self.state.allow_trailer_fields = msg
269+
.head
270+
.headers
271+
.get(TE)
272+
.map(|te_header| te_header == "trailers")
273+
.unwrap_or(false);
274+
267275
Poll::Ready(Some(Ok((msg.head, msg.decode, wants))))
268276
}
269277

@@ -640,6 +648,31 @@ where
640648
self.state.writing = state;
641649
}
642650

651+
pub(crate) fn write_trailers(&mut self, trailers: HeaderMap) {
652+
if T::is_server() && self.state.allow_trailer_fields == false {
653+
debug!("trailers not allowed to be sent");
654+
return;
655+
}
656+
debug_assert!(self.can_write_body() && self.can_buffer_body());
657+
658+
match self.state.writing {
659+
Writing::Body(ref encoder) => {
660+
if let Some(enc_buf) =
661+
encoder.encode_trailers(trailers, self.state.title_case_headers)
662+
{
663+
self.io.buffer(enc_buf);
664+
665+
self.state.writing = if encoder.is_last() || encoder.is_close_delimited() {
666+
Writing::Closed
667+
} else {
668+
Writing::KeepAlive
669+
};
670+
}
671+
}
672+
_ => unreachable!("write_trailers invalid state: {:?}", self.state.writing),
673+
}
674+
}
675+
643676
pub(crate) fn write_body_and_end(&mut self, chunk: B) {
644677
debug_assert!(self.can_write_body() && self.can_buffer_body());
645678
// empty chunks should be discarded at Dispatcher level
@@ -842,6 +875,8 @@ struct State {
842875
upgrade: Option<crate::upgrade::Pending>,
843876
/// Either HTTP/1.0 or 1.1 connection
844877
version: Version,
878+
/// Flag to track if trailer fields are allowed to be sent
879+
allow_trailer_fields: bool,
845880
}
846881

847882
#[derive(Debug)]

src/proto/h1/dispatch.rs

+24-18
Original file line numberDiff line numberDiff line change
@@ -351,27 +351,33 @@ where
351351
*clear_body = true;
352352
crate::Error::new_user_body(e)
353353
})?;
354-
let chunk = if let Ok(data) = frame.into_data() {
355-
data
356-
} else {
357-
trace!("discarding non-data frame");
358-
continue;
359-
};
360-
let eos = body.is_end_stream();
361-
if eos {
362-
*clear_body = true;
363-
if chunk.remaining() == 0 {
364-
trace!("discarding empty chunk");
365-
self.conn.end_body()?;
354+
355+
if frame.is_data() {
356+
let chunk = frame.into_data().unwrap_or_else(|_| unreachable!());
357+
let eos = body.is_end_stream();
358+
if eos {
359+
*clear_body = true;
360+
if chunk.remaining() == 0 {
361+
trace!("discarding empty chunk");
362+
self.conn.end_body()?;
363+
} else {
364+
self.conn.write_body_and_end(chunk);
365+
}
366366
} else {
367-
self.conn.write_body_and_end(chunk);
367+
if chunk.remaining() == 0 {
368+
trace!("discarding empty chunk");
369+
continue;
370+
}
371+
self.conn.write_body(chunk);
368372
}
373+
} else if frame.is_trailers() {
374+
*clear_body = true;
375+
self.conn.write_trailers(
376+
frame.into_trailers().unwrap_or_else(|_| unreachable!()),
377+
);
369378
} else {
370-
if chunk.remaining() == 0 {
371-
trace!("discarding empty chunk");
372-
continue;
373-
}
374-
self.conn.write_body(chunk);
379+
trace!("discarding unknown frame");
380+
continue;
375381
}
376382
} else {
377383
*clear_body = true;

0 commit comments

Comments
 (0)