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.concurrent.atomic.AtomicReference;
32
33 import org.apache.hc.client5.http.cache.HttpCacheEntry;
34 import org.apache.hc.client5.http.cache.ResponseCacheControl;
35 import org.apache.hc.core5.http.Header;
36 import org.apache.hc.core5.http.HttpHeaders;
37 import org.apache.hc.core5.http.message.MessageSupport;
38 import org.apache.hc.core5.util.TimeValue;
39 import org.slf4j.Logger;
40 import org.slf4j.LoggerFactory;
41
42 class CacheValidityPolicy {
43
44 private static final Logger LOG = LoggerFactory.getLogger(CacheValidityPolicy.class);
45
46 private final boolean shared;
47 private final boolean useHeuristicCaching;
48 private final float heuristicCoefficient;
49 private final TimeValue heuristicDefaultLifetime;
50
51
52
53
54
55
56
57
58 CacheValidityPolicy(final CacheConfig config) {
59 super();
60 this.shared = config != null ? config.isSharedCache() : CacheConfig.DEFAULT.isSharedCache();
61 this.useHeuristicCaching = config != null ? config.isHeuristicCachingEnabled() : CacheConfig.DEFAULT.isHeuristicCachingEnabled();
62 this.heuristicCoefficient = config != null ? config.getHeuristicCoefficient() : CacheConfig.DEFAULT.getHeuristicCoefficient();
63 this.heuristicDefaultLifetime = config != null ? config.getHeuristicDefaultLifetime() : CacheConfig.DEFAULT.getHeuristicDefaultLifetime();
64 }
65
66
67
68
69 CacheValidityPolicy() {
70 this(null);
71 }
72
73
74 public TimeValue getCurrentAge(final HttpCacheEntry entry, final Instant now) {
75 return TimeValue.ofSeconds(getCorrectedInitialAge(entry).toSeconds() + getResidentTime(entry, now).toSeconds());
76 }
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91 public TimeValue getFreshnessLifetime(final ResponseCacheControl responseCacheControl, final HttpCacheEntry entry) {
92
93 if (shared) {
94 final long sharedMaxAge = responseCacheControl.getSharedMaxAge();
95 if (sharedMaxAge > -1) {
96 if (LOG.isDebugEnabled()) {
97 LOG.debug("Using s-maxage directive for freshness lifetime calculation: {} seconds", sharedMaxAge);
98 }
99 return TimeValue.ofSeconds(sharedMaxAge);
100 }
101 }
102
103
104 final long maxAge = responseCacheControl.getMaxAge();
105 if (maxAge > -1) {
106 if (LOG.isDebugEnabled()) {
107 LOG.debug("Using max-age directive for freshness lifetime calculation: {} seconds", maxAge);
108 }
109 return TimeValue.ofSeconds(maxAge);
110 }
111
112
113 final Instant dateValue = entry.getInstant();
114 if (dateValue != null) {
115 final Instant expiry = entry.getExpires();
116 if (expiry != null) {
117 final Duration diff = Duration.between(dateValue, expiry);
118 if (diff.isNegative()) {
119 if (LOG.isDebugEnabled()) {
120 LOG.debug("Negative freshness lifetime detected. Content is already expired. Returning zero freshness lifetime.");
121 }
122 return TimeValue.ZERO_MILLISECONDS;
123 }
124 return TimeValue.ofSeconds(diff.getSeconds());
125 }
126 }
127
128 if (useHeuristicCaching) {
129
130 if (LOG.isDebugEnabled()) {
131 LOG.debug("No explicit expiration time present in the response. Using heuristic freshness lifetime calculation.");
132 }
133 return getHeuristicFreshnessLifetime(entry);
134 }
135 return TimeValue.ZERO_MILLISECONDS;
136 }
137
138 TimeValue getHeuristicFreshnessLifetime(final HttpCacheEntry entry) {
139 final Instant dateValue = entry.getInstant();
140 final Instant lastModifiedValue = entry.getLastModified();
141
142 if (dateValue != null && lastModifiedValue != null) {
143 final Duration diff = Duration.between(lastModifiedValue, dateValue);
144
145 if (diff.isNegative()) {
146 return TimeValue.ZERO_MILLISECONDS;
147 }
148 return TimeValue.ofSeconds((long) (heuristicCoefficient * diff.getSeconds()));
149 }
150
151 return heuristicDefaultLifetime;
152 }
153
154 TimeValue getApparentAge(final HttpCacheEntry entry) {
155 final Instant dateValue = entry.getInstant();
156 if (dateValue == null) {
157 return CacheSupport.MAX_AGE;
158 }
159 final Duration diff = Duration.between(dateValue, entry.getResponseInstant());
160 if (diff.isNegative()) {
161 return TimeValue.ZERO_MILLISECONDS;
162 }
163 return TimeValue.ofSeconds(diff.getSeconds());
164 }
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181 long getAgeValue(final HttpCacheEntry entry) {
182 final Header age = entry.getFirstHeader(HttpHeaders.AGE);
183 if (age != null) {
184 final AtomicReference<String> firstToken = new AtomicReference<>();
185 MessageSupport.parseTokens(age, token -> firstToken.compareAndSet(null, token));
186 final long delta = CacheSupport.deltaSeconds(firstToken.get());
187 if (delta == -1 && LOG.isDebugEnabled()) {
188 LOG.debug("Malformed Age value: {}", age);
189 }
190 return delta > 0 ? delta : 0;
191 }
192
193 return 0;
194 }
195
196 TimeValue getCorrectedAgeValue(final HttpCacheEntry entry) {
197 final long ageValue = getAgeValue(entry);
198 final long responseDelay = getResponseDelay(entry).toSeconds();
199 return TimeValue.ofSeconds(ageValue + responseDelay);
200 }
201
202 TimeValue getResponseDelay(final HttpCacheEntry entry) {
203 final Duration diff = Duration.between(entry.getRequestInstant(), entry.getResponseInstant());
204 return TimeValue.ofSeconds(diff.getSeconds());
205 }
206
207 TimeValue getCorrectedInitialAge(final HttpCacheEntry entry) {
208 final long apparentAge = getApparentAge(entry).toSeconds();
209 final long correctedReceivedAge = getCorrectedAgeValue(entry).toSeconds();
210 return TimeValue.ofSeconds(Math.max(apparentAge, correctedReceivedAge));
211 }
212
213 TimeValue getResidentTime(final HttpCacheEntry entry, final Instant now) {
214 final Duration diff = Duration.between(entry.getResponseInstant(), now);
215 return TimeValue.ofSeconds(diff.getSeconds());
216 }
217
218 }