diff --git a/.github/workflows/ci-java7.yaml b/.github/workflows/ci-java7.yaml index 2c8257d45..e2ce98b22 100644 --- a/.github/workflows/ci-java7.yaml +++ b/.github/workflows/ci-java7.yaml @@ -55,7 +55,7 @@ jobs: # dailymotion-simple-cmdline-sample and google-http-client-assembly depend on # google-http-client-jackson2 mvn --batch-mode --show-version -ntp test \ - --projects '!google-http-client-jackson2,!google-http-client-appengine,!samples/dailymotion-simple-cmdline-sample,!google-http-client-assembly' \ + --projects '!google-http-client-jackson2,!google-http-client-appengine,!samples/dailymotion-simple-cmdline-sample,!google-http-client-assembly,!google-http-client-apache-v5' \ -Dclirr.skip=true -Denforcer.skip=true -Dmaven.javadoc.skip=true \ -Dgcloud.download.skip=true -T 1C \ -Dproject.surefire.version=2.22.2 \ diff --git a/docs/http-transport.md b/docs/http-transport.md index 0167db4ac..b6d94cce2 100644 --- a/docs/http-transport.md +++ b/docs/http-transport.md @@ -19,8 +19,12 @@ There are three built-in low-level HTTP transports: 1. [`NetHttpTransport`][net-http-transport]: based on [`HttpURLConnection`][http-url-connection] that is found in all Java SDKs, and thus usually the simplest choice. +1. [`Apache5HttpTransport`][apache-http-transport]: based on the popular + [Apache 5.x HttpClient][apache5-http-client] that allows for more customization. 1. [`ApacheHttpTransport`][apache-http-transport]: based on the popular -[Apache HttpClient][apache-http-client] that allows for more customization. +[Apache 4.x HttpClient][apache-http-client] that allows for more customization. Note that this transport implementation +relies on [Apache 4.x HttpCore][apache-http-core] which has reached end of life. It is recommended to use +[`Apache5HttpTransport`][apache-http-transport] instead. 1. [`UrlFetchTransport`][url-fetch-transport]: based on the [URL Fetch Java API][url-fetch] in the Google App Engine SDK. @@ -124,7 +128,10 @@ HttpRequestFactory requestFactory = transport.createRequestFactory(new MyInitial [net-http-transport]: https://googleapis.dev/java/google-http-client/latest/index.html?com/google/api/client/http/javanet/NetHttpTransport.html [http-url-connection]: http://docs.oracle.com/javase/7/docs/api/java/net/HttpURLConnection.html [apache-http-transport]: https://googleapis.dev/java/google-http-client/latest/index.html?com/google/api/client/http/apache/v2/ApacheHttpTransport.html -[apache-http-client]: http://hc.apache.org/httpcomponents-client-ga/index.html +[apache5-http-transport]: https://github.com/googleapis/google-http-java-client/blob/de8743587d1415e8a6046096ac1fc0a5e81490c3/google-http-client-apache-v5/src/main/java/com/google/api/client/http/apache/v5/Apache5HttpTransport.java +[apache-http-client]: https://hc.apache.org/httpcomponents-client-4.5.x/index.html +[apache-http-core]: https://hc.apache.org/httpcomponents-core-4.4.x/index.html +[apache5-http-client]: https://hc.apache.org/httpcomponents-client-5.3.x/index.html [url-fetch-transport]: https://googleapis.dev/java/google-http-client/latest/index.html?com/google/api/client/extensions/appengine/http/UrlFetchTransport.html [url-fetch]: https://cloud.google.com/appengine/docs/java/javadoc/com/google/appengine/api/urlfetch/package-summary [logger]: https://docs.oracle.com/javase/7/docs/api/java/util/logging/Logger.html diff --git a/google-http-client-apache-v5/pom.xml b/google-http-client-apache-v5/pom.xml new file mode 100644 index 000000000..a67897975 --- /dev/null +++ b/google-http-client-apache-v5/pom.xml @@ -0,0 +1,116 @@ + + 4.0.0 + + com.google.http-client + google-http-client-parent + 1.44.3-SNAPSHOT + ../pom.xml + + google-http-client-apache-v5 + 1.44.3-SNAPSHOT + Apache HTTP transport v5 for the Google HTTP Client Library for Java. + + + + + maven-javadoc-plugin + + + https://download.oracle.com/javase/7/docs/api/ + + ${project.name} ${project.version} + ${project.artifactId} ${project.version} + + + + maven-source-plugin + + + org.codehaus.mojo + build-helper-maven-plugin + 3.3.0 + + + add-test-source + generate-test-sources + + add-test-source + + + + target/generated-test-sources + + + + + + + maven-jar-plugin + + + ${project.build.outputDirectory}/META-INF/MANIFEST.MF + + com.google.api.client.http.apache.v5 + + + + + + org.apache.felix + maven-bundle-plugin + 5.1.9 + + + bundle-manifest + process-classes + + manifest + + + + + + maven-compiler-plugin + 3.13.0 + + 1.8 + 1.8 + + + + + + + com.google.http-client + google-http-client + + + org.apache.httpcomponents + httpcore + + + org.apache.httpcomponents + httpclient + + + + + com.google.guava + guava + + + org.apache.httpcomponents.client5 + httpclient5 + + + org.apache.httpcomponents.core5 + httpcore5 + + + junit + junit + test + + + diff --git a/google-http-client-apache-v5/src/main/java/com/google/api/client/http/apache/v5/Apache5ContentEntity.java b/google-http-client-apache-v5/src/main/java/com/google/api/client/http/apache/v5/Apache5ContentEntity.java new file mode 100644 index 000000000..4a5ab84e6 --- /dev/null +++ b/google-http-client-apache-v5/src/main/java/com/google/api/client/http/apache/v5/Apache5ContentEntity.java @@ -0,0 +1,79 @@ +/* + * Copyright 2024 Google LLC + * + * 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. + */ + +package com.google.api.client.http.apache.v5; + +import com.google.api.client.util.Preconditions; +import com.google.api.client.util.StreamingContent; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import org.apache.hc.core5.http.io.entity.AbstractHttpEntity; + +/** + * Translation class to make google-http-client entity conform with Apache 5.x {@link + * AbstractHttpEntity} + */ +final class Apache5ContentEntity extends AbstractHttpEntity { + + /** Content length or less than zero if not known. */ + private final long contentLength; + + /** Streaming content. */ + private final StreamingContent streamingContent; + + /** + * @param contentLength content length or less than zero if not known + * @param streamingContent streaming content + */ + Apache5ContentEntity( + long contentLength, + StreamingContent streamingContent, + String contentType, + String contentEncoding) { + super(contentType, contentEncoding, contentLength == -1); + this.contentLength = contentLength; + this.streamingContent = Preconditions.checkNotNull(streamingContent); + } + + @Override + public InputStream getContent() { + throw new UnsupportedOperationException(); + } + + @Override + public long getContentLength() { + return contentLength; + } + + @Override + public boolean isRepeatable() { + return false; + } + + @Override + public boolean isStreaming() { + return true; + } + + @Override + public void writeTo(OutputStream out) throws IOException { + if (contentLength != 0) { + streamingContent.writeTo(out); + } + } + + @Override + public void close() throws IOException {} +} diff --git a/google-http-client-apache-v5/src/main/java/com/google/api/client/http/apache/v5/Apache5HttpRequest.java b/google-http-client-apache-v5/src/main/java/com/google/api/client/http/apache/v5/Apache5HttpRequest.java new file mode 100644 index 000000000..99d6eca8e --- /dev/null +++ b/google-http-client-apache-v5/src/main/java/com/google/api/client/http/apache/v5/Apache5HttpRequest.java @@ -0,0 +1,78 @@ +/* + * Copyright 2024 Google LLC + * + * 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. + */ + +package com.google.api.client.http.apache.v5; + +import com.google.api.client.http.LowLevelHttpRequest; +import com.google.api.client.http.LowLevelHttpResponse; +import java.io.IOException; +import java.util.concurrent.TimeUnit; +import org.apache.hc.client5.http.ClientProtocolException; +import org.apache.hc.client5.http.classic.HttpClient; +import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase; +import org.apache.hc.client5.http.config.RequestConfig; +import org.apache.hc.client5.http.routing.RoutingSupport; +import org.apache.hc.core5.http.ClassicHttpResponse; +import org.apache.hc.core5.http.HttpException; +import org.apache.hc.core5.http.HttpHost; +import org.apache.hc.core5.util.Timeout; + +public final class Apache5HttpRequest extends LowLevelHttpRequest { + + private final HttpUriRequestBase request; + + private final RequestConfig.Builder requestConfig; + + private final HttpClient httpClient; + + Apache5HttpRequest(HttpClient httpClient, HttpUriRequestBase request) { + this.httpClient = httpClient; + this.request = request; + // disable redirects as google-http-client handles redirects + this.requestConfig = RequestConfig.custom().setRedirectsEnabled(false); + } + + @Override + public void addHeader(String name, String value) { + request.addHeader(name, value); + } + + @Override + public void setTimeout(int connectTimeout, int readTimeout) throws IOException { + requestConfig + .setConnectTimeout(Timeout.of(connectTimeout, TimeUnit.MILLISECONDS)) + // ResponseTimeout behaves the same as 4.x's SocketTimeout + .setResponseTimeout(Timeout.of(readTimeout, TimeUnit.MILLISECONDS)); + } + + @Override + public LowLevelHttpResponse execute() throws IOException { + if (getStreamingContent() != null) { + Apache5ContentEntity entity = + new Apache5ContentEntity( + getContentLength(), getStreamingContent(), getContentType(), getContentEncoding()); + request.setEntity(entity); + } + request.setConfig(requestConfig.build()); + HttpHost target; + try { + target = RoutingSupport.determineHost(request); + } catch (HttpException e) { + throw new ClientProtocolException("The request's host is invalid.", e); + } + // we use a null context so the client creates the default one internally + ClassicHttpResponse httpResponse = httpClient.executeOpen(target, request, null); + return new Apache5HttpResponse(request, httpResponse); + } +} diff --git a/google-http-client-apache-v5/src/main/java/com/google/api/client/http/apache/v5/Apache5HttpResponse.java b/google-http-client-apache-v5/src/main/java/com/google/api/client/http/apache/v5/Apache5HttpResponse.java new file mode 100644 index 000000000..1574c8c89 --- /dev/null +++ b/google-http-client-apache-v5/src/main/java/com/google/api/client/http/apache/v5/Apache5HttpResponse.java @@ -0,0 +1,102 @@ +/* + * Copyright 2024 Google LLC + * + * 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. + */ + +package com.google.api.client.http.apache.v5; + +import com.google.api.client.http.LowLevelHttpResponse; +import java.io.IOException; +import java.io.InputStream; +import java.util.logging.Logger; +import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase; +import org.apache.hc.core5.http.ClassicHttpResponse; +import org.apache.hc.core5.http.Header; +import org.apache.hc.core5.http.HttpEntity; +import org.apache.hc.core5.http.message.StatusLine; + +final class Apache5HttpResponse extends LowLevelHttpResponse { + + private static final Logger LOGGER = Logger.getLogger(Apache5HttpResponse.class.getName()); + private final HttpUriRequestBase request; + private final ClassicHttpResponse response; + private final Header[] allHeaders; + private final HttpEntity entity; + + Apache5HttpResponse(HttpUriRequestBase request, ClassicHttpResponse response) { + this.request = request; + this.response = response; + this.allHeaders = response.getHeaders(); + this.entity = response.getEntity(); + } + + @Override + public int getStatusCode() { + return response.getCode(); + } + + @Override + public InputStream getContent() throws IOException { + return new Apache5ResponseContent(entity.getContent(), response); + } + + @Override + public String getContentEncoding() { + return entity != null ? entity.getContentEncoding() : null; + } + + @Override + public long getContentLength() { + return entity == null ? -1 : entity.getContentLength(); + } + + @Override + public String getContentType() { + return entity == null ? null : entity.getContentType(); + } + + @Override + public String getReasonPhrase() { + return response.getReasonPhrase(); + } + + @Override + public String getStatusLine() { + return new StatusLine(response).toString(); + } + + public String getHeaderValue(String name) { + return response.getLastHeader(name).getValue(); + } + + @Override + public int getHeaderCount() { + return allHeaders.length; + } + + @Override + public String getHeaderName(int index) { + return allHeaders[index].getName(); + } + + @Override + public String getHeaderValue(int index) { + return allHeaders[index].getValue(); + } + + /** Aborts execution of the request. */ + @Override + public void disconnect() throws IOException { + request.abort(); + response.close(); + } +} diff --git a/google-http-client-apache-v5/src/main/java/com/google/api/client/http/apache/v5/Apache5HttpTransport.java b/google-http-client-apache-v5/src/main/java/com/google/api/client/http/apache/v5/Apache5HttpTransport.java new file mode 100644 index 000000000..868a2cf93 --- /dev/null +++ b/google-http-client-apache-v5/src/main/java/com/google/api/client/http/apache/v5/Apache5HttpTransport.java @@ -0,0 +1,221 @@ +/* + * Copyright 2024 Google LLC + * + * 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. + */ + +package com.google.api.client.http.apache.v5; + +import com.google.api.client.http.HttpMethods; +import com.google.api.client.http.HttpTransport; +import com.google.common.annotations.Beta; +import com.google.common.base.Preconditions; +import java.io.IOException; +import java.net.ProxySelector; +import java.net.URI; +import java.util.concurrent.TimeUnit; +import org.apache.hc.client5.http.classic.HttpClient; +import org.apache.hc.client5.http.classic.methods.HttpDelete; +import org.apache.hc.client5.http.classic.methods.HttpGet; +import org.apache.hc.client5.http.classic.methods.HttpHead; +import org.apache.hc.client5.http.classic.methods.HttpOptions; +import org.apache.hc.client5.http.classic.methods.HttpPatch; +import org.apache.hc.client5.http.classic.methods.HttpPost; +import org.apache.hc.client5.http.classic.methods.HttpPut; +import org.apache.hc.client5.http.classic.methods.HttpTrace; +import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase; +import org.apache.hc.client5.http.config.ConnectionConfig; +import org.apache.hc.client5.http.impl.classic.HttpClientBuilder; +import org.apache.hc.client5.http.impl.classic.HttpClients; +import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager; +import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder; +import org.apache.hc.client5.http.impl.routing.SystemDefaultRoutePlanner; +import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory; +import org.apache.hc.core5.io.CloseMode; +import org.apache.hc.core5.io.ModalCloseable; + +/** + * Thread-safe HTTP transport based on the Apache HTTP Client library. + * + *

