Skip to content

Commit 7b0a1b3

Browse files
authored
Merge pull request #210 from http-rs/cache-control
Cache control
2 parents d4f85be + 3af9864 commit 7b0a1b3

File tree

5 files changed

+436
-0
lines changed

5 files changed

+436
-0
lines changed
Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
use crate::cache::CacheDirective;
2+
use crate::headers::{HeaderName, HeaderValue, Headers, ToHeaderValues, CACHE_CONTROL};
3+
4+
use std::fmt::{self, Debug, Write};
5+
use std::iter::Iterator;
6+
use std::option;
7+
use std::slice;
8+
9+
/// A Cache-Control header.
10+
///
11+
/// # Examples
12+
///
13+
/// ```
14+
/// # fn main() -> http_types::Result<()> {
15+
/// #
16+
/// use http_types::Response;
17+
/// use http_types::cache::{CacheControl, CacheDirective};
18+
/// let mut entries = CacheControl::new();
19+
/// entries.push(CacheDirective::Immutable);
20+
/// entries.push(CacheDirective::NoStore);
21+
///
22+
/// let mut res = Response::new(200);
23+
/// entries.apply(&mut res);
24+
///
25+
/// let entries = CacheControl::from_headers(res)?.unwrap();
26+
/// let mut entries = entries.iter();
27+
/// assert_eq!(entries.next().unwrap(), &CacheDirective::Immutable);
28+
/// assert_eq!(entries.next().unwrap(), &CacheDirective::NoStore);
29+
/// #
30+
/// # Ok(()) }
31+
/// ```
32+
pub struct CacheControl {
33+
entries: Vec<CacheDirective>,
34+
}
35+
36+
impl CacheControl {
37+
/// Create a new instance of `CacheControl`.
38+
pub fn new() -> Self {
39+
Self { entries: vec![] }
40+
}
41+
42+
/// Create a new instance from headers.
43+
pub fn from_headers(headers: impl AsRef<Headers>) -> crate::Result<Option<Self>> {
44+
let mut entries = vec![];
45+
let headers = match headers.as_ref().get(CACHE_CONTROL) {
46+
Some(headers) => headers,
47+
None => return Ok(None),
48+
};
49+
50+
for value in headers {
51+
for part in value.as_str().trim().split(',') {
52+
// Try and parse a directive from a str. If the directive is
53+
// unkown we skip it.
54+
if let Some(entry) = CacheDirective::from_str(part)? {
55+
entries.push(entry);
56+
}
57+
}
58+
}
59+
60+
Ok(Some(Self { entries }))
61+
}
62+
63+
/// Sets the `Server-Timing` header.
64+
pub fn apply(&self, mut headers: impl AsMut<Headers>) {
65+
headers.as_mut().insert(CACHE_CONTROL, self.value());
66+
}
67+
68+
/// Get the `HeaderName`.
69+
pub fn name(&self) -> HeaderName {
70+
CACHE_CONTROL
71+
}
72+
73+
/// Get the `HeaderValue`.
74+
pub fn value(&self) -> HeaderValue {
75+
let mut output = String::new();
76+
for (n, directive) in self.entries.iter().enumerate() {
77+
let directive: HeaderValue = directive.clone().into();
78+
match n {
79+
0 => write!(output, "{}", directive).unwrap(),
80+
_ => write!(output, ", {}", directive).unwrap(),
81+
};
82+
}
83+
84+
// SAFETY: the internal string is validated to be ASCII.
85+
unsafe { HeaderValue::from_bytes_unchecked(output.into()) }
86+
}
87+
/// Push a directive into the list of entries.
88+
pub fn push(&mut self, directive: CacheDirective) {
89+
self.entries.push(directive);
90+
}
91+
92+
/// An iterator visiting all server entries.
93+
pub fn iter(&self) -> Iter<'_> {
94+
Iter {
95+
inner: self.entries.iter(),
96+
}
97+
}
98+
99+
/// An iterator visiting all server entries.
100+
pub fn iter_mut(&mut self) -> IterMut<'_> {
101+
IterMut {
102+
inner: self.entries.iter_mut(),
103+
}
104+
}
105+
}
106+
107+
impl IntoIterator for CacheControl {
108+
type Item = CacheDirective;
109+
type IntoIter = IntoIter;
110+
111+
#[inline]
112+
fn into_iter(self) -> Self::IntoIter {
113+
IntoIter {
114+
inner: self.entries.into_iter(),
115+
}
116+
}
117+
}
118+
119+
impl<'a> IntoIterator for &'a CacheControl {
120+
type Item = &'a CacheDirective;
121+
type IntoIter = Iter<'a>;
122+
123+
#[inline]
124+
fn into_iter(self) -> Self::IntoIter {
125+
self.iter()
126+
}
127+
}
128+
129+
impl<'a> IntoIterator for &'a mut CacheControl {
130+
type Item = &'a mut CacheDirective;
131+
type IntoIter = IterMut<'a>;
132+
133+
#[inline]
134+
fn into_iter(self) -> Self::IntoIter {
135+
self.iter_mut()
136+
}
137+
}
138+
139+
/// A borrowing iterator over entries in `CacheControl`.
140+
#[derive(Debug)]
141+
pub struct IntoIter {
142+
inner: std::vec::IntoIter<CacheDirective>,
143+
}
144+
145+
impl Iterator for IntoIter {
146+
type Item = CacheDirective;
147+
148+
fn next(&mut self) -> Option<Self::Item> {
149+
self.inner.next()
150+
}
151+
152+
#[inline]
153+
fn size_hint(&self) -> (usize, Option<usize>) {
154+
self.inner.size_hint()
155+
}
156+
}
157+
158+
/// A lending iterator over entries in `CacheControl`.
159+
#[derive(Debug)]
160+
pub struct Iter<'a> {
161+
inner: slice::Iter<'a, CacheDirective>,
162+
}
163+
164+
impl<'a> Iterator for Iter<'a> {
165+
type Item = &'a CacheDirective;
166+
167+
fn next(&mut self) -> Option<Self::Item> {
168+
self.inner.next()
169+
}
170+
171+
#[inline]
172+
fn size_hint(&self) -> (usize, Option<usize>) {
173+
self.inner.size_hint()
174+
}
175+
}
176+
177+
/// A mutable iterator over entries in `CacheControl`.
178+
#[derive(Debug)]
179+
pub struct IterMut<'a> {
180+
inner: slice::IterMut<'a, CacheDirective>,
181+
}
182+
183+
impl<'a> Iterator for IterMut<'a> {
184+
type Item = &'a mut CacheDirective;
185+
186+
fn next(&mut self) -> Option<Self::Item> {
187+
self.inner.next()
188+
}
189+
190+
#[inline]
191+
fn size_hint(&self) -> (usize, Option<usize>) {
192+
self.inner.size_hint()
193+
}
194+
}
195+
196+
impl ToHeaderValues for CacheControl {
197+
type Iter = option::IntoIter<HeaderValue>;
198+
fn to_header_values(&self) -> crate::Result<Self::Iter> {
199+
// A HeaderValue will always convert into itself.
200+
Ok(self.value().to_header_values().unwrap())
201+
}
202+
}
203+
204+
impl Debug for CacheControl {
205+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
206+
let mut list = f.debug_list();
207+
for directive in &self.entries {
208+
list.entry(directive);
209+
}
210+
list.finish()
211+
}
212+
}
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
use crate::headers::HeaderValue;
2+
use crate::Status;
3+
4+
use std::time::Duration;
5+
6+
/// An HTTP `Cache-Control` directive.
7+
#[non_exhaustive]
8+
#[derive(Debug, Clone, PartialEq, Eq)]
9+
pub enum CacheDirective {
10+
/// The response body will not change over time.
11+
Immutable,
12+
/// The maximum amount of time a resource is considered fresh.
13+
MaxAge(Duration),
14+
/// Indicates the client will accept a stale response.
15+
MaxStale(Option<Duration>),
16+
/// A response that will still be fresh for at least the specified duration.
17+
MinFresh(Duration),
18+
/// Once a response is stale, a fresh response must be retrieved.
19+
MustRevalidate,
20+
/// The response may be cached, but must always be revalidated before being used.
21+
NoCache,
22+
/// The response may not be cached.
23+
NoStore,
24+
/// An intermediate cache or proxy should not edit the response body,
25+
/// Content-Encoding, Content-Range, or Content-Type.
26+
NoTransform,
27+
/// Do not use the network for a response.
28+
OnlyIfCached,
29+
/// The response may be stored only by a browser's cache, even if the
30+
/// response is normally non-cacheable.
31+
Private,
32+
/// Like must-revalidate, but only for shared caches (e.g., proxies).
33+
ProxyRevalidate,
34+
/// The response may be stored by any cache, even if the response is normally
35+
/// non-cacheable.
36+
Public,
37+
/// Overrides max-age or the Expires header, but only for shared caches.
38+
SMaxAge(Duration),
39+
/// The client will accept a stale response if retrieving a fresh one fails.
40+
StaleIfError(Duration),
41+
/// Indicates the client will accept a stale response, while asynchronously
42+
/// checking in the background for a fresh one.
43+
StaleWhileRevalidate(Duration),
44+
}
45+
46+
impl CacheDirective {
47+
/// Check whether this directive is valid in an HTTP request.
48+
pub fn valid_in_req(&self) -> bool {
49+
use CacheDirective::*;
50+
matches!(self,
51+
MaxAge(_) | MaxStale(_) | MinFresh(_) | NoCache | NoStore | NoTransform
52+
| OnlyIfCached)
53+
}
54+
55+
/// Check whether this directive is valid in an HTTP response.
56+
pub fn valid_in_res(&self) -> bool {
57+
use CacheDirective::*;
58+
matches!(self,
59+
MustRevalidate
60+
| NoCache
61+
| NoStore
62+
| NoTransform
63+
| Public
64+
| Private
65+
| ProxyRevalidate
66+
| MaxAge(_)
67+
| SMaxAge(_)
68+
| StaleIfError(_)
69+
| StaleWhileRevalidate(_))
70+
}
71+
72+
/// Create an instance from a string slice.
73+
//
74+
// This is a private method rather than a trait because we assume the
75+
// input string is a single-value only. This is upheld by the calling
76+
// function, but we cannot guarantee this to be true in the general
77+
// sense.
78+
pub(crate) fn from_str(s: &str) -> crate::Result<Option<Self>> {
79+
use CacheDirective::*;
80+
81+
let s = s.trim();
82+
83+
// We're dealing with an empty string.
84+
if s.is_empty() {
85+
return Ok(None);
86+
}
87+
88+
s.to_lowercase();
89+
let mut parts = s.split('=');
90+
let next = parts.next().unwrap();
91+
92+
let mut get_dur = || -> crate::Result<Duration> {
93+
let dur = parts.next().status(400)?;
94+
let dur: u64 = dur.parse().status(400)?;
95+
Ok(Duration::new(dur, 0))
96+
};
97+
98+
// This won't panic because each input string has at least one part.
99+
let res = match next {
100+
"immutable" => Some(Immutable),
101+
"no-cache" => Some(NoCache),
102+
"no-store" => Some(NoStore),
103+
"no-transform" => Some(NoTransform),
104+
"only-if-cached" => Some(OnlyIfCached),
105+
"must-revalidate" => Some(MustRevalidate),
106+
"public" => Some(Public),
107+
"private" => Some(Private),
108+
"proxy-revalidate" => Some(ProxyRevalidate),
109+
"max-age" => Some(MaxAge(get_dur()?)),
110+
"max-stale" => match parts.next() {
111+
Some(secs) => {
112+
let dur: u64 = secs.parse().status(400)?;
113+
Some(MaxStale(Some(Duration::new(dur, 0))))
114+
}
115+
None => Some(MaxStale(None)),
116+
},
117+
"min-fresh" => Some(MinFresh(get_dur()?)),
118+
"s-maxage" => Some(SMaxAge(get_dur()?)),
119+
"stale-if-error" => Some(StaleIfError(get_dur()?)),
120+
"stale-while-revalidate" => Some(StaleWhileRevalidate(get_dur()?)),
121+
_ => None,
122+
};
123+
Ok(res)
124+
}
125+
}
126+
127+
impl From<CacheDirective> for HeaderValue {
128+
fn from(directive: CacheDirective) -> Self {
129+
use CacheDirective::*;
130+
let h = |s: String| unsafe { HeaderValue::from_bytes_unchecked(s.into_bytes()) };
131+
132+
match directive {
133+
Immutable => h("immutable".to_string()),
134+
MaxAge(dur) => h(format!("max-age={}", dur.as_secs())),
135+
MaxStale(dur) => match dur {
136+
Some(dur) => h(format!("max-stale={}", dur.as_secs())),
137+
None => h("max-stale".to_string()),
138+
},
139+
MinFresh(dur) => h(format!("min-fresh={}", dur.as_secs())),
140+
MustRevalidate => h("must-revalidate".to_string()),
141+
NoCache => h("no-cache".to_string()),
142+
NoStore => h("no-store".to_string()),
143+
NoTransform => h("no-transform".to_string()),
144+
OnlyIfCached => h("only-if-cached".to_string()),
145+
Private => h("private".to_string()),
146+
ProxyRevalidate => h("proxy-revalidate".to_string()),
147+
Public => h("public".to_string()),
148+
SMaxAge(dur) => h(format!("s-max-age={}", dur.as_secs())),
149+
StaleIfError(dur) => h(format!("stale-if-error={}", dur.as_secs())),
150+
StaleWhileRevalidate(dur) => h(format!("stale-while-revalidate={}", dur.as_secs())),
151+
}
152+
}
153+
}

0 commit comments

Comments
 (0)