Skip to content

Commit 1037bc7

Browse files
Julius de Bruijnseanmonstar
Julius de Bruijn
authored andcommitted
feat(header): Add support for Retry-After header
This used to be an external crate, https://github.com/jwilm/retry-after
1 parent f45e9c8 commit 1037bc7

File tree

2 files changed

+197
-0
lines changed

2 files changed

+197
-0
lines changed

src/header/common/mod.rs

+2
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ pub use self::preference_applied::PreferenceApplied;
5151
pub use self::range::{Range, ByteRangeSpec};
5252
pub use self::referer::Referer;
5353
pub use self::referrer_policy::ReferrerPolicy;
54+
pub use self::retry_after::RetryAfter;
5455
pub use self::server::Server;
5556
pub use self::set_cookie::SetCookie;
5657
pub use self::strict_transport_security::StrictTransportSecurity;
@@ -384,6 +385,7 @@ mod preference_applied;
384385
mod range;
385386
mod referer;
386387
mod referrer_policy;
388+
mod retry_after;
387389
mod server;
388390
mod set_cookie;
389391
mod strict_transport_security;

src/header/common/retry_after.rs

+195
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
// Copyright (c) 2016 retry-after Developers
2+
//
3+
// This file is dual licensed under MIT and Apache 2.0
4+
//
5+
// *******************************************************
6+
//
7+
// Permission is hereby granted, free of charge, to any
8+
// person obtaining a copy of this software and associated
9+
// documentation files (the "Software"), to deal in the
10+
// Software without restriction, including without
11+
// limitation the rights to use, copy, modify, merge,
12+
// publish, distribute, sublicense, and/or sell copies of
13+
// the Software, and to permit persons to whom the Software
14+
// is furnished to do so, subject to the following
15+
//
16+
// conditions:
17+
//
18+
// The above copyright notice and this permission notice
19+
// shall be included in all copies or substantial portions
20+
// of the Software.
21+
//
22+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
23+
// ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
24+
// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
25+
// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
26+
// SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
27+
// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
28+
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
29+
// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
30+
// DEALINGS IN THE SOFTWARE.
31+
//
32+
// *******************************************************
33+
//
34+
// Apache License
35+
// Version 2.0, January 2004
36+
// http://www.apache.org/licenses/
37+
38+
use header::{Header, Raw};
39+
use header::shared::HttpDate;
40+
use time;
41+
use time::{Duration, Tm};
42+
use std::fmt;
43+
44+
/// The `Retry-After` header.
45+
///
46+
/// The `Retry-After` response-header field can be used with a 503 (Service
47+
/// Unavailable) response to indicate how long the service is expected to be
48+
/// unavailable to the requesting client. This field MAY also be used with any
49+
/// 3xx (Redirection) response to indicate the minimum time the user-agent is
50+
/// asked wait before issuing the redirected request. The value of this field
51+
/// can be either an HTTP-date or an integer number of seconds (in decimal)
52+
/// after the time of the response.
53+
///
54+
/// # Examples
55+
/// ```
56+
/// # extern crate hyper;
57+
/// # extern crate time;
58+
/// # fn main() {
59+
/// // extern crate time;
60+
/// use time::{Duration};
61+
/// use hyper::header::{Headers, RetryAfter};
62+
///
63+
/// let mut headers = Headers::new();
64+
/// headers.set(
65+
/// RetryAfter::Delay(Duration::seconds(300))
66+
/// );
67+
/// # }
68+
/// ```
69+
/// ```
70+
/// # extern crate hyper;
71+
/// # extern crate time;
72+
/// # fn main() {
73+
/// // extern crate time;
74+
/// use time;
75+
/// use time::{Duration};
76+
/// use hyper::header::{Headers, RetryAfter};
77+
///
78+
/// let mut headers = Headers::new();
79+
/// headers.set(
80+
/// RetryAfter::DateTime(time::now_utc() + Duration::seconds(300))
81+
/// );
82+
/// # }
83+
/// ```
84+
85+
/// Retry-After header, defined in [RFC7231](http://tools.ietf.org/html/rfc7231#section-7.1.3)
86+
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
87+
pub enum RetryAfter {
88+
/// Retry after this duration has elapsed
89+
///
90+
/// This can be coupled with a response time header to produce a DateTime.
91+
Delay(Duration),
92+
93+
/// Retry after the given DateTime
94+
DateTime(Tm),
95+
}
96+
97+
impl Header for RetryAfter {
98+
fn header_name() -> &'static str {
99+
static NAME: &'static str = "Retry-After";
100+
NAME
101+
}
102+
103+
fn parse_header(raw: &Raw) -> ::Result<RetryAfter> {
104+
if let Some(ref line) = raw.one() {
105+
let utf8_str = match ::std::str::from_utf8(line) {
106+
Ok(utf8_str) => utf8_str,
107+
Err(_) => return Err(::Error::Header),
108+
};
109+
110+
if let Ok(datetime) = utf8_str.parse::<HttpDate>() {
111+
return Ok(RetryAfter::DateTime(datetime.0))
112+
}
113+
114+
if let Ok(seconds) = utf8_str.parse::<i64>() {
115+
return Ok(RetryAfter::Delay(Duration::seconds(seconds)));
116+
}
117+
118+
Err(::Error::Header)
119+
} else {
120+
Err(::Error::Header)
121+
}
122+
}
123+
124+
fn fmt_header(&self, f: &mut fmt::Formatter) -> fmt::Result {
125+
match *self {
126+
RetryAfter::Delay(ref duration) => {
127+
write!(f, "{}", duration.num_seconds())
128+
},
129+
RetryAfter::DateTime(ref datetime) => {
130+
// According to RFC7231, the sender of an HTTP-date must use the RFC1123 format.
131+
// http://tools.ietf.org/html/rfc7231#section-7.1.1.1
132+
if let Ok(date_string) = time::strftime("%a, %d %b %Y %T GMT", datetime) {
133+
write!(f, "{}", date_string)
134+
} else {
135+
Err(fmt::Error::default())
136+
}
137+
}
138+
}
139+
}
140+
}
141+
142+
#[cfg(test)]
143+
mod tests {
144+
extern crate httparse;
145+
146+
use header::{Header, Headers};
147+
use header::shared::HttpDate;
148+
use time::{Duration};
149+
150+
use super::RetryAfter;
151+
152+
#[test]
153+
fn header_name_regression() {
154+
assert_eq!(RetryAfter::header_name(), "Retry-After");
155+
}
156+
157+
#[test]
158+
fn parse_delay() {
159+
let retry_after = RetryAfter::parse_header(&vec![b"1234".to_vec()].into()).unwrap();
160+
161+
assert_eq!(RetryAfter::Delay(Duration::seconds(1234)), retry_after);
162+
}
163+
164+
macro_rules! test_retry_after_datetime {
165+
($name:ident, $bytes:expr) => {
166+
#[test]
167+
fn $name() {
168+
let dt = "Sun, 06 Nov 1994 08:49:37 GMT".parse::<HttpDate>().unwrap();
169+
let retry_after = RetryAfter::parse_header(&vec![$bytes.to_vec()].into()).expect("parse_header ok");
170+
171+
assert_eq!(RetryAfter::DateTime(dt.0), retry_after);
172+
}
173+
}
174+
}
175+
176+
test_retry_after_datetime!(header_parse_rfc1123, b"Sun, 06 Nov 1994 08:49:37 GMT");
177+
test_retry_after_datetime!(header_parse_rfc850, b"Sunday, 06-Nov-94 08:49:37 GMT");
178+
test_retry_after_datetime!(header_parse_asctime, b"Sun Nov 6 08:49:37 1994");
179+
180+
#[test]
181+
fn hyper_headers_from_raw_delay() {
182+
let headers = Headers::from_raw(&[httparse::Header { name: "Retry-After", value: b"300" }]).unwrap();
183+
let retry_after = headers.get::<RetryAfter>().unwrap();
184+
assert_eq!(retry_after, &RetryAfter::Delay(Duration::seconds(300)));
185+
}
186+
187+
#[test]
188+
fn hyper_headers_from_raw_datetime() {
189+
let headers = Headers::from_raw(&[httparse::Header { name: "Retry-After", value: b"Sun, 06 Nov 1994 08:49:37 GMT" }]).unwrap();
190+
let retry_after = headers.get::<RetryAfter>().unwrap();
191+
let expected = "Sun, 06 Nov 1994 08:49:37 GMT".parse::<HttpDate>().unwrap();
192+
193+
assert_eq!(retry_after, &RetryAfter::DateTime(expected.0));
194+
}
195+
}

0 commit comments

Comments
 (0)