You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by du...@apache.org on 2020/02/13 15:00:33 UTC

[sling-org-apache-sling-testing-clients] 01/01: SLING-9053 Implement retries mechanism for http 5XX response codes

This is an automated email from the ASF dual-hosted git repository.

dulvac pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-testing-clients.git

commit 3d658ce650da482056d61d131cc48d4890cbc94b
Author: Andrei Dulvac <ad...@adobe.com>
AuthorDate: Thu Feb 13 16:00:07 2020 +0100

    SLING-9053 Implement retries mechanism for http 5XX response codes
---
 .../apache/sling/testing/clients/Constants.java    |  24 ++--
 .../apache/sling/testing/clients/SlingClient.java  |  13 +-
 .../interceptors/FormBasedAuthInterceptor.java     |   6 +-
 .../LoggedServiceUnavailableRetryStrategy.java     |  56 ---------
 .../clients/util/ServerErrorRetryStrategy.java     |  84 +++++++++++++
 .../SlingClientRetryServiceUnavailableTest.java    |  90 --------------
 .../clients/SlingClientRetryStrategyTest.java      | 132 +++++++++++++++++++++
 7 files changed, 242 insertions(+), 163 deletions(-)

diff --git a/src/main/java/org/apache/sling/testing/clients/Constants.java b/src/main/java/org/apache/sling/testing/clients/Constants.java
index 18eafc1..43a2cf6 100644
--- a/src/main/java/org/apache/sling/testing/clients/Constants.java
+++ b/src/main/java/org/apache/sling/testing/clients/Constants.java
@@ -16,10 +16,6 @@
  */
 package org.apache.sling.testing.clients;
 
-import org.apache.http.client.ServiceUnavailableRetryStrategy;
-import org.apache.http.impl.client.DefaultServiceUnavailableRetryStrategy;
-import org.apache.sling.testing.clients.util.LoggedServiceUnavailableRetryStrategy;
-
 public class Constants {
 
     /**
@@ -62,13 +58,21 @@ public class Constants {
     public static final long HTTP_DELAY = delay;
 
     /**
-     * Custom ServiceUnavailableRetryStrategy.
-     * Used by {@link org.apache.sling.testing.clients.SlingClient}
-     * and {@link org.apache.sling.testing.clients.interceptors.FormBasedAuthInterceptor}
+     * Number of http call retries in case of a 5XX response code
      */
-    public static final ServiceUnavailableRetryStrategy HTTP_RETRY_STRATEGY = logRetries
-            ? new LoggedServiceUnavailableRetryStrategy(retries, retriesDelay)
-            : new DefaultServiceUnavailableRetryStrategy(retries, retriesDelay);
+    public static final int HTTP_RETRIES = retries;
+
+    /**
+     * The delay in milliseconds between http retries
+     */
+    public static final int HTTP_RETRIES_DELAY = retriesDelay;
+
+    /**
+     * Whether to log or not http request retries
+     */
+    public static final boolean HTTP_LOG_RETRIES = logRetries;
+
+
 
     /**
      * Handle to OSGI console
diff --git a/src/main/java/org/apache/sling/testing/clients/SlingClient.java b/src/main/java/org/apache/sling/testing/clients/SlingClient.java
index 4ecf731..15a20db 100644
--- a/src/main/java/org/apache/sling/testing/clients/SlingClient.java
+++ b/src/main/java/org/apache/sling/testing/clients/SlingClient.java
@@ -25,16 +25,16 @@ import org.apache.http.annotation.Immutable;
 import org.apache.http.client.CookieStore;
 import org.apache.http.client.CredentialsProvider;
 import org.apache.http.client.RedirectStrategy;
+import org.apache.http.client.ServiceUnavailableRetryStrategy;
 import org.apache.http.client.entity.UrlEncodedFormEntity;
 import org.apache.http.entity.ContentType;
 import org.apache.http.entity.mime.MultipartEntityBuilder;
 import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.DefaultServiceUnavailableRetryStrategy;
 import org.apache.http.impl.client.HttpClientBuilder;
 import org.apache.sling.testing.clients.interceptors.DelayRequestInterceptor;
 import org.apache.sling.testing.clients.interceptors.TestDescriptionInterceptor;
-import org.apache.sling.testing.clients.util.FormEntityBuilder;
-import org.apache.sling.testing.clients.util.HttpUtils;
-import org.apache.sling.testing.clients.util.JsonUtils;
+import org.apache.sling.testing.clients.util.*;
 import org.apache.sling.testing.clients.util.poller.AbstractPoller;
 import org.apache.sling.testing.clients.util.poller.Polling;
 import org.codehaus.jackson.JsonNode;
@@ -626,8 +626,7 @@ public class SlingClient extends AbstractSlingClient {
         private final HttpClientBuilder httpClientBuilder;
 
         protected InternalBuilder(URI url, String user, String password) {
-            this.httpClientBuilder = HttpClientBuilder.create()
-                    .setServiceUnavailableRetryStrategy(Constants.HTTP_RETRY_STRATEGY);
+            this.httpClientBuilder = HttpClientBuilder.create();
             this.configBuilder = SlingClientConfig.Builder.create().setUrl(url).setUser(user).setPassword(password);
 
             setDefaults();
@@ -692,6 +691,10 @@ public class SlingClient extends AbstractSlingClient {
             httpClientBuilder.addInterceptorLast(new TestDescriptionInterceptor());
             httpClientBuilder.addInterceptorLast(new DelayRequestInterceptor(Constants.HTTP_DELAY));
 
+            // HTTP request strategy
+            httpClientBuilder.setServiceUnavailableRetryStrategy(new ServerErrorRetryStrategy(
+                    Constants.HTTP_RETRIES, Constants.HTTP_RETRIES_DELAY, Constants.HTTP_LOG_RETRIES));
+
             return this;
         }
 
diff --git a/src/main/java/org/apache/sling/testing/clients/interceptors/FormBasedAuthInterceptor.java b/src/main/java/org/apache/sling/testing/clients/interceptors/FormBasedAuthInterceptor.java
index 9360f4b..e7e251f 100644
--- a/src/main/java/org/apache/sling/testing/clients/interceptors/FormBasedAuthInterceptor.java
+++ b/src/main/java/org/apache/sling/testing/clients/interceptors/FormBasedAuthInterceptor.java
@@ -28,6 +28,7 @@ import org.apache.http.impl.client.HttpClientBuilder;
 import org.apache.http.message.BasicNameValuePair;
 import org.apache.http.protocol.HttpContext;
 import org.apache.sling.testing.clients.Constants;
+import org.apache.sling.testing.clients.util.ServerErrorRetryStrategy;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -75,8 +76,9 @@ public class FormBasedAuthInterceptor implements HttpRequestInterceptor {
 
         HttpPost loginPost = new HttpPost(URI.create(request.getRequestLine().getUri()).resolve(loginPath));
         loginPost.setEntity(httpEntity);
-        final CloseableHttpClient client = HttpClientBuilder.create()
-                .setServiceUnavailableRetryStrategy(Constants.HTTP_RETRY_STRATEGY)
+        final CloseableHttpClient client = HttpClientBuilder.create().setServiceUnavailableRetryStrategy(
+                new ServerErrorRetryStrategy(
+                        Constants.HTTP_RETRIES, Constants.HTTP_RETRIES_DELAY, Constants.HTTP_LOG_RETRIES))
                 .disableRedirectHandling().build();
 
         client.execute(host, loginPost, context);
diff --git a/src/main/java/org/apache/sling/testing/clients/util/LoggedServiceUnavailableRetryStrategy.java b/src/main/java/org/apache/sling/testing/clients/util/LoggedServiceUnavailableRetryStrategy.java
deleted file mode 100644
index 1303b7b..0000000
--- a/src/main/java/org/apache/sling/testing/clients/util/LoggedServiceUnavailableRetryStrategy.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with this
- * work for additional information regarding copyright ownership. The ASF
- * licenses this file to You under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package org.apache.sling.testing.clients.util;
-
-import org.apache.http.HttpResponse;
-import org.apache.http.annotation.Immutable;
-import org.apache.http.impl.client.DefaultServiceUnavailableRetryStrategy;
-import org.apache.http.protocol.HttpContext;
-import org.apache.http.util.EntityUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.IOException;
-import java.util.Arrays;
-
-@Immutable
-public class LoggedServiceUnavailableRetryStrategy extends DefaultServiceUnavailableRetryStrategy {
-
-    private static final Logger LOGGER = LoggerFactory.getLogger(LoggedServiceUnavailableRetryStrategy.class);
-
-    public LoggedServiceUnavailableRetryStrategy(final int maxRetries, final int retryInterval) {
-        super(maxRetries, retryInterval);
-    }
-
-    @Override
-    public boolean retryRequest(final HttpResponse response, final int executionCount, final HttpContext context) {
-        boolean needRetry = super.retryRequest(response, executionCount, context);
-        if (needRetry && LOGGER.isWarnEnabled()) {
-            LOGGER.warn("Request retry needed due to service unavailable response");
-            LOGGER.warn("Response headers contained:");
-            Arrays.stream(response.getAllHeaders()).forEach(h -> LOGGER.warn("Header {}:{}", h.getName(), h.getValue()));
-            try {
-                String content = EntityUtils.toString(response.getEntity());
-                LOGGER.warn("Response content: {}", content);
-            } catch (IOException exc) {
-                LOGGER.warn("Response as no content");
-            }
-        }
-        return needRetry;
-    }
-
-}
diff --git a/src/main/java/org/apache/sling/testing/clients/util/ServerErrorRetryStrategy.java b/src/main/java/org/apache/sling/testing/clients/util/ServerErrorRetryStrategy.java
new file mode 100644
index 0000000..bcce9e4
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/clients/util/ServerErrorRetryStrategy.java
@@ -0,0 +1,84 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.apache.sling.testing.clients.util;
+
+import org.apache.http.HttpResponse;
+import org.apache.http.HttpStatus;
+import org.apache.http.client.ServiceUnavailableRetryStrategy;
+import org.apache.http.protocol.HttpContext;
+import org.apache.http.util.Args;
+import org.apache.http.util.EntityUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.Arrays;
+
+/**
+ * {code ServiceUnavailableRetryStrategy} strategy for retrying request in case of a 5XX response code
+ */
+public class ServerErrorRetryStrategy implements ServiceUnavailableRetryStrategy {
+
+    private static final Logger LOG = LoggerFactory.getLogger(ServerErrorRetryStrategy.class);
+
+    private final int maxRetries;
+    private final int retryInterval;
+    private final boolean logRetries;
+
+    public ServerErrorRetryStrategy(final int maxRetries, final int retryInterval, final boolean logRetries) {
+        super();
+        Args.positive(maxRetries, "Max retries");
+        Args.positive(retryInterval, "Retry interval");
+        this.maxRetries = maxRetries;
+        this.retryInterval = retryInterval;
+        this.logRetries = logRetries;
+    }
+
+    public ServerErrorRetryStrategy(int maxRetries, int retryInterval) {
+        this (1, 1000, true);
+    }
+
+    public ServerErrorRetryStrategy() {
+        this(1, 1000);
+    }
+
+    @Override
+    public boolean retryRequest(final HttpResponse response, final int executionCount, final HttpContext context) {
+        boolean needsRetry = executionCount <= this.maxRetries &&
+                response.getStatusLine().getStatusCode() >= HttpStatus.SC_INTERNAL_SERVER_ERROR &&
+                response.getStatusLine().getStatusCode() < HttpStatus.SC_INTERNAL_SERVER_ERROR + 100;
+
+        if (this.logRetries && needsRetry && LOG.isWarnEnabled()) {
+            LOG.warn("Request retry needed due to service unavailable response");
+            LOG.warn("Response headers contained:");
+            Arrays.stream(response.getAllHeaders()).forEach(h -> LOG.warn("Header {}:{}", h.getName(), h.getValue()));
+            try {
+                String content = EntityUtils.toString(response.getEntity());
+                LOG.warn("Response content: {}", content);
+            } catch (IOException exc) {
+                LOG.warn("Response as no content");
+            }
+        }
+        return needsRetry;
+    }
+
+    @Override
+    public long getRetryInterval() {
+        return retryInterval;
+    }
+
+}
diff --git a/src/test/java/org/apache/sling/testing/clients/SlingClientRetryServiceUnavailableTest.java b/src/test/java/org/apache/sling/testing/clients/SlingClientRetryServiceUnavailableTest.java
deleted file mode 100644
index 2670983..0000000
--- a/src/test/java/org/apache/sling/testing/clients/SlingClientRetryServiceUnavailableTest.java
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with this
- * work for additional information regarding copyright ownership. The ASF
- * licenses this file to You under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package org.apache.sling.testing.clients;
-
-import org.apache.http.HttpException;
-import org.apache.http.HttpRequest;
-import org.apache.http.HttpResponse;
-import org.apache.http.entity.StringEntity;
-import org.apache.http.protocol.HttpContext;
-import org.apache.http.protocol.HttpRequestHandler;
-import org.junit.ClassRule;
-import org.junit.Test;
-
-import java.io.IOException;
-import java.util.concurrent.TimeoutException;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-public class SlingClientRetryServiceUnavailableTest {
-    private static final String GET_UNAVAILABLE_PATH = "/test/unavailable/resource";
-    private static final String NOK_RESPONSE = "TEST_NOK";
-    private static final String OK_RESPONSE = "TEST_OK";
-
-    private static final int MAX_RETRIES = 5;
-
-    private static int requestCount = 0;
-    private static int availableAtRequestCount = Integer.MAX_VALUE;
-
-    static {
-        System.setProperty(Constants.CONFIG_PROP_PREFIX + "http.logRetries", "true");
-    }
-
-    @ClassRule
-    public static HttpServerRule httpServer = new HttpServerRule() {
-        @Override
-        protected void registerHandlers() throws IOException {
-            serverBootstrap.registerHandler(GET_UNAVAILABLE_PATH, new HttpRequestHandler() {
-                @Override
-                public void handle(HttpRequest request, HttpResponse response, HttpContext context) throws HttpException, IOException {
-                    requestCount++;
-                    if (requestCount == availableAtRequestCount) {
-                        response.setEntity(new StringEntity(OK_RESPONSE));
-                        response.setStatusCode(200);
-                    } else {
-                        response.setEntity(new StringEntity(NOK_RESPONSE));
-                        response.setStatusCode(503);
-                    }
-                }
-            });
-        }
-    };
-
-    @Test
-    public void testRetryReallyUnavailable() throws Exception {
-        requestCount = 0;
-        availableAtRequestCount = Integer.MAX_VALUE; // never available
-        SlingClient c = new SlingClient(httpServer.getURI(), "user", "pass");
-        SlingHttpResponse slingHttpResponse = c.doGet(GET_UNAVAILABLE_PATH, 503, 10);
-        assertEquals(MAX_RETRIES + 1, requestCount);
-        assertEquals(NOK_RESPONSE, slingHttpResponse.getContent());
-    }
-
-    @Test
-    public void testRetryEventuallyAvailable() throws Exception {
-        requestCount = 0;
-        availableAtRequestCount = 3;
-        SlingClient c = new SlingClient(httpServer.getURI(), "user", "pass");
-        SlingHttpResponse slingHttpResponse = c.doGet(GET_UNAVAILABLE_PATH, 200, 10);
-        assertEquals(availableAtRequestCount, requestCount);
-        assertEquals(OK_RESPONSE, slingHttpResponse.getContent());
-
-    }
-
-}
diff --git a/src/test/java/org/apache/sling/testing/clients/SlingClientRetryStrategyTest.java b/src/test/java/org/apache/sling/testing/clients/SlingClientRetryStrategyTest.java
new file mode 100644
index 0000000..23f3b63
--- /dev/null
+++ b/src/test/java/org/apache/sling/testing/clients/SlingClientRetryStrategyTest.java
@@ -0,0 +1,132 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.apache.sling.testing.clients;
+
+import org.apache.http.entity.StringEntity;
+import org.junit.ClassRule;
+import org.junit.Test;
+
+import java.io.IOException;
+
+import static org.junit.Assert.assertEquals;
+
+public class SlingClientRetryStrategyTest {
+    private static final String GET_UNAVAILABLE_PATH = "/test/unavailable/resource";
+    private static final String GET_INEXISTENT_PATH = "/test/inexistent/resource";
+    private static final String GET_INTERNAL_ERROR_PATH = "/test/internalerror/resource";
+    private static final String NOK_RESPONSE = "TEST_NOK";
+    private static final String OK_RESPONSE = "TEST_OK";
+
+    private static final int MAX_RETRIES = 5;
+
+    private static int requestCount = 0;
+    private static int availableAtRequestCount = Integer.MAX_VALUE;
+
+    static {
+        System.setProperty(Constants.CONFIG_PROP_PREFIX + Constants.HTTP_LOG_RETRIES, "true");
+        System.setProperty(Constants.CONFIG_PROP_PREFIX + Constants.HTTP_DELAY, "50");
+        System.setProperty(Constants.CONFIG_PROP_PREFIX + Constants.HTTP_RETRIES, "5");
+    }
+
+    @ClassRule
+    public static HttpServerRule httpServer = new HttpServerRule() {
+        @Override
+        protected void registerHandlers() throws IOException {
+            serverBootstrap.registerHandler(GET_UNAVAILABLE_PATH, (request, response, context) -> {
+                requestCount++;
+                if (requestCount == availableAtRequestCount) {
+                    response.setEntity(new StringEntity(OK_RESPONSE));
+                    response.setStatusCode(200);
+                } else {
+                    response.setEntity(new StringEntity(NOK_RESPONSE));
+                    response.setStatusCode(503);
+                }
+            });
+
+            serverBootstrap.registerHandler(GET_INTERNAL_ERROR_PATH, (request, response, context) -> {
+                requestCount++;
+                if (requestCount == availableAtRequestCount) {
+                    response.setEntity(new StringEntity(OK_RESPONSE));
+                    response.setStatusCode(200);
+                } else {
+                    response.setEntity(new StringEntity(NOK_RESPONSE));
+                    response.setStatusCode(500);
+                }
+            });
+
+            serverBootstrap.registerHandler(GET_INEXISTENT_PATH, (request, response, context) -> {
+                requestCount++;
+                response.setEntity(new StringEntity(NOK_RESPONSE));
+                response.setStatusCode(404);
+            });
+        }
+    };
+
+    @Test
+    public void testRetryReallyUnavailable() throws Exception {
+        requestCount = 0;
+        availableAtRequestCount = Integer.MAX_VALUE; // never available
+        SlingClient c = new SlingClient(httpServer.getURI(), "user", "pass");
+        SlingHttpResponse slingHttpResponse = c.doGet(GET_UNAVAILABLE_PATH, 503);
+        assertEquals(MAX_RETRIES + 1, requestCount);
+        assertEquals(NOK_RESPONSE, slingHttpResponse.getContent());
+    }
+
+    @Test
+    public void testRetryReallyInternalError() throws Exception {
+        requestCount = 0;
+        availableAtRequestCount = Integer.MAX_VALUE; // never available
+        SlingClient c = new SlingClient(httpServer.getURI(), "user", "pass");
+        SlingHttpResponse slingHttpResponse = c.doGet(GET_INTERNAL_ERROR_PATH, 500);
+        assertEquals(MAX_RETRIES + 1, requestCount);
+        assertEquals(NOK_RESPONSE, slingHttpResponse.getContent());
+    }
+
+    @Test
+    public void testRetryInexistent() throws Exception {
+        requestCount = 0;
+        availableAtRequestCount = Integer.MAX_VALUE; // never available
+        SlingClient c = new SlingClient(httpServer.getURI(), "user", "pass");
+        SlingHttpResponse slingHttpResponse = c.doGet(GET_INEXISTENT_PATH, 404);
+        // should not retry at all
+        assertEquals(1, requestCount);
+        assertEquals(NOK_RESPONSE, slingHttpResponse.getContent());
+    }
+
+    @Test
+    public void testRetryEventuallyAvailable() throws Exception {
+        requestCount = 0;
+        availableAtRequestCount = 3;
+        SlingClient c = new SlingClient(httpServer.getURI(), "user", "pass");
+        SlingHttpResponse slingHttpResponse = c.doGet(GET_UNAVAILABLE_PATH, 200);
+        assertEquals(availableAtRequestCount, requestCount);
+        assertEquals(OK_RESPONSE, slingHttpResponse.getContent());
+
+    }
+
+    @Test
+    public void testRetryEventuallyNoError() throws Exception {
+        requestCount = 0;
+        availableAtRequestCount = 3;
+        SlingClient c = new SlingClient(httpServer.getURI(), "user", "pass");
+        SlingHttpResponse slingHttpResponse = c.doGet(GET_INTERNAL_ERROR_PATH, 200);
+        assertEquals(availableAtRequestCount, requestCount);
+        assertEquals(OK_RESPONSE, slingHttpResponse.getContent());
+
+    }
+
+}