1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.jclouds.http;
20
21 import static com.google.common.base.Preconditions.checkArgument;
22 import static com.google.common.base.Preconditions.checkState;
23 import static com.google.common.base.Throwables.getCausalChain;
24 import static com.google.common.base.Throwables.propagate;
25 import static com.google.common.collect.Iterables.filter;
26 import static com.google.common.collect.Iterables.get;
27 import static com.google.common.collect.Iterables.size;
28 import static com.google.common.collect.Lists.newArrayList;
29 import static com.google.common.io.ByteStreams.toByteArray;
30 import static com.google.common.io.Closeables.closeQuietly;
31 import static javax.ws.rs.core.HttpHeaders.CONTENT_ENCODING;
32 import static javax.ws.rs.core.HttpHeaders.CONTENT_LANGUAGE;
33 import static javax.ws.rs.core.HttpHeaders.CONTENT_LENGTH;
34 import static javax.ws.rs.core.HttpHeaders.CONTENT_TYPE;
35 import static org.jclouds.util.Patterns.PATTERN_THAT_BREAKS_URI;
36 import static org.jclouds.util.Patterns.URI_PATTERN;
37
38 import java.io.IOException;
39 import java.io.InputStream;
40 import java.net.URI;
41 import java.util.Collection;
42 import java.util.List;
43 import java.util.Map.Entry;
44 import java.util.regex.Matcher;
45
46 import javax.inject.Named;
47 import javax.inject.Singleton;
48 import javax.ws.rs.core.HttpHeaders;
49
50 import org.jclouds.Constants;
51 import org.jclouds.crypto.CryptoStreams;
52 import org.jclouds.io.ContentMetadata;
53 import org.jclouds.io.InputSuppliers;
54 import org.jclouds.io.MutableContentMetadata;
55 import org.jclouds.io.Payload;
56 import org.jclouds.io.PayloadEnclosing;
57 import org.jclouds.io.Payloads;
58 import org.jclouds.logging.Logger;
59 import org.jclouds.logging.internal.Wire;
60 import org.jclouds.util.Strings2;
61
62 import com.google.common.base.Joiner;
63 import com.google.common.base.Predicate;
64 import com.google.common.base.Splitter;
65 import com.google.common.collect.ImmutableMultimap;
66 import com.google.common.collect.ImmutableMultimap.Builder;
67 import com.google.common.collect.Multimap;
68 import com.google.common.collect.SortedSetMultimap;
69 import com.google.common.collect.TreeMultimap;
70 import com.google.inject.Inject;
71
72
73
74
75 @Singleton
76 public class HttpUtils {
77
78 @Inject(optional = true)
79 @Named(Constants.PROPERTY_RELAX_HOSTNAME)
80 private boolean relaxHostname = false;
81
82 @Inject(optional = true)
83 @Named(Constants.PROPERTY_PROXY_SYSTEM)
84 private boolean systemProxies = System.getProperty("java.net.useSystemProxies") != null ? Boolean
85 .parseBoolean(System.getProperty("java.net.useSystemProxies")) : false;
86
87 private final int globalMaxConnections;
88 private final int globalMaxConnectionsPerHost;
89 private final int connectionTimeout;
90 private final int soTimeout;
91 @Inject(optional = true)
92 @Named(Constants.PROPERTY_PROXY_HOST)
93 private String proxyHost;
94 @Inject(optional = true)
95 @Named(Constants.PROPERTY_PROXY_PORT)
96 private Integer proxyPort;
97 @Inject(optional = true)
98 @Named(Constants.PROPERTY_PROXY_USER)
99 private String proxyUser;
100 @Inject(optional = true)
101 @Named(Constants.PROPERTY_PROXY_PASSWORD)
102 private String proxyPassword;
103 @Inject(optional = true)
104 @Named(Constants.PROPERTY_TRUST_ALL_CERTS)
105 private boolean trustAllCerts;
106
107 @Inject
108 public HttpUtils(@Named(Constants.PROPERTY_CONNECTION_TIMEOUT) int connectionTimeout,
109 @Named(Constants.PROPERTY_SO_TIMEOUT) int soTimeout,
110 @Named(Constants.PROPERTY_MAX_CONNECTIONS_PER_CONTEXT) int globalMaxConnections,
111 @Named(Constants.PROPERTY_MAX_CONNECTIONS_PER_HOST) int globalMaxConnectionsPerHost) {
112 this.soTimeout = soTimeout;
113 this.connectionTimeout = connectionTimeout;
114 this.globalMaxConnections = globalMaxConnections;
115 this.globalMaxConnectionsPerHost = globalMaxConnectionsPerHost;
116 }
117
118
119
120
121 public String getProxyHost() {
122 return proxyHost;
123 }
124
125
126
127
128 public Integer getProxyPort() {
129 return proxyPort;
130 }
131
132
133
134
135 public String getProxyUser() {
136 return proxyUser;
137 }
138
139
140
141
142 public String getProxyPassword() {
143 return proxyPassword;
144 }
145
146 public int getSocketOpenTimeout() {
147 return soTimeout;
148 }
149
150 public int getConnectionTimeout() {
151 return connectionTimeout;
152 }
153
154 public boolean relaxHostname() {
155 return relaxHostname;
156 }
157
158 public boolean trustAllCerts() {
159 return trustAllCerts;
160 }
161
162 public boolean useSystemProxies() {
163 return systemProxies;
164 }
165
166 public int getMaxConnections() {
167 return globalMaxConnections;
168 }
169
170 public int getMaxConnectionsPerHost() {
171 return globalMaxConnectionsPerHost;
172 }
173
174
175
176
177
178 public static URI createBaseEndpointFor(URI endpoint) {
179 if (endpoint.getPort() == -1) {
180 return URI.create(String.format("%s://%s", endpoint.getScheme(), endpoint.getHost()));
181 } else {
182 return URI.create(String.format("%s://%s:%d", endpoint.getScheme(), endpoint.getHost(), endpoint.getPort()));
183 }
184 }
185
186 public static Multimap<String, String> getContentHeadersFromMetadata(ContentMetadata md) {
187 Builder<String, String> builder = ImmutableMultimap.builder();
188 if (md.getContentType() != null)
189 builder.put(HttpHeaders.CONTENT_TYPE, md.getContentType());
190 if (md.getContentDisposition() != null)
191 builder.put("Content-Disposition", md.getContentDisposition());
192 if (md.getContentEncoding() != null)
193 builder.put(HttpHeaders.CONTENT_ENCODING, md.getContentEncoding());
194 if (md.getContentLanguage() != null)
195 builder.put(HttpHeaders.CONTENT_LANGUAGE, md.getContentLanguage());
196 if (md.getContentLength() != null)
197 builder.put(HttpHeaders.CONTENT_LENGTH, md.getContentLength() + "");
198 if (md.getContentMD5() != null)
199 builder.put("Content-MD5", CryptoStreams.base64(md.getContentMD5()));
200 return builder.build();
201 }
202
203 public static byte[] toByteArrayOrNull(PayloadEnclosing response) {
204 if (response.getPayload() != null) {
205 InputStream input = response.getPayload().getInput();
206 try {
207 return toByteArray(input);
208 } catch (IOException e) {
209 propagate(e);
210 } finally {
211 closeQuietly(input);
212 }
213 }
214 return null;
215 }
216
217
218
219
220
221
222 public static byte[] closeClientButKeepContentStream(PayloadEnclosing response) {
223 byte[] returnVal = toByteArrayOrNull(response);
224 if (returnVal != null && !response.getPayload().isRepeatable()) {
225 Payload newPayload = Payloads.newByteArrayPayload(returnVal);
226 MutableContentMetadata fromMd = response.getPayload().getContentMetadata();
227 MutableContentMetadata toMd = newPayload.getContentMetadata();
228 copy(fromMd, toMd);
229 response.setPayload(newPayload);
230 }
231 return returnVal;
232 }
233
234 public static void copy(ContentMetadata fromMd, MutableContentMetadata toMd) {
235 toMd.setContentLength(fromMd.getContentLength());
236 toMd.setContentMD5(fromMd.getContentMD5());
237 toMd.setContentType(fromMd.getContentType());
238 toMd.setContentDisposition(fromMd.getContentDisposition());
239 toMd.setContentEncoding(fromMd.getContentEncoding());
240 toMd.setContentLanguage(fromMd.getContentLanguage());
241 }
242
243 public static URI parseEndPoint(String hostHeader) {
244 URI redirectURI = URI.create(hostHeader);
245 String scheme = redirectURI.getScheme();
246
247 checkState(redirectURI.getScheme().startsWith("http"),
248 String.format("header %s didn't parse an http scheme: [%s]", hostHeader, scheme));
249 int port = redirectURI.getPort() > 0 ? redirectURI.getPort() : redirectURI.getScheme().equals("https") ? 443 : 80;
250 String host = redirectURI.getHost();
251 checkState(host.indexOf('/') == -1,
252 String.format("header %s didn't parse an http host correctly: [%s]", hostHeader, host));
253 URI endPoint = URI.create(String.format("%s://%s:%d", scheme, host, port));
254 return endPoint;
255 }
256
257 public static URI replaceHostInEndPoint(URI endPoint, String host) {
258 return URI.create(endPoint.toString().replace(endPoint.getHost(), host));
259 }
260
261
262
263
264
265
266
267
268 public static URI createUri(String uriPath) {
269 List<String> onQuery = newArrayList(Splitter.on('?').split(uriPath));
270 if (onQuery.size() == 2) {
271 onQuery.add(Strings2.urlEncode(onQuery.remove(1), '=', '&'));
272 uriPath = Joiner.on('?').join(onQuery);
273 }
274 if (uriPath.indexOf('@') != 1) {
275 List<String> parts = newArrayList(Splitter.on('@').split(uriPath));
276 String path = parts.remove(parts.size() - 1);
277 if (parts.size() > 1) {
278 parts = newArrayList(Strings2.urlEncode(Joiner.on('@').join(parts), '/', ':'));
279 }
280 parts.add(Strings2.urlEncode(path, '/', ':'));
281 uriPath = Joiner.on('@').join(parts);
282 } else {
283 List<String> parts = newArrayList(Splitter.on('/').split(uriPath));
284 String path = parts.remove(parts.size() - 1);
285 parts.add(Strings2.urlEncode(path, ':'));
286 uriPath = Joiner.on('/').join(parts);
287 }
288
289 if (PATTERN_THAT_BREAKS_URI.matcher(uriPath).matches()) {
290
291 Matcher matcher = URI_PATTERN.matcher(uriPath);
292 if (matcher.find()) {
293 String scheme = matcher.group(1);
294 String rest = matcher.group(4);
295 String identity = matcher.group(2);
296 String key = matcher.group(3);
297 return URI.create(String.format("%s://%s:%s@%s", scheme, Strings2.urlEncode(identity),
298 Strings2.urlEncode(key), rest));
299 } else {
300 throw new IllegalArgumentException("bad syntax");
301 }
302 } else {
303 return URI.create(uriPath);
304 }
305 }
306
307 public void logRequest(Logger logger, HttpRequest request, String prefix) {
308 if (logger.isDebugEnabled()) {
309 logger.debug("%s %s", prefix, request.getRequestLine().toString());
310 logMessage(logger, request, prefix);
311 }
312 }
313
314 private void logMessage(Logger logger, HttpMessage message, String prefix) {
315 for (Entry<String, String> header : message.getHeaders().entries()) {
316 if (header.getKey() != null)
317 logger.debug("%s %s: %s", prefix, header.getKey(), header.getValue());
318 }
319 if (message.getPayload() != null) {
320 if (message.getPayload().getContentMetadata().getContentType() != null)
321 logger.debug("%s %s: %s", prefix, CONTENT_TYPE, message.getPayload().getContentMetadata().getContentType());
322 if (message.getPayload().getContentMetadata().getContentLength() != null)
323 logger.debug("%s %s: %s", prefix, CONTENT_LENGTH, message.getPayload().getContentMetadata()
324 .getContentLength());
325 if (message.getPayload().getContentMetadata().getContentMD5() != null)
326 try {
327 logger.debug(
328 "%s %s: %s",
329 prefix,
330 "Content-MD5",
331 CryptoStreams.base64Encode(InputSuppliers.of(message.getPayload().getContentMetadata()
332 .getContentMD5())));
333 } catch (IOException e) {
334 logger.warn(e, " error getting md5 for %s", message);
335 }
336 if (message.getPayload().getContentMetadata().getContentDisposition() != null)
337 logger.debug("%s %s: %s", prefix, "Content-Disposition", message.getPayload().getContentMetadata()
338 .getContentDisposition());
339 if (message.getPayload().getContentMetadata().getContentEncoding() != null)
340 logger.debug("%s %s: %s", prefix, CONTENT_ENCODING, message.getPayload().getContentMetadata()
341 .getContentEncoding());
342 if (message.getPayload().getContentMetadata().getContentLanguage() != null)
343 logger.debug("%s %s: %s", prefix, CONTENT_LANGUAGE, message.getPayload().getContentMetadata()
344 .getContentLanguage());
345 }
346 }
347
348 public void logResponse(Logger logger, HttpResponse response, String prefix) {
349 if (logger.isDebugEnabled()) {
350 logger.debug("%s %s", prefix, response.getStatusLine().toString());
351 logMessage(logger, response, prefix);
352 }
353 }
354
355 public static String sortAndConcatHeadersIntoString(Multimap<String, String> headers) {
356 StringBuffer buffer = new StringBuffer();
357 SortedSetMultimap<String, String> sortedMap = TreeMultimap.create();
358 sortedMap.putAll(headers);
359 for (Entry<String, String> header : sortedMap.entries()) {
360 if (header.getKey() != null)
361 buffer.append(String.format("%s: %s\n", header.getKey(), header.getValue()));
362 }
363 return buffer.toString();
364 }
365
366 public void checkRequestHasRequiredProperties(HttpRequest message) {
367 checkArgument(
368 message.getPayload() == null || message.getFirstHeaderOrNull(CONTENT_TYPE) == null,
369 "configuration error please use request.getPayload().getContentMetadata().setContentType(value) as opposed to adding a content type header: "
370 + message);
371 checkArgument(
372 message.getPayload() == null || message.getFirstHeaderOrNull(CONTENT_LENGTH) == null,
373 "configuration error please use request.getPayload().getContentMetadata().setContentLength(value) as opposed to adding a content length header: "
374 + message);
375 checkArgument(
376 message.getPayload() == null || message.getPayload().getContentMetadata().getContentLength() != null
377 || "chunked".equalsIgnoreCase(message.getFirstHeaderOrNull("Transfer-Encoding")),
378 "either chunked encoding must be set on the http request or contentlength set on the payload: " + message);
379 checkArgument(
380 message.getPayload() == null || message.getFirstHeaderOrNull("Content-MD5") == null,
381 "configuration error please use request.getPayload().getContentMetadata().setContentMD5(value) as opposed to adding a content md5 header: "
382 + message);
383 checkArgument(
384 message.getPayload() == null || message.getFirstHeaderOrNull("Content-Disposition") == null,
385 "configuration error please use request.getPayload().getContentMetadata().setContentDisposition(value) as opposed to adding a content disposition header: "
386 + message);
387 checkArgument(
388 message.getPayload() == null || message.getFirstHeaderOrNull(CONTENT_ENCODING) == null,
389 "configuration error please use request.getPayload().getContentMetadata().setContentEncoding(value) as opposed to adding a content encoding header: "
390 + message);
391 checkArgument(
392 message.getPayload() == null || message.getFirstHeaderOrNull(CONTENT_LANGUAGE) == null,
393 "configuration error please use request.getPayload().getContentMetadata().setContentLanguage(value) as opposed to adding a content language header: "
394 + message);
395 }
396
397 public static void releasePayload(HttpMessage from) {
398 if (from.getPayload() != null)
399 from.getPayload().release();
400 }
401
402 public String valueOrEmpty(String in) {
403 return in != null ? in : "";
404 }
405
406 public String valueOrEmpty(byte[] md5) {
407 return md5 != null ? CryptoStreams.base64(md5) : "";
408 }
409
410 public String valueOrEmpty(Collection<String> collection) {
411 return (collection != null && collection.size() >= 1) ? collection.iterator().next() : "";
412 }
413
414 public static Long attemptToParseSizeAndRangeFromHeaders(HttpMessage from) throws HttpException {
415 String contentRange = from.getFirstHeaderOrNull("Content-Range");
416 if (contentRange == null && from.getPayload() != null) {
417 return from.getPayload().getContentMetadata().getContentLength();
418 } else if (contentRange != null) {
419 return Long.parseLong(contentRange.substring(contentRange.lastIndexOf('/') + 1));
420 }
421 return null;
422 }
423
424 public static void checkRequestHasContentLengthOrChunkedEncoding(HttpMessage request, String message) {
425 boolean chunked = "chunked".equals(request.getFirstHeaderOrNull("Transfer-Encoding"));
426 checkArgument(request.getPayload() == null || chunked
427 || request.getPayload().getContentMetadata().getContentLength() != null, message);
428 }
429
430 public static void wirePayloadIfEnabled(Wire wire, HttpMessage request) {
431 if (request.getPayload() != null && wire.enabled()) {
432 wire.output(request);
433 checkRequestHasContentLengthOrChunkedEncoding(request,
434 "After wiring, the request has neither chunked encoding nor content length: " + request);
435 }
436 }
437
438 public static <T> T returnValueOnCodeOrNull(Exception from, T value, Predicate<Integer> codePredicate) {
439 Iterable<HttpResponseException> throwables = filter(getCausalChain(from), HttpResponseException.class);
440 if (size(throwables) >= 1 && get(throwables, 0).getResponse() != null
441 && codePredicate.apply(get(throwables, 0).getResponse().getStatusCode())) {
442 return value;
443 }
444 return null;
445 }
446 }