/* * SoapUI, Copyright (C) 2004-2016 SmartBear Software * * Licensed under the EUPL, Version 1.1 or - as soon as they will be approved by the European Commission - subsequent * versions of the EUPL (the "Licence"); * You may not use this work except in compliance with the Licence. * You may obtain a copy of the Licence at: * * http://ec.europa.eu/idabc/eupl * * Unless required by applicable law or agreed to in writing, software distributed under the Licence is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the Licence for the specific language governing permissions and limitations * under the Licence. */ package com.eviware.soapui.impl.wsdl.support.http; import com.eviware.soapui.SoapUI; import com.eviware.soapui.SoapUISystemProperties; import com.eviware.soapui.impl.wsdl.submit.transports.http.ExtendedHttpMethod; import com.eviware.soapui.impl.wsdl.submit.transports.http.support.metrics.SoapUIMetrics; import com.eviware.soapui.impl.wsdl.support.CompressionSupport; import com.eviware.soapui.model.propertyexpansion.PropertyExpander; import com.eviware.soapui.model.settings.Settings; import com.eviware.soapui.model.settings.SettingsListener; import com.eviware.soapui.settings.HttpSettings; import com.eviware.soapui.settings.SSLSettings; import org.apache.commons.ssl.KeyMaterial; import org.apache.http.Header; import org.apache.http.HttpClientConnection; import org.apache.http.HttpException; import org.apache.http.HttpRequest; import org.apache.http.HttpResponse; import org.apache.http.HttpStatus; import org.apache.http.client.ClientProtocolException; import org.apache.http.client.CookieStore; import org.apache.http.client.protocol.ClientContext; import org.apache.http.conn.ClientConnectionManager; import org.apache.http.conn.scheme.PlainSocketFactory; import org.apache.http.conn.scheme.Scheme; import org.apache.http.conn.scheme.SchemeRegistry; import org.apache.http.impl.client.BasicCookieStore; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.impl.client.RequestWrapper; import org.apache.http.params.CoreConnectionPNames; import org.apache.http.protocol.BasicHttpContext; import org.apache.http.protocol.HttpContext; import org.apache.http.protocol.HttpProcessor; import org.apache.http.protocol.HttpRequestExecutor; import org.apache.log4j.Logger; import java.io.File; import java.io.IOException; import java.net.ProxySelector; import java.security.KeyManagementException; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.UnrecoverableKeyException; import java.security.cert.CertificateException; /** * HttpClient related tools * * @author Ole.Matzura */ public class HttpClientSupport { private final static Helper helper = new Helper(); static { if (PropertyExpander.getDefaultExpander() == null) { SoapUI.log.warn("Default property expander was null - will set global proxy later"); } else { ProxyUtils.setGlobalProxy(SoapUI.getSettings()); } } /** * Internal helper to ensure synchronized access.. */ public static class SoapUIHttpClient extends DefaultHttpClient { public SoapUIHttpClient(final ClientConnectionManager conman) { super(conman, null); } @Override protected HttpRequestExecutor createRequestExecutor() { return new SoapUIHttpRequestExecutor(); } } public static class SoapUIHttpRequestExecutor extends HttpRequestExecutor { @Override public void preProcess(final HttpRequest request, final HttpProcessor processor, final HttpContext context) throws HttpException, IOException { HttpRequest original = request; if (original instanceof RequestWrapper) { RequestWrapper w = (RequestWrapper) request; original = w.getOriginal(); } if (original instanceof ExtendedHttpMethod) { SoapUIMetrics metrics = ((ExtendedHttpMethod) original).getMetrics(); metrics.getConnectTimer().stop(); metrics.getTimeToFirstByteTimer().start(); } super.preProcess(request, processor, context); } @Override protected HttpResponse doSendRequest(HttpRequest request, HttpClientConnection conn, HttpContext context) throws IOException, HttpException { HttpResponse response = super.doSendRequest(request, conn, context); return response; } @Override protected HttpResponse doReceiveResponse(final HttpRequest request, final HttpClientConnection conn, final HttpContext context) throws HttpException, IOException { if (request == null) { throw new IllegalArgumentException("HTTP request may not be null"); } if (conn == null) { throw new IllegalArgumentException("HTTP connection may not be null"); } if (context == null) { throw new IllegalArgumentException("HTTP context may not be null"); } HttpResponse response = null; int statuscode = 0; HttpRequest original = request; if (original instanceof RequestWrapper) { RequestWrapper w = (RequestWrapper) request; original = w.getOriginal(); } while (response == null || statuscode < HttpStatus.SC_OK) { response = conn.receiveResponseHeader(); SoapUIMetrics metrics = null; if (original instanceof ExtendedHttpMethod) { metrics = ((ExtendedHttpMethod) original).getMetrics(); metrics.getTimeToFirstByteTimer().stop(); metrics.getReadTimer().start(); } if (canResponseHaveBody(request, response)) { conn.receiveResponseEntity(response); // if( metrics != null ) { // metrics.getReadTimer().stop(); // } } statuscode = response.getStatusLine().getStatusCode(); if (conn.getMetrics() instanceof SoapUIMetrics) { SoapUIMetrics connectionMetrics = (SoapUIMetrics) conn.getMetrics(); if (metrics != null && connectionMetrics != null && !connectionMetrics.isDone()) { metrics.getDNSTimer().set(connectionMetrics.getDNSTimer().getStart(), connectionMetrics.getDNSTimer().getStop()); // reset connection-level metrics connectionMetrics.reset(); } } } // while intermediate response if (original instanceof ExtendedHttpMethod) { ExtendedHttpMethod extendedHttpMethod = (ExtendedHttpMethod) original; extendedHttpMethod.afterReadResponse(((SoapUIMultiThreadedHttpConnectionManager.SoapUIBasicPooledConnAdapter) conn).getSSLSession()); } return response; } } private static class Helper { private final SoapUIHttpClient httpClient; private final static Logger log = Logger.getLogger(HttpClientSupport.Helper.class); private final SoapUIMultiThreadedHttpConnectionManager connectionManager; private final SchemeRegistry registry; public Helper() { Settings settings = SoapUI.getSettings(); registry = new SchemeRegistry(); registry.register(new Scheme("http", 80, PlainSocketFactory.getSocketFactory())); try { SoapUISSLSocketFactory socketFactory = initSocketFactory(); registry.register(new Scheme("https", 443, socketFactory)); } catch (Throwable e) { SoapUI.logError(e); } connectionManager = new SoapUIMultiThreadedHttpConnectionManager(registry); connectionManager.setMaxTotal((int) settings.getLong(HttpSettings.MAX_TOTAL_CONNECTIONS, 2000)); connectionManager .setDefaultMaxPerRoute((int) settings.getLong(HttpSettings.MAX_CONNECTIONS_PER_HOST, 500)); httpClient = new SoapUIHttpClient(connectionManager); // this interceptor needs to be last one added and executed. httpClient.addRequestInterceptor(new HeaderRequestInterceptor(), httpClient.getRequestInterceptorCount()); settings.addSettingsListener(new SSLSettingsListener()); } public SoapUIHttpClient getHttpClient() { return httpClient; } private SchemeRegistry getRegistry() { return registry; } public HttpResponse execute(ExtendedHttpMethod method, HttpContext httpContext) throws ClientProtocolException, IOException { method.afterWriteRequest(); if (method.getMetrics() != null) { method.getMetrics().getConnectTimer().start(); } HttpResponse httpResponse = httpClient.execute(method, httpContext); method.setHttpResponse(httpResponse); return httpResponse; } public HttpResponse execute(ExtendedHttpMethod method) throws ClientProtocolException, IOException { method.afterWriteRequest(); if (method.getMetrics() != null) { method.getMetrics().getConnectTimer().start(); } HttpResponse httpResponse = httpClient.execute(method); method.setHttpResponse(httpResponse); return httpResponse; } public final class SSLSettingsListener implements SettingsListener { @Override public void settingChanged(String name, String newValue, String oldValue) { if (name.equals(SSLSettings.KEYSTORE) || name.equals(SSLSettings.KEYSTORE_PASSWORD)) { try { log.info("Updating keyStore.."); registry.register(new Scheme("https", 443, initSocketFactory())); } catch (Throwable e) { SoapUI.logError(e); } } else if (name.equals(HttpSettings.MAX_CONNECTIONS_PER_HOST)) { log.info("Updating max connections per host to " + newValue); connectionManager.setDefaultMaxPerRoute(Integer.parseInt(newValue)); } else if (name.equals(HttpSettings.MAX_TOTAL_CONNECTIONS)) { log.info("Updating max total connections host to " + newValue); connectionManager.setMaxTotal(Integer.parseInt(newValue)); } } @Override public void settingsReloaded() { try { log.info("Updating keyStore.."); registry.register(new Scheme("https", 443, initSocketFactory())); } catch (Throwable e) { SoapUI.logError(e); } } } public SoapUISSLSocketFactory initSocketFactory() throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException, UnrecoverableKeyException, KeyManagementException { KeyStore keyStore = null; Settings settings = SoapUI.getSettings(); String keyStoreUrl = System.getProperty(SoapUISystemProperties.SOAPUI_SSL_KEYSTORE_LOCATION, settings.getString(SSLSettings.KEYSTORE, null)); keyStoreUrl = keyStoreUrl != null ? keyStoreUrl.trim() : ""; String pass = System.getProperty(SoapUISystemProperties.SOAPUI_SSL_KEYSTORE_PASSWORD, settings.getString(SSLSettings.KEYSTORE_PASSWORD, "")); char[] pwd = pass.toCharArray(); if (keyStoreUrl.trim().length() > 0) { File f = new File(keyStoreUrl); if (f.exists()) { log.info("Initializing KeyStore"); try { KeyMaterial km = new KeyMaterial(f, pwd); keyStore = km.getKeyStore(); } catch (Exception e) { SoapUI.logError(e); } } } return new SoapUISSLSocketFactory(keyStore, pass); } } public static SoapUIHttpClient getHttpClient() { return helper.getHttpClient(); } public static void setProxySelector(ProxySelector proxySelector) { getHttpClient().setRoutePlanner(new OverridableProxySelectorRoutePlanner(helper.getRegistry(), proxySelector)); } public static HttpResponse execute(ExtendedHttpMethod method, HttpContext httpContext) throws ClientProtocolException, IOException { return helper.execute(method, httpContext); } public static HttpResponse execute(ExtendedHttpMethod method) throws ClientProtocolException, IOException { return helper.execute(method); } public static void applyHttpSettings(HttpRequest httpMethod, Settings settings) { // user agent? String userAgent = settings.getString(HttpSettings.USER_AGENT, null); if (userAgent != null && userAgent.length() > 0) { httpMethod.setHeader("User-Agent", userAgent); } // timeout? long timeout = settings.getLong(HttpSettings.SOCKET_TIMEOUT, HttpSettings.DEFAULT_SOCKET_TIMEOUT); httpMethod.getParams().setParameter(CoreConnectionPNames.SO_TIMEOUT, (int) timeout); } public static String getResponseCompressionType(HttpResponse httpResponse) { Header contentType = null; if (httpResponse.getEntity() != null) { contentType = httpResponse.getEntity().getContentType(); } Header contentEncoding = null; if (httpResponse.getEntity() != null) { contentEncoding = httpResponse.getEntity().getContentEncoding(); } return getCompressionType(contentType == null ? null : contentType.getValue(), contentEncoding == null ? null : contentEncoding.getValue()); } public static String getCompressionType(String contentType, String contentEncoding) { String compressionAlg = contentType == null ? null : CompressionSupport.getAvailableAlgorithm(contentType); if (compressionAlg != null) { return compressionAlg; } if (contentEncoding == null) { return null; } else { return CompressionSupport.getAvailableAlgorithm(contentEncoding); } } public static void addSSLListener(Settings settings) { settings.addSettingsListener(helper.new SSLSettingsListener()); } public static BasicHttpContext createEmptyContext() { BasicHttpContext httpContext = new BasicHttpContext(); // always use local cookie store so we don't share cookies with other threads/executions/requests CookieStore cookieStore = new BasicCookieStore(); httpContext.setAttribute(ClientContext.COOKIE_STORE, cookieStore); return httpContext; } }