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.hc.client5.http.impl.cache;
28  
29  import org.apache.hc.core5.util.Args;
30  import org.apache.hc.core5.util.TimeValue;
31  
32  /**
33   * <p>Java Beans-style configuration for caching {@link org.apache.hc.client5.http.classic.HttpClient}.
34   * Any class in the caching module that has configuration options should take a
35   * {@link CacheConfig} argument in one of its constructors. A
36   * {@code CacheConfig} instance has sane and conservative defaults, so the
37   * easiest way to specify options is to get an instance and then set just
38   * the options you want to modify from their defaults.</p>
39   *
40   * <p><b>N.B.</b> This class is only for caching-specific configuration; to
41   * configure the behavior of the rest of the client, configure the
42   * {@link org.apache.hc.client5.http.classic.HttpClient} used as the &quot;backend&quot;
43   * for the {@code CachingHttpClient}.</p>
44   *
45   * <p>Cache configuration can be grouped into the following categories:</p>
46   *
47   * <p><b>Cache size.</b> If the backend storage supports these limits, you
48   * can specify the {@link CacheConfig#getMaxCacheEntries maximum number of
49   * cache entries} as well as the {@link CacheConfig#getMaxObjectSize()}
50   * maximum cacheable response body size}.</p>
51   *
52   * <p><b>Public/private caching.</b> By default, the caching module considers
53   * itself to be a shared (public) cache, and will not, for example, cache
54   * responses to requests with {@code Authorization} headers or responses
55   * marked with {@code Cache-Control: private}. If, however, the cache
56   * is only going to be used by one logical "user" (behaving similarly to a
57   * browser cache), then you will want to {@link
58   * CacheConfig#isSharedCache()}  turn off the shared cache setting}.</p>
59   *
60   * <p><b>303 caching</b>. RFC2616 explicitly disallows caching 303 responses;
61   * however, the HTTPbis working group says they can be cached
62   * if explicitly indicated in the response headers and permitted by the request method.
63   * (They also indicate that disallowing 303 caching is actually an unintended
64   * spec error in RFC2616).
65   * This behavior is off by default, to err on the side of a conservative
66   * adherence to the existing standard, but you may want to
67   * {@link Builder#setAllow303Caching(boolean) enable it}.
68   *
69   * <p><b>Weak ETags on PUT/DELETE If-Match requests</b>. RFC2616 explicitly
70   * prohibits the use of weak validators in non-GET requests, however, the
71   * HTTPbis working group says while the limitation for weak validators on ranged
72   * requests makes sense, weak ETag validation is useful on full non-GET
73   * requests; e.g., PUT with If-Match. This behavior is off by default, to err on
74   * the side of a conservative adherence to the existing standard, but you may
75   * want to {@link Builder#setWeakETagOnPutDeleteAllowed(boolean) enable it}.
76   *
77   * <p><b>Heuristic caching</b>. Per RFC2616, a cache may cache certain cache
78   * entries even if no explicit cache control headers are set by the origin.
79   * This behavior is off by default, but you may want to turn this on if you
80   * are working with an origin that doesn't set proper headers but where you
81   * still want to cache the responses. You will want to {@link
82   * CacheConfig#isHeuristicCachingEnabled()} enable heuristic caching},
83   * then specify either a {@link CacheConfig#getHeuristicDefaultLifetime()
84   * default freshness lifetime} and/or a {@link
85   * CacheConfig#getHeuristicCoefficient() fraction of the time since
86   * the resource was last modified}. See Sections
87   * <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.2.2">
88   * 13.2.2</a> and <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.2.4">
89   * 13.2.4</a> of the HTTP/1.1 RFC for more details on heuristic caching.</p>
90   *
91   * <p><b>Background validation</b>. The cache module supports the
92   * {@code stale-while-revalidate} directive of
93   * <a href="http://tools.ietf.org/html/rfc5861">RFC5861</a>, which allows
94   * certain cache entry revalidations to happen in the background. Asynchronous
95   * validation is enabled by default but it could be disabled by setting the number
96   * of re-validation workers to {@code 0} with {@link CacheConfig#getAsynchronousWorkers()}
97   * parameter</p>
98   */
99  public class CacheConfig implements Cloneable {
100 
101     /** Default setting for the maximum object size that will be
102      * cached, in bytes.
103      */
104     public final static int DEFAULT_MAX_OBJECT_SIZE_BYTES = 8192;
105 
106     /** Default setting for the maximum number of cache entries
107      * that will be retained.
108      */
109     public final static int DEFAULT_MAX_CACHE_ENTRIES = 1000;
110 
111     /** Default setting for the number of retries on a failed
112      * cache processChallenge
113      */
114     public final static int DEFAULT_MAX_UPDATE_RETRIES = 1;
115 
116     /** Default setting for 303 caching
117      */
118     public final static boolean DEFAULT_303_CACHING_ENABLED = false;
119 
120     /** Default setting to allow weak tags on PUT/DELETE methods
121      */
122     public final static boolean DEFAULT_WEAK_ETAG_ON_PUTDELETE_ALLOWED = false;
123 
124     /** Default setting for heuristic caching
125      */
126     public final static boolean DEFAULT_HEURISTIC_CACHING_ENABLED = false;
127 
128     /** Default coefficient used to heuristically determine freshness
129      * lifetime from the Last-Modified time of a cache entry.
130      */
131     public final static float DEFAULT_HEURISTIC_COEFFICIENT = 0.1f;
132 
133     /** Default lifetime to be assumed when we cannot calculate
134      * freshness heuristically.
135      */
136     public final static TimeValue DEFAULT_HEURISTIC_LIFETIME = TimeValue.ZERO_MILLISECONDS;
137 
138     /** Default number of worker threads to allow for background revalidations
139      * resulting from the stale-while-revalidate directive.
140      */
141     public static final int DEFAULT_ASYNCHRONOUS_WORKERS = 1;
142 
143     public static final CacheConfig DEFAULT = new Builder().build();
144 
145     private final long maxObjectSize;
146     private final int maxCacheEntries;
147     private final int maxUpdateRetries;
148     private final boolean allow303Caching;
149     private final boolean weakETagOnPutDeleteAllowed;
150     private final boolean heuristicCachingEnabled;
151     private final float heuristicCoefficient;
152     private final TimeValue heuristicDefaultLifetime;
153     private final boolean sharedCache;
154     private final boolean freshnessCheckEnabled;
155     private final int asynchronousWorkers;
156     private final boolean neverCacheHTTP10ResponsesWithQuery;
157 
158     CacheConfig(
159             final long maxObjectSize,
160             final int maxCacheEntries,
161             final int maxUpdateRetries,
162             final boolean allow303Caching,
163             final boolean weakETagOnPutDeleteAllowed,
164             final boolean heuristicCachingEnabled,
165             final float heuristicCoefficient,
166             final TimeValue heuristicDefaultLifetime,
167             final boolean sharedCache,
168             final boolean freshnessCheckEnabled,
169             final int asynchronousWorkers,
170             final boolean neverCacheHTTP10ResponsesWithQuery) {
171         super();
172         this.maxObjectSize = maxObjectSize;
173         this.maxCacheEntries = maxCacheEntries;
174         this.maxUpdateRetries = maxUpdateRetries;
175         this.allow303Caching = allow303Caching;
176         this.weakETagOnPutDeleteAllowed = weakETagOnPutDeleteAllowed;
177         this.heuristicCachingEnabled = heuristicCachingEnabled;
178         this.heuristicCoefficient = heuristicCoefficient;
179         this.heuristicDefaultLifetime = heuristicDefaultLifetime;
180         this.sharedCache = sharedCache;
181         this.freshnessCheckEnabled = freshnessCheckEnabled;
182         this.asynchronousWorkers = asynchronousWorkers;
183         this.neverCacheHTTP10ResponsesWithQuery = neverCacheHTTP10ResponsesWithQuery;
184     }
185 
186     /**
187      * Returns the current maximum response body size that will be cached.
188      * @return size in bytes
189      *
190      * @since 4.2
191      */
192     public long getMaxObjectSize() {
193         return maxObjectSize;
194     }
195 
196     /**
197      * Returns whether the cache will never cache HTTP 1.0 responses with a query string or not.
198      * @return {@code true} to not cache query string responses, {@code false} to cache if explicit cache headers are
199      * found
200      */
201     public boolean isNeverCacheHTTP10ResponsesWithQuery() {
202         return neverCacheHTTP10ResponsesWithQuery;
203     }
204 
205     /**
206      * Returns the maximum number of cache entries the cache will retain.
207      */
208     public int getMaxCacheEntries() {
209         return maxCacheEntries;
210     }
211 
212     /**
213      * Returns the number of times to retry a cache processChallenge on failure
214      */
215     public int getMaxUpdateRetries(){
216         return maxUpdateRetries;
217     }
218 
219     /**
220      * Returns whether 303 caching is enabled.
221      * @return {@code true} if it is enabled.
222      */
223     public boolean is303CachingEnabled() {
224         return allow303Caching;
225     }
226 
227     /**
228      * Returns whether weak etags is allowed with PUT/DELETE methods.
229      * @return {@code true} if it is allowed.
230      */
231     public boolean isWeakETagOnPutDeleteAllowed() {
232         return weakETagOnPutDeleteAllowed;
233     }
234 
235     /**
236      * Returns whether heuristic caching is enabled.
237      * @return {@code true} if it is enabled.
238      */
239     public boolean isHeuristicCachingEnabled() {
240         return heuristicCachingEnabled;
241     }
242 
243     /**
244      * Returns lifetime coefficient used in heuristic freshness caching.
245      */
246     public float getHeuristicCoefficient() {
247         return heuristicCoefficient;
248     }
249 
250     /**
251      * Get the default lifetime to be used if heuristic freshness calculation is
252      * not possible.
253      */
254     public TimeValue getHeuristicDefaultLifetime() {
255         return heuristicDefaultLifetime;
256     }
257 
258     /**
259      * Returns whether the cache will behave as a shared cache or not.
260      * @return {@code true} for a shared cache, {@code false} for a non-
261      * shared (private) cache
262      */
263     public boolean isSharedCache() {
264         return sharedCache;
265     }
266 
267     /**
268      * Returns whether the cache will perform an extra cache entry freshness check
269      * upon cache update in case of a cache miss
270      *
271      * @since 5.0
272      */
273     public boolean isFreshnessCheckEnabled() {
274         return freshnessCheckEnabled;
275     }
276 
277     /**
278      * Returns the maximum number of threads to allow for background
279      * revalidations due to the {@code stale-while-revalidate} directive. A
280      * value of 0 means background revalidations are disabled.
281      */
282     public int getAsynchronousWorkers() {
283         return asynchronousWorkers;
284     }
285 
286     @Override
287     protected CacheConfig clone() throws CloneNotSupportedException {
288         return (CacheConfig) super.clone();
289     }
290 
291     public static Builder custom() {
292         return new Builder();
293     }
294 
295     public static Builder copy(final CacheConfig config) {
296         Args.notNull(config, "Cache config");
297         return new Builder()
298             .setMaxObjectSize(config.getMaxObjectSize())
299             .setMaxCacheEntries(config.getMaxCacheEntries())
300             .setMaxUpdateRetries(config.getMaxUpdateRetries())
301             .setHeuristicCachingEnabled(config.isHeuristicCachingEnabled())
302             .setHeuristicCoefficient(config.getHeuristicCoefficient())
303             .setHeuristicDefaultLifetime(config.getHeuristicDefaultLifetime())
304             .setSharedCache(config.isSharedCache())
305             .setAsynchronousWorkers(config.getAsynchronousWorkers())
306             .setNeverCacheHTTP10ResponsesWithQueryString(config.isNeverCacheHTTP10ResponsesWithQuery());
307     }
308 
309 
310     public static class Builder {
311 
312         private long maxObjectSize;
313         private int maxCacheEntries;
314         private int maxUpdateRetries;
315         private boolean allow303Caching;
316         private boolean weakETagOnPutDeleteAllowed;
317         private boolean heuristicCachingEnabled;
318         private float heuristicCoefficient;
319         private TimeValue heuristicDefaultLifetime;
320         private boolean sharedCache;
321         private boolean freshnessCheckEnabled;
322         private int asynchronousWorkers;
323         private boolean neverCacheHTTP10ResponsesWithQuery;
324 
325         Builder() {
326             this.maxObjectSize = DEFAULT_MAX_OBJECT_SIZE_BYTES;
327             this.maxCacheEntries = DEFAULT_MAX_CACHE_ENTRIES;
328             this.maxUpdateRetries = DEFAULT_MAX_UPDATE_RETRIES;
329             this.allow303Caching = DEFAULT_303_CACHING_ENABLED;
330             this.weakETagOnPutDeleteAllowed = DEFAULT_WEAK_ETAG_ON_PUTDELETE_ALLOWED;
331             this.heuristicCachingEnabled = DEFAULT_HEURISTIC_CACHING_ENABLED;
332             this.heuristicCoefficient = DEFAULT_HEURISTIC_COEFFICIENT;
333             this.heuristicDefaultLifetime = DEFAULT_HEURISTIC_LIFETIME;
334             this.sharedCache = true;
335             this.freshnessCheckEnabled = true;
336             this.asynchronousWorkers = DEFAULT_ASYNCHRONOUS_WORKERS;
337         }
338 
339         /**
340          * Specifies the maximum response body size that will be eligible for caching.
341          * @param maxObjectSize size in bytes
342          */
343         public Builder setMaxObjectSize(final long maxObjectSize) {
344             this.maxObjectSize = maxObjectSize;
345             return this;
346         }
347 
348         /**
349          * Sets the maximum number of cache entries the cache will retain.
350          */
351         public Builder setMaxCacheEntries(final int maxCacheEntries) {
352             this.maxCacheEntries = maxCacheEntries;
353             return this;
354         }
355 
356         /**
357          * Sets the number of times to retry a cache processChallenge on failure
358          */
359         public Builder setMaxUpdateRetries(final int maxUpdateRetries) {
360             this.maxUpdateRetries = maxUpdateRetries;
361             return this;
362         }
363 
364         /**
365          * Enables or disables 303 caching.
366          * @param allow303Caching should be {@code true} to
367          *   permit 303 caching, {@code false} to disable it.
368          */
369         public Builder setAllow303Caching(final boolean allow303Caching) {
370             this.allow303Caching = allow303Caching;
371             return this;
372         }
373 
374         /**
375          * Allows or disallows weak etags to be used with PUT/DELETE If-Match requests.
376          * @param weakETagOnPutDeleteAllowed should be {@code true} to
377          *   permit weak etags, {@code false} to reject them.
378          */
379         public Builder setWeakETagOnPutDeleteAllowed(final boolean weakETagOnPutDeleteAllowed) {
380             this.weakETagOnPutDeleteAllowed = weakETagOnPutDeleteAllowed;
381             return this;
382         }
383 
384         /**
385          * Enables or disables heuristic caching.
386          * @param heuristicCachingEnabled should be {@code true} to
387          *   permit heuristic caching, {@code false} to enable it.
388          */
389         public Builder setHeuristicCachingEnabled(final boolean heuristicCachingEnabled) {
390             this.heuristicCachingEnabled = heuristicCachingEnabled;
391             return this;
392         }
393 
394         /**
395          * Sets coefficient to be used in heuristic freshness caching. This is
396          * interpreted as the fraction of the time between the {@code Last-Modified}
397          * and {@code Date} headers of a cached response during which the cached
398          * response will be considered heuristically fresh.
399          * @param heuristicCoefficient should be between {@code 0.0} and
400          *   {@code 1.0}.
401          */
402         public Builder setHeuristicCoefficient(final float heuristicCoefficient) {
403             this.heuristicCoefficient = heuristicCoefficient;
404             return this;
405         }
406 
407         /**
408          * Sets default lifetime to be used if heuristic freshness calculation
409          * is not possible. Explicit cache control directives on either the
410          * request or origin response will override this, as will the heuristic
411          * {@code Last-Modified} freshness calculation if it is available.
412          *
413          * @param heuristicDefaultLifetime is the number to consider a
414          *   cache-eligible response fresh in the absence of other information.
415          *   Set this to {@code 0} to disable this style of heuristic caching.
416          */
417         public Builder setHeuristicDefaultLifetime(final TimeValue heuristicDefaultLifetime) {
418             this.heuristicDefaultLifetime = heuristicDefaultLifetime;
419             return this;
420         }
421 
422         /**
423          * Sets whether the cache should behave as a shared cache or not.
424          * @param sharedCache true to behave as a shared cache, false to
425          * behave as a non-shared (private) cache. To have the cache
426          * behave like a browser cache, you want to set this to {@code false}.
427          */
428         public Builder setSharedCache(final boolean sharedCache) {
429             this.sharedCache = sharedCache;
430             return this;
431         }
432 
433         /**
434          * Sets the maximum number of threads to allow for background
435          * revalidations due to the {@code stale-while-revalidate} directive.
436          * @param asynchronousWorkers number of threads; a value of 0 disables background
437          * revalidations.
438          */
439         public Builder setAsynchronousWorkers(final int asynchronousWorkers) {
440             this.asynchronousWorkers = asynchronousWorkers;
441             return this;
442         }
443 
444         /**
445          * Sets whether the cache should never cache HTTP 1.0 responses with a query string or not.
446          * @param neverCacheHTTP10ResponsesWithQuery true to never cache responses with a query
447          * string, false to cache if explicit cache headers are found.  Set this to {@code true}
448          * to better emulate IE, which also never caches responses, regardless of what caching
449          * headers may be present.
450          */
451         public Builder setNeverCacheHTTP10ResponsesWithQueryString(
452                 final boolean neverCacheHTTP10ResponsesWithQuery) {
453             this.neverCacheHTTP10ResponsesWithQuery = neverCacheHTTP10ResponsesWithQuery;
454             return this;
455         }
456 
457         public Builder setFreshnessCheckEnabled(final boolean freshnessCheckEnabled) {
458             this.freshnessCheckEnabled = freshnessCheckEnabled;
459             return this;
460         }
461 
462         public CacheConfig build() {
463             return new CacheConfig(
464                     maxObjectSize,
465                     maxCacheEntries,
466                     maxUpdateRetries,
467                     allow303Caching,
468                     weakETagOnPutDeleteAllowed,
469                     heuristicCachingEnabled,
470                     heuristicCoefficient,
471                     heuristicDefaultLifetime,
472                     sharedCache,
473                     freshnessCheckEnabled,
474                     asynchronousWorkers,
475                     neverCacheHTTP10ResponsesWithQuery);
476         }
477 
478     }
479 
480     @Override
481     public String toString() {
482         final StringBuilder builder = new StringBuilder();
483         builder.append("[maxObjectSize=").append(this.maxObjectSize)
484                 .append(", maxCacheEntries=").append(this.maxCacheEntries)
485                 .append(", maxUpdateRetries=").append(this.maxUpdateRetries)
486                 .append(", 303CachingEnabled=").append(this.allow303Caching)
487                 .append(", weakETagOnPutDeleteAllowed=").append(this.weakETagOnPutDeleteAllowed)
488                 .append(", heuristicCachingEnabled=").append(this.heuristicCachingEnabled)
489                 .append(", heuristicCoefficient=").append(this.heuristicCoefficient)
490                 .append(", heuristicDefaultLifetime=").append(this.heuristicDefaultLifetime)
491                 .append(", sharedCache=").append(this.sharedCache)
492                 .append(", freshnessCheckEnabled=").append(this.freshnessCheckEnabled)
493                 .append(", asynchronousWorkers=").append(this.asynchronousWorkers)
494                 .append(", neverCacheHTTP10ResponsesWithQuery=").append(this.neverCacheHTTP10ResponsesWithQuery)
495                 .append("]");
496         return builder.toString();
497     }
498 
499 }