HttpClientConnection.java

  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. import static org.eclipse.jgit.util.HttpSupport.METHOD_GET;
  45. import static org.eclipse.jgit.util.HttpSupport.METHOD_HEAD;
  46. import static org.eclipse.jgit.util.HttpSupport.METHOD_POST;
  47. import static org.eclipse.jgit.util.HttpSupport.METHOD_PUT;

  48. import java.io.IOException;
  49. import java.io.InputStream;
  50. import java.io.OutputStream;
  51. import java.net.InetSocketAddress;
  52. import java.net.MalformedURLException;
  53. import java.net.ProtocolException;
  54. import java.net.Proxy;
  55. import java.net.URL;
  56. import java.security.KeyManagementException;
  57. import java.security.NoSuchAlgorithmException;
  58. import java.security.SecureRandom;
  59. import java.util.Arrays;
  60. import java.util.Collections;
  61. import java.util.HashMap;
  62. import java.util.LinkedList;
  63. import java.util.List;
  64. import java.util.Map;
  65. import java.util.stream.Collectors;

  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. import org.apache.http.Header;
  71. import org.apache.http.HeaderElement;
  72. import org.apache.http.HttpEntity;
  73. import org.apache.http.HttpEntityEnclosingRequest;
  74. import org.apache.http.HttpHost;
  75. import org.apache.http.HttpResponse;
  76. import org.apache.http.client.ClientProtocolException;
  77. import org.apache.http.client.HttpClient;
  78. import org.apache.http.client.config.RequestConfig;
  79. import org.apache.http.client.methods.HttpGet;
  80. import org.apache.http.client.methods.HttpHead;
  81. import org.apache.http.client.methods.HttpPost;
  82. import org.apache.http.client.methods.HttpPut;
  83. import org.apache.http.client.methods.HttpUriRequest;
  84. import org.apache.http.config.Registry;
  85. import org.apache.http.config.RegistryBuilder;
  86. import org.apache.http.conn.socket.ConnectionSocketFactory;
  87. import org.apache.http.conn.socket.PlainConnectionSocketFactory;
  88. import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
  89. import org.apache.http.impl.client.HttpClientBuilder;
  90. import org.apache.http.impl.client.HttpClients;
  91. import org.apache.http.impl.conn.BasicHttpClientConnectionManager;
  92. import org.eclipse.jgit.annotations.NonNull;
  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.  * A {@link org.eclipse.jgit.transport.http.HttpConnection} which uses
  99.  * {@link org.apache.http.client.HttpClient}
  100.  *
  101.  * @since 3.3
  102.  */
  103. public class HttpClientConnection implements HttpConnection {
  104.     HttpClient client;

  105.     URL url;

  106.     HttpUriRequest req;

  107.     HttpResponse resp = null;

  108.     String method = "GET"; //$NON-NLS-1$

  109.     private TemporaryBufferEntity entity;

  110.     private boolean isUsingProxy = false;

  111.     private Proxy proxy;

  112.     private Integer timeout = null;

  113.     private Integer readTimeout;

  114.     private Boolean followRedirects;

  115.     private HostnameVerifier hostnameverifier;

  116.     SSLContext ctx;

  117.     private HttpClient getClient() {
  118.         if (client == null) {
  119.             HttpClientBuilder clientBuilder = HttpClients.custom();
  120.             RequestConfig.Builder configBuilder = RequestConfig.custom();
  121.             if (proxy != null && !Proxy.NO_PROXY.equals(proxy)) {
  122.                 isUsingProxy = true;
  123.                 InetSocketAddress adr = (InetSocketAddress) proxy.address();
  124.                 clientBuilder.setProxy(
  125.                         new HttpHost(adr.getHostName(), adr.getPort()));
  126.             }
  127.             if (timeout != null) {
  128.                 configBuilder.setConnectTimeout(timeout.intValue());
  129.             }
  130.             if (readTimeout != null) {
  131.                 configBuilder.setSocketTimeout(readTimeout.intValue());
  132.             }
  133.             if (followRedirects != null) {
  134.                 configBuilder
  135.                         .setRedirectsEnabled(followRedirects.booleanValue());
  136.             }
  137.             if (hostnameverifier != null) {
  138.                 SSLConnectionSocketFactory sslConnectionFactory = new SSLConnectionSocketFactory(
  139.                         getSSLContext(), hostnameverifier);
  140.                 clientBuilder.setSSLSocketFactory(sslConnectionFactory);
  141.                 Registry<ConnectionSocketFactory> registry = RegistryBuilder
  142.                         .<ConnectionSocketFactory> create()
  143.                         .register("https", sslConnectionFactory)
  144.                         .register("http", PlainConnectionSocketFactory.INSTANCE)
  145.                         .build();
  146.                 clientBuilder.setConnectionManager(
  147.                         new BasicHttpClientConnectionManager(registry));
  148.             }
  149.             clientBuilder.setDefaultRequestConfig(configBuilder.build());
  150.             client = clientBuilder.build();
  151.         }

  152.         return client;
  153.     }

  154.     private SSLContext getSSLContext() {
  155.         if (ctx == null) {
  156.             try {
  157.                 ctx = SSLContext.getInstance("TLS"); //$NON-NLS-1$
  158.             } catch (NoSuchAlgorithmException e) {
  159.                 throw new IllegalStateException(
  160.                         HttpApacheText.get().unexpectedSSLContextException, e);
  161.             }
  162.         }
  163.         return ctx;
  164.     }

  165.     /**
  166.      * Sets the buffer from which to take the request body
  167.      *
  168.      * @param buffer
  169.      */
  170.     public void setBuffer(TemporaryBuffer buffer) {
  171.         this.entity = new TemporaryBufferEntity(buffer);
  172.     }

  173.     /**
  174.      * Constructor for HttpClientConnection.
  175.      *
  176.      * @param urlStr
  177.      * @throws MalformedURLException
  178.      */
  179.     public HttpClientConnection(String urlStr) throws MalformedURLException {
  180.         this(urlStr, null);
  181.     }

  182.     /**
  183.      * Constructor for HttpClientConnection.
  184.      *
  185.      * @param urlStr
  186.      * @param proxy
  187.      * @throws MalformedURLException
  188.      */
  189.     public HttpClientConnection(String urlStr, Proxy proxy)
  190.             throws MalformedURLException {
  191.         this(urlStr, proxy, null);
  192.     }

  193.     /**
  194.      * Constructor for HttpClientConnection.
  195.      *
  196.      * @param urlStr
  197.      * @param proxy
  198.      * @param cl
  199.      * @throws MalformedURLException
  200.      */
  201.     public HttpClientConnection(String urlStr, Proxy proxy, HttpClient cl)
  202.             throws MalformedURLException {
  203.         this.client = cl;
  204.         this.url = new URL(urlStr);
  205.         this.proxy = proxy;
  206.     }

  207.     /** {@inheritDoc} */
  208.     @Override
  209.     public int getResponseCode() throws IOException {
  210.         execute();
  211.         return resp.getStatusLine().getStatusCode();
  212.     }

  213.     /** {@inheritDoc} */
  214.     @Override
  215.     public URL getURL() {
  216.         return url;
  217.     }

  218.     /** {@inheritDoc} */
  219.     @Override
  220.     public String getResponseMessage() throws IOException {
  221.         execute();
  222.         return resp.getStatusLine().getReasonPhrase();
  223.     }

  224.     private void execute() throws IOException, ClientProtocolException {
  225.         if (resp != null) {
  226.             return;
  227.         }

  228.         if (entity == null) {
  229.             resp = getClient().execute(req);
  230.             return;
  231.         }

  232.         try {
  233.             if (req instanceof HttpEntityEnclosingRequest) {
  234.                 HttpEntityEnclosingRequest eReq = (HttpEntityEnclosingRequest) req;
  235.                 eReq.setEntity(entity);
  236.             }
  237.             resp = getClient().execute(req);
  238.         } finally {
  239.             entity.close();
  240.             entity = null;
  241.         }
  242.     }

  243.     /** {@inheritDoc} */
  244.     @Override
  245.     public Map<String, List<String>> getHeaderFields() {
  246.         Map<String, List<String>> ret = new HashMap<>();
  247.         for (Header hdr : resp.getAllHeaders()) {
  248.             List<String> list = ret.get(hdr.getName());
  249.             if (list == null) {
  250.                 list = new LinkedList<>();
  251.                 ret.put(hdr.getName(), list);
  252.             }
  253.             for (HeaderElement hdrElem : hdr.getElements()) {
  254.                 list.add(hdrElem.toString());
  255.             }
  256.         }
  257.         return ret;
  258.     }

  259.     /** {@inheritDoc} */
  260.     @Override
  261.     public void setRequestProperty(String name, String value) {
  262.         req.addHeader(name, value);
  263.     }

  264.     /** {@inheritDoc} */
  265.     @Override
  266.     public void setRequestMethod(String method) throws ProtocolException {
  267.         this.method = method;
  268.         if (METHOD_GET.equalsIgnoreCase(method)) {
  269.             req = new HttpGet(url.toString());
  270.         } else if (METHOD_HEAD.equalsIgnoreCase(method)) {
  271.             req = new HttpHead(url.toString());
  272.         } else if (METHOD_PUT.equalsIgnoreCase(method)) {
  273.             req = new HttpPut(url.toString());
  274.         } else if (METHOD_POST.equalsIgnoreCase(method)) {
  275.             req = new HttpPost(url.toString());
  276.         } else {
  277.             this.method = null;
  278.             throw new UnsupportedOperationException();
  279.         }
  280.     }

  281.     /** {@inheritDoc} */
  282.     @Override
  283.     public void setUseCaches(boolean usecaches) {
  284.         // not needed
  285.     }

  286.     /** {@inheritDoc} */
  287.     @Override
  288.     public void setConnectTimeout(int timeout) {
  289.         this.timeout = Integer.valueOf(timeout);
  290.     }

  291.     /** {@inheritDoc} */
  292.     @Override
  293.     public void setReadTimeout(int readTimeout) {
  294.         this.readTimeout = Integer.valueOf(readTimeout);
  295.     }

  296.     /** {@inheritDoc} */
  297.     @Override
  298.     public String getContentType() {
  299.         HttpEntity responseEntity = resp.getEntity();
  300.         if (responseEntity != null) {
  301.             Header contentType = responseEntity.getContentType();
  302.             if (contentType != null)
  303.                 return contentType.getValue();
  304.         }
  305.         return null;
  306.     }

  307.     /** {@inheritDoc} */
  308.     @Override
  309.     public InputStream getInputStream() throws IOException {
  310.         execute();
  311.         return resp.getEntity().getContent();
  312.     }

  313.     // will return only the first field
  314.     /** {@inheritDoc} */
  315.     @Override
  316.     public String getHeaderField(@NonNull String name) {
  317.         Header header = resp.getFirstHeader(name);
  318.         return (header == null) ? null : header.getValue();
  319.     }

  320.     @Override
  321.     public List<String> getHeaderFields(@NonNull String name) {
  322.         return Collections.unmodifiableList(Arrays.asList(resp.getHeaders(name))
  323.                 .stream().map(Header::getValue).collect(Collectors.toList()));
  324.     }

  325.     /** {@inheritDoc} */
  326.     @Override
  327.     public int getContentLength() {
  328.         Header contentLength = resp.getFirstHeader("content-length"); //$NON-NLS-1$
  329.         if (contentLength == null) {
  330.             return -1;
  331.         }

  332.         try {
  333.             int l = Integer.parseInt(contentLength.getValue());
  334.             return l < 0 ? -1 : l;
  335.         } catch (NumberFormatException e) {
  336.             return -1;
  337.         }
  338.     }

  339.     /** {@inheritDoc} */
  340.     @Override
  341.     public void setInstanceFollowRedirects(boolean followRedirects) {
  342.         this.followRedirects = Boolean.valueOf(followRedirects);
  343.     }

  344.     /** {@inheritDoc} */
  345.     @Override
  346.     public void setDoOutput(boolean dooutput) {
  347.         // TODO: check whether we can really ignore this.
  348.     }

  349.     /** {@inheritDoc} */
  350.     @Override
  351.     public void setFixedLengthStreamingMode(int contentLength) {
  352.         if (entity != null)
  353.             throw new IllegalArgumentException();
  354.         entity = new TemporaryBufferEntity(new LocalFile(null));
  355.         entity.setContentLength(contentLength);
  356.     }

  357.     /** {@inheritDoc} */
  358.     @Override
  359.     public OutputStream getOutputStream() throws IOException {
  360.         if (entity == null)
  361.             entity = new TemporaryBufferEntity(new LocalFile(null));
  362.         return entity.getBuffer();
  363.     }

  364.     /** {@inheritDoc} */
  365.     @Override
  366.     public void setChunkedStreamingMode(int chunklen) {
  367.         if (entity == null)
  368.             entity = new TemporaryBufferEntity(new LocalFile(null));
  369.         entity.setChunked(true);
  370.     }

  371.     /** {@inheritDoc} */
  372.     @Override
  373.     public String getRequestMethod() {
  374.         return method;
  375.     }

  376.     /** {@inheritDoc} */
  377.     @Override
  378.     public boolean usingProxy() {
  379.         return isUsingProxy;
  380.     }

  381.     /** {@inheritDoc} */
  382.     @Override
  383.     public void connect() throws IOException {
  384.         execute();
  385.     }

  386.     /** {@inheritDoc} */
  387.     @Override
  388.     public void setHostnameVerifier(HostnameVerifier hostnameverifier) {
  389.         this.hostnameverifier = hostnameverifier;
  390.     }

  391.     /** {@inheritDoc} */
  392.     @Override
  393.     public void configure(KeyManager[] km, TrustManager[] tm,
  394.             SecureRandom random) throws KeyManagementException {
  395.         getSSLContext().init(km, tm, random);
  396.     }
  397. }