View Javadoc
1   /*
2    * ====================================================================
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *   http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing,
14   * software distributed under the License is distributed on an
15   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16   * KIND, either express or implied.  See the License for the
17   * specific language governing permissions and limitations
18   * under the License.
19   * ====================================================================
20   *
21   * This software consists of voluntary contributions made by many
22   * individuals on behalf of the Apache Software Foundation.  For more
23   * information on the Apache Software Foundation, please see
24   * <http://www.apache.org/>.
25   *
26   */
27  package org.apache.http.impl.client.cache;
28  
29  import java.util.Date;
30  
31  import org.apache.http.Header;
32  import org.apache.http.HeaderElement;
33  import org.apache.http.HttpRequest;
34  import org.apache.http.annotation.Contract;
35  import org.apache.http.annotation.ThreadingBehavior;
36  import org.apache.http.client.cache.HeaderConstants;
37  import org.apache.http.client.cache.HttpCacheEntry;
38  import org.apache.http.client.utils.DateUtils;
39  import org.apache.http.protocol.HTTP;
40  
41  /**
42   * @since 4.1
43   */
44  @Contract(threading = ThreadingBehavior.IMMUTABLE)
45  class CacheValidityPolicy {
46  
47      public static final long MAX_AGE = 2147483648L;
48  
49      CacheValidityPolicy() {
50          super();
51      }
52  
53      public long getCurrentAgeSecs(final HttpCacheEntry entry, final Date now) {
54          return getCorrectedInitialAgeSecs(entry) + getResidentTimeSecs(entry, now);
55      }
56  
57      public long getFreshnessLifetimeSecs(final HttpCacheEntry entry) {
58          final long maxage = getMaxAge(entry);
59          if (maxage > -1) {
60              return maxage;
61          }
62  
63          final Date dateValue = entry.getDate();
64          if (dateValue == null) {
65              return 0L;
66          }
67  
68          final Date expiry = getExpirationDate(entry);
69          if (expiry == null) {
70              return 0;
71          }
72          final long diff = expiry.getTime() - dateValue.getTime();
73          return (diff / 1000);
74      }
75  
76      public boolean isResponseFresh(final HttpCacheEntry entry, final Date now) {
77          return (getCurrentAgeSecs(entry, now) < getFreshnessLifetimeSecs(entry));
78      }
79  
80      /**
81       * Decides if this response is fresh enough based Last-Modified and Date, if available.
82       * This entry is meant to be used when isResponseFresh returns false.  The algorithm is as follows:
83       *
84       * if last-modified and date are defined, freshness lifetime is coefficient*(date-lastModified),
85       * else freshness lifetime is defaultLifetime
86       *
87       * @param entry the cache entry
88       * @param now what time is it currently (When is right NOW)
89       * @param coefficient Part of the heuristic for cache entry freshness
90       * @param defaultLifetime How long can I assume a cache entry is default TTL
91       * @return {@code true} if the response is fresh
92       */
93      public boolean isResponseHeuristicallyFresh(final HttpCacheEntry entry,
94              final Date now, final float coefficient, final long defaultLifetime) {
95          return (getCurrentAgeSecs(entry, now) < getHeuristicFreshnessLifetimeSecs(entry, coefficient, defaultLifetime));
96      }
97  
98      public long getHeuristicFreshnessLifetimeSecs(final HttpCacheEntry entry,
99              final float coefficient, final long defaultLifetime) {
100         final Date dateValue = entry.getDate();
101         final Date lastModifiedValue = getLastModifiedValue(entry);
102 
103         if (dateValue != null && lastModifiedValue != null) {
104             final long diff = dateValue.getTime() - lastModifiedValue.getTime();
105             if (diff < 0) {
106                 return 0;
107             }
108             return (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         for (final Header h : entry.getHeaders(HeaderConstants.CACHE_CONTROL)) {
129             for(final HeaderElement elt : h.getElements()) {
130                 if (HeaderConstants.STALE_WHILE_REVALIDATE.equalsIgnoreCase(elt.getName())) {
131                     try {
132                         final int allowedStalenessLifetime = Integer.parseInt(elt.getValue());
133                         if (getStalenessSecs(entry, now) <= allowedStalenessLifetime) {
134                             return true;
135                         }
136                     } catch (final NumberFormatException nfe) {
137                         // skip malformed directive
138                     }
139                 }
140             }
141         }
142 
143         return false;
144     }
145 
146     public boolean mayReturnStaleIfError(final HttpRequest request,
147             final HttpCacheEntry entry, final Date now) {
148         final long stalenessSecs = getStalenessSecs(entry, now);
149         return mayReturnStaleIfError(request.getHeaders(HeaderConstants.CACHE_CONTROL),
150                                      stalenessSecs)
151                 || mayReturnStaleIfError(entry.getHeaders(HeaderConstants.CACHE_CONTROL),
152                                          stalenessSecs);
153     }
154 
155     private boolean mayReturnStaleIfError(final Header[] headers, final long stalenessSecs) {
156         boolean result = false;
157         for(final Header h : headers) {
158             for(final HeaderElement elt : h.getElements()) {
159                 if (HeaderConstants.STALE_IF_ERROR.equals(elt.getName())) {
160                     try {
161                         final int staleIfErrorSecs = Integer.parseInt(elt.getValue());
162                         if (stalenessSecs <= staleIfErrorSecs) {
163                             result = true;
164                             break;
165                         }
166                     } catch (final NumberFormatException nfe) {
167                         // skip malformed directive
168                     }
169                 }
170             }
171         }
172         return result;
173     }
174 
175     /**
176      * @deprecated (4.3) use {@link HttpCacheEntry#getDate()}.
177      * @param entry
178      * @return
179      */
180     @Deprecated
181     protected Date getDateValue(final HttpCacheEntry entry) {
182         return entry.getDate();
183     }
184 
185     protected Date getLastModifiedValue(final HttpCacheEntry entry) {
186         final Header dateHdr = entry.getFirstHeader(HeaderConstants.LAST_MODIFIED);
187         if (dateHdr == null) {
188             return null;
189         }
190         return DateUtils.parseDate(dateHdr.getValue());
191     }
192 
193     protected long getContentLengthValue(final HttpCacheEntry entry) {
194         final Header cl = entry.getFirstHeader(HTTP.CONTENT_LEN);
195         if (cl == null) {
196             return -1;
197         }
198 
199         try {
200             return Long.parseLong(cl.getValue());
201         } catch (final NumberFormatException ex) {
202             return -1;
203         }
204     }
205 
206     protected boolean hasContentLengthHeader(final HttpCacheEntry entry) {
207         return null != entry.getFirstHeader(HTTP.CONTENT_LEN);
208     }
209 
210     /**
211      * This matters for deciding whether the cache entry is valid to serve as a
212      * response. If these values do not match, we might have a partial response
213      *
214      * @param entry The cache entry we are currently working with
215      * @return boolean indicating whether actual length matches Content-Length
216      */
217     protected boolean contentLengthHeaderMatchesActualLength(final HttpCacheEntry entry) {
218         return !hasContentLengthHeader(entry) ||
219                 (entry.getResource() != null && getContentLengthValue(entry) == entry.getResource().length());
220     }
221 
222     protected long getApparentAgeSecs(final HttpCacheEntry entry) {
223         final Date dateValue = entry.getDate();
224         if (dateValue == null) {
225             return MAX_AGE;
226         }
227         final long diff = entry.getResponseDate().getTime() - dateValue.getTime();
228         if (diff < 0L) {
229             return 0;
230         }
231         return (diff / 1000);
232     }
233 
234     protected long getAgeValue(final HttpCacheEntry entry) {
235         long ageValue = 0;
236         for (final Header hdr : entry.getHeaders(HeaderConstants.AGE)) {
237             long hdrAge;
238             try {
239                 hdrAge = Long.parseLong(hdr.getValue());
240                 if (hdrAge < 0) {
241                     hdrAge = MAX_AGE;
242                 }
243             } catch (final NumberFormatException nfe) {
244                 hdrAge = MAX_AGE;
245             }
246             ageValue = (hdrAge > ageValue) ? hdrAge : ageValue;
247         }
248         return ageValue;
249     }
250 
251     protected long getCorrectedReceivedAgeSecs(final HttpCacheEntry entry) {
252         final long apparentAge = getApparentAgeSecs(entry);
253         final long ageValue = getAgeValue(entry);
254         return (apparentAge > ageValue) ? apparentAge : ageValue;
255     }
256 
257     protected long getResponseDelaySecs(final HttpCacheEntry entry) {
258         final long diff = entry.getResponseDate().getTime() - entry.getRequestDate().getTime();
259         return (diff / 1000L);
260     }
261 
262     protected long getCorrectedInitialAgeSecs(final HttpCacheEntry entry) {
263         return getCorrectedReceivedAgeSecs(entry) + getResponseDelaySecs(entry);
264     }
265 
266     protected long getResidentTimeSecs(final HttpCacheEntry entry, final Date now) {
267         final long diff = now.getTime() - entry.getResponseDate().getTime();
268         return (diff / 1000L);
269     }
270 
271     protected long getMaxAge(final HttpCacheEntry entry) {
272         long maxage = -1;
273         for (final Header hdr : entry.getHeaders(HeaderConstants.CACHE_CONTROL)) {
274             for (final HeaderElement elt : hdr.getElements()) {
275                 if (HeaderConstants.CACHE_CONTROL_MAX_AGE.equals(elt.getName())
276                         || "s-maxage".equals(elt.getName())) {
277                     try {
278                         final long currMaxAge = Long.parseLong(elt.getValue());
279                         if (maxage == -1 || currMaxAge < maxage) {
280                             maxage = currMaxAge;
281                         }
282                     } catch (final NumberFormatException nfe) {
283                         // be conservative if can't parse
284                         maxage = 0;
285                     }
286                 }
287             }
288         }
289         return maxage;
290     }
291 
292     protected Date getExpirationDate(final HttpCacheEntry entry) {
293         final Header expiresHeader = entry.getFirstHeader(HeaderConstants.EXPIRES);
294         if (expiresHeader == null) {
295             return null;
296         }
297         return DateUtils.parseDate(expiresHeader.getValue());
298     }
299 
300     public boolean hasCacheControlDirective(final HttpCacheEntry entry,
301             final String directive) {
302         for (final Header h : entry.getHeaders(HeaderConstants.CACHE_CONTROL)) {
303             for(final HeaderElement elt : h.getElements()) {
304                 if (directive.equalsIgnoreCase(elt.getName())) {
305                     return true;
306                 }
307             }
308         }
309         return false;
310     }
311 
312     public long getStalenessSecs(final HttpCacheEntry entry, final Date now) {
313         final long age = getCurrentAgeSecs(entry, now);
314         final long freshness = getFreshnessLifetimeSecs(entry);
315         if (age <= freshness) {
316             return 0L;
317         }
318         return (age - freshness);
319     }
320 
321 
322 }