From e667b0baa5c384edf64c978e6a80ac5dcf19a6da Mon Sep 17 00:00:00 2001 From: Michele Rastelli Date: Fri, 2 Jul 2021 12:01:21 +0200 Subject: [PATCH] httpclient v5 --- pom.xml | 25 +-- src/main/java/com/arangodb/ArangoDB.java | 6 +- .../internal/InternalArangoDBBuilder.java | 6 +- .../arangodb/internal/http/CURLLogger.java | 2 +- .../internal/http/HttpConnection.java | 150 +++++++++--------- .../internal/http/HttpConnectionFactory.java | 4 +- .../internal/http/HttpDeleteWithBody.java | 43 ----- .../com/arangodb/example/ssl/SslExample.java | 2 +- .../arangodb/internal/http/HttpRetryTest.java | 21 ++- 9 files changed, 107 insertions(+), 152 deletions(-) delete mode 100644 src/main/java/com/arangodb/internal/http/HttpDeleteWithBody.java diff --git a/pom.xml b/pom.xml index bed9c2882..845008d63 100644 --- a/pom.xml +++ b/pom.xml @@ -189,8 +189,8 @@ slf4j-api - org.apache.httpcomponents - httpclient + org.apache.httpcomponents.client5 + httpclient5 true @@ -229,24 +229,9 @@ 2.0.0 - org.apache.httpcomponents - httpclient - 4.5.13 - - - org.apache.httpcomponents - httpcore - 4.4.14 - - - commons-codec - commons-codec - 1.14 - - - commons-logging - commons-logging - 1.2 + org.apache.httpcomponents.client5 + httpclient5 + 5.1 com.arangodb diff --git a/src/main/java/com/arangodb/ArangoDB.java b/src/main/java/com/arangodb/ArangoDB.java index c61e9dc73..635e874bc 100644 --- a/src/main/java/com/arangodb/ArangoDB.java +++ b/src/main/java/com/arangodb/ArangoDB.java @@ -68,7 +68,7 @@ import com.arangodb.velocypack.ValueType; import com.arangodb.velocystream.Request; import com.arangodb.velocystream.Response; -import org.apache.http.client.HttpRequestRetryHandler; +import org.apache.hc.client5.http.HttpRequestRetryStrategy; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.SSLContext; @@ -212,7 +212,7 @@ public Builder hostnameVerifier(final HostnameVerifier hostnameVerifier) { } /** - * Sets the {@link HttpRequestRetryHandler} to be used when using http protocol. + * Sets the {@link HttpRequestRetryStrategy} to be used when using http protocol. * * @param httpRequestRetryHandler HttpRequestRetryHandler to be used * @return {@link ArangoDB.Builder} @@ -223,7 +223,7 @@ public Builder hostnameVerifier(final HostnameVerifier hostnameVerifier) { * 9.1 Safe and Idempotent Methods. * Please refer to HTTP API Documentation for details. */ - public Builder httpRequestRetryHandler(final HttpRequestRetryHandler httpRequestRetryHandler) { + public Builder httpRequestRetryHandler(final HttpRequestRetryStrategy httpRequestRetryHandler) { setHttpRequestRetryHandler(httpRequestRetryHandler); return this; } diff --git a/src/main/java/com/arangodb/internal/InternalArangoDBBuilder.java b/src/main/java/com/arangodb/internal/InternalArangoDBBuilder.java index 8a06f6564..4ca4bcfcf 100644 --- a/src/main/java/com/arangodb/internal/InternalArangoDBBuilder.java +++ b/src/main/java/com/arangodb/internal/InternalArangoDBBuilder.java @@ -42,7 +42,7 @@ import com.arangodb.util.ArangoSerializer; import com.arangodb.velocypack.VPack; import com.arangodb.velocypack.VPackParser; -import org.apache.http.client.HttpRequestRetryHandler; +import org.apache.hc.client5.http.HttpRequestRetryStrategy; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -87,7 +87,7 @@ public abstract class InternalArangoDBBuilder { protected String password; protected Boolean useSsl; protected String httpCookieSpec; - protected HttpRequestRetryHandler httpRequestRetryHandler; + protected HttpRequestRetryStrategy httpRequestRetryHandler; protected SSLContext sslContext; protected HostnameVerifier hostnameVerifier; protected Integer chunksize; @@ -182,7 +182,7 @@ protected void setHostnameVerifier(final HostnameVerifier hostnameVerifier) { this.hostnameVerifier = hostnameVerifier; } - protected void setHttpRequestRetryHandler(final HttpRequestRetryHandler httpRequestRetryHandler) { + protected void setHttpRequestRetryHandler(final HttpRequestRetryStrategy httpRequestRetryHandler) { this.httpRequestRetryHandler = httpRequestRetryHandler; } diff --git a/src/main/java/com/arangodb/internal/http/CURLLogger.java b/src/main/java/com/arangodb/internal/http/CURLLogger.java index 17acbd163..6917b1235 100644 --- a/src/main/java/com/arangodb/internal/http/CURLLogger.java +++ b/src/main/java/com/arangodb/internal/http/CURLLogger.java @@ -23,7 +23,7 @@ import com.arangodb.util.ArangoSerialization; import com.arangodb.velocystream.Request; import com.arangodb.velocystream.RequestType; -import org.apache.http.auth.Credentials; +import org.apache.hc.client5.http.auth.Credentials; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/src/main/java/com/arangodb/internal/http/HttpConnection.java b/src/main/java/com/arangodb/internal/http/HttpConnection.java index 5a221164c..84b6e6251 100644 --- a/src/main/java/com/arangodb/internal/http/HttpConnection.java +++ b/src/main/java/com/arangodb/internal/http/HttpConnection.java @@ -31,37 +31,39 @@ import com.arangodb.velocypack.VPackSlice; import com.arangodb.velocystream.Request; 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.entity.StringEntity; -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 org.apache.hc.client5.http.ConnectionKeepAliveStrategy; +import org.apache.hc.client5.http.HttpRequestRetryStrategy; +import org.apache.hc.client5.http.auth.Credentials; +import org.apache.hc.client5.http.auth.UsernamePasswordCredentials; +import org.apache.hc.client5.http.classic.methods.*; +import org.apache.hc.client5.http.config.RequestConfig; +import org.apache.hc.client5.http.impl.DefaultHttpRequestRetryStrategy; +import org.apache.hc.client5.http.impl.auth.BasicScheme; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; +import org.apache.hc.client5.http.impl.classic.HttpClientBuilder; +import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager; +import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder; +import org.apache.hc.client5.http.protocol.HttpClientContext; +import org.apache.hc.client5.http.ssl.HttpsSupport; +import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory; +import org.apache.hc.core5.http.*; +import org.apache.hc.core5.http.io.entity.ByteArrayEntity; +import org.apache.hc.core5.http.io.entity.StringEntity; +import org.apache.hc.core5.http.message.BasicClassicHttpRequest; +import org.apache.hc.core5.http.message.BasicHeaderElementIterator; +import org.apache.hc.core5.http.message.BasicNameValuePair; +import org.apache.hc.core5.net.URLEncodedUtils; +import org.apache.hc.core5.ssl.SSLContexts; +import org.apache.hc.core5.util.TimeValue; +import org.apache.hc.core5.util.Timeout; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.SSLContext; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -91,7 +93,7 @@ public static class Builder { private SSLContext sslContext; private HostnameVerifier hostnameVerifier; private Integer timeout; - private HttpRequestRetryHandler httpRequestRetryHandler; + private HttpRequestRetryStrategy httpRequestRetryHandler; public Builder user(final String user) { this.user = user; @@ -148,7 +150,7 @@ public Builder timeout(final Integer timeout) { return this; } - public Builder httpRequestRetryHandler(final HttpRequestRetryHandler httpRequestRetryHandler) { + public Builder httpRequestRetryHandler(final HttpRequestRetryStrategy httpRequestRetryHandler) { this.httpRequestRetryHandler = httpRequestRetryHandler; return this; } @@ -161,76 +163,77 @@ public HttpConnection build() { private final PoolingHttpClientConnectionManager cm; private final CloseableHttpClient client; - private final String user; - private final String password; private final ArangoSerialization util; private final Boolean useSsl; private final Protocol contentType; private final HostDescription host; + private final HttpClientContext authCtx; + private final Credentials credentials; private HttpConnection(final HostDescription host, final Integer timeout, final String user, final String password, final Boolean useSsl, final SSLContext sslContext, final HostnameVerifier hostnameVerifier, final ArangoSerialization util, final Protocol contentType, - final Long ttl, final String httpCookieSpec, final HttpRequestRetryHandler httpRequestRetryHandler) { + final Long ttl, final String httpCookieSpec, final HttpRequestRetryStrategy httpRequestRetryHandler) { super(); this.host = host; - this.user = user; - this.password = password; this.useSsl = useSsl; this.util = util; this.contentType = contentType; - final RegistryBuilder registryBuilder = RegistryBuilder - .create(); + + PoolingHttpClientConnectionManagerBuilder cmBuilder = PoolingHttpClientConnectionManagerBuilder.create() + .setMaxConnPerRoute(1) + .setMaxConnTotal(1); if (Boolean.TRUE == useSsl) { - registryBuilder.register("https", new SSLConnectionSocketFactory( + cmBuilder.setSSLSocketFactory(new SSLConnectionSocketFactory( sslContext != null ? sslContext : SSLContexts.createSystemDefault(), - hostnameVerifier != null ? hostnameVerifier : SSLConnectionSocketFactory.getDefaultHostnameVerifier() + hostnameVerifier != null ? hostnameVerifier : HttpsSupport.getDefaultHostnameVerifier() )); - } else { - registryBuilder.register("http", new PlainConnectionSocketFactory()); } - cm = new PoolingHttpClientConnectionManager(registryBuilder.build()); - cm.setDefaultMaxPerRoute(1); - cm.setMaxTotal(1); + if (ttl != null) { + cmBuilder.setConnectionTimeToLive(TimeValue.ofMilliseconds(ttl)); + } + cm = cmBuilder.build(); + final RequestConfig.Builder requestConfig = RequestConfig.custom(); if (timeout != null && timeout >= 0) { - requestConfig.setConnectTimeout(timeout); - requestConfig.setConnectionRequestTimeout(timeout); - requestConfig.setSocketTimeout(timeout); + requestConfig.setConnectTimeout(Timeout.of(timeout, TimeUnit.MILLISECONDS)); + requestConfig.setConnectionRequestTimeout(Timeout.of(timeout, TimeUnit.MILLISECONDS)); + requestConfig.setResponseTimeout(Timeout.of(timeout, TimeUnit.MILLISECONDS)); } if (httpCookieSpec != null && httpCookieSpec.length() > 1) { requestConfig.setCookieSpec(httpCookieSpec); } - final ConnectionKeepAliveStrategy keepAliveStrategy = (response, context) -> HttpConnection.this.getKeepAliveDuration(response); + final ConnectionKeepAliveStrategy keepAliveStrategy = (response, context) -> TimeValue.ofSeconds(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); - } + .setRetryStrategy(httpRequestRetryHandler != null ? httpRequestRetryHandler : new DefaultHttpRequestRetryStrategy()); + client = builder.build(); + String pwd = password != null ? password : ""; + credentials = user != null ? new UsernamePasswordCredentials(user, pwd.toCharArray()) : null; + authCtx = createAuthCtx(); } private long getKeepAliveDuration(final HttpResponse response) { - final HeaderElementIterator it = new BasicHeaderElementIterator(response.headerIterator(HTTP.CONN_KEEP_ALIVE)); + final BasicHeaderElementIterator it = new BasicHeaderElementIterator(response.headerIterator(HeaderElements.KEEP_ALIVE)); while (it.hasNext()) { - final HeaderElement he = it.nextElement(); + final HeaderElement he = it.next(); final String param = he.getName(); final String value = he.getValue(); if (value != null && "timeout".equalsIgnoreCase(param)) { try { - return Long.parseLong(value) * 1000L; + return Long.parseLong(value); } catch (final NumberFormatException ignore) { } } } - return 30L * 1000L; + return 30L; } @Override public void close() throws IOException { - cm.shutdown(); + cm.close(); client.close(); } @@ -247,14 +250,14 @@ private static String buildUrl(final String baseUrl, final Request request) { } else { sb.append("?"); } - final String paramString = URLEncodedUtils.format(toList(request.getQueryParam()), "utf-8"); + final String paramString = URLEncodedUtils.format(toList(request.getQueryParam()), StandardCharsets.UTF_8); sb.append(paramString); } return sb.toString(); } - private HttpRequestBase buildHttpRequestBase(final Request request, final String url) { - final HttpRequestBase httpRequest; + private BasicClassicHttpRequest buildHttpRequestBase(final Request request, final String url) { + final BasicClassicHttpRequest httpRequest; switch (request.getRequestType()) { case POST: httpRequest = requestWithBody(new HttpPost(url), request); @@ -266,7 +269,7 @@ private HttpRequestBase buildHttpRequestBase(final Request request, final String httpRequest = requestWithBody(new HttpPatch(url), request); break; case DELETE: - httpRequest = requestWithBody(new HttpDeleteWithBody(url), request); + httpRequest = requestWithBody(new HttpDelete(url), request); break; case HEAD: httpRequest = new HttpHead(url); @@ -279,7 +282,7 @@ private HttpRequestBase buildHttpRequestBase(final Request request, final String return httpRequest; } - private HttpRequestBase requestWithBody(final HttpEntityEnclosingRequestBase httpRequest, final Request request) { + private BasicClassicHttpRequest requestWithBody(final BasicClassicHttpRequest httpRequest, final Request request) { final VPackSlice body = request.getBody(); if (body != null) { if (contentType == Protocol.HTTP_VPACK) { @@ -309,45 +312,42 @@ private static List toList(final Map parameters) public Response execute(final Request request) throws ArangoDBException, IOException { final String url = buildUrl(buildBaseUrl(host), request); - final HttpRequestBase httpRequest = buildHttpRequestBase(request, url); + final BasicClassicHttpRequest httpRequest = buildHttpRequestBase(request, url); httpRequest.setHeader("User-Agent", "Mozilla/5.0 (compatible; ArangoDB-JavaDriver/1.1; +http://mt.orz.at/)"); if (contentType == Protocol.HTTP_VPACK) { httpRequest.setHeader("Accept", "application/x-velocypack"); } addHeader(request, httpRequest); - final Credentials credentials = addCredentials(httpRequest); if (LOGGER.isDebugEnabled()) { CURLLogger.log(url, request, credentials, util); } Response response; - response = buildResponse(client.execute(httpRequest)); + response = buildResponse(client.execute(httpRequest, authCtx)); checkError(response); return response; } - private static void addHeader(final Request request, final HttpRequestBase httpRequest) { + private static void addHeader(final Request request, final BasicClassicHttpRequest httpRequest) { for (final Entry header : request.getHeaderParam().entrySet()) { httpRequest.addHeader(header.getKey(), header.getValue()); } } - public Credentials addCredentials(final HttpRequestBase httpRequest) { - Credentials credentials = null; - 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); - } + public HttpClientContext createAuthCtx() { + final HttpClientContext localContext = HttpClientContext.create(); + if (credentials != null) { + BasicScheme auth = new BasicScheme(StandardCharsets.UTF_8); + auth.initPreemptive(credentials); + HttpHost target = new HttpHost(Boolean.TRUE == useSsl ? "https" : "http", host.getHost(), host.getPort()); + localContext.resetAuthExchange(target, auth); } - return credentials; + return localContext; } public Response buildResponse(final CloseableHttpResponse httpResponse) throws UnsupportedOperationException, IOException { final Response response = new Response(); - response.setResponseCode(httpResponse.getStatusLine().getStatusCode()); + response.setResponseCode(httpResponse.getCode()); final HttpEntity entity = httpResponse.getEntity(); if (entity != null && entity.getContent() != null) { if (contentType == Protocol.HTTP_VPACK) { @@ -363,7 +363,7 @@ public Response buildResponse(final CloseableHttpResponse httpResponse) } } } - final Header[] headers = httpResponse.getAllHeaders(); + final Header[] headers = httpResponse.getHeaders(); final Map meta = response.getMeta(); for (final Header header : headers) { meta.put(header.getName(), header.getValue()); diff --git a/src/main/java/com/arangodb/internal/http/HttpConnectionFactory.java b/src/main/java/com/arangodb/internal/http/HttpConnectionFactory.java index 0461cd098..87fcd2f15 100644 --- a/src/main/java/com/arangodb/internal/http/HttpConnectionFactory.java +++ b/src/main/java/com/arangodb/internal/http/HttpConnectionFactory.java @@ -25,7 +25,7 @@ import com.arangodb.internal.net.ConnectionFactory; import com.arangodb.internal.net.HostDescription; import com.arangodb.util.ArangoSerialization; -import org.apache.http.client.HttpRequestRetryHandler; +import org.apache.hc.client5.http.HttpRequestRetryStrategy; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.SSLContext; @@ -40,7 +40,7 @@ public class HttpConnectionFactory implements ConnectionFactory { public HttpConnectionFactory(final Integer timeout, final String user, final String password, final Boolean useSsl, final SSLContext sslContext, final HostnameVerifier hostnameVerifier, final ArangoSerialization util, final Protocol protocol, final Long connectionTtl, - final String httpCookieSpec, final HttpRequestRetryHandler httpRequestRetryHandler) { + final String httpCookieSpec, final HttpRequestRetryStrategy httpRequestRetryHandler) { super(); builder = new HttpConnection.Builder().timeout(timeout).user(user).password(password).useSsl(useSsl) .sslContext(sslContext).hostnameVerifier(hostnameVerifier).serializationUtil(util).contentType(protocol) 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/test/java/com/arangodb/example/ssl/SslExample.java b/src/test/java/com/arangodb/example/ssl/SslExample.java index 7512f30ff..3f762b3d2 100644 --- a/src/test/java/com/arangodb/example/ssl/SslExample.java +++ b/src/test/java/com/arangodb/example/ssl/SslExample.java @@ -23,7 +23,7 @@ import com.arangodb.ArangoDB; import com.arangodb.Protocol; import com.arangodb.entity.ArangoDBVersion; -import org.apache.http.conn.ssl.NoopHostnameVerifier; +import org.apache.hc.client5.http.ssl.NoopHostnameVerifier; import org.junit.Ignore; import org.junit.Test; diff --git a/src/test/java/com/arangodb/internal/http/HttpRetryTest.java b/src/test/java/com/arangodb/internal/http/HttpRetryTest.java index 384ae92f5..5b4cd0ec1 100644 --- a/src/test/java/com/arangodb/internal/http/HttpRetryTest.java +++ b/src/test/java/com/arangodb/internal/http/HttpRetryTest.java @@ -24,8 +24,11 @@ import com.arangodb.ArangoDB; import com.arangodb.ArangoDBException; import com.arangodb.Protocol; -import org.apache.http.client.HttpRequestRetryHandler; -import org.apache.http.protocol.HttpContext; +import org.apache.hc.client5.http.HttpRequestRetryStrategy; +import org.apache.hc.core5.http.HttpRequest; +import org.apache.hc.core5.http.HttpResponse; +import org.apache.hc.core5.http.protocol.HttpContext; +import org.apache.hc.core5.util.TimeValue; import org.junit.Ignore; import org.junit.Test; @@ -42,14 +45,24 @@ public class HttpRetryTest { private final static int RETRIES = 2; - private static class TestRetryHandler implements HttpRequestRetryHandler { + private static class TestRetryHandler implements HttpRequestRetryStrategy { private int retriesCounter = 0; @Override - public boolean retryRequest(IOException exception, int executionCount, HttpContext context) { + public boolean retryRequest(HttpRequest request, IOException exception, int execCount, HttpContext context) { return ++retriesCounter < RETRIES; } + @Override + public boolean retryRequest(HttpResponse response, int execCount, HttpContext context) { + return ++retriesCounter < RETRIES; + } + + @Override + public TimeValue getRetryInterval(HttpResponse response, int execCount, HttpContext context) { + return TimeValue.ofMilliseconds(10); + } + } /**