View Javadoc
1   /*
2    * Copyright (C) 2013 Christian Halstrick <christian.halstrick@sap.com>
3    * and other copyright owners as documented in the project's IP log.
4    *
5    * This program and the accompanying materials are made available
6    * under the terms of the Eclipse Distribution License v1.0 which
7    * accompanies this distribution, is reproduced below, and is
8    * available at http://www.eclipse.org/org/documents/edl-v10.php
9    *
10   * All rights reserved.
11   *
12   * Redistribution and use in source and binary forms, with or
13   * without modification, are permitted provided that the following
14   * conditions are met:
15   *
16   * - Redistributions of source code must retain the above copyright
17   *   notice, this list of conditions and the following disclaimer.
18   *
19   * - Redistributions in binary form must reproduce the above
20   *   copyright notice, this list of conditions and the following
21   *   disclaimer in the documentation and/or other materials provided
22   *   with the distribution.
23   *
24   * - Neither the name of the Eclipse Foundation, Inc. nor the
25   *   names of its contributors may be used to endorse or promote
26   *   products derived from this software without specific prior
27   *   written permission.
28   *
29   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
30   * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
31   * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
32   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
33   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
34   * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
35   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
36   * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
37   * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
38   * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
39   * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
40   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
41   * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
42   */
43  package org.eclipse.jgit.transport.http.apache;
44  
45  import static org.eclipse.jgit.util.HttpSupport.METHOD_GET;
46  import static org.eclipse.jgit.util.HttpSupport.METHOD_HEAD;
47  import static org.eclipse.jgit.util.HttpSupport.METHOD_POST;
48  import static org.eclipse.jgit.util.HttpSupport.METHOD_PUT;
49  
50  import java.io.IOException;
51  import java.io.InputStream;
52  import java.io.OutputStream;
53  import java.net.InetSocketAddress;
54  import java.net.MalformedURLException;
55  import java.net.ProtocolException;
56  import java.net.Proxy;
57  import java.net.URL;
58  import java.security.KeyManagementException;
59  import java.security.NoSuchAlgorithmException;
60  import java.security.SecureRandom;
61  import java.security.cert.X509Certificate;
62  import java.util.HashMap;
63  import java.util.LinkedList;
64  import java.util.List;
65  import java.util.Map;
66  
67  import javax.net.ssl.HostnameVerifier;
68  import javax.net.ssl.KeyManager;
69  import javax.net.ssl.SSLContext;
70  import javax.net.ssl.SSLException;
71  import javax.net.ssl.SSLSession;
72  import javax.net.ssl.SSLSocket;
73  import javax.net.ssl.TrustManager;
74  
75  import org.apache.http.Header;
76  import org.apache.http.HeaderElement;
77  import org.apache.http.HttpEntity;
78  import org.apache.http.HttpEntityEnclosingRequest;
79  import org.apache.http.HttpHost;
80  import org.apache.http.HttpResponse;
81  import org.apache.http.client.ClientProtocolException;
82  import org.apache.http.client.HttpClient;
83  import org.apache.http.client.config.RequestConfig;
84  import org.apache.http.client.methods.HttpGet;
85  import org.apache.http.client.methods.HttpHead;
86  import org.apache.http.client.methods.HttpPost;
87  import org.apache.http.client.methods.HttpPut;
88  import org.apache.http.client.methods.HttpUriRequest;
89  import org.apache.http.config.Registry;
90  import org.apache.http.config.RegistryBuilder;
91  import org.apache.http.conn.socket.ConnectionSocketFactory;
92  import org.apache.http.conn.socket.PlainConnectionSocketFactory;
93  import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
94  import org.apache.http.conn.ssl.X509HostnameVerifier;
95  import org.apache.http.impl.client.HttpClientBuilder;
96  import org.apache.http.impl.client.HttpClients;
97  import org.apache.http.impl.conn.BasicHttpClientConnectionManager;
98  import org.eclipse.jgit.transport.http.HttpConnection;
99  import org.eclipse.jgit.transport.http.apache.internal.HttpApacheText;
100 import org.eclipse.jgit.util.TemporaryBuffer;
101 import org.eclipse.jgit.util.TemporaryBuffer.LocalFile;
102 
103 /**
104  * A {@link HttpConnection} which uses {@link HttpClient}
105  *
106  * @since 3.3
107  */
108 public class HttpClientConnection implements HttpConnection {
109 	HttpClient client;
110 
111 	URL url;
112 
113 	HttpUriRequest req;
114 
115 	HttpResponse resp = null;
116 
117 	String method = "GET"; //$NON-NLS-1$
118 
119 	private TemporaryBufferEntity entity;
120 
121 	private boolean isUsingProxy = false;
122 
123 	private Proxy proxy;
124 
125 	private Integer timeout = null;
126 
127 	private Integer readTimeout;
128 
129 	private Boolean followRedirects;
130 
131 	private X509HostnameVerifier hostnameverifier;
132 
133 	SSLContext ctx;
134 
135 	private HttpClient getClient() {
136 		if (client == null) {
137 			HttpClientBuilder clientBuilder = HttpClients.custom();
138 			RequestConfig.Builder configBuilder = RequestConfig.custom();
139 			if (proxy != null && !Proxy.NO_PROXY.equals(proxy)) {
140 				isUsingProxy = true;
141 				InetSocketAddress adr = (InetSocketAddress) proxy.address();
142 				clientBuilder.setProxy(
143 						new HttpHost(adr.getHostName(), adr.getPort()));
144 			}
145 			if (timeout != null) {
146 				configBuilder.setConnectTimeout(timeout.intValue());
147 			}
148 			if (readTimeout != null) {
149 				configBuilder.setSocketTimeout(readTimeout.intValue());
150 			}
151 			if (followRedirects != null) {
152 				configBuilder
153 						.setRedirectsEnabled(followRedirects.booleanValue());
154 			}
155 			if (hostnameverifier != null) {
156 				SSLConnectionSocketFactory sslConnectionFactory = new SSLConnectionSocketFactory(
157 						getSSLContext(), hostnameverifier);
158 				clientBuilder.setSSLSocketFactory(sslConnectionFactory);
159 				Registry<ConnectionSocketFactory> registry = RegistryBuilder
160 						.<ConnectionSocketFactory> create()
161 						.register("https", sslConnectionFactory)
162 						.register("http", PlainConnectionSocketFactory.INSTANCE)
163 						.build();
164 				clientBuilder.setConnectionManager(
165 						new BasicHttpClientConnectionManager(registry));
166 			}
167 			clientBuilder.setDefaultRequestConfig(configBuilder.build());
168 			client = clientBuilder.build();
169 		}
170 
171 		return client;
172 	}
173 
174 	private SSLContext getSSLContext() {
175 		if (ctx == null) {
176 			try {
177 				ctx = SSLContext.getInstance("TLS"); //$NON-NLS-1$
178 			} catch (NoSuchAlgorithmException e) {
179 				throw new IllegalStateException(
180 						HttpApacheText.get().unexpectedSSLContextException, e);
181 			}
182 		}
183 		return ctx;
184 	}
185 
186 	/**
187 	 * Sets the buffer from which to take the request body
188 	 *
189 	 * @param buffer
190 	 */
191 	public void setBuffer(TemporaryBuffer buffer) {
192 		this.entity = new TemporaryBufferEntity(buffer);
193 	}
194 
195 	/**
196 	 * @param urlStr
197 	 * @throws MalformedURLException
198 	 */
199 	public HttpClientConnection(String urlStr) throws MalformedURLException {
200 		this(urlStr, null);
201 	}
202 
203 	/**
204 	 * @param urlStr
205 	 * @param proxy
206 	 * @throws MalformedURLException
207 	 */
208 	public HttpClientConnection(String urlStr, Proxy proxy)
209 			throws MalformedURLException {
210 		this(urlStr, proxy, null);
211 	}
212 
213 	/**
214 	 * @param urlStr
215 	 * @param proxy
216 	 * @param cl
217 	 * @throws MalformedURLException
218 	 */
219 	public HttpClientConnection(String urlStr, Proxy proxy, HttpClient cl)
220 			throws MalformedURLException {
221 		this.client = cl;
222 		this.url = new URL(urlStr);
223 		this.proxy = proxy;
224 	}
225 
226 	public int getResponseCode() throws IOException {
227 		execute();
228 		return resp.getStatusLine().getStatusCode();
229 	}
230 
231 	public URL getURL() {
232 		return url;
233 	}
234 
235 	public String getResponseMessage() throws IOException {
236 		execute();
237 		return resp.getStatusLine().getReasonPhrase();
238 	}
239 
240 	private void execute() throws IOException, ClientProtocolException {
241 		if (resp != null) {
242 			return;
243 		}
244 
245 		if (entity == null) {
246 			resp = getClient().execute(req);
247 			return;
248 		}
249 
250 		try {
251 			if (req instanceof HttpEntityEnclosingRequest) {
252 				HttpEntityEnclosingRequest eReq = (HttpEntityEnclosingRequest) req;
253 				eReq.setEntity(entity);
254 			}
255 			resp = getClient().execute(req);
256 		} finally {
257 			entity.close();
258 			entity = null;
259 		}
260 	}
261 
262 	public Map<String, List<String>> getHeaderFields() {
263 		Map<String, List<String>> ret = new HashMap<String, List<String>>();
264 		for (Header hdr : resp.getAllHeaders()) {
265 			List<String> list = new LinkedList<String>();
266 			for (HeaderElement hdrElem : hdr.getElements())
267 				list.add(hdrElem.toString());
268 			ret.put(hdr.getName(), list);
269 		}
270 		return ret;
271 	}
272 
273 	public void setRequestProperty(String name, String value) {
274 		req.addHeader(name, value);
275 	}
276 
277 	public void setRequestMethod(String method) throws ProtocolException {
278 		this.method = method;
279 		if (METHOD_GET.equalsIgnoreCase(method)) {
280 			req = new HttpGet(url.toString());
281 		} else if (METHOD_HEAD.equalsIgnoreCase(method)) {
282 			req = new HttpHead(url.toString());
283 		} else if (METHOD_PUT.equalsIgnoreCase(method)) {
284 			req = new HttpPut(url.toString());
285 		} else if (METHOD_POST.equalsIgnoreCase(method)) {
286 			req = new HttpPost(url.toString());
287 		} else {
288 			this.method = null;
289 			throw new UnsupportedOperationException();
290 		}
291 	}
292 
293 	public void setUseCaches(boolean usecaches) {
294 		// not needed
295 	}
296 
297 	public void setConnectTimeout(int timeout) {
298 		this.timeout = Integer.valueOf(timeout);
299 	}
300 
301 	public void setReadTimeout(int readTimeout) {
302 		this.readTimeout = Integer.valueOf(readTimeout);
303 	}
304 
305 	public String getContentType() {
306 		HttpEntity responseEntity = resp.getEntity();
307 		if (responseEntity != null) {
308 			Header contentType = responseEntity.getContentType();
309 			if (contentType != null)
310 				return contentType.getValue();
311 		}
312 		return null;
313 	}
314 
315 	public InputStream getInputStream() throws IOException {
316 		return resp.getEntity().getContent();
317 	}
318 
319 	// will return only the first field
320 	public String getHeaderField(String name) {
321 		Header header = resp.getFirstHeader(name);
322 		return (header == null) ? null : header.getValue();
323 	}
324 
325 	public int getContentLength() {
326 		Header contentLength = resp.getFirstHeader("content-length"); //$NON-NLS-1$
327 		if (contentLength == null) {
328 			return -1;
329 		}
330 
331 		try {
332 			int l = Integer.parseInt(contentLength.getValue());
333 			return l < 0 ? -1 : l;
334 		} catch (NumberFormatException e) {
335 			return -1;
336 		}
337 	}
338 
339 	public void setInstanceFollowRedirects(boolean followRedirects) {
340 		this.followRedirects = Boolean.valueOf(followRedirects);
341 	}
342 
343 	public void setDoOutput(boolean dooutput) {
344 		// TODO: check whether we can really ignore this.
345 	}
346 
347 	public void setFixedLengthStreamingMode(int contentLength) {
348 		if (entity != null)
349 			throw new IllegalArgumentException();
350 		entity = new TemporaryBufferEntity(new LocalFile(null));
351 		entity.setContentLength(contentLength);
352 	}
353 
354 	public OutputStream getOutputStream() throws IOException {
355 		if (entity == null)
356 			entity = new TemporaryBufferEntity(new LocalFile(null));
357 		return entity.getBuffer();
358 	}
359 
360 	public void setChunkedStreamingMode(int chunklen) {
361 		if (entity == null)
362 			entity = new TemporaryBufferEntity(new LocalFile(null));
363 		entity.setChunked(true);
364 	}
365 
366 	public String getRequestMethod() {
367 		return method;
368 	}
369 
370 	public boolean usingProxy() {
371 		return isUsingProxy;
372 	}
373 
374 	public void connect() throws IOException {
375 		execute();
376 	}
377 
378 	public void setHostnameVerifier(final HostnameVerifier hostnameverifier) {
379 		this.hostnameverifier = new X509HostnameVerifier() {
380 			public boolean verify(String hostname, SSLSession session) {
381 				return hostnameverifier.verify(hostname, session);
382 			}
383 
384 			public void verify(String host, String[] cns, String[] subjectAlts)
385 					throws SSLException {
386 				throw new UnsupportedOperationException(); // TODO message
387 			}
388 
389 			public void verify(String host, X509Certificate cert)
390 					throws SSLException {
391 				throw new UnsupportedOperationException(); // TODO message
392 			}
393 
394 			public void verify(String host, SSLSocket ssl) throws IOException {
395 				hostnameverifier.verify(host, ssl.getSession());
396 			}
397 		};
398 	}
399 
400 	public void configure(KeyManager[] km, TrustManager[] tm,
401 			SecureRandom random) throws KeyManagementException {
402 		getSSLContext().init(km, tm, random);
403 	}
404 }