HttpClientConnection.java

  1. /*
  2.  * Copyright (C) 2013, 2020 Christian Halstrick <christian.halstrick@sap.com> and others
  3.  *
  4.  * This program and the accompanying materials are made available under the
  5.  * terms of the Eclipse Distribution License v. 1.0 which is available at
  6.  * https://www.eclipse.org/org/documents/edl-v10.php.
  7.  *
  8.  * SPDX-License-Identifier: BSD-3-Clause
  9.  */
  10. package org.eclipse.jgit.transport.http.apache;

  11. import static org.eclipse.jgit.util.HttpSupport.METHOD_GET;
  12. import static org.eclipse.jgit.util.HttpSupport.METHOD_HEAD;
  13. import static org.eclipse.jgit.util.HttpSupport.METHOD_POST;
  14. import static org.eclipse.jgit.util.HttpSupport.METHOD_PUT;

  15. import java.io.IOException;
  16. import java.io.InputStream;
  17. import java.io.OutputStream;
  18. import java.net.InetSocketAddress;
  19. import java.net.MalformedURLException;
  20. import java.net.ProtocolException;
  21. import java.net.Proxy;
  22. import java.net.URL;
  23. import java.security.KeyManagementException;
  24. import java.security.NoSuchAlgorithmException;
  25. import java.security.SecureRandom;
  26. import java.util.Arrays;
  27. import java.util.Collections;
  28. import java.util.HashMap;
  29. import java.util.LinkedList;
  30. import java.util.List;
  31. import java.util.Map;
  32. import java.util.stream.Collectors;

  33. import javax.net.ssl.HostnameVerifier;
  34. import javax.net.ssl.KeyManager;
  35. import javax.net.ssl.SSLContext;
  36. import javax.net.ssl.SSLSocket;
  37. import javax.net.ssl.TrustManager;

  38. import org.apache.http.Header;
  39. import org.apache.http.HeaderElement;
  40. import org.apache.http.HttpEntity;
  41. import org.apache.http.HttpEntityEnclosingRequest;
  42. import org.apache.http.HttpHost;
  43. import org.apache.http.HttpResponse;
  44. import org.apache.http.client.ClientProtocolException;
  45. import org.apache.http.client.HttpClient;
  46. import org.apache.http.client.config.RequestConfig;
  47. import org.apache.http.client.methods.HttpGet;
  48. import org.apache.http.client.methods.HttpHead;
  49. import org.apache.http.client.methods.HttpPost;
  50. import org.apache.http.client.methods.HttpPut;
  51. import org.apache.http.client.methods.HttpUriRequest;
  52. import org.apache.http.config.Registry;
  53. import org.apache.http.config.RegistryBuilder;
  54. import org.apache.http.conn.socket.ConnectionSocketFactory;
  55. import org.apache.http.conn.socket.PlainConnectionSocketFactory;
  56. import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
  57. import org.apache.http.impl.client.HttpClientBuilder;
  58. import org.apache.http.impl.client.HttpClients;
  59. import org.apache.http.impl.client.SystemDefaultCredentialsProvider;
  60. import org.apache.http.impl.conn.BasicHttpClientConnectionManager;
  61. import org.apache.http.ssl.SSLContexts;
  62. import org.eclipse.jgit.annotations.NonNull;
  63. import org.eclipse.jgit.transport.http.HttpConnection;
  64. import org.eclipse.jgit.transport.http.apache.internal.HttpApacheText;
  65. import org.eclipse.jgit.util.HttpSupport;
  66. import org.eclipse.jgit.util.TemporaryBuffer;
  67. import org.eclipse.jgit.util.TemporaryBuffer.LocalFile;

  68. /**
  69.  * A {@link org.eclipse.jgit.transport.http.HttpConnection} which uses
  70.  * {@link org.apache.http.client.HttpClient}
  71.  *
  72.  * @since 3.3
  73.  */
  74. public class HttpClientConnection implements HttpConnection {
  75.     HttpClient client;

  76.     URL url;

  77.     HttpUriRequest req;

  78.     HttpResponse resp = null;

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

  80.     private TemporaryBufferEntity entity;

  81.     private boolean isUsingProxy = false;

  82.     private Proxy proxy;

  83.     private Integer timeout = null;

  84.     private Integer readTimeout;

  85.     private Boolean followRedirects;

  86.     private HostnameVerifier hostnameverifier;

  87.     private SSLContext ctx;

  88.     private SSLConnectionSocketFactory socketFactory;

  89.     private boolean usePooling = true;

  90.     private HttpClient getClient() {
  91.         if (client == null) {
  92.             HttpClientBuilder clientBuilder = HttpClients.custom();
  93.             RequestConfig.Builder configBuilder = RequestConfig.custom();
  94.             if (proxy != null && !Proxy.NO_PROXY.equals(proxy)) {
  95.                 isUsingProxy = true;
  96.                 InetSocketAddress adr = (InetSocketAddress) proxy.address();
  97.                 clientBuilder.setProxy(
  98.                         new HttpHost(adr.getHostName(), adr.getPort()));
  99.             }
  100.             if (timeout != null) {
  101.                 configBuilder.setConnectTimeout(timeout.intValue());
  102.             }
  103.             if (readTimeout != null) {
  104.                 configBuilder.setSocketTimeout(readTimeout.intValue());
  105.             }
  106.             if (followRedirects != null) {
  107.                 configBuilder
  108.                         .setRedirectsEnabled(followRedirects.booleanValue());
  109.             }
  110.             boolean pooled = true;
  111.             SSLConnectionSocketFactory sslConnectionFactory;
  112.             if (socketFactory != null) {
  113.                 pooled = usePooling;
  114.                 sslConnectionFactory = socketFactory;
  115.             } else {
  116.                 // Legacy implementation.
  117.                 pooled = (hostnameverifier == null);
  118.                 sslConnectionFactory = getSSLSocketFactory();
  119.             }
  120.             clientBuilder.setSSLSocketFactory(sslConnectionFactory);
  121.             if (!pooled) {
  122.                 Registry<ConnectionSocketFactory> registry = RegistryBuilder
  123.                         .<ConnectionSocketFactory> create()
  124.                         .register("https", sslConnectionFactory)
  125.                         .register("http", PlainConnectionSocketFactory.INSTANCE)
  126.                         .build();
  127.                 clientBuilder.setConnectionManager(
  128.                         new BasicHttpClientConnectionManager(registry));
  129.             }
  130.             clientBuilder.setDefaultRequestConfig(configBuilder.build());
  131.             clientBuilder.setDefaultCredentialsProvider(
  132.                     new SystemDefaultCredentialsProvider());
  133.             client = clientBuilder.build();
  134.         }

  135.         return client;
  136.     }

  137.     void setSSLSocketFactory(@NonNull SSLConnectionSocketFactory factory,
  138.             boolean isDefault) {
  139.         socketFactory = factory;
  140.         usePooling = isDefault;
  141.     }

  142.     private SSLConnectionSocketFactory getSSLSocketFactory() {
  143.         HostnameVerifier verifier = hostnameverifier;
  144.         SSLContext context;
  145.         if (verifier == null) {
  146.             // Use defaults
  147.             context = SSLContexts.createSystemDefault();
  148.             verifier = SSLConnectionSocketFactory.getDefaultHostnameVerifier();
  149.         } else {
  150.             // Using a custom verifier. Attention: configure() must have been
  151.             // called already, otherwise one gets a "context not initialized"
  152.             // exception. In JGit this branch is reached only when hostname
  153.             // verification is switched off, and JGit _does_ call configure()
  154.             // before we get here.
  155.             context = getSSLContext();
  156.         }
  157.         return new SSLConnectionSocketFactory(context, verifier) {

  158.             @Override
  159.             protected void prepareSocket(SSLSocket socket) throws IOException {
  160.                 super.prepareSocket(socket);
  161.                 HttpSupport.configureTLS(socket);
  162.             }
  163.         };
  164.     }

  165.     private SSLContext getSSLContext() {
  166.         if (ctx == null) {
  167.             try {
  168.                 ctx = SSLContext.getInstance("TLS"); //$NON-NLS-1$
  169.             } catch (NoSuchAlgorithmException e) {
  170.                 throw new IllegalStateException(
  171.                         HttpApacheText.get().unexpectedSSLContextException, e);
  172.             }
  173.         }
  174.         return ctx;
  175.     }

  176.     /**
  177.      * Sets the buffer from which to take the request body
  178.      *
  179.      * @param buffer
  180.      */
  181.     public void setBuffer(TemporaryBuffer buffer) {
  182.         this.entity = new TemporaryBufferEntity(buffer);
  183.     }

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

  193.     /**
  194.      * Constructor for HttpClientConnection.
  195.      *
  196.      * @param urlStr
  197.      * @param proxy
  198.      * @throws MalformedURLException
  199.      */
  200.     public HttpClientConnection(String urlStr, Proxy proxy)
  201.             throws MalformedURLException {
  202.         this(urlStr, proxy, null);
  203.     }

  204.     /**
  205.      * Constructor for HttpClientConnection.
  206.      *
  207.      * @param urlStr
  208.      * @param proxy
  209.      * @param cl
  210.      * @throws MalformedURLException
  211.      */
  212.     public HttpClientConnection(String urlStr, Proxy proxy, HttpClient cl)
  213.             throws MalformedURLException {
  214.         this.client = cl;
  215.         this.url = new URL(urlStr);
  216.         this.proxy = proxy;
  217.     }

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

  224.     /** {@inheritDoc} */
  225.     @Override
  226.     public URL getURL() {
  227.         return url;
  228.     }

  229.     /** {@inheritDoc} */
  230.     @Override
  231.     public String getResponseMessage() throws IOException {
  232.         execute();
  233.         return resp.getStatusLine().getReasonPhrase();
  234.     }

  235.     private void execute() throws IOException, ClientProtocolException {
  236.         if (resp != null) {
  237.             return;
  238.         }

  239.         if (entity == null) {
  240.             resp = getClient().execute(req);
  241.             return;
  242.         }

  243.         try {
  244.             if (req instanceof HttpEntityEnclosingRequest) {
  245.                 HttpEntityEnclosingRequest eReq = (HttpEntityEnclosingRequest) req;
  246.                 eReq.setEntity(entity);
  247.             }
  248.             resp = getClient().execute(req);
  249.         } finally {
  250.             entity.close();
  251.             entity = null;
  252.         }
  253.     }

  254.     /** {@inheritDoc} */
  255.     @Override
  256.     public Map<String, List<String>> getHeaderFields() {
  257.         Map<String, List<String>> ret = new HashMap<>();
  258.         for (Header hdr : resp.getAllHeaders()) {
  259.             List<String> list = ret.get(hdr.getName());
  260.             if (list == null) {
  261.                 list = new LinkedList<>();
  262.                 ret.put(hdr.getName(), list);
  263.             }
  264.             for (HeaderElement hdrElem : hdr.getElements()) {
  265.                 list.add(hdrElem.toString());
  266.             }
  267.         }
  268.         return ret;
  269.     }

  270.     /** {@inheritDoc} */
  271.     @Override
  272.     public void setRequestProperty(String name, String value) {
  273.         req.addHeader(name, value);
  274.     }

  275.     /** {@inheritDoc} */
  276.     @Override
  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.     /** {@inheritDoc} */
  293.     @Override
  294.     public void setUseCaches(boolean usecaches) {
  295.         // not needed
  296.     }

  297.     /** {@inheritDoc} */
  298.     @Override
  299.     public void setConnectTimeout(int timeout) {
  300.         this.timeout = Integer.valueOf(timeout);
  301.     }

  302.     /** {@inheritDoc} */
  303.     @Override
  304.     public void setReadTimeout(int readTimeout) {
  305.         this.readTimeout = Integer.valueOf(readTimeout);
  306.     }

  307.     /** {@inheritDoc} */
  308.     @Override
  309.     public String getContentType() {
  310.         HttpEntity responseEntity = resp.getEntity();
  311.         if (responseEntity != null) {
  312.             Header contentType = responseEntity.getContentType();
  313.             if (contentType != null)
  314.                 return contentType.getValue();
  315.         }
  316.         return null;
  317.     }

  318.     /** {@inheritDoc} */
  319.     @Override
  320.     public InputStream getInputStream() throws IOException {
  321.         execute();
  322.         return resp.getEntity().getContent();
  323.     }

  324.     // will return only the first field
  325.     /** {@inheritDoc} */
  326.     @Override
  327.     public String getHeaderField(@NonNull String name) {
  328.         Header header = resp.getFirstHeader(name);
  329.         return (header == null) ? null : header.getValue();
  330.     }

  331.     @Override
  332.     public List<String> getHeaderFields(@NonNull String name) {
  333.         return Collections.unmodifiableList(Arrays.asList(resp.getHeaders(name))
  334.                 .stream().map(Header::getValue).collect(Collectors.toList()));
  335.     }

  336.     /** {@inheritDoc} */
  337.     @Override
  338.     public int getContentLength() {
  339.         Header contentLength = resp.getFirstHeader("content-length"); //$NON-NLS-1$
  340.         if (contentLength == null) {
  341.             return -1;
  342.         }

  343.         try {
  344.             int l = Integer.parseInt(contentLength.getValue());
  345.             return l < 0 ? -1 : l;
  346.         } catch (NumberFormatException e) {
  347.             return -1;
  348.         }
  349.     }

  350.     /** {@inheritDoc} */
  351.     @Override
  352.     public void setInstanceFollowRedirects(boolean followRedirects) {
  353.         this.followRedirects = Boolean.valueOf(followRedirects);
  354.     }

  355.     /** {@inheritDoc} */
  356.     @Override
  357.     public void setDoOutput(boolean dooutput) {
  358.         // TODO: check whether we can really ignore this.
  359.     }

  360.     /** {@inheritDoc} */
  361.     @Override
  362.     public void setFixedLengthStreamingMode(int contentLength) {
  363.         if (entity != null)
  364.             throw new IllegalArgumentException();
  365.         entity = new TemporaryBufferEntity(new LocalFile(null));
  366.         entity.setContentLength(contentLength);
  367.     }

  368.     /** {@inheritDoc} */
  369.     @Override
  370.     public OutputStream getOutputStream() throws IOException {
  371.         if (entity == null)
  372.             entity = new TemporaryBufferEntity(new LocalFile(null));
  373.         return entity.getBuffer();
  374.     }

  375.     /** {@inheritDoc} */
  376.     @Override
  377.     public void setChunkedStreamingMode(int chunklen) {
  378.         if (entity == null)
  379.             entity = new TemporaryBufferEntity(new LocalFile(null));
  380.         entity.setChunked(true);
  381.     }

  382.     /** {@inheritDoc} */
  383.     @Override
  384.     public String getRequestMethod() {
  385.         return method;
  386.     }

  387.     /** {@inheritDoc} */
  388.     @Override
  389.     public boolean usingProxy() {
  390.         return isUsingProxy;
  391.     }

  392.     /** {@inheritDoc} */
  393.     @Override
  394.     public void connect() throws IOException {
  395.         execute();
  396.     }

  397.     /** {@inheritDoc} */
  398.     @Override
  399.     public void setHostnameVerifier(HostnameVerifier hostnameverifier) {
  400.         this.hostnameverifier = hostnameverifier;
  401.     }

  402.     /** {@inheritDoc} */
  403.     @Override
  404.     public void configure(KeyManager[] km, TrustManager[] tm,
  405.             SecureRandom random) throws KeyManagementException {
  406.         getSSLContext().init(km, tm, random);
  407.     }
  408. }