1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27 package org.apache.hc.client5.http.impl.cache;
28
29 import java.time.Duration;
30 import java.time.Instant;
31 import java.util.Iterator;
32
33 import org.apache.hc.client5.http.cache.HeaderConstants;
34 import org.apache.hc.client5.http.cache.HttpCacheEntry;
35 import org.apache.hc.client5.http.cache.Resource;
36 import org.apache.hc.client5.http.utils.DateUtils;
37 import org.apache.hc.core5.http.Header;
38 import org.apache.hc.core5.http.HeaderElement;
39 import org.apache.hc.core5.http.HttpHeaders;
40 import org.apache.hc.core5.http.HttpRequest;
41 import org.apache.hc.core5.http.MessageHeaders;
42 import org.apache.hc.core5.http.message.MessageSupport;
43 import org.apache.hc.core5.util.TimeValue;
44
45 class CacheValidityPolicy {
46
47 public static final TimeValue MAX_AGE = TimeValue.ofSeconds(Integer.MAX_VALUE + 1L);
48
49 CacheValidityPolicy() {
50 super();
51 }
52
53
54 public TimeValue getCurrentAge(final HttpCacheEntry entry, final Instant now) {
55 return TimeValue.ofSeconds(getCorrectedInitialAge(entry).toSeconds() + getResidentTime(entry, now).toSeconds());
56 }
57
58 public TimeValue getFreshnessLifetime(final HttpCacheEntry entry) {
59 final long maxAge = getMaxAge(entry);
60 if (maxAge > -1) {
61 return TimeValue.ofSeconds(maxAge);
62 }
63
64 final Instant dateValue = entry.getInstant();
65 if (dateValue == null) {
66 return TimeValue.ZERO_MILLISECONDS;
67 }
68
69 final Instant expiry = DateUtils.parseStandardDate(entry, HeaderConstants.EXPIRES);
70 if (expiry == null) {
71 return TimeValue.ZERO_MILLISECONDS;
72 }
73 final Duration diff = Duration.between(dateValue, expiry);
74 return TimeValue.ofSeconds(diff.getSeconds());
75 }
76
77 public boolean isResponseFresh(final HttpCacheEntry entry, final Instant now) {
78 return getCurrentAge(entry, now).compareTo(getFreshnessLifetime(entry)) == -1;
79 }
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95 public boolean isResponseHeuristicallyFresh(final HttpCacheEntry entry,
96 final Instant now, final float coefficient, final TimeValue defaultLifetime) {
97 return getCurrentAge(entry, now).compareTo(getHeuristicFreshnessLifetime(entry, coefficient, defaultLifetime)) == -1;
98 }
99
100 public TimeValue getHeuristicFreshnessLifetime(final HttpCacheEntry entry,
101 final float coefficient, final TimeValue defaultLifetime) {
102 final Instant dateValue = entry.getInstant();
103 final Instant lastModifiedValue = DateUtils.parseStandardDate(entry, HeaderConstants.LAST_MODIFIED);
104
105 if (dateValue != null && lastModifiedValue != null) {
106 final Duration diff = Duration.between(lastModifiedValue, dateValue);
107
108 if (diff.isNegative()) {
109 return TimeValue.ZERO_MILLISECONDS;
110 }
111 return TimeValue.ofSeconds((long) (coefficient * diff.getSeconds()));
112 }
113
114 return defaultLifetime;
115 }
116
117 public boolean isRevalidatable(final HttpCacheEntry entry) {
118 return entry.getFirstHeader(HeaderConstants.ETAG) != null
119 || entry.getFirstHeader(HeaderConstants.LAST_MODIFIED) != null;
120 }
121
122 public boolean mustRevalidate(final HttpCacheEntry entry) {
123 return hasCacheControlDirective(entry, HeaderConstants.CACHE_CONTROL_MUST_REVALIDATE);
124 }
125
126 public boolean proxyRevalidate(final HttpCacheEntry entry) {
127 return hasCacheControlDirective(entry, HeaderConstants.CACHE_CONTROL_PROXY_REVALIDATE);
128 }
129
130 public boolean mayReturnStaleWhileRevalidating(final HttpCacheEntry entry, final Instant now) {
131 final Iterator<HeaderElement> it = MessageSupport.iterate(entry, HeaderConstants.CACHE_CONTROL);
132 while (it.hasNext()) {
133 final HeaderElement elt = it.next();
134 if (HeaderConstants.STALE_WHILE_REVALIDATE.equalsIgnoreCase(elt.getName())) {
135 try {
136
137 final int allowedStalenessLifetime = Integer.parseInt(elt.getValue());
138 if (getStaleness(entry, now).compareTo(TimeValue.ofSeconds(allowedStalenessLifetime)) <= 0) {
139 return true;
140 }
141 } catch (final NumberFormatException nfe) {
142
143 }
144 }
145 }
146
147 return false;
148 }
149
150 public boolean mayReturnStaleIfError(final HttpRequest request, final HttpCacheEntry entry, final Instant now) {
151 final TimeValue staleness = getStaleness(entry, now);
152 return mayReturnStaleIfError(request, HeaderConstants.CACHE_CONTROL, staleness)
153 || mayReturnStaleIfError(entry, HeaderConstants.CACHE_CONTROL, staleness);
154 }
155
156 private boolean mayReturnStaleIfError(final MessageHeaders headers, final String name, final TimeValue staleness) {
157 boolean result = false;
158 final Iterator<HeaderElement> it = MessageSupport.iterate(headers, name);
159 while (it.hasNext()) {
160 final HeaderElement elt = it.next();
161 if (HeaderConstants.STALE_IF_ERROR.equals(elt.getName())) {
162 try {
163
164 final int staleIfError = Integer.parseInt(elt.getValue());
165 if (staleness.compareTo(TimeValue.ofSeconds(staleIfError)) <= 0) {
166 result = true;
167 break;
168 }
169 } catch (final NumberFormatException nfe) {
170
171 }
172 }
173 }
174 return result;
175 }
176
177
178
179
180
181
182
183
184 protected boolean contentLengthHeaderMatchesActualLength(final HttpCacheEntry entry) {
185 final Header h = entry.getFirstHeader(HttpHeaders.CONTENT_LENGTH);
186 if (h != null) {
187 try {
188 final long responseLen = Long.parseLong(h.getValue());
189 final Resource resource = entry.getResource();
190 if (resource == null) {
191 return false;
192 }
193 final long resourceLen = resource.length();
194 return responseLen == resourceLen;
195 } catch (final NumberFormatException ex) {
196 return false;
197 }
198 }
199 return true;
200 }
201
202 protected TimeValue getApparentAge(final HttpCacheEntry entry) {
203 final Instant dateValue = entry.getInstant();
204 if (dateValue == null) {
205 return MAX_AGE;
206 }
207 final Duration diff = Duration.between(dateValue, entry.getResponseInstant());
208 if (diff.isNegative()) {
209 return TimeValue.ZERO_MILLISECONDS;
210 }
211 return TimeValue.ofSeconds(diff.getSeconds());
212 }
213
214 protected long getAgeValue(final HttpCacheEntry entry) {
215
216 long ageValue = 0;
217 for (final Header hdr : entry.getHeaders(HeaderConstants.AGE)) {
218 long hdrAge;
219 try {
220 hdrAge = Long.parseLong(hdr.getValue());
221 if (hdrAge < 0) {
222 hdrAge = MAX_AGE.toSeconds();
223 }
224 } catch (final NumberFormatException nfe) {
225 hdrAge = MAX_AGE.toSeconds();
226 }
227 ageValue = (hdrAge > ageValue) ? hdrAge : ageValue;
228 }
229 return ageValue;
230 }
231
232 protected TimeValue getCorrectedReceivedAge(final HttpCacheEntry entry) {
233 final TimeValue apparentAge = getApparentAge(entry);
234 final long ageValue = getAgeValue(entry);
235 return (apparentAge.toSeconds() > ageValue) ? apparentAge : TimeValue.ofSeconds(ageValue);
236 }
237
238 protected TimeValue getResponseDelay(final HttpCacheEntry entry) {
239 final Duration diff = Duration.between(entry.getRequestInstant(), entry.getResponseInstant());
240 return TimeValue.ofSeconds(diff.getSeconds());
241 }
242
243 protected TimeValue getCorrectedInitialAge(final HttpCacheEntry entry) {
244 return TimeValue.ofSeconds(getCorrectedReceivedAge(entry).toSeconds() + getResponseDelay(entry).toSeconds());
245 }
246
247 protected TimeValue getResidentTime(final HttpCacheEntry entry, final Instant now) {
248 final Duration diff = Duration.between(entry.getResponseInstant(), now);
249 return TimeValue.ofSeconds(diff.getSeconds());
250 }
251
252
253 protected long getMaxAge(final HttpCacheEntry entry) {
254
255 long maxAge = -1;
256 final Iterator<HeaderElement> it = MessageSupport.iterate(entry, HeaderConstants.CACHE_CONTROL);
257 while (it.hasNext()) {
258 final HeaderElement elt = it.next();
259 if (HeaderConstants.CACHE_CONTROL_MAX_AGE.equals(elt.getName()) || "s-maxage".equals(elt.getName())) {
260 try {
261 final long currMaxAge = Long.parseLong(elt.getValue());
262 if (maxAge == -1 || currMaxAge < maxAge) {
263 maxAge = currMaxAge;
264 }
265 } catch (final NumberFormatException nfe) {
266
267 maxAge = 0;
268 }
269 }
270 }
271 return maxAge;
272 }
273
274 public boolean hasCacheControlDirective(final HttpCacheEntry entry, final String directive) {
275 final Iterator<HeaderElement> it = MessageSupport.iterate(entry, HeaderConstants.CACHE_CONTROL);
276 while (it.hasNext()) {
277 final HeaderElement elt = it.next();
278 if (directive.equalsIgnoreCase(elt.getName())) {
279 return true;
280 }
281 }
282 return false;
283 }
284
285 public TimeValue getStaleness(final HttpCacheEntry entry, final Instant now) {
286 final TimeValue age = getCurrentAge(entry, now);
287 final TimeValue freshness = getFreshnessLifetime(entry);
288 if (age.compareTo(freshness) <= 0) {
289 return TimeValue.ZERO_MILLISECONDS;
290 }
291 return TimeValue.ofSeconds(age.toSeconds() - freshness.toSeconds());
292 }
293
294
295 }