Skip to content

Commit 721785e

Browse files
authored
feat(server): add Builder::auto_date_header(bool) to allow disabling Date headers
The default is enabled `true`. The method exists on both HTTP/1 and HTTP/2 builders.
1 parent a3269f7 commit 721785e

File tree

7 files changed

+136
-6
lines changed

7 files changed

+136
-6
lines changed

src/proto/h1/conn.rs

+6
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ where
6262
#[cfg(feature = "server")]
6363
h1_header_read_timeout_running: false,
6464
#[cfg(feature = "server")]
65+
date_header: true,
66+
#[cfg(feature = "server")]
6567
timer: Time::Empty,
6668
preserve_header_case: false,
6769
#[cfg(feature = "ffi")]
@@ -564,6 +566,8 @@ where
564566
keep_alive: self.state.wants_keep_alive(),
565567
req_method: &mut self.state.method,
566568
title_case_headers: self.state.title_case_headers,
569+
#[cfg(feature = "server")]
570+
date_header: self.state.date_header,
567571
},
568572
buf,
569573
) {
@@ -859,6 +863,8 @@ struct State {
859863
#[cfg(feature = "server")]
860864
h1_header_read_timeout_running: bool,
861865
#[cfg(feature = "server")]
866+
date_header: bool,
867+
#[cfg(feature = "server")]
862868
timer: Time,
863869
preserve_header_case: bool,
864870
#[cfg(feature = "ffi")]

src/proto/h1/mod.rs

+2
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,8 @@ pub(crate) struct Encode<'a, T> {
103103
keep_alive: bool,
104104
req_method: &'a mut Option<Method>,
105105
title_case_headers: bool,
106+
#[cfg(feature = "server")]
107+
date_header: bool,
106108
}
107109

108110
/// Extra flags that a request "wants", like expect-continue or upgrades.

src/proto/h1/role.rs

+47-1
Original file line numberDiff line numberDiff line change
@@ -934,7 +934,8 @@ impl Server {
934934
}
935935

936936
// cached date is much faster than formatting every request
937-
if !wrote_date {
937+
// don't force the write if disabled
938+
if !wrote_date && msg.date_header {
938939
dst.reserve(date::DATE_VALUE_LENGTH + 8);
939940
header_name_writer.write_header_name_with_colon(dst, "date: ", header::DATE);
940941
date::extend(dst);
@@ -2503,6 +2504,7 @@ mod tests {
25032504
keep_alive: true,
25042505
req_method: &mut None,
25052506
title_case_headers: true,
2507+
date_header: true,
25062508
},
25072509
&mut vec,
25082510
)
@@ -2534,6 +2536,7 @@ mod tests {
25342536
keep_alive: true,
25352537
req_method: &mut None,
25362538
title_case_headers: false,
2539+
date_header: true,
25372540
},
25382541
&mut vec,
25392542
)
@@ -2568,6 +2571,7 @@ mod tests {
25682571
keep_alive: true,
25692572
req_method: &mut None,
25702573
title_case_headers: true,
2574+
date_header: true,
25712575
},
25722576
&mut vec,
25732577
)
@@ -2592,6 +2596,7 @@ mod tests {
25922596
keep_alive: true,
25932597
req_method: &mut Some(Method::CONNECT),
25942598
title_case_headers: false,
2599+
date_header: true,
25952600
},
25962601
&mut vec,
25972602
)
@@ -2621,6 +2626,7 @@ mod tests {
26212626
keep_alive: true,
26222627
req_method: &mut None,
26232628
title_case_headers: true,
2629+
date_header: true,
26242630
},
26252631
&mut vec,
26262632
)
@@ -2655,6 +2661,7 @@ mod tests {
26552661
keep_alive: true,
26562662
req_method: &mut None,
26572663
title_case_headers: false,
2664+
date_header: true,
26582665
},
26592666
&mut vec,
26602667
)
@@ -2689,17 +2696,54 @@ mod tests {
26892696
keep_alive: true,
26902697
req_method: &mut None,
26912698
title_case_headers: true,
2699+
date_header: true,
26922700
},
26932701
&mut vec,
26942702
)
26952703
.unwrap();
26962704

2705+
// this will also test that the date does exist
26972706
let expected_response =
26982707
b"HTTP/1.1 200 OK\r\nCONTENT-LENGTH: 10\r\nContent-Type: application/json\r\nDate: ";
26992708

27002709
assert_eq!(&vec[..expected_response.len()], &expected_response[..]);
27012710
}
27022711

