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.HttpEntity;
33  import org.apache.http.HttpResponse;
34  import org.apache.http.HttpStatus;
35  import org.apache.http.HttpVersion;
36  import org.apache.http.annotation.Contract;
37  import org.apache.http.annotation.ThreadingBehavior;
38  import org.apache.http.client.cache.HeaderConstants;
39  import org.apache.http.client.cache.HttpCacheEntry;
40  import org.apache.http.client.methods.CloseableHttpResponse;
41  import org.apache.http.client.methods.HttpRequestWrapper;
42  import org.apache.http.client.utils.DateUtils;
43  import org.apache.http.message.BasicHeader;
44  import org.apache.http.message.BasicHttpResponse;
45  import org.apache.http.protocol.HTTP;
46  
47  /**
48   * Rebuilds an {@link HttpResponse} from a {@link net.sf.ehcache.CacheEntry}
49   *
50   * @since 4.1
51   */
52  @Contract(threading = ThreadingBehavior.IMMUTABLE_CONDITIONAL)
53  class CachedHttpResponseGenerator {
54  
55      private final CacheValidityPolicy validityStrategy;
56  
57      CachedHttpResponseGenerator(final CacheValidityPolicy validityStrategy) {
58          super();
59          this.validityStrategy = validityStrategy;
60      }
61  
62      CachedHttpResponseGenerator() {
63          this(new CacheValidityPolicy());
64      }
65  
66      /**
67       * If I was able to use a {@link CacheEntity} to response to the {@link org.apache.http.HttpRequest} then
68       * generate an {@link HttpResponse} based on the cache entry.
69       * @param request {@link HttpRequestWrapper} to generate the response for
70       * @param entry {@link CacheEntity} to transform into an {@link HttpResponse}
71       * @return {@link HttpResponse} that was constructed
72       */
73      CloseableHttpResponse generateResponse(final HttpRequestWrapper request, final HttpCacheEntry entry) {
74          final Date now = new Date();
75          final HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, entry
76                  .getStatusCode(), entry.getReasonPhrase());
77  
78          response.setHeaders(entry.getAllHeaders());
79  
80          if (responseShouldContainEntity(request, entry)) {
81              final HttpEntity entity = new CacheEntity(entry);
82              addMissingContentLengthHeader(response, entity);
83              response.setEntity(entity);
84          }
85  
86          final long age = this.validityStrategy.getCurrentAgeSecs(entry, now);
87          if (age > 0) {
88              if (age >= Integer.MAX_VALUE) {
89                  response.setHeader(HeaderConstants.AGE, "2147483648");
90              } else {
91                  response.setHeader(HeaderConstants.AGE, "" + ((int) age));
92              }
93          }
94  
95          return Proxies.enhanceResponse(response);
96      }
97  
98      /**
99       * Generate a 304 - Not Modified response from a {@link CacheEntity}.  This should be
100      * used to respond to conditional requests, when the entry exists or has been re-validated.
101      */
102     CloseableHttpResponse generateNotModifiedResponse(final HttpCacheEntry entry) {
103 
104         final HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1,
105                 HttpStatus.SC_NOT_MODIFIED, "Not Modified");
106 
107         // The response MUST include the following headers
108         //  (http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html)
109 
110         // - Date, unless its omission is required by section 14.8.1
111         Header dateHeader = entry.getFirstHeader(HTTP.DATE_HEADER);
112         if (dateHeader == null) {
113              dateHeader = new BasicHeader(HTTP.DATE_HEADER, DateUtils.formatDate(new Date()));
114         }
115         response.addHeader(dateHeader);
116 
117         // - ETag and/or Content-Location, if the header would have been sent
118         //   in a 200 response to the same request
119         final Header etagHeader = entry.getFirstHeader(HeaderConstants.ETAG);
120         if (etagHeader != null) {
121             response.addHeader(etagHeader);
122         }
123 
124         final Header contentLocationHeader = entry.getFirstHeader("Content-Location");
125         if (contentLocationHeader != null) {
126             response.addHeader(contentLocationHeader);
127         }
128 
129         // - Expires, Cache-Control, and/or Vary, if the field-value might
130         //   differ from that sent in any previous response for the same
131         //   variant
132         final Header expiresHeader = entry.getFirstHeader(HeaderConstants.EXPIRES);
133         if (expiresHeader != null) {
134             response.addHeader(expiresHeader);
135         }
136 
137         final Header cacheControlHeader = entry.getFirstHeader(HeaderConstants.CACHE_CONTROL);
138         if (cacheControlHeader != null) {
139             response.addHeader(cacheControlHeader);
140         }
141 
142         final Header varyHeader = entry.getFirstHeader(HeaderConstants.VARY);
143         if (varyHeader != null) {
144             response.addHeader(varyHeader);
145         }
146 
147         return Proxies.enhanceResponse(response);
148     }
149 
150     private void addMissingContentLengthHeader(final HttpResponse response, final HttpEntity entity) {
151         if (transferEncodingIsPresent(response)) {
152             return;
153         }
154         // Some well known proxies respond with Content-Length=0, when returning 304. For robustness, always
155         // use the cached entity's content length, as modern browsers do.
156         final Header contentLength = new BasicHeader(HTTP.CONTENT_LEN, Long.toString(entity.getContentLength()));
157         response.setHeader(contentLength);
158     }
159 
160     private boolean transferEncodingIsPresent(final HttpResponse response) {
161         final Header hdr = response.getFirstHeader(HTTP.TRANSFER_ENCODING);
162         return hdr != null;
163     }
164 
165     private boolean responseShouldContainEntity(final HttpRequestWrapper request, final HttpCacheEntry cacheEntry) {
166         return request.getRequestLine().getMethod().equals(HeaderConstants.GET_METHOD) &&
167                cacheEntry.getResource() != null;
168     }
169 
170 }