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.core5.http.Header;
35 import org.apache.hc.core5.http.HttpHeaders;
36 import org.apache.hc.core5.http.message.MessageSupport;
37 import org.apache.hc.core5.util.TimeValue;
38 import org.slf4j.Logger;
39 import org.slf4j.LoggerFactory;
40
41 class CacheValidityPolicy {
42
43 private static final Logger LOG = LoggerFactory.getLogger(CacheValidityPolicy.class);
44
45 private final boolean shared;
46 private final boolean useHeuristicCaching;
47 private final float heuristicCoefficient;
48 private final TimeValue heuristicDefaultLifetime;
49
50
51
52
53
54
55
56
57 CacheValidityPolicy(final CacheConfig config) {
58 super();
59 this.shared = config != null ? config.isSharedCache() : CacheConfig.DEFAULT.isSharedCache();
60 this.useHeuristicCaching = config != null ? config.isHeuristicCachingEnabled() : CacheConfig.DEFAULT.isHeuristicCachingEnabled();
61 this.heuristicCoefficient = config != null ? config.getHeuristicCoefficient() : CacheConfig.DEFAULT.getHeuristicCoefficient();
62 this.heuristicDefaultLifetime = config != null ? config.getHeuristicDefaultLifetime() : CacheConfig.DEFAULT.getHeuristicDefaultLifetime();
63 }
64
65
66
67
68 CacheValidityPolicy() {
69 this(null);
70 }
71
72
73 public TimeValue getCurrentAge(final HttpCacheEntry entry, final Instant now) {
74 return TimeValue.ofSeconds(getCorrectedInitialAge(entry).toSeconds() + getResidentTime(entry, now).toSeconds());
75 }
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90 public TimeValue getFreshnessLifetime(final ResponseCacheControl responseCacheControl, final HttpCacheEntry entry) {
91
92 if (shared) {
93 final long sharedMaxAge = responseCacheControl.getSharedMaxAge();
94 if (sharedMaxAge > -1) {
95 if (LOG.isDebugEnabled()) {
96 LOG.debug("Using s-maxage directive for freshness lifetime calculation: {} seconds", sharedMaxAge);
97 }
98 return TimeValue.ofSeconds(sharedMaxAge);
99 }
100 }
101
102
103 final long maxAge = responseCacheControl.getMaxAge();
104 if (maxAge > -1) {
105 if (LOG.isDebugEnabled()) {
106 LOG.debug("Using max-age directive for freshness lifetime calculation: {} seconds", maxAge);
107 }
108 return TimeValue.ofSeconds(maxAge);
109 }
110
111
112 final Instant dateValue = entry.getInstant();
113 if (dateValue != null) {
114 final Instant expiry = entry.getExpires();
115 if (expiry != null) {
116 final Duration diff = Duration.between(dateValue, expiry);
117 if (diff.isNegative()) {
118 if (LOG.isDebugEnabled()) {
119 LOG.debug("Negative freshness lifetime detected. Content is already expired. Returning zero freshness lifetime.");
120 }
121 return TimeValue.ZERO_MILLISECONDS;
122 }
123 return TimeValue.ofSeconds(diff.getSeconds());
124 }
125 }
126
127 if (useHeuristicCaching) {
128
129 if (LOG.isDebugEnabled()) {
130 LOG.debug("No explicit expiration time present in the response. Using heuristic freshness lifetime calculation.");
131 }
132 return getHeuristicFreshnessLifetime(entry);
133 } else {
134 return TimeValue.ZERO_MILLISECONDS;
135 }
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 }