1   
2   
3   
4   
5   
6   
7   
8   
9   
10  
11  
12  
13  
14  
15  
16  
17  
18  
19  
20  
21  
22  
23  
24  
25  
26  
27  package org.apache.hc.client5.http.impl.cache;
28  
29  import java.net.URI;
30  import java.net.URISyntaxException;
31  import java.nio.charset.StandardCharsets;
32  import java.util.ArrayList;
33  import java.util.Collection;
34  import java.util.Iterator;
35  import java.util.List;
36  import java.util.Locale;
37  import java.util.Spliterator;
38  import java.util.Spliterators;
39  import java.util.concurrent.atomic.AtomicBoolean;
40  import java.util.function.Consumer;
41  import java.util.stream.StreamSupport;
42  
43  import org.apache.hc.client5.http.cache.HttpCacheEntry;
44  import org.apache.hc.core5.annotation.Contract;
45  import org.apache.hc.core5.annotation.Internal;
46  import org.apache.hc.core5.annotation.ThreadingBehavior;
47  import org.apache.hc.core5.function.Resolver;
48  import org.apache.hc.core5.http.Header;
49  import org.apache.hc.core5.http.HeaderElement;
50  import org.apache.hc.core5.http.HttpHeaders;
51  import org.apache.hc.core5.http.HttpHost;
52  import org.apache.hc.core5.http.HttpRequest;
53  import org.apache.hc.core5.http.MessageHeaders;
54  import org.apache.hc.core5.http.NameValuePair;
55  import org.apache.hc.core5.http.URIScheme;
56  import org.apache.hc.core5.http.message.BasicHeaderElementIterator;
57  import org.apache.hc.core5.http.message.BasicHeaderValueFormatter;
58  import org.apache.hc.core5.http.message.BasicNameValuePair;
59  import org.apache.hc.core5.http.message.MessageSupport;
60  import org.apache.hc.core5.net.PercentCodec;
61  import org.apache.hc.core5.net.URIAuthority;
62  import org.apache.hc.core5.net.URIBuilder;
63  import org.apache.hc.core5.util.Args;
64  import org.apache.hc.core5.util.CharArrayBuffer;
65  import org.apache.hc.core5.util.TextUtils;
66  
67  
68  
69  
70  @Contract(threading = ThreadingBehavior.STATELESS)
71  public class CacheKeyGenerator implements Resolver<URI, String> {
72  
73      
74  
75  
76      public static final CacheKeyGenerator INSTANCE = new CacheKeyGenerator();
77  
78      @Override
79      public String resolve(final URI uri) {
80          return generateKey(uri);
81      }
82  
83      
84  
85  
86  
87  
88  
89  
90  
91  
92  
93  
94  
95  
96      @Internal
97      public static String getRequestUri(final HttpHost target, final HttpRequest request) {
98          Args.notNull(target, "Target");
99          Args.notNull(request, "HTTP request");
100         final StringBuilder buf = new StringBuilder();
101         final URIAuthority authority = request.getAuthority();
102         if (authority != null) {
103             final String scheme = request.getScheme();
104             buf.append(scheme != null ? scheme : URIScheme.HTTP.id).append("://");
105             buf.append(authority.getHostName());
106             if (authority.getPort() >= 0) {
107                 buf.append(":").append(authority.getPort());
108             }
109         } else {
110             buf.append(target.getSchemeName()).append("://");
111             buf.append(target.getHostName());
112             if (target.getPort() >= 0) {
113                 buf.append(":").append(target.getPort());
114             }
115         }
116         final String path = request.getPath();
117         if (path == null) {
118             buf.append("/");
119         } else {
120             if (buf.length() > 0 && !path.startsWith("/")) {
121                 buf.append("/");
122             }
123             buf.append(path);
124         }
125         return buf.toString();
126     }
127 
128     
129 
130 
131 
132 
133     @Internal
134     public static URI normalize(final URI requestUri) throws URISyntaxException {
135         Args.notNull(requestUri, "URI");
136         final URIBuilder builder = new URIBuilder(requestUri);
137         if (builder.getHost() != null) {
138             if (builder.getScheme() == null) {
139                 builder.setScheme(URIScheme.HTTP.id);
140             }
141             if (builder.getPort() <= -1) {
142                 if (URIScheme.HTTP.same(builder.getScheme())) {
143                     builder.setPort(80);
144                 } else if (URIScheme.HTTPS.same(builder.getScheme())) {
145                     builder.setPort(443);
146                 }
147             }
148         }
149         builder.setFragment(null);
150         return builder.optimize().build();
151     }
152 
153     
154 
155 
156     @Internal
157     public static URI normalize(final String requestUri) {
158         if (requestUri == null) {
159             return null;
160         }
161         try {
162             return CacheKeyGenerator.normalize(new URI(requestUri));
163         } catch (final URISyntaxException ex) {
164             return null;
165         }
166     }
167 
168     
169 
170 
171 
172 
173 
174 
175 
176     public String generateKey(final URI requestUri) {
177         try {
178             final URI normalizeRequestUri = normalize(requestUri);
179             return normalizeRequestUri.toASCIIString();
180         } catch (final URISyntaxException ex) {
181             return requestUri.toASCIIString();
182         }
183     }
184 
185     
186 
187 
188 
189 
190 
191 
192 
193     public String generateKey(final HttpHost host, final HttpRequest request) {
194         final String s = getRequestUri(host, request);
195         try {
196             return generateKey(new URI(s));
197         } catch (final URISyntaxException ex) {
198             return s;
199         }
200     }
201 
202     
203 
204 
205 
206 
207     public static List<String> variantNames(final MessageHeaders message) {
208         if (message == null) {
209             return null;
210         }
211         final List<String> names = new ArrayList<>();
212         for (final Iterator<Header> it = message.headerIterator(HttpHeaders.VARY); it.hasNext(); ) {
213             final Header header = it.next();
214             MessageSupport.parseTokens(header, names::add);
215         }
216         return names;
217     }
218 
219     @Internal
220     public static void normalizeElements(final MessageHeaders message, final String headerName, final Consumer<String> consumer) {
221         
222         if (headerName.equalsIgnoreCase(HttpHeaders.USER_AGENT)) {
223             final Header header = message.getFirstHeader(headerName);
224             if (header != null) {
225                 consumer.accept(header.getValue().toLowerCase(Locale.ROOT));
226             }
227         } else {
228             normalizeElements(message.headerIterator(headerName), consumer);
229         }
230     }
231 
232     @Internal
233     public static void normalizeElements(final Iterator<Header> iterator, final Consumer<String> consumer) {
234         final Iterator<HeaderElement> it = new BasicHeaderElementIterator(iterator);
235         StreamSupport.stream(Spliterators.spliteratorUnknownSize(it, Spliterator.NONNULL), false)
236                 .filter(e -> !TextUtils.isBlank(e.getName()))
237                 .map(e -> {
238                     if (e.getValue() == null && e.getParameterCount() == 0) {
239                         return e.getName().toLowerCase(Locale.ROOT);
240                     }
241                     final CharArrayBuffer buf = new CharArrayBuffer(1024);
242                     BasicHeaderValueFormatter.INSTANCE.formatNameValuePair(
243                             buf,
244                             new BasicNameValuePair(
245                                 e.getName().toLowerCase(Locale.ROOT),
246                                 !TextUtils.isBlank(e.getValue()) ? e.getValue() : null),
247                             false);
248                     if (e.getParameterCount() > 0) {
249                         for (final NameValuePair nvp : e.getParameters()) {
250                             if (!TextUtils.isBlank(nvp.getName())) {
251                                 buf.append(';');
252                                 BasicHeaderValueFormatter.INSTANCE.formatNameValuePair(
253                                         buf,
254                                         new BasicNameValuePair(
255                                                 nvp.getName().toLowerCase(Locale.ROOT),
256                                                 !TextUtils.isBlank(nvp.getValue()) ? nvp.getValue() : null),
257                                         false);
258                             }
259                         }
260                     }
261                     return buf.toString();
262                 })
263                 .sorted()
264                 .distinct()
265                 .forEach(consumer);
266     }
267 
268     
269 
270 
271 
272 
273 
274 
275 
276     public String generateVariantKey(final HttpRequest request, final Collection<String> variantNames) {
277         Args.notNull(variantNames, "Variant names");
278         final StringBuilder buf = new StringBuilder("{");
279         final AtomicBoolean firstHeader = new AtomicBoolean();
280         variantNames.stream()
281                 .map(h -> h.toLowerCase(Locale.ROOT))
282                 .sorted()
283                 .distinct()
284                 .forEach(h -> {
285                     if (!firstHeader.compareAndSet(false, true)) {
286                         buf.append("&");
287                     }
288                     buf.append(PercentCodec.encode(h, StandardCharsets.UTF_8)).append("=");
289                     final AtomicBoolean firstToken = new AtomicBoolean();
290                     normalizeElements(request, h, t -> {
291                         if (!firstToken.compareAndSet(false, true)) {
292                             buf.append(",");
293                         }
294                         buf.append(PercentCodec.encode(t, StandardCharsets.UTF_8));
295                     });
296                 });
297         buf.append("}");
298         return buf.toString();
299     }
300 
301     
302 
303 
304 
305 
306 
307 
308 
309     public String generateVariantKey(final HttpRequest request, final HttpCacheEntry entry) {
310         if (entry.containsHeader(HttpHeaders.VARY)) {
311             final List<String> variantNames = variantNames(entry);
312             return generateVariantKey(request, variantNames);
313         }
314         return null;
315     }
316 
317     
318 
319 
320 
321 
322 
323 
324 
325 
326 
327 
328 
329     @Deprecated
330     public String generateKey(final HttpHost host, final HttpRequest request, final HttpCacheEntry entry) {
331         final String rootKey = generateKey(host, request);
332         final List<String> variantNames = variantNames(entry);
333         if (variantNames.isEmpty()) {
334             return rootKey;
335         }
336         return generateVariantKey(request, variantNames) + rootKey;
337     }
338 
339 }