Skip to content

Commit 549435c

Browse files
committed
Allow recording the raw HTTP/1 headers sent and received
This allows feeding them into a WARC file (https://en.wikipedia.org/wiki/WARC_(file_format)). The rest of the request and response is already available as it's either the explicitly set body or the received response body. This includes the final `\r\n\r\n` between the headers and the response (so that it can be distinguished from bare `\n\n`). Needed for a similar request in reqwest: seanmonstar/reqwest#1229.
1 parent 621d8e4 commit 549435c

File tree

6 files changed

+154
-4
lines changed

6 files changed

+154
-4
lines changed

src/client/conn/http1.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,8 @@ pub struct Builder {
112112
h1_parser_config: ParserConfig,
113113
h1_writev: Option<bool>,
114114
h1_title_case_headers: bool,
115+
h1_record_raw_request_headers: bool,
116+
h1_record_raw_response_headers: bool,
115117
h1_preserve_header_case: bool,
116118
h1_max_headers: Option<usize>,
117119
#[cfg(feature = "ffi")]
@@ -312,6 +314,8 @@ impl Builder {
312314
h1_read_buf_exact_size: None,
313315
h1_parser_config: Default::default(),
314316
h1_title_case_headers: false,
317+
h1_record_raw_request_headers: false,
318+
h1_record_raw_response_headers: false,
315319
h1_preserve_header_case: false,
316320
h1_max_headers: None,
317321
#[cfg(feature = "ffi")]
@@ -428,6 +432,22 @@ impl Builder {
428432
self
429433
}
430434

435+
/// Set whether to record the raw headers sent.
436+
///
437+
/// Default is false.
438+
pub fn record_raw_request_headers(&mut self, enabled: bool) -> &mut Builder {
439+
self.h1_record_raw_request_headers = enabled;
440+
self
441+
}
442+
443+
/// Set whether to record the raw headers received.
444+
///
445+
/// Default is false.
446+
pub fn record_raw_response_headers(&mut self, enabled: bool) -> &mut Builder {
447+
self.h1_record_raw_response_headers = enabled;
448+
self
449+
}
450+
431451
/// Set whether to support preserving original header cases.
432452
///
433453
/// Currently, this will record the original cases received, and store them
@@ -539,6 +559,12 @@ impl Builder {
539559
if opts.h1_title_case_headers {
540560
conn.set_title_case_headers();
541561
}
562+
if opts.h1_record_raw_request_headers {
563+
conn.set_record_raw_request_headers();
564+
}
565+
if opts.h1_record_raw_response_headers {
566+
conn.set_record_raw_response_headers();
567+
}
542568
if opts.h1_preserve_header_case {
543569
conn.set_preserve_header_case();
544570
}

src/ext/mod.rs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,39 @@ impl fmt::Debug for Protocol {
8686
}
8787
}
8888

89+
/// Raw request headers as sent over the TLS or TCP connection.
90+
#[cfg(all(any(feature = "client", feature = "server"), feature = "http1"))]
91+
#[derive(Clone, Debug)]
92+
pub struct RawRequestHeaders(Bytes);
93+
94+
#[cfg(all(any(feature = "client", feature = "server"), feature = "http1"))]
95+
impl RawRequestHeaders {
96+
/// Returns the raw bytes sent for the header of the request.
97+
pub fn as_bytes(&self) -> &[u8] {
98+
&self.0
99+
}
100+
pub(crate) fn from(bytes: Bytes) -> Self {
101+
Self(bytes)
102+
}
103+
}
104+
105+
/// Raw response headers as sent over the TLS or TCP connection.
106+
#[cfg(all(any(feature = "client", feature = "server"), feature = "http1"))]
107+
#[derive(Clone, Debug)]
108+
pub struct RawResponseHeaders(Bytes);
109+
110+
#[cfg(all(any(feature = "client", feature = "server"), feature = "http1"))]
111+
impl RawResponseHeaders {
112+
/// Returns the raw bytes received for the header of the response.
113+
pub fn as_bytes(&self) -> &[u8] {
114+
&self.0
115+
}
116+
#[cfg(feature = "client")]
117+
pub(crate) fn from(bytes: Bytes) -> Self {
118+
Self(bytes)
119+
}
120+
}
121+
89122
/// A map from header names to their original casing as received in an HTTP message.
90123
///
91124
/// If an HTTP/1 response `res` is parsed on a connection whose option

src/proto/h1/conn.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,9 @@ where
6868
date_header: true,
6969
#[cfg(feature = "server")]
7070
timer: Time::Empty,
71+
raw_request_headers: None,
72+
record_raw_request_headers: false,
73+
record_raw_response_headers: false,
7174
preserve_header_case: false,
7275
#[cfg(feature = "ffi")]
7376
preserve_header_order: false,
@@ -123,6 +126,16 @@ where
123126
self.state.title_case_headers = true;
124127
}
125128

129+
#[cfg(feature = "client")]
130+
pub(crate) fn set_record_raw_request_headers(&mut self) {
131+
self.state.record_raw_request_headers = true;
132+
}
133+
134+
#[cfg(feature = "client")]
135+
pub(crate) fn set_record_raw_response_headers(&mut self) {
136+
self.state.record_raw_response_headers = true;
137+
}
138+
126139
pub(crate) fn set_preserve_header_case(&mut self) {
127140
self.state.preserve_header_case = true;
128141
}
@@ -241,6 +254,8 @@ where
241254
req_method: &mut self.state.method,
242255
h1_parser_config: self.state.h1_parser_config.clone(),
243256
h1_max_headers: self.state.h1_max_headers,
257+
raw_request_headers: self.state.raw_request_headers.as_ref(),
258+
record_raw_headers: self.state.record_raw_response_headers,
244259
preserve_header_case: self.state.preserve_header_case,
245260
#[cfg(feature = "ffi")]
246261
preserve_header_order: self.state.preserve_header_order,
@@ -617,6 +632,7 @@ where
617632
self.enforce_version(&mut head);
618633

619634
let buf = self.io.headers_buf();
635+
let headers_start = buf.len();
620636
match super::role::encode_headers::<T>(
621637
Encode {
622638
head: &mut head,
@@ -633,6 +649,13 @@ where
633649
Ok(encoder) => {
634650
debug_assert!(self.state.cached_headers.is_none());
635651
debug_assert!(head.headers.is_empty());
652+
if self.state.record_raw_request_headers {
653+
self.state.raw_request_headers = Some(crate::ext::RawRequestHeaders::from(
654+
Bytes::copy_from_slice(&buf[headers_start..]),
655+
));
656+
} else {
657+
self.state.raw_request_headers = None;
658+
}
636659
self.state.cached_headers = Some(head.headers);
637660

638661
#[cfg(feature = "client")]
@@ -934,6 +957,9 @@ struct State {
934957
date_header: bool,
935958
#[cfg(feature = "server")]
936959
timer: Time,
960+
raw_request_headers: Option<crate::ext::RawRequestHeaders>,
961+
record_raw_request_headers: bool,
962+
record_raw_response_headers: bool,
937963
preserve_header_case: bool,
938964
#[cfg(feature = "ffi")]
939965
preserve_header_order: bool,

src/proto/h1/io.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,8 @@ where
184184
req_method: parse_ctx.req_method,
185185
h1_parser_config: parse_ctx.h1_parser_config.clone(),
186186
h1_max_headers: parse_ctx.h1_max_headers,
187+
raw_request_headers: parse_ctx.raw_request_headers,
188+
record_raw_headers: parse_ctx.record_raw_headers,
187189
preserve_header_case: parse_ctx.preserve_header_case,
188190
#[cfg(feature = "ffi")]
189191
preserve_header_order: parse_ctx.preserve_header_order,
@@ -706,6 +708,8 @@ mod tests {
706708
req_method: &mut None,
707709
h1_parser_config: Default::default(),
708710
h1_max_headers: None,
711+
raw_request_headers: None,
712+
record_raw_headers: false,
709713
preserve_header_case: false,
710714
#[cfg(feature = "ffi")]
711715
preserve_header_order: false,

src/proto/h1/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@ pub(crate) struct ParseContext<'a> {
7373
req_method: &'a mut Option<Method>,
7474
h1_parser_config: ParserConfig,
7575
h1_max_headers: Option<usize>,
76+
record_raw_headers: bool,
77+
raw_request_headers: Option<&'a crate::ext::RawRequestHeaders>,
7678
preserve_header_case: bool,
7779
#[cfg(feature = "ffi")]
7880
preserve_header_order: bool,

0 commit comments

Comments
 (0)