2712+
#[test]
2713+
fn test_disabled_date_header() {
2714+
use crate::proto::BodyLength;
2715+
use http::header::{HeaderValue, CONTENT_LENGTH};
2716+
2717+
let mut head = MessageHead::default();
2718+
head.headers
2719+
.insert("content-length", HeaderValue::from_static("10"));
2720+
head.headers
2721+
.insert("content-type", HeaderValue::from_static("application/json"));
2722+
2723+
let mut orig_headers = HeaderCaseMap::default();
2724+
orig_headers.insert(CONTENT_LENGTH, "CONTENT-LENGTH".into());
2725+
head.extensions.insert(orig_headers);
2726+
2727+
let mut vec = Vec::new();
2728+
Server::encode(
2729+
Encode {
2730+
head: &mut head,
2731+
body: Some(BodyLength::Known(10)),
2732+
keep_alive: true,
2733+
req_method: &mut None,
2734+
title_case_headers: true,
2735+
date_header: false,
2736+
},
2737+
&mut vec,
2738+
)
2739+
.unwrap();
2740+
2741+
let expected_response =
2742+
b"HTTP/1.1 200 OK\r\nCONTENT-LENGTH: 10\r\nContent-Type: application/json\r\n\r\n";
2743+
2744+
assert_eq!(&vec, &expected_response);
2745+
}
2746+
27032747
#[test]
27042748
fn parse_header_htabs() {
27052749
let mut bytes = BytesMut::from("HTTP/1.1 200 OK\r\nserver: hello\tworld\r\n\r\n");
@@ -3037,6 +3081,7 @@ mod tests {
30373081
keep_alive: true,
30383082
req_method: &mut Some(Method::GET),
30393083
title_case_headers: false,
3084+
date_header: true,
30403085
},
30413086
&mut vec,
30423087
)
@@ -3065,6 +3110,7 @@ mod tests {
30653110
keep_alive: true,
30663111
req_method: &mut Some(Method::GET),
30673112
title_case_headers: false,
3113+
date_header: true,
30683114
},
30693115
&mut vec,
30703116
)

src/proto/h2/server.rs

+22-5
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ pub(crate) struct Config {
5555
pub(crate) keep_alive_timeout: Duration,
5656
pub(crate) max_send_buffer_size: usize,
5757
pub(crate) max_header_list_size: u32,
58+
pub(crate) date_header: bool,
5859
}
5960

6061
impl Default for Config {
@@ -72,6 +73,7 @@ impl Default for Config {
7273
keep_alive_timeout: Duration::from_secs(20),
7374
max_send_buffer_size: DEFAULT_MAX_SEND_BUF_SIZE,
7475
max_header_list_size: DEFAULT_SETTINGS_MAX_HEADER_LIST_SIZE,
76+
date_header: true,
7577
}
7678
}
7779
}
@@ -86,6 +88,7 @@ pin_project! {
8688
timer: Time,
8789
service: S,
8890
state: State<T, B>,
91+
date_header: bool,
8992
}
9093
}
9194

@@ -108,6 +111,7 @@ where
108111
ping: Option<(ping::Recorder, ping::Ponger)>,
109112
conn: Connection<Compat<T>, SendBuf<B::Data>>,
110113
closing: Option<crate::Error>,
114+
date_header: bool,
111115
}
112116

113117
impl<T, S, B, E> Server<T, S, B, E>
@@ -167,6 +171,7 @@ where
167171
hs: handshake,
168172
},
169173
service,
174+
date_header: config.date_header,
170175
}
171176
}
172177

@@ -219,6 +224,7 @@ where
219224
ping,
220225
conn,
221226
closing: None,
227+
date_header: me.date_header,
222228
})
223229
}
224230
State::Serving(ref mut srv) => {
@@ -302,7 +308,13 @@ where
302308
req.extensions_mut().insert(Protocol::from_inner(protocol));
303309
}
304310

305-
let fut = H2Stream::new(service.call(req), connect_parts, respond);
311+
let fut = H2Stream::new(
312+
service.call(req),
313+
connect_parts,
314+
respond,
315+
self.date_header,
316+
);
317+
306318
exec.execute_h2stream(fut);
307319
}
308320
Some(Err(e)) => {
@@ -357,6 +369,7 @@ pin_project! {
357369
reply: SendResponse<SendBuf<B::Data>>,
358370
#[pin]
359371
state: H2StreamState<F, B>,
372+
date_header: bool,
360373
}
361374
}
362375

@@ -392,10 +405,12 @@ where
392405
fut: F,
393406
connect_parts: Option<ConnectParts>,
394407
respond: SendResponse<SendBuf<B::Data>>,
408+
date_header: bool,
395409
) -> H2Stream<F, B> {
396410
H2Stream {
397411
reply: respond,
398412
state: H2StreamState::Service { fut, connect_parts },
413+
date_header,
399414
}
400415
}
401416
}
@@ -454,10 +469,12 @@ where
454469
let mut res = ::http::Response::from_parts(head, ());
455470
super::strip_connection_headers(res.headers_mut(), false);
456471

