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