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.io.UnsupportedEncodingException;
30  import java.net.URI;
31  import java.net.URISyntaxException;
32  import java.net.URLEncoder;
33  import java.util.ArrayList;
34  import java.util.Collections;
35  import java.util.List;
36  
37  import org.apache.http.Consts;
38  import org.apache.http.Header;
39  import org.apache.http.HeaderElement;
40  import org.apache.http.HttpHost;
41  import org.apache.http.HttpRequest;
42  import org.apache.http.annotation.Contract;
43  import org.apache.http.annotation.ThreadingBehavior;
44  import org.apache.http.client.cache.HeaderConstants;
45  import org.apache.http.client.cache.HttpCacheEntry;
46  import org.apache.http.client.methods.HttpUriRequest;
47  import org.apache.http.client.utils.URIBuilder;
48  import org.apache.http.client.utils.URIUtils;
49  import org.apache.http.client.utils.URLEncodedUtils;
50  import org.apache.http.util.Args;
51  
52  /**
53   * @since 4.1
54   */
55  @Contract(threading = ThreadingBehavior.IMMUTABLE)
56  class CacheKeyGenerator {
57  
58      private static final URI BASE_URI = URI.create("http://example.com/");
59  
60      static URIBuilder getRequestUriBuilder(final HttpRequest request) throws URISyntaxException {
61          if (request instanceof HttpUriRequest) {
62              final URI uri = ((HttpUriRequest) request).getURI();
63              if (uri != null) {
64                  return new URIBuilder(uri);
65              }
66          }
67          return new URIBuilder(request.getRequestLine().getUri());
68      }
69  
70      static URI getRequestUri(final HttpRequest request, final HttpHost target) throws URISyntaxException {
71          Args.notNull(request, "HTTP request");
72          Args.notNull(target, "Target");
73          final URIBuilder uriBuilder = getRequestUriBuilder(request);
74  
75          // Decode path segments to preserve original behavior for backward compatibility
76          final String path = uriBuilder.getPath();
77          if (path != null) {
78              uriBuilder.setPathSegments(URLEncodedUtils.parsePathSegments(path));
79          }
80  
81          if (!uriBuilder.isAbsolute()) {
82              uriBuilder.setScheme(target.getSchemeName());
83              uriBuilder.setHost(target.getHostName());
84              uriBuilder.setPort(target.getPort());
85          }
86          return uriBuilder.build();
87      }
88  
89      static URI normalize(final URI requestUri) throws URISyntaxException {
90          Args.notNull(requestUri, "URI");
91          final URIBuilder builder = new URIBuilder(requestUri.isAbsolute() ? URIUtils.resolve(BASE_URI, requestUri) : requestUri) ;
92          if (builder.getHost() != null) {
93              if (builder.getScheme() == null) {
94                  builder.setScheme("http");
95              }
96              if (builder.getPort() <= -1) {
97                  if ("http".equalsIgnoreCase(builder.getScheme())) {
98                      builder.setPort(80);
99                  } else if ("https".equalsIgnoreCase(builder.getScheme())) {
100                     builder.setPort(443);
101                 }
102             }
103         }
104         builder.setFragment(null);
105         return builder.build();
106     }
107 
108     /**
109      * For a given {@link HttpHost} and {@link HttpRequest} get a URI from the
110      * pair that I can use as an identifier KEY into my HttpCache
111      *
112      * @param host The host for this request
113      * @param req the {@link HttpRequest}
114      * @return String the extracted URI
115      */
116     public String getURI(final HttpHost host, final HttpRequest req) {
117         try {
118             final URI uri = normalize(getRequestUri(req, host));
119             return uri.toASCIIString();
120         } catch (final URISyntaxException ex) {
121             return req.getRequestLine().getUri();
122         }
123     }
124 
125     public String canonicalizeUri(final String uri) {
126         try {
127             final URI normalized = normalize(URIUtils.resolve(BASE_URI, uri));
128             return normalized.toASCIIString();
129         } catch (final URISyntaxException ex) {
130             return uri;
131         }
132     }
133 
134     protected String getFullHeaderValue(final Header[] headers) {
135         if (headers == null) {
136             return "";
137         }
138 
139         final StringBuilder buf = new StringBuilder("");
140         boolean first = true;
141         for (final Header hdr : headers) {
142             if (!first) {
143                 buf.append(", ");
144             }
145             buf.append(hdr.getValue().trim());
146             first = false;
147 
148         }
149         return buf.toString();
150     }
151 
152     /**
153      * For a given {@link HttpHost} and {@link HttpRequest} if the request has a
154      * VARY header - I need to get an additional URI from the pair of host and
155      * request so that I can also store the variant into my HttpCache.
156      *
157      * @param host The host for this request
158      * @param req the {@link HttpRequest}
159      * @param entry the parent entry used to track the variants
160      * @return String the extracted variant URI
161      */
162     public String getVariantURI(final HttpHost host, final HttpRequest req, final HttpCacheEntry entry) {
163         if (!entry.hasVariants()) {
164             return getURI(host, req);
165         }
166         return getVariantKey(req, entry) + getURI(host, req);
167     }
168 
169     /**
170      * Compute a "variant key" from the headers of a given request that are
171      * covered by the Vary header of a given cache entry. Any request whose
172      * varying headers match those of this request should have the same
173      * variant key.
174      * @param req originating request
175      * @param entry cache entry in question that has variants
176      * @return a {@code String} variant key
177      */
178     public String getVariantKey(final HttpRequest req, final HttpCacheEntry entry) {
179         final List<String> variantHeaderNames = new ArrayList<String>();
180         for (final Header varyHdr : entry.getHeaders(HeaderConstants.VARY)) {
181             for (final HeaderElement elt : varyHdr.getElements()) {
182                 variantHeaderNames.add(elt.getName());
183             }
184         }
185         Collections.sort(variantHeaderNames);
186 
187         final StringBuilder buf;
188         try {
189             buf = new StringBuilder("{");
190             boolean first = true;
191             for (final String headerName : variantHeaderNames) {
192                 if (!first) {
193                     buf.append("&");
194                 }
195                 buf.append(URLEncoder.encode(headerName, Consts.UTF_8.name()));
196                 buf.append("=");
197                 buf.append(URLEncoder.encode(getFullHeaderValue(req.getHeaders(headerName)),
198                         Consts.UTF_8.name()));
199                 first = false;
200             }
201             buf.append("}");
202         } catch (final UnsupportedEncodingException uee) {
203             throw new RuntimeException("couldn't encode to UTF-8", uee);
204         }
205         return buf.toString();
206     }
207 
208 }