org.junit
diff --git a/src/main/java/com/arangodb/ArangoDB.java b/src/main/java/com/arangodb/ArangoDB.java
index 3983b7b55..fc9eb6d3f 100644
--- a/src/main/java/com/arangodb/ArangoDB.java
+++ b/src/main/java/com/arangodb/ArangoDB.java
@@ -40,10 +40,8 @@
import com.arangodb.serde.JacksonSerde;
import com.arangodb.velocystream.Request;
import com.arangodb.velocystream.Response;
-import org.apache.http.client.HttpRequestRetryHandler;
import javax.annotation.concurrent.ThreadSafe;
-import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import java.io.InputStream;
import java.util.Collection;
@@ -351,7 +349,7 @@ private static Protocol loadProtocol(final Properties properties, final Protocol
return Protocol.valueOf(
getProperty(properties, PROPERTY_KEY_PROTOCOL, currentValue,
ArangoDefaults.DEFAULT_NETWORK_PROTOCOL)
- .toUpperCase(Locale.ENGLISH));
+ .toUpperCase(Locale.ROOT));
}
@Override
@@ -450,30 +448,13 @@ public Builder sslContext(final SSLContext sslContext) {
}
/**
- * Sets the {@link javax.net.ssl.HostnameVerifier} to be used when using ssl with http protocol.
+ * Set whether hostname verification is enabled
*
- * @param hostnameVerifier HostnameVerifier to be used
+ * @param verifyHost {@code true} if enabled
* @return {@link ArangoDB.Builder}
*/
- public Builder hostnameVerifier(final HostnameVerifier hostnameVerifier) {
- setHostnameVerifier(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);
+ public Builder verifyHost(final Boolean verifyHost) {
+ setVerifyHost(verifyHost);
return this;
}
@@ -621,8 +602,8 @@ public ArangoDB build() {
final ConnectionFactory connectionFactory = (protocol == null || Protocol.VST == protocol)
? new VstConnectionFactorySync(host, timeout, connectionTtl, keepAliveInterval, useSsl, sslContext)
- : new HttpConnectionFactory(timeout, user, password, useSsl, sslContext, hostnameVerifier, serde,
- protocol, connectionTtl, httpCookieSpec, httpRequestRetryHandler);
+ : new HttpConnectionFactory(timeout, user, password, useSsl, sslContext, verifyHost, serde,
+ protocol, connectionTtl);
final Collection hostList = createHostList(max, connectionFactory);
final HostResolver hostResolver = createHostResolver(hostList, max, connectionFactory);
diff --git a/src/main/java/com/arangodb/internal/ArangoExecutor.java b/src/main/java/com/arangodb/internal/ArangoExecutor.java
index 711308012..37917dad8 100644
--- a/src/main/java/com/arangodb/internal/ArangoExecutor.java
+++ b/src/main/java/com/arangodb/internal/ArangoExecutor.java
@@ -48,14 +48,14 @@ protected T createResult(final Type type, final Response response) {
}
protected final void interceptResponse(Response response) {
- String queueTime = response.getMeta().get("X-Arango-Queue-Time-Seconds");
+ String queueTime = response.getMeta("X-Arango-Queue-Time-Seconds");
if (queueTime != null) {
qtMetrics.add(Double.parseDouble(queueTime));
}
}
protected final Request interceptRequest(Request request) {
- request.putHeaderParam("X-Arango-Queue-Time-Seconds", timeoutS);
+ request.putHeaderParam("x-arango-queue-time-seconds", timeoutS);
return request;
}
diff --git a/src/main/java/com/arangodb/internal/ArangoRequestParam.java b/src/main/java/com/arangodb/internal/ArangoRequestParam.java
index 7db9e599b..16c497994 100644
--- a/src/main/java/com/arangodb/internal/ArangoRequestParam.java
+++ b/src/main/java/com/arangodb/internal/ArangoRequestParam.java
@@ -28,8 +28,8 @@ public final class ArangoRequestParam {
public static final String SYSTEM = "_system";
public static final String DATABASE = "database";
public static final String WAIT_FOR_SYNC = "waitForSync";
- public static final String IF_NONE_MATCH = "If-None-Match";
- public static final String IF_MATCH = "If-Match";
+ public static final String IF_NONE_MATCH = "if-none-match";
+ public static final String IF_MATCH = "if-match";
public static final String KEEP_NULL = "keepNull";
private ArangoRequestParam() {
diff --git a/src/main/java/com/arangodb/internal/InternalArangoCollection.java b/src/main/java/com/arangodb/internal/InternalArangoCollection.java
index ea5d283ce..fdc1f24c1 100644
--- a/src/main/java/com/arangodb/internal/InternalArangoCollection.java
+++ b/src/main/java/com/arangodb/internal/InternalArangoCollection.java
@@ -196,7 +196,7 @@ protected ResponseDeserializer> getDocumentsResponseD
final Class type, final DocumentReadOptions options) {
return response -> {
final MultiDocumentEntity multiDocument = new MultiDocumentEntity<>();
- boolean potentialDirtyRead = Boolean.parseBoolean(response.getMeta().get("X-Arango-Potential-Dirty-Read"));
+ boolean potentialDirtyRead = Boolean.parseBoolean(response.getMeta("X-Arango-Potential-Dirty-Read"));
multiDocument.setPotentialDirtyRead(potentialDirtyRead);
final Collection docs = new ArrayList<>();
final Collection errors = new ArrayList<>();
diff --git a/src/main/java/com/arangodb/internal/InternalArangoDBBuilder.java b/src/main/java/com/arangodb/internal/InternalArangoDBBuilder.java
index 63ba8e603..38e6b6441 100644
--- a/src/main/java/com/arangodb/internal/InternalArangoDBBuilder.java
+++ b/src/main/java/com/arangodb/internal/InternalArangoDBBuilder.java
@@ -26,11 +26,9 @@
import com.arangodb.internal.net.*;
import com.arangodb.internal.util.HostUtils;
import com.arangodb.serde.ArangoSerde;
-import org.apache.http.client.HttpRequestRetryHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import java.io.IOException;
import java.io.InputStream;
@@ -52,7 +50,6 @@ public abstract class InternalArangoDBBuilder {
private static final String PROPERTY_KEY_PASSWORD = "arangodb.password";
private static final String PROPERTY_KEY_JWT = "arangodb.jwt";
private static final String PROPERTY_KEY_USE_SSL = "arangodb.usessl";
- private static final String PROPERTY_KEY_COOKIE_SPEC = "arangodb.httpCookieSpec";
private static final String PROPERTY_KEY_V_STREAM_CHUNK_CONTENT_SIZE = "arangodb.chunksize";
private static final String PROPERTY_KEY_MAX_CONNECTIONS = "arangodb.connections.max";
private static final String PROPERTY_KEY_CONNECTION_TTL = "arangodb.connections.ttl";
@@ -70,10 +67,8 @@ public abstract class InternalArangoDBBuilder {
protected String password;
protected String jwt;
protected Boolean useSsl;
- protected String httpCookieSpec;
- protected HttpRequestRetryHandler httpRequestRetryHandler;
protected SSLContext sslContext;
- protected HostnameVerifier hostnameVerifier;
+ protected Boolean verifyHost;
protected Integer chunksize;
protected Integer maxConnections;
protected Long connectionTtl;
@@ -151,10 +146,6 @@ private static Boolean loadUseSsl(final Properties properties, final Boolean cur
getProperty(properties, PROPERTY_KEY_USE_SSL, currentValue, ArangoDefaults.DEFAULT_USE_SSL));
}
- private static String loadhttpCookieSpec(final Properties properties, final String currentValue) {
- return getProperty(properties, PROPERTY_KEY_COOKIE_SPEC, currentValue, "");
- }
-
private static Integer loadChunkSize(final Properties properties, final Integer currentValue) {
return Integer.parseInt(getProperty(properties, PROPERTY_KEY_V_STREAM_CHUNK_CONTENT_SIZE, currentValue,
ArangoDefaults.CHUNK_DEFAULT_CONTENT_SIZE));
@@ -196,7 +187,7 @@ private static LoadBalancingStrategy loadLoadBalancingStrategy(
final Properties properties,
final LoadBalancingStrategy currentValue) {
return LoadBalancingStrategy.valueOf(getProperty(properties, PROPERTY_KEY_LOAD_BALANCING_STRATEGY, currentValue,
- ArangoDefaults.DEFAULT_LOAD_BALANCING_STRATEGY).toUpperCase(Locale.ENGLISH));
+ ArangoDefaults.DEFAULT_LOAD_BALANCING_STRATEGY).toUpperCase(Locale.ROOT));
}
protected static String getProperty(
@@ -245,7 +236,6 @@ protected void loadProperties(final Properties properties) {
password = loadPassword(properties, password);
jwt = loadJwt(properties, jwt);
useSsl = loadUseSsl(properties, useSsl);
- httpCookieSpec = loadhttpCookieSpec(properties, httpCookieSpec);
chunksize = loadChunkSize(properties, chunksize);
maxConnections = loadMaxConnections(properties, maxConnections);
connectionTtl = loadConnectionTtl(properties, connectionTtl);
@@ -284,12 +274,8 @@ protected void setSslContext(final SSLContext sslContext) {
this.sslContext = sslContext;
}
- protected void setHostnameVerifier(final HostnameVerifier hostnameVerifier) {
- this.hostnameVerifier = hostnameVerifier;
- }
-
- protected void setHttpRequestRetryHandler(final HttpRequestRetryHandler httpRequestRetryHandler) {
- this.httpRequestRetryHandler = httpRequestRetryHandler;
+ protected void setVerifyHost(final Boolean verifyHost) {
+ this.verifyHost = verifyHost;
}
protected void setChunksize(final Integer chunksize) {
diff --git a/src/main/java/com/arangodb/internal/InternalArangoDatabase.java b/src/main/java/com/arangodb/internal/InternalArangoDatabase.java
index c1da4db4d..edfe70b70 100644
--- a/src/main/java/com/arangodb/internal/InternalArangoDatabase.java
+++ b/src/main/java/com/arangodb/internal/InternalArangoDatabase.java
@@ -162,10 +162,7 @@ protected Request queryRequest(final String query, final Map bin
protected Request queryNextRequest(final String id, final AqlQueryOptions options, Map meta) {
final Request request = request(dbName, RequestType.POST, PATH_API_CURSOR, id);
-
- if (meta != null) {
- request.getHeaderParam().putAll(meta);
- }
+ request.putHeaderParams(meta);
final AqlQueryOptions opt = options != null ? options : new AqlQueryOptions();
@@ -179,10 +176,7 @@ protected Request queryNextRequest(final String id, final AqlQueryOptions option
protected Request queryCloseRequest(final String id, final AqlQueryOptions options, Map meta) {
final Request request = request(dbName, RequestType.DELETE, PATH_API_CURSOR, id);
-
- if (meta != null) {
- request.getHeaderParam().putAll(meta);
- }
+ request.putHeaderParams(meta);
final AqlQueryOptions opt = options != null ? options : new AqlQueryOptions();
diff --git a/src/main/java/com/arangodb/internal/cursor/ArangoCursorImpl.java b/src/main/java/com/arangodb/internal/cursor/ArangoCursorImpl.java
index 8f3ac6f0a..cff3dd4b5 100644
--- a/src/main/java/com/arangodb/internal/cursor/ArangoCursorImpl.java
+++ b/src/main/java/com/arangodb/internal/cursor/ArangoCursorImpl.java
@@ -51,7 +51,7 @@ public ArangoCursorImpl(final InternalArangoDatabase, ?> db, final ArangoCurso
this.type = type;
iterator = createIterator(this, db, execute, result);
id = result.getId();
- this.isPontentialDirtyRead = Boolean.parseBoolean(result.getMeta().get("X-Arango-Potential-Dirty-Read"));
+ this.isPontentialDirtyRead = Boolean.parseBoolean(result.getMeta().get("x-arango-potential-dirty-read"));
}
protected ArangoCursorIterator createIterator(
diff --git a/src/main/java/com/arangodb/internal/cursor/entity/InternalCursorEntity.java b/src/main/java/com/arangodb/internal/cursor/entity/InternalCursorEntity.java
index 1ab557b49..abaa5ad41 100644
--- a/src/main/java/com/arangodb/internal/cursor/entity/InternalCursorEntity.java
+++ b/src/main/java/com/arangodb/internal/cursor/entity/InternalCursorEntity.java
@@ -26,7 +26,10 @@
import java.util.Collection;
import java.util.Collections;
+import java.util.HashMap;
import java.util.Map;
+import java.util.function.Function;
+import java.util.stream.Collectors;
/**
* @author Mark Vollmary
@@ -89,22 +92,24 @@ public JsonNode getResult() {
return result;
}
+ @Override
public Map getMeta() {
if (meta == null) return Collections.emptyMap();
- return meta;
+ return Collections.unmodifiableMap(meta);
}
+ @Override
public void setMeta(Map meta) {
- this.meta = cleanupMeta(meta);
+ this.meta = cleanupMeta(new HashMap<>(meta));
}
/**
* @return remove not allowed (valid storable) meta information
*/
public Map cleanupMeta(Map meta) {
- meta.remove("Content-Length");
- meta.remove("Transfer-Encoding");
- meta.remove("X-Arango-Queue-Time-Seconds");
+ meta.remove("content-length");
+ meta.remove("transfer-encoding");
+ meta.remove("x-arango-queue-time-seconds");
return meta;
}
diff --git a/src/main/java/com/arangodb/internal/http/CURLLogger.java b/src/main/java/com/arangodb/internal/http/CURLLogger.java
index 31e4a29a9..b2e72418b 100644
--- a/src/main/java/com/arangodb/internal/http/CURLLogger.java
+++ b/src/main/java/com/arangodb/internal/http/CURLLogger.java
@@ -23,7 +23,6 @@
import com.arangodb.internal.serde.InternalSerde;
import com.arangodb.velocystream.Request;
import com.arangodb.velocystream.RequestType;
-import org.apache.http.auth.Credentials;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -40,10 +39,9 @@ private CURLLogger() {
}
public static void log(
- final String url,
+ final String baseUrl,
+ final String path,
final Request request,
- final Credentials credentials,
- final String jwt,
final InternalSerde util) {
final RequestType requestType = request.getRequestType();
final boolean includeBody = (requestType == RequestType.POST || requestType == RequestType.PUT
@@ -54,23 +52,17 @@ public static void log(
buffer.append("cat <<-___EOB___ | ");
}
buffer.append("curl -X ").append(requestType);
+ // FIXME: add --http2 in case protocol is HTTP/2
buffer.append(" --dump -");
if (request.getHeaderParam().size() > 0) {
for (final Entry header : request.getHeaderParam().entrySet()) {
buffer.append(" -H '").append(header.getKey()).append(":").append(header.getValue()).append("'");
}
}
- if (credentials != null) {
- buffer.append(" -u ").append(credentials.getUserPrincipal().getName()).append(":")
- .append(credentials.getPassword());
- }
- if (jwt != null) {
- buffer.append(" -H ").append("'Authorization: Bearer ").append(jwt).append("'");
- }
if (includeBody) {
buffer.append(" -d @-");
}
- buffer.append(" '").append(url).append("'");
+ buffer.append(" '").append(baseUrl).append(path).append("'");
if (includeBody) {
buffer.append("\n");
buffer.append(util.toJsonString(request.getBody()));
diff --git a/src/main/java/com/arangodb/internal/http/HttpConnection.java b/src/main/java/com/arangodb/internal/http/HttpConnection.java
index 69dbb4dd4..0303fc740 100644
--- a/src/main/java/com/arangodb/internal/http/HttpConnection.java
+++ b/src/main/java/com/arangodb/internal/http/HttpConnection.java
@@ -26,258 +26,257 @@
import com.arangodb.internal.net.Connection;
import com.arangodb.internal.net.HostDescription;
import com.arangodb.internal.serde.InternalSerde;
-import com.arangodb.internal.util.IOUtils;
import com.arangodb.internal.util.ResponseUtils;
import com.arangodb.velocystream.Request;
+import com.arangodb.velocystream.RequestType;
import com.arangodb.velocystream.Response;
-import org.apache.http.*;
-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;
-import org.apache.http.config.RegistryBuilder;
-import org.apache.http.conn.ConnectionKeepAliveStrategy;
-import org.apache.http.conn.socket.ConnectionSocketFactory;
-import org.apache.http.conn.socket.PlainConnectionSocketFactory;
-import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
-import org.apache.http.entity.ByteArrayEntity;
-import org.apache.http.entity.ContentType;
-import org.apache.http.impl.auth.BasicScheme;
-import org.apache.http.impl.client.CloseableHttpClient;
-import org.apache.http.impl.client.DefaultHttpRequestRetryHandler;
-import org.apache.http.impl.client.HttpClientBuilder;
-import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
-import org.apache.http.message.BasicHeaderElementIterator;
-import org.apache.http.message.BasicNameValuePair;
-import org.apache.http.protocol.HTTP;
-import org.apache.http.ssl.SSLContexts;
+import io.netty.handler.ssl.ApplicationProtocolConfig;
+import io.netty.handler.ssl.ClientAuth;
+import io.netty.handler.ssl.IdentityCipherSuiteFilter;
+import io.netty.handler.ssl.JdkSslContext;
+import io.vertx.core.Vertx;
+import io.vertx.core.VertxOptions;
+import io.vertx.core.buffer.Buffer;
+import io.vertx.core.http.HttpHeaders;
+import io.vertx.core.http.HttpMethod;
+import io.vertx.core.http.HttpVersion;
+import io.vertx.core.net.JdkSSLEngineOptions;
+import io.vertx.core.spi.tls.SslContextFactory;
+import io.vertx.ext.auth.authentication.TokenCredentials;
+import io.vertx.ext.auth.authentication.UsernamePasswordCredentials;
+import io.vertx.ext.web.client.HttpRequest;
+import io.vertx.ext.web.client.HttpResponse;
+import io.vertx.ext.web.client.WebClient;
+import io.vertx.ext.web.client.WebClientOptions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.security.NoSuchAlgorithmException;
+import java.util.Iterator;
import java.util.Map.Entry;
+import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
-import static org.apache.http.HttpHeaders.AUTHORIZATION;
/**
* @author Mark Vollmary
*/
public class HttpConnection implements Connection {
-
private static final Logger LOGGER = LoggerFactory.getLogger(HttpCommunication.class);
- private static final ContentType CONTENT_TYPE_APPLICATION_JSON_UTF8 = ContentType.create("application/json",
- "utf-8");
- private static final ContentType CONTENT_TYPE_VPACK = ContentType.create("application/x-velocypack");
- private final PoolingHttpClientConnectionManager cm;
- private final CloseableHttpClient client;
- private final String user;
- private final String password;
+ private static final String CONTENT_TYPE_APPLICATION_JSON_UTF8 = "application/json; charset=utf-8";
+ private static final String CONTENT_TYPE_VPACK = "application/x-velocypack";
+ private static final AtomicInteger THREAD_COUNT = new AtomicInteger();
private final InternalSerde util;
- private final Boolean useSsl;
+ private final String baseUrl;
private final Protocol contentType;
- private final HostDescription host;
- private volatile String jwt = null;
+ private volatile String auth;
+ private final WebClient client;
+ private final Integer timeout;
+ private final Vertx vertx;
private HttpConnection(final HostDescription host, final Integer timeout, final String user, final String password,
- final Boolean useSsl, final SSLContext sslContext, final HostnameVerifier hostnameVerifier
- , final InternalSerde util, final Protocol contentType,
- final Long ttl, final String httpCookieSpec,
- final HttpRequestRetryHandler httpRequestRetryHandler) {
+ final Boolean useSsl, final SSLContext sslContext, final Boolean verifyHost,
+ final InternalSerde util, final Protocol contentType, final Long ttl) {
super();
- this.host = host;
- this.user = user;
- this.password = password;
- this.useSsl = useSsl;
this.util = util;
this.contentType = contentType;
- final RegistryBuilder registryBuilder = RegistryBuilder
- .create();
+ this.timeout = timeout;
+ baseUrl = buildBaseUrl(host, useSsl);
+ auth = new UsernamePasswordCredentials(user, password != null ? password : "").toHttpAuthorization();
+ vertx = Vertx.vertx(new VertxOptions().setPreferNativeTransport(true).setEventLoopPoolSize(1));
+ vertx.runOnContext(e -> Thread.currentThread().setName("adb-eventloop-" + THREAD_COUNT.getAndIncrement()));
+
+ int _ttl = ttl == null ? 0 : Math.toIntExact(ttl / 1000);
+
+ WebClientOptions webClientOptions = new WebClientOptions()
+ .setConnectTimeout(timeout)
+ .setIdleTimeoutUnit(TimeUnit.MILLISECONDS)
+ .setIdleTimeout(timeout)
+ .setKeepAliveTimeout(_ttl)
+ .setHttp2KeepAliveTimeout(_ttl)
+ .setUserAgentEnabled(false)
+ .setFollowRedirects(false)
+ .setLogActivity(true)
+ .setKeepAlive(true)
+ .setTcpKeepAlive(true)
+ .setPipelining(true)
+ .setReuseAddress(true)
+ .setReusePort(true)
+ .setHttp2ClearTextUpgrade(false)
+ //TODO: allow configuring HTTP_2 or HTTP_1_1
+// .setProtocolVersion(HttpVersion.HTTP_1_1)
+ .setProtocolVersion(HttpVersion.HTTP_2)
+ .setUseAlpn(true)
+ .setDefaultHost(host.getHost())
+ .setDefaultPort(host.getPort());
+
+
if (Boolean.TRUE.equals(useSsl)) {
- registryBuilder.register("https", new SSLConnectionSocketFactory(
- sslContext != null ? sslContext : SSLContexts.createSystemDefault(),
- hostnameVerifier != null ? hostnameVerifier :
- SSLConnectionSocketFactory.getDefaultHostnameVerifier()
- ));
- } else {
- registryBuilder.register("http", new PlainConnectionSocketFactory());
- }
- cm = new PoolingHttpClientConnectionManager(registryBuilder.build());
- cm.setDefaultMaxPerRoute(1);
- cm.setMaxTotal(1);
- final RequestConfig.Builder requestConfig = RequestConfig.custom();
- if (timeout != null && timeout >= 0) {
- requestConfig.setConnectTimeout(timeout);
- requestConfig.setConnectionRequestTimeout(timeout);
- requestConfig.setSocketTimeout(timeout);
- }
+ SSLContext ctx;
+ if (sslContext != null) {
+ ctx = sslContext;
+ } else {
+ try {
+ ctx = SSLContext.getDefault();
+ } catch (NoSuchAlgorithmException e) {
+ throw new ArangoDBException(e);
+ }
+ }
- if (httpCookieSpec != null && httpCookieSpec.length() > 1) {
- requestConfig.setCookieSpec(httpCookieSpec);
+ webClientOptions
+ .setSsl(true)
+ .setVerifyHost(verifyHost != null ? verifyHost : true)
+ .setJdkSslEngineOptions(new JdkSSLEngineOptions() {
+ @Override
+ public JdkSSLEngineOptions copy() {
+ return this;
+ }
+
+ @Override
+ public SslContextFactory sslContextFactory() {
+ return () -> new JdkSslContext(
+ ctx,
+ true,
+ null,
+ IdentityCipherSuiteFilter.INSTANCE,
+ ApplicationProtocolConfig.DISABLED,
+ ClientAuth.NONE,
+ null,
+ false
+ );
+ }
+ });
}
- final ConnectionKeepAliveStrategy keepAliveStrategy =
- (response, context) -> HttpConnection.this.getKeepAliveDuration(response);
- final HttpClientBuilder builder = HttpClientBuilder.create().setDefaultRequestConfig(requestConfig.build())
- .setConnectionManager(cm).setKeepAliveStrategy(keepAliveStrategy)
- .setRetryHandler(httpRequestRetryHandler != null ? httpRequestRetryHandler :
- new DefaultHttpRequestRetryHandler());
- if (ttl != null) {
- builder.setConnectionTimeToLive(ttl, TimeUnit.MILLISECONDS);
- }
- client = builder.build();
+ client = WebClient.create(vertx, webClientOptions);
}
- private static String buildUrl(final String baseUrl, final Request request) {
- StringBuilder sb = new StringBuilder().append(baseUrl);
+ private static String buildUrl(final Request request) {
+ StringBuilder sb = new StringBuilder();
DbName dbName = request.getDbName();
if (dbName != null && !dbName.get().isEmpty()) {
sb.append("/_db/").append(dbName.getEncoded());
}
sb.append(request.getRequest());
if (!request.getQueryParam().isEmpty()) {
- if (request.getRequest().contains("?")) {
- sb.append("&");
- } else {
- sb.append("?");
- }
- final String paramString = URLEncodedUtils.format(toList(request.getQueryParam()), "utf-8");
- sb.append(paramString);
- }
- return sb.toString();
- }
+ sb.append("?");
- private static List toList(final Map parameters) {
- final ArrayList paramList = new ArrayList<>(parameters.size());
- for (final Entry param : parameters.entrySet()) {
- if (param.getValue() != null) {
- paramList.add(new BasicNameValuePair(param.getKey(), param.getValue()));
+ try {
+ for (Iterator> iterator = request.getQueryParam().entrySet().iterator(); iterator.hasNext(); ) {
+ Entry param = iterator.next();
+ if (param.getValue() != null) {
+ sb.append(URLEncoder.encode(param.getKey(), StandardCharsets.UTF_8.toString()));
+ sb.append("=");
+ sb.append(URLEncoder.encode(param.getValue(), StandardCharsets.UTF_8.toString()));
+ if (iterator.hasNext()) {
+ sb.append("&");
+ }
+ }
+ }
+ } catch (UnsupportedEncodingException e) {
+ throw new RuntimeException(e);
}
- }
- return paramList;
- }
- private static void addHeader(final Request request, final HttpRequestBase httpRequest) {
- for (final Entry header : request.getHeaderParam().entrySet()) {
- httpRequest.addHeader(header.getKey(), header.getValue());
}
+ return sb.toString();
}
- private long getKeepAliveDuration(final HttpResponse response) {
- final HeaderElementIterator it = new BasicHeaderElementIterator(response.headerIterator(HTTP.CONN_KEEP_ALIVE));
- while (it.hasNext()) {
- final HeaderElement he = it.nextElement();
- final String param = he.getName();
- final String value = he.getValue();
- if (value != null && "timeout".equalsIgnoreCase(param)) {
- try {
- return Long.parseLong(value) * 1000L;
- } catch (final NumberFormatException ignore) {
- }
- }
+ private static void addHeader(final Request request, final HttpRequest> httpRequest) {
+ for (final Entry header : request.getHeaderParam().entrySet()) {
+ httpRequest.putHeader(header.getKey(), header.getValue());
}
- return 30L * 1000L;
}
@Override
public void close() throws IOException {
- cm.shutdown();
client.close();
+ vertx.close();
}
- private HttpRequestBase buildHttpRequestBase(final Request request, final String url) {
- final HttpRequestBase httpRequest;
- switch (request.getRequestType()) {
+ private HttpMethod requestTypeToHttpMethod(RequestType requestType) {
+ switch (requestType) {
case POST:
- httpRequest = requestWithBody(new HttpPost(url), request);
- break;
+ return HttpMethod.POST;
case PUT:
- httpRequest = requestWithBody(new HttpPut(url), request);
- break;
+ return HttpMethod.PUT;
case PATCH:
- httpRequest = requestWithBody(new HttpPatch(url), request);
- break;
+ return HttpMethod.PATCH;
case DELETE:
- httpRequest = requestWithBody(new HttpDeleteWithBody(url), request);
- break;
+ return HttpMethod.DELETE;
case HEAD:
- httpRequest = new HttpHead(url);
- break;
+ return HttpMethod.HEAD;
case GET:
default:
- httpRequest = new HttpGet(url);
- break;
- }
- return httpRequest;
- }
-
- private HttpRequestBase requestWithBody(final HttpEntityEnclosingRequestBase httpRequest, final Request request) {
- final byte[] body = request.getBody();
- if (body != null) {
- if (contentType == Protocol.HTTP_VPACK) {
- httpRequest.setEntity(new ByteArrayEntity(body, CONTENT_TYPE_VPACK));
- } else {
- httpRequest.setEntity(new ByteArrayEntity(body, CONTENT_TYPE_APPLICATION_JSON_UTF8));
- }
+ return HttpMethod.GET;
}
- return httpRequest;
}
- private String buildBaseUrl(final HostDescription host) {
+ private String buildBaseUrl(HostDescription host, boolean useSsl) {
return (Boolean.TRUE.equals(useSsl) ? "https://" : "http://") + host.getHost() + ":" + host.getPort();
}
public Response execute(final Request request) throws IOException {
- final String url = buildUrl(buildBaseUrl(host), request);
- final HttpRequestBase httpRequest = buildHttpRequestBase(request, url);
- httpRequest.setHeader("User-Agent", "Mozilla/5.0 (compatible; ArangoDB-JavaDriver/1.1; +http://mt.orz.at/)");
+ String path = buildUrl(request);
+ HttpRequest httpRequest = client
+ .request(requestTypeToHttpMethod(request.getRequestType()), path)
+ .timeout(timeout);
if (contentType == Protocol.HTTP_VPACK) {
- httpRequest.setHeader("Accept", "application/x-velocypack");
+ httpRequest.putHeader("Accept", "application/x-velocypack");
}
addHeader(request, httpRequest);
- Credentials credentials = null;
- if (jwt != null) {
- httpRequest.addHeader(AUTHORIZATION, "Bearer " + jwt);
- } else if (user != null) {
- credentials = new UsernamePasswordCredentials(user, password != null ? password : "");
- try {
- httpRequest.addHeader(new BasicScheme().authenticate(credentials, httpRequest, null));
- } catch (final AuthenticationException e) {
- throw new ArangoDBException(e);
+ httpRequest.putHeader(HttpHeaders.AUTHORIZATION.toString(), auth);
+
+ if (LOGGER.isDebugEnabled()) {
+ CURLLogger.log(baseUrl, path, request, util);
+ }
+
+ byte[] reqBody = request.getBody();
+ Buffer buffer;
+ if (reqBody != null) {
+ buffer = Buffer.buffer(reqBody);
+ if (contentType == Protocol.HTTP_VPACK) {
+ httpRequest.putHeader(HttpHeaders.CONTENT_TYPE.toString(), CONTENT_TYPE_VPACK);
+ } else {
+ httpRequest.putHeader(HttpHeaders.CONTENT_TYPE.toString(), CONTENT_TYPE_APPLICATION_JSON_UTF8);
}
+ } else {
+ buffer = Buffer.buffer();
}
- if (LOGGER.isDebugEnabled()) {
- CURLLogger.log(url, request, credentials, jwt, util);
+ HttpResponse bufferResponse;
+ try {
+ // FIXME: make async API
+ bufferResponse = httpRequest.sendBuffer(buffer).toCompletionStage().toCompletableFuture().get();
+ } catch (InterruptedException | ExecutionException e) {
+ Throwable cause = e.getCause();
+ if (cause instanceof IOException) {
+ throw (IOException) cause;
+ } else {
+ throw new ArangoDBException(e);
+ }
}
- Response response;
- response = buildResponse(client.execute(httpRequest));
+ Response response = buildResponse(bufferResponse);
checkError(response);
return response;
}
- public Response buildResponse(final CloseableHttpResponse httpResponse)
- throws UnsupportedOperationException, IOException {
+ public Response buildResponse(final HttpResponse httpResponse) throws UnsupportedOperationException {
final Response response = new Response();
- response.setResponseCode(httpResponse.getStatusLine().getStatusCode());
- final HttpEntity entity = httpResponse.getEntity();
- if (entity != null && entity.getContent() != null) {
- final byte[] content = IOUtils.toByteArray(entity.getContent());
- if (content.length > 0) {
- response.setBody(content);
+ response.setResponseCode(httpResponse.statusCode());
+ Buffer body = httpResponse.body();
+ if (body != null) {
+ byte[] bytes = body.getBytes();
+ if (bytes.length > 0) {
+ response.setBody(bytes);
}
}
- final Header[] headers = httpResponse.getAllHeaders();
- final Map meta = response.getMeta();
- for (final Header header : headers) {
- meta.put(header.getName(), header.getValue());
+ for (Entry header : httpResponse.headers()) {
+ response.putMeta(header.getKey(), header.getValue());
}
return response;
}
@@ -288,7 +287,9 @@ protected void checkError(final Response response) {
@Override
public void setJwt(String jwt) {
- this.jwt = jwt;
+ if (jwt != null) {
+ auth = new TokenCredentials(jwt).toHttpAuthorization();
+ }
}
public static class Builder {
@@ -296,14 +297,12 @@ public static class Builder {
private String password;
private InternalSerde util;
private Boolean useSsl;
- private String httpCookieSpec;
private Protocol contentType;
private HostDescription host;
private Long ttl;
private SSLContext sslContext;
- private HostnameVerifier hostnameVerifier;
+ private Boolean verifyHost;
private Integer timeout;
- private HttpRequestRetryHandler httpRequestRetryHandler;
public Builder user(final String user) {
this.user = user;
@@ -325,11 +324,6 @@ public Builder useSsl(final Boolean useSsl) {
return this;
}
- public Builder httpCookieSpec(String httpCookieSpec) {
- this.httpCookieSpec = httpCookieSpec;
- return this;
- }
-
public Builder contentType(final Protocol contentType) {
this.contentType = contentType;
return this;
@@ -350,8 +344,8 @@ public Builder sslContext(final SSLContext sslContext) {
return this;
}
- public Builder hostnameVerifier(final HostnameVerifier hostnameVerifier) {
- this.hostnameVerifier = hostnameVerifier;
+ public Builder verifyHost(final Boolean verifyHost) {
+ this.verifyHost = verifyHost;
return this;
}
@@ -360,14 +354,9 @@ 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, httpRequestRetryHandler);
+ return new HttpConnection(host, timeout, user, password, useSsl, sslContext, verifyHost, util,
+ contentType, ttl);
}
}
diff --git a/src/main/java/com/arangodb/internal/http/HttpConnectionFactory.java b/src/main/java/com/arangodb/internal/http/HttpConnectionFactory.java
index d39d5375a..5e0abc74b 100644
--- a/src/main/java/com/arangodb/internal/http/HttpConnectionFactory.java
+++ b/src/main/java/com/arangodb/internal/http/HttpConnectionFactory.java
@@ -25,9 +25,7 @@
import com.arangodb.internal.net.ConnectionFactory;
import com.arangodb.internal.net.HostDescription;
import com.arangodb.internal.serde.InternalSerde;
-import org.apache.http.client.HttpRequestRetryHandler;
-import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
/**
@@ -38,13 +36,12 @@ 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 InternalSerde util, final Protocol protocol, final Long connectionTtl,
- final String httpCookieSpec, final HttpRequestRetryHandler httpRequestRetryHandler) {
+ final SSLContext sslContext, final Boolean verifyHost,
+ final InternalSerde util, final Protocol protocol, final Long connectionTtl) {
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).httpRequestRetryHandler(httpRequestRetryHandler);
+ .sslContext(sslContext).verifyHost(verifyHost).serializationUtil(util).contentType(protocol)
+ .ttl(connectionTtl);
}
diff --git a/src/main/java/com/arangodb/internal/http/HttpDeleteWithBody.java b/src/main/java/com/arangodb/internal/http/HttpDeleteWithBody.java
deleted file mode 100644
index 080e01974..000000000
--- a/src/main/java/com/arangodb/internal/http/HttpDeleteWithBody.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * 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 org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
-
-import java.net.URI;
-
-/**
- * @author Mark Vollmary
- */
-public class HttpDeleteWithBody extends HttpEntityEnclosingRequestBase {
- public final static String METHOD_NAME = "DELETE";
-
- public HttpDeleteWithBody(final String uri) {
- super();
- setURI(URI.create(uri));
- }
-
- @Override
- public String getMethod() {
- return METHOD_NAME;
- }
-
-}
diff --git a/src/main/java/com/arangodb/internal/serde/InternalDeserializers.java b/src/main/java/com/arangodb/internal/serde/InternalDeserializers.java
index 6abb792ce..d99449a64 100644
--- a/src/main/java/com/arangodb/internal/serde/InternalDeserializers.java
+++ b/src/main/java/com/arangodb/internal/serde/InternalDeserializers.java
@@ -82,7 +82,7 @@ public Response deserialize(final JsonParser p, final DeserializationContext ctx
response.setType(it.next().intValue());
response.setResponseCode(it.next().intValue());
if (it.hasNext()) {
- response.setMeta(readTreeAsValue(p, ctxt, it.next(), Map.class));
+ response.putMetas(readTreeAsValue(p, ctxt, it.next(), Map.class));
}
return response;
}
diff --git a/src/main/java/com/arangodb/internal/util/IOUtils.java b/src/main/java/com/arangodb/internal/util/IOUtils.java
deleted file mode 100644
index c2985c4f6..000000000
--- a/src/main/java/com/arangodb/internal/util/IOUtils.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * 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.util;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.*;
-
-/**
- * @author Mark Vollmary
- */
-public final class IOUtils {
-
- private static final Logger LOGGER = LoggerFactory.getLogger(IOUtils.class);
-
- private IOUtils() {
- }
-
- public static String toString(final InputStream input) throws IOException {
- return toString(input, "utf-8");
- }
-
- public static String toString(final InputStream input, final String encode) throws IOException {
- try {
- final StringBuilder buffer = new StringBuilder(8012);
- final InputStreamReader in = new InputStreamReader(new BufferedInputStream(input), encode);
- final char[] cbuf = new char[8012];
- int len;
- while ((len = in.read(cbuf)) != -1) {
- buffer.append(cbuf, 0, len);
- }
- return buffer.toString();
- } catch (final UnsupportedEncodingException e) {
- throw new RuntimeException(e);
- } finally {
- if (input != null) {
- try {
- input.close();
- } catch (final IOException e) {
- // TODO
- }
- }
- }
- }
-
- public static byte[] toByteArray(final InputStream input) throws IOException {
- final ByteArrayOutputStream buffer = new ByteArrayOutputStream();
- int nRead;
- final byte[] data = new byte[8012];
- while ((nRead = input.read(data, 0, data.length)) != -1) {
- buffer.write(data, 0, nRead);
- }
- buffer.flush();
- return buffer.toByteArray();
- }
-
-}
diff --git a/src/main/java/com/arangodb/internal/util/RequestUtils.java b/src/main/java/com/arangodb/internal/util/RequestUtils.java
index 2b5f56b47..1539b8a76 100644
--- a/src/main/java/com/arangodb/internal/util/RequestUtils.java
+++ b/src/main/java/com/arangodb/internal/util/RequestUtils.java
@@ -29,7 +29,7 @@
*/
public final class RequestUtils {
- public static final String HEADER_ALLOW_DIRTY_READ = "X-Arango-Allow-Dirty-Read";
+ public static final String HEADER_ALLOW_DIRTY_READ = "x-arango-allow-dirty-read";
private RequestUtils() {
super();
@@ -40,7 +40,7 @@ public static Request allowDirtyRead(final Request request) {
}
public static AccessType determineAccessType(final Request request) {
- if (request.getHeaderParam().containsKey(HEADER_ALLOW_DIRTY_READ)) {
+ if (request.containsHeaderParam(HEADER_ALLOW_DIRTY_READ)) {
return AccessType.DIRTY_READ;
}
if (request.getRequestType() == RequestType.GET) {
diff --git a/src/main/java/com/arangodb/internal/util/ResponseUtils.java b/src/main/java/com/arangodb/internal/util/ResponseUtils.java
index d09cd181b..fcab67e88 100644
--- a/src/main/java/com/arangodb/internal/util/ResponseUtils.java
+++ b/src/main/java/com/arangodb/internal/util/ResponseUtils.java
@@ -36,7 +36,7 @@ public final class ResponseUtils {
private static final int ERROR_STATUS = 300;
private static final int ERROR_INTERNAL = 503;
- private static final String HEADER_ENDPOINT = "X-Arango-Endpoint";
+ private static final String HEADER_ENDPOINT = "x-arango-endpoint";
private ResponseUtils() {
super();
@@ -45,9 +45,9 @@ private ResponseUtils() {
public static void checkError(final InternalSerde util, final Response response) {
final int responseCode = response.getResponseCode();
if (responseCode >= ERROR_STATUS) {
- if (responseCode == ERROR_INTERNAL && response.getMeta().containsKey(HEADER_ENDPOINT)) {
+ if (responseCode == ERROR_INTERNAL && response.containsMeta(HEADER_ENDPOINT)) {
throw new ArangoDBRedirectException(String.format("Response Code: %s", responseCode),
- response.getMeta().get(HEADER_ENDPOINT));
+ response.getMeta(HEADER_ENDPOINT));
} else if (response.getBody() != null) {
final ErrorEntity errorEntity = util.deserialize(response.getBody(), ErrorEntity.class);
ArangoDBException e = new ArangoDBException(errorEntity);
diff --git a/src/main/java/com/arangodb/velocystream/Request.java b/src/main/java/com/arangodb/velocystream/Request.java
index 666b00e21..4802c6c00 100644
--- a/src/main/java/com/arangodb/velocystream/Request.java
+++ b/src/main/java/com/arangodb/velocystream/Request.java
@@ -22,7 +22,9 @@
import com.arangodb.DbName;
+import java.util.Collections;
import java.util.HashMap;
+import java.util.Locale;
import java.util.Map;
/**
@@ -91,12 +93,29 @@ public Request putQueryParam(final String key, final Object value) {
}
public Map getHeaderParam() {
- return headerParam;
+ return Collections.unmodifiableMap(headerParam);
+ }
+
+ public String getHeaderParam(final String key) {
+ return headerParam.get(key.toLowerCase(Locale.ROOT));
+ }
+
+ public boolean containsHeaderParam(final String key) {
+ return headerParam.containsKey(key.toLowerCase(Locale.ROOT));
}
public Request putHeaderParam(final String key, final String value) {
if (value != null) {
- headerParam.put(key, value);
+ headerParam.put(key.toLowerCase(Locale.ROOT), value);
+ }
+ return this;
+ }
+
+ public Request putHeaderParams(final Map params) {
+ if (params != null) {
+ for (Map.Entry it : params.entrySet()) {
+ putHeaderParam(it.getKey(), it.getValue());
+ }
}
return this;
}
diff --git a/src/main/java/com/arangodb/velocystream/Response.java b/src/main/java/com/arangodb/velocystream/Response.java
index 1f7b164bd..9b5cf6fd8 100644
--- a/src/main/java/com/arangodb/velocystream/Response.java
+++ b/src/main/java/com/arangodb/velocystream/Response.java
@@ -20,7 +20,9 @@
package com.arangodb.velocystream;
+import java.util.Collections;
import java.util.HashMap;
+import java.util.Locale;
import java.util.Map;
/**
@@ -31,7 +33,7 @@ public class Response {
private int version = 1;
private int type = 2;
private int responseCode;
- private Map meta;
+ private final Map meta;
private byte[] body = null;
public Response() {
@@ -64,11 +66,25 @@ public void setResponseCode(final int responseCode) {
}
public Map getMeta() {
- return meta;
+ return Collections.unmodifiableMap(meta);
}
- public void setMeta(final Map meta) {
- this.meta = meta;
+ public String getMeta(final String key) {
+ return meta.get(key.toLowerCase(Locale.ROOT));
+ }
+
+ public boolean containsMeta(final String key) {
+ return meta.containsKey(key.toLowerCase(Locale.ROOT));
+ }
+
+ public void putMeta(final String key, final String value) {
+ this.meta.put(key.toLowerCase(Locale.ROOT), value);
+ }
+
+ public void putMetas(final Map meta) {
+ for (Map.Entry it : meta.entrySet()) {
+ putMeta(it.getKey(), it.getValue());
+ }
}
public byte[] getBody() {
diff --git a/src/test/java/com/arangodb/example/ssl/SslExampleTest.java b/src/test/java/com/arangodb/example/ssl/SslExampleTest.java
index 1fd34647d..764f1ae52 100644
--- a/src/test/java/com/arangodb/example/ssl/SslExampleTest.java
+++ b/src/test/java/com/arangodb/example/ssl/SslExampleTest.java
@@ -23,7 +23,6 @@
import com.arangodb.ArangoDB;
import com.arangodb.Protocol;
import com.arangodb.entity.ArangoDBVersion;
-import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.EnabledIfSystemProperty;
@@ -76,7 +75,7 @@ void noopHostnameVerifier() throws Exception {
.password("test")
.useSsl(true)
.sslContext(createSslContext())
- .hostnameVerifier(NoopHostnameVerifier.INSTANCE)
+ .verifyHost(false)
.useProtocol(Protocol.HTTP_JSON)
.build();
final ArangoDBVersion version = arangoDB.getVersion();
diff --git a/src/test/java/perf/Benchmark.java b/src/test/java/perf/Benchmark.java
new file mode 100644
index 000000000..8fade9e17
--- /dev/null
+++ b/src/test/java/perf/Benchmark.java
@@ -0,0 +1,123 @@
+package perf;
+
+import java.util.Date;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.atomic.AtomicInteger;
+
+public abstract class Benchmark {
+
+ private static final int SYNC_THREADS = 128;
+ private final CountDownLatch completed = new CountDownLatch(1);
+ private volatile Long startTime = null;
+ private volatile Long endTime = null;
+ private volatile int targetCount = Integer.MAX_VALUE;
+ private final AtomicInteger counter = new AtomicInteger();
+ private final ExecutorService es = Executors.newFixedThreadPool(SYNC_THREADS);
+ private final int warmupDurationSeconds;
+ private final int numberOfRequests;
+
+ public Benchmark(int warmupDurationSeconds, int numberOfRequests) {
+ this.warmupDurationSeconds = warmupDurationSeconds;
+ this.numberOfRequests = numberOfRequests;
+ }
+
+ public void run() {
+ // warmup
+ startBenchmark();
+
+ // start monitor / warmup
+ startMonitor();
+
+ // start benchmark
+ startMeasuring();
+ }
+
+ private void startMonitor() {
+ for (int i = 0; i < warmupDurationSeconds; i++) {
+ counter.set(0);
+ long start = new Date().getTime();
+ try {
+ Thread.sleep(1_000);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+
+ long current = new Date().getTime();
+ long elapsed = current - start;
+ double reqsPerSec = 1_000.0 * counter.get() / elapsed;
+ System.out.println("reqs/s: \t" + reqsPerSec);
+ }
+ }
+
+ private void startBenchmark() {
+ start();
+ new Thread(() -> {
+ try {
+ completed.await();
+ // wait graceful shutdown
+ Thread.sleep(1_000);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ // force shutdown
+ es.shutdown();
+ shutdown();
+ }).start();
+ }
+
+ private void startMeasuring() {
+ counter.set(0);
+ targetCount = numberOfRequests;
+ startTime = System.currentTimeMillis();
+ }
+
+ public long waitComplete() {
+ try {
+ completed.await();
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ return endTime - startTime;
+ }
+
+ /**
+ * @return req/s
+ */
+ public long getThroughput() {
+ return targetCount * 1000L / (endTime - startTime);
+ }
+
+ /**
+ * notify the success of #count requests
+ *
+ * @return whether more requests should be performed
+ */
+ private boolean success() {
+ if (endTime != null) return false;
+ if (counter.addAndGet(1) >= targetCount) {
+ endTime = System.currentTimeMillis();
+ completed.countDown();
+ return false;
+ }
+ return true;
+ }
+
+ private void start() {
+ for (int i = 0; i < SYNC_THREADS; i++) {
+ es.execute(() -> {
+ boolean more = true;
+ while (more) {
+ sendRequest();
+ more = success();
+ }
+ });
+ }
+ }
+
+ protected abstract void sendRequest();
+
+ protected abstract void shutdown();
+
+}
diff --git a/src/test/java/perf/SyncBenchmarkTest.java b/src/test/java/perf/SyncBenchmarkTest.java
new file mode 100644
index 000000000..fb8fea25f
--- /dev/null
+++ b/src/test/java/perf/SyncBenchmarkTest.java
@@ -0,0 +1,60 @@
+package perf;
+
+import com.arangodb.ArangoDB;
+import com.arangodb.DbName;
+import com.arangodb.Protocol;
+import com.arangodb.velocystream.Request;
+import com.arangodb.velocystream.RequestType;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.EnumSource;
+
+@Disabled
+public class SyncBenchmarkTest {
+ private final int warmupDurationSeconds = 15;
+ private final int numberOfRequests = 1_000_000;
+
+ @ParameterizedTest
+ @EnumSource(Protocol.class)
+ void getVersion(Protocol protocol) {
+ ArangoDB adb = new ArangoDB.Builder().useProtocol(protocol).build();
+ Benchmark benchmark = new Benchmark(warmupDurationSeconds, numberOfRequests) {
+ @Override
+ protected void sendRequest() {
+ adb.getVersion();
+ }
+
+ @Override
+ protected void shutdown() {
+ adb.shutdown();
+ }
+ };
+ benchmark.run();
+ System.out.println("elapsed time [ms]: \t" + benchmark.waitComplete());
+ System.out.println("throughput [req/s]: \t" + benchmark.getThroughput());
+ }
+
+ @ParameterizedTest
+ @EnumSource(Protocol.class)
+ void getVersionWithDetails(Protocol protocol) {
+ ArangoDB adb = new ArangoDB.Builder().useProtocol(protocol).build();
+ Benchmark benchmark = new Benchmark(warmupDurationSeconds, numberOfRequests) {
+ private final Request request = new Request(DbName.SYSTEM, RequestType.GET,
+ "/_api/version").putQueryParam("details", true);
+
+ @Override
+ protected void sendRequest() {
+ adb.execute(request);
+ }
+
+ @Override
+ protected void shutdown() {
+ adb.shutdown();
+ }
+ };
+ benchmark.run();
+ System.out.println("elapsed time [ms]: \t" + benchmark.waitComplete());
+ System.out.println("throughput [req/s]: \t" + benchmark.getThroughput());
+ }
+
+}