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.util.Arrays;
62  import java.util.Collections;
63  import java.util.HashMap;
64  import java.util.LinkedList;
65  import java.util.List;
66  import java.util.Map;
67  import java.util.stream.Collectors;
68  
69  import javax.net.ssl.HostnameVerifier;
70  import javax.net.ssl.KeyManager;
71  import javax.net.ssl.SSLContext;
72  import javax.net.ssl.TrustManager;
73  
74  import org.apache.http.Header;
75  import org.apache.http.HeaderElement;
76  import org.apache.http.HttpEntity;
77  import org.apache.http.HttpEntityEnclosingRequest;
78  import org.apache.http.HttpHost;
79  import org.apache.http.HttpResponse;
80  import org.apache.http.client.ClientProtocolException;
81  import org.apache.http.client.HttpClient;
82  import org.apache.http.client.config.RequestConfig;
83  import org.apache.http.client.methods.HttpGet;
84  import org.apache.http.client.methods.HttpHead;
85  import org.apache.http.client.methods.HttpPost;
86  import org.apache.http.client.methods.HttpPut;
87  import org.apache.http.client.methods.HttpUriRequest;
88  import org.apache.http.config.Registry;
89  import org.apache.http.config.RegistryBuilder;
90  import org.apache.http.conn.socket.ConnectionSocketFactory;
91  import org.apache.http.conn.socket.PlainConnectionSocketFactory;
92  import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
93  import org.apache.http.impl.client.HttpClientBuilder;
94  import org.apache.http.impl.client.HttpClients;
95  import org.apache.http.impl.conn.BasicHttpClientConnectionManager;
96  import org.eclipse.jgit.annotations.NonNull;
97  import org.eclipse.jgit.transport.http.HttpConnection;
98  import org.eclipse.jgit.transport.http.apache.internal.HttpApacheText;
99  import org.eclipse.jgit.util.TemporaryBuffer;
100 import org.eclipse.jgit.util.TemporaryBuffer.LocalFile;
101 
102 /**
103  * A {@link org.eclipse.jgit.transport.http.HttpConnection} which uses
104  * {@link org.apache.http.client.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 HostnameVerifier 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 	 * Constructor for HttpClientConnection.
197 	 *
198 	 * @param urlStr
199 	 * @throws MalformedURLException
200 	 */
201 	public HttpClientConnection(String urlStr) throws MalformedURLException {
202 		this(urlStr, null);
203 	}
204 
205 	/**
206 	 * Constructor for HttpClientConnection.
207 	 *
208 	 * @param urlStr
209 	 * @param proxy
210 	 * @throws MalformedURLException
211 	 */
212 	public HttpClientConnection(String urlStr, Proxy proxy)
213 			throws MalformedURLException {
214 		this(urlStr, proxy, null);
215 	}
216 
217 	/**
218 	 * Constructor for HttpClientConnection.
219 	 *
220 	 * @param urlStr
221 	 * @param proxy
222 	 * @param cl
223 	 * @throws MalformedURLException
224 	 */
225 	public HttpClientConnection(String urlStr, Proxy proxy, HttpClient cl)
226 			throws MalformedURLException {
227 		this.client = cl;
228 		this.url = new URL(urlStr);
229 		this.proxy = proxy;
230 	}
231 
232 	/** {@inheritDoc} */
233 	@Override
234 	public int getResponseCode() throws IOException {
235 		execute();
236 		return resp.getStatusLine().getStatusCode();
237 	}
238 
239 	/** {@inheritDoc} */
240 	@Override
241 	public URL getURL() {
242 		return url;
243 	}
244 
245 	/** {@inheritDoc} */
246 	@Override
247 	public String getResponseMessage() throws IOException {
248 		execute();
249 		return resp.getStatusLine().getReasonPhrase();
250 	}
251 
252 	private void execute() throws IOException, ClientProtocolException {
253 		if (resp != null) {
254 			return;
255 		}
256 
257 		if (entity == null) {
258 			resp = getClient().execute(req);
259 			return;
260 		}
261 
262 		try {
263 			if (req instanceof HttpEntityEnclosingRequest) {
264 				HttpEntityEnclosingRequest eReq = (HttpEntityEnclosingRequest) req;
265 				eReq.setEntity(entity);
266 			}
267 			resp = getClient().execute(req);
268 		} finally {
269 			entity.close();
270 			entity = null;
271 		}
272 	}
273 
274 	/** {@inheritDoc} */
275 	@Override
276 	public Map<String, List<String>> getHeaderFields() {
277 		Map<String, List<String>> ret = new HashMap<>();
278 		for (Header hdr : resp.getAllHeaders()) {
279 			List<String> list = ret.get(hdr.getName());
280 			if (list == null) {
281 				list = new LinkedList<>();
282 				ret.put(hdr.getName(), list);
283 			}
284 			for (HeaderElement hdrElem : hdr.getElements()) {
285 				list.add(hdrElem.toString());
286 			}
287 		}
288 		return ret;
289 	}
290 
291 	/** {@inheritDoc} */
292 	@Override
293 	public void setRequestProperty(String name, String value) {
294 		req.addHeader(name, value);
295 	}
296 
297 	/** {@inheritDoc} */
298 	@Override
299 	public void setRequestMethod(String method) throws ProtocolException {
300 		this.method = method;
301 		if (METHOD_GET.equalsIgnoreCase(method)) {
302 			req = new HttpGet(url.toString());
303 		} else if (METHOD_HEAD.equalsIgnoreCase(method)) {
304 			req = new HttpHead(url.toString());
305 		} else if (METHOD_PUT.equalsIgnoreCase(method)) {
306 			req = new HttpPut(url.toString());
307 		} else if (METHOD_POST.equalsIgnoreCase(method)) {
308 			req = new HttpPost(url.toString());
309 		} else {
310 			this.method = null;
311 			throw new UnsupportedOperationException();
312 		}
313 	}
314 
315 	/** {@inheritDoc} */
316 	@Override
317 	public void setUseCaches(boolean usecaches) {
318 		// not needed
319 	}
320 
321 	/** {@inheritDoc} */
322 	@Override
323 	public void setConnectTimeout(int timeout) {
324 		this.timeout = Integer.valueOf(timeout);
325 	}
326 
327 	/** {@inheritDoc} */
328 	@Override
329 	public void setReadTimeout(int readTimeout) {
330 		this.readTimeout = Integer.valueOf(readTimeout);
331 	}
332 
333 	/** {@inheritDoc} */
334 	@Override
335 	public String getContentType() {
336 		HttpEntity responseEntity = resp.getEntity();
337 		if (responseEntity != null) {
338 			Header contentType = responseEntity.getContentType();
339 			if (contentType != null)
340 				return contentType.getValue();
341 		}
342 		return null;
343 	}
344 
345 	/** {@inheritDoc} */
346 	@Override
347 	public InputStream getInputStream() throws IOException {
348 		return resp.getEntity().getContent();
349 	}
350 
351 	// will return only the first field
352 	/** {@inheritDoc} */
353 	@Override
354 	public String getHeaderField(@NonNull String name) {
355 		Header header = resp.getFirstHeader(name);
356 		return (header == null) ? null : header.getValue();
357 	}
358 
359 	@Override
360 	public List<String> getHeaderFields(@NonNull String name) {
361 		return Collections.unmodifiableList(Arrays.asList(resp.getHeaders(name))
362 				.stream().map(Header::getValue).collect(Collectors.toList()));
363 	}
364 
365 	/** {@inheritDoc} */
366 	@Override
367 	public int getContentLength() {
368 		Header contentLength = resp.getFirstHeader("content-length"); //$NON-NLS-1$
369 		if (contentLength == null) {
370 			return -1;
371 		}
372 
373 		try {
374 			int l = Integer.parseInt(contentLength.getValue());
375 			return l < 0 ? -1 : l;
376 		} catch (NumberFormatException e) {
377 			return -1;
378 		}
379 	}
380 
381 	/** {@inheritDoc} */
382 	@Override
383 	public void setInstanceFollowRedirects(boolean followRedirects) {
384 		this.followRedirects = Boolean.valueOf(followRedirects);
385 	}
386 
387 	/** {@inheritDoc} */
388 	@Override
389 	public void setDoOutput(boolean dooutput) {
390 		// TODO: check whether we can really ignore this.
391 	}
392 
393 	/** {@inheritDoc} */
394 	@Override
395 	public void setFixedLengthStreamingMode(int contentLength) {
396 		if (entity != null)
397 			throw new IllegalArgumentException();
398 		entity = new TemporaryBufferEntity(new LocalFile(null));
399 		entity.setContentLength(contentLength);
400 	}
401 
402 	/** {@inheritDoc} */
403 	@Override
404 	public OutputStream getOutputStream() throws IOException {
405 		if (entity == null)
406 			entity = new TemporaryBufferEntity(new LocalFile(null));
407 		return entity.getBuffer();
408 	}
409 
410 	/** {@inheritDoc} */
411 	@Override
412 	public void setChunkedStreamingMode(int chunklen) {
413 		if (entity == null)
414 			entity = new TemporaryBufferEntity(new LocalFile(null));
415 		entity.setChunked(true);
416 	}
417 
418 	/** {@inheritDoc} */
419 	@Override
420 	public String getRequestMethod() {
421 		return method;
422 	}
423 
424 	/** {@inheritDoc} */
425 	@Override
426 	public boolean usingProxy() {
427 		return isUsingProxy;
428 	}
429 
430 	/** {@inheritDoc} */
431 	@Override
432 	public void connect() throws IOException {
433 		execute();
434 	}
435 
436 	/** {@inheritDoc} */
437 	@Override
438 	public void setHostnameVerifier(HostnameVerifier hostnameverifier) {
439 		this.hostnameverifier = hostnameverifier;
440 	}
441 
442 	/** {@inheritDoc} */
443 	@Override
444 	public void configure(KeyManager[] km, TrustManager[] tm,
445 			SecureRandom random) throws KeyManagementException {
446 		getSSLContext().init(km, tm, random);
447 	}
448 }