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.HashMap;
62  import java.util.LinkedList;
63  import java.util.List;
64  import java.util.Map;
65  
66  import javax.net.ssl.HostnameVerifier;
67  import javax.net.ssl.KeyManager;
68  import javax.net.ssl.SSLContext;
69  import javax.net.ssl.TrustManager;
70  
71  import org.apache.http.Header;
72  import org.apache.http.HeaderElement;
73  import org.apache.http.HttpEntity;
74  import org.apache.http.HttpEntityEnclosingRequest;
75  import org.apache.http.HttpHost;
76  import org.apache.http.HttpResponse;
77  import org.apache.http.client.ClientProtocolException;
78  import org.apache.http.client.HttpClient;
79  import org.apache.http.client.config.RequestConfig;
80  import org.apache.http.client.methods.HttpGet;
81  import org.apache.http.client.methods.HttpHead;
82  import org.apache.http.client.methods.HttpPost;
83  import org.apache.http.client.methods.HttpPut;
84  import org.apache.http.client.methods.HttpUriRequest;
85  import org.apache.http.config.Registry;
86  import org.apache.http.config.RegistryBuilder;
87  import org.apache.http.conn.socket.ConnectionSocketFactory;
88  import org.apache.http.conn.socket.PlainConnectionSocketFactory;
89  import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
90  import org.apache.http.impl.client.HttpClientBuilder;
91  import org.apache.http.impl.client.HttpClients;
92  import org.apache.http.impl.conn.BasicHttpClientConnectionManager;
93  import org.eclipse.jgit.transport.http.HttpConnection;
94  import org.eclipse.jgit.transport.http.apache.internal.HttpApacheText;
95  import org.eclipse.jgit.util.TemporaryBuffer;
96  import org.eclipse.jgit.util.TemporaryBuffer.LocalFile;
97  
98  /**
99   * A {@link org.eclipse.jgit.transport.http.HttpConnection} which uses
100  * {@link org.apache.http.client.HttpClient}
101  *
102  * @since 3.3
103  */
104 public class HttpClientConnection implements HttpConnection {
105 	HttpClient client;
106 
107 	URL url;
108 
109 	HttpUriRequest req;
110 
111 	HttpResponse resp = null;
112 
113 	String method = "GET"; //$NON-NLS-1$
114 
115 	private TemporaryBufferEntity entity;
116 
117 	private boolean isUsingProxy = false;
118 
119 	private Proxy proxy;
120 
121 	private Integer timeout = null;
122 
123 	private Integer readTimeout;
124 
125 	private Boolean followRedirects;
126 
127 	private HostnameVerifier hostnameverifier;
128 
129 	SSLContext ctx;
130 
131 	private HttpClient getClient() {
132 		if (client == null) {
133 			HttpClientBuilder clientBuilder = HttpClients.custom();
134 			RequestConfig.Builder configBuilder = RequestConfig.custom();
135 			if (proxy != null && !Proxy.NO_PROXY.equals(proxy)) {
136 				isUsingProxy = true;
137 				InetSocketAddress adr = (InetSocketAddress) proxy.address();
138 				clientBuilder.setProxy(
139 						new HttpHost(adr.getHostName(), adr.getPort()));
140 			}
141 			if (timeout != null) {
142 				configBuilder.setConnectTimeout(timeout.intValue());
143 			}
144 			if (readTimeout != null) {
145 				configBuilder.setSocketTimeout(readTimeout.intValue());
146 			}
147 			if (followRedirects != null) {
148 				configBuilder
149 						.setRedirectsEnabled(followRedirects.booleanValue());
150 			}
151 			if (hostnameverifier != null) {
152 				SSLConnectionSocketFactory sslConnectionFactory = new SSLConnectionSocketFactory(
153 						getSSLContext(), hostnameverifier);
154 				clientBuilder.setSSLSocketFactory(sslConnectionFactory);
155 				Registry<ConnectionSocketFactory> registry = RegistryBuilder
156 						.<ConnectionSocketFactory> create()
157 						.register("https", sslConnectionFactory)
158 						.register("http", PlainConnectionSocketFactory.INSTANCE)
159 						.build();
160 				clientBuilder.setConnectionManager(
161 						new BasicHttpClientConnectionManager(registry));
162 			}
163 			clientBuilder.setDefaultRequestConfig(configBuilder.build());
164 			client = clientBuilder.build();
165 		}
166 
167 		return client;
168 	}
169 
170 	private SSLContext getSSLContext() {
171 		if (ctx == null) {
172 			try {
173 				ctx = SSLContext.getInstance("TLS"); //$NON-NLS-1$
174 			} catch (NoSuchAlgorithmException e) {
175 				throw new IllegalStateException(
176 						HttpApacheText.get().unexpectedSSLContextException, e);
177 			}
178 		}
179 		return ctx;
180 	}
181 
182 	/**
183 	 * Sets the buffer from which to take the request body
184 	 *
185 	 * @param buffer
186 	 */
187 	public void setBuffer(TemporaryBuffer buffer) {
188 		this.entity = new TemporaryBufferEntity(buffer);
189 	}
190 
191 	/**
192 	 * Constructor for HttpClientConnection.
193 	 *
194 	 * @param urlStr
195 	 * @throws MalformedURLException
196 	 */
197 	public HttpClientConnection(String urlStr) throws MalformedURLException {
198 		this(urlStr, null);
199 	}
200 
201 	/**
202 	 * Constructor for HttpClientConnection.
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 	 * Constructor for HttpClientConnection.
215 	 *
216 	 * @param urlStr
217 	 * @param proxy
218 	 * @param cl
219 	 * @throws MalformedURLException
220 	 */
221 	public HttpClientConnection(String urlStr, Proxy proxy, HttpClient cl)
222 			throws MalformedURLException {
223 		this.client = cl;
224 		this.url = new URL(urlStr);
225 		this.proxy = proxy;
226 	}
227 
228 	/** {@inheritDoc} */
229 	@Override
230 	public int getResponseCode() throws IOException {
231 		execute();
232 		return resp.getStatusLine().getStatusCode();
233 	}
234 
235 	/** {@inheritDoc} */
236 	@Override
237 	public URL getURL() {
238 		return url;
239 	}
240 
241 	/** {@inheritDoc} */
242 	@Override
243 	public String getResponseMessage() throws IOException {
244 		execute();
245 		return resp.getStatusLine().getReasonPhrase();
246 	}
247 
248 	private void execute() throws IOException, ClientProtocolException {
249 		if (resp != null) {
250 			return;
251 		}
252 
253 		if (entity == null) {
254 			resp = getClient().execute(req);
255 			return;
256 		}
257 
258 		try {
259 			if (req instanceof HttpEntityEnclosingRequest) {
260 				HttpEntityEnclosingRequest eReq = (HttpEntityEnclosingRequest) req;
261 				eReq.setEntity(entity);
262 			}
263 			resp = getClient().execute(req);
264 		} finally {
265 			entity.close();
266 			entity = null;
267 		}
268 	}
269 
270 	/** {@inheritDoc} */
271 	@Override
272 	public Map<String, List<String>> getHeaderFields() {
273 		Map<String, List<String>> ret = new HashMap<>();
274 		for (Header hdr : resp.getAllHeaders()) {
275 			List<String> list = new LinkedList<>();
276 			for (HeaderElement hdrElem : hdr.getElements())
277 				list.add(hdrElem.toString());
278 			ret.put(hdr.getName(), list);
279 		}
280 		return ret;
281 	}
282 
283 	/** {@inheritDoc} */
284 	@Override
285 	public void setRequestProperty(String name, String value) {
286 		req.addHeader(name, value);
287 	}
288 
289 	/** {@inheritDoc} */
290 	@Override
291 	public void setRequestMethod(String method) throws ProtocolException {
292 		this.method = method;
293 		if (METHOD_GET.equalsIgnoreCase(method)) {
294 			req = new HttpGet(url.toString());
295 		} else if (METHOD_HEAD.equalsIgnoreCase(method)) {
296 			req = new HttpHead(url.toString());
297 		} else if (METHOD_PUT.equalsIgnoreCase(method)) {
298 			req = new HttpPut(url.toString());
299 		} else if (METHOD_POST.equalsIgnoreCase(method)) {
300 			req = new HttpPost(url.toString());
301 		} else {
302 			this.method = null;
303 			throw new UnsupportedOperationException();
304 		}
305 	}
306 
307 	/** {@inheritDoc} */
308 	@Override
309 	public void setUseCaches(boolean usecaches) {
310 		// not needed
311 	}
312 
313 	/** {@inheritDoc} */
314 	@Override
315 	public void setConnectTimeout(int timeout) {
316 		this.timeout = Integer.valueOf(timeout);
317 	}
318 
319 	/** {@inheritDoc} */
320 	@Override
321 	public void setReadTimeout(int readTimeout) {
322 		this.readTimeout = Integer.valueOf(readTimeout);
323 	}
324 
325 	/** {@inheritDoc} */
326 	@Override
327 	public String getContentType() {
328 		HttpEntity responseEntity = resp.getEntity();
329 		if (responseEntity != null) {
330 			Header contentType = responseEntity.getContentType();
331 			if (contentType != null)
332 				return contentType.getValue();
333 		}
334 		return null;
335 	}
336 
337 	/** {@inheritDoc} */
338 	@Override
339 	public InputStream getInputStream() throws IOException {
340 		return resp.getEntity().getContent();
341 	}
342 
343 	// will return only the first field
344 	/** {@inheritDoc} */
345 	@Override
346 	public String getHeaderField(String name) {
347 		Header header = resp.getFirstHeader(name);
348 		return (header == null) ? null : header.getValue();
349 	}
350 
351 	/** {@inheritDoc} */
352 	@Override
353 	public int getContentLength() {
354 		Header contentLength = resp.getFirstHeader("content-length"); //$NON-NLS-1$
355 		if (contentLength == null) {
356 			return -1;
357 		}
358 
359 		try {
360 			int l = Integer.parseInt(contentLength.getValue());
361 			return l < 0 ? -1 : l;
362 		} catch (NumberFormatException e) {
363 			return -1;
364 		}
365 	}
366 
367 	/** {@inheritDoc} */
368 	@Override
369 	public void setInstanceFollowRedirects(boolean followRedirects) {
370 		this.followRedirects = Boolean.valueOf(followRedirects);
371 	}
372 
373 	/** {@inheritDoc} */
374 	@Override
375 	public void setDoOutput(boolean dooutput) {
376 		// TODO: check whether we can really ignore this.
377 	}
378 
379 	/** {@inheritDoc} */
380 	@Override
381 	public void setFixedLengthStreamingMode(int contentLength) {
382 		if (entity != null)
383 			throw new IllegalArgumentException();
384 		entity = new TemporaryBufferEntity(new LocalFile(null));
385 		entity.setContentLength(contentLength);
386 	}
387 
388 	/** {@inheritDoc} */
389 	@Override
390 	public OutputStream getOutputStream() throws IOException {
391 		if (entity == null)
392 			entity = new TemporaryBufferEntity(new LocalFile(null));
393 		return entity.getBuffer();
394 	}
395 
396 	/** {@inheritDoc} */
397 	@Override
398 	public void setChunkedStreamingMode(int chunklen) {
399 		if (entity == null)
400 			entity = new TemporaryBufferEntity(new LocalFile(null));
401 		entity.setChunked(true);
402 	}
403 
404 	/** {@inheritDoc} */
405 	@Override
406 	public String getRequestMethod() {
407 		return method;
408 	}
409 
410 	/** {@inheritDoc} */
411 	@Override
412 	public boolean usingProxy() {
413 		return isUsingProxy;
414 	}
415 
416 	/** {@inheritDoc} */
417 	@Override
418 	public void connect() throws IOException {
419 		execute();
420 	}
421 
422 	/** {@inheritDoc} */
423 	@Override
424 	public void setHostnameVerifier(HostnameVerifier hostnameverifier) {
425 		this.hostnameverifier = hostnameverifier;
426 	}
427 
428 	/** {@inheritDoc} */
429 	@Override
430 	public void configure(KeyManager[] km, TrustManager[] tm,
431 			SecureRandom random) throws KeyManagementException {
432 		getSSLContext().init(km, tm, random);
433 	}
434 }