feat(header): Add support for Retry-After header
This used to be an external crate, https://github.com/jwilm/retry-after
This commit is contained in:
committed by
Sean McArthur
parent
f45e9c8e4f
commit
1037bc7732
@@ -51,6 +51,7 @@ pub use self::preference_applied::PreferenceApplied;
|
||||
pub use self::range::{Range, ByteRangeSpec};
|
||||
pub use self::referer::Referer;
|
||||
pub use self::referrer_policy::ReferrerPolicy;
|
||||
pub use self::retry_after::RetryAfter;
|
||||
pub use self::server::Server;
|
||||
pub use self::set_cookie::SetCookie;
|
||||
pub use self::strict_transport_security::StrictTransportSecurity;
|
||||
@@ -384,6 +385,7 @@ mod preference_applied;
|
||||
mod range;
|
||||
mod referer;
|
||||
mod referrer_policy;
|
||||
mod retry_after;
|
||||
mod server;
|
||||
mod set_cookie;
|
||||
mod strict_transport_security;
|
||||
|
||||
195
src/header/common/retry_after.rs
Normal file
195
src/header/common/retry_after.rs
Normal file
@@ -0,0 +1,195 @@
|
||||
// Copyright (c) 2016 retry-after Developers
|
||||
//
|
||||
// This file is dual licensed under MIT and Apache 2.0
|
||||
//
|
||||
// *******************************************************
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any
|
||||
// person obtaining a copy of this software and associated
|
||||
// documentation files (the "Software"), to deal in the
|
||||
// Software without restriction, including without
|
||||
// limitation the rights to use, copy, modify, merge,
|
||||
// publish, distribute, sublicense, and/or sell copies of
|
||||
// the Software, and to permit persons to whom the Software
|
||||
// is furnished to do so, subject to the following
|
||||
//
|
||||
// conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice
|
||||
// shall be included in all copies or substantial portions
|
||||
// of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
|
||||
// ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||
// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||
// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
// SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
||||
// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
// DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
// *******************************************************
|
||||
//
|
||||
// Apache License
|
||||
// Version 2.0, January 2004
|
||||
// http://www.apache.org/licenses/
|
||||
|
||||
use header::{Header, Raw};
|
||||
use header::shared::HttpDate;
|
||||
use time;
|
||||
use time::{Duration, Tm};
|
||||
use std::fmt;
|
||||
|
||||
/// The `Retry-After` header.
|
||||
///
|
||||
/// The `Retry-After` response-header field can be used with a 503 (Service
|
||||
/// Unavailable) response to indicate how long the service is expected to be
|
||||
/// unavailable to the requesting client. This field MAY also be used with any
|
||||
/// 3xx (Redirection) response to indicate the minimum time the user-agent is
|
||||
/// asked wait before issuing the redirected request. The value of this field
|
||||
/// can be either an HTTP-date or an integer number of seconds (in decimal)
|
||||
/// after the time of the response.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// # extern crate hyper;
|
||||
/// # extern crate time;
|
||||
/// # fn main() {
|
||||
/// // extern crate time;
|
||||
/// use time::{Duration};
|
||||
/// use hyper::header::{Headers, RetryAfter};
|
||||
///
|
||||
/// let mut headers = Headers::new();
|
||||
/// headers.set(
|
||||
/// RetryAfter::Delay(Duration::seconds(300))
|
||||
/// );
|
||||
/// # }
|
||||
/// ```
|
||||
/// ```
|
||||
/// # extern crate hyper;
|
||||
/// # extern crate time;
|
||||
/// # fn main() {
|
||||
/// // extern crate time;
|
||||
/// use time;
|
||||
/// use time::{Duration};
|
||||
/// use hyper::header::{Headers, RetryAfter};
|
||||
///
|
||||
/// let mut headers = Headers::new();
|
||||
/// headers.set(
|
||||
/// RetryAfter::DateTime(time::now_utc() + Duration::seconds(300))
|
||||
/// );
|
||||
/// # }
|
||||
/// ```
|
||||
|
||||
/// Retry-After header, defined in [RFC7231](http://tools.ietf.org/html/rfc7231#section-7.1.3)
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
pub enum RetryAfter {
|
||||
/// Retry after this duration has elapsed
|
||||
///
|
||||
/// This can be coupled with a response time header to produce a DateTime.
|
||||
Delay(Duration),
|
||||
|
||||
/// Retry after the given DateTime
|
||||
DateTime(Tm),
|
||||
}
|
||||
|
||||
impl Header for RetryAfter {
|
||||
fn header_name() -> &'static str {
|
||||
static NAME: &'static str = "Retry-After";
|
||||
NAME
|
||||
}
|
||||
|
||||
fn parse_header(raw: &Raw) -> ::Result<RetryAfter> {
|
||||
if let Some(ref line) = raw.one() {
|
||||
let utf8_str = match ::std::str::from_utf8(line) {
|
||||
Ok(utf8_str) => utf8_str,
|
||||
Err(_) => return Err(::Error::Header),
|
||||
};
|
||||
|
||||
if let Ok(datetime) = utf8_str.parse::<HttpDate>() {
|
||||
return Ok(RetryAfter::DateTime(datetime.0))
|
||||
}
|
||||
|
||||
if let Ok(seconds) = utf8_str.parse::<i64>() {
|
||||
return Ok(RetryAfter::Delay(Duration::seconds(seconds)));
|
||||
}
|
||||
|
||||
Err(::Error::Header)
|
||||
} else {
|
||||
Err(::Error::Header)
|
||||
}
|
||||
}
|
||||
|
||||
fn fmt_header(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match *self {
|
||||
RetryAfter::Delay(ref duration) => {
|
||||
write!(f, "{}", duration.num_seconds())
|
||||
},
|
||||
RetryAfter::DateTime(ref datetime) => {
|
||||
// According to RFC7231, the sender of an HTTP-date must use the RFC1123 format.
|
||||
// http://tools.ietf.org/html/rfc7231#section-7.1.1.1
|
||||
if let Ok(date_string) = time::strftime("%a, %d %b %Y %T GMT", datetime) {
|
||||
write!(f, "{}", date_string)
|
||||
} else {
|
||||
Err(fmt::Error::default())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
extern crate httparse;
|
||||
|
||||
use header::{Header, Headers};
|
||||
use header::shared::HttpDate;
|
||||
use time::{Duration};
|
||||
|
||||
use super::RetryAfter;
|
||||
|
||||
#[test]
|
||||
fn header_name_regression() {
|
||||
assert_eq!(RetryAfter::header_name(), "Retry-After");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_delay() {
|
||||
let retry_after = RetryAfter::parse_header(&vec![b"1234".to_vec()].into()).unwrap();
|
||||
|
||||
assert_eq!(RetryAfter::Delay(Duration::seconds(1234)), retry_after);
|
||||
}
|
||||
|
||||
macro_rules! test_retry_after_datetime {
|
||||
($name:ident, $bytes:expr) => {
|
||||
#[test]
|
||||
fn $name() {
|
||||
let dt = "Sun, 06 Nov 1994 08:49:37 GMT".parse::<HttpDate>().unwrap();
|
||||
let retry_after = RetryAfter::parse_header(&vec![$bytes.to_vec()].into()).expect("parse_header ok");
|
||||
|
||||
assert_eq!(RetryAfter::DateTime(dt.0), retry_after);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test_retry_after_datetime!(header_parse_rfc1123, b"Sun, 06 Nov 1994 08:49:37 GMT");
|
||||
test_retry_after_datetime!(header_parse_rfc850, b"Sunday, 06-Nov-94 08:49:37 GMT");
|
||||
test_retry_after_datetime!(header_parse_asctime, b"Sun Nov 6 08:49:37 1994");
|
||||
|
||||
#[test]
|
||||
fn hyper_headers_from_raw_delay() {
|
||||
let headers = Headers::from_raw(&[httparse::Header { name: "Retry-After", value: b"300" }]).unwrap();
|
||||
let retry_after = headers.get::<RetryAfter>().unwrap();
|
||||
assert_eq!(retry_after, &RetryAfter::Delay(Duration::seconds(300)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hyper_headers_from_raw_datetime() {
|
||||
let headers = Headers::from_raw(&[httparse::Header { name: "Retry-After", value: b"Sun, 06 Nov 1994 08:49:37 GMT" }]).unwrap();
|
||||
let retry_after = headers.get::<RetryAfter>().unwrap();
|
||||
let expected = "Sun, 06 Nov 1994 08:49:37 GMT".parse::<HttpDate>().unwrap();
|
||||
|
||||
assert_eq!(retry_after, &RetryAfter::DateTime(expected.0));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user