457-
// set Date header if it isn't already set...
458-
res.headers_mut()
459-
.entry(::http::header::DATE)
460-
.or_insert_with(date::update_and_header_value);
472+
// set Date header if it isn't already set if instructed
473+
if *me.date_header {
474+
res.headers_mut()
475+
.entry(::http::header::DATE)
476+
.or_insert_with(date::update_and_header_value);
477+
}
461478

462479
if let Some(connect_parts) = connect_parts.take() {
463480
if res.status().is_success() {

src/server/conn/http1.rs

+12
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ pub struct Builder {
7979
h1_writev: Option<bool>,
8080
max_buf_size: Option<usize>,
8181
pipeline_flush: bool,
82+
date_header: bool,
8283
}
8384

8485
/// Deconstructed parts of a `Connection`.
@@ -246,6 +247,7 @@ impl Builder {
246247
h1_writev: None,
247248
max_buf_size: None,
248249
pipeline_flush: false,
250+
date_header: true,
249251
}
250252
}
251253
/// Set whether HTTP/1 connections should support half-closures.
@@ -359,6 +361,16 @@ impl Builder {
359361
self
360362
}
361363

364+
/// Set whether the `date` header should be included in HTTP responses.
365+
///
366+
/// Note that including the `date` header is recommended by RFC 7231.
367+
///
368+
/// Default is true.
369+
pub fn auto_date_header(&mut self, enabled: bool) -> &mut Self {
370+
self.date_header = enabled;
371+
self
372+
}
373+
362374
/// Aggregates flushes to better support pipelined responses.
363375
///
364376
/// Experimental, may have bugs.

src/server/conn/http2.rs

+10
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,16 @@ impl<E> Builder<E> {
277277
self
278278
}
279279

280+
/// Set whether the `date` header should be included in HTTP responses.
281+
///
282+
/// Note that including the `date` header is recommended by RFC 7231.
283+
///
284+
/// Default is true.
285+
pub fn auto_date_header(&mut self, enabled: bool) -> &mut Self {
286+
self.h2_builder.date_header = enabled;
287+
self
288+
}
289+
280290
/// Bind a connection together with a [`Service`](crate::service::Service).
281291
///
282292
/// This returns a Future that must be polled in order for HTTP to be

tests/server.rs

+37
Original file line numberDiff line numberDiff line change
@@ -2529,6 +2529,7 @@ async fn http2_keep_alive_detects_unresponsive_client() {
25292529
.timer(TokioTimer)
25302530
.keep_alive_interval(Duration::from_secs(1))
25312531
.keep_alive_timeout(Duration::from_secs(1))
2532+
.auto_date_header(true)
25322533
.serve_connection(socket, unreachable_service())
25332534
.await
25342535
.expect_err("serve_connection should error");
@@ -2569,6 +2570,42 @@ async fn http2_keep_alive_with_responsive_client() {
25692570
client.send_request(req).await.expect("client.send_request");
25702571
}
25712572

2573+
#[tokio::test]
2574+
async fn http2_check_date_header_disabled() {
2575+
let (listener, addr) = setup_tcp_listener();
2576+
2577+
tokio::spawn(async move {
2578+
let (socket, _) = listener.accept().await.expect("accept");
2579+
let socket = TokioIo::new(socket);
2580+
2581+
http2::Builder::new(TokioExecutor)
2582+
.timer(TokioTimer)
2583+
.keep_alive_interval(Duration::from_secs(1))
2584+
.auto_date_header(false)
2585+
.keep_alive_timeout(Duration::from_secs(1))
2586+
.serve_connection(socket, HelloWorld)
2587+
.await
2588+
.expect("serve_connection");
2589+
});
2590+
2591+
let tcp = TokioIo::new(connect_async(addr).await);
2592+
let (mut client, conn) = hyper::client::conn::http2::Builder::new(TokioExecutor)
2593+
.handshake(tcp)
2594+
.await
2595+
.expect("http handshake");
2596+
2597+
tokio::spawn(async move {
2598+
conn.await.expect("client conn");
2599+
});
2600+
2601+
TokioTimer.sleep(Duration::from_secs(4)).await;
2602+
2603+
let req = http::Request::new(Empty::<Bytes>::new());
2604+
let resp = client.send_request(req).await.expect("client.send_request");
2605+
2606+
assert!(resp.headers().get("Date").is_none());
2607+
}
2608+
25722609
fn is_ping_frame(buf: &[u8]) -> bool {
25732610
buf[3] == 6
25742611
}

0 commit comments

Comments
 (0)