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