Implementation is thread-safe, as long as any parameter modification to the {@link + * #getHttpClient() Apache HTTP Client} is only done at initialization time. For maximum efficiency, + * applications should use a single globally-shared instance of the HTTP transport. + * + *

Default settings are specified in {@link #newDefaultHttpClient()}. Use the {@link + * #Apache5HttpTransport(HttpClient)} constructor to override the Apache HTTP Client used. Please + * read the + * Apache HTTP Client 5.x configuration example for more complex configuration options. + */ +public final class Apache5HttpTransport extends HttpTransport { + + /** Apache HTTP client. */ + private final HttpClient httpClient; + + /** If the HTTP client uses mTLS channel. */ + private final boolean isMtls; + + /** Constructor that uses {@link #newDefaultHttpClient()} for the Apache HTTP client. */ + public Apache5HttpTransport() { + this(newDefaultHttpClient(), false); + } + + /** + * Constructor that allows an alternative Apache HTTP client to be used. + * + *

If you choose to provide your own Apache HttpClient implementation, be sure that + * + *

+ * + * @param httpClient Apache HTTP client to use + */ + public Apache5HttpTransport(HttpClient httpClient) { + this.httpClient = httpClient; + this.isMtls = false; + } + + /** + * {@link Beta}
+ * Constructor that allows an alternative CLoseable Apache HTTP client to be used. + * + *

If you choose to provide your own Apache HttpClient implementation, be sure that + * + *

+ * + * @param httpClient Apache HTTP client to use + * @param isMtls If the HTTP client is mutual TLS + */ + @Beta + public Apache5HttpTransport(HttpClient httpClient, boolean isMtls) { + this.httpClient = httpClient; + this.isMtls = isMtls; + } + + /** + * Creates a new instance of the Apache HTTP client that is used by the {@link + * #Apache5HttpTransport()} constructor. + * + *

Settings: + * + *

+ * + * @return new instance of the Apache HTTP client + */ + public static HttpClient newDefaultHttpClient() { + return newDefaultHttpClientBuilder().build(); + } + + /** + * Creates a new Apache HTTP client builder that is used by the {@link #Apache5HttpTransport()} + * constructor. + * + *

Settings: + * + *

+ * + * @return new instance of the Apache HTTP client builder + */ + public static HttpClientBuilder newDefaultHttpClientBuilder() { + PoolingHttpClientConnectionManager connectionManager = + PoolingHttpClientConnectionManagerBuilder.create() + .setSSLSocketFactory(SSLConnectionSocketFactory.getSocketFactory()) + .setMaxConnTotal(200) + .setMaxConnPerRoute(20) + .setDefaultConnectionConfig( + ConnectionConfig.custom().setTimeToLive(-1, TimeUnit.MILLISECONDS).build()) + .build(); + + return HttpClients.custom() + .useSystemProperties() + .setConnectionManager(connectionManager) + .setRoutePlanner(new SystemDefaultRoutePlanner(ProxySelector.getDefault())) + .disableRedirectHandling() + .disableAutomaticRetries(); + } + + @Override + public boolean supportsMethod(String method) { + return true; + } + + @Override + protected Apache5HttpRequest buildRequest(String method, String url) { + HttpUriRequestBase requestBase; + if (method.equals(HttpMethods.DELETE)) { + requestBase = new HttpDelete(url); + } else if (method.equals(HttpMethods.GET)) { + requestBase = new HttpGet(url); + } else if (method.equals(HttpMethods.HEAD)) { + requestBase = new HttpHead(url); + } else if (method.equals(HttpMethods.PATCH)) { + requestBase = new HttpPatch(url); + } else if (method.equals(HttpMethods.POST)) { + requestBase = new HttpPost(url); + } else if (method.equals(HttpMethods.PUT)) { + requestBase = new HttpPut(url); + } else if (method.equals(HttpMethods.TRACE)) { + requestBase = new HttpTrace(url); + } else if (method.equals(HttpMethods.OPTIONS)) { + requestBase = new HttpOptions(url); + } else { + requestBase = new HttpUriRequestBase(Preconditions.checkNotNull(method), URI.create(url)); + } + return new Apache5HttpRequest(httpClient, requestBase); + } + + /** + * Gracefully shuts down the connection manager and releases allocated resources. This closes all + * connections, whether they are currently used or not. + */ + @Override + public void shutdown() throws IOException { + if (httpClient instanceof ModalCloseable) { + ((ModalCloseable) httpClient).close(CloseMode.GRACEFUL); + } + // otherwise no-op + } + + /** Returns the Apache HTTP client. */ + public HttpClient getHttpClient() { + return httpClient; + } + + /** Returns if the underlying HTTP client is mTLS. */ + @Override + public boolean isMtls() { + return isMtls; + } +} diff --git a/google-http-client-apache-v5/src/main/java/com/google/api/client/http/apache/v5/Apache5ResponseContent.java b/google-http-client-apache-v5/src/main/java/com/google/api/client/http/apache/v5/Apache5ResponseContent.java new file mode 100644 index 000000000..c2d3091df --- /dev/null +++ b/google-http-client-apache-v5/src/main/java/com/google/api/client/http/apache/v5/Apache5ResponseContent.java @@ -0,0 +1,75 @@ +package com.google.api.client.http.apache.v5; + +import com.google.common.annotations.VisibleForTesting; +import java.io.IOException; +import java.io.InputStream; +import org.apache.hc.core5.http.ClassicHttpResponse; +import org.apache.hc.core5.http.HttpResponse; + +/** + * Class that wraps an {@link org.apache.hc.core5.http.HttpEntity}'s content {@link InputStream} + * along with the {@link ClassicHttpResponse} that contains this entity. The main purpose is to be + * able to close the response as well as the content input stream when {@link #close()} is called, + * in order to not break the existing contract with clients using apache v4 that only required them + * to close the input stream to clean up all resources. + */ +public class Apache5ResponseContent extends InputStream { + private final ClassicHttpResponse response; + private final InputStream wrappedStream; + + public Apache5ResponseContent(InputStream wrappedStream, ClassicHttpResponse response) { + this.response = response; + this.wrappedStream = wrappedStream; + } + + @Override + public int read() throws IOException { + return wrappedStream.read(); + } + + @Override + public int read(byte b[]) throws IOException { + return wrappedStream.read(b); + } + + @Override + public int read(byte b[], int off, int len) throws IOException { + return wrappedStream.read(b, off, len); + } + + @Override + public long skip(long n) throws IOException { + return wrappedStream.skip(n); + } + + @Override + public int available() throws IOException { + return wrappedStream.available(); + } + + @Override + public synchronized void mark(int readlimit) { + wrappedStream.mark(readlimit); + } + + @Override + public synchronized void reset() throws IOException { + wrappedStream.reset(); + } + + @Override + public void close() throws IOException { + wrappedStream.close(); + response.close(); + } + + @Override + public boolean markSupported() { + return wrappedStream.markSupported(); + } + + @VisibleForTesting + HttpResponse getResponse() { + return response; + } +} diff --git a/google-http-client-apache-v5/src/main/java/com/google/api/client/http/apache/v5/package-info.java b/google-http-client-apache-v5/src/main/java/com/google/api/client/http/apache/v5/package-info.java new file mode 100644 index 000000000..223edc82d --- /dev/null +++ b/google-http-client-apache-v5/src/main/java/com/google/api/client/http/apache/v5/package-info.java @@ -0,0 +1,16 @@ +/* + * Copyright 2024 Google LLC + * + * 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. + */ + +/** HTTP Transport library for Google API's based on Apache HTTP Client/Core version 5.x */ +package com.google.api.client.http.apache.v5; diff --git a/google-http-client-apache-v5/src/test/java/com/google/api/client/http/apache/v5/Apache5HttpRequestTest.java b/google-http-client-apache-v5/src/test/java/com/google/api/client/http/apache/v5/Apache5HttpRequestTest.java new file mode 100644 index 000000000..3b7ca4a21 --- /dev/null +++ b/google-http-client-apache-v5/src/test/java/com/google/api/client/http/apache/v5/Apache5HttpRequestTest.java @@ -0,0 +1,131 @@ +/* + * Copyright 2019 Google LLC + * + * 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. + */ + +package com.google.api.client.http.apache.v5; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import com.google.api.client.http.ByteArrayContent; +import com.google.api.client.http.HttpContent; +import com.google.api.client.http.InputStreamContent; +import com.google.api.client.http.LowLevelHttpResponse; +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.concurrent.atomic.AtomicInteger; +import org.apache.hc.client5.http.classic.methods.HttpPost; +import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase; +import org.apache.hc.core5.http.ClassicHttpRequest; +import org.apache.hc.core5.http.ClassicHttpResponse; +import org.apache.hc.core5.http.ContentType; +import org.apache.hc.core5.http.HttpEntity; +import org.apache.hc.core5.http.HttpHost; +import org.apache.hc.core5.http.io.entity.BasicHttpEntity; +import org.apache.hc.core5.http.protocol.HttpContext; +import org.junit.Test; + +public class Apache5HttpRequestTest { + @Test + public void testContentLengthSet() throws Exception { + HttpUriRequestBase base = new HttpPost("http://www.google.com"); + Apache5HttpRequest request = + new Apache5HttpRequest( + new MockHttpClient() { + @Override + public ClassicHttpResponse executeOpen( + HttpHost target, ClassicHttpRequest request, HttpContext context) { + return new MockClassicHttpResponse(); + } + }, + base); + HttpContent content = + new ByteArrayContent("text/plain", "sample".getBytes(StandardCharsets.UTF_8)); + request.setStreamingContent(content); + request.setContentLength(content.getLength()); + request.execute(); + + assertFalse(base.getEntity().isChunked()); + assertEquals(6, base.getEntity().getContentLength()); + } + + @Test + public void testChunked() throws Exception { + byte[] buf = new byte[300]; + Arrays.fill(buf, (byte) ' '); + HttpUriRequestBase base = new HttpPost("http://www.google.com"); + Apache5HttpRequest request = + new Apache5HttpRequest( + new MockHttpClient() { + @Override + public ClassicHttpResponse executeOpen( + HttpHost target, ClassicHttpRequest request, HttpContext context) { + return new MockClassicHttpResponse(); + } + }, + base); + HttpContent content = new InputStreamContent("text/plain", new ByteArrayInputStream(buf)); + request.setStreamingContent(content); + request.execute(); + + assertTrue(base.getEntity().isChunked()); + assertEquals(-1, base.getEntity().getContentLength()); + } + + @Test + public void testExecute_closeContent_closesResponse() throws Exception { + HttpUriRequestBase base = new HttpPost("http://www.google.com"); + final InputStream responseContentStream = new ByteArrayInputStream(new byte[] {1, 2, 3}); + BasicHttpEntity testEntity = + new BasicHttpEntity(responseContentStream, ContentType.DEFAULT_BINARY); + AtomicInteger closedResponseCounter = new AtomicInteger(0); + ClassicHttpResponse classicResponse = + new MockClassicHttpResponse() { + @Override + public HttpEntity getEntity() { + return testEntity; + } + + @Override + public void close() { + closedResponseCounter.incrementAndGet(); + } + }; + + Apache5HttpRequest request = + new Apache5HttpRequest( + new MockHttpClient() { + @Override + public ClassicHttpResponse executeOpen( + HttpHost target, ClassicHttpRequest request, HttpContext context) { + return classicResponse; + } + }, + base); + LowLevelHttpResponse response = request.execute(); + assertTrue(response instanceof Apache5HttpResponse); + + // we confirm that the classic response we prepared in this test is the same as the content's + // response + assertTrue(response.getContent() instanceof Apache5ResponseContent); + assertEquals(classicResponse, ((Apache5ResponseContent) response.getContent()).getResponse()); + + // we close the response's content stream and confirm the response is also closed + assertEquals(0, closedResponseCounter.get()); + response.getContent().close(); + assertEquals(1, closedResponseCounter.get()); + } +} diff --git a/google-http-client-apache-v5/src/test/java/com/google/api/client/http/apache/v5/Apache5HttpTransportTest.java b/google-http-client-apache-v5/src/test/java/com/google/api/client/http/apache/v5/Apache5HttpTransportTest.java new file mode 100644 index 000000000..99045d99d --- /dev/null +++ b/google-http-client-apache-v5/src/test/java/com/google/api/client/http/apache/v5/Apache5HttpTransportTest.java @@ -0,0 +1,353 @@ +/* + * Copyright 2019 Google LLC + * + * 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. + */ + +package com.google.api.client.http.apache.v5; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.junit.Assume.assumeTrue; + +import com.google.api.client.http.GenericUrl; +import com.google.api.client.http.HttpResponseException; +import com.google.api.client.http.HttpTransport; +import com.google.api.client.http.LowLevelHttpResponse; +import com.google.api.client.util.ByteArrayStreamingContent; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import org.apache.hc.client5.http.ConnectTimeoutException; +import org.apache.hc.client5.http.HttpHostConnectException; +import org.apache.hc.client5.http.classic.HttpClient; +import org.apache.hc.client5.http.impl.classic.HttpClients; +import org.apache.hc.core5.http.ClassicHttpRequest; +import org.apache.hc.core5.http.ClassicHttpResponse; +import org.apache.hc.core5.http.ContentType; +import org.apache.hc.core5.http.EntityDetails; +import org.apache.hc.core5.http.Header; +import org.apache.hc.core5.http.HttpException; +import org.apache.hc.core5.http.HttpHeaders; +import org.apache.hc.core5.http.HttpHost; +import org.apache.hc.core5.http.HttpRequest; +import org.apache.hc.core5.http.HttpRequestInterceptor; +import org.apache.hc.core5.http.HttpRequestMapper; +import org.apache.hc.core5.http.HttpResponse; +import org.apache.hc.core5.http.HttpStatus; +import org.apache.hc.core5.http.impl.bootstrap.HttpServer; +import org.apache.hc.core5.http.impl.io.HttpRequestExecutor; +import org.apache.hc.core5.http.impl.io.HttpService; +import org.apache.hc.core5.http.io.HttpClientConnection; +import org.apache.hc.core5.http.io.HttpRequestHandler; +import org.apache.hc.core5.http.io.entity.ByteArrayEntity; +import org.apache.hc.core5.http.io.support.BasicHttpServerRequestHandler; +import org.apache.hc.core5.http.protocol.HttpContext; +import org.apache.hc.core5.http.protocol.HttpProcessor; +import org.junit.Assert; +import org.junit.Test; + +/** Tests {@link Apache5HttpTransport}. */ +public class Apache5HttpTransportTest { + + @Test + public void testApacheHttpTransport() { + Apache5HttpTransport transport = new Apache5HttpTransport(); + checkHttpTransport(transport); + assertFalse(transport.isMtls()); + } + + @Test + public void testApacheHttpTransportWithParam() { + Apache5HttpTransport transport = new Apache5HttpTransport(HttpClients.custom().build(), true); + checkHttpTransport(transport); + assertTrue(transport.isMtls()); + } + + @Test + public void testNewDefaultHttpClient() { + HttpClient client = Apache5HttpTransport.newDefaultHttpClient(); + checkHttpClient(client); + } + + private void checkHttpTransport(Apache5HttpTransport transport) { + assertNotNull(transport); + HttpClient client = transport.getHttpClient(); + checkHttpClient(client); + } + + private void checkHttpClient(HttpClient client) { + assertNotNull(client); + // TODO(chingor): Is it possible to test this effectively? The newer HttpClient implementations + // are read-only and we're testing that we built the client with the right configuration + } + + @Test + public void testRequestsWithContent() throws IOException { + // This test confirms that we can set the content on any type of request + HttpClient mockClient = + new MockHttpClient() { + @Override + public ClassicHttpResponse executeOpen( + HttpHost target, ClassicHttpRequest request, HttpContext context) { + return new MockClassicHttpResponse(); + } + }; + Apache5HttpTransport transport = new Apache5HttpTransport(mockClient); + + // Test GET. + execute(transport.buildRequest("GET", "http://www.test.url")); + // Test DELETE. + execute(transport.buildRequest("DELETE", "http://www.test.url")); + // Test HEAD. + execute(transport.buildRequest("HEAD", "http://www.test.url")); + + // Test PATCH. + execute(transport.buildRequest("PATCH", "http://www.test.url")); + // Test PUT. + execute(transport.buildRequest("PUT", "http://www.test.url")); + // Test POST. + execute(transport.buildRequest("POST", "http://www.test.url")); + // Test PATCH. + execute(transport.buildRequest("PATCH", "http://www.test.url")); + } + + private void execute(Apache5HttpRequest request) throws IOException { + byte[] bytes = "abc".getBytes(StandardCharsets.UTF_8); + request.setStreamingContent(new ByteArrayStreamingContent(bytes)); + request.setContentType("text/html"); + request.setContentLength(bytes.length); + request.execute(); + } + + @Test + public void testRequestShouldNotFollowRedirects() throws IOException { + final AtomicInteger requestsAttempted = new AtomicInteger(0); + HttpRequestExecutor requestExecutor = + new HttpRequestExecutor() { + @Override + public ClassicHttpResponse execute( + ClassicHttpRequest request, HttpClientConnection connection, HttpContext context) + throws IOException, HttpException { + ClassicHttpResponse response = new MockClassicHttpResponse(); + response.setCode(302); + response.setReasonPhrase(null); + response.addHeader("location", "https://google.com/path"); + response.addHeader(HttpHeaders.SET_COOKIE, ""); + requestsAttempted.incrementAndGet(); + return response; + } + }; + HttpClient client = HttpClients.custom().setRequestExecutor(requestExecutor).build(); + Apache5HttpTransport transport = new Apache5HttpTransport(client); + Apache5HttpRequest request = transport.buildRequest("GET", "https://google.com"); + LowLevelHttpResponse response = request.execute(); + assertEquals(1, requestsAttempted.get()); + assertEquals(302, response.getStatusCode()); + } + + @Test + public void testRequestCanSetHeaders() { + final AtomicBoolean interceptorCalled = new AtomicBoolean(false); + HttpClient client = + HttpClients.custom() + .addRequestInterceptorFirst( + new HttpRequestInterceptor() { + @Override + public void process( + HttpRequest request, EntityDetails details, HttpContext context) + throws HttpException, IOException { + Header header = request.getFirstHeader("foo"); + assertNotNull("Should have found header", header); + assertEquals("bar", header.getValue()); + interceptorCalled.set(true); + throw new IOException("cancelling request"); + } + }) + .build(); + + Apache5HttpTransport transport = new Apache5HttpTransport(client); + Apache5HttpRequest request = transport.buildRequest("GET", "https://google.com"); + request.addHeader("foo", "bar"); + try { + LowLevelHttpResponse response = request.execute(); + fail("should not actually make the request"); + } catch (IOException exception) { + assertEquals("cancelling request", exception.getMessage()); + } + assertTrue("Expected to have called our test interceptor", interceptorCalled.get()); + } + + @Test(timeout = 10_000L) + public void testConnectTimeout() { + // TODO(chanseok): Java 17 returns an IOException (SocketException: Network is unreachable). + // Figure out a way to verify connection timeout works on Java 17+. + assumeTrue(System.getProperty("java.version").compareTo("17") < 0); + + HttpTransport httpTransport = new Apache5HttpTransport(); + GenericUrl url = new GenericUrl("http://google.com:81"); + try { + httpTransport.createRequestFactory().buildGetRequest(url).setConnectTimeout(100).execute(); + fail("should have thrown an exception"); + } catch (HttpHostConnectException | ConnectTimeoutException expected) { + // expected + } catch (IOException e) { + fail("unexpected IOException: " + e.getClass().getName() + ": " + e.getMessage()); + } + } + + private static class FakeServer implements AutoCloseable { + private final HttpServer server; + + FakeServer(final HttpRequestHandler httpHandler) throws IOException { + HttpRequestMapper mapper = + new HttpRequestMapper() { + @Override + public HttpRequestHandler resolve(HttpRequest request, HttpContext context) + throws HttpException { + return httpHandler; + }; + }; + server = + new HttpServer( + 0, + HttpService.builder() + .withHttpProcessor( + new HttpProcessor() { + @Override + public void process( + HttpRequest request, EntityDetails entity, HttpContext context) + throws HttpException, IOException {} + + @Override + public void process( + HttpResponse response, EntityDetails entity, HttpContext context) + throws HttpException, IOException {} + }) + .withHttpServerRequestHandler(new BasicHttpServerRequestHandler(mapper)) + .build(), + null, + null, + null, + null, + null, + null); + // server.createContext("/", httpHandler); + server.start(); + } + + public int getPort() { + return server.getLocalPort(); + } + + @Override + public void close() { + server.initiateShutdown(); + } + } + + @Test + public void testNormalizedUrl() throws IOException { + final HttpRequestHandler handler = + new HttpRequestHandler() { + @Override + public void handle( + ClassicHttpRequest request, ClassicHttpResponse response, HttpContext context) + throws HttpException, IOException { + // Extract the request URI and convert to bytes + byte[] responseData = request.getRequestUri().getBytes(StandardCharsets.UTF_8); + + // Set the response headers (status code and content length) + response.setCode(HttpStatus.SC_OK); + response.setHeader(HttpHeaders.CONTENT_LENGTH, String.valueOf(responseData.length)); + + // Set the response entity (body) + ByteArrayEntity entity = new ByteArrayEntity(responseData, ContentType.TEXT_PLAIN); + response.setEntity(entity); + } + }; + try (FakeServer server = new FakeServer(handler)) { + HttpTransport transport = new Apache5HttpTransport(); + GenericUrl testUrl = new GenericUrl("http://localhost/foo//bar"); + testUrl.setPort(server.getPort()); + com.google.api.client.http.HttpResponse response = + transport.createRequestFactory().buildGetRequest(testUrl).execute(); + assertEquals(200, response.getStatusCode()); + assertEquals("/foo//bar", response.parseAsString()); + } + } + + @Test + public void testReadErrorStream() throws IOException { + final HttpRequestHandler handler = + new HttpRequestHandler() { + @Override + public void handle( + ClassicHttpRequest request, ClassicHttpResponse response, HttpContext context) + throws HttpException, IOException { + byte[] responseData = "Forbidden".getBytes(StandardCharsets.UTF_8); + response.setCode(HttpStatus.SC_FORBIDDEN); // 403 Forbidden + response.setHeader(HttpHeaders.CONTENT_LENGTH, String.valueOf(responseData.length)); + ByteArrayEntity entity = new ByteArrayEntity(responseData, ContentType.TEXT_PLAIN); + response.setEntity(entity); + } + }; + try (FakeServer server = new FakeServer(handler)) { + HttpTransport transport = new Apache5HttpTransport(); + GenericUrl testUrl = new GenericUrl("http://localhost/foo//bar"); + testUrl.setPort(server.getPort()); + com.google.api.client.http.HttpRequest getRequest = + transport.createRequestFactory().buildGetRequest(testUrl); + getRequest.setThrowExceptionOnExecuteError(false); + com.google.api.client.http.HttpResponse response = getRequest.execute(); + assertEquals(403, response.getStatusCode()); + assertEquals("Forbidden", response.parseAsString()); + } + } + + @Test + public void testReadErrorStream_withException() throws IOException { + final HttpRequestHandler handler = + new HttpRequestHandler() { + @Override + public void handle( + ClassicHttpRequest request, ClassicHttpResponse response, HttpContext context) + throws HttpException, IOException { + byte[] responseData = "Forbidden".getBytes(StandardCharsets.UTF_8); + response.setCode(HttpStatus.SC_FORBIDDEN); // 403 Forbidden + response.setHeader(HttpHeaders.CONTENT_LENGTH, String.valueOf(responseData.length)); + ByteArrayEntity entity = new ByteArrayEntity(responseData, ContentType.TEXT_PLAIN); + response.setEntity(entity); + } + }; + try (FakeServer server = new FakeServer(handler)) { + HttpTransport transport = new Apache5HttpTransport(); + GenericUrl testUrl = new GenericUrl("http://localhost/foo//bar"); + testUrl.setPort(server.getPort()); + com.google.api.client.http.HttpRequest getRequest = + transport.createRequestFactory().buildGetRequest(testUrl); + try { + getRequest.execute(); + Assert.fail(); + } catch (HttpResponseException ex) { + assertEquals("Forbidden", ex.getContent()); + } + } + } + + private boolean isWindows() { + return System.getProperty("os.name").startsWith("Windows"); + } +} diff --git a/google-http-client-apache-v5/src/test/java/com/google/api/client/http/apache/v5/MockClassicHttpResponse.java b/google-http-client-apache-v5/src/test/java/com/google/api/client/http/apache/v5/MockClassicHttpResponse.java new file mode 100644 index 000000000..091721745 --- /dev/null +++ b/google-http-client-apache-v5/src/test/java/com/google/api/client/http/apache/v5/MockClassicHttpResponse.java @@ -0,0 +1,182 @@ +package com.google.api.client.http.apache.v5; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.stream.Collectors; +import org.apache.hc.core5.http.ClassicHttpResponse; +import org.apache.hc.core5.http.Header; +import org.apache.hc.core5.http.HttpEntity; +import org.apache.hc.core5.http.HttpVersion; +import org.apache.hc.core5.http.ProtocolException; +import org.apache.hc.core5.http.ProtocolVersion; + +public class MockClassicHttpResponse implements ClassicHttpResponse { + List
headers = new ArrayList<>(); + int code = 200; + + @Override + public int getCode() { + return code; + } + + @Override + public void setCode(int code) { + this.code = code; + } + + @Override + public String getReasonPhrase() { + return null; + } + + @Override + public void setReasonPhrase(String reason) {} + + @Override + public Locale getLocale() { + return null; + } + + @Override + public void setLocale(Locale loc) {} + + @Override + public void setVersion(ProtocolVersion version) {} + + @Override + public ProtocolVersion getVersion() { + return HttpVersion.HTTP_1_1; + } + + @Override + public void addHeader(Header header) { + headers.add(header); + } + + @Override + public void addHeader(String name, Object value) { + addHeader(newHeader(name, value)); + } + + private Header newHeader(String key, Object value) { + return new Header() { + @Override + public boolean isSensitive() { + return false; + } + + @Override + public String getName() { + return key; + } + + @Override + public String getValue() { + return value.toString(); + } + }; + } + + @Override + public void setHeader(Header header) { + if (headers.contains(header)) { + int index = headers.indexOf(header); + headers.set(index, header); + } else { + addHeader(header); + } + } + + @Override + public void setHeader(String name, Object value) { + setHeader(newHeader(name, value)); + } + + @Override + public void setHeaders(Header... headers) { + for (Header header : headers) { + setHeader(header); + } + } + + @Override + public boolean removeHeader(Header header) { + if (headers.contains(header)) { + headers.remove(headers.indexOf(header)); + return true; + } + return false; + } + + @Override + public boolean removeHeaders(String name) { + int initialSize = headers.size(); + for (Header header : + headers.stream().filter(h -> h.getName() == name).collect(Collectors.toList())) { + removeHeader(header); + } + return headers.size() < initialSize; + } + + @Override + public boolean containsHeader(String name) { + return headers.stream().anyMatch(h -> h.getName() == name); + } + + @Override + public int countHeaders(String name) { + return headers.size(); + } + + @Override + public Header getFirstHeader(String name) { + return headers.stream().findFirst().orElse(null); + } + + @Override + public Header getHeader(String name) throws ProtocolException { + return headers.stream().filter(h -> h.getName() == name).findFirst().orElse(null); + } + + @Override + public Header[] getHeaders() { + return headers.toArray(new Header[0]); + } + + @Override + public Header[] getHeaders(String name) { + return headers.stream() + .filter(h -> h.getName() == name) + .collect(Collectors.toList()) + .toArray(new Header[0]); + } + + @Override + public Header getLastHeader(String name) { + return headers.isEmpty() ? null : headers.get(headers.size() - 1); + } + + @Override + public Iterator
headerIterator() { + return headers.iterator(); + } + + @Override + public Iterator
headerIterator(String name) { + return headers.stream().filter(h -> h.getName() == name).iterator(); + } + + @Override + public void close() throws IOException {} + + @Override + public HttpEntity getEntity() { + return null; + } + + @Override + public void setEntity(HttpEntity entity) {} +} diff --git a/google-http-client-apache-v5/src/test/java/com/google/api/client/http/apache/v5/MockHttpClient.java b/google-http-client-apache-v5/src/test/java/com/google/api/client/http/apache/v5/MockHttpClient.java new file mode 100644 index 000000000..8d26096cf --- /dev/null +++ b/google-http-client-apache-v5/src/test/java/com/google/api/client/http/apache/v5/MockHttpClient.java @@ -0,0 +1,86 @@ +package com.google.api.client.http.apache.v5; + +import com.google.api.client.util.Preconditions; +import java.io.IOException; +import org.apache.hc.client5.http.classic.HttpClient; +import org.apache.hc.core5.http.ClassicHttpRequest; +import org.apache.hc.core5.http.ClassicHttpResponse; +import org.apache.hc.core5.http.HttpHost; +import org.apache.hc.core5.http.HttpResponse; +import org.apache.hc.core5.http.io.HttpClientResponseHandler; +import org.apache.hc.core5.http.protocol.HttpContext; + +public class MockHttpClient implements HttpClient { + + /** HTTP response code to use. */ + int responseCode; + + /** Returns the HTTP response code to use. */ + public final int getResponseCode() { + return responseCode; + } + + /** Sets the HTTP response code to use. */ + public MockHttpClient setResponseCode(int responseCode) { + Preconditions.checkArgument(responseCode >= 0); + this.responseCode = responseCode; + return this; + } + + @Override + public HttpResponse execute(ClassicHttpRequest request) throws IOException { + return null; + } + + @Override + public HttpResponse execute(ClassicHttpRequest request, HttpContext context) throws IOException { + return null; + } + + @Override + public ClassicHttpResponse execute(HttpHost target, ClassicHttpRequest request) + throws IOException { + return null; + } + + @Override + public HttpResponse execute(HttpHost target, ClassicHttpRequest request, HttpContext context) + throws IOException { + return null; + } + + @Override + public T execute( + ClassicHttpRequest request, HttpClientResponseHandler responseHandler) + throws IOException { + return null; + } + + @Override + public T execute( + ClassicHttpRequest request, + HttpContext context, + HttpClientResponseHandler responseHandler) + throws IOException { + return null; + } + + @Override + public T execute( + HttpHost target, + ClassicHttpRequest request, + HttpClientResponseHandler responseHandler) + throws IOException { + return null; + } + + @Override + public T execute( + HttpHost target, + ClassicHttpRequest request, + HttpContext context, + HttpClientResponseHandler responseHandler) + throws IOException { + return null; + } +} diff --git a/google-http-client-assembly/classpath-include b/google-http-client-assembly/classpath-include index c1bd80328..c7bbd573f 100644 --- a/google-http-client-assembly/classpath-include +++ b/google-http-client-assembly/classpath-include @@ -7,8 +7,8 @@ - - + + diff --git a/google-http-client-assembly/readme.html b/google-http-client-assembly/readme.html index 5e7af564d..8a146df1e 100644 --- a/google-http-client-assembly/readme.html +++ b/google-http-client-assembly/readme.html @@ -135,8 +135,8 @@

General Purpose Java Environment Dependencies

required for general purpose Java applications :
  • commons-logging-${project.commons-logging.version}.jar
  • -
  • httpclient-${project.httpclient.version}.jar
  • -
  • httpcore-${project.httpcore.version}.jar
  • +
  • httpclient-${project.apache-httpclient-4.version}.jar
  • +
  • httpcore-${project.apache-httpcore-4.version}.jar
diff --git a/pom.xml b/pom.xml index cdc778f9f..bc76101f5 100644 --- a/pom.xml +++ b/pom.xml @@ -62,6 +62,7 @@ google-http-client-appengine google-http-client-android google-http-client-apache-v2 + google-http-client-apache-v5 google-http-client-protobuf google-http-client-gson google-http-client-jackson2 @@ -156,12 +157,22 @@ org.apache.httpcomponents httpclient - ${project.httpclient.version} + ${project.apache-httpclient-4.version} org.apache.httpcomponents httpcore - ${project.httpcore.version} + ${project.apache-httpcore-4.version} + + + org.apache.httpcomponents.client5 + httpclient5 + ${project.apache-httpclient-5.version} + + + org.apache.httpcomponents.core5 + httpcore5 + ${project.apache-httpcore-5.version} com.google.guava @@ -601,8 +612,10 @@ 3.21.12 30.1.1-android 1.1.4c - 4.5.14 - 4.4.16 + 4.5.14 + 4.4.16 + 5.3.1 + 5.2.4 0.31.1 .. 3.2.5 diff --git a/versions.txt b/versions.txt index 963efeb8d..87dbbb662 100644 --- a/versions.txt +++ b/versions.txt @@ -7,6 +7,7 @@ google-http-client-parent:1.44.2:1.44.3-SNAPSHOT google-http-client-android:1.44.2:1.44.3-SNAPSHOT google-http-client-android-test:1.44.2:1.44.3-SNAPSHOT google-http-client-apache-v2:1.44.2:1.44.3-SNAPSHOT +google-http-client-apache-v5:1.44.2:1.44.3-SNAPSHOT google-http-client-appengine:1.44.2:1.44.3-SNAPSHOT google-http-client-assembly:1.44.2:1.44.3-SNAPSHOT google-http-client-findbugs:1.44.2:1.44.3-SNAPSHOT