From b5aeeac822bfe1a734835e3aa0caa65b56b3643a Mon Sep 17 00:00:00 2001 From: Thomas Lenz Date: Mon, 11 May 2020 19:19:25 +0200 Subject: update HttpClientFactory to facilitate request retrying in case of an error --- .../impl/http/EaafHttpRequestRetryHandler.java | 33 ++++ .../core/impl/http/HttpClientConfiguration.java | 8 +- .../eaaf/core/impl/http/HttpClientFactory.java | 44 ++++- .../eaaf/core/impl/http/IHttpClientFactory.java | 4 +- .../eaaf/core/test/http/HttpClientFactoryTest.java | 206 +++++++++++++++++++++ .../src/test/resources/data/config1.properties | 6 +- 6 files changed, 292 insertions(+), 9 deletions(-) create mode 100644 eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/http/EaafHttpRequestRetryHandler.java diff --git a/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/http/EaafHttpRequestRetryHandler.java b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/http/EaafHttpRequestRetryHandler.java new file mode 100644 index 00000000..3aa908e8 --- /dev/null +++ b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/http/EaafHttpRequestRetryHandler.java @@ -0,0 +1,33 @@ +package at.gv.egiz.eaaf.core.impl.http; + +import java.net.UnknownHostException; +import java.util.Arrays; + +import javax.net.ssl.SSLException; + +import org.apache.http.client.HttpRequestRetryHandler; +import org.apache.http.impl.client.DefaultHttpRequestRetryHandler; + +public class EaafHttpRequestRetryHandler extends DefaultHttpRequestRetryHandler implements + HttpRequestRetryHandler { + + /** + * Create the request retry handler using the following list of non-retriable. + * IOException classes:
+ * + * + * @param retryCount how many times to retry; 0 means no retries + * @param requestSentRetryEnabled true if it's OK to retry non-idempotent + * requests that have been sent + */ + public EaafHttpRequestRetryHandler(final int retryCount, final boolean requestSentRetryEnabled) { + super(retryCount, requestSentRetryEnabled, Arrays.asList( + UnknownHostException.class, + SSLException.class)); + + } + +} diff --git a/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/http/HttpClientConfiguration.java b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/http/HttpClientConfiguration.java index 6a66dfff..ec7d115a 100644 --- a/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/http/HttpClientConfiguration.java +++ b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/http/HttpClientConfiguration.java @@ -56,7 +56,13 @@ public class HttpClientConfiguration { @Setter private boolean followHttpRedirects = true; - + + @Setter + private int httpErrorRetryCount = 3; + + @Setter + private boolean httpErrorRetryPost = false; + /** * Get a new HTTP-client configuration object. * diff --git a/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/http/HttpClientFactory.java b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/http/HttpClientFactory.java index 4e811eaa..b53226ce 100644 --- a/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/http/HttpClientFactory.java +++ b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/http/HttpClientFactory.java @@ -65,7 +65,11 @@ public class HttpClientFactory implements IHttpClientFactory { public static final String PROP_CONFIG_CLIENT_HTTP_CONNECTION_TIMEOUT_CONNECTION = "client.http.connection.timeout.connection"; public static final String PROP_CONFIG_CLIENT_HTTP_CONNECTION_TIMEOUT_REQUEST = - "client.http.connection.timeout.request"; + "client.http.connection.timeout.request"; + public static final String PROP_CONFIG_CLIENT_HTTP_CONNECTION_RETRY_COUNT = + "client.http.connection.retry.count"; + public static final String PROP_CONFIG_CLIENT_HTTP_CONNECTION_RETRY_POST = + "client.http.connection.retry.post"; public static final String PROP_CONFIG_CLIENT_HTTP_SSL_HOSTNAMEVERIFIER_TRUSTALL = "client.http.ssl.hostnameverifier.trustall"; @@ -91,7 +95,9 @@ public class HttpClientFactory implements IHttpClientFactory { public static final String DEFAULT_CONFIG_CLIENT_HTTP_CONNECTION_TIMEOUT_REQUEST = "30"; public static final String DEFAULT_CONFIG_CLIENT_HTTP_CONNECTION_POOL_MAXTOTAL = "500"; public static final String DEFAULT_CONFIG_CLIENT_HTTP_CONNECTION_POOL_MAXPERROUTE = "100"; - + public static final String DEFAULT_CONFIG_CLIENT_HTTP_CONNECTION_RETRY_COUNT = "3"; + public static final String DEFAUTL_CONFIG_CLIENT_HTTP_CONNECTION_RETRY_POST = String.valueOf(false); + private String defaultConfigurationId = null; private final Map availableBuilders = new HashMap<>(); @@ -127,8 +133,11 @@ public class HttpClientFactory implements IHttpClientFactory { config.validate(); builder = HttpClients.custom(); + + //inject request configuration builder.setDefaultRequestConfig(buildDefaultRequestConfig()); - + injectInternalRetryHandler(builder, config); + //inject basic authentication infos injectBasicAuthenticationIfRequired(builder, config); @@ -147,6 +156,22 @@ public class HttpClientFactory implements IHttpClientFactory { } + private void injectInternalRetryHandler(HttpClientBuilder builder, HttpClientConfiguration config) { + if (config.getHttpErrorRetryCount() > 0) { + log.info("Set HTTP error-retry to {} for http-client: {}", + config.getHttpErrorRetryCount(), config.getFriendlyName()); + builder.setRetryHandler(new EaafHttpRequestRetryHandler( + config.getHttpErrorRetryCount(), + config.isHttpErrorRetryPost())); + + } else { + log.info("Disable HTTP error-retry for http-client: {}", config.getFriendlyName()); + builder.disableAutomaticRetries(); + + } + + } + @PostConstruct private void initalize() throws EaafException { final HttpClientConfiguration defaultHttpClientConfig = buildDefaultHttpClientConfiguration(); @@ -157,7 +182,8 @@ public class HttpClientFactory implements IHttpClientFactory { // set default request configuration defaultHttpClientBuilder.setDefaultRequestConfig(buildDefaultRequestConfig()); - + injectInternalRetryHandler(defaultHttpClientBuilder, defaultHttpClientConfig); + //inject http basic authentication injectBasicAuthenticationIfRequired(defaultHttpClientBuilder, defaultHttpClientConfig); @@ -205,6 +231,14 @@ public class HttpClientFactory implements IHttpClientFactory { config.setDisableHostnameValidation(basicConfig.getBasicConfigurationBoolean( PROP_CONFIG_CLIENT_HTTP_SSL_HOSTNAMEVERIFIER_TRUSTALL, false)); + config.setHttpErrorRetryCount(Integer.parseInt(basicConfig.getBasicConfiguration( + PROP_CONFIG_CLIENT_HTTP_CONNECTION_RETRY_COUNT, + DEFAULT_CONFIG_CLIENT_HTTP_CONNECTION_RETRY_COUNT))); + config.setHttpErrorRetryPost(Boolean.parseBoolean(basicConfig.getBasicConfiguration( + PROP_CONFIG_CLIENT_HTTP_CONNECTION_RETRY_POST, + DEFAUTL_CONFIG_CLIENT_HTTP_CONNECTION_RETRY_POST))); + + // validate configuration object config.validate(); @@ -324,7 +358,7 @@ public class HttpClientFactory implements IHttpClientFactory { .setSocketTimeout(Integer.parseInt( basicConfig.getBasicConfiguration(PROP_CONFIG_CLIENT_HTTP_CONNECTION_TIMEOUT_SOCKET, DEFAULT_CONFIG_CLIENT_HTTP_CONNECTION_TIMEOUT_SOCKET)) - * 1000) + * 1000) .build(); return requestConfig; diff --git a/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/http/IHttpClientFactory.java b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/http/IHttpClientFactory.java index 7ec58d46..4e8374e1 100644 --- a/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/http/IHttpClientFactory.java +++ b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/http/IHttpClientFactory.java @@ -2,10 +2,10 @@ package at.gv.egiz.eaaf.core.impl.http; import javax.annotation.Nonnull; -import at.gv.egiz.eaaf.core.exceptions.EaafException; - import org.apache.http.impl.client.CloseableHttpClient; +import at.gv.egiz.eaaf.core.exceptions.EaafException; + public interface IHttpClientFactory { /** diff --git a/eaaf_core_utils/src/test/java/at/gv/egiz/eaaf/core/test/http/HttpClientFactoryTest.java b/eaaf_core_utils/src/test/java/at/gv/egiz/eaaf/core/test/http/HttpClientFactoryTest.java index 72ec7008..63ac38a3 100644 --- a/eaaf_core_utils/src/test/java/at/gv/egiz/eaaf/core/test/http/HttpClientFactoryTest.java +++ b/eaaf_core_utils/src/test/java/at/gv/egiz/eaaf/core/test/http/HttpClientFactoryTest.java @@ -3,6 +3,7 @@ package at.gv.egiz.eaaf.core.test.http; import java.io.IOException; import java.net.HttpURLConnection; import java.net.InetAddress; +import java.net.SocketTimeoutException; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.Provider; @@ -12,6 +13,7 @@ import org.apache.commons.lang3.RandomStringUtils; import org.apache.http.client.ClientProtocolException; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.impl.client.CloseableHttpClient; import org.junit.After; @@ -32,6 +34,7 @@ import okhttp3.HttpUrl; import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.MockWebServer; import okhttp3.mockwebserver.RecordedRequest; +import okhttp3.mockwebserver.SocketPolicy; import okhttp3.tls.HandshakeCertificates; import okhttp3.tls.HeldCertificate; @@ -83,6 +86,27 @@ public class HttpClientFactoryTest { } + @Test + public void defaultHttpClientRetryOneTime() throws EaafException, InterruptedException, + ClientProtocolException, IOException { + CloseableHttpClient client = httpClientFactory.getHttpClient(); + Assert.assertNotNull("No httpClient", client); + + mockWebServer = new MockWebServer(); + mockServerUrl = mockWebServer.url("/sp/junit"); + mockWebServer.enqueue(new MockResponse() + .setSocketPolicy(SocketPolicy.NO_RESPONSE) + .setResponseCode(HttpURLConnection.HTTP_NO_CONTENT)); + mockWebServer.enqueue(new MockResponse().setResponseCode(200) + .setBody("GetData")); + + //request webservice + final HttpUriRequest httpGet1 = new HttpGet(mockServerUrl.url().toString()); + final CloseableHttpResponse httpResp1 = client.execute(httpGet1); + Assert.assertEquals("http statusCode", 200, httpResp1.getStatusLine().getStatusCode()); + + } + @Test public void getCustomClientsDefault() throws EaafException { final HttpClientConfiguration config = new HttpClientConfiguration("jUnit"); @@ -156,6 +180,187 @@ public class HttpClientFactoryTest { } } + @Test + public void httpPostRetryNotAllowed() throws EaafException, InterruptedException, + ClientProtocolException, IOException { + final HttpClientConfiguration config = + new HttpClientConfiguration("jUnit_retry_" + RandomStringUtils.randomAlphabetic(3)); + config.setHttpErrorRetryCount(2); + config.setHttpErrorRetryPost(false); + + CloseableHttpClient client = httpClientFactory.getHttpClient(config); + Assert.assertNotNull("No httpClient", client); + + + mockWebServer = new MockWebServer(); + mockServerUrl = mockWebServer.url("/sp/junit"); + mockWebServer.enqueue(new MockResponse() + .setSocketPolicy(SocketPolicy.NO_RESPONSE) + .setResponseCode(HttpURLConnection.HTTP_NO_CONTENT)); + mockWebServer.enqueue(new MockResponse().setResponseCode(200) + .setBody("GetData")); + + //request webservice + final HttpUriRequest httpGet1 = new HttpPost(mockServerUrl.url().toString()); + try { + client.execute(httpGet1); + Assert.fail("HTTP POST retry not allowed"); + + } catch (SocketTimeoutException e) { + Assert.assertNotNull("No errorMsg", e.getMessage()); + + } + + } + + @Test + public void httpPostRetryOneTime() throws EaafException, InterruptedException, + ClientProtocolException, IOException { + final HttpClientConfiguration config = + new HttpClientConfiguration("jUnit_retry_" + RandomStringUtils.randomAlphabetic(3)); + config.setHttpErrorRetryCount(2); + config.setHttpErrorRetryPost(true); + + CloseableHttpClient client = httpClientFactory.getHttpClient(config); + Assert.assertNotNull("No httpClient", client); + + + mockWebServer = new MockWebServer(); + mockServerUrl = mockWebServer.url("/sp/junit"); + mockWebServer.enqueue(new MockResponse() + .setSocketPolicy(SocketPolicy.NO_RESPONSE) + .setResponseCode(HttpURLConnection.HTTP_NO_CONTENT)); + mockWebServer.enqueue(new MockResponse().setResponseCode(200) + .setBody("GetData")); + + //request webservice + final HttpUriRequest httpGet1 = new HttpPost(mockServerUrl.url().toString()); + final CloseableHttpResponse httpResp1 = client.execute(httpGet1); + Assert.assertEquals("http statusCode", 200, httpResp1.getStatusLine().getStatusCode()); + + } + + @Test + public void testHttpClientRetryOneTime() throws EaafException, InterruptedException, + ClientProtocolException, IOException { + final HttpClientConfiguration config = + new HttpClientConfiguration("jUnit_retry_" + RandomStringUtils.randomAlphabetic(3)); + config.setHttpErrorRetryCount(2); + + CloseableHttpClient client = httpClientFactory.getHttpClient(config); + Assert.assertNotNull("No httpClient", client); + + + mockWebServer = new MockWebServer(); + mockServerUrl = mockWebServer.url("/sp/junit"); + mockWebServer.enqueue(new MockResponse() + .setSocketPolicy(SocketPolicy.NO_RESPONSE) + .setResponseCode(HttpURLConnection.HTTP_NO_CONTENT)); + mockWebServer.enqueue(new MockResponse().setResponseCode(200) + .setBody("GetData")); + + //request webservice + final HttpUriRequest httpGet1 = new HttpGet(mockServerUrl.url().toString()); + final CloseableHttpResponse httpResp1 = client.execute(httpGet1); + Assert.assertEquals("http statusCode", 200, httpResp1.getStatusLine().getStatusCode()); + + } + + @Test + public void testHttpClientRetryTwoTime() throws EaafException, InterruptedException, + ClientProtocolException, IOException { + final HttpClientConfiguration config = + new HttpClientConfiguration("jUnit_retry_" + RandomStringUtils.randomAlphabetic(3)); + config.setHttpErrorRetryCount(2); + + CloseableHttpClient client = httpClientFactory.getHttpClient(config); + Assert.assertNotNull("No httpClient", client); + + + mockWebServer = new MockWebServer(); + mockServerUrl = mockWebServer.url("/sp/junit"); + mockWebServer.enqueue(new MockResponse() + .setSocketPolicy(SocketPolicy.NO_RESPONSE) + .setResponseCode(HttpURLConnection.HTTP_NO_CONTENT)); + mockWebServer.enqueue(new MockResponse() + .setSocketPolicy(SocketPolicy.NO_RESPONSE) + .setResponseCode(HttpURLConnection.HTTP_NO_CONTENT)); + mockWebServer.enqueue(new MockResponse().setResponseCode(200) + .setBody("GetData")); + + //request webservice + final HttpUriRequest httpGet1 = new HttpGet(mockServerUrl.url().toString()); + final CloseableHttpResponse httpResp1 = client.execute(httpGet1); + Assert.assertEquals("http statusCode", 200, httpResp1.getStatusLine().getStatusCode()); + + } + + @Test + public void testHttpClientRetryMaxReached() throws EaafException, InterruptedException, + ClientProtocolException, IOException { + final HttpClientConfiguration config = + new HttpClientConfiguration("jUnit_retry_" + RandomStringUtils.randomAlphabetic(3)); + config.setHttpErrorRetryCount(2); + + CloseableHttpClient client = httpClientFactory.getHttpClient(config); + Assert.assertNotNull("No httpClient", client); + + mockWebServer = new MockWebServer(); + mockServerUrl = mockWebServer.url("/sp/junit"); + mockWebServer.enqueue(new MockResponse() + .setSocketPolicy(SocketPolicy.NO_RESPONSE) + .setResponseCode(HttpURLConnection.HTTP_NO_CONTENT)); + mockWebServer.enqueue(new MockResponse() + .setSocketPolicy(SocketPolicy.NO_RESPONSE) + .setResponseCode(HttpURLConnection.HTTP_NO_CONTENT)); + mockWebServer.enqueue(new MockResponse() + .setSocketPolicy(SocketPolicy.NO_RESPONSE) + .setResponseCode(HttpURLConnection.HTTP_NO_CONTENT)); + mockWebServer.enqueue(new MockResponse().setResponseCode(200) + .setBody("GetData")); + + //request webservice + final HttpUriRequest httpGet1 = new HttpGet(mockServerUrl.url().toString()); + try { + client.execute(httpGet1); + Assert.fail("Max retry failed"); + + } catch (SocketTimeoutException e) { + Assert.assertNotNull("No errorMsg", e.getMessage()); + + } + } + + @Test + public void testHttpClientNoRetry() throws EaafException, InterruptedException, + ClientProtocolException, IOException { + final HttpClientConfiguration config = + new HttpClientConfiguration("jUnit_retry_" + RandomStringUtils.randomAlphabetic(3)); + config.setHttpErrorRetryCount(0); + + CloseableHttpClient client = httpClientFactory.getHttpClient(config); + Assert.assertNotNull("No httpClient", client); + + mockWebServer = new MockWebServer(); + mockServerUrl = mockWebServer.url("/sp/junit"); + mockWebServer.enqueue(new MockResponse() + .setSocketPolicy(SocketPolicy.NO_RESPONSE) + .setResponseCode(HttpURLConnection.HTTP_NO_CONTENT)); + mockWebServer.enqueue(new MockResponse().setResponseCode(200) + .setBody("GetData")); + + //request webservice + final HttpUriRequest httpGet1 = new HttpGet(mockServerUrl.url().toString()); + try { + client.execute(httpGet1); + Assert.fail("Max retry failed"); + + } catch (SocketTimeoutException e) { + Assert.assertNotNull("No errorMsg", e.getMessage()); + + } + } + @Test public void getCustomClientBasicAuthNoPassword() throws EaafException { final HttpClientConfiguration config = new HttpClientConfiguration("jUnit"); @@ -370,4 +575,5 @@ public class HttpClientFactoryTest { Assert.assertEquals("http statusCode", 200, httpResp2.getStatusLine().getStatusCode()); } + } diff --git a/eaaf_core_utils/src/test/resources/data/config1.properties b/eaaf_core_utils/src/test/resources/data/config1.properties index 25bd201f..93729b15 100644 --- a/eaaf_core_utils/src/test/resources/data/config1.properties +++ b/eaaf_core_utils/src/test/resources/data/config1.properties @@ -2,4 +2,8 @@ security.hsmfacade.host=eid.a-sit.at security.hsmfacade.port=9050 security.hsmfacade.trustedsslcert=src/test/resources/data/hsm_facade_trust_root.crt security.hsmfacade.username=authhandler-junit -security.hsmfacade.password=supersecret123 \ No newline at end of file +security.hsmfacade.password=supersecret123 + +client.http.connection.timeout.socket=1 +client.http.connection.timeout.connection=1 +client.http.connection.timeout.request=1 \ No newline at end of file -- cgit v1.2.3