diff --git a/src/main/java/com/arangodb/ArangoDB.java b/src/main/java/com/arangodb/ArangoDB.java index 260d939d9..403ba3a26 100644 --- a/src/main/java/com/arangodb/ArangoDB.java +++ b/src/main/java/com/arangodb/ArangoDB.java @@ -45,6 +45,7 @@ import com.arangodb.velocypack.*; import com.arangodb.velocystream.Request; import com.arangodb.velocystream.Response; +import org.apache.http.client.HttpRequestRetryHandler; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.SSLContext; @@ -187,6 +188,23 @@ public Builder hostnameVerifier(final HostnameVerifier hostnameVerifier) { return this; } + /** + * Sets the {@link HttpRequestRetryHandler} to be used when using http protocol. + * + * @param httpRequestRetryHandler HttpRequestRetryHandler to be used + * @return {@link ArangoDB.Builder} + * + *

+ * NOTE: + * Some ArangoDB HTTP endpoints do not honor RFC-2616 HTTP 1.1 specification in respect to + * 9.1 Safe and Idempotent Methods. + * Please refer to HTTP API Documentation for details. + */ + public Builder httpRequestRetryHandler(final HttpRequestRetryHandler httpRequestRetryHandler) { + setHttpRequestRetryHandler(httpRequestRetryHandler); + return this; + } + /** * Sets the chunk size when {@link Protocol#VST} is used. * @@ -584,7 +602,7 @@ public synchronized ArangoDB build() { final ConnectionFactory connectionFactory = (protocol == null || Protocol.VST == protocol) ? new VstConnectionFactorySync(host, timeout, connectionTtl, useSsl, sslContext) : new HttpConnectionFactory(timeout, user, password, useSsl, sslContext, hostnameVerifier, custom, - protocol, connectionTtl, httpCookieSpec); + protocol, connectionTtl, httpCookieSpec, httpRequestRetryHandler); final Collection hostList = createHostList(max, connectionFactory); final HostResolver hostResolver = createHostResolver(hostList, max, connectionFactory); diff --git a/src/main/java/com/arangodb/internal/InternalArangoDBBuilder.java b/src/main/java/com/arangodb/internal/InternalArangoDBBuilder.java index a8fdd01d3..41aaae257 100644 --- a/src/main/java/com/arangodb/internal/InternalArangoDBBuilder.java +++ b/src/main/java/com/arangodb/internal/InternalArangoDBBuilder.java @@ -31,6 +31,7 @@ import com.arangodb.util.ArangoSerializer; import com.arangodb.velocypack.VPack; import com.arangodb.velocypack.VPackParser; +import org.apache.http.client.HttpRequestRetryHandler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -74,6 +75,7 @@ public abstract class InternalArangoDBBuilder { protected String password; protected Boolean useSsl; protected String httpCookieSpec; + protected HttpRequestRetryHandler httpRequestRetryHandler; protected SSLContext sslContext; protected HostnameVerifier hostnameVerifier; protected Integer chunksize; @@ -166,6 +168,10 @@ protected void setHostnameVerifier(final HostnameVerifier hostnameVerifier) { this.hostnameVerifier = hostnameVerifier; } + protected void setHttpRequestRetryHandler(final HttpRequestRetryHandler httpRequestRetryHandler) { + this.httpRequestRetryHandler = httpRequestRetryHandler; + } + protected void setChunksize(final Integer chunksize) { this.chunksize = chunksize; } diff --git a/src/main/java/com/arangodb/internal/http/HttpConnection.java b/src/main/java/com/arangodb/internal/http/HttpConnection.java index 5c6069d38..5a221164c 100644 --- a/src/main/java/com/arangodb/internal/http/HttpConnection.java +++ b/src/main/java/com/arangodb/internal/http/HttpConnection.java @@ -35,6 +35,7 @@ import org.apache.http.auth.AuthenticationException; import org.apache.http.auth.Credentials; import org.apache.http.auth.UsernamePasswordCredentials; +import org.apache.http.client.HttpRequestRetryHandler; import org.apache.http.client.config.RequestConfig; import org.apache.http.client.methods.*; import org.apache.http.client.utils.URLEncodedUtils; @@ -90,6 +91,7 @@ public static class Builder { private SSLContext sslContext; private HostnameVerifier hostnameVerifier; private Integer timeout; + private HttpRequestRetryHandler httpRequestRetryHandler; public Builder user(final String user) { this.user = user; @@ -146,8 +148,14 @@ public Builder timeout(final Integer timeout) { return this; } + public Builder httpRequestRetryHandler(final HttpRequestRetryHandler httpRequestRetryHandler) { + this.httpRequestRetryHandler = httpRequestRetryHandler; + return this; + } + public HttpConnection build() { - return new HttpConnection(host, timeout, user, password, useSsl, sslContext, hostnameVerifier, util, contentType, ttl, httpCookieSpec); + return new HttpConnection(host, timeout, user, password, useSsl, sslContext, hostnameVerifier, util, + contentType, ttl, httpCookieSpec, httpRequestRetryHandler); } } @@ -162,7 +170,7 @@ public HttpConnection build() { private HttpConnection(final HostDescription host, final Integer timeout, final String user, final String password, final Boolean useSsl, final SSLContext sslContext, final HostnameVerifier hostnameVerifier, final ArangoSerialization util, final Protocol contentType, - final Long ttl, final String httpCookieSpec) { + final Long ttl, final String httpCookieSpec, final HttpRequestRetryHandler httpRequestRetryHandler) { super(); this.host = host; this.user = user; @@ -197,7 +205,7 @@ private HttpConnection(final HostDescription host, final Integer timeout, final final ConnectionKeepAliveStrategy keepAliveStrategy = (response, context) -> HttpConnection.this.getKeepAliveDuration(response); final HttpClientBuilder builder = HttpClientBuilder.create().setDefaultRequestConfig(requestConfig.build()) .setConnectionManager(cm).setKeepAliveStrategy(keepAliveStrategy) - .setRetryHandler(new DefaultHttpRequestRetryHandler()); + .setRetryHandler(httpRequestRetryHandler != null ? httpRequestRetryHandler : new DefaultHttpRequestRetryHandler()); if (ttl != null) { builder.setConnectionTimeToLive(ttl, TimeUnit.MILLISECONDS); } diff --git a/src/main/java/com/arangodb/internal/http/HttpConnectionFactory.java b/src/main/java/com/arangodb/internal/http/HttpConnectionFactory.java index bab2b8233..0461cd098 100644 --- a/src/main/java/com/arangodb/internal/http/HttpConnectionFactory.java +++ b/src/main/java/com/arangodb/internal/http/HttpConnectionFactory.java @@ -25,6 +25,7 @@ import com.arangodb.internal.net.ConnectionFactory; import com.arangodb.internal.net.HostDescription; import com.arangodb.util.ArangoSerialization; +import org.apache.http.client.HttpRequestRetryHandler; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.SSLContext; @@ -37,12 +38,13 @@ public class HttpConnectionFactory implements ConnectionFactory { private final HttpConnection.Builder builder; public HttpConnectionFactory(final Integer timeout, final String user, final String password, final Boolean useSsl, - final SSLContext sslContext, final HostnameVerifier hostnameVerifier, final ArangoSerialization util, final Protocol protocol, - final Long connectionTtl, String httpCookieSpec) { + final SSLContext sslContext, final HostnameVerifier hostnameVerifier, + final ArangoSerialization util, final Protocol protocol, final Long connectionTtl, + final String httpCookieSpec, final HttpRequestRetryHandler httpRequestRetryHandler) { super(); builder = new HttpConnection.Builder().timeout(timeout).user(user).password(password).useSsl(useSsl) .sslContext(sslContext).hostnameVerifier(hostnameVerifier).serializationUtil(util).contentType(protocol) - .ttl(connectionTtl).httpCookieSpec(httpCookieSpec); + .ttl(connectionTtl).httpCookieSpec(httpCookieSpec).httpRequestRetryHandler(httpRequestRetryHandler); } diff --git a/src/test/java/com/arangodb/internal/http/HttpRetryTest.java b/src/test/java/com/arangodb/internal/http/HttpRetryTest.java new file mode 100644 index 000000000..384ae92f5 --- /dev/null +++ b/src/test/java/com/arangodb/internal/http/HttpRetryTest.java @@ -0,0 +1,77 @@ +/* + * DISCLAIMER + * + * Copyright 2016 ArangoDB GmbH, Cologne, Germany + * + * 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. + * + * Copyright holder is ArangoDB GmbH, Cologne, Germany + */ + +package com.arangodb.internal.http; + + +import com.arangodb.ArangoDB; +import com.arangodb.ArangoDBException; +import com.arangodb.Protocol; +import org.apache.http.client.HttpRequestRetryHandler; +import org.apache.http.protocol.HttpContext; +import org.junit.Ignore; +import org.junit.Test; + +import java.io.IOException; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.fail; + +/** + * @author Michele Rastelli + */ +public class HttpRetryTest { + + private final static int RETRIES = 2; + + private static class TestRetryHandler implements HttpRequestRetryHandler { + private int retriesCounter = 0; + + @Override + public boolean retryRequest(IOException exception, int executionCount, HttpContext context) { + return ++retriesCounter < RETRIES; + } + + } + + /** + * remove host from src/test/resources/arangodb.properties to run this test + */ + @Test + @Ignore + public void retry() { + TestRetryHandler retryHandler = new TestRetryHandler(); + ArangoDB arangoDB = new ArangoDB.Builder() + .host("wrongHost", 8529) + .useProtocol(Protocol.HTTP_JSON) + .httpRequestRetryHandler(retryHandler) + .build(); + + try { + arangoDB.getVersion(); + fail("it should throw I/O exception"); + } catch (ArangoDBException e) { + assertThat(retryHandler.retriesCounter, is(RETRIES)); + } + + } + +}