diff --git a/ChangeLog.md b/ChangeLog.md index e45f5540a..0b27cc0df 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -27,6 +27,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a - `BaseDocument` and `BaseEdgeDocument` are now `final` - `BaseDocument#getProperties()` and `BaseEdgeDocument#getProperties()` return now an unmodifiable map - changed API method signatures removing throw declarations like: `throws ArangoDBException` (unchecked exception) +- removed passwords from debug level requests logs ### Removed diff --git a/pom.xml b/pom.xml index db102d8d0..b8d177cb9 100644 --- a/pom.xml +++ b/pom.xml @@ -310,10 +310,7 @@ org.slf4j slf4j-api - - - org.apache.httpcomponents - httpclient + 1.7.36 com.fasterxml.jackson.core @@ -327,9 +324,14 @@ com.fasterxml.jackson.core jackson-annotations + + io.vertx + vertx-web-client + com.arangodb jackson-dataformat-velocypack + 3.0.1 true @@ -346,6 +348,7 @@ ch.qos.logback logback-classic + 1.2.11 test @@ -399,39 +402,11 @@ pom - com.arangodb - jackson-dataformat-velocypack - 3.0.1 - - - org.apache.httpcomponents - httpclient - 4.5.13 - - - org.apache.httpcomponents - httpcore - 4.4.15 - - - commons-codec - commons-codec - 1.15 - - - commons-logging - commons-logging - 1.2 - - - org.slf4j - slf4j-api - 1.7.36 - - - ch.qos.logback - logback-classic - 1.2.11 + io.vertx + vertx-stack-depchain + 4.3.4 + pom + import 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()); + } + +}