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.MalformedURLException;
31  import java.net.URI;
32  import java.net.URL;
33  import java.net.URLEncoder;
34  import java.util.ArrayList;
35  import java.util.Collections;
36  import java.util.List;
37  
38  import org.apache.http.Consts;
39  import org.apache.http.Header;
40  import org.apache.http.HeaderElement;
41  import org.apache.http.HttpHost;
42  import org.apache.http.HttpRequest;
43  import org.apache.http.annotation.Contract;
44  import org.apache.http.annotation.ThreadingBehavior;
45  import org.apache.http.client.cache.HeaderConstants;
46  import org.apache.http.client.cache.HttpCacheEntry;
47  import org.apache.http.client.utils.URIUtils;
48  
49  /**
50   * @since 4.1
51   */
52  @Contract(threading = ThreadingBehavior.IMMUTABLE)
53  class CacheKeyGenerator {
54  
55      private static final URI BASE_URI = URI.create("http://example.com/");
56  
57      /**
58       * For a given {@link HttpHost} and {@link HttpRequest} get a URI from the
59       * pair that I can use as an identifier KEY into my HttpCache
60       *
61       * @param host The host for this request
62       * @param req the {@link HttpRequest}
63       * @return String the extracted URI
64       */
65      public String getURI(final HttpHost host, final HttpRequest req) {
66          if (isRelativeRequest(req)) {
67              return canonicalizeUri(String.format("%s%s", host.toString(), req.getRequestLine().getUri()));
68          }
69          return canonicalizeUri(req.getRequestLine().getUri());
70      }
71  
72      public String canonicalizeUri(final String uri) {
73          try {
74              final URI normalized = URIUtils.resolve(BASE_URI, uri);
75              final URL u = new URL(normalized.toASCIIString());
76              final String protocol = u.getProtocol();
77              final String hostname = u.getHost();
78              final int port = canonicalizePort(u.getPort(), protocol);
79              final String path = u.getPath();
80              final String query = u.getQuery();
81              final String file = (query != null) ? (path + "?" + query) : path;
82              final URL out = new URL(protocol, hostname, port, file);
83              return out.toString();
84          } catch (final IllegalArgumentException e) {
85              return uri;
86          } catch (final MalformedURLException e) {
87              return uri;
88          }
89      }
90  
91      private int canonicalizePort(final int port, final String protocol) {
92          if (port == -1 && "http".equalsIgnoreCase(protocol)) {
93              return 80;
94          } else if (port == -1 && "https".equalsIgnoreCase(protocol)) {
95              return 443;
96          }
97          return port;
98      }
99  
100     private boolean isRelativeRequest(final HttpRequest req) {
101         final String requestUri = req.getRequestLine().getUri();
102         return ("*".equals(requestUri) || requestUri.startsWith("/"));
103     }
104 
105     protected String getFullHeaderValue(final Header[] headers) {
106         if (headers == null) {
107             return "";
108         }
109 
110         final StringBuilder buf = new StringBuilder("");
111         boolean first = true;
112         for (final Header hdr : headers) {
113             if (!first) {
114                 buf.append(", ");
115             }
116             buf.append(hdr.getValue().trim());
117             first = false;
118 
119         }
120         return buf.toString();
121     }
122 
123     /**
124      * For a given {@link HttpHost} and {@link HttpRequest} if the request has a
125      * VARY header - I need to get an additional URI from the pair of host and
126      * request so that I can also store the variant into my HttpCache.
127      *
128      * @param host The host for this request
129      * @param req the {@link HttpRequest}
130      * @param entry the parent entry used to track the variants
131      * @return String the extracted variant URI
132      */
133     public String getVariantURI(final HttpHost host, final HttpRequest req, final HttpCacheEntry entry) {
134         if (!entry.hasVariants()) {
135             return getURI(host, req);
136         }
137         return getVariantKey(req, entry) + getURI(host, req);
138     }
139 
140     /**
141      * Compute a "variant key" from the headers of a given request that are
142      * covered by the Vary header of a given cache entry. Any request whose
143      * varying headers match those of this request should have the same
144      * variant key.
145      * @param req originating request
146      * @param entry cache entry in question that has variants
147      * @return a {@code String} variant key
148      */
149     public String getVariantKey(final HttpRequest req, final HttpCacheEntry entry) {
150         final List<String> variantHeaderNames = new ArrayList<String>();
151         for (final Header varyHdr : entry.getHeaders(HeaderConstants.VARY)) {
152             for (final HeaderElement elt : varyHdr.getElements()) {
153                 variantHeaderNames.add(elt.getName());
154             }
155         }
156         Collections.sort(variantHeaderNames);
157 
158         final StringBuilder buf;
159         try {
160             buf = new StringBuilder("{");
161             boolean first = true;
162             for (final String headerName : variantHeaderNames) {
163                 if (!first) {
164                     buf.append("&");
165                 }
166                 buf.append(URLEncoder.encode(headerName, Consts.UTF_8.name()));
167                 buf.append("=");
168                 buf.append(URLEncoder.encode(getFullHeaderValue(req.getHeaders(headerName)),
169                         Consts.UTF_8.name()));
170                 first = false;
171             }
172             buf.append("}");
173         } catch (final UnsupportedEncodingException uee) {
174             throw new RuntimeException("couldn't encode to UTF-8", uee);
175         }
176         return buf.toString();
177     }
178 
179 }