diff --git a/.github/.OwlBot.lock.yaml b/.github/.OwlBot.lock.yaml
index 5db36a5f7..359fe71c1 100644
--- a/.github/.OwlBot.lock.yaml
+++ b/.github/.OwlBot.lock.yaml
@@ -13,5 +13,5 @@
# limitations under the License.
docker:
image: gcr.io/cloud-devrel-public-resources/owlbot-java:latest
- digest: sha256:68ba5f5164a4b55529d358bb262feaa000536a0c62980727dd05a91bbb47ea5e
-# created: 2024-05-09T16:31:37.168667071Z
+ digest: sha256:72f0d373307d128b2cb720c5cb4d90b31f0e86529dd138c632710ae0c69efae3
+# created: 2024-06-05T18:32:21.724930324Z
diff --git a/.github/release-please.yml b/.github/release-please.yml
index eb0e905a5..b1900aa11 100644
--- a/.github/release-please.yml
+++ b/.github/release-please.yml
@@ -22,3 +22,7 @@ branches:
handleGHRelease: true
releaseType: java-backport
branch: 1.43.x
+ - bumpMinorPreMajor: true
+ handleGHRelease: true
+ releaseType: java-backport
+ branch: 1.44.x
diff --git a/.github/sync-repo-settings.yaml b/.github/sync-repo-settings.yaml
index 3faf69390..5f2262e8a 100644
--- a/.github/sync-repo-settings.yaml
+++ b/.github/sync-repo-settings.yaml
@@ -84,6 +84,20 @@ branchProtectionRules:
- dependencies (11)
- clirr
- cla/google
+ - pattern: 1.44.x
+ isAdminEnforced: true
+ requiredApprovingReviewCount: 1
+ requiresCodeOwnerReviews: true
+ requiresStrictStatusChecks: false
+ requiredStatusCheckContexts:
+ - units (7)
+ - units (8)
+ - units (11)
+ - windows
+ - dependencies (8)
+ - dependencies (11)
+ - clirr
+ - cla/google
permissionRules:
- team: yoshi-admins
permission: admin
diff --git a/.github/trusted-contribution.yml b/.github/trusted-contribution.yml
index a0ba1f7d9..88d3ac9bf 100644
--- a/.github/trusted-contribution.yml
+++ b/.github/trusted-contribution.yml
@@ -1,3 +1,9 @@
trustedContributors:
- renovate-bot
- gcf-owl-bot[bot]
+
+annotations:
+- type: comment
+ text: "/gcbrun"
+- type: label
+ text: "kokoro:force-run"
diff --git a/.github/workflows/auto-release.yaml b/.github/workflows/auto-release.yaml
index 7a106d007..18d92e5a2 100644
--- a/.github/workflows/auto-release.yaml
+++ b/.github/workflows/auto-release.yaml
@@ -21,7 +21,7 @@ jobs:
runs-on: ubuntu-latest
if: contains(github.head_ref, 'release-please')
steps:
- - uses: actions/github-script@v6
+ - uses: actions/github-script@v7
with:
github-token: ${{secrets.YOSHI_APPROVER_TOKEN}}
debug: true
diff --git a/.github/workflows/ci-java7.yaml b/.github/workflows/ci-java7.yaml
index 2c8257d45..5086c87d5 100644
--- a/.github/workflows/ci-java7.yaml
+++ b/.github/workflows/ci-java7.yaml
@@ -24,20 +24,21 @@ jobs:
name: "units (7)"
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v3
- - uses: actions/setup-java@v1
+ - uses: actions/checkout@v4
+ - uses: actions/setup-java@v4
# setup-java v2 or higher does not have version 1.7
with:
- version: 1.7
+ java-version: 7
+ distribution: zulu
architecture: x64
- run: |
java -version
# This value is used in "-Djvm=" later
echo "JAVA7_HOME=${JAVA_HOME}" >> $GITHUB_ENV
- - uses: actions/setup-java@v3
+ - uses: actions/setup-java@v4
with:
java-version: 17
- distribution: temurin
+ distribution: zulu
- name: Set up Maven
uses: stCarolas/setup-maven@v4.5
with:
@@ -55,7 +56,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/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index 32feef7d1..4595806ea 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -27,8 +27,8 @@ jobs:
matrix:
java: [8, 11, 17]
steps:
- - uses: actions/checkout@v3
- - uses: actions/setup-java@v3
+ - uses: actions/checkout@v4
+ - uses: actions/setup-java@v4
with:
distribution: temurin
java-version: ${{matrix.java}}
@@ -41,8 +41,8 @@ jobs:
steps:
- name: Support longpaths
run: git config --system core.longpaths true
- - uses: actions/checkout@v3
- - uses: actions/setup-java@v3
+ - uses: actions/checkout@v4
+ - uses: actions/setup-java@v4
with:
distribution: temurin
java-version: 8
@@ -56,8 +56,8 @@ jobs:
matrix:
java: [8, 11, 17]
steps:
- - uses: actions/checkout@v3
- - uses: actions/setup-java@v3
+ - uses: actions/checkout@v4
+ - uses: actions/setup-java@v4
with:
distribution: temurin
java-version: ${{matrix.java}}
@@ -66,8 +66,8 @@ jobs:
lint:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v3
- - uses: actions/setup-java@v3
+ - uses: actions/checkout@v4
+ - uses: actions/setup-java@v4
with:
distribution: temurin
java-version: 11
@@ -78,8 +78,8 @@ jobs:
clirr:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v3
- - uses: actions/setup-java@v3
+ - uses: actions/checkout@v4
+ - uses: actions/setup-java@v4
with:
distribution: temurin
java-version: 8
@@ -94,8 +94,8 @@ jobs:
name: "units (21)"
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v3
- - uses: actions/setup-java@v3
+ - uses: actions/checkout@v4
+ - uses: actions/setup-java@v4
with:
java-version: 21
distribution: temurin
@@ -104,7 +104,7 @@ jobs:
# https://maven.apache.org/surefire/maven-surefire-plugin/test-mojo.html#jvm
run: echo "SUREFIRE_JVM_OPT=-Djvm=${JAVA_HOME}/bin/java" >> $GITHUB_ENV
shell: bash
- - uses: actions/setup-java@v3
+ - uses: actions/setup-java@v4
with:
java-version: 8
distribution: temurin
diff --git a/.github/workflows/downstream.yaml b/.github/workflows/downstream.yaml
index c4be85eaa..1f44d518d 100644
--- a/.github/workflows/downstream.yaml
+++ b/.github/workflows/downstream.yaml
@@ -133,8 +133,8 @@ jobs:
- workflow-executions
- workflows
steps:
- - uses: actions/checkout@v3
- - uses: actions/setup-java@v3
+ - uses: actions/checkout@v4
+ - uses: actions/setup-java@v4
with:
distribution: zulu
java-version: ${{matrix.java}}
diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml
index 7dd91434a..f153dddd4 100644
--- a/.github/workflows/scorecard.yml
+++ b/.github/workflows/scorecard.yml
@@ -32,12 +32,12 @@ jobs:
steps:
- name: "Checkout code"
- uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # v3.1.0
+ uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
with:
persist-credentials: false
- name: "Run analysis"
- uses: ossf/scorecard-action@e38b1902ae4f44df626f11ba0734b14fb91f8f86 # v2.1.2
+ uses: ossf/scorecard-action@62b2cac7ed8198b15735ed49ab1e5cf35480ba46 # v2.4.0
with:
results_file: results.sarif
results_format: sarif
@@ -59,7 +59,7 @@ jobs:
# Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
# format to the repository Actions tab.
- name: "Upload artifact"
- uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3
+ uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4.3.6
with:
name: SARIF file
path: results.sarif
@@ -67,6 +67,6 @@ jobs:
# Upload the results to GitHub's code scanning dashboard.
- name: "Upload to code-scanning"
- uses: github/codeql-action/upload-sarif@17573ee1cc1b9d061760f3a006fc4aac4f944fd5 # v2.2.4
+ uses: github/codeql-action/upload-sarif@883d8588e56d1753a8a58c1c86e88976f0c23449 # v3.26.3
with:
sarif_file: results.sarif
diff --git a/.kokoro/presubmit/graalvm-native-a.cfg b/.kokoro/presubmit/graalvm-native-a.cfg
index 0b14fb23d..54dca4f2c 100644
--- a/.kokoro/presubmit/graalvm-native-a.cfg
+++ b/.kokoro/presubmit/graalvm-native-a.cfg
@@ -3,7 +3,7 @@
# Configure the docker image for kokoro-trampoline.
env_vars: {
key: "TRAMPOLINE_IMAGE"
- value: "gcr.io/cloud-devrel-public-resources/graalvm_a:1.7.6"
+ value: "gcr.io/cloud-devrel-public-resources/graalvm_a:1.11.0"
}
env_vars: {
diff --git a/.kokoro/presubmit/graalvm-native-b.cfg b/.kokoro/presubmit/graalvm-native-b.cfg
index 799c80594..8b05c77ff 100644
--- a/.kokoro/presubmit/graalvm-native-b.cfg
+++ b/.kokoro/presubmit/graalvm-native-b.cfg
@@ -3,7 +3,7 @@
# Configure the docker image for kokoro-trampoline.
env_vars: {
key: "TRAMPOLINE_IMAGE"
- value: "gcr.io/cloud-devrel-public-resources/graalvm_b:1.7.6"
+ value: "gcr.io/cloud-devrel-public-resources/graalvm_b:1.11.0"
}
env_vars: {
@@ -30,4 +30,4 @@ env_vars: {
env_vars: {
key: "SECRET_MANAGER_KEYS"
value: "java-it-service-account"
-}
\ No newline at end of file
+}
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4cb4c04d2..3dcf93689 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,33 @@
# Changelog
+## [1.45.0](https://github.com/googleapis/google-http-java-client/compare/v1.44.2...v1.45.0) (2024-08-21)
+
+
+### Features
+
+* Introduce google-http-client-apache-v5 (Apache Client/Core 5.x) ([#1960](https://github.com/googleapis/google-http-java-client/issues/1960)) ([5d527dc](https://github.com/googleapis/google-http-java-client/commit/5d527dc3afade0834a82e5280f22e0129b5f1297))
+* Next release from main is 1.45.0 ([#1972](https://github.com/googleapis/google-http-java-client/issues/1972)) ([094dcc8](https://github.com/googleapis/google-http-java-client/commit/094dcc87f22d16686242ce6cb06151b2a199ec2a))
+
+
+### Dependencies
+
+* Update actions/checkout action to v4 ([#1993](https://github.com/googleapis/google-http-java-client/issues/1993)) ([f8b0cc1](https://github.com/googleapis/google-http-java-client/commit/f8b0cc1c908b4ebdf3c30a9f9a9b50c96d33f781))
+* Update actions/github-script action to v7 ([#1994](https://github.com/googleapis/google-http-java-client/issues/1994)) ([e527f0d](https://github.com/googleapis/google-http-java-client/commit/e527f0d9624e47c37a551590cf555f6207751fe9))
+* Update actions/setup-java action to v4 ([#1995](https://github.com/googleapis/google-http-java-client/issues/1995)) ([07aa01c](https://github.com/googleapis/google-http-java-client/commit/07aa01c2f86dc35fd6d1f968cbf111315ef26aa5))
+* Update actions/upload-artifact action to v4 ([#1996](https://github.com/googleapis/google-http-java-client/issues/1996)) ([5ba7021](https://github.com/googleapis/google-http-java-client/commit/5ba70218d8bdae18094b1031f9c800688aa58fb4))
+* Update dependency com.fasterxml.jackson.core:jackson-core to v2.17.2 ([#1987](https://github.com/googleapis/google-http-java-client/issues/1987)) ([4202d32](https://github.com/googleapis/google-http-java-client/commit/4202d328ea2cefff0771036b010fbfd80928f88c))
+* Update dependency com.google.cloud:native-image-shared-config to v1.7.7 ([#1937](https://github.com/googleapis/google-http-java-client/issues/1937)) ([b224a1d](https://github.com/googleapis/google-http-java-client/commit/b224a1d64cae44878f1bb0af83fb8e33e2e12d63))
+* Update dependency com.google.cloud:native-image-shared-config to v1.9.0 ([#1961](https://github.com/googleapis/google-http-java-client/issues/1961)) ([792e44f](https://github.com/googleapis/google-http-java-client/commit/792e44f6a2b7678fef30c3bdfc0955be533a7613))
+* Update dependency com.google.code.gson:gson to v2.11.0 ([#1988](https://github.com/googleapis/google-http-java-client/issues/1988)) ([63afd35](https://github.com/googleapis/google-http-java-client/commit/63afd35f0a3c15f5a66f5b6a06bae01f82b54504))
+* Update dependency com.google.errorprone:error_prone_annotations to v2.30.0 ([#1989](https://github.com/googleapis/google-http-java-client/issues/1989)) ([6e19c5c](https://github.com/googleapis/google-http-java-client/commit/6e19c5ca66c67dad53272998535ed197559dfe02))
+* Update dependency com.google.j2objc:j2objc-annotations to v3 ([#1998](https://github.com/googleapis/google-http-java-client/issues/1998)) ([3d70537](https://github.com/googleapis/google-http-java-client/commit/3d7053747076e599f819c41f8362f6070a96ce8a))
+* Update dependency io.grpc:grpc-context to v1.66.0 ([#1990](https://github.com/googleapis/google-http-java-client/issues/1990)) ([66a9f15](https://github.com/googleapis/google-http-java-client/commit/66a9f15c35bc64c25f10094c424e025ea6b0a693))
+* Update dependency org.apache.httpcomponents.core5:httpcore5 to v5.2.5 ([#2002](https://github.com/googleapis/google-http-java-client/issues/2002)) ([8c61065](https://github.com/googleapis/google-http-java-client/commit/8c6106505a119380e555c64151542445f0e1a5f8))
+* Update github/codeql-action action to v3 ([#2000](https://github.com/googleapis/google-http-java-client/issues/2000)) ([7250f64](https://github.com/googleapis/google-http-java-client/commit/7250f649be7a989dc0a855d6f6ddff987ac0ebaa))
+* Update ossf/scorecard-action action to v2.4.0 ([#1992](https://github.com/googleapis/google-http-java-client/issues/1992)) ([08c5e5a](https://github.com/googleapis/google-http-java-client/commit/08c5e5a7f7a887aaccc03a40c784d34ecbc45984))
+* Update project.appengine.version to v2.0.27 ([#1938](https://github.com/googleapis/google-http-java-client/issues/1938)) ([3f27cc8](https://github.com/googleapis/google-http-java-client/commit/3f27cc800db62c2208160234a5afad641f1a3781))
+* Update project.appengine.version to v2.0.29 ([#1978](https://github.com/googleapis/google-http-java-client/issues/1978)) ([a3fd1e3](https://github.com/googleapis/google-http-java-client/commit/a3fd1e34925a531d47613145d5f3b5473cef2d82))
+
## [1.44.2](https://github.com/googleapis/google-http-java-client/compare/v1.44.1...v1.44.2) (2024-05-16)
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-android-test/pom.xml b/google-http-client-android-test/pom.xml
index 372af1f53..48bc0f333 100644
--- a/google-http-client-android-test/pom.xml
+++ b/google-http-client-android-test/pom.xml
@@ -4,7 +4,7 @@
google-http-clientgoogle-http-client-android-testTest project for google-http-client-android.
- 1.44.2
+ 1.45.0apk
@@ -53,7 +53,7 @@
com.google.http-clientgoogle-http-client-android
- 1.44.2
+ 1.45.0android
@@ -72,7 +72,7 @@
com.google.http-clientgoogle-http-client-test
- 1.44.2
+ 1.45.0junit
diff --git a/google-http-client-android/pom.xml b/google-http-client-android/pom.xml
index fadc64cb6..1ac9f5e2b 100644
--- a/google-http-client-android/pom.xml
+++ b/google-http-client-android/pom.xml
@@ -4,11 +4,11 @@
com.google.http-clientgoogle-http-client-parent
- 1.44.2
+ 1.45.0../pom.xmlgoogle-http-client-android
- 1.44.2
+ 1.45.0Android Platform Extensions to the Google HTTP Client Library for Java.
diff --git a/google-http-client-apache-v2/pom.xml b/google-http-client-apache-v2/pom.xml
index 55676873b..0e1c72742 100644
--- a/google-http-client-apache-v2/pom.xml
+++ b/google-http-client-apache-v2/pom.xml
@@ -4,11 +4,11 @@
com.google.http-clientgoogle-http-client-parent
- 1.44.2
+ 1.45.0../pom.xmlgoogle-http-client-apache-v2
- 1.44.2
+ 1.45.0Apache HTTP transport v2 for the Google HTTP Client Library for Java.
@@ -29,7 +29,7 @@
org.codehaus.mojobuild-helper-maven-plugin
- 3.3.0
+ 3.6.0add-test-source
diff --git a/google-http-client-apache-v5/pom.xml b/google-http-client-apache-v5/pom.xml
new file mode 100644
index 000000000..c2cd4f070
--- /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.45.0
+ ../pom.xml
+
+ google-http-client-apache-v5
+ 1.45.0
+ 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
+ *
+ *
+ *
HTTP version is set to 1.1.
+ *
Retries are disabled (google-http-client handles retries).
+ *
+ *
+ * @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
+ *
+ *
+ *
HTTP version is set to 1.1.
+ *
Retries are disabled (google-http-client handles retries).
+ *
Redirects are disabled (google-http-client handles retries).
+ *
+ *
+ * @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:
+ *
+ *
+ *
The client connection manager is set to {@link PoolingHttpClientConnectionManager}.
+ *
The retry mechanism is turned off using {@link
+ * HttpClientBuilder#disableAutomaticRetries()}.
+ *
Redirects are turned off using {@link HttpClientBuilder#disableRedirectHandling}.
+ *
The route planner uses {@link SystemDefaultRoutePlanner} with {@link
+ * ProxySelector#getDefault()}, which uses the proxy settings from system
+ * properties.
+ *
+ *
+ * @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:
+ *
+ *
+ *
The client connection manager is set to {@link PoolingHttpClientConnectionManager}.
+ *
The retry mechanism is turned off using {@link
+ * HttpClientBuilder#disableAutomaticRetries()}.
+ *
Redirects are turned off using {@link HttpClientBuilder#disableRedirectHandling}.
+ *
The route planner uses {@link SystemDefaultRoutePlanner} with {@link
+ * ProxySelector#getDefault()}, which uses the proxy settings from system
+ * properties.
+ *
+ *
+ * @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 extends T> responseHandler)
+ throws IOException {
+ return null;
+ }
+
+ @Override
+ public T execute(
+ ClassicHttpRequest request,
+ HttpContext context,
+ HttpClientResponseHandler extends T> responseHandler)
+ throws IOException {
+ return null;
+ }
+
+ @Override
+ public T execute(
+ HttpHost target,
+ ClassicHttpRequest request,
+ HttpClientResponseHandler extends T> responseHandler)
+ throws IOException {
+ return null;
+ }
+
+ @Override
+ public T execute(
+ HttpHost target,
+ ClassicHttpRequest request,
+ HttpContext context,
+ HttpClientResponseHandler extends T> responseHandler)
+ throws IOException {
+ return null;
+ }
+}
diff --git a/google-http-client-appengine/pom.xml b/google-http-client-appengine/pom.xml
index 20ebf8f84..573b3a334 100644
--- a/google-http-client-appengine/pom.xml
+++ b/google-http-client-appengine/pom.xml
@@ -4,11 +4,11 @@
com.google.http-clientgoogle-http-client-parent
- 1.44.2
+ 1.45.0../pom.xmlgoogle-http-client-appengine
- 1.44.2
+ 1.45.0Google App Engine extensions to the Google HTTP Client Library for Java.
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/pom.xml b/google-http-client-assembly/pom.xml
index f2cc29862..3f58b39d8 100644
--- a/google-http-client-assembly/pom.xml
+++ b/google-http-client-assembly/pom.xml
@@ -4,12 +4,12 @@
com.google.http-clientgoogle-http-client-parent
- 1.44.2
+ 1.45.0../pom.xmlcom.google.http-clientgoogle-http-client-assembly
- 1.44.2
+ 1.45.0pomAssembly for the Google HTTP Client Library for Java
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 @@