From 1250f3b7acbe10bd301c390df0cc8eba7e144e93 Mon Sep 17 00:00:00 2001 From: Jaco de Groot Date: Thu, 7 Sep 2023 18:45:04 +0200 Subject: [PATCH] fix: connection pool doesn't recover from closed connection --- Dockerfile | 4 +- frank-runner.properties | 2 + .../adapterframework/http/HttpSenderBase.java | 780 ++---------------- .../http/HttpSenderBase.java-orig | 766 ++--------------- .../http/HttpSessionBase .java-orig | 741 +++++++++++++++++ .../http/HttpSessionBase.java | 756 +++++++++++++++++ 6 files changed, 1672 insertions(+), 1377 deletions(-) create mode 100644 frank-runner.properties create mode 100644 java/nl/nn/adapterframework/http/HttpSessionBase .java-orig create mode 100644 java/nl/nn/adapterframework/http/HttpSessionBase.java diff --git a/Dockerfile b/Dockerfile index 0587eb4..8df3713 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,6 @@ -FROM wearefrank/zaakbrug-base:5357284526 +# Keep in sync with version in frank-runner.properties +# Check whether java-orig files have changed in F!F and update custom code (java and java-orig files) accordingly +FROM wearefrank/frank-framework:7.9-20230905.223421 # Copy dependencies COPY --chown=tomcat lib/server/ /usr/local/tomcat/lib/ diff --git a/frank-runner.properties b/frank-runner.properties new file mode 100644 index 0000000..198ad48 --- /dev/null +++ b/frank-runner.properties @@ -0,0 +1,2 @@ +# Keep in sync with version in Dockerfile +ff.version=7.9-20230905.223421 \ No newline at end of file diff --git a/java/nl/nn/adapterframework/http/HttpSenderBase.java b/java/nl/nn/adapterframework/http/HttpSenderBase.java index 86823fc..a2aee00 100644 --- a/java/nl/nn/adapterframework/http/HttpSenderBase.java +++ b/java/nl/nn/adapterframework/http/HttpSenderBase.java @@ -21,82 +21,47 @@ import java.net.URI; import java.net.URISyntaxException; import java.net.URLEncoder; -import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; -import java.util.StringTokenizer; -import java.util.concurrent.TimeUnit; -import javax.net.ssl.HostnameVerifier; import javax.servlet.http.HttpServletResponse; import javax.xml.transform.TransformerConfigurationException; import org.apache.commons.lang3.StringUtils; -import org.apache.http.HttpHost; import org.apache.http.HttpResponse; import org.apache.http.MethodNotSupportedException; import org.apache.http.StatusLine; -import org.apache.http.auth.AuthProtocolState; -import org.apache.http.auth.AuthScope; -import org.apache.http.auth.AuthState; -import org.apache.http.auth.Credentials; -import org.apache.http.auth.UsernamePasswordCredentials; -import org.apache.http.client.AuthCache; -import org.apache.http.client.CredentialsProvider; -import org.apache.http.client.config.AuthSchemes; -import org.apache.http.client.config.RequestConfig; -import org.apache.http.client.config.RequestConfig.Builder; import org.apache.http.client.methods.HttpRequestBase; -import org.apache.http.client.protocol.HttpClientContext; -import org.apache.http.client.utils.URIBuilder; -import org.apache.http.config.Registry; -import org.apache.http.config.RegistryBuilder; -import org.apache.http.conn.socket.ConnectionSocketFactory; -import org.apache.http.conn.socket.PlainConnectionSocketFactory; -import org.apache.http.conn.ssl.DefaultHostnameVerifier; -import org.apache.http.conn.ssl.NoopHostnameVerifier; -import org.apache.http.conn.ssl.SSLConnectionSocketFactory; import org.apache.http.entity.ContentType; -import org.apache.http.impl.auth.BasicScheme; -import org.apache.http.impl.client.BasicAuthCache; -import org.apache.http.impl.client.BasicCredentialsProvider; import org.apache.http.impl.client.CloseableHttpClient; -import org.apache.http.impl.client.DefaultRedirectStrategy; -import org.apache.http.impl.client.HttpClientBuilder; -import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; import lombok.Getter; +import lombok.Setter; import nl.nn.adapterframework.configuration.ConfigurationException; import nl.nn.adapterframework.configuration.ConfigurationWarning; +import nl.nn.adapterframework.core.CanUseSharedResource; import nl.nn.adapterframework.core.HasPhysicalDestination; +import nl.nn.adapterframework.core.ISenderWithParameters; import nl.nn.adapterframework.core.ParameterException; import nl.nn.adapterframework.core.PipeLineSession; import nl.nn.adapterframework.core.Resource; import nl.nn.adapterframework.core.SenderException; import nl.nn.adapterframework.core.SenderResult; import nl.nn.adapterframework.core.TimeoutException; -import nl.nn.adapterframework.encryption.AuthSSLContextFactory; -import nl.nn.adapterframework.encryption.HasKeystore; -import nl.nn.adapterframework.encryption.HasTruststore; import nl.nn.adapterframework.encryption.KeystoreType; -import nl.nn.adapterframework.http.authentication.AuthenticationScheme; -import nl.nn.adapterframework.http.authentication.HttpAuthenticationException; -import nl.nn.adapterframework.http.authentication.OAuthAccessTokenManager; -import nl.nn.adapterframework.http.authentication.OAuthAuthenticationScheme; -import nl.nn.adapterframework.http.authentication.OAuthPreferringAuthenticationStrategy; import nl.nn.adapterframework.parameters.Parameter; +import nl.nn.adapterframework.parameters.ParameterList; import nl.nn.adapterframework.parameters.ParameterValue; import nl.nn.adapterframework.parameters.ParameterValueList; -import nl.nn.adapterframework.senders.SenderWithParametersBase; import nl.nn.adapterframework.stream.Message; import nl.nn.adapterframework.task.TimeoutGuard; import nl.nn.adapterframework.util.AppConstants; import nl.nn.adapterframework.util.ClassUtils; -import nl.nn.adapterframework.util.CredentialFactory; import nl.nn.adapterframework.util.StreamUtil; +import nl.nn.adapterframework.util.StringUtil; import nl.nn.adapterframework.util.TransformerPool; import nl.nn.adapterframework.util.XmlUtils; @@ -114,55 +79,6 @@ * another_param_name=another_param_value * * - *

- * Note 1: - * Some certificates require the <java_home>/jre/lib/security/xxx_policy.jar files to be upgraded to unlimited strength. Typically, in such a case, an error message like - * Error in loading the keystore: Private key decryption error: (java.lang.SecurityException: Unsupported keysize or algorithm parameters is observed. - * For IBM JDKs these files can be downloaded from http://www.ibm.com/developerworks/java/jdk/security/50/ (scroll down to 'IBM SDK Policy files') - *

- * Replace in the directory java\jre\lib\security the following files: - * - *

- * Note 2: - * To debug ssl-related problems, set the following system property: - *

- *

- *

- * Note 3: - * In case javax.net.ssl.SSLHandshakeException: unknown certificate-exceptions are thrown, - * probably the certificate of the other party is not trusted. Try to use one of the certificates in the path as your truststore by doing the following: - *

- *

- * Note 4: - * In case cannot create or initialize SocketFactory: (IOException) Unable to verify MAC-exceptions are thrown, - * please check password or authAlias configuration of the corresponding certificate. - *

- * * @ff.parameters Any parameters present are appended to the request (when method is GET as request-parameters, when method POST as body part) except the headersParams list, which are added as HTTP headers, and the urlParam header * @ff.forward "<statusCode of the HTTP response>" default * @@ -171,15 +87,17 @@ */ //TODO: Fix javadoc! -public abstract class HttpSenderBase extends SenderWithParametersBase implements HasPhysicalDestination, HasKeystore, HasTruststore { +public abstract class HttpSenderBase extends HttpSessionBase implements HasPhysicalDestination, ISenderWithParameters, CanUseSharedResource { - private final String CONTEXT_KEY_STATUS_CODE="Http.StatusCode"; - private final String CONTEXT_KEY_REASON_PHRASE="Http.ReasonPhrase"; + private static final String CONTEXT_KEY_STATUS_CODE = "Http.StatusCode"; + private static final String CONTEXT_KEY_REASON_PHRASE = "Http.ReasonPhrase"; public static final String MESSAGE_ID_HEADER = "Message-Id"; public static final String CORRELATION_ID_HEADER = "Correlation-Id"; private final @Getter(onMethod = @__(@Override)) String domain = "Http"; + private @Setter String sharedResourceRef; + private @Getter String url; private @Getter String urlParam = "url"; @@ -192,127 +110,55 @@ public enum HttpMethod { private @Getter ContentType fullContentType = null; private @Getter String contentType = null; - /* CONNECTION POOL */ - private @Getter int timeout = 10000; - private @Getter int maxConnections = 10; - private @Getter int maxExecuteRetries = 1; - private @Getter boolean staleChecking=true; - private @Getter int staleTimeout = 5000; // [ms] - private @Getter int connectionTimeToLive = 900; // [s] - private @Getter int connectionIdleTimeout = 10; // [s] - private HttpClientBuilder httpClientBuilder = HttpClientBuilder.create(); - private @Getter HttpClientContext httpClientContext = HttpClientContext.create(); - private @Getter CloseableHttpClient httpClient; - - /* SECURITY */ - private @Getter String authAlias; - private @Getter String username; - private @Getter String password; - private @Getter String authDomain; - private @Getter String tokenEndpoint; - private @Getter int tokenExpiry=-1; - private @Getter String clientAuthAlias; - private @Getter String clientId; - private @Getter String clientSecret; - private @Getter String scope; - private @Getter boolean authenticatedTokenRequest; - - /* PROXY */ - private @Getter String proxyHost; - private @Getter int proxyPort=80; - private @Getter String proxyAuthAlias; - private @Getter String proxyUsername; - private @Getter String proxyPassword; - private @Getter String proxyRealm=null; - - /* SSL */ - private @Getter String keystore; - private @Getter String keystoreAuthAlias; - private @Getter String keystorePassword; - private @Getter KeystoreType keystoreType=KeystoreType.PKCS12; - private @Getter String keystoreAlias; - private @Getter String keystoreAliasAuthAlias; - private @Getter String keystoreAliasPassword; - private @Getter String keyManagerAlgorithm=null; - - private @Getter String truststore=null; - private @Getter String truststoreAuthAlias; - private @Getter String truststorePassword=null; - private @Getter KeystoreType truststoreType=KeystoreType.JKS; - private @Getter String trustManagerAlgorithm=null; - private @Getter boolean allowSelfSignedCertificates = false; - private @Getter boolean verifyHostname=true; - private @Getter boolean ignoreCertificateExpiredException=false; - private @Getter String headersParams=""; - private @Getter boolean followRedirects=true; - private @Getter boolean ignoreRedirects=false; private @Getter boolean xhtml=false; private @Getter String styleSheetName=null; - private @Getter String protocol=null; private @Getter String resultStatusCodeSessionKey; private @Getter String parametersToSkipWhenEmpty; private final boolean APPEND_MESSAGEID_HEADER = AppConstants.getInstance(getConfigurationClassLoader()).getBoolean("http.headers.messageid", true); private final boolean APPEND_CORRELATIONID_HEADER = AppConstants.getInstance(getConfigurationClassLoader()).getBoolean("http.headers.correlationid", true); - private boolean disableCookies = false; private TransformerPool transformerPool=null; protected Parameter urlParameter; protected URI staticUri; - private CredentialFactory credentials; - private CredentialFactory user_cf; - private CredentialFactory client_cf; protected Set requestOrBodyParamsSet=new HashSet<>(); protected Set headerParamsSet=new LinkedHashSet<>(); protected Set parametersToSkipWhenEmptySet=new HashSet<>(); - /** - * Makes sure only http(s) requests can be performed. - */ - protected URI getURI(String url) throws URISyntaxException { - URIBuilder uri = new URIBuilder(url); - - if(uri.getScheme() == null) { - throw new URISyntaxException("", "must use an absolute url starting with http(s)://"); - } - if (!uri.getScheme().matches("(?i)https?")) { - throw new IllegalArgumentException(ClassUtils.nameOf(this) + " only supports web based schemes. (http or https)"); - } + protected ParameterList paramList = null; - if (uri.getPath()==null) { - uri.setPath("/"); + @Override + public void addParameter(Parameter p) { + if (paramList==null) { + paramList=new ParameterList(); } + paramList.add(p); + } - log.info(getLogPrefix()+"created uri: scheme=["+uri.getScheme()+"] host=["+uri.getHost()+"] path=["+uri.getPath()+"]"); - return uri.build(); + /** + * return the Parameters + */ + @Override + public ParameterList getParameterList() { + return paramList; } @Override public void configure() throws ConfigurationException { - super.configure(); - - /** - * TODO find out if this really breaks proxy authentication or not. - */ -// httpClientBuilder.disableAuthCaching(); - - Builder requestConfigBuilder = RequestConfig.custom(); - requestConfigBuilder.setConnectTimeout(getTimeout()); - requestConfigBuilder.setConnectionRequestTimeout(getTimeout()); - requestConfigBuilder.setSocketTimeout(getTimeout()); + if(StringUtils.isBlank(sharedResourceRef)) { + log.info("configuring local HttpSession"); + super.configure(); + } if (paramList!=null) { paramList.configure(); + if (StringUtils.isNotEmpty(getHeadersParams())) { - StringTokenizer st = new StringTokenizer(getHeadersParams(), ","); - while (st.hasMoreElements()) { - String paramName = st.nextToken().trim(); - headerParamsSet.add(paramName); - } + headerParamsSet.addAll(StringUtil.split(getHeadersParams())); } for (Parameter p: paramList) { String paramName = p.getName(); @@ -332,26 +178,18 @@ public void configure() throws ConfigurationException { parametersToSkipWhenEmptySet.addAll(headerParamsSet); parametersToSkipWhenEmptySet.addAll(requestOrBodyParamsSet); } else { - StringTokenizer st = new StringTokenizer(getParametersToSkipWhenEmpty(), ","); - while (st.hasMoreElements()) { - String paramName = st.nextToken().trim(); - parametersToSkipWhenEmptySet.add(paramName); - } + parametersToSkipWhenEmptySet.addAll(StringUtil.split(getParametersToSkipWhenEmpty())); } } } if(StringUtils.isNotEmpty(getContentType())) { fullContentType = ContentType.parse(getContentType()); - if(fullContentType != null && fullContentType.getCharset() == null) { + if(fullContentType.getCharset() == null) { fullContentType = fullContentType.withCharset(getCharSet()); } } - if (getMaxConnections() <= 0) { - throw new ConfigurationException(getLogPrefix()+"maxConnections is set to ["+getMaxConnections()+"], which is not enough for adequate operation"); - } - try { if (urlParameter == null) { if (StringUtils.isEmpty(getUrl())) { @@ -360,102 +198,32 @@ public void configure() throws ConfigurationException { staticUri = getURI(getUrl()); } } catch (URISyntaxException e) { - throw new ConfigurationException(getLogPrefix()+"cannot interpret url ["+getUrl()+"]", e); - } - - AuthSSLContextFactory.verifyKeystoreConfiguration(this, this); - - if (StringUtils.isNotEmpty(getAuthAlias()) || StringUtils.isNotEmpty(getUsername())) { - user_cf = new CredentialFactory(getAuthAlias(), getUsername(), getPassword()); - credentials = user_cf; - } - client_cf = new CredentialFactory(getClientAuthAlias(), getClientId(), getClientSecret()); - if (credentials==null) { - credentials = client_cf; - } - if (StringUtils.isNotEmpty(getTokenEndpoint()) && StringUtils.isEmpty(getClientAuthAlias()) && StringUtils.isEmpty(getClientId())) { - throw new ConfigurationException("To obtain accessToken at tokenEndpoint ["+getTokenEndpoint()+"] a clientAuthAlias or ClientId and ClientSecret must be specified"); - } - HttpHost proxy = null; - CredentialFactory pcf = null; - if (StringUtils.isNotEmpty(getProxyHost())) { - proxy = new HttpHost(getProxyHost(), getProxyPort()); - pcf = new CredentialFactory(getProxyAuthAlias(), getProxyUsername(), getProxyPassword()); - requestConfigBuilder.setProxy(proxy); - httpClientBuilder.setProxy(proxy); - } - - try { - setupAuthentication(pcf, proxy, requestConfigBuilder); - } catch (HttpAuthenticationException e) { - throw new ConfigurationException("exception configuring authentication", e); + throw new ConfigurationException("cannot interpret url ["+getUrl()+"]", e); } if (StringUtils.isNotEmpty(getStyleSheetName())) { try { Resource stylesheet = Resource.getResource(this, getStyleSheetName()); if (stylesheet == null) { - throw new ConfigurationException(getLogPrefix() + "cannot find stylesheet ["+getStyleSheetName()+"]"); + throw new ConfigurationException("cannot find stylesheet ["+getStyleSheetName()+"]"); } transformerPool = TransformerPool.getInstance(stylesheet); } catch (IOException e) { - throw new ConfigurationException(getLogPrefix() + "cannot retrieve ["+ getStyleSheetName() + "]", e); + throw new ConfigurationException("cannot retrieve ["+ getStyleSheetName() + "]", e); } catch (TransformerConfigurationException te) { - throw new ConfigurationException(getLogPrefix() + "got error creating transformer from file [" + getStyleSheetName() + "]", te); + throw new ConfigurationException("got error creating transformer from file [" + getStyleSheetName() + "]", te); } } - - httpClientBuilder.setDefaultRequestConfig(requestConfigBuilder.build()); - - httpClientBuilder.setRetryHandler(new HttpRequestRetryHandler(getMaxExecuteRetries())); - - if(areCookiesDisabled()) { - httpClientBuilder.disableCookieManagement(); - } - - // The redirect strategy used to only redirect GET, DELETE and HEAD. - httpClientBuilder.setRedirectStrategy(new DefaultRedirectStrategy() { - @Override - protected boolean isRedirectable(String method) { - return isFollowRedirects(); - } - }); } @Override public void open() throws SenderException { - // In order to support multiThreading and connectionPooling - // If a sslSocketFactory has been defined, the connectionManager has to be initialized with the sslSocketFactory - PoolingHttpClientConnectionManager connectionManager; - int timeToLive = getConnectionTimeToLive(); - if (timeToLive<=0) { - timeToLive = -1; - } - SSLConnectionSocketFactory sslSocketFactory = getSSLConnectionSocketFactory(); - if(sslSocketFactory != null) { - Registry socketFactoryRegistry = RegistryBuilder.create() - .register("http", PlainConnectionSocketFactory.getSocketFactory()) - .register("https", sslSocketFactory) - .build(); - connectionManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry, null, null, null, timeToLive, TimeUnit.SECONDS); - log.debug(getLogPrefix()+"created PoolingHttpClientConnectionManager with custom SSLConnectionSocketFactory"); - } - else { - connectionManager = new PoolingHttpClientConnectionManager(timeToLive, TimeUnit.SECONDS); - log.debug(getLogPrefix()+"created default PoolingHttpClientConnectionManager"); - } - - connectionManager.setMaxTotal(getMaxConnections()); - connectionManager.setDefaultMaxPerRoute(getMaxConnections()); - - if (isStaleChecking()) { - log.info(getLogPrefix()+"set up connectionManager, setting stale checking ["+isStaleChecking()+"]"); - connectionManager.setValidateAfterInactivity(getStaleTimeout()); + try { + start(); + } catch (Exception e) { + throw new SenderException(getLogPrefix()+"unable to create HttpClient", e); } - httpClientBuilder.setConnectionManager(connectionManager); - httpClientBuilder.evictIdleConnections(getConnectionIdleTimeout(), TimeUnit.SECONDS); - if (transformerPool!=null) { try { transformerPool.open(); @@ -463,116 +231,36 @@ public void open() throws SenderException { throw new SenderException(getLogPrefix()+"cannot start TransformerPool", e); } } - - httpClient = httpClientBuilder.build(); } @Override - public void close() throws SenderException { - try { - //Close the HttpClient and ConnectionManager to release resources and potential open connections - if(httpClient != null) { - httpClient.close(); - } - } catch (IOException e) { - throw new SenderException(e); - } - - if (transformerPool!=null) { - transformerPool.close(); - } + public Class getObjectType() { + return CloseableHttpClient.class; } - private void setupAuthentication(CredentialFactory proxyCredentials, HttpHost proxy, Builder requestConfigBuilder) throws HttpAuthenticationException { - CredentialsProvider credentialsProvider = new BasicCredentialsProvider(); - if (StringUtils.isNotEmpty(credentials.getUsername()) || StringUtils.isNotEmpty(getTokenEndpoint())) { - - credentialsProvider.setCredentials(new AuthScope(AuthScope.ANY_HOST, AuthScope.ANY_PORT), getCredentials()); - - AuthenticationScheme preferredAuthenticationScheme = getPreferredAuthenticationScheme(); - requestConfigBuilder.setTargetPreferredAuthSchemes(Arrays.asList(preferredAuthenticationScheme.getSchemeName())); - requestConfigBuilder.setAuthenticationEnabled(true); - - if (preferredAuthenticationScheme == AuthenticationScheme.OAUTH) { - OAuthAccessTokenManager accessTokenManager = new OAuthAccessTokenManager(getTokenEndpoint(), getScope(), client_cf, user_cf==null, isAuthenticatedTokenRequest(), this, getTokenExpiry()); - httpClientContext.setAttribute(OAuthAuthenticationScheme.ACCESSTOKEN_MANAGER_KEY, accessTokenManager); - httpClientBuilder.setTargetAuthenticationStrategy(new OAuthPreferringAuthenticationStrategy()); - } - } - if (proxy!=null) { - AuthScope scope = new AuthScope(proxy, proxyRealm, AuthScope.ANY_SCHEME); - - - if (StringUtils.isNotEmpty(proxyCredentials.getUsername())) { - Credentials credentials = new UsernamePasswordCredentials(proxyCredentials.getUsername(), proxyCredentials.getPassword()); - credentialsProvider.setCredentials(scope, credentials); - } - //log.trace("setting credentialProvider [" + credentialsProvider.toString() + "]"); - - if(prefillProxyAuthCache()) { - requestConfigBuilder.setProxyPreferredAuthSchemes(Arrays.asList(AuthSchemes.BASIC)); - - AuthCache authCache = httpClientContext.getAuthCache(); - if(authCache == null) - authCache = new BasicAuthCache(); - - authCache.put(proxy, new BasicScheme()); - httpClientContext.setAuthCache(authCache); - } - - } - - httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider); - } - - private void preAuthenticate() { - if (credentials != null && !StringUtils.isEmpty(credentials.getUsername())) { - AuthState authState = httpClientContext.getTargetAuthState(); - if (authState==null) { - authState = new AuthState(); - httpClientContext.setAttribute(HttpClientContext.TARGET_AUTH_STATE, authState); - } - authState.setState(AuthProtocolState.CHALLENGED); - authState.update(getPreferredAuthenticationScheme().createScheme(), getCredentials()); - } - } - - private Credentials getCredentials() { - String uname; - if (StringUtils.isNotEmpty(getAuthDomain())) { - uname = getAuthDomain() + "\\" + credentials.getUsername(); + @Override + public void start() { + if(StringUtils.isNotBlank(sharedResourceRef)) { + setHttpClient(getSharedResource(sharedResourceRef)); } else { - uname = credentials.getUsername(); + buildHttpClient(); } - - return new UsernamePasswordCredentials(uname, credentials.getPassword()); - } - - private AuthenticationScheme getPreferredAuthenticationScheme() { - return StringUtils.isNotEmpty(getTokenEndpoint()) ? AuthenticationScheme.OAUTH : AuthenticationScheme.BASIC; } - protected SSLConnectionSocketFactory getSSLConnectionSocketFactory() throws SenderException { - SSLConnectionSocketFactory sslSocketFactory; - HostnameVerifier hostnameVerifier = verifyHostname ? new DefaultHostnameVerifier() : new NoopHostnameVerifier(); - - try { - javax.net.ssl.SSLSocketFactory socketfactory = AuthSSLContextFactory.createSSLSocketFactory(this, this, getProtocol()); - sslSocketFactory = new SSLConnectionSocketFactory(socketfactory, hostnameVerifier); - } catch (Exception e) { - throw new SenderException("cannot create or initialize SocketFactory", e); + @Override + public void close() { + if(StringUtils.isBlank(sharedResourceRef)) { + super.stop(); } - // This method will be overwritten by the connectionManager when connectionPooling is enabled! - // Can still be null when no default or an invalid system sslSocketFactory has been defined - if(sslSocketFactory != null) { - httpClientBuilder.setSSLSocketFactory(sslSocketFactory); + + if (transformerPool!=null) { + transformerPool.close(); } - return sslSocketFactory; } - protected boolean appendParameters(boolean parametersAppended, StringBuffer path, ParameterValueList parameters) throws SenderException { + protected boolean appendParameters(boolean parametersAppended, StringBuilder path, ParameterValueList parameters) throws SenderException { if (parameters != null) { - if (log.isDebugEnabled()) log.debug(getLogPrefix()+"appending ["+parameters.size()+"] parameters"); + log.debug("appending [{}] parameters", parameters::size); for(ParameterValue pv : parameters) { if (requestOrBodyParamsSet.contains(pv.getName())) { String value = pv.asStringValue(""); @@ -586,7 +274,7 @@ protected boolean appendParameters(boolean parametersAppended, StringBuffer path } String parameterToAppend = pv.getDefinition().getName() +"="+ URLEncoder.encode(value, getCharSet()); - if (log.isDebugEnabled()) log.debug(getLogPrefix()+"appending parameter ["+parameterToAppend+"]"); + log.debug("appending parameter [{}]", parameterToAppend); path.append(parameterToAppend); } catch (UnsupportedEncodingException e) { throw new SenderException(getLogPrefix()+"["+getCharSet()+"] encoding error. Failed to add parameter ["+pv.getDefinition().getName()+"]", e); @@ -598,6 +286,17 @@ protected boolean appendParameters(boolean parametersAppended, StringBuffer path return parametersAppended; } + + /** + * Returns the true name of the class and not XsltPipe$$EnhancerBySpringCGLIB$$563e6b5d. + * {@link ClassUtils#nameOf(Object)} makes sure the original class will be used. + * + * @return className + name of the ISender + */ + protected String getLogPrefix() { + return ClassUtils.nameOf(this) + " "; + } + /** * Custom implementation to create a {@link HttpRequestBase HttpRequest} object. * @param uri endpoint to send the message to @@ -690,17 +389,16 @@ public SenderResult sendMessage(Message message, PipeLineSession session) throws preAuthenticate(); - log.info(getLogPrefix()+"configured httpclient for host ["+targetUri.getHost()+"]"); + log.info("configured httpclient for host [{}]", targetUri::getHost); } catch (Exception e) { throw new SenderException(e); } - Message result = null; - int statusCode = -1; + Message result; + int statusCode; boolean success; - String reasonPhrase = null; - HttpHost targetHost = new HttpHost(targetUri.getHost(), targetUri.getPort(), targetUri.getScheme()); + String reasonPhrase; TimeoutGuard tg = new TimeoutGuard(1+getTimeout()/1000, getName()) { @@ -711,23 +409,9 @@ protected void abort() { }; try { - // Might be nice to implement storeHttpClientInSessionKey and getHttpClientFromSessionKey - CloseableHttpClient httpClient = null; - Object httpClientObject = session.get("httpClient"); - if (httpClientObject != null) { - if (httpClientObject instanceof CloseableHttpClient) { - httpClient = (CloseableHttpClient)httpClientObject; - } else { - log.warn(getLogPrefix()+"httpClient is not an instance of CloseableHttpClient"); - } - } - if (httpClient == null) { - httpClient = getHttpClient(); - session.put("httpClient", httpClient); - } - log.debug(getLogPrefix()+"executing method [" + httpRequestBase.getRequestLine() + "]"); - HttpResponse httpResponse = httpClient.execute(targetHost, httpRequestBase, httpClientContext); - log.debug(getLogPrefix()+"executed method"); + log.debug("executing method [{}]", httpRequestBase::getRequestLine); + HttpResponse httpResponse = execute(targetUri, httpRequestBase, session); + log.debug("executed method"); HttpResponseHandler responseHandler = new HttpResponseHandler(httpResponse); StatusLine statusline = httpResponse.getStatusLine(); @@ -741,14 +425,14 @@ protected void abort() { // Only give warnings for 4xx (client errors) and 5xx (server errors) if (statusCode >= 400 && statusCode < 600) { - log.warn(getLogPrefix()+"status ["+statusline.toString()+"]"); + log.warn("status [{}]", statusline); } else { - log.debug(getLogPrefix()+"status ["+statusCode+"]"); + log.debug("status [{}]", statusCode); } result = extractResult(responseHandler, session); - log.debug(getLogPrefix()+"retrieved result ["+result+"]"); + log.debug("retrieved result [{}]", result); } catch (IOException e) { httpRequestBase.abort(); if (e instanceof SocketTimeoutException) { @@ -763,8 +447,8 @@ protected void abort() { // in a sessionKey for later use in the pipeline. // // IMPORTANT: It is possible that poorly written implementations - // wont read or close the response. - // This will cause the connection to become stale.. + // won't read or close the response. + // This will cause the connection to become stale. if (tg.cancel()) { throw new TimeoutException(getLogPrefix()+"timeout of ["+getTimeout()+"] ms exceeded"); @@ -776,15 +460,16 @@ protected void abort() { } if (isXhtml() && !Message.isEmpty(result)) { + // TODO: Streaming XHTML conversion for better performance with large result message? String xhtml; - try { - xhtml = XmlUtils.toXhtml(result); + try(Message m = result) { + xhtml = XmlUtils.toXhtml(m); } catch (IOException e) { throw new SenderException("error reading http response as String", e); } if (transformerPool != null && xhtml != null) { - log.debug(getLogPrefix() + " transforming result [" + xhtml + "]"); + log.debug("transforming result [{}]", xhtml); try { xhtml = transformerPool.transform(Message.asSource(xhtml)); } catch (Exception e) { @@ -795,7 +480,7 @@ protected void abort() { result = Message.asMessage(xhtml); } - if (result==null) { + if (result == null) { result = Message.nullMessage(); } log.debug("Storing [{}]=[{}], [{}]=[{}]", CONTEXT_KEY_STATUS_CODE, statusCode, CONTEXT_KEY_REASON_PHRASE, reasonPhrase); @@ -849,174 +534,18 @@ public void setCharSet(String string) { charSet = string; } - /** - * Timeout in ms of obtaining a connection/result. 0 means no timeout - * @ff.default 10000 - */ - public void setTimeout(int i) { - timeout = i; - } - - /** - * The maximum number of concurrent connections - * @ff.default 10 - */ - public void setMaxConnections(int i) { - maxConnections = i; - } - - /** - * The maximum number of times the execution is retried - * @ff.default 1 (for repeatable messages) else 0 - */ - public void setMaxExecuteRetries(int i) { - maxExecuteRetries = i; - } - - /** Authentication alias used for authentication to the host */ - public void setAuthAlias(String string) { - authAlias = string; - } - - /** Username used for authentication to the host */ - public void setUsername(String username) { - this.username = username; - } @Deprecated @ConfigurationWarning("Please use attribute username instead") public void setUserName(String username) { setUsername(username); } - /** Password used for authentication to the host */ - public void setPassword(String string) { - password = string; - } - - /** - * Corporate domain name. Should only be used in combination with sAMAccountName, never with an UPN.
- *
- * Assuming the following user:
- * UPN: john.doe@CorpDomain.biz
- * sAMAccountName: CORPDOMAIN\john.doe
- *
- * The username attribute may be set to john.doe
- * The AuthDomain attribute may be set to CORPDOMAIN
- */ - @Deprecated - @ConfigurationWarning("Please use the UPN or the full sAM-AccountName instead") - public void setAuthDomain(String string) { - authDomain = string; - } - - /** - * Endpoint to obtain OAuth accessToken. If authAlias or username( and password) are specified, - * then a PasswordGrant is used, otherwise a ClientCredentials grant. The obtained accessToken will be added to the regular requests - * in an HTTP Header 'Authorization' with a 'Bearer' prefix. - */ - public void setTokenEndpoint(String string) { - tokenEndpoint = string; - } - /** - * If set to a non-negative value, then determines the time (in seconds) after which the token will be refreshed. Otherwise the token - * will be refreshed when it is half way its lifetime as defined by the expires_in clause of the token response, - * or when the regular server returns a 401 status with a challenge. - * If not specified, and the accessTokens lifetime is not found in the token response, the accessToken will not be refreshed preemptively. - * @ff.default -1 - */ - public void setTokenExpiry(int value) { - tokenExpiry = value; - } - /** Alias used to obtain client_id and client_secret for authentication to tokenEndpoint */ - public void setClientAlias(String clientAuthAlias) { - this.clientAuthAlias = clientAuthAlias; - } - /** Client_id used in authentication to tokenEndpoint */ - public void setClientId(String clientId) { - this.clientId = clientId; - } - - /** Client_secret used in authentication to tokenEndpoint */ - public void setClientSecret(String clientSecret) { - this.clientSecret = clientSecret; - } - /** Space or comma separated list of scope items requested for accessToken, e.g. read write. Only used when tokenEndpoint is specified */ - public void setScope(String string) { - scope = string; - } - /** if set true, clientId and clientSecret will be added as Basic Authentication header to the tokenRequest, instead of as request parameters */ - public void setAuthenticatedTokenRequest(boolean authenticatedTokenRequest) { - this.authenticatedTokenRequest = authenticatedTokenRequest; - } - - - /** Proxy host */ - public void setProxyHost(String string) { - proxyHost = string; - } - - /** - * Proxy port - * @ff.default 80 - */ - public void setProxyPort(int i) { - proxyPort = i; - } - - /** Alias used to obtain credentials for authentication to proxy */ - public void setProxyAuthAlias(String string) { - proxyAuthAlias = string; - } - - /** - * Proxy username - * @ff.default - */ - public void setProxyUsername(String string) { - proxyUsername = string; - } @Deprecated @ConfigurationWarning("Please use \"proxyUsername\" instead") public void setProxyUserName(String string) { setProxyUsername(string); } - /** - * Proxy password - * @ff.default - */ - public void setProxyPassword(String string) { - proxyPassword = string; - } - - /** - * Proxy realm - * @ff.default - */ - public void setProxyRealm(String string) { - proxyRealm = StringUtils.isNotEmpty(string) ? string : null; - } - - /** - * TODO: make this configurable - * @return false - */ - public boolean prefillProxyAuthCache() { - return false; - } - - /** - * Disables the use of cookies, making the sender completely stateless - * @ff.default false - */ - public void setDisableCookies(boolean disableCookies) { - this.disableCookies = disableCookies; - } - public boolean areCookiesDisabled() { - return disableCookies; - } - - @Deprecated @ConfigurationWarning("Please use attribute keystore instead") public void setCertificate(String string) { @@ -1038,87 +567,6 @@ public void setCertificatePassword(String string) { setKeystorePassword(string); } - /** resource URL to keystore or certificate to be used for authentication. If none specified, the JVMs default keystore will be used. */ - @Override - public void setKeystore(String string) { - keystore = string; - } - - @Override - public void setKeystoreType(KeystoreType value) { - keystoreType = value; - } - - @Override - public void setKeystoreAuthAlias(String string) { - keystoreAuthAlias = string; - } - - @Override - public void setKeystorePassword(String string) { - keystorePassword = string; - } - - @Override - public void setKeyManagerAlgorithm(String keyManagerAlgorithm) { - this.keyManagerAlgorithm = keyManagerAlgorithm; - } - - @Override - public void setKeystoreAlias(String string) { - keystoreAlias = string; - } - @Override - public void setKeystoreAliasAuthAlias(String string) { - keystoreAliasAuthAlias = string; - } - @Override - public void setKeystoreAliasPassword(String string) { - keystoreAliasPassword = string; - } - - @Override - /** Resource URL to truststore to be used for authenticating peer. If none specified, the JVMs default truststore will be used. */ - public void setTruststore(String string) { - truststore = string; - } - - @Override - public void setTruststoreAuthAlias(String string) { - truststoreAuthAlias = string; - } - - @Override - public void setTruststorePassword(String string) { - truststorePassword = string; - } - - @Override - public void setTruststoreType(KeystoreType value) { - truststoreType = value; - } - - @Override - public void setTrustManagerAlgorithm(String trustManagerAlgorithm) { - this.trustManagerAlgorithm = trustManagerAlgorithm; - } - - @Override - public void setVerifyHostname(boolean b) { - verifyHostname = b; - } - - @Override - public void setAllowSelfSignedCertificates(boolean allowSelfSignedCertificates) { - this.allowSelfSignedCertificates = allowSelfSignedCertificates; - } - - @Override - public void setIgnoreCertificateExpiredException(boolean b) { - ignoreCertificateExpiredException = b; - } - - /** Comma separated list of parameter names which should be set as HTTP headers */ public void setHeadersParams(String headersParams) { this.headersParams = headersParams; @@ -1129,56 +577,6 @@ public void setParametersToSkipWhenEmpty(String parametersToSkipWhenEmpty) { this.parametersToSkipWhenEmpty = parametersToSkipWhenEmpty; } - - /** - * If true, a redirect request will be honoured, e.g. to switch to HTTPS - * @ff.default true - */ - public void setFollowRedirects(boolean b) { - followRedirects = b; - } - - /** - * If true, besides http status code 200 (OK) also the code 301 (MOVED_PERMANENTLY), 302 (MOVED_TEMPORARILY) and 307 (TEMPORARY_REDIRECT) are considered successful - * @ff.default false - */ - public void setIgnoreRedirects(boolean b) { - ignoreRedirects = b; - } - - - /** - * Controls whether connections checked to be stale, i.e. appear open, but are not. - * @ff.default true - */ - public void setStaleChecking(boolean b) { - staleChecking = b; - } - - /** - * Used when StaleChecking=true. Timeout after which an idle connection will be validated before being used. - * @ff.default 5000 ms - */ - public void setStaleTimeout(int timeout) { - staleTimeout = timeout; - } - - /** - * Maximum Time to Live for connections in the pool. No connection will be re-used past its timeToLive value. - * @ff.default 900 s - */ - public void setConnectionTimeToLive(int timeToLive) { - connectionTimeToLive = timeToLive; - } - - /** - * Maximum Time for connection to stay idle in the pool. Connections that are idle longer will periodically be evicted from the pool - * @ff.default 10 s - */ - public void setConnectionIdleTimeout(int idleTimeout) { - connectionIdleTimeout = idleTimeout; - } - /** * If true, the HTML response is transformed to XHTML * @ff.default false @@ -1192,14 +590,6 @@ public void setStyleSheetName(String stylesheetName){ this.styleSheetName=stylesheetName; } - /** - * Secure socket protocol (such as 'SSL' and 'TLS') to use when a SSLContext object is generated. - * @ff.default SSL - */ - public void setProtocol(String protocol) { - this.protocol = protocol; - } - /** If set, the status code of the HTTP response is put in the specified sessionKey and the (error or okay) response message is returned. * Setting this property has a side effect. If a 4xx or 5xx result code is returned and if the configuration does not implement * the specific forward for the returned HTTP result code, then the success forward is followed instead of the exception forward. @@ -1207,4 +597,4 @@ public void setProtocol(String protocol) { public void setResultStatusCodeSessionKey(String resultStatusCodeSessionKey) { this.resultStatusCodeSessionKey = resultStatusCodeSessionKey; } -} \ No newline at end of file +} diff --git a/java/nl/nn/adapterframework/http/HttpSenderBase.java-orig b/java/nl/nn/adapterframework/http/HttpSenderBase.java-orig index 9e2a796..6e33a79 100644 --- a/java/nl/nn/adapterframework/http/HttpSenderBase.java-orig +++ b/java/nl/nn/adapterframework/http/HttpSenderBase.java-orig @@ -21,82 +21,47 @@ import java.net.SocketTimeoutException; import java.net.URI; import java.net.URISyntaxException; import java.net.URLEncoder; -import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; -import java.util.StringTokenizer; -import java.util.concurrent.TimeUnit; -import javax.net.ssl.HostnameVerifier; import javax.servlet.http.HttpServletResponse; import javax.xml.transform.TransformerConfigurationException; import org.apache.commons.lang3.StringUtils; -import org.apache.http.HttpHost; import org.apache.http.HttpResponse; import org.apache.http.MethodNotSupportedException; import org.apache.http.StatusLine; -import org.apache.http.auth.AuthProtocolState; -import org.apache.http.auth.AuthScope; -import org.apache.http.auth.AuthState; -import org.apache.http.auth.Credentials; -import org.apache.http.auth.UsernamePasswordCredentials; -import org.apache.http.client.AuthCache; -import org.apache.http.client.CredentialsProvider; -import org.apache.http.client.config.AuthSchemes; -import org.apache.http.client.config.RequestConfig; -import org.apache.http.client.config.RequestConfig.Builder; import org.apache.http.client.methods.HttpRequestBase; -import org.apache.http.client.protocol.HttpClientContext; -import org.apache.http.client.utils.URIBuilder; -import org.apache.http.config.Registry; -import org.apache.http.config.RegistryBuilder; -import org.apache.http.conn.socket.ConnectionSocketFactory; -import org.apache.http.conn.socket.PlainConnectionSocketFactory; -import org.apache.http.conn.ssl.DefaultHostnameVerifier; -import org.apache.http.conn.ssl.NoopHostnameVerifier; -import org.apache.http.conn.ssl.SSLConnectionSocketFactory; import org.apache.http.entity.ContentType; -import org.apache.http.impl.auth.BasicScheme; -import org.apache.http.impl.client.BasicAuthCache; -import org.apache.http.impl.client.BasicCredentialsProvider; import org.apache.http.impl.client.CloseableHttpClient; -import org.apache.http.impl.client.DefaultRedirectStrategy; -import org.apache.http.impl.client.HttpClientBuilder; -import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; import lombok.Getter; +import lombok.Setter; import nl.nn.adapterframework.configuration.ConfigurationException; import nl.nn.adapterframework.configuration.ConfigurationWarning; +import nl.nn.adapterframework.core.CanUseSharedResource; import nl.nn.adapterframework.core.HasPhysicalDestination; +import nl.nn.adapterframework.core.ISenderWithParameters; import nl.nn.adapterframework.core.ParameterException; import nl.nn.adapterframework.core.PipeLineSession; import nl.nn.adapterframework.core.Resource; import nl.nn.adapterframework.core.SenderException; import nl.nn.adapterframework.core.SenderResult; import nl.nn.adapterframework.core.TimeoutException; -import nl.nn.adapterframework.encryption.AuthSSLContextFactory; -import nl.nn.adapterframework.encryption.HasKeystore; -import nl.nn.adapterframework.encryption.HasTruststore; import nl.nn.adapterframework.encryption.KeystoreType; -import nl.nn.adapterframework.http.authentication.AuthenticationScheme; -import nl.nn.adapterframework.http.authentication.HttpAuthenticationException; -import nl.nn.adapterframework.http.authentication.OAuthAccessTokenManager; -import nl.nn.adapterframework.http.authentication.OAuthAuthenticationScheme; -import nl.nn.adapterframework.http.authentication.OAuthPreferringAuthenticationStrategy; import nl.nn.adapterframework.parameters.Parameter; +import nl.nn.adapterframework.parameters.ParameterList; import nl.nn.adapterframework.parameters.ParameterValue; import nl.nn.adapterframework.parameters.ParameterValueList; -import nl.nn.adapterframework.senders.SenderWithParametersBase; import nl.nn.adapterframework.stream.Message; import nl.nn.adapterframework.task.TimeoutGuard; import nl.nn.adapterframework.util.AppConstants; import nl.nn.adapterframework.util.ClassUtils; -import nl.nn.adapterframework.util.CredentialFactory; import nl.nn.adapterframework.util.StreamUtil; +import nl.nn.adapterframework.util.StringUtil; import nl.nn.adapterframework.util.TransformerPool; import nl.nn.adapterframework.util.XmlUtils; @@ -114,55 +79,6 @@ import nl.nn.adapterframework.util.XmlUtils; * another_param_name=another_param_value * * - *

- * Note 1: - * Some certificates require the <java_home>/jre/lib/security/xxx_policy.jar files to be upgraded to unlimited strength. Typically, in such a case, an error message like - * Error in loading the keystore: Private key decryption error: (java.lang.SecurityException: Unsupported keysize or algorithm parameters is observed. - * For IBM JDKs these files can be downloaded from http://www.ibm.com/developerworks/java/jdk/security/50/ (scroll down to 'IBM SDK Policy files') - *

- * Replace in the directory java\jre\lib\security the following files: - *
    - *
  • local_policy.jar
  • - *
  • US_export_policy.jar
  • - *
- *

- * Note 2: - * To debug ssl-related problems, set the following system property: - *

    - *
  • IBM / WebSphere: -Djavax.net.debug=true
  • - *
  • SUN: -Djavax.net.debug=all
  • - *
- *

- *

- * Note 3: - * In case javax.net.ssl.SSLHandshakeException: unknown certificate-exceptions are thrown, - * probably the certificate of the other party is not trusted. Try to use one of the certificates in the path as your truststore by doing the following: - *

    - *
  • open the URL you are trying to reach in InternetExplorer
  • - *
  • click on the yellow padlock on the right in the bottom-bar. This opens the certificate information window
  • - *
  • click on tab 'Certificeringspad'
  • - *
  • double click on root certificate in the tree displayed. This opens the certificate information window for the root certificate
  • - *
  • click on tab 'Details'
  • - *
  • click on 'Kopieren naar bestand'
  • - *
  • click 'next', choose 'DER Encoded Binary X.509 (.CER)'
  • - *
  • click 'next', choose a filename
  • - *
  • click 'next' and 'finish'
  • - *
  • Start IBM key management tool ikeyman.bat, located in Program Files/IBM/WebSphere Studio/Application Developer/v5.1.2/runtimes/base_v51/bin (or similar)
  • - *
  • create a new key-database (Sleuteldatabase -> Nieuw...), or open the default key.jks (default password="changeit")
  • - *
  • add the generated certificate (Toevoegen...)
  • - *
  • store the key-database in JKS format
  • - *
  • if you didn't use the standard keydatabase, then reference the file in the truststore-attribute in Configuration.xml (include the file as a resource)
  • - *
  • use jks for the truststoreType-attribute
  • - *
  • restart your application
  • - *
  • instead of IBM ikeyman you can use the standard java tool keytool as follows: - * keytool -import -alias yourAlias -file pathToSavedCertificate
  • - *
- *

- * Note 4: - * In case cannot create or initialize SocketFactory: (IOException) Unable to verify MAC-exceptions are thrown, - * please check password or authAlias configuration of the corresponding certificate. - *

- * * @ff.parameters Any parameters present are appended to the request (when method is GET as request-parameters, when method POST as body part) except the headersParams list, which are added as HTTP headers, and the urlParam header * @ff.forward "<statusCode of the HTTP response>" default * @@ -171,15 +87,17 @@ import nl.nn.adapterframework.util.XmlUtils; */ //TODO: Fix javadoc! -public abstract class HttpSenderBase extends SenderWithParametersBase implements HasPhysicalDestination, HasKeystore, HasTruststore { +public abstract class HttpSenderBase extends HttpSessionBase implements HasPhysicalDestination, ISenderWithParameters, CanUseSharedResource { - private final String CONTEXT_KEY_STATUS_CODE="Http.StatusCode"; - private final String CONTEXT_KEY_REASON_PHRASE="Http.ReasonPhrase"; + private static final String CONTEXT_KEY_STATUS_CODE = "Http.StatusCode"; + private static final String CONTEXT_KEY_REASON_PHRASE = "Http.ReasonPhrase"; public static final String MESSAGE_ID_HEADER = "Message-Id"; public static final String CORRELATION_ID_HEADER = "Correlation-Id"; private final @Getter(onMethod = @__(@Override)) String domain = "Http"; + private @Setter String sharedResourceRef; + private @Getter String url; private @Getter String urlParam = "url"; @@ -192,127 +110,55 @@ public abstract class HttpSenderBase extends SenderWithParametersBase implements private @Getter ContentType fullContentType = null; private @Getter String contentType = null; - /* CONNECTION POOL */ - private @Getter int timeout = 10000; - private @Getter int maxConnections = 10; - private @Getter int maxExecuteRetries = 1; - private @Getter boolean staleChecking=true; - private @Getter int staleTimeout = 5000; // [ms] - private @Getter int connectionTimeToLive = 900; // [s] - private @Getter int connectionIdleTimeout = 10; // [s] - private HttpClientBuilder httpClientBuilder = HttpClientBuilder.create(); - private @Getter HttpClientContext httpClientContext = HttpClientContext.create(); - private @Getter CloseableHttpClient httpClient; - - /* SECURITY */ - private @Getter String authAlias; - private @Getter String username; - private @Getter String password; - private @Getter String authDomain; - private @Getter String tokenEndpoint; - private @Getter int tokenExpiry=-1; - private @Getter String clientAuthAlias; - private @Getter String clientId; - private @Getter String clientSecret; - private @Getter String scope; - private @Getter boolean authenticatedTokenRequest; - - /* PROXY */ - private @Getter String proxyHost; - private @Getter int proxyPort=80; - private @Getter String proxyAuthAlias; - private @Getter String proxyUsername; - private @Getter String proxyPassword; - private @Getter String proxyRealm=null; - - /* SSL */ - private @Getter String keystore; - private @Getter String keystoreAuthAlias; - private @Getter String keystorePassword; - private @Getter KeystoreType keystoreType=KeystoreType.PKCS12; - private @Getter String keystoreAlias; - private @Getter String keystoreAliasAuthAlias; - private @Getter String keystoreAliasPassword; - private @Getter String keyManagerAlgorithm=null; - - private @Getter String truststore=null; - private @Getter String truststoreAuthAlias; - private @Getter String truststorePassword=null; - private @Getter KeystoreType truststoreType=KeystoreType.JKS; - private @Getter String trustManagerAlgorithm=null; - private @Getter boolean allowSelfSignedCertificates = false; - private @Getter boolean verifyHostname=true; - private @Getter boolean ignoreCertificateExpiredException=false; - private @Getter String headersParams=""; - private @Getter boolean followRedirects=true; - private @Getter boolean ignoreRedirects=false; private @Getter boolean xhtml=false; private @Getter String styleSheetName=null; - private @Getter String protocol=null; private @Getter String resultStatusCodeSessionKey; private @Getter String parametersToSkipWhenEmpty; private final boolean APPEND_MESSAGEID_HEADER = AppConstants.getInstance(getConfigurationClassLoader()).getBoolean("http.headers.messageid", true); private final boolean APPEND_CORRELATIONID_HEADER = AppConstants.getInstance(getConfigurationClassLoader()).getBoolean("http.headers.correlationid", true); - private boolean disableCookies = false; private TransformerPool transformerPool=null; protected Parameter urlParameter; protected URI staticUri; - private CredentialFactory credentials; - private CredentialFactory user_cf; - private CredentialFactory client_cf; protected Set requestOrBodyParamsSet=new HashSet<>(); protected Set headerParamsSet=new LinkedHashSet<>(); protected Set parametersToSkipWhenEmptySet=new HashSet<>(); - /** - * Makes sure only http(s) requests can be performed. - */ - protected URI getURI(String url) throws URISyntaxException { - URIBuilder uri = new URIBuilder(url); - - if(uri.getScheme() == null) { - throw new URISyntaxException("", "must use an absolute url starting with http(s)://"); - } - if (!uri.getScheme().matches("(?i)https?")) { - throw new IllegalArgumentException(ClassUtils.nameOf(this) + " only supports web based schemes. (http or https)"); - } + protected ParameterList paramList = null; - if (uri.getPath()==null) { - uri.setPath("/"); + @Override + public void addParameter(Parameter p) { + if (paramList==null) { + paramList=new ParameterList(); } + paramList.add(p); + } - log.info(getLogPrefix()+"created uri: scheme=["+uri.getScheme()+"] host=["+uri.getHost()+"] path=["+uri.getPath()+"]"); - return uri.build(); + /** + * return the Parameters + */ + @Override + public ParameterList getParameterList() { + return paramList; } @Override public void configure() throws ConfigurationException { - super.configure(); - - /** - * TODO find out if this really breaks proxy authentication or not. - */ -// httpClientBuilder.disableAuthCaching(); - - Builder requestConfigBuilder = RequestConfig.custom(); - requestConfigBuilder.setConnectTimeout(getTimeout()); - requestConfigBuilder.setConnectionRequestTimeout(getTimeout()); - requestConfigBuilder.setSocketTimeout(getTimeout()); + if(StringUtils.isBlank(sharedResourceRef)) { + log.info("configuring local HttpSession"); + super.configure(); + } if (paramList!=null) { paramList.configure(); + if (StringUtils.isNotEmpty(getHeadersParams())) { - StringTokenizer st = new StringTokenizer(getHeadersParams(), ","); - while (st.hasMoreElements()) { - String paramName = st.nextToken().trim(); - headerParamsSet.add(paramName); - } + headerParamsSet.addAll(StringUtil.split(getHeadersParams())); } for (Parameter p: paramList) { String paramName = p.getName(); @@ -332,26 +178,18 @@ public abstract class HttpSenderBase extends SenderWithParametersBase implements parametersToSkipWhenEmptySet.addAll(headerParamsSet); parametersToSkipWhenEmptySet.addAll(requestOrBodyParamsSet); } else { - StringTokenizer st = new StringTokenizer(getParametersToSkipWhenEmpty(), ","); - while (st.hasMoreElements()) { - String paramName = st.nextToken().trim(); - parametersToSkipWhenEmptySet.add(paramName); - } + parametersToSkipWhenEmptySet.addAll(StringUtil.split(getParametersToSkipWhenEmpty())); } } } if(StringUtils.isNotEmpty(getContentType())) { fullContentType = ContentType.parse(getContentType()); - if(fullContentType != null && fullContentType.getCharset() == null) { + if(fullContentType.getCharset() == null) { fullContentType = fullContentType.withCharset(getCharSet()); } } - if (getMaxConnections() <= 0) { - throw new ConfigurationException(getLogPrefix()+"maxConnections is set to ["+getMaxConnections()+"], which is not enough for adequate operation"); - } - try { if (urlParameter == null) { if (StringUtils.isEmpty(getUrl())) { @@ -360,102 +198,32 @@ public abstract class HttpSenderBase extends SenderWithParametersBase implements staticUri = getURI(getUrl()); } } catch (URISyntaxException e) { - throw new ConfigurationException(getLogPrefix()+"cannot interpret url ["+getUrl()+"]", e); - } - - AuthSSLContextFactory.verifyKeystoreConfiguration(this, this); - - if (StringUtils.isNotEmpty(getAuthAlias()) || StringUtils.isNotEmpty(getUsername())) { - user_cf = new CredentialFactory(getAuthAlias(), getUsername(), getPassword()); - credentials = user_cf; - } - client_cf = new CredentialFactory(getClientAuthAlias(), getClientId(), getClientSecret()); - if (credentials==null) { - credentials = client_cf; - } - if (StringUtils.isNotEmpty(getTokenEndpoint()) && StringUtils.isEmpty(getClientAuthAlias()) && StringUtils.isEmpty(getClientId())) { - throw new ConfigurationException("To obtain accessToken at tokenEndpoint ["+getTokenEndpoint()+"] a clientAuthAlias or ClientId and ClientSecret must be specified"); - } - HttpHost proxy = null; - CredentialFactory pcf = null; - if (StringUtils.isNotEmpty(getProxyHost())) { - proxy = new HttpHost(getProxyHost(), getProxyPort()); - pcf = new CredentialFactory(getProxyAuthAlias(), getProxyUsername(), getProxyPassword()); - requestConfigBuilder.setProxy(proxy); - httpClientBuilder.setProxy(proxy); - } - - try { - setupAuthentication(pcf, proxy, requestConfigBuilder); - } catch (HttpAuthenticationException e) { - throw new ConfigurationException("exception configuring authentication", e); + throw new ConfigurationException("cannot interpret url ["+getUrl()+"]", e); } if (StringUtils.isNotEmpty(getStyleSheetName())) { try { Resource stylesheet = Resource.getResource(this, getStyleSheetName()); if (stylesheet == null) { - throw new ConfigurationException(getLogPrefix() + "cannot find stylesheet ["+getStyleSheetName()+"]"); + throw new ConfigurationException("cannot find stylesheet ["+getStyleSheetName()+"]"); } transformerPool = TransformerPool.getInstance(stylesheet); } catch (IOException e) { - throw new ConfigurationException(getLogPrefix() + "cannot retrieve ["+ getStyleSheetName() + "]", e); + throw new ConfigurationException("cannot retrieve ["+ getStyleSheetName() + "]", e); } catch (TransformerConfigurationException te) { - throw new ConfigurationException(getLogPrefix() + "got error creating transformer from file [" + getStyleSheetName() + "]", te); + throw new ConfigurationException("got error creating transformer from file [" + getStyleSheetName() + "]", te); } } - - httpClientBuilder.setDefaultRequestConfig(requestConfigBuilder.build()); - - httpClientBuilder.setRetryHandler(new HttpRequestRetryHandler(getMaxExecuteRetries())); - - if(areCookiesDisabled()) { - httpClientBuilder.disableCookieManagement(); - } - - // The redirect strategy used to only redirect GET, DELETE and HEAD. - httpClientBuilder.setRedirectStrategy(new DefaultRedirectStrategy() { - @Override - protected boolean isRedirectable(String method) { - return isFollowRedirects(); - } - }); } @Override public void open() throws SenderException { - // In order to support multiThreading and connectionPooling - // If a sslSocketFactory has been defined, the connectionManager has to be initialized with the sslSocketFactory - PoolingHttpClientConnectionManager connectionManager; - int timeToLive = getConnectionTimeToLive(); - if (timeToLive<=0) { - timeToLive = -1; - } - SSLConnectionSocketFactory sslSocketFactory = getSSLConnectionSocketFactory(); - if(sslSocketFactory != null) { - Registry socketFactoryRegistry = RegistryBuilder.create() - .register("http", PlainConnectionSocketFactory.getSocketFactory()) - .register("https", sslSocketFactory) - .build(); - connectionManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry, null, null, null, timeToLive, TimeUnit.SECONDS); - log.debug(getLogPrefix()+"created PoolingHttpClientConnectionManager with custom SSLConnectionSocketFactory"); - } - else { - connectionManager = new PoolingHttpClientConnectionManager(timeToLive, TimeUnit.SECONDS); - log.debug(getLogPrefix()+"created default PoolingHttpClientConnectionManager"); - } - - connectionManager.setMaxTotal(getMaxConnections()); - connectionManager.setDefaultMaxPerRoute(getMaxConnections()); - - if (isStaleChecking()) { - log.info(getLogPrefix()+"set up connectionManager, setting stale checking ["+isStaleChecking()+"]"); - connectionManager.setValidateAfterInactivity(getStaleTimeout()); + try { + start(); + } catch (Exception e) { + throw new SenderException(getLogPrefix()+"unable to create HttpClient", e); } - httpClientBuilder.setConnectionManager(connectionManager); - httpClientBuilder.evictIdleConnections(getConnectionIdleTimeout(), TimeUnit.SECONDS); - if (transformerPool!=null) { try { transformerPool.open(); @@ -463,116 +231,36 @@ public abstract class HttpSenderBase extends SenderWithParametersBase implements throw new SenderException(getLogPrefix()+"cannot start TransformerPool", e); } } - - httpClient = httpClientBuilder.build(); } @Override - public void close() throws SenderException { - try { - //Close the HttpClient and ConnectionManager to release resources and potential open connections - if(httpClient != null) { - httpClient.close(); - } - } catch (IOException e) { - throw new SenderException(e); - } - - if (transformerPool!=null) { - transformerPool.close(); - } + public Class getObjectType() { + return CloseableHttpClient.class; } - private void setupAuthentication(CredentialFactory proxyCredentials, HttpHost proxy, Builder requestConfigBuilder) throws HttpAuthenticationException { - CredentialsProvider credentialsProvider = new BasicCredentialsProvider(); - if (StringUtils.isNotEmpty(credentials.getUsername()) || StringUtils.isNotEmpty(getTokenEndpoint())) { - - credentialsProvider.setCredentials(new AuthScope(AuthScope.ANY_HOST, AuthScope.ANY_PORT), getCredentials()); - - AuthenticationScheme preferredAuthenticationScheme = getPreferredAuthenticationScheme(); - requestConfigBuilder.setTargetPreferredAuthSchemes(Arrays.asList(preferredAuthenticationScheme.getSchemeName())); - requestConfigBuilder.setAuthenticationEnabled(true); - - if (preferredAuthenticationScheme == AuthenticationScheme.OAUTH) { - OAuthAccessTokenManager accessTokenManager = new OAuthAccessTokenManager(getTokenEndpoint(), getScope(), client_cf, user_cf==null, isAuthenticatedTokenRequest(), this, getTokenExpiry()); - httpClientContext.setAttribute(OAuthAuthenticationScheme.ACCESSTOKEN_MANAGER_KEY, accessTokenManager); - httpClientBuilder.setTargetAuthenticationStrategy(new OAuthPreferringAuthenticationStrategy()); - } - } - if (proxy!=null) { - AuthScope scope = new AuthScope(proxy, proxyRealm, AuthScope.ANY_SCHEME); - - - if (StringUtils.isNotEmpty(proxyCredentials.getUsername())) { - Credentials credentials = new UsernamePasswordCredentials(proxyCredentials.getUsername(), proxyCredentials.getPassword()); - credentialsProvider.setCredentials(scope, credentials); - } - //log.trace("setting credentialProvider [" + credentialsProvider.toString() + "]"); - - if(prefillProxyAuthCache()) { - requestConfigBuilder.setProxyPreferredAuthSchemes(Arrays.asList(AuthSchemes.BASIC)); - - AuthCache authCache = httpClientContext.getAuthCache(); - if(authCache == null) - authCache = new BasicAuthCache(); - - authCache.put(proxy, new BasicScheme()); - httpClientContext.setAuthCache(authCache); - } - - } - - httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider); - } - - private void preAuthenticate() { - if (credentials != null && !StringUtils.isEmpty(credentials.getUsername())) { - AuthState authState = httpClientContext.getTargetAuthState(); - if (authState==null) { - authState = new AuthState(); - httpClientContext.setAttribute(HttpClientContext.TARGET_AUTH_STATE, authState); - } - authState.setState(AuthProtocolState.CHALLENGED); - authState.update(getPreferredAuthenticationScheme().createScheme(), getCredentials()); - } - } - - private Credentials getCredentials() { - String uname; - if (StringUtils.isNotEmpty(getAuthDomain())) { - uname = getAuthDomain() + "\\" + credentials.getUsername(); + @Override + public void start() { + if(StringUtils.isNotBlank(sharedResourceRef)) { + setHttpClient(getSharedResource(sharedResourceRef)); } else { - uname = credentials.getUsername(); + buildHttpClient(); } - - return new UsernamePasswordCredentials(uname, credentials.getPassword()); - } - - private AuthenticationScheme getPreferredAuthenticationScheme() { - return StringUtils.isNotEmpty(getTokenEndpoint()) ? AuthenticationScheme.OAUTH : AuthenticationScheme.BASIC; } - protected SSLConnectionSocketFactory getSSLConnectionSocketFactory() throws SenderException { - SSLConnectionSocketFactory sslSocketFactory; - HostnameVerifier hostnameVerifier = verifyHostname ? new DefaultHostnameVerifier() : new NoopHostnameVerifier(); - - try { - javax.net.ssl.SSLSocketFactory socketfactory = AuthSSLContextFactory.createSSLSocketFactory(this, this, getProtocol()); - sslSocketFactory = new SSLConnectionSocketFactory(socketfactory, hostnameVerifier); - } catch (Exception e) { - throw new SenderException("cannot create or initialize SocketFactory", e); + @Override + public void close() { + if(StringUtils.isBlank(sharedResourceRef)) { + super.stop(); } - // This method will be overwritten by the connectionManager when connectionPooling is enabled! - // Can still be null when no default or an invalid system sslSocketFactory has been defined - if(sslSocketFactory != null) { - httpClientBuilder.setSSLSocketFactory(sslSocketFactory); + + if (transformerPool!=null) { + transformerPool.close(); } - return sslSocketFactory; } - protected boolean appendParameters(boolean parametersAppended, StringBuffer path, ParameterValueList parameters) throws SenderException { + protected boolean appendParameters(boolean parametersAppended, StringBuilder path, ParameterValueList parameters) throws SenderException { if (parameters != null) { - if (log.isDebugEnabled()) log.debug(getLogPrefix()+"appending ["+parameters.size()+"] parameters"); + log.debug("appending [{}] parameters", parameters::size); for(ParameterValue pv : parameters) { if (requestOrBodyParamsSet.contains(pv.getName())) { String value = pv.asStringValue(""); @@ -586,7 +274,7 @@ public abstract class HttpSenderBase extends SenderWithParametersBase implements } String parameterToAppend = pv.getDefinition().getName() +"="+ URLEncoder.encode(value, getCharSet()); - if (log.isDebugEnabled()) log.debug(getLogPrefix()+"appending parameter ["+parameterToAppend+"]"); + log.debug("appending parameter [{}]", parameterToAppend); path.append(parameterToAppend); } catch (UnsupportedEncodingException e) { throw new SenderException(getLogPrefix()+"["+getCharSet()+"] encoding error. Failed to add parameter ["+pv.getDefinition().getName()+"]", e); @@ -598,6 +286,17 @@ public abstract class HttpSenderBase extends SenderWithParametersBase implements return parametersAppended; } + + /** + * Returns the true name of the class and not XsltPipe$$EnhancerBySpringCGLIB$$563e6b5d. + * {@link ClassUtils#nameOf(Object)} makes sure the original class will be used. + * + * @return className + name of the ISender + */ + protected String getLogPrefix() { + return ClassUtils.nameOf(this) + " "; + } + /** * Custom implementation to create a {@link HttpRequestBase HttpRequest} object. * @param uri endpoint to send the message to @@ -690,17 +389,16 @@ public abstract class HttpSenderBase extends SenderWithParametersBase implements preAuthenticate(); - log.info(getLogPrefix()+"configured httpclient for host ["+targetUri.getHost()+"]"); + log.info("configured httpclient for host [{}]", targetUri::getHost); } catch (Exception e) { throw new SenderException(e); } - Message result = null; - int statusCode = -1; + Message result; + int statusCode; boolean success; - String reasonPhrase = null; - HttpHost targetHost = new HttpHost(targetUri.getHost(), targetUri.getPort(), targetUri.getScheme()); + String reasonPhrase; TimeoutGuard tg = new TimeoutGuard(1+getTimeout()/1000, getName()) { @@ -711,9 +409,9 @@ public abstract class HttpSenderBase extends SenderWithParametersBase implements }; try { - log.debug(getLogPrefix()+"executing method [" + httpRequestBase.getRequestLine() + "]"); - HttpResponse httpResponse = getHttpClient().execute(targetHost, httpRequestBase, httpClientContext); - log.debug(getLogPrefix()+"executed method"); + log.debug("executing method [{}]", httpRequestBase::getRequestLine); + HttpResponse httpResponse = execute(targetUri, httpRequestBase); + log.debug("executed method"); HttpResponseHandler responseHandler = new HttpResponseHandler(httpResponse); StatusLine statusline = httpResponse.getStatusLine(); @@ -727,14 +425,14 @@ public abstract class HttpSenderBase extends SenderWithParametersBase implements // Only give warnings for 4xx (client errors) and 5xx (server errors) if (statusCode >= 400 && statusCode < 600) { - log.warn(getLogPrefix()+"status ["+statusline.toString()+"]"); + log.warn("status [{}]", statusline); } else { - log.debug(getLogPrefix()+"status ["+statusCode+"]"); + log.debug("status [{}]", statusCode); } result = extractResult(responseHandler, session); - log.debug(getLogPrefix()+"retrieved result ["+result+"]"); + log.debug("retrieved result [{}]", result); } catch (IOException e) { httpRequestBase.abort(); if (e instanceof SocketTimeoutException) { @@ -749,8 +447,8 @@ public abstract class HttpSenderBase extends SenderWithParametersBase implements // in a sessionKey for later use in the pipeline. // // IMPORTANT: It is possible that poorly written implementations - // wont read or close the response. - // This will cause the connection to become stale.. + // won't read or close the response. + // This will cause the connection to become stale. if (tg.cancel()) { throw new TimeoutException(getLogPrefix()+"timeout of ["+getTimeout()+"] ms exceeded"); @@ -762,15 +460,16 @@ public abstract class HttpSenderBase extends SenderWithParametersBase implements } if (isXhtml() && !Message.isEmpty(result)) { + // TODO: Streaming XHTML conversion for better performance with large result message? String xhtml; - try { - xhtml = XmlUtils.toXhtml(result); + try(Message m = result) { + xhtml = XmlUtils.toXhtml(m); } catch (IOException e) { throw new SenderException("error reading http response as String", e); } if (transformerPool != null && xhtml != null) { - log.debug(getLogPrefix() + " transforming result [" + xhtml + "]"); + log.debug("transforming result [{}]", xhtml); try { xhtml = transformerPool.transform(Message.asSource(xhtml)); } catch (Exception e) { @@ -781,7 +480,7 @@ public abstract class HttpSenderBase extends SenderWithParametersBase implements result = Message.asMessage(xhtml); } - if (result==null) { + if (result == null) { result = Message.nullMessage(); } log.debug("Storing [{}]=[{}], [{}]=[{}]", CONTEXT_KEY_STATUS_CODE, statusCode, CONTEXT_KEY_REASON_PHRASE, reasonPhrase); @@ -835,174 +534,18 @@ public abstract class HttpSenderBase extends SenderWithParametersBase implements charSet = string; } - /** - * Timeout in ms of obtaining a connection/result. 0 means no timeout - * @ff.default 10000 - */ - public void setTimeout(int i) { - timeout = i; - } - - /** - * The maximum number of concurrent connections - * @ff.default 10 - */ - public void setMaxConnections(int i) { - maxConnections = i; - } - - /** - * The maximum number of times the execution is retried - * @ff.default 1 (for repeatable messages) else 0 - */ - public void setMaxExecuteRetries(int i) { - maxExecuteRetries = i; - } - - /** Authentication alias used for authentication to the host */ - public void setAuthAlias(String string) { - authAlias = string; - } - - /** Username used for authentication to the host */ - public void setUsername(String username) { - this.username = username; - } @Deprecated @ConfigurationWarning("Please use attribute username instead") public void setUserName(String username) { setUsername(username); } - /** Password used for authentication to the host */ - public void setPassword(String string) { - password = string; - } - - /** - * Corporate domain name. Should only be used in combination with sAMAccountName, never with an UPN.
- *
- * Assuming the following user:
- * UPN: john.doe@CorpDomain.biz
- * sAMAccountName: CORPDOMAIN\john.doe
- *
- * The username attribute may be set to john.doe
- * The AuthDomain attribute may be set to CORPDOMAIN
- */ - @Deprecated - @ConfigurationWarning("Please use the UPN or the full sAM-AccountName instead") - public void setAuthDomain(String string) { - authDomain = string; - } - - /** - * Endpoint to obtain OAuth accessToken. If authAlias or username( and password) are specified, - * then a PasswordGrant is used, otherwise a ClientCredentials grant. The obtained accessToken will be added to the regular requests - * in an HTTP Header 'Authorization' with a 'Bearer' prefix. - */ - public void setTokenEndpoint(String string) { - tokenEndpoint = string; - } - /** - * If set to a non-negative value, then determines the time (in seconds) after which the token will be refreshed. Otherwise the token - * will be refreshed when it is half way its lifetime as defined by the expires_in clause of the token response, - * or when the regular server returns a 401 status with a challenge. - * If not specified, and the accessTokens lifetime is not found in the token response, the accessToken will not be refreshed preemptively. - * @ff.default -1 - */ - public void setTokenExpiry(int value) { - tokenExpiry = value; - } - /** Alias used to obtain client_id and client_secret for authentication to tokenEndpoint */ - public void setClientAlias(String clientAuthAlias) { - this.clientAuthAlias = clientAuthAlias; - } - /** Client_id used in authentication to tokenEndpoint */ - public void setClientId(String clientId) { - this.clientId = clientId; - } - - /** Client_secret used in authentication to tokenEndpoint */ - public void setClientSecret(String clientSecret) { - this.clientSecret = clientSecret; - } - /** Space or comma separated list of scope items requested for accessToken, e.g. read write. Only used when tokenEndpoint is specified */ - public void setScope(String string) { - scope = string; - } - /** if set true, clientId and clientSecret will be added as Basic Authentication header to the tokenRequest, instead of as request parameters */ - public void setAuthenticatedTokenRequest(boolean authenticatedTokenRequest) { - this.authenticatedTokenRequest = authenticatedTokenRequest; - } - - - /** Proxy host */ - public void setProxyHost(String string) { - proxyHost = string; - } - - /** - * Proxy port - * @ff.default 80 - */ - public void setProxyPort(int i) { - proxyPort = i; - } - - /** Alias used to obtain credentials for authentication to proxy */ - public void setProxyAuthAlias(String string) { - proxyAuthAlias = string; - } - - /** - * Proxy username - * @ff.default - */ - public void setProxyUsername(String string) { - proxyUsername = string; - } @Deprecated @ConfigurationWarning("Please use \"proxyUsername\" instead") public void setProxyUserName(String string) { setProxyUsername(string); } - /** - * Proxy password - * @ff.default - */ - public void setProxyPassword(String string) { - proxyPassword = string; - } - - /** - * Proxy realm - * @ff.default - */ - public void setProxyRealm(String string) { - proxyRealm = StringUtils.isNotEmpty(string) ? string : null; - } - - /** - * TODO: make this configurable - * @return false - */ - public boolean prefillProxyAuthCache() { - return false; - } - - /** - * Disables the use of cookies, making the sender completely stateless - * @ff.default false - */ - public void setDisableCookies(boolean disableCookies) { - this.disableCookies = disableCookies; - } - public boolean areCookiesDisabled() { - return disableCookies; - } - - @Deprecated @ConfigurationWarning("Please use attribute keystore instead") public void setCertificate(String string) { @@ -1024,87 +567,6 @@ public abstract class HttpSenderBase extends SenderWithParametersBase implements setKeystorePassword(string); } - /** resource URL to keystore or certificate to be used for authentication. If none specified, the JVMs default keystore will be used. */ - @Override - public void setKeystore(String string) { - keystore = string; - } - - @Override - public void setKeystoreType(KeystoreType value) { - keystoreType = value; - } - - @Override - public void setKeystoreAuthAlias(String string) { - keystoreAuthAlias = string; - } - - @Override - public void setKeystorePassword(String string) { - keystorePassword = string; - } - - @Override - public void setKeyManagerAlgorithm(String keyManagerAlgorithm) { - this.keyManagerAlgorithm = keyManagerAlgorithm; - } - - @Override - public void setKeystoreAlias(String string) { - keystoreAlias = string; - } - @Override - public void setKeystoreAliasAuthAlias(String string) { - keystoreAliasAuthAlias = string; - } - @Override - public void setKeystoreAliasPassword(String string) { - keystoreAliasPassword = string; - } - - @Override - /** Resource URL to truststore to be used for authenticating peer. If none specified, the JVMs default truststore will be used. */ - public void setTruststore(String string) { - truststore = string; - } - - @Override - public void setTruststoreAuthAlias(String string) { - truststoreAuthAlias = string; - } - - @Override - public void setTruststorePassword(String string) { - truststorePassword = string; - } - - @Override - public void setTruststoreType(KeystoreType value) { - truststoreType = value; - } - - @Override - public void setTrustManagerAlgorithm(String trustManagerAlgorithm) { - this.trustManagerAlgorithm = trustManagerAlgorithm; - } - - @Override - public void setVerifyHostname(boolean b) { - verifyHostname = b; - } - - @Override - public void setAllowSelfSignedCertificates(boolean allowSelfSignedCertificates) { - this.allowSelfSignedCertificates = allowSelfSignedCertificates; - } - - @Override - public void setIgnoreCertificateExpiredException(boolean b) { - ignoreCertificateExpiredException = b; - } - - /** Comma separated list of parameter names which should be set as HTTP headers */ public void setHeadersParams(String headersParams) { this.headersParams = headersParams; @@ -1115,56 +577,6 @@ public abstract class HttpSenderBase extends SenderWithParametersBase implements this.parametersToSkipWhenEmpty = parametersToSkipWhenEmpty; } - - /** - * If true, a redirect request will be honoured, e.g. to switch to HTTPS - * @ff.default true - */ - public void setFollowRedirects(boolean b) { - followRedirects = b; - } - - /** - * If true, besides http status code 200 (OK) also the code 301 (MOVED_PERMANENTLY), 302 (MOVED_TEMPORARILY) and 307 (TEMPORARY_REDIRECT) are considered successful - * @ff.default false - */ - public void setIgnoreRedirects(boolean b) { - ignoreRedirects = b; - } - - - /** - * Controls whether connections checked to be stale, i.e. appear open, but are not. - * @ff.default true - */ - public void setStaleChecking(boolean b) { - staleChecking = b; - } - - /** - * Used when StaleChecking=true. Timeout after which an idle connection will be validated before being used. - * @ff.default 5000 ms - */ - public void setStaleTimeout(int timeout) { - staleTimeout = timeout; - } - - /** - * Maximum Time to Live for connections in the pool. No connection will be re-used past its timeToLive value. - * @ff.default 900 s - */ - public void setConnectionTimeToLive(int timeToLive) { - connectionTimeToLive = timeToLive; - } - - /** - * Maximum Time for connection to stay idle in the pool. Connections that are idle longer will periodically be evicted from the pool - * @ff.default 10 s - */ - public void setConnectionIdleTimeout(int idleTimeout) { - connectionIdleTimeout = idleTimeout; - } - /** * If true, the HTML response is transformed to XHTML * @ff.default false @@ -1178,14 +590,6 @@ public abstract class HttpSenderBase extends SenderWithParametersBase implements this.styleSheetName=stylesheetName; } - /** - * Secure socket protocol (such as 'SSL' and 'TLS') to use when a SSLContext object is generated. - * @ff.default SSL - */ - public void setProtocol(String protocol) { - this.protocol = protocol; - } - /** If set, the status code of the HTTP response is put in the specified sessionKey and the (error or okay) response message is returned. * Setting this property has a side effect. If a 4xx or 5xx result code is returned and if the configuration does not implement * the specific forward for the returned HTTP result code, then the success forward is followed instead of the exception forward. @@ -1193,4 +597,4 @@ public abstract class HttpSenderBase extends SenderWithParametersBase implements public void setResultStatusCodeSessionKey(String resultStatusCodeSessionKey) { this.resultStatusCodeSessionKey = resultStatusCodeSessionKey; } -} \ No newline at end of file +} diff --git a/java/nl/nn/adapterframework/http/HttpSessionBase .java-orig b/java/nl/nn/adapterframework/http/HttpSessionBase .java-orig new file mode 100644 index 0000000..7978fc2 --- /dev/null +++ b/java/nl/nn/adapterframework/http/HttpSessionBase .java-orig @@ -0,0 +1,741 @@ +/* + Copyright 2017-2023 WeAreFrank! + + Licensed 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 nl.nn.adapterframework.http; + +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Arrays; +import java.util.concurrent.TimeUnit; + +import javax.annotation.Nonnull; +import javax.net.ssl.HostnameVerifier; + +import org.apache.commons.lang3.StringUtils; +import org.apache.http.HttpHost; +import org.apache.http.HttpResponse; +import org.apache.http.auth.AuthProtocolState; +import org.apache.http.auth.AuthScope; +import org.apache.http.auth.AuthState; +import org.apache.http.auth.Credentials; +import org.apache.http.auth.UsernamePasswordCredentials; +import org.apache.http.client.AuthCache; +import org.apache.http.client.CredentialsProvider; +import org.apache.http.client.config.AuthSchemes; +import org.apache.http.client.config.RequestConfig; +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.HttpRequestBase; +import org.apache.http.client.protocol.HttpClientContext; +import org.apache.http.client.utils.URIBuilder; +import org.apache.http.config.Registry; +import org.apache.http.config.RegistryBuilder; +import org.apache.http.conn.socket.ConnectionSocketFactory; +import org.apache.http.conn.socket.PlainConnectionSocketFactory; +import org.apache.http.conn.ssl.DefaultHostnameVerifier; +import org.apache.http.conn.ssl.NoopHostnameVerifier; +import org.apache.http.conn.ssl.SSLConnectionSocketFactory; +import org.apache.http.impl.auth.BasicScheme; +import org.apache.http.impl.client.BasicAuthCache; +import org.apache.http.impl.client.BasicCredentialsProvider; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.DefaultRedirectStrategy; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; +import org.apache.logging.log4j.Logger; +import org.springframework.context.ApplicationContext; + +import lombok.Getter; +import lombok.Setter; +import nl.nn.adapterframework.configuration.ConfigurationException; +import nl.nn.adapterframework.configuration.ConfigurationWarning; +import nl.nn.adapterframework.encryption.AuthSSLContextFactory; +import nl.nn.adapterframework.encryption.HasKeystore; +import nl.nn.adapterframework.encryption.HasTruststore; +import nl.nn.adapterframework.encryption.KeystoreType; +import nl.nn.adapterframework.http.authentication.AuthenticationScheme; +import nl.nn.adapterframework.http.authentication.HttpAuthenticationException; +import nl.nn.adapterframework.http.authentication.OAuthAccessTokenManager; +import nl.nn.adapterframework.http.authentication.OAuthAuthenticationScheme; +import nl.nn.adapterframework.http.authentication.OAuthPreferringAuthenticationStrategy; +import nl.nn.adapterframework.lifecycle.ConfigurableLifecycle; +import nl.nn.adapterframework.util.ClassUtils; +import nl.nn.adapterframework.util.CredentialFactory; +import nl.nn.adapterframework.util.LogUtil; + +/** + *

+ * Note 1: + * Some certificates require the <java_home>/jre/lib/security/xxx_policy.jar files to be upgraded to unlimited strength. Typically, in such a case, an error message like + * Error in loading the keystore: Private key decryption error: (java.lang.SecurityException: Unsupported keysize or algorithm parameters is observed. + * For IBM JDKs these files can be downloaded from http://www.ibm.com/developerworks/java/jdk/security/50/ (scroll down to 'IBM SDK Policy files') + *

+ * Replace in the directory java\jre\lib\security the following files: + *
    + *
  • local_policy.jar
  • + *
  • US_export_policy.jar
  • + *
+ *

+ * Note 2: + * To debug ssl-related problems, set the following system property: + *

    + *
  • IBM / WebSphere: -Djavax.net.debug=true
  • + *
  • SUN: -Djavax.net.debug=all
  • + *
+ *

+ *

+ * Note 3: + * In case javax.net.ssl.SSLHandshakeException: unknown certificate-exceptions are thrown, + * probably the certificate of the other party is not trusted. Try to use one of the certificates in the path as your truststore by doing the following: + *

    + *
  • open the URL you are trying to reach in InternetExplorer
  • + *
  • click on the yellow padlock on the right in the bottom-bar. This opens the certificate information window
  • + *
  • click on tab 'Certificeringspad'
  • + *
  • double click on root certificate in the tree displayed. This opens the certificate information window for the root certificate
  • + *
  • click on tab 'Details'
  • + *
  • click on 'Kopieren naar bestand'
  • + *
  • click 'next', choose 'DER Encoded Binary X.509 (.CER)'
  • + *
  • click 'next', choose a filename
  • + *
  • click 'next' and 'finish'
  • + *
  • Start IBM key management tool ikeyman.bat, located in Program Files/IBM/WebSphere Studio/Application Developer/v5.1.2/runtimes/base_v51/bin (or similar)
  • + *
  • create a new key-database (Sleuteldatabase -> Nieuw...), or open the default key.jks (default password="changeit")
  • + *
  • add the generated certificate (Toevoegen...)
  • + *
  • store the key-database in JKS format
  • + *
  • if you didn't use the standard keydatabase, then reference the file in the truststore-attribute in Configuration.xml (include the file as a resource)
  • + *
  • use jks for the truststoreType-attribute
  • + *
  • restart your application
  • + *
  • instead of IBM ikeyman you can use the standard java tool keytool as follows: + * keytool -import -alias yourAlias -file pathToSavedCertificate
  • + *
+ *

+ * Note 4: + * In case cannot create or initialize SocketFactory: (IOException) Unable to verify MAC-exceptions are thrown, + * please check password or authAlias configuration of the corresponding certificate. + *

+ * + * @author Niels Meijer + * @since 7.0 + */ +public abstract class HttpSessionBase implements ConfigurableLifecycle, HasKeystore, HasTruststore { + protected final Logger log = LogUtil.getLogger(this); + + private @Getter ClassLoader configurationClassLoader = Thread.currentThread().getContextClassLoader(); + private @Getter @Setter String name; + private @Getter @Setter ApplicationContext applicationContext; + + /* CONNECTION POOL */ + private @Getter int timeout = 10000; + private @Getter int maxConnections = 10; + private @Getter int maxExecuteRetries = 1; + private @Getter boolean staleChecking=true; + private @Getter int staleTimeout = 5000; // [ms] + private @Getter int connectionTimeToLive = 900; // [s] + private @Getter int connectionIdleTimeout = 10; // [s] + private HttpClientBuilder httpClientBuilder = HttpClientBuilder.create(); + private HttpClientContext httpClientContext = HttpClientContext.create(); + private @Getter CloseableHttpClient httpClient; + + /* SECURITY */ + private @Getter String authAlias; + private @Getter String username; + private @Getter String password; + private @Getter String authDomain; + private @Getter String tokenEndpoint; + private @Getter int tokenExpiry=-1; + private @Getter String clientAuthAlias; + private @Getter String clientId; + private @Getter String clientSecret; + private @Getter String scope; + private @Getter boolean authenticatedTokenRequest; + + /* PROXY */ + private @Getter String proxyHost; + private @Getter int proxyPort=80; + private @Getter String proxyAuthAlias; + private @Getter String proxyUsername; + private @Getter String proxyPassword; + private @Getter String proxyRealm=null; + private @Getter boolean prefillProxyAuthCache; + + /* SSL */ + private @Getter String keystore; + private @Getter String keystoreAuthAlias; + private @Getter String keystorePassword; + private @Getter KeystoreType keystoreType=KeystoreType.PKCS12; + private @Getter String keystoreAlias; + private @Getter String keystoreAliasAuthAlias; + private @Getter String keystoreAliasPassword; + private @Getter String keyManagerAlgorithm=null; + + private @Getter String truststore=null; + private @Getter String truststoreAuthAlias; + private @Getter String truststorePassword=null; + private @Getter KeystoreType truststoreType=KeystoreType.JKS; + private @Getter String trustManagerAlgorithm=null; + private @Getter boolean allowSelfSignedCertificates = false; + private @Getter boolean verifyHostname=true; + private @Getter boolean ignoreCertificateExpiredException=false; + + private @Getter boolean followRedirects=true; + private @Getter boolean ignoreRedirects=false; + private @Getter String protocol=null; + private SSLConnectionSocketFactory sslSocketFactory; + + private boolean disableCookies = false; + + private CredentialFactory credentials; + private CredentialFactory user_cf; + private CredentialFactory client_cf; + + /** + * Makes sure only http(s) requests can be performed. + */ + protected URI getURI(String url) throws URISyntaxException { + URIBuilder uri = new URIBuilder(url); + + if(uri.getScheme() == null) { + throw new URISyntaxException("", "must use an absolute url starting with http(s)://"); + } + if (!uri.getScheme().matches("(?i)https?")) { + throw new IllegalArgumentException(ClassUtils.nameOf(this) + " only supports web based schemes. (http or https)"); + } + + if (uri.getPath()==null) { + uri.setPath("/"); + } + + log.info("created uri: scheme=["+uri.getScheme()+"] host=["+uri.getHost()+"] path=["+uri.getPath()+"]"); + return uri.build(); + } + + @Override + public void configure() throws ConfigurationException { + /** + * TODO find out if this really breaks proxy authentication or not. + */ +// httpClientBuilder.disableAuthCaching(); + + if (getMaxConnections() <= 0) { + throw new ConfigurationException("maxConnections is set to ["+getMaxConnections()+"], which is not enough for adequate operation"); + } + + AuthSSLContextFactory.verifyKeystoreConfiguration(this, this); + + if (StringUtils.isNotEmpty(getAuthAlias()) || StringUtils.isNotEmpty(getUsername())) { + user_cf = new CredentialFactory(getAuthAlias(), getUsername(), getPassword()); + credentials = user_cf; + } + client_cf = new CredentialFactory(getClientAuthAlias(), getClientId(), getClientSecret()); + if (credentials==null) { + credentials = client_cf; + } + if (StringUtils.isNotEmpty(getTokenEndpoint()) && StringUtils.isEmpty(getClientAuthAlias()) && StringUtils.isEmpty(getClientId())) { + throw new ConfigurationException("To obtain accessToken at tokenEndpoint ["+getTokenEndpoint()+"] a clientAuthAlias or ClientId and ClientSecret must be specified"); + } + + + RequestConfig.Builder requestConfigBuilder = RequestConfig.custom(); + requestConfigBuilder.setConnectTimeout(getTimeout()); + requestConfigBuilder.setConnectionRequestTimeout(getTimeout()); + requestConfigBuilder.setSocketTimeout(getTimeout()); + + HttpHost proxy = null; + CredentialFactory pcf = null; + if (StringUtils.isNotEmpty(getProxyHost())) { + proxy = new HttpHost(getProxyHost(), getProxyPort()); + pcf = new CredentialFactory(getProxyAuthAlias(), getProxyUsername(), getProxyPassword()); + requestConfigBuilder.setProxy(proxy); + httpClientBuilder.setProxy(proxy); + } + + try { + setupAuthentication(pcf, proxy, requestConfigBuilder); + } catch (HttpAuthenticationException e) { + throw new ConfigurationException("exception configuring authentication", e); + } + + httpClientBuilder.setDefaultRequestConfig(requestConfigBuilder.build()); + + httpClientBuilder.setRetryHandler(new HttpRequestRetryHandler(getMaxExecuteRetries())); + + if(areCookiesDisabled()) { + httpClientBuilder.disableCookieManagement(); + } + httpClientBuilder.evictIdleConnections((long) getConnectionIdleTimeout(), TimeUnit.SECONDS); + + sslSocketFactory = getSSLConnectionSocketFactory(); //Configure it here, so we can handle exceptions + + configureRedirectStrategy(); + } + + /** The redirect strategy used to only redirect GET, DELETE and HEAD. */ + private void configureRedirectStrategy() { + if(isFollowRedirects()) { + httpClientBuilder.setRedirectStrategy(new DefaultRedirectStrategy(new String[] { HttpGet.METHOD_NAME, HttpHead.METHOD_NAME, HttpDelete.METHOD_NAME })); + } else { + httpClientBuilder.disableRedirectHandling(); + } + } + + /** + * In order to support multiThreading and connectionPooling. + * The connectionManager has to be initialized with a sslSocketFactory. + * The pool must be re-created once closed. + */ + public void configureConnectionManager() { + int timeToLive = getConnectionTimeToLive(); + if (timeToLive<=0) { + timeToLive = -1; + } + + Registry socketFactoryRegistry = RegistryBuilder.create() + .register("http", PlainConnectionSocketFactory.getSocketFactory()) + .register("https", sslSocketFactory) + .build(); + + PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry, null, null, null, timeToLive, TimeUnit.SECONDS); + log.debug("created PoolingHttpClientConnectionManager with custom SSLConnectionSocketFactory"); + + connectionManager.setMaxTotal(getMaxConnections()); + connectionManager.setDefaultMaxPerRoute(getMaxConnections()); + + if (isStaleChecking()) { + log.info("set up connectionManager, setting stale checking ["+isStaleChecking()+"]"); + connectionManager.setValidateAfterInactivity(getStaleTimeout()); + } + + httpClientBuilder.setConnectionManager(connectionManager); + } + + @Override + public void start() { + buildHttpClient(); + } + + protected void buildHttpClient() { + configureConnectionManager(); + httpClient = httpClientBuilder.build(); + } + + protected void setHttpClient(CloseableHttpClient httpClient) { + this.httpClient = httpClient; + } + + @Override + public boolean isRunning() { + return getHttpClient() != null; + } + + @Override + public void stop() { + //Close the HttpClient and ConnectionManager to release resources and potential open connections + if(httpClient != null) { + try { + httpClient.close(); + } catch (IOException e) { + log.warn("unable to close HttpClient", e); + } + } + } + + private void setupAuthentication(CredentialFactory proxyCredentials, HttpHost proxy, RequestConfig.Builder requestConfigBuilder) throws HttpAuthenticationException { + CredentialsProvider credentialsProvider = new BasicCredentialsProvider(); + if (StringUtils.isNotEmpty(credentials.getUsername()) || StringUtils.isNotEmpty(getTokenEndpoint())) { + + credentialsProvider.setCredentials(new AuthScope(AuthScope.ANY_HOST, AuthScope.ANY_PORT), getCredentials()); + + AuthenticationScheme preferredAuthenticationScheme = getPreferredAuthenticationScheme(); + requestConfigBuilder.setTargetPreferredAuthSchemes(Arrays.asList(preferredAuthenticationScheme.getSchemeName())); + requestConfigBuilder.setAuthenticationEnabled(true); + + if (preferredAuthenticationScheme == AuthenticationScheme.OAUTH) { + OAuthAccessTokenManager accessTokenManager = new OAuthAccessTokenManager(getTokenEndpoint(), getScope(), client_cf, user_cf==null, isAuthenticatedTokenRequest(), this, getTokenExpiry()); + httpClientContext.setAttribute(OAuthAuthenticationScheme.ACCESSTOKEN_MANAGER_KEY, accessTokenManager); + httpClientBuilder.setTargetAuthenticationStrategy(new OAuthPreferringAuthenticationStrategy()); + } + } + if (proxy!=null) { + AuthScope authScope = new AuthScope(proxy, proxyRealm, AuthScope.ANY_SCHEME); + + + if (StringUtils.isNotEmpty(proxyCredentials.getUsername())) { + Credentials httpCredentials = new UsernamePasswordCredentials(proxyCredentials.getUsername(), proxyCredentials.getPassword()); + credentialsProvider.setCredentials(authScope, httpCredentials); + } + log.trace("setting credentialProvider [{}]", credentialsProvider); + + if(isPrefillProxyAuthCache()) { + requestConfigBuilder.setProxyPreferredAuthSchemes(Arrays.asList(AuthSchemes.BASIC)); + + AuthCache authCache = httpClientContext.getAuthCache(); + if(authCache == null) + authCache = new BasicAuthCache(); + + authCache.put(proxy, new BasicScheme()); + httpClientContext.setAuthCache(authCache); + } + + } + + httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider); + } + + protected void preAuthenticate() { + if (credentials != null && !StringUtils.isEmpty(credentials.getUsername())) { + AuthState authState = httpClientContext.getTargetAuthState(); + if (authState==null) { + authState = new AuthState(); + httpClientContext.setAttribute(HttpClientContext.TARGET_AUTH_STATE, authState); + } + authState.setState(AuthProtocolState.CHALLENGED); + authState.update(getPreferredAuthenticationScheme().createScheme(), getCredentials()); + } + } + + private Credentials getCredentials() { + String uname; + if (StringUtils.isNotEmpty(getAuthDomain())) { + uname = getAuthDomain() + "\\" + credentials.getUsername(); + } else { + uname = credentials.getUsername(); + } + + return new UsernamePasswordCredentials(uname, credentials.getPassword()); + } + + private AuthenticationScheme getPreferredAuthenticationScheme() { + return StringUtils.isNotEmpty(getTokenEndpoint()) ? AuthenticationScheme.OAUTH : AuthenticationScheme.BASIC; + } + + @Nonnull + protected SSLConnectionSocketFactory getSSLConnectionSocketFactory() throws ConfigurationException { + SSLConnectionSocketFactory sslSocketFactory; + HostnameVerifier hostnameVerifier = verifyHostname ? new DefaultHostnameVerifier() : new NoopHostnameVerifier(); + + try { + javax.net.ssl.SSLSocketFactory socketfactory = AuthSSLContextFactory.createSSLSocketFactory(this, this, getProtocol()); + sslSocketFactory = new SSLConnectionSocketFactory(socketfactory, hostnameVerifier); + } catch (Exception e) { + throw new ConfigurationException("cannot create or initialize SocketFactory", e); + } + + // This method will be overwritten by the connectionManager when connectionPooling is enabled! + // Can still be null when no default or an invalid system sslSocketFactory has been defined + httpClientBuilder.setSSLSocketFactory(sslSocketFactory); + + return sslSocketFactory; + } + + protected HttpResponse execute(URI targetUri, HttpRequestBase httpRequestBase) throws IOException { + HttpHost targetHost = new HttpHost(targetUri.getHost(), targetUri.getPort(), targetUri.getScheme()); + return getHttpClient().execute(targetHost, httpRequestBase, httpClientContext); + } + + /** + * Timeout in ms of obtaining a connection/result. 0 means no timeout + * @ff.default 10000 + */ + public void setTimeout(int i) { + timeout = i; + } + + /** + * The maximum number of concurrent connections + * @ff.default 10 + */ + public void setMaxConnections(int i) { + maxConnections = i; + } + + /** + * The maximum number of times the execution is retried + * @ff.default 1 (for repeatable messages) else 0 + */ + public void setMaxExecuteRetries(int i) { + maxExecuteRetries = i; + } + + /** Authentication alias used for authentication to the host */ + public void setAuthAlias(String string) { + authAlias = string; + } + + /** Username used for authentication to the host */ + public void setUsername(String username) { + this.username = username; + } + + /** + * Corporate domain name. Should only be used in combination with sAMAccountName, never with an UPN.
+ *
+ * Assuming the following user:
+ * UPN: john.doe@CorpDomain.biz
+ * sAMAccountName: CORPDOMAIN\john.doe
+ *
+ * The username attribute may be set to john.doe
+ * The AuthDomain attribute may be set to CORPDOMAIN
+ */ + @Deprecated + @ConfigurationWarning("Please use the UPN or the full sAM-AccountName instead") + public void setAuthDomain(String string) { + authDomain = string; + } + + /** Password used for authentication to the host */ + public void setPassword(String string) { + password = string; + } + + /** + * Endpoint to obtain OAuth accessToken. If authAlias or username( and password) are specified, + * then a PasswordGrant is used, otherwise a ClientCredentials grant. The obtained accessToken will be added to the regular requests + * in an HTTP Header 'Authorization' with a 'Bearer' prefix. + */ + public void setTokenEndpoint(String string) { + tokenEndpoint = string; + } + /** + * If set to a non-negative value, then determines the time (in seconds) after which the token will be refreshed. Otherwise the token + * will be refreshed when it is half way its lifetime as defined by the expires_in clause of the token response, + * or when the regular server returns a 401 status with a challenge. + * If not specified, and the accessTokens lifetime is not found in the token response, the accessToken will not be refreshed preemptively. + * @ff.default -1 + */ + public void setTokenExpiry(int value) { + tokenExpiry = value; + } + /** Alias used to obtain client_id and client_secret for authentication to tokenEndpoint */ + public void setClientAlias(String clientAuthAlias) { + this.clientAuthAlias = clientAuthAlias; + } + /** Client_id used in authentication to tokenEndpoint */ + public void setClientId(String clientId) { + this.clientId = clientId; + } + + /** Client_secret used in authentication to tokenEndpoint */ + public void setClientSecret(String clientSecret) { + this.clientSecret = clientSecret; + } + /** Space or comma separated list of scope items requested for accessToken, e.g. read write. Only used when tokenEndpoint is specified */ + public void setScope(String string) { + scope = string; + } + /** if set true, clientId and clientSecret will be added as Basic Authentication header to the tokenRequest, instead of as request parameters */ + public void setAuthenticatedTokenRequest(boolean authenticatedTokenRequest) { + this.authenticatedTokenRequest = authenticatedTokenRequest; + } + + + /** Proxy host */ + public void setProxyHost(String string) { + proxyHost = string; + } + + /** + * Proxy port + * @ff.default 80 + */ + public void setProxyPort(int i) { + proxyPort = i; + } + + /** Alias used to obtain credentials for authentication to proxy */ + public void setProxyAuthAlias(String string) { + proxyAuthAlias = string; + } + + /** + * Proxy username + * @ff.default + */ + public void setProxyUsername(String string) { + proxyUsername = string; + } + + /** + * Proxy password + * @ff.default + */ + public void setProxyPassword(String string) { + proxyPassword = string; + } + + /** + * Proxy realm + * @ff.default + */ + public void setProxyRealm(String string) { + proxyRealm = StringUtils.isNotEmpty(string) ? string : null; + } + + /** + * Create a pre-emptive login context for the proxy connection(s). + */ + public void setPrefillProxyAuthCache(boolean b) { + this.prefillProxyAuthCache = b; + } + + /** + * Disables the use of cookies, making the sender completely stateless + * @ff.default false + */ + public void setDisableCookies(boolean disableCookies) { + this.disableCookies = disableCookies; + } + public boolean areCookiesDisabled() { + return disableCookies; + } + + + /** resource URL to keystore or certificate to be used for authentication. If none specified, the JVMs default keystore will be used. */ + @Override + public void setKeystore(String string) { + keystore = string; + } + + @Override + public void setKeystoreType(KeystoreType value) { + keystoreType = value; + } + + @Override + public void setKeystoreAuthAlias(String string) { + keystoreAuthAlias = string; + } + + @Override + public void setKeystorePassword(String string) { + keystorePassword = string; + } + + @Override + public void setKeyManagerAlgorithm(String keyManagerAlgorithm) { + this.keyManagerAlgorithm = keyManagerAlgorithm; + } + + @Override + public void setKeystoreAlias(String string) { + keystoreAlias = string; + } + @Override + public void setKeystoreAliasAuthAlias(String string) { + keystoreAliasAuthAlias = string; + } + @Override + public void setKeystoreAliasPassword(String string) { + keystoreAliasPassword = string; + } + + @Override + /** Resource URL to truststore to be used for authenticating peer. If none specified, the JVMs default truststore will be used. */ + public void setTruststore(String string) { + truststore = string; + } + + @Override + public void setTruststoreAuthAlias(String string) { + truststoreAuthAlias = string; + } + + @Override + public void setTruststorePassword(String string) { + truststorePassword = string; + } + + @Override + public void setTruststoreType(KeystoreType value) { + truststoreType = value; + } + + @Override + public void setTrustManagerAlgorithm(String trustManagerAlgorithm) { + this.trustManagerAlgorithm = trustManagerAlgorithm; + } + + @Override + public void setVerifyHostname(boolean b) { + verifyHostname = b; + } + + @Override + public void setAllowSelfSignedCertificates(boolean allowSelfSignedCertificates) { + this.allowSelfSignedCertificates = allowSelfSignedCertificates; + } + + @Override + public void setIgnoreCertificateExpiredException(boolean b) { + ignoreCertificateExpiredException = b; + } + + /** + * If true, a redirect request will be honoured, e.g. to switch to HTTPS + * @ff.default true + */ + public void setFollowRedirects(boolean b) { + followRedirects = b; + } + + /** + * If true, besides http status code 200 (OK) also the code 301 (MOVED_PERMANENTLY), 302 (MOVED_TEMPORARILY) and 307 (TEMPORARY_REDIRECT) are considered successful + * @ff.default false + */ + public void setIgnoreRedirects(boolean b) { + ignoreRedirects = b; + } + + + /** + * Controls whether connections checked to be stale, i.e. appear open, but are not. + * @ff.default true + */ + public void setStaleChecking(boolean b) { + staleChecking = b; + } + + /** + * Used when StaleChecking=true. Timeout after which an idle connection will be validated before being used. + * @ff.default 5000 ms + */ + public void setStaleTimeout(int timeout) { + staleTimeout = timeout; + } + + /** + * Maximum Time to Live for connections in the pool. No connection will be re-used past its timeToLive value. + * @ff.default 900 s + */ + public void setConnectionTimeToLive(int timeToLive) { + connectionTimeToLive = timeToLive; + } + + /** + * Maximum Time for connection to stay idle in the pool. Connections that are idle longer will periodically be evicted from the pool + * @ff.default 10 s + */ + public void setConnectionIdleTimeout(int idleTimeout) { + connectionIdleTimeout = idleTimeout; + } + + /** + * Secure socket protocol (such as 'SSL' and 'TLS') to use when a SSLContext object is generated. + * @ff.default SSL + */ + public void setProtocol(String protocol) { + this.protocol = protocol; + } +} diff --git a/java/nl/nn/adapterframework/http/HttpSessionBase.java b/java/nl/nn/adapterframework/http/HttpSessionBase.java new file mode 100644 index 0000000..6feb797 --- /dev/null +++ b/java/nl/nn/adapterframework/http/HttpSessionBase.java @@ -0,0 +1,756 @@ +/* + Copyright 2017-2023 WeAreFrank! + + Licensed 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 nl.nn.adapterframework.http; + +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Arrays; +import java.util.concurrent.TimeUnit; + +import javax.annotation.Nonnull; +import javax.net.ssl.HostnameVerifier; + +import org.apache.commons.lang3.StringUtils; +import org.apache.http.HttpHost; +import org.apache.http.HttpResponse; +import org.apache.http.auth.AuthProtocolState; +import org.apache.http.auth.AuthScope; +import org.apache.http.auth.AuthState; +import org.apache.http.auth.Credentials; +import org.apache.http.auth.UsernamePasswordCredentials; +import org.apache.http.client.AuthCache; +import org.apache.http.client.CredentialsProvider; +import org.apache.http.client.config.AuthSchemes; +import org.apache.http.client.config.RequestConfig; +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.HttpRequestBase; +import org.apache.http.client.protocol.HttpClientContext; +import org.apache.http.client.utils.URIBuilder; +import org.apache.http.config.Registry; +import org.apache.http.config.RegistryBuilder; +import org.apache.http.conn.socket.ConnectionSocketFactory; +import org.apache.http.conn.socket.PlainConnectionSocketFactory; +import org.apache.http.conn.ssl.DefaultHostnameVerifier; +import org.apache.http.conn.ssl.NoopHostnameVerifier; +import org.apache.http.conn.ssl.SSLConnectionSocketFactory; +import org.apache.http.impl.auth.BasicScheme; +import org.apache.http.impl.client.BasicAuthCache; +import org.apache.http.impl.client.BasicCredentialsProvider; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.DefaultRedirectStrategy; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; +import org.apache.logging.log4j.Logger; +import org.springframework.context.ApplicationContext; + +import lombok.Getter; +import lombok.Setter; +import nl.nn.adapterframework.configuration.ConfigurationException; +import nl.nn.adapterframework.configuration.ConfigurationWarning; +import nl.nn.adapterframework.core.PipeLineSession; +import nl.nn.adapterframework.encryption.AuthSSLContextFactory; +import nl.nn.adapterframework.encryption.HasKeystore; +import nl.nn.adapterframework.encryption.HasTruststore; +import nl.nn.adapterframework.encryption.KeystoreType; +import nl.nn.adapterframework.http.authentication.AuthenticationScheme; +import nl.nn.adapterframework.http.authentication.HttpAuthenticationException; +import nl.nn.adapterframework.http.authentication.OAuthAccessTokenManager; +import nl.nn.adapterframework.http.authentication.OAuthAuthenticationScheme; +import nl.nn.adapterframework.http.authentication.OAuthPreferringAuthenticationStrategy; +import nl.nn.adapterframework.lifecycle.ConfigurableLifecycle; +import nl.nn.adapterframework.util.ClassUtils; +import nl.nn.adapterframework.util.CredentialFactory; +import nl.nn.adapterframework.util.LogUtil; + +/** + *

+ * Note 1: + * Some certificates require the <java_home>/jre/lib/security/xxx_policy.jar files to be upgraded to unlimited strength. Typically, in such a case, an error message like + * Error in loading the keystore: Private key decryption error: (java.lang.SecurityException: Unsupported keysize or algorithm parameters is observed. + * For IBM JDKs these files can be downloaded from http://www.ibm.com/developerworks/java/jdk/security/50/ (scroll down to 'IBM SDK Policy files') + *

+ * Replace in the directory java\jre\lib\security the following files: + *
    + *
  • local_policy.jar
  • + *
  • US_export_policy.jar
  • + *
+ *

+ * Note 2: + * To debug ssl-related problems, set the following system property: + *

    + *
  • IBM / WebSphere: -Djavax.net.debug=true
  • + *
  • SUN: -Djavax.net.debug=all
  • + *
+ *

+ *

+ * Note 3: + * In case javax.net.ssl.SSLHandshakeException: unknown certificate-exceptions are thrown, + * probably the certificate of the other party is not trusted. Try to use one of the certificates in the path as your truststore by doing the following: + *

    + *
  • open the URL you are trying to reach in InternetExplorer
  • + *
  • click on the yellow padlock on the right in the bottom-bar. This opens the certificate information window
  • + *
  • click on tab 'Certificeringspad'
  • + *
  • double click on root certificate in the tree displayed. This opens the certificate information window for the root certificate
  • + *
  • click on tab 'Details'
  • + *
  • click on 'Kopieren naar bestand'
  • + *
  • click 'next', choose 'DER Encoded Binary X.509 (.CER)'
  • + *
  • click 'next', choose a filename
  • + *
  • click 'next' and 'finish'
  • + *
  • Start IBM key management tool ikeyman.bat, located in Program Files/IBM/WebSphere Studio/Application Developer/v5.1.2/runtimes/base_v51/bin (or similar)
  • + *
  • create a new key-database (Sleuteldatabase -> Nieuw...), or open the default key.jks (default password="changeit")
  • + *
  • add the generated certificate (Toevoegen...)
  • + *
  • store the key-database in JKS format
  • + *
  • if you didn't use the standard keydatabase, then reference the file in the truststore-attribute in Configuration.xml (include the file as a resource)
  • + *
  • use jks for the truststoreType-attribute
  • + *
  • restart your application
  • + *
  • instead of IBM ikeyman you can use the standard java tool keytool as follows: + * keytool -import -alias yourAlias -file pathToSavedCertificate
  • + *
+ *

+ * Note 4: + * In case cannot create or initialize SocketFactory: (IOException) Unable to verify MAC-exceptions are thrown, + * please check password or authAlias configuration of the corresponding certificate. + *

+ * + * @author Niels Meijer + * @since 7.0 + */ +public abstract class HttpSessionBase implements ConfigurableLifecycle, HasKeystore, HasTruststore { + protected final Logger log = LogUtil.getLogger(this); + + private @Getter ClassLoader configurationClassLoader = Thread.currentThread().getContextClassLoader(); + private @Getter @Setter String name; + private @Getter @Setter ApplicationContext applicationContext; + + /* CONNECTION POOL */ + private @Getter int timeout = 10000; + private @Getter int maxConnections = 10; + private @Getter int maxExecuteRetries = 1; + private @Getter boolean staleChecking=true; + private @Getter int staleTimeout = 5000; // [ms] + private @Getter int connectionTimeToLive = 900; // [s] + private @Getter int connectionIdleTimeout = 10; // [s] + private HttpClientBuilder httpClientBuilder = HttpClientBuilder.create(); + private HttpClientContext httpClientContext = HttpClientContext.create(); + private @Getter CloseableHttpClient httpClient; + + /* SECURITY */ + private @Getter String authAlias; + private @Getter String username; + private @Getter String password; + private @Getter String authDomain; + private @Getter String tokenEndpoint; + private @Getter int tokenExpiry=-1; + private @Getter String clientAuthAlias; + private @Getter String clientId; + private @Getter String clientSecret; + private @Getter String scope; + private @Getter boolean authenticatedTokenRequest; + + /* PROXY */ + private @Getter String proxyHost; + private @Getter int proxyPort=80; + private @Getter String proxyAuthAlias; + private @Getter String proxyUsername; + private @Getter String proxyPassword; + private @Getter String proxyRealm=null; + private @Getter boolean prefillProxyAuthCache; + + /* SSL */ + private @Getter String keystore; + private @Getter String keystoreAuthAlias; + private @Getter String keystorePassword; + private @Getter KeystoreType keystoreType=KeystoreType.PKCS12; + private @Getter String keystoreAlias; + private @Getter String keystoreAliasAuthAlias; + private @Getter String keystoreAliasPassword; + private @Getter String keyManagerAlgorithm=null; + + private @Getter String truststore=null; + private @Getter String truststoreAuthAlias; + private @Getter String truststorePassword=null; + private @Getter KeystoreType truststoreType=KeystoreType.JKS; + private @Getter String trustManagerAlgorithm=null; + private @Getter boolean allowSelfSignedCertificates = false; + private @Getter boolean verifyHostname=true; + private @Getter boolean ignoreCertificateExpiredException=false; + + private @Getter boolean followRedirects=true; + private @Getter boolean ignoreRedirects=false; + private @Getter String protocol=null; + private SSLConnectionSocketFactory sslSocketFactory; + + private boolean disableCookies = false; + + private CredentialFactory credentials; + private CredentialFactory user_cf; + private CredentialFactory client_cf; + + /** + * Makes sure only http(s) requests can be performed. + */ + protected URI getURI(String url) throws URISyntaxException { + URIBuilder uri = new URIBuilder(url); + + if(uri.getScheme() == null) { + throw new URISyntaxException("", "must use an absolute url starting with http(s)://"); + } + if (!uri.getScheme().matches("(?i)https?")) { + throw new IllegalArgumentException(ClassUtils.nameOf(this) + " only supports web based schemes. (http or https)"); + } + + if (uri.getPath()==null) { + uri.setPath("/"); + } + + log.info("created uri: scheme=["+uri.getScheme()+"] host=["+uri.getHost()+"] path=["+uri.getPath()+"]"); + return uri.build(); + } + + @Override + public void configure() throws ConfigurationException { + /** + * TODO find out if this really breaks proxy authentication or not. + */ +// httpClientBuilder.disableAuthCaching(); + + if (getMaxConnections() <= 0) { + throw new ConfigurationException("maxConnections is set to ["+getMaxConnections()+"], which is not enough for adequate operation"); + } + + AuthSSLContextFactory.verifyKeystoreConfiguration(this, this); + + if (StringUtils.isNotEmpty(getAuthAlias()) || StringUtils.isNotEmpty(getUsername())) { + user_cf = new CredentialFactory(getAuthAlias(), getUsername(), getPassword()); + credentials = user_cf; + } + client_cf = new CredentialFactory(getClientAuthAlias(), getClientId(), getClientSecret()); + if (credentials==null) { + credentials = client_cf; + } + if (StringUtils.isNotEmpty(getTokenEndpoint()) && StringUtils.isEmpty(getClientAuthAlias()) && StringUtils.isEmpty(getClientId())) { + throw new ConfigurationException("To obtain accessToken at tokenEndpoint ["+getTokenEndpoint()+"] a clientAuthAlias or ClientId and ClientSecret must be specified"); + } + + + RequestConfig.Builder requestConfigBuilder = RequestConfig.custom(); + requestConfigBuilder.setConnectTimeout(getTimeout()); + requestConfigBuilder.setConnectionRequestTimeout(getTimeout()); + requestConfigBuilder.setSocketTimeout(getTimeout()); + + HttpHost proxy = null; + CredentialFactory pcf = null; + if (StringUtils.isNotEmpty(getProxyHost())) { + proxy = new HttpHost(getProxyHost(), getProxyPort()); + pcf = new CredentialFactory(getProxyAuthAlias(), getProxyUsername(), getProxyPassword()); + requestConfigBuilder.setProxy(proxy); + httpClientBuilder.setProxy(proxy); + } + + try { + setupAuthentication(pcf, proxy, requestConfigBuilder); + } catch (HttpAuthenticationException e) { + throw new ConfigurationException("exception configuring authentication", e); + } + + httpClientBuilder.setDefaultRequestConfig(requestConfigBuilder.build()); + + httpClientBuilder.setRetryHandler(new HttpRequestRetryHandler(getMaxExecuteRetries())); + + if(areCookiesDisabled()) { + httpClientBuilder.disableCookieManagement(); + } + httpClientBuilder.evictIdleConnections((long) getConnectionIdleTimeout(), TimeUnit.SECONDS); + + sslSocketFactory = getSSLConnectionSocketFactory(); //Configure it here, so we can handle exceptions + + configureRedirectStrategy(); + } + + /** The redirect strategy used to only redirect GET, DELETE and HEAD. */ + private void configureRedirectStrategy() { + if(isFollowRedirects()) { + httpClientBuilder.setRedirectStrategy(new DefaultRedirectStrategy(new String[] { HttpGet.METHOD_NAME, HttpHead.METHOD_NAME, HttpDelete.METHOD_NAME })); + } else { + httpClientBuilder.disableRedirectHandling(); + } + } + + /** + * In order to support multiThreading and connectionPooling. + * The connectionManager has to be initialized with a sslSocketFactory. + * The pool must be re-created once closed. + */ + public void configureConnectionManager() { + int timeToLive = getConnectionTimeToLive(); + if (timeToLive<=0) { + timeToLive = -1; + } + + Registry socketFactoryRegistry = RegistryBuilder.create() + .register("http", PlainConnectionSocketFactory.getSocketFactory()) + .register("https", sslSocketFactory) + .build(); + + PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry, null, null, null, timeToLive, TimeUnit.SECONDS); + log.debug("created PoolingHttpClientConnectionManager with custom SSLConnectionSocketFactory"); + + connectionManager.setMaxTotal(getMaxConnections()); + connectionManager.setDefaultMaxPerRoute(getMaxConnections()); + + if (isStaleChecking()) { + log.info("set up connectionManager, setting stale checking ["+isStaleChecking()+"]"); + connectionManager.setValidateAfterInactivity(getStaleTimeout()); + } + + httpClientBuilder.setConnectionManager(connectionManager); + } + + @Override + public void start() { + buildHttpClient(); + } + + protected void buildHttpClient() { + configureConnectionManager(); + httpClient = httpClientBuilder.build(); + } + + protected void setHttpClient(CloseableHttpClient httpClient) { + this.httpClient = httpClient; + } + + @Override + public boolean isRunning() { + return getHttpClient() != null; + } + + @Override + public void stop() { + //Close the HttpClient and ConnectionManager to release resources and potential open connections + if(httpClient != null) { + try { + httpClient.close(); + } catch (IOException e) { + log.warn("unable to close HttpClient", e); + } + } + } + + private void setupAuthentication(CredentialFactory proxyCredentials, HttpHost proxy, RequestConfig.Builder requestConfigBuilder) throws HttpAuthenticationException { + CredentialsProvider credentialsProvider = new BasicCredentialsProvider(); + if (StringUtils.isNotEmpty(credentials.getUsername()) || StringUtils.isNotEmpty(getTokenEndpoint())) { + + credentialsProvider.setCredentials(new AuthScope(AuthScope.ANY_HOST, AuthScope.ANY_PORT), getCredentials()); + + AuthenticationScheme preferredAuthenticationScheme = getPreferredAuthenticationScheme(); + requestConfigBuilder.setTargetPreferredAuthSchemes(Arrays.asList(preferredAuthenticationScheme.getSchemeName())); + requestConfigBuilder.setAuthenticationEnabled(true); + + if (preferredAuthenticationScheme == AuthenticationScheme.OAUTH) { + OAuthAccessTokenManager accessTokenManager = new OAuthAccessTokenManager(getTokenEndpoint(), getScope(), client_cf, user_cf==null, isAuthenticatedTokenRequest(), this, getTokenExpiry()); + httpClientContext.setAttribute(OAuthAuthenticationScheme.ACCESSTOKEN_MANAGER_KEY, accessTokenManager); + httpClientBuilder.setTargetAuthenticationStrategy(new OAuthPreferringAuthenticationStrategy()); + } + } + if (proxy!=null) { + AuthScope authScope = new AuthScope(proxy, proxyRealm, AuthScope.ANY_SCHEME); + + + if (StringUtils.isNotEmpty(proxyCredentials.getUsername())) { + Credentials httpCredentials = new UsernamePasswordCredentials(proxyCredentials.getUsername(), proxyCredentials.getPassword()); + credentialsProvider.setCredentials(authScope, httpCredentials); + } + log.trace("setting credentialProvider [{}]", credentialsProvider); + + if(isPrefillProxyAuthCache()) { + requestConfigBuilder.setProxyPreferredAuthSchemes(Arrays.asList(AuthSchemes.BASIC)); + + AuthCache authCache = httpClientContext.getAuthCache(); + if(authCache == null) + authCache = new BasicAuthCache(); + + authCache.put(proxy, new BasicScheme()); + httpClientContext.setAuthCache(authCache); + } + + } + + httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider); + } + + protected void preAuthenticate() { + if (credentials != null && !StringUtils.isEmpty(credentials.getUsername())) { + AuthState authState = httpClientContext.getTargetAuthState(); + if (authState==null) { + authState = new AuthState(); + httpClientContext.setAttribute(HttpClientContext.TARGET_AUTH_STATE, authState); + } + authState.setState(AuthProtocolState.CHALLENGED); + authState.update(getPreferredAuthenticationScheme().createScheme(), getCredentials()); + } + } + + private Credentials getCredentials() { + String uname; + if (StringUtils.isNotEmpty(getAuthDomain())) { + uname = getAuthDomain() + "\\" + credentials.getUsername(); + } else { + uname = credentials.getUsername(); + } + + return new UsernamePasswordCredentials(uname, credentials.getPassword()); + } + + private AuthenticationScheme getPreferredAuthenticationScheme() { + return StringUtils.isNotEmpty(getTokenEndpoint()) ? AuthenticationScheme.OAUTH : AuthenticationScheme.BASIC; + } + + @Nonnull + protected SSLConnectionSocketFactory getSSLConnectionSocketFactory() throws ConfigurationException { + SSLConnectionSocketFactory sslSocketFactory; + HostnameVerifier hostnameVerifier = verifyHostname ? new DefaultHostnameVerifier() : new NoopHostnameVerifier(); + + try { + javax.net.ssl.SSLSocketFactory socketfactory = AuthSSLContextFactory.createSSLSocketFactory(this, this, getProtocol()); + sslSocketFactory = new SSLConnectionSocketFactory(socketfactory, hostnameVerifier); + } catch (Exception e) { + throw new ConfigurationException("cannot create or initialize SocketFactory", e); + } + + // This method will be overwritten by the connectionManager when connectionPooling is enabled! + // Can still be null when no default or an invalid system sslSocketFactory has been defined + httpClientBuilder.setSSLSocketFactory(sslSocketFactory); + + return sslSocketFactory; + } + + protected HttpResponse execute(URI targetUri, HttpRequestBase httpRequestBase, PipeLineSession session) throws IOException { + // Might be nice to implement storeHttpClientInSessionKey and getHttpClientFromSessionKey + CloseableHttpClient httpClient = null; + Object httpClientObject = session.get("httpClient"); + if (httpClientObject != null) { + if (httpClientObject instanceof CloseableHttpClient) { + httpClient = (CloseableHttpClient)httpClientObject; + } else { + log.warn("httpClient is not an instance of CloseableHttpClient"); + } + } + if (httpClient == null) { + httpClient = getHttpClient(); + session.put("httpClient", httpClient); + } + HttpHost targetHost = new HttpHost(targetUri.getHost(), targetUri.getPort(), targetUri.getScheme()); + return httpClient.execute(targetHost, httpRequestBase, httpClientContext); + } + + /** + * Timeout in ms of obtaining a connection/result. 0 means no timeout + * @ff.default 10000 + */ + public void setTimeout(int i) { + timeout = i; + } + + /** + * The maximum number of concurrent connections + * @ff.default 10 + */ + public void setMaxConnections(int i) { + maxConnections = i; + } + + /** + * The maximum number of times the execution is retried + * @ff.default 1 (for repeatable messages) else 0 + */ + public void setMaxExecuteRetries(int i) { + maxExecuteRetries = i; + } + + /** Authentication alias used for authentication to the host */ + public void setAuthAlias(String string) { + authAlias = string; + } + + /** Username used for authentication to the host */ + public void setUsername(String username) { + this.username = username; + } + + /** + * Corporate domain name. Should only be used in combination with sAMAccountName, never with an UPN.
+ *
+ * Assuming the following user:
+ * UPN: john.doe@CorpDomain.biz
+ * sAMAccountName: CORPDOMAIN\john.doe
+ *
+ * The username attribute may be set to john.doe
+ * The AuthDomain attribute may be set to CORPDOMAIN
+ */ + @Deprecated + @ConfigurationWarning("Please use the UPN or the full sAM-AccountName instead") + public void setAuthDomain(String string) { + authDomain = string; + } + + /** Password used for authentication to the host */ + public void setPassword(String string) { + password = string; + } + + /** + * Endpoint to obtain OAuth accessToken. If authAlias or username( and password) are specified, + * then a PasswordGrant is used, otherwise a ClientCredentials grant. The obtained accessToken will be added to the regular requests + * in an HTTP Header 'Authorization' with a 'Bearer' prefix. + */ + public void setTokenEndpoint(String string) { + tokenEndpoint = string; + } + /** + * If set to a non-negative value, then determines the time (in seconds) after which the token will be refreshed. Otherwise the token + * will be refreshed when it is half way its lifetime as defined by the expires_in clause of the token response, + * or when the regular server returns a 401 status with a challenge. + * If not specified, and the accessTokens lifetime is not found in the token response, the accessToken will not be refreshed preemptively. + * @ff.default -1 + */ + public void setTokenExpiry(int value) { + tokenExpiry = value; + } + /** Alias used to obtain client_id and client_secret for authentication to tokenEndpoint */ + public void setClientAlias(String clientAuthAlias) { + this.clientAuthAlias = clientAuthAlias; + } + /** Client_id used in authentication to tokenEndpoint */ + public void setClientId(String clientId) { + this.clientId = clientId; + } + + /** Client_secret used in authentication to tokenEndpoint */ + public void setClientSecret(String clientSecret) { + this.clientSecret = clientSecret; + } + /** Space or comma separated list of scope items requested for accessToken, e.g. read write. Only used when tokenEndpoint is specified */ + public void setScope(String string) { + scope = string; + } + /** if set true, clientId and clientSecret will be added as Basic Authentication header to the tokenRequest, instead of as request parameters */ + public void setAuthenticatedTokenRequest(boolean authenticatedTokenRequest) { + this.authenticatedTokenRequest = authenticatedTokenRequest; + } + + + /** Proxy host */ + public void setProxyHost(String string) { + proxyHost = string; + } + + /** + * Proxy port + * @ff.default 80 + */ + public void setProxyPort(int i) { + proxyPort = i; + } + + /** Alias used to obtain credentials for authentication to proxy */ + public void setProxyAuthAlias(String string) { + proxyAuthAlias = string; + } + + /** + * Proxy username + * @ff.default + */ + public void setProxyUsername(String string) { + proxyUsername = string; + } + + /** + * Proxy password + * @ff.default + */ + public void setProxyPassword(String string) { + proxyPassword = string; + } + + /** + * Proxy realm + * @ff.default + */ + public void setProxyRealm(String string) { + proxyRealm = StringUtils.isNotEmpty(string) ? string : null; + } + + /** + * Create a pre-emptive login context for the proxy connection(s). + */ + public void setPrefillProxyAuthCache(boolean b) { + this.prefillProxyAuthCache = b; + } + + /** + * Disables the use of cookies, making the sender completely stateless + * @ff.default false + */ + public void setDisableCookies(boolean disableCookies) { + this.disableCookies = disableCookies; + } + public boolean areCookiesDisabled() { + return disableCookies; + } + + + /** resource URL to keystore or certificate to be used for authentication. If none specified, the JVMs default keystore will be used. */ + @Override + public void setKeystore(String string) { + keystore = string; + } + + @Override + public void setKeystoreType(KeystoreType value) { + keystoreType = value; + } + + @Override + public void setKeystoreAuthAlias(String string) { + keystoreAuthAlias = string; + } + + @Override + public void setKeystorePassword(String string) { + keystorePassword = string; + } + + @Override + public void setKeyManagerAlgorithm(String keyManagerAlgorithm) { + this.keyManagerAlgorithm = keyManagerAlgorithm; + } + + @Override + public void setKeystoreAlias(String string) { + keystoreAlias = string; + } + @Override + public void setKeystoreAliasAuthAlias(String string) { + keystoreAliasAuthAlias = string; + } + @Override + public void setKeystoreAliasPassword(String string) { + keystoreAliasPassword = string; + } + + @Override + /** Resource URL to truststore to be used for authenticating peer. If none specified, the JVMs default truststore will be used. */ + public void setTruststore(String string) { + truststore = string; + } + + @Override + public void setTruststoreAuthAlias(String string) { + truststoreAuthAlias = string; + } + + @Override + public void setTruststorePassword(String string) { + truststorePassword = string; + } + + @Override + public void setTruststoreType(KeystoreType value) { + truststoreType = value; + } + + @Override + public void setTrustManagerAlgorithm(String trustManagerAlgorithm) { + this.trustManagerAlgorithm = trustManagerAlgorithm; + } + + @Override + public void setVerifyHostname(boolean b) { + verifyHostname = b; + } + + @Override + public void setAllowSelfSignedCertificates(boolean allowSelfSignedCertificates) { + this.allowSelfSignedCertificates = allowSelfSignedCertificates; + } + + @Override + public void setIgnoreCertificateExpiredException(boolean b) { + ignoreCertificateExpiredException = b; + } + + /** + * If true, a redirect request will be honoured, e.g. to switch to HTTPS + * @ff.default true + */ + public void setFollowRedirects(boolean b) { + followRedirects = b; + } + + /** + * If true, besides http status code 200 (OK) also the code 301 (MOVED_PERMANENTLY), 302 (MOVED_TEMPORARILY) and 307 (TEMPORARY_REDIRECT) are considered successful + * @ff.default false + */ + public void setIgnoreRedirects(boolean b) { + ignoreRedirects = b; + } + + + /** + * Controls whether connections checked to be stale, i.e. appear open, but are not. + * @ff.default true + */ + public void setStaleChecking(boolean b) { + staleChecking = b; + } + + /** + * Used when StaleChecking=true. Timeout after which an idle connection will be validated before being used. + * @ff.default 5000 ms + */ + public void setStaleTimeout(int timeout) { + staleTimeout = timeout; + } + + /** + * Maximum Time to Live for connections in the pool. No connection will be re-used past its timeToLive value. + * @ff.default 900 s + */ + public void setConnectionTimeToLive(int timeToLive) { + connectionTimeToLive = timeToLive; + } + + /** + * Maximum Time for connection to stay idle in the pool. Connections that are idle longer will periodically be evicted from the pool + * @ff.default 10 s + */ + public void setConnectionIdleTimeout(int idleTimeout) { + connectionIdleTimeout = idleTimeout; + } + + /** + * Secure socket protocol (such as 'SSL' and 'TLS') to use when a SSLContext object is generated. + * @ff.default SSL + */ + public void setProtocol(String protocol) { + this.protocol = protocol; + } +}