use std::fmt;
use std::time::{
Duration,
SystemTime,
};
use http::header::RETRY_AFTER;
use http::{
HeaderMap,
Method,
StatusCode,
};
use httpdate::parse_http_date;
use url::Url;
use crate::sanitize::SanitizedDebugger;
use crate::LogSanitizePolicy;
#[derive(Clone)]
pub struct HttpResponseMeta {
status: StatusCode,
headers: HeaderMap,
url: Url,
method: Method,
log_sanitize_policy: LogSanitizePolicy,
}
impl HttpResponseMeta {
pub fn new(status: StatusCode, headers: HeaderMap, url: Url, method: Method) -> Self {
Self {
status,
headers,
url,
method,
log_sanitize_policy: LogSanitizePolicy::default(),
}
}
pub fn with_log_sanitize_policy(mut self, policy: LogSanitizePolicy) -> Self {
self.log_sanitize_policy = policy;
self
}
#[inline]
pub fn status(&self) -> StatusCode {
self.status
}
#[inline]
pub fn headers(&self) -> &HeaderMap {
&self.headers
}
#[inline]
pub fn url(&self) -> &Url {
&self.url
}
#[inline]
pub fn method(&self) -> &Method {
&self.method
}
pub fn retry_after_hint(&self) -> Option<Duration> {
Self::retry_after_hint_from_parts(self.status, &self.headers)
}
pub(super) fn retry_after_hint_from_parts(status: StatusCode, headers: &HeaderMap) -> Option<Duration> {
if !is_retry_after_applicable_status(status) {
return None;
}
headers
.get(RETRY_AFTER)
.and_then(|value| value.to_str().ok())
.and_then(parse_retry_after_value)
}
pub(super) fn set_headers(&mut self, headers: HeaderMap) {
self.headers = headers;
}
pub(super) fn set_url(&mut self, url: Url) {
self.url = url;
}
pub(super) fn log_sanitize_policy(&self) -> &LogSanitizePolicy {
&self.log_sanitize_policy
}
pub(super) fn set_log_sanitize_policy(&mut self, policy: LogSanitizePolicy) {
self.log_sanitize_policy = policy;
}
}
impl fmt::Debug for HttpResponseMeta {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
let debugger = SanitizedDebugger::new(&self.log_sanitize_policy);
let url = debugger.url(&self.url);
formatter
.debug_struct("HttpResponseMeta")
.field("status", &self.status)
.field("headers", &debugger.headers(&self.headers))
.field("url", &url)
.field("method", &self.method)
.finish()
}
}
fn is_retry_after_applicable_status(status: StatusCode) -> bool {
status == StatusCode::TOO_MANY_REQUESTS || status.is_server_error()
}
fn parse_retry_after_value(value: &str) -> Option<Duration> {
let trimmed = value.trim();
if trimmed.is_empty() {
return None;
}
if let Ok(seconds) = trimmed.parse::<u64>() {
return Some(Duration::from_secs(seconds));
}
let retry_at = parse_http_date(trimmed).ok()?;
let now = SystemTime::now();
Some(retry_at.duration_since(now).unwrap_or_else(|_| Duration::from_secs(0)))
}