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 java.time.Instant;
30  import java.util.concurrent.atomic.AtomicLong;
31  
32  import org.apache.hc.client5.http.async.methods.SimpleHttpResponse;
33  import org.apache.hc.client5.http.cache.HttpCacheEntry;
34  import org.apache.hc.client5.http.cache.ResourceIOException;
35  import org.apache.hc.core5.http.EntityDetails;
36  import org.apache.hc.core5.http.Header;
37  import org.apache.hc.core5.http.HttpHeaders;
38  import org.apache.hc.core5.http.HttpRequest;
39  import org.apache.hc.core5.http.HttpResponse;
40  import org.apache.hc.core5.http.HttpStatus;
41  import org.apache.hc.core5.http.Method;
42  
43  public class CachingExecBase {
44  
45      final AtomicLong cacheHits = new AtomicLong();
46      final AtomicLong cacheMisses = new AtomicLong();
47      final AtomicLong cacheUpdates = new AtomicLong();
48  
49      final ResponseCachingPolicy responseCachingPolicy;
50      final CacheValidityPolicy validityPolicy;
51      final CachedHttpResponseGenerator responseGenerator;
52      final CacheableRequestPolicy cacheableRequestPolicy;
53      final CachedResponseSuitabilityChecker suitabilityChecker;
54      final CacheConfig cacheConfig;
55  
56      CachingExecBase(
57              final CacheValidityPolicy validityPolicy,
58              final ResponseCachingPolicy responseCachingPolicy,
59              final CachedHttpResponseGenerator responseGenerator,
60              final CacheableRequestPolicy cacheableRequestPolicy,
61              final CachedResponseSuitabilityChecker suitabilityChecker,
62              final CacheConfig config) {
63          this.responseCachingPolicy = responseCachingPolicy;
64          this.validityPolicy = validityPolicy;
65          this.responseGenerator = responseGenerator;
66          this.cacheableRequestPolicy = cacheableRequestPolicy;
67          this.suitabilityChecker = suitabilityChecker;
68          this.cacheConfig = config != null ? config : CacheConfig.DEFAULT;
69      }
70  
71      CachingExecBase(final CacheConfig config) {
72          super();
73          this.cacheConfig = config != null ? config : CacheConfig.DEFAULT;
74          this.validityPolicy = new CacheValidityPolicy(config);
75          this.responseGenerator = new CachedHttpResponseGenerator(this.validityPolicy);
76          this.cacheableRequestPolicy = new CacheableRequestPolicy();
77          this.suitabilityChecker = new CachedResponseSuitabilityChecker(this.validityPolicy, this.cacheConfig);
78          this.responseCachingPolicy = new ResponseCachingPolicy(
79                  this.cacheConfig.isSharedCache(),
80                  this.cacheConfig.isNeverCacheHTTP10ResponsesWithQuery(),
81                  this.cacheConfig.isNeverCacheHTTP11ResponsesWithQuery());
82      }
83  
84      /**
85       * Reports the number of times that the cache successfully responded
86       * to an {@link HttpRequest} without contacting the origin server.
87       * @return the number of cache hits
88       */
89      public long getCacheHits() {
90          return cacheHits.get();
91      }
92  
93      /**
94       * Reports the number of times that the cache contacted the origin
95       * server because it had no appropriate response cached.
96       * @return the number of cache misses
97       */
98      public long getCacheMisses() {
99          return cacheMisses.get();
100     }
101 
102     /**
103      * Reports the number of times that the cache was able to satisfy
104      * a response by revalidating an existing but stale cache entry.
105      * @return the number of cache revalidations
106      */
107     public long getCacheUpdates() {
108         return cacheUpdates.get();
109     }
110 
111     SimpleHttpResponse generateCachedResponse(
112             final HttpRequest request,
113             final HttpCacheEntry entry,
114             final Instant now) throws ResourceIOException {
115         if (shouldSendNotModifiedResponse(request, entry, now)) {
116             return responseGenerator.generateNotModifiedResponse(entry);
117         } else {
118             return responseGenerator.generateResponse(request, entry);
119         }
120     }
121 
122     SimpleHttpResponse generateGatewayTimeout() {
123         return SimpleHttpResponse.create(HttpStatus.SC_GATEWAY_TIMEOUT, "Gateway Timeout");
124     }
125 
126     Instant getCurrentDate() {
127         return Instant.now();
128     }
129 
130     boolean clientRequestsOurOptions(final HttpRequest request) {
131         if (!Method.OPTIONS.isSame(request.getMethod())) {
132             return false;
133         }
134 
135         if (!"*".equals(request.getRequestUri())) {
136             return false;
137         }
138 
139         final Header h = request.getFirstHeader(HttpHeaders.MAX_FORWARDS);
140         return "0".equals(h != null ? h.getValue() : null);
141     }
142 
143     boolean shouldSendNotModifiedResponse(final HttpRequest request, final HttpCacheEntry responseEntry, final Instant now) {
144         return suitabilityChecker.isConditional(request)
145                 && suitabilityChecker.allConditionalsMatch(request, responseEntry, now);
146     }
147 
148     boolean staleIfErrorAppliesTo(final int statusCode) {
149         return statusCode == HttpStatus.SC_INTERNAL_SERVER_ERROR
150                 || statusCode == HttpStatus.SC_BAD_GATEWAY
151                 || statusCode == HttpStatus.SC_SERVICE_UNAVAILABLE
152                 || statusCode == HttpStatus.SC_GATEWAY_TIMEOUT;
153     }
154 
155     /**
156      * For 304 Not modified responses, adds a "Last-Modified" header with the
157      * value of the "If-Modified-Since" header passed in the request. This
158      * header is required to be able to reuse match the cache entry for
159      * subsequent requests but as defined in http specifications it is not
160      * included in 304 responses by backend servers. This header will not be
161      * included in the resulting response.
162      */
163     void storeRequestIfModifiedSinceFor304Response(final HttpRequest request, final HttpResponse backendResponse) {
164         if (backendResponse.getCode() == HttpStatus.SC_NOT_MODIFIED) {
165             final Header h = request.getFirstHeader(HttpHeaders.IF_MODIFIED_SINCE);
166             if (h != null) {
167                 backendResponse.addHeader(HttpHeaders.LAST_MODIFIED, h.getValue());
168             }
169         }
170     }
171 
172     boolean isResponseTooBig(final EntityDetails entityDetails) {
173         if (entityDetails == null) {
174             return false;
175         }
176         final long contentLength = entityDetails.getContentLength();
177         if (contentLength == -1) {
178             return false;
179         }
180         final long maxObjectSize = cacheConfig.getMaxObjectSize();
181         return contentLength > maxObjectSize;
182     }
183 
184 }