From 3d3df3942a528fb74baff1efb60212b9ebcae83e Mon Sep 17 00:00:00 2001 From: Matthias Sohn Date: Thu, 20 Feb 2014 22:07:48 +0100 Subject: Move Apache httpclient based HTTP support to a separate bundle This move avoids that all consumers of org.eclipse.jgit depend on Apache httpclient. Also add another feature to make this optional for OSGi consumers as well. Change-Id: I5ef5e00c53678b9e1d7cfd54bbca3ff6f1c1c967 Signed-off-by: Matthias Sohn --- .../http/apache/HttpClientConnection.java | 366 +++++++++++++++++++++ .../http/apache/HttpClientConnectionFactory.java | 66 ++++ .../http/apache/TemporaryBufferEntity.java | 109 ++++++ .../http/apache/internal/HttpApacheText.java | 62 ++++ 4 files changed, 603 insertions(+) create mode 100644 org.eclipse.jgit.http.apache/src/org/eclipse/jgit/transport/http/apache/HttpClientConnection.java create mode 100644 org.eclipse.jgit.http.apache/src/org/eclipse/jgit/transport/http/apache/HttpClientConnectionFactory.java create mode 100644 org.eclipse.jgit.http.apache/src/org/eclipse/jgit/transport/http/apache/TemporaryBufferEntity.java create mode 100644 org.eclipse.jgit.http.apache/src/org/eclipse/jgit/transport/http/apache/internal/HttpApacheText.java (limited to 'org.eclipse.jgit.http.apache/src/org') diff --git a/org.eclipse.jgit.http.apache/src/org/eclipse/jgit/transport/http/apache/HttpClientConnection.java b/org.eclipse.jgit.http.apache/src/org/eclipse/jgit/transport/http/apache/HttpClientConnection.java new file mode 100644 index 0000000000..b776119e23 --- /dev/null +++ b/org.eclipse.jgit.http.apache/src/org/eclipse/jgit/transport/http/apache/HttpClientConnection.java @@ -0,0 +1,366 @@ +/* + * Copyright (C) 2013 Christian Halstrick + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.eclipse.jgit.transport.http.apache; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.InetSocketAddress; +import java.net.MalformedURLException; +import java.net.ProtocolException; +import java.net.Proxy; +import java.net.URL; +import java.security.KeyManagementException; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.security.cert.X509Certificate; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.KeyManager; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLException; +import javax.net.ssl.SSLSession; +import javax.net.ssl.SSLSocket; +import javax.net.ssl.TrustManager; + +import org.apache.http.Header; +import org.apache.http.HeaderElement; +import org.apache.http.HttpEntity; +import org.apache.http.HttpEntityEnclosingRequest; +import org.apache.http.HttpHost; +import org.apache.http.HttpResponse; +import org.apache.http.client.ClientProtocolException; +import org.apache.http.client.HttpClient; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.client.methods.HttpPut; +import org.apache.http.client.methods.HttpUriRequest; +import org.apache.http.client.params.ClientPNames; +import org.apache.http.conn.params.ConnRoutePNames; +import org.apache.http.conn.scheme.Scheme; +import org.apache.http.conn.ssl.SSLSocketFactory; +import org.apache.http.conn.ssl.X509HostnameVerifier; +import org.apache.http.impl.client.DefaultHttpClient; +import org.apache.http.params.CoreConnectionPNames; +import org.apache.http.params.HttpParams; +import org.eclipse.jgit.transport.http.HttpConnection; +import org.eclipse.jgit.transport.http.apache.internal.HttpApacheText; +import org.eclipse.jgit.util.TemporaryBuffer; +import org.eclipse.jgit.util.TemporaryBuffer.LocalFile; + +/** + * A {@link HttpConnection} which uses {@link HttpClient} + * + * @since 3.3 + */ +public class HttpClientConnection implements HttpConnection { + HttpClient client; + + String urlStr; + + HttpUriRequest req; + + HttpResponse resp = null; + + String method = "GET"; //$NON-NLS-1$ + + private TemporaryBufferEntity entity; + + private boolean isUsingProxy = false; + + private Proxy proxy; + + private Integer timeout = null; + + private Integer readTimeout; + + private Boolean followRedirects; + + private X509HostnameVerifier hostnameverifier; + + SSLContext ctx; + + private HttpClient getClient() { + if (client == null) + client = new DefaultHttpClient(); + HttpParams params = client.getParams(); + if (proxy != null && !Proxy.NO_PROXY.equals(proxy)) { + isUsingProxy = true; + InetSocketAddress adr = (InetSocketAddress) proxy.address(); + params.setParameter(ConnRoutePNames.DEFAULT_PROXY, + new HttpHost(adr.getHostName(), adr.getPort())); + } + if (timeout != null) + params.setIntParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, + timeout.intValue()); + if (readTimeout != null) + params.setIntParameter(CoreConnectionPNames.SO_TIMEOUT, + readTimeout.intValue()); + if (followRedirects != null) + params.setBooleanParameter(ClientPNames.HANDLE_REDIRECTS, + followRedirects.booleanValue()); + if (hostnameverifier != null) { + SSLSocketFactory sf; + sf = new SSLSocketFactory(getSSLContext(), hostnameverifier); + Scheme https = new Scheme("https", 443, sf); //$NON-NLS-1$ + client.getConnectionManager().getSchemeRegistry().register(https); + } + + return client; + } + + private SSLContext getSSLContext() { + if (ctx == null) { + try { + ctx = SSLContext.getInstance("TLS"); //$NON-NLS-1$ + } catch (NoSuchAlgorithmException e) { + throw new IllegalStateException( + HttpApacheText.get().unexpectedSSLContextException, e); + } + } + return ctx; + } + + /** + * Sets the buffer from which to take the request body + * + * @param buffer + */ + public void setBuffer(TemporaryBuffer buffer) { + this.entity = new TemporaryBufferEntity(buffer); + } + + /** + * @param urlStr + */ + public HttpClientConnection(String urlStr) { + this(urlStr, null); + } + + /** + * @param urlStr + * @param proxy + */ + public HttpClientConnection(String urlStr, Proxy proxy) { + this(urlStr, proxy, null); + } + + /** + * @param urlStr + * @param proxy + * @param cl + */ + public HttpClientConnection(String urlStr, Proxy proxy, HttpClient cl) { + this.client = cl; + this.urlStr = urlStr; + this.proxy = proxy; + } + + public int getResponseCode() throws IOException { + execute(); + return resp.getStatusLine().getStatusCode(); + } + + public URL getURL() { + try { + return new URL(urlStr); + } catch (MalformedURLException e) { + return null; + } + } + + public String getResponseMessage() throws IOException { + execute(); + return resp.getStatusLine().getReasonPhrase(); + } + + private void execute() throws IOException, ClientProtocolException { + if (resp == null) + if (entity != null) { + if (req instanceof HttpEntityEnclosingRequest) { + HttpEntityEnclosingRequest eReq = (HttpEntityEnclosingRequest) req; + eReq.setEntity(entity); + } + resp = getClient().execute(req); + entity.getBuffer().close(); + entity = null; + } else + resp = getClient().execute(req); + } + + public Map> getHeaderFields() { + Map> ret = new HashMap>(); + for (Header hdr : resp.getAllHeaders()) { + List list = new LinkedList(); + for (HeaderElement hdrElem : hdr.getElements()) + list.add(hdrElem.toString()); + ret.put(hdr.getName(), list); + } + return ret; + } + + public void setRequestProperty(String name, String value) { + req.addHeader(name, value); + } + + public void setRequestMethod(String method) throws ProtocolException { + this.method = method; + if ("GET".equalsIgnoreCase(method)) //$NON-NLS-1$ + req = new HttpGet(urlStr); + else if ("PUT".equalsIgnoreCase(method)) //$NON-NLS-1$ + req = new HttpPut(urlStr); + else if ("POST".equalsIgnoreCase(method)) //$NON-NLS-1$ + req = new HttpPost(urlStr); + else { + this.method = null; + throw new UnsupportedOperationException(); + } + } + + public void setUseCaches(boolean usecaches) { + // not needed + } + + public void setConnectTimeout(int timeout) { + this.timeout = new Integer(timeout); + } + + public void setReadTimeout(int readTimeout) { + this.readTimeout = new Integer(readTimeout); + } + + public String getContentType() { + HttpEntity responseEntity = resp.getEntity(); + if (responseEntity != null) { + Header contentType = responseEntity.getContentType(); + if (contentType != null) + return contentType.getValue(); + } + return null; + } + + public InputStream getInputStream() throws IOException { + return resp.getEntity().getContent(); + } + + // will return only the first field + public String getHeaderField(String name) { + Header header = resp.getFirstHeader(name); + return (header == null) ? null : header.getValue(); + } + + public int getContentLength() { + return Integer.parseInt(resp.getFirstHeader("content-length") //$NON-NLS-1$ + .getValue()); + } + + public void setInstanceFollowRedirects(boolean followRedirects) { + this.followRedirects = new Boolean(followRedirects); + } + + public void setDoOutput(boolean dooutput) { + // TODO: check whether we can really ignore this. + } + + public void setFixedLengthStreamingMode(int contentLength) { + if (entity != null) + throw new IllegalArgumentException(); + entity = new TemporaryBufferEntity(new LocalFile()); + entity.setContentLength(contentLength); + } + + public OutputStream getOutputStream() throws IOException { + if (entity == null) + entity = new TemporaryBufferEntity(new LocalFile()); + return entity.getBuffer(); + } + + public void setChunkedStreamingMode(int chunklen) { + if (entity == null) + entity = new TemporaryBufferEntity(new LocalFile()); + entity.setChunked(true); + } + + public String getRequestMethod() { + return method; + } + + public boolean usingProxy() { + return isUsingProxy; + } + + public void connect() throws IOException { + execute(); + } + + public void setHostnameVerifier(final HostnameVerifier hostnameverifier) { + this.hostnameverifier = new X509HostnameVerifier() { + public boolean verify(String hostname, SSLSession session) { + return hostnameverifier.verify(hostname, session); + } + + public void verify(String host, String[] cns, String[] subjectAlts) + throws SSLException { + throw new UnsupportedOperationException(); // TODO message + } + + public void verify(String host, X509Certificate cert) + throws SSLException { + throw new UnsupportedOperationException(); // TODO message + } + + public void verify(String host, SSLSocket ssl) throws IOException { + hostnameverifier.verify(host, ssl.getSession()); + } + }; + } + + public void configure(KeyManager[] km, TrustManager[] tm, + SecureRandom random) throws KeyManagementException { + getSSLContext().init(km, tm, random); + } +} diff --git a/org.eclipse.jgit.http.apache/src/org/eclipse/jgit/transport/http/apache/HttpClientConnectionFactory.java b/org.eclipse.jgit.http.apache/src/org/eclipse/jgit/transport/http/apache/HttpClientConnectionFactory.java new file mode 100644 index 0000000000..fe1eef484a --- /dev/null +++ b/org.eclipse.jgit.http.apache/src/org/eclipse/jgit/transport/http/apache/HttpClientConnectionFactory.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2013 Christian Halstrick + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.eclipse.jgit.transport.http.apache; + +import java.io.IOException; +import java.net.Proxy; +import java.net.URL; + +import org.eclipse.jgit.transport.http.HttpConnection; +import org.eclipse.jgit.transport.http.HttpConnectionFactory; + +/** + * A factory returning instances of {@link HttpClientConnection} + * + * @since 3.3 + */ +public class HttpClientConnectionFactory implements HttpConnectionFactory { + public HttpConnection create(URL url) throws IOException { + return new HttpClientConnection(url.toString()); + } + + public HttpConnection create(URL url, Proxy proxy) + throws IOException { + return new HttpClientConnection(url.toString(), proxy); + } +} diff --git a/org.eclipse.jgit.http.apache/src/org/eclipse/jgit/transport/http/apache/TemporaryBufferEntity.java b/org.eclipse.jgit.http.apache/src/org/eclipse/jgit/transport/http/apache/TemporaryBufferEntity.java new file mode 100644 index 0000000000..1ff168e237 --- /dev/null +++ b/org.eclipse.jgit.http.apache/src/org/eclipse/jgit/transport/http/apache/TemporaryBufferEntity.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2014 Christian Halstrick + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.eclipse.jgit.transport.http.apache; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import org.apache.http.HttpEntity; +import org.apache.http.entity.AbstractHttpEntity; +import org.eclipse.jgit.util.TemporaryBuffer; + +/** + * A {@link HttpEntity} which takes it's content from a {@link TemporaryBuffer} + * + * @since 3.3 + */ +public class TemporaryBufferEntity extends AbstractHttpEntity { + private TemporaryBuffer buffer; + + private Integer contentLength; + + /** + * Construct a new {@link HttpEntity} which will contain the content stored + * in the specified buffer + * + * @param buffer + */ + public TemporaryBufferEntity(TemporaryBuffer buffer) { + this.buffer = buffer; + } + + /** + * @return buffer containing the content + */ + public TemporaryBuffer getBuffer() { + return buffer; + } + + public boolean isRepeatable() { + return true; + } + + public long getContentLength() { + if (contentLength != null) + return contentLength.intValue(); + return buffer.length(); + } + + public InputStream getContent() throws IOException, IllegalStateException { + return buffer.openInputStream(); + } + + public void writeTo(OutputStream outstream) throws IOException { + // TODO: dont we need a progressmonitor + buffer.writeTo(outstream, null); + } + + public boolean isStreaming() { + return false; + } + + /** + * @param contentLength + */ + public void setContentLength(int contentLength) { + this.contentLength = new Integer(contentLength); + } +} diff --git a/org.eclipse.jgit.http.apache/src/org/eclipse/jgit/transport/http/apache/internal/HttpApacheText.java b/org.eclipse.jgit.http.apache/src/org/eclipse/jgit/transport/http/apache/internal/HttpApacheText.java new file mode 100644 index 0000000000..38f1578199 --- /dev/null +++ b/org.eclipse.jgit.http.apache/src/org/eclipse/jgit/transport/http/apache/internal/HttpApacheText.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2014, SAP AG + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.transport.http.apache.internal; + +import org.eclipse.jgit.nls.NLS; +import org.eclipse.jgit.nls.TranslationBundle; + +/** + * Translation bundle for archivers + */ +public class HttpApacheText extends TranslationBundle { + /** + * @return an instance of this translation bundle + */ + public static HttpApacheText get() { + return NLS.getBundleFor(HttpApacheText.class); + } + + // @formatter:off + /***/ public String unexpectedSSLContextException; +} -- cgit v1.2.3