diff --git a/google-http-client-jackson2/pom.xml b/google-http-client-jackson2/pom.xml
index 358b8c40a..c26a2747f 100644
--- a/google-http-client-jackson2/pom.xml
+++ b/google-http-client-jackson2/pom.xml
@@ -85,5 +85,22 @@
I wanted to put this in google-http-client module, but google-http-client-json dependency
+ * would create a dependency cycle. Therefore I place this in this class.
+ */
+public class BatchTest {
+
+ private static InputStream toStream(String content) throws IOException {
+ return new ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8));
+ }
+
+ // This test case tries to simulate Beam's GcsUtilTest, where it reads the content of failed
+ // response and throws corresponding IOException after a retry on status code 429.
+ // https://github.com/apache/beam/pull/14527/files#diff-1b8fce5e4444d5c3e99bd0564a8848f9e6d232550efb67902bfeb5ac53819836R505
+ @Test
+ public void testErrorContentReadRetry() throws IOException {
+ String contentBoundary = "batch_foobarbaz";
+ String contentBoundaryLine = "--" + contentBoundary;
+ String endOfContentBoundaryLine = "--" + contentBoundary + "--";
+ String content =
+ contentBoundaryLine
+ + "\n"
+ + "Content-Type: application/http\n"
+ + "\n"
+ + "HTTP/1.1 404 Not Found\n"
+ + "Content-Length: -1\n"
+ + "\n"
+ + "{\"error\":{\"code\":404}}"
+ + "\n"
+ + "\n"
+ + endOfContentBoundaryLine
+ + "\n";
+ final LowLevelHttpResponse mockResponse = Mockito.mock(LowLevelHttpResponse.class);
+ when(mockResponse.getContentType())
+ .thenReturn("text/plain; charset=UTF-8")
+ .thenReturn("multipart/mixed; boundary=" + contentBoundary);
+
+ // 429: Too many requests, then 200: OK.
+ final int statusCode429_TooManyRequest = 429;
+ when(mockResponse.getStatusCode()).thenReturn(statusCode429_TooManyRequest, 200);
+ when(mockResponse.getContent()).thenReturn(toStream("rateLimitExceeded"), toStream(content));
+
+ MockHttpTransport mockTransport =
+ new MockHttpTransport.Builder()
+ .setLowLevelHttpRequest(
+ new MockLowLevelHttpRequest() {
+ @Override
+ public LowLevelHttpResponse execute() throws IOException {
+ return mockResponse;
+ }
+ })
+ .build();
+
+ HttpRequestInitializer httpRequestInitializer =
+ new HttpRequestInitializer() {
+ @Override
+ public void initialize(HttpRequest request) throws IOException {
+ request.setUnsuccessfulResponseHandler(
+ new HttpUnsuccessfulResponseHandler() {
+ @Override
+ public boolean handleResponse(
+ HttpRequest request, HttpResponse response, boolean supportsRetry)
+ throws IOException {
+ // true to retry
+ boolean willRetry = response.getStatusCode() == statusCode429_TooManyRequest;
+ return willRetry;
+ }
+ });
+ }
+ };
+ Storage storageClient =
+ new Storage(mockTransport, JacksonFactory.getDefaultInstance(), httpRequestInitializer);
+ BatchRequest batch = storageClient.batch(httpRequestInitializer);
+
+ Storage.Objects.Get getRequest = storageClient.objects().get("testbucket", "testobject");
+
+ final GoogleJsonError[] capturedGoogleJsonError = new GoogleJsonError[1];
+ getRequest.queue(
+ batch,
+ new JsonBatchCallback I wanted to put this in google-http-client module, but google-http-client-json dependency
+ * would create a dependency cycle. Therefore I place this in this class.
+ */
+public class BatchTestWithFakeServer {
+
+ @Test
+ public void testErrorContentReadRetry_withFakeServer() throws IOException {
+ final int statusCode429_TooManyRequest = 429;
+
+ final HttpHandler handler =
+ new HttpHandler() {
+ int count = 0;
+
+ @Override
+ public void handle(HttpExchange httpExchange) throws IOException {
+ Headers responseHeaders = httpExchange.getResponseHeaders();
+ if (count == 0) {
+ // 1st request
+ byte[] response = "rateLimitExceeded".getBytes(StandardCharsets.UTF_8);
+ responseHeaders.set("Content-Type", "text/plain; charset=UTF-8");
+ httpExchange.sendResponseHeaders(statusCode429_TooManyRequest, response.length);
+ try (OutputStream out = httpExchange.getResponseBody()) {
+ out.write(response);
+ }
+ count++;
+ } else {
+ // 2nd request
+ String contentBoundary = "batch_foobarbaz";
+ String contentBoundaryLine = "--" + contentBoundary;
+ String endOfContentBoundaryLine = "--" + contentBoundary + "--";
+ String content =
+ contentBoundaryLine
+ + "\n"
+ + "Content-Type: application/http\n"
+ + "\n"
+ + "HTTP/1.1 404 Not Found\n"
+ + "Content-Length: -1\n"
+ + "\n"
+ + "{\"error\":{\"code\":404}}"
+ + "\n"
+ + "\n"
+ + endOfContentBoundaryLine
+ + "\n";
+ byte[] response = content.getBytes(StandardCharsets.UTF_8);
+ responseHeaders.set("Content-Type", "multipart/mixed; boundary=" + contentBoundary);
+ httpExchange.sendResponseHeaders(200, response.length);
+ try (OutputStream out = httpExchange.getResponseBody()) {
+ out.write(response);
+ }
+ }
+ }
+ };
+ try (FakeServer server = new FakeServer(handler)) {
+ HttpTransport transport = new ApacheHttpTransport();
+ GenericUrl testUrl = new GenericUrl("http://localhost/foo//bar");
+ testUrl.setPort(server.getPort());
+
+ HttpRequestInitializer httpRequestInitializer =
+ new HttpRequestInitializer() {
+ @Override
+ public void initialize(HttpRequest request) throws IOException {
+ request.setUnsuccessfulResponseHandler(
+ new HttpUnsuccessfulResponseHandler() {
+ @Override
+ public boolean handleResponse(
+ HttpRequest request, HttpResponse response, boolean supportsRetry)
+ throws IOException {
+ // true to retry
+ boolean willRetry = response.getStatusCode() == statusCode429_TooManyRequest;
+ return willRetry;
+ }
+ });
+ }
+ };
+
+ Storage storageClient =
+ new Storage(transport, JacksonFactory.getDefaultInstance(), httpRequestInitializer);
+
+ BatchRequest batch = storageClient.batch(httpRequestInitializer);
+ batch.setBatchUrl(testUrl);
+
+ Storage.Objects.Get getRequest = storageClient.objects().get("testbucket", "testobject");
+
+ final GoogleJsonError[] capturedGoogleJsonError = new GoogleJsonError[1];
+ getRequest.queue(
+ batch,
+ new JsonBatchCallback