/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.zeppelin.notebook.repo.zeppelinhub.rest; import java.io.IOException; import java.io.InputStream; import java.net.URI; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import javax.net.ssl.SSLContext; import org.apache.commons.io.IOUtils; import org.apache.commons.lang.StringUtils; import org.apache.http.HttpHost; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpDelete; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpHead; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpPut; import org.apache.http.client.methods.HttpRequestBase; import org.apache.http.concurrent.FutureCallback; import org.apache.http.config.Registry; import org.apache.http.config.RegistryBuilder; import org.apache.http.conn.ssl.BrowserCompatHostnameVerifier; import org.apache.http.conn.ssl.SSLContexts; import org.apache.http.conn.ssl.X509HostnameVerifier; import org.apache.http.impl.client.DefaultRedirectStrategy; import org.apache.http.impl.nio.client.CloseableHttpAsyncClient; import org.apache.http.impl.nio.client.HttpAsyncClientBuilder; import org.apache.http.impl.nio.client.HttpAsyncClients; import org.apache.http.impl.nio.conn.PoolingNHttpClientConnectionManager; import org.apache.http.impl.nio.reactor.DefaultConnectingIOReactor; import org.apache.http.nio.conn.NoopIOSessionStrategy; import org.apache.http.nio.conn.SchemeIOSessionStrategy; import org.apache.http.nio.conn.ssl.SSLIOSessionStrategy; import org.apache.http.nio.reactor.ConnectingIOReactor; import org.apache.http.nio.reactor.IOReactorException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * This is http client class for the case of proxy usage * jetty-client has issue with https over proxy for 9.2.x * https://github.com/eclipse/jetty.project/issues/408 * https://github.com/eclipse/jetty.project/issues/827 * */ public class HttpProxyClient { private static final Logger LOG = LoggerFactory.getLogger(HttpProxyClient.class); public static final String ZEPPELIN_TOKEN_HEADER = "X-Zeppelin-Token"; private CloseableHttpAsyncClient client; private URI proxyUri; public static HttpProxyClient newInstance(URI proxyUri) { return new HttpProxyClient(proxyUri); } private HttpProxyClient(URI uri) { this.proxyUri = uri; client = getAsyncProxyHttpClient(proxyUri); client.start(); } public URI getProxyUri() { return proxyUri; } private CloseableHttpAsyncClient getAsyncProxyHttpClient(URI proxyUri) { LOG.info("Creating async proxy http client"); PoolingNHttpClientConnectionManager cm = getAsyncConnectionManager(); HttpHost proxy = new HttpHost(proxyUri.getHost(), proxyUri.getPort()); HttpAsyncClientBuilder clientBuilder = HttpAsyncClients.custom(); if (cm != null) { clientBuilder = clientBuilder.setConnectionManager(cm); } if (proxy != null) { clientBuilder = clientBuilder.setProxy(proxy); } clientBuilder = setRedirects(clientBuilder); return clientBuilder.build(); } private PoolingNHttpClientConnectionManager getAsyncConnectionManager() { ConnectingIOReactor ioReactor = null; PoolingNHttpClientConnectionManager cm = null; try { ioReactor = new DefaultConnectingIOReactor(); // ssl setup SSLContext sslcontext = SSLContexts.createSystemDefault(); X509HostnameVerifier hostnameVerifier = new BrowserCompatHostnameVerifier(); @SuppressWarnings("deprecation") Registry<SchemeIOSessionStrategy> sessionStrategyRegistry = RegistryBuilder .<SchemeIOSessionStrategy>create() .register("http", NoopIOSessionStrategy.INSTANCE) .register("https", new SSLIOSessionStrategy(sslcontext, hostnameVerifier)) .build(); cm = new PoolingNHttpClientConnectionManager(ioReactor, sessionStrategyRegistry); } catch (IOReactorException e) { LOG.error("Couldn't initialize multi-threaded async client ", e); return null; } return cm; } private HttpAsyncClientBuilder setRedirects(HttpAsyncClientBuilder clientBuilder) { clientBuilder.setRedirectStrategy(new DefaultRedirectStrategy() { /** Redirectable methods. */ private String[] REDIRECT_METHODS = new String[] { HttpGet.METHOD_NAME, HttpPost.METHOD_NAME, HttpPut.METHOD_NAME, HttpDelete.METHOD_NAME, HttpHead.METHOD_NAME }; @Override protected boolean isRedirectable(String method) { for (String m : REDIRECT_METHODS) { if (m.equalsIgnoreCase(method)) { return true; } } return false; } }); return clientBuilder; } public String sendToZeppelinHub(HttpRequestBase request, boolean withResponse) throws IOException { return withResponse ? sendAndGetResponse(request) : sendWithoutResponseBody(request); } private String sendWithoutResponseBody(HttpRequestBase request) throws IOException { FutureCallback<HttpResponse> callback = getCallback(request); client.execute(request, callback); return StringUtils.EMPTY; } private String sendAndGetResponse(HttpRequestBase request) throws IOException { String data = StringUtils.EMPTY; try { HttpResponse response = client.execute(request, null).get(30, TimeUnit.SECONDS); int code = response.getStatusLine().getStatusCode(); if (code == 200) { try (InputStream responseContent = response.getEntity().getContent()) { data = IOUtils.toString(responseContent, "UTF-8"); } } else { LOG.error("ZeppelinHub {} {} returned with status {} ", request.getMethod(), request.getURI(), code); throw new IOException("Cannot perform " + request.getMethod() + " request to ZeppelinHub"); } } catch (InterruptedException | ExecutionException | TimeoutException | NullPointerException e) { throw new IOException(e); } return data; } private FutureCallback<HttpResponse> getCallback(final HttpRequestBase request) { return new FutureCallback<HttpResponse>() { public void completed(final HttpResponse response) { request.releaseConnection(); LOG.info("Note {} completed with {} status", request.getMethod(), response.getStatusLine()); } public void failed(final Exception ex) { request.releaseConnection(); LOG.error("Note {} failed with {} message", request.getMethod(), ex.getMessage()); } public void cancelled() { request.releaseConnection(); LOG.info("Note {} was canceled", request.getMethod()); } }; } public void stop() { try { client.close(); } catch (Exception e) { LOG.error("Failed to close proxy client ", e); } } }