1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.jclouds.http.internal;
20
21 import static com.google.common.base.Preconditions.checkArgument;
22 import static com.google.common.base.Preconditions.checkNotNull;
23 import static com.google.common.base.Throwables.propagate;
24 import static com.google.common.collect.Iterables.getLast;
25 import static com.google.common.io.ByteStreams.toByteArray;
26 import static com.google.common.io.Closeables.closeQuietly;
27 import static org.jclouds.io.Payloads.newInputStreamPayload;
28
29 import java.io.ByteArrayInputStream;
30 import java.io.IOException;
31 import java.io.InputStream;
32 import java.lang.reflect.Field;
33 import java.net.Authenticator;
34 import java.net.HttpURLConnection;
35 import java.net.InetSocketAddress;
36 import java.net.PasswordAuthentication;
37 import java.net.ProtocolException;
38 import java.net.Proxy;
39 import java.net.ProxySelector;
40 import java.net.SocketAddress;
41 import java.net.URL;
42 import java.util.concurrent.ExecutorService;
43
44 import javax.annotation.Resource;
45 import javax.inject.Inject;
46 import javax.inject.Named;
47 import javax.inject.Singleton;
48 import javax.net.ssl.HostnameVerifier;
49 import javax.net.ssl.HttpsURLConnection;
50 import javax.net.ssl.SSLContext;
51 import javax.ws.rs.core.HttpHeaders;
52
53 import org.jclouds.Constants;
54 import org.jclouds.crypto.CryptoStreams;
55 import org.jclouds.http.HttpCommandExecutorService;
56 import org.jclouds.http.HttpRequest;
57 import org.jclouds.http.HttpResponse;
58 import org.jclouds.http.HttpUtils;
59 import org.jclouds.http.IOExceptionRetryHandler;
60 import org.jclouds.http.handlers.DelegatingErrorHandler;
61 import org.jclouds.http.handlers.DelegatingRetryHandler;
62 import org.jclouds.io.MutableContentMetadata;
63 import org.jclouds.io.Payload;
64 import org.jclouds.logging.Logger;
65 import org.jclouds.rest.internal.RestAnnotationProcessor;
66
67 import com.google.common.base.Supplier;
68 import com.google.common.collect.ImmutableMultimap;
69 import com.google.common.collect.ImmutableMultimap.Builder;
70 import com.google.common.io.CountingOutputStream;
71
72
73
74
75
76
77 @Singleton
78 public class JavaUrlHttpCommandExecutorService extends BaseHttpCommandExecutorService<HttpURLConnection> {
79
80 public static final String USER_AGENT = "jclouds/1.0 java/" + System.getProperty("java.version");
81 @Resource
82 protected Logger logger = Logger.NULL;
83 private final Supplier<SSLContext> untrustedSSLContextProvider;
84 private final HostnameVerifier verifier;
85 private final Field methodField;
86
87 @Inject
88 public JavaUrlHttpCommandExecutorService(HttpUtils utils,
89 @Named(Constants.PROPERTY_IO_WORKER_THREADS) ExecutorService ioWorkerExecutor,
90 DelegatingRetryHandler retryHandler, IOExceptionRetryHandler ioRetryHandler,
91 DelegatingErrorHandler errorHandler, HttpWire wire, @Named("untrusted") HostnameVerifier verifier,
92 @Named("untrusted") Supplier<SSLContext> untrustedSSLContextProvider) throws SecurityException,
93 NoSuchFieldException {
94 super(utils, ioWorkerExecutor, retryHandler, ioRetryHandler, errorHandler, wire);
95 if (utils.getMaxConnections() > 0)
96 System.setProperty("http.maxConnections", String.valueOf(checkNotNull(utils, "utils").getMaxConnections()));
97 this.untrustedSSLContextProvider = checkNotNull(untrustedSSLContextProvider, "untrustedSSLContextProvider");
98 this.verifier = checkNotNull(verifier, "verifier");
99 this.methodField = HttpURLConnection.class.getDeclaredField("method");
100 methodField.setAccessible(true);
101 }
102
103 @Override
104 protected HttpResponse invoke(HttpURLConnection connection) throws IOException, InterruptedException {
105 HttpResponse.Builder builder = HttpResponse.builder();
106 InputStream in = null;
107 try {
108 in = consumeOnClose(connection.getInputStream());
109 } catch (IOException e) {
110 in = bufferAndCloseStream(connection.getErrorStream());
111 } catch (RuntimeException e) {
112 closeQuietly(in);
113 propagate(e);
114 assert false : "should have propagated exception";
115 }
116
117 int responseCode = connection.getResponseCode();
118 if (responseCode == 204) {
119 closeQuietly(in);
120 in = null;
121 }
122 builder.statusCode(responseCode);
123 builder.message(connection.getResponseMessage());
124
125 Builder<String, String> headerBuilder = ImmutableMultimap.<String, String> builder();
126 for (String header : connection.getHeaderFields().keySet()) {
127
128 if (header != null)
129 headerBuilder.putAll(header, connection.getHeaderFields().get(header));
130 }
131 ImmutableMultimap<String, String> headers = headerBuilder.build();
132 Payload payload = in != null ? newInputStreamPayload(in) : null;
133 if (payload != null) {
134 payload.getContentMetadata().setPropertiesFromHttpHeaders(headers);
135 builder.payload(payload);
136 }
137 builder.headers(RestAnnotationProcessor.filterOutContentHeaders(headers));
138 return builder.build();
139 }
140
141 private InputStream bufferAndCloseStream(InputStream inputStream) throws IOException {
142 InputStream in = null;
143 try {
144 if (inputStream != null) {
145 in = new ByteArrayInputStream(toByteArray(inputStream));
146 }
147 } finally {
148 closeQuietly(inputStream);
149 }
150 return in;
151 }
152
153 @Override
154 protected HttpURLConnection convert(HttpRequest request) throws IOException, InterruptedException {
155 boolean chunked = "chunked".equals(request.getFirstHeaderOrNull("Transfer-Encoding"));
156 URL url = request.getEndpoint().toURL();
157
158 HttpURLConnection connection;
159
160 if (utils.useSystemProxies()) {
161 System.setProperty("java.net.useSystemProxies", "true");
162 Iterable<Proxy> proxies = ProxySelector.getDefault().select(request.getEndpoint());
163 Proxy proxy = getLast(proxies);
164 connection = (HttpURLConnection) url.openConnection(proxy);
165 } else if (utils.getProxyHost() != null) {
166 SocketAddress addr = new InetSocketAddress(utils.getProxyHost(), utils.getProxyPort());
167 Proxy proxy = new Proxy(Proxy.Type.HTTP, addr);
168 Authenticator authenticator = new Authenticator() {
169 public PasswordAuthentication getPasswordAuthentication() {
170 return (new PasswordAuthentication(utils.getProxyUser(), utils.getProxyPassword().toCharArray()));
171 }
172 };
173 Authenticator.setDefault(authenticator);
174 connection = (HttpURLConnection) url.openConnection(proxy);
175 } else {
176 connection = (HttpURLConnection) url.openConnection();
177 }
178 if (connection instanceof HttpsURLConnection) {
179 HttpsURLConnection sslCon = (HttpsURLConnection) connection;
180 if (utils.relaxHostname())
181 sslCon.setHostnameVerifier(verifier);
182 if (utils.trustAllCerts())
183 sslCon.setSSLSocketFactory(untrustedSSLContextProvider.get().getSocketFactory());
184 }
185 if (utils.getConnectionTimeout() > 0) {
186 connection.setConnectTimeout(utils.getConnectionTimeout());
187 }
188 if (utils.getSocketOpenTimeout() > 0) {
189 connection.setReadTimeout(utils.getSocketOpenTimeout());
190 }
191 connection.setDoOutput(true);
192 connection.setAllowUserInteraction(false);
193
194
195
196 connection.setInstanceFollowRedirects(false);
197 try {
198 connection.setRequestMethod(request.getMethod());
199 } catch (ProtocolException e) {
200 try {
201 methodField.set(connection, request.getMethod());
202 } catch (Exception e1) {
203 logger.error(e, "could not set request method: ", request.getMethod());
204 propagate(e1);
205 }
206 }
207
208 for (String header : request.getHeaders().keys()) {
209 for (String value : request.getHeaders().get(header)) {
210 connection.setRequestProperty(header, value);
211 }
212 }
213 connection.setRequestProperty(HttpHeaders.HOST, request.getEndpoint().getHost());
214 connection.setRequestProperty(HttpHeaders.USER_AGENT, USER_AGENT);
215
216 if (request.getPayload() != null) {
217 MutableContentMetadata md = request.getPayload().getContentMetadata();
218 if (md.getContentMD5() != null)
219 connection.setRequestProperty("Content-MD5", CryptoStreams.base64(md.getContentMD5()));
220 if (md.getContentType() != null)
221 connection.setRequestProperty(HttpHeaders.CONTENT_TYPE, md.getContentType());
222 if (md.getContentDisposition() != null)
223 connection.setRequestProperty("Content-Disposition", md.getContentDisposition());
224 if (md.getContentEncoding() != null)
225 connection.setRequestProperty("Content-Encoding", md.getContentEncoding());
226 if (md.getContentLanguage() != null)
227 connection.setRequestProperty("Content-Language", md.getContentLanguage());
228 if (chunked) {
229 connection.setChunkedStreamingMode(8196);
230 } else {
231 Long length = checkNotNull(md.getContentLength(), "payload.getContentLength");
232 connection.setRequestProperty(HttpHeaders.CONTENT_LENGTH, length.toString());
233
234 checkArgument(length < Integer.MAX_VALUE,
235 "JDK 1.6 does not support >2GB chunks. Use chunked encoding, if possible.");
236 connection.setFixedLengthStreamingMode(length.intValue());
237 if (length.intValue() > 0) {
238 connection.setRequestProperty("Expect", "100-continue");
239 }
240 }
241 CountingOutputStream out = new CountingOutputStream(connection.getOutputStream());
242 try {
243 request.getPayload().writeTo(out);
244 } catch (IOException e) {
245 throw new RuntimeException(String.format("error after writing %d/%s bytes to %s", out.getCount(), md
246 .getContentLength(), request.getRequestLine()), e);
247 }
248 } else {
249 connection.setRequestProperty(HttpHeaders.CONTENT_LENGTH, "0");
250
251 if (connection.getRequestMethod().equals("POST") || connection.getRequestMethod().equals("PUT"))
252 connection.setFixedLengthStreamingMode(0);
253 }
254 return connection;
255
256 }
257
258
259
260
261 @Override
262 protected void cleanup(HttpURLConnection connection) {
263 if (connection != null && connection.getContentLength() == 0)
264 connection.disconnect();
265 }
266
267 }