Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit b762cf0

Browse files
committedNov 16, 2023
Introduce hyper::ext::Http1RawMessage
1 parent 4899703 commit b762cf0

File tree

14 files changed

+195
-145
lines changed

14 files changed

+195
-145
lines changed
 

‎capi/include/hyper.h

Lines changed: 0 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -391,17 +391,6 @@ void hyper_clientconn_options_exec(struct hyper_clientconn_options *opts,
391391
*/
392392
enum hyper_code hyper_clientconn_options_http2(struct hyper_clientconn_options *opts, int enabled);
393393

394-
/*
395-
Set the whether to include a copy of the raw headers in responses
396-
received on this connection.
397-
398-
Pass `0` to disable, `1` to enable.
399-
400-
If enabled, see `hyper_response_headers_raw()` for usage.
401-
*/
402-
enum hyper_code hyper_clientconn_options_headers_raw(struct hyper_clientconn_options *opts,
403-
int enabled);
404-
405394
/*
406395
Frees a `hyper_error`.
407396
*/
@@ -557,21 +546,6 @@ const uint8_t *hyper_response_reason_phrase(const struct hyper_response *resp);
557546
*/
558547
size_t hyper_response_reason_phrase_len(const struct hyper_response *resp);
559548

560-
/*
561-
Get a reference to the full raw headers of this response.
562-
563-
You must have enabled `hyper_clientconn_options_headers_raw()`, or this
564-
will return NULL.
565-
566-
The returned `hyper_buf *` is just a reference, owned by the response.
567-
You need to make a copy if you wish to use it after freeing the
568-
response.
569-
570-
The buffer is not null-terminated, see the `hyper_buf` functions for
571-
getting the bytes and length.
572-
*/
573-
const struct hyper_buf *hyper_response_headers_raw(const struct hyper_response *resp);
574-
575549
/*
576550
Get the HTTP version used by this response.
577551

‎src/client/conn.rs

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -192,8 +192,7 @@ pub struct Builder {
192192
h1_preserve_header_order: bool,
193193
h1_read_buf_exact_size: Option<usize>,
194194
h1_max_buf_size: Option<usize>,
195-
#[cfg(feature = "ffi")]
196-
h1_headers_raw: bool,
195+
h1_raw_message: bool,
197196
#[cfg(feature = "http2")]
198197
h2_builder: proto::h2::client::Config,
199198
version: Proto,
@@ -603,8 +602,7 @@ impl Builder {
603602
#[cfg(feature = "ffi")]
604603
h1_preserve_header_order: false,
605604
h1_max_buf_size: None,
606-
#[cfg(feature = "ffi")]
607-
h1_headers_raw: false,
605+
h1_raw_message: false,
608606
#[cfg(feature = "http2")]
609607
h2_builder: Default::default(),
610608
#[cfg(feature = "http1")]
@@ -811,9 +809,16 @@ impl Builder {
811809
self
812810
}
813811

814-
#[cfg(feature = "ffi")]
815-
pub(crate) fn http1_headers_raw(&mut self, enabled: bool) -> &mut Self {
816-
self.h1_headers_raw = enabled;
812+
/// Set whether to include the raw bytes of HTTP/1 responses.
813+
///
814+
/// This will store a [`Http1RawMessage`](crate::ext::Http1RawMessage)
815+
/// in extensions of HTTP/1 responses.
816+
///
817+
/// Note that this setting does not affect HTTP/2.
818+
///
819+
/// Default is false.
820+
pub fn http1_raw_message(&mut self, enabled: bool) -> &mut Self {
821+
self.h1_raw_message = enabled;
817822
self
818823
}
819824

@@ -1033,8 +1038,9 @@ impl Builder {
10331038
conn.set_h09_responses();
10341039
}
10351040

1036-
#[cfg(feature = "ffi")]
1037-
conn.set_raw_headers(opts.h1_headers_raw);
1041+
if opts.h1_raw_message {
1042+
conn.set_h1_raw_message();
1043+
}
10381044

10391045
if let Some(sz) = opts.h1_read_buf_exact_size {
10401046
conn.set_read_buf_exact_size(sz);

‎src/ext.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use http::HeaderMap;
1010
use std::collections::HashMap;
1111
#[cfg(feature = "http2")]
1212
use std::fmt;
13+
use std::ops::Deref;
1314

1415
#[cfg(any(feature = "http1", feature = "ffi"))]
1516
mod h1_reason_phrase;
@@ -131,6 +132,41 @@ impl HeaderCaseMap {
131132
}
132133
}
133134

135+
/// Raw bytes of HTTP/1 requests and responses.
136+
///
137+
/// Included in HTTP/1 requests and responses when `http1_raw_message` is set
138+
/// to true.
139+
#[derive(Clone, Debug, PartialEq, PartialOrd, Hash)]
140+
pub struct Http1RawMessage {
141+
pub(crate) buf: Bytes,
142+
}
143+
144+
impl Deref for Http1RawMessage {
145+
type Target = [u8];
146+
147+
fn deref(&self) -> &Self::Target {
148+
&self.buf
149+
}
150+
}
151+
152+
impl AsRef<Bytes> for Http1RawMessage {
153+
fn as_ref(&self) -> &Bytes {
154+
&self.buf
155+
}
156+
}
157+
158+
impl AsRef<[u8]> for Http1RawMessage {
159+
fn as_ref(&self) -> &[u8] {
160+
&self
161+
}
162+
}
163+
164+
impl From<Http1RawMessage> for Bytes {
165+
fn from(message: Http1RawMessage) -> Self {
166+
message.buf
167+
}
168+
}
169+
134170
#[cfg(feature = "ffi")]
135171
#[derive(Clone, Debug)]
136172
/// Hashmap<Headername, numheaders with that name>

‎src/ffi/client.rs

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -166,17 +166,3 @@ ffi_fn! {
166166
}
167167
}
168168
}
169-
170-
ffi_fn! {
171-
/// Set the whether to include a copy of the raw headers in responses
172-
/// received on this connection.
173-
///
174-
/// Pass `0` to disable, `1` to enable.
175-
///
176-
/// If enabled, see `hyper_response_headers_raw()` for usage.
177-
fn hyper_clientconn_options_headers_raw(opts: *mut hyper_clientconn_options, enabled: c_int) -> hyper_code {
178-
let opts = non_null! { &mut *opts ?= hyper_code::HYPERE_INVALID_ARG };
179-
opts.builder.http1_headers_raw(enabled != 0);
180-
hyper_code::HYPERE_OK
181-
}
182-
}

‎src/ffi/http_types.rs

Lines changed: 1 addition & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use bytes::Bytes;
22
use libc::{c_int, size_t};
33
use std::ffi::c_void;
44

5-
use super::body::{hyper_body, hyper_buf};
5+
use super::body::hyper_body;
66
use super::error::hyper_code;
77
use super::task::{hyper_task_return_type, AsTaskType};
88
use super::{UserDataPointer, HYPER_ITER_CONTINUE};
@@ -25,8 +25,6 @@ pub struct hyper_headers {
2525
orig_order: OriginalHeaderOrder,
2626
}
2727

28-
pub(crate) struct RawHeaders(pub(crate) hyper_buf);
29-
3028
pub(crate) struct OnInformational {
3129
func: hyper_request_on_informational_callback,
3230
data: UserDataPointer,
@@ -278,27 +276,6 @@ ffi_fn! {
278276
}
279277
}
280278

281-
ffi_fn! {
282-
/// Get a reference to the full raw headers of this response.
283-
///
284-
/// You must have enabled `hyper_clientconn_options_headers_raw()`, or this
285-
/// will return NULL.
286-
///
287-
/// The returned `hyper_buf *` is just a reference, owned by the response.
288-
/// You need to make a copy if you wish to use it after freeing the
289-
/// response.
290-
///
291-
/// The buffer is not null-terminated, see the `hyper_buf` functions for
292-
/// getting the bytes and length.
293-
fn hyper_response_headers_raw(resp: *const hyper_response) -> *const hyper_buf {
294-
let resp = non_null!(&*resp ?= std::ptr::null());
295-
match resp.0.extensions().get::<RawHeaders>() {
296-
Some(raw) => &raw.0,
297-
None => std::ptr::null(),
298-
}
299-
} ?= std::ptr::null()
300-
}
301-
302279
ffi_fn! {
303280
/// Get the HTTP version used by this response.
304281
///

‎src/proto/h1/conn.rs

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,7 @@ where
6666
h09_responses: false,
6767
#[cfg(feature = "ffi")]
6868
on_informational: None,
69-
#[cfg(feature = "ffi")]
70-
raw_headers: false,
69+
h1_raw_message: false,
7170
notify_read: false,
7271
reading: Reading::Init,
7372
writing: Writing::Init,
@@ -135,9 +134,8 @@ where
135134
self.state.allow_half_close = true;
136135
}
137136

138-
#[cfg(feature = "ffi")]
139-
pub(crate) fn set_raw_headers(&mut self, enabled: bool) {
140-
self.state.raw_headers = enabled;
137+
pub(crate) fn set_h1_raw_message(&mut self) {
138+
self.state.h1_raw_message = true;
141139
}
142140

143141
pub(crate) fn into_inner(self) -> (I, Bytes) {
@@ -210,8 +208,7 @@ where
210208
h09_responses: self.state.h09_responses,
211209
#[cfg(feature = "ffi")]
212210
on_informational: &mut self.state.on_informational,
213-
#[cfg(feature = "ffi")]
214-
raw_headers: self.state.raw_headers,
211+
h1_raw_message: self.state.h1_raw_message,
215212
}
216213
)) {
217214
Ok(msg) => msg,
@@ -838,8 +835,7 @@ struct State {
838835
/// received.
839836
#[cfg(feature = "ffi")]
840837
on_informational: Option<crate::ffi::OnInformational>,
841-
#[cfg(feature = "ffi")]
842-
raw_headers: bool,
838+
h1_raw_message: bool,
843839
/// Set to true when the Dispatcher should poll read operations
844840
/// again. See the `maybe_notify` method for more.
845841
notify_read: bool,

‎src/proto/h1/io.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -200,8 +200,7 @@ where
200200
h09_responses: parse_ctx.h09_responses,
201201
#[cfg(feature = "ffi")]
202202
on_informational: parse_ctx.on_informational,
203-
#[cfg(feature = "ffi")]
204-
raw_headers: parse_ctx.raw_headers,
203+
h1_raw_message: parse_ctx.h1_raw_message,
205204
},
206205
)? {
207206
Some(msg) => {
@@ -745,8 +744,7 @@ mod tests {
745744
h09_responses: false,
746745
#[cfg(feature = "ffi")]
747746
on_informational: &mut None,
748-
#[cfg(feature = "ffi")]
749-
raw_headers: false,
747+
h1_raw_message: false,
750748
};
751749
assert!(buffered
752750
.parse::<ClientTransaction>(cx, parse_ctx)

‎src/proto/h1/mod.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,7 @@ pub(crate) struct ParseContext<'a> {
8888
h09_responses: bool,
8989
#[cfg(feature = "ffi")]
9090
on_informational: &'a mut Option<crate::ffi::OnInformational>,
91-
#[cfg(feature = "ffi")]
92-
raw_headers: bool,
91+
h1_raw_message: bool,
9392
}
9493

9594
/// Passed to Http1Transaction::encode

‎src/proto/h1/role.rs

Lines changed: 42 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@ use crate::body::DecodedLength;
1515
#[cfg(feature = "server")]
1616
use crate::common::date;
1717
use crate::error::Parse;
18-
use crate::ext::HeaderCaseMap;
1918
#[cfg(feature = "ffi")]
2019
use crate::ext::OriginalHeaderOrder;
20+
use crate::ext::{HeaderCaseMap, Http1RawMessage};
2121
use crate::headers;
2222
use crate::proto::h1::{
2323
Encode, Encoder, Http1Transaction, ParseContext, ParseResult, ParsedMessage,
@@ -193,6 +193,12 @@ impl Http1Transaction for Server {
193193

194194
let slice = buf.split_to(len).freeze();
195195

196+
let mut extensions = http::Extensions::default();
197+
198+
if ctx.h1_raw_message {
199+
extensions.insert(Http1RawMessage { buf: slice.clone() });
200+
}
201+
196202
// According to https://tools.ietf.org/html/rfc7230#section-3.3.3
197203
// 1. (irrelevant to Request)
198204
// 2. (irrelevant to Request)
@@ -311,8 +317,6 @@ impl Http1Transaction for Server {
311317
return Err(Parse::transfer_encoding_invalid());
312318
}
313319

314-
let mut extensions = http::Extensions::default();
315-
316320
if let Some(header_case_map) = header_case_map {
317321
extensions.insert(header_case_map);
318322
}
@@ -986,10 +990,18 @@ impl Http1Transaction for Client {
986990

987991
let mut slice = buf.split_to(len);
988992

989-
if ctx
993+
let mut extensions = http::Extensions::default();
994+
995+
let slice = if ctx
990996
.h1_parser_config
991997
.obsolete_multiline_headers_in_responses_are_allowed()
992998
{
999+
if ctx.h1_raw_message {
1000+
extensions.insert(Http1RawMessage {
1001+
buf: slice.clone().freeze(),
1002+
});
1003+
}
1004+
9931005
for header in &headers_indices[..headers_len] {
9941006
// SAFETY: array is valid up to `headers_len`
9951007
let header = unsafe { &*header.as_ptr() };
@@ -999,9 +1011,17 @@ impl Http1Transaction for Client {
9991011
}
10001012
}
10011013
}
1002-
}
10031014

1004-
let slice = slice.freeze();
1015+
slice.freeze()
1016+
} else {
1017+
let slice = slice.freeze();
1018+
1019+
if ctx.h1_raw_message {
1020+
extensions.insert(Http1RawMessage { buf: slice.clone() });
1021+
}
1022+
1023+
slice
1024+
};
10051025

10061026
let mut headers = ctx.cached_headers.take().unwrap_or_else(HeaderMap::new);
10071027

@@ -1050,8 +1070,6 @@ impl Http1Transaction for Client {
10501070
headers.append(name, value);
10511071
}
10521072

1053-
let mut extensions = http::Extensions::default();
1054-
10551073
if let Some(header_case_map) = header_case_map {
10561074
extensions.insert(header_case_map);
10571075
}
@@ -1068,11 +1086,6 @@ impl Http1Transaction for Client {
10681086
extensions.insert(reason);
10691087
}
10701088

1071-
#[cfg(feature = "ffi")]
1072-
if ctx.raw_headers {
1073-
extensions.insert(crate::ffi::RawHeaders(crate::ffi::hyper_buf(slice)));
1074-
}
1075-
10761089
let head = MessageHead {
10771090
version,
10781091
subject: status,
@@ -1535,8 +1548,7 @@ mod tests {
15351548
h09_responses: false,
15361549
#[cfg(feature = "ffi")]
15371550
on_informational: &mut None,
1538-
#[cfg(feature = "ffi")]
1539-
raw_headers: false,
1551+
h1_raw_message: false,
15401552
},
15411553
)
15421554
.unwrap()
@@ -1570,8 +1582,7 @@ mod tests {
15701582
h09_responses: false,
15711583
#[cfg(feature = "ffi")]
15721584
on_informational: &mut None,
1573-
#[cfg(feature = "ffi")]
1574-
raw_headers: false,
1585+
h1_raw_message: false,
15751586
};
15761587
let msg = Client::parse(&mut raw, ctx).unwrap().unwrap();
15771588
assert_eq!(raw.len(), 0);
@@ -1600,8 +1611,7 @@ mod tests {
16001611
h09_responses: false,
16011612
#[cfg(feature = "ffi")]
16021613
on_informational: &mut None,
1603-
#[cfg(feature = "ffi")]
1604-
raw_headers: false,
1614+
h1_raw_message: false,
16051615
};
16061616
Server::parse(&mut raw, ctx).unwrap_err();
16071617
}
@@ -1628,8 +1638,7 @@ mod tests {
16281638
h09_responses: true,
16291639
#[cfg(feature = "ffi")]
16301640
on_informational: &mut None,
1631-
#[cfg(feature = "ffi")]
1632-
raw_headers: false,
1641+
h1_raw_message: false,
16331642
};
16341643
let msg = Client::parse(&mut raw, ctx).unwrap().unwrap();
16351644
assert_eq!(raw, H09_RESPONSE);
@@ -1658,8 +1667,7 @@ mod tests {
16581667
h09_responses: false,
16591668
#[cfg(feature = "ffi")]
16601669
on_informational: &mut None,
1661-
#[cfg(feature = "ffi")]
1662-
raw_headers: false,
1670+
h1_raw_message: false,
16631671
};
16641672
Client::parse(&mut raw, ctx).unwrap_err();
16651673
assert_eq!(raw, H09_RESPONSE);
@@ -1692,8 +1700,7 @@ mod tests {
16921700
h09_responses: false,
16931701
#[cfg(feature = "ffi")]
16941702
on_informational: &mut None,
1695-
#[cfg(feature = "ffi")]
1696-
raw_headers: false,
1703+
h1_raw_message: false,
16971704
};
16981705
let msg = Client::parse(&mut raw, ctx).unwrap().unwrap();
16991706
assert_eq!(raw.len(), 0);
@@ -1723,8 +1730,7 @@ mod tests {
17231730
h09_responses: false,
17241731
#[cfg(feature = "ffi")]
17251732
on_informational: &mut None,
1726-
#[cfg(feature = "ffi")]
1727-
raw_headers: false,
1733+
h1_raw_message: false,
17281734
};
17291735
Client::parse(&mut raw, ctx).unwrap_err();
17301736
}
@@ -1749,8 +1755,7 @@ mod tests {
17491755
h09_responses: false,
17501756
#[cfg(feature = "ffi")]
17511757
on_informational: &mut None,
1752-
#[cfg(feature = "ffi")]
1753-
raw_headers: false,
1758+
h1_raw_message: false,
17541759
};
17551760
let parsed_message = Server::parse(&mut raw, ctx).unwrap().unwrap();
17561761
let orig_headers = parsed_message
@@ -1796,8 +1801,7 @@ mod tests {
17961801
h09_responses: false,
17971802
#[cfg(feature = "ffi")]
17981803
on_informational: &mut None,
1799-
#[cfg(feature = "ffi")]
1800-
raw_headers: false,
1804+
h1_raw_message: false,
18011805
},
18021806
)
18031807
.expect("parse ok")
@@ -1824,8 +1828,7 @@ mod tests {
18241828
h09_responses: false,
18251829
#[cfg(feature = "ffi")]
18261830
on_informational: &mut None,
1827-
#[cfg(feature = "ffi")]
1828-
raw_headers: false,
1831+
h1_raw_message: false,
18291832
},
18301833
)
18311834
.expect_err(comment)
@@ -2061,8 +2064,7 @@ mod tests {
20612064
h09_responses: false,
20622065
#[cfg(feature = "ffi")]
20632066
on_informational: &mut None,
2064-
#[cfg(feature = "ffi")]
2065-
raw_headers: false,
2067+
h1_raw_message: false,
20662068
}
20672069
)
20682070
.expect("parse ok")
@@ -2089,8 +2091,7 @@ mod tests {
20892091
h09_responses: false,
20902092
#[cfg(feature = "ffi")]
20912093
on_informational: &mut None,
2092-
#[cfg(feature = "ffi")]
2093-
raw_headers: false,
2094+
h1_raw_message: false,
20942095
},
20952096
)
20962097
.expect("parse ok")
@@ -2117,8 +2118,7 @@ mod tests {
21172118
h09_responses: false,
21182119
#[cfg(feature = "ffi")]
21192120
on_informational: &mut None,
2120-
#[cfg(feature = "ffi")]
2121-
raw_headers: false,
2121+
h1_raw_message: false,
21222122
},
21232123
)
21242124
.expect_err("parse should err")
@@ -2622,8 +2622,7 @@ mod tests {
26222622
h09_responses: false,
26232623
#[cfg(feature = "ffi")]
26242624
on_informational: &mut None,
2625-
#[cfg(feature = "ffi")]
2626-
raw_headers: false,
2625+
h1_raw_message: false,
26272626
},
26282627
)
26292628
.expect("parse ok")
@@ -2714,8 +2713,7 @@ mod tests {
27142713
h09_responses: false,
27152714
#[cfg(feature = "ffi")]
27162715
on_informational: &mut None,
2717-
#[cfg(feature = "ffi")]
2718-
raw_headers: false,
2716+
h1_raw_message: false,
27192717
},
27202718
)
27212719
.unwrap()
@@ -2762,8 +2760,7 @@ mod tests {
27622760
h09_responses: false,
27632761
#[cfg(feature = "ffi")]
27642762
on_informational: &mut None,
2765-
#[cfg(feature = "ffi")]
2766-
raw_headers: false,
2763+
h1_raw_message: false,
27672764
},
27682765
)
27692766
.unwrap()

‎src/server/conn.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ pub struct Http<E = Exec> {
113113
h1_keep_alive: bool,
114114
h1_title_case_headers: bool,
115115
h1_preserve_header_case: bool,
116+
h1_raw_message: bool,
116117
#[cfg(all(feature = "http1", feature = "runtime"))]
117118
h1_header_read_timeout: Option<Duration>,
118119
h1_writev: Option<bool>,
@@ -259,6 +260,7 @@ impl Http {
259260
h1_keep_alive: true,
260261
h1_title_case_headers: false,
261262
h1_preserve_header_case: false,
263+
h1_raw_message: false,
262264
#[cfg(all(feature = "http1", feature = "runtime"))]
263265
h1_header_read_timeout: None,
264266
h1_writev: None,
@@ -349,6 +351,19 @@ impl<E> Http<E> {
349351
self
350352
}
351353

354+
/// Set whether to include the raw bytes of HTTP/1 requests.
355+
///
356+
/// This will store a [`ext::Http1RawMessage`] in extensions of
357+
/// HTTP/1 requests.
358+
///
359+
/// Default is false.
360+
#[cfg(feature = "http1")]
361+
#[cfg_attr(docsrs, doc(cfg(feature = "http1")))]
362+
pub fn http1_raw_message(&mut self, enabled: bool) -> &mut Self {
363+
self.h1_raw_message = enabled;
364+
self
365+
}
366+
352367
/// Set a timeout for reading client request headers. If a client does not
353368
/// transmit the entire header within this time, the connection is closed.
354369
///
@@ -606,6 +621,7 @@ impl<E> Http<E> {
606621
h1_keep_alive: self.h1_keep_alive,
607622
h1_title_case_headers: self.h1_title_case_headers,
608623
h1_preserve_header_case: self.h1_preserve_header_case,
624+
h1_raw_message: self.h1_raw_message,
609625
#[cfg(all(feature = "http1", feature = "runtime"))]
610626
h1_header_read_timeout: self.h1_header_read_timeout,
611627
h1_writev: self.h1_writev,
@@ -670,6 +686,9 @@ impl<E> Http<E> {
670686
if self.h1_preserve_header_case {
671687
conn.set_preserve_header_case();
672688
}
689+
if self.h1_raw_message {
690+
conn.set_h1_raw_message();
691+
}
673692
#[cfg(all(feature = "http1", feature = "runtime"))]
674693
if let Some(header_read_timeout) = self.h1_header_read_timeout {
675694
conn.set_http1_header_read_timeout(header_read_timeout);

‎src/server/conn/http1.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ pub struct Builder {
4242
h1_keep_alive: bool,
4343
h1_title_case_headers: bool,
4444
h1_preserve_header_case: bool,
45+
h1_raw_message: bool,
4546
h1_header_read_timeout: Option<Duration>,
4647
h1_writev: Option<bool>,
4748
max_buf_size: Option<usize>,
@@ -208,6 +209,7 @@ impl Builder {
208209
h1_keep_alive: true,
209210
h1_title_case_headers: false,
210211
h1_preserve_header_case: false,
212+
h1_raw_message: false,
211213
h1_header_read_timeout: None,
212214
h1_writev: None,
213215
max_buf_size: None,
@@ -260,6 +262,17 @@ impl Builder {
260262
self
261263
}
262264

265+
/// Set whether to include the raw bytes of HTTP/1 requests and responses.
266+
///
267+
/// This will store a [`ext::Http1RawMessage`] in extensions of
268+
/// HTTP/1 requests and responses.
269+
///
270+
/// Default is false.
271+
pub fn raw_message(&mut self, enabled: bool) -> &mut Self {
272+
self.h1_raw_message = enabled;
273+
self
274+
}
275+
263276
/// Set a timeout for reading client request headers. If a client does not
264277
/// transmit the entire header within this time, the connection is closed.
265278
///
@@ -370,6 +383,9 @@ impl Builder {
370383
if self.h1_preserve_header_case {
371384
conn.set_preserve_header_case();
372385
}
386+
if self.h1_raw_message {
387+
conn.set_h1_raw_message();
388+
}
373389
if let Some(header_read_timeout) = self.h1_header_read_timeout {
374390
conn.set_http1_header_read_timeout(header_read_timeout);
375391
}

‎src/server/server.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,19 @@ impl<I, E> Builder<I, E> {
257257
self
258258
}
259259

260+
/// Set whether to include the raw bytes of HTTP/1 requests.
261+
///
262+
/// This will store a [`ext::Http1RawMessage`] in extensions of
263+
/// HTTP/1 requests.
264+
///
265+
/// Default is false.
266+
#[cfg(feature = "http1")]
267+
#[cfg_attr(docsrs, doc(cfg(feature = "http1")))]
268+
pub fn http1_raw_message(mut self, enabled: bool) -> Self {
269+
self.protocol.http1_raw_message(enabled);
270+
self
271+
}
272+
260273
/// Set whether HTTP/1 connections should support half-closures.
261274
///
262275
/// Clients can chose to shutdown their write-side while waiting

‎tests/client.rs

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2200,6 +2200,7 @@ mod conn {
22002200
use tokio::net::{TcpListener as TkTcpListener, TcpStream};
22012201

22022202
use hyper::client::conn;
2203+
use hyper::ext::Http1RawMessage;
22032204
use hyper::{self, Body, Method, Request, Response, StatusCode};
22042205

22052206
use super::{concat, s, support, tcp_connect, FutureHyperExt};
@@ -2381,6 +2382,8 @@ mod conn {
23812382
.unwrap();
23822383
let addr = listener.local_addr().unwrap();
23832384

2385+
const RESPONSE: &[u8] = b"HTTP/1.1 200 OK\r\nContent-Length: \r\n 0\r\nLine-Folded-Header: hello\r\n world \r\n \r\n\r\n";
2386+
23842387
let server = async move {
23852388
let mut sock = listener.accept().await.unwrap().0;
23862389
let mut buf = [0; 4096];
@@ -2392,15 +2395,14 @@ mod conn {
23922395
let expected = "GET /a HTTP/1.1\r\n\r\n";
23932396
assert_eq!(s(&buf[..n]), expected);
23942397

2395-
sock.write_all(b"HTTP/1.1 200 OK\r\nContent-Length: \r\n 0\r\nLine-Folded-Header: hello\r\n world \r\n \r\n\r\n")
2396-
.await
2397-
.unwrap();
2398+
sock.write_all(RESPONSE).await.unwrap();
23982399
};
23992400

24002401
let client = async move {
24012402
let tcp = tcp_connect(&addr).await.expect("connect");
24022403
let (mut client, conn) = conn::Builder::new()
24032404
.http1_allow_obsolete_multiline_headers_in_responses(true)
2405+
.http1_raw_message(true)
24042406
.handshake::<_, Body>(tcp)
24052407
.await
24062408
.expect("handshake");
@@ -2414,16 +2416,25 @@ mod conn {
24142416
.body(Default::default())
24152417
.unwrap();
24162418
let mut res = client.send_request(req).await.expect("send_request");
2419+
24172420
assert_eq!(res.status(), hyper::StatusCode::OK);
24182421
assert_eq!(res.headers().len(), 2);
2422+
24192423
assert_eq!(
24202424
res.headers().get(http::header::CONTENT_LENGTH).unwrap(),
24212425
"0"
24222426
);
2427+
24232428
assert_eq!(
24242429
res.headers().get("line-folded-header").unwrap(),
24252430
"hello world"
24262431
);
2432+
2433+
let raw_response = res.extensions().get::<Http1RawMessage>().unwrap();
2434+
2435+
// The raw response includes the line folded headers prior to \r\n replacement.
2436+
assert_eq!(**raw_response, *RESPONSE);
2437+
24272438
assert!(res.body_mut().next().await.is_none());
24282439
};
24292440

‎tests/server.rs

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ use tokio::net::{TcpListener, TcpStream as TkTcpStream};
2828

2929
use hyper::body::HttpBody as _;
3030
use hyper::client::Client;
31+
use hyper::ext::Http1RawMessage;
3132
use hyper::server::conn::Http;
3233
use hyper::server::Server;
3334
use hyper::service::{make_service_fn, service_fn};
@@ -58,20 +59,21 @@ fn get_should_ignore_body() {
5859

5960
#[test]
6061
fn get_with_body() {
61-
let server = serve();
62-
let mut req = connect(server.addr());
63-
req.write_all(
64-
b"\
62+
const REQUEST_HEAD: &[u8] = b"\
6563
GET / HTTP/1.1\r\n\
6664
Host: example.domain\r\n\
6765
Content-Length: 19\r\n\
6866
\r\n\
69-
I'm a good request.\r\n\
70-
",
71-
)
72-
.unwrap();
67+
";
68+
69+
let server = serve_opts().http1_raw_message().serve();
70+
let mut req = connect(server.addr());
71+
req.write_all(REQUEST_HEAD).unwrap();
72+
req.write_all(b"I'm a good request.\r\n").unwrap();
7373
req.read(&mut [0; 256]).unwrap();
7474

75+
assert_eq!(*server.raw_request(), *REQUEST_HEAD);
76+
7577
// note: doesn't include trailing \r\n, cause Content-Length wasn't 21
7678
assert_eq!(server.body(), b"I'm a good request.");
7779
}
@@ -2815,6 +2817,13 @@ impl Serve {
28152817
&self.addr
28162818
}
28172819

2820+
fn raw_request(&self) -> Http1RawMessage {
2821+
match self.msg_rx.recv() {
2822+
Ok(Msg::RawRequest(raw_request)) => raw_request,
2823+
res => panic!("expected raw request, found: {:?}", res),
2824+
}
2825+
}
2826+
28182827
fn body(&self) -> Vec<u8> {
28192828
self.try_body().expect("body")
28202829
}
@@ -2832,7 +2841,7 @@ impl Serve {
28322841
}
28332842
Ok(Msg::Error(e)) => return Err(e),
28342843
Ok(Msg::End) => break,
2835-
Err(e) => panic!("expected body, found: {:?}", e),
2844+
res => panic!("expected body, found: {:?}", res),
28362845
}
28372846
}
28382847
Ok(buf)
@@ -2950,6 +2959,7 @@ enum Reply {
29502959

29512960
#[derive(Debug)]
29522961
enum Msg {
2962+
RawRequest(Http1RawMessage),
29532963
Chunk(Vec<u8>),
29542964
Error(hyper::Error),
29552965
End,
@@ -2968,6 +2978,10 @@ impl tower_service::Service<Request<Body>> for TestService {
29682978
let tx = self.tx.clone();
29692979
let replies = self.reply.clone();
29702980

2981+
if let Some(raw_request) = req.extensions_mut().remove::<Http1RawMessage>() {
2982+
tx.send(Msg::RawRequest(raw_request)).unwrap();
2983+
}
2984+
29712985
Box::pin(async move {
29722986
while let Some(chunk) = req.data().await {
29732987
match chunk {
@@ -3067,6 +3081,7 @@ fn serve_opts() -> ServeOptions {
30673081
struct ServeOptions {
30683082
keep_alive: bool,
30693083
http1_only: bool,
3084+
http1_raw_message: bool,
30703085
pipeline: bool,
30713086
}
30723087

@@ -3075,6 +3090,7 @@ impl Default for ServeOptions {
30753090
ServeOptions {
30763091
keep_alive: true,
30773092
http1_only: false,
3093+
http1_raw_message: false,
30783094
pipeline: false,
30793095
}
30803096
}
@@ -3086,6 +3102,11 @@ impl ServeOptions {
30863102
self
30873103
}
30883104

3105+
fn http1_raw_message(mut self) -> Self {
3106+
self.http1_raw_message = true;
3107+
self
3108+
}
3109+
30893110
fn keep_alive(mut self, enabled: bool) -> Self {
30903111
self.keep_alive = enabled;
30913112
self
@@ -3133,6 +3154,7 @@ impl ServeOptions {
31333154
let builder = builder
31343155
.http1_only(_options.http1_only)
31353156
.http1_keepalive(_options.keep_alive)
3157+
.http1_raw_message(_options.http1_raw_message)
31363158
.http1_pipeline_flush(_options.pipeline);
31373159

31383160
let server = builder.serve(service);

0 commit comments

Comments
 (0)
Please sign in to comment.