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:
- *
- * - 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 @@
*/
//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;
+ }
+}