Skip to content

Commit f84ed59

Browse files
authored
fix: when disconnecting, close the underlying connection before the response InputStream (#1315)
Adds a test to the `NetHttpTransport` and `ApacheHttpTransport` to make sure we don't wait until all the content is read when disconnecting the response. Fixes #1303
1 parent 9cb50e4 commit f84ed59

File tree

3 files changed

+149
-20
lines changed

3 files changed

+149
-20
lines changed

google-http-client-apache-v2/src/test/java/com/google/api/client/http/apache/v2/ApacheHttpTransportTest.java

+70-13
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@
3535
import java.io.OutputStream;
3636
import java.net.InetSocketAddress;
3737
import java.nio.charset.StandardCharsets;
38+
import java.util.concurrent.ExecutorService;
39+
import java.util.concurrent.Executors;
3840
import java.util.concurrent.atomic.AtomicBoolean;
3941
import java.util.concurrent.atomic.AtomicInteger;
4042
import org.apache.http.Header;
@@ -213,11 +215,32 @@ public void testConnectTimeout() {
213215
}
214216
}
215217

218+
static class FakeServer implements AutoCloseable {
219+
private final HttpServer server;
220+
private final ExecutorService executorService;
221+
222+
public FakeServer(HttpHandler httpHandler) throws IOException {
223+
this.server = HttpServer.create(new InetSocketAddress(0), 0);
224+
this.executorService = Executors.newFixedThreadPool(1);
225+
server.setExecutor(this.executorService);
226+
server.createContext("/", httpHandler);
227+
server.start();
228+
}
229+
230+
public int getPort() {
231+
return server.getAddress().getPort();
232+
}
233+
234+
@Override
235+
public void close() {
236+
this.server.stop(0);
237+
this.executorService.shutdownNow();
238+
}
239+
}
240+
216241
@Test
217242
public void testNormalizedUrl() throws IOException {
218-
HttpServer server = HttpServer.create(new InetSocketAddress(0), 0);
219-
server.createContext(
220-
"/",
243+
final HttpHandler handler =
221244
new HttpHandler() {
222245
@Override
223246
public void handle(HttpExchange httpExchange) throws IOException {
@@ -227,19 +250,53 @@ public void handle(HttpExchange httpExchange) throws IOException {
227250
out.write(response);
228251
}
229252
}
230-
});
231-
server.start();
232-
233-
ApacheHttpTransport transport = new ApacheHttpTransport();
234-
GenericUrl testUrl = new GenericUrl("http://localhost/foo//bar");
235-
testUrl.setPort(server.getAddress().getPort());
236-
com.google.api.client.http.HttpResponse response =
237-
transport.createRequestFactory().buildGetRequest(testUrl).execute();
238-
assertEquals(200, response.getStatusCode());
239-
assertEquals("/foo//bar", response.parseAsString());
253+
};
254+
try (FakeServer server = new FakeServer(handler)) {
255+
HttpTransport transport = new ApacheHttpTransport();
256+
GenericUrl testUrl = new GenericUrl("http://localhost/foo//bar");
257+
testUrl.setPort(server.getPort());
258+
com.google.api.client.http.HttpResponse response =
259+
transport.createRequestFactory().buildGetRequest(testUrl).execute();
260+
assertEquals(200, response.getStatusCode());
261+
assertEquals("/foo//bar", response.parseAsString());
262+
}
240263
}
241264

242265
private boolean isWindows() {
243266
return System.getProperty("os.name").startsWith("Windows");
244267
}
268+
269+
@Test(timeout = 10_000L)
270+
public void testDisconnectShouldNotWaitToReadResponse() throws IOException {
271+
// This handler waits for 100s before returning writing content. The test should
272+
// timeout if disconnect waits for the response before closing the connection.
273+
final HttpHandler handler =
274+
new HttpHandler() {
275+
@Override
276+
public void handle(HttpExchange httpExchange) throws IOException {
277+
byte[] response = httpExchange.getRequestURI().toString().getBytes();
278+
httpExchange.sendResponseHeaders(200, response.length);
279+
280+
// Sleep for longer than the test timeout
281+
try {
282+
Thread.sleep(100_000);
283+
} catch (InterruptedException e) {
284+
throw new IOException("interrupted", e);
285+
}
286+
try (OutputStream out = httpExchange.getResponseBody()) {
287+
out.write(response);
288+
}
289+
}
290+
};
291+
292+
try (FakeServer server = new FakeServer(handler)) {
293+
HttpTransport transport = new ApacheHttpTransport();
294+
GenericUrl testUrl = new GenericUrl("http://localhost/foo//bar");
295+
testUrl.setPort(server.getPort());
296+
com.google.api.client.http.HttpResponse response =
297+
transport.createRequestFactory().buildGetRequest(testUrl).execute();
298+
// disconnect should not wait to read the entire content
299+
response.disconnect();
300+
}
301+
}
245302
}

google-http-client/src/main/java/com/google/api/client/http/HttpResponse.java

+12-7
Original file line numberDiff line numberDiff line change
@@ -351,9 +351,9 @@ public InputStream getContent() throws IOException {
351351
try {
352352
// gzip encoding (wrap content with GZipInputStream)
353353
if (!returnRawInputStream && this.contentEncoding != null) {
354-
String oontentencoding = this.contentEncoding.trim().toLowerCase(Locale.ENGLISH);
355-
if (CONTENT_ENCODING_GZIP.equals(oontentencoding)
356-
|| CONTENT_ENCODING_XGZIP.equals(oontentencoding)) {
354+
String contentEncoding = this.contentEncoding.trim().toLowerCase(Locale.ENGLISH);
355+
if (CONTENT_ENCODING_GZIP.equals(contentEncoding)
356+
|| CONTENT_ENCODING_XGZIP.equals(contentEncoding)) {
357357
// Wrap the original stream in a ConsumingInputStream before passing it to
358358
// GZIPInputStream. The GZIPInputStream leaves content unconsumed in the original
359359
// stream (it almost always leaves the last chunk unconsumed in chunked responses).
@@ -419,9 +419,12 @@ public void download(OutputStream outputStream) throws IOException {
419419

420420
/** Closes the content of the HTTP response from {@link #getContent()}, ignoring any content. */
421421
public void ignore() throws IOException {
422-
InputStream content = getContent();
423-
if (content != null) {
424-
content.close();
422+
if (this.response == null) {
423+
return;
424+
}
425+
InputStream lowLevelResponseContent = this.response.getContent();
426+
if (lowLevelResponseContent != null) {
427+
lowLevelResponseContent.close();
425428
}
426429
}
427430

@@ -432,8 +435,10 @@ public void ignore() throws IOException {
432435
* @since 1.4
433436
*/
434437
public void disconnect() throws IOException {
435-
ignore();
438+
// Close the connection before trying to close the InputStream content. If you are trying to
439+
// disconnect, we shouldn't need to try to read any further content.
436440
response.disconnect();
441+
ignore();
437442
}
438443

439444
/**

google-http-client/src/test/java/com/google/api/client/http/javanet/NetHttpTransportTest.java

+67
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,27 @@
1414

1515
package com.google.api.client.http.javanet;
1616

17+
import com.google.api.client.http.GenericUrl;
18+
import com.google.api.client.http.HttpTransport;
1719
import com.google.api.client.testing.http.HttpTesting;
1820
import com.google.api.client.testing.http.javanet.MockHttpURLConnection;
1921
import com.google.api.client.util.ByteArrayStreamingContent;
2022
import com.google.api.client.util.StringUtils;
23+
import com.sun.net.httpserver.HttpExchange;
24+
import com.sun.net.httpserver.HttpHandler;
25+
import com.sun.net.httpserver.HttpServer;
2126
import java.io.ByteArrayInputStream;
2227
import java.io.IOException;
2328
import java.io.InputStream;
29+
import java.io.OutputStream;
2430
import java.net.HttpURLConnection;
31+
import java.net.InetSocketAddress;
2532
import java.net.URL;
2633
import java.security.KeyStore;
34+
import java.util.concurrent.ExecutorService;
35+
import java.util.concurrent.Executors;
2736
import junit.framework.TestCase;
37+
import org.junit.Test;
2838

2939
/**
3040
* Tests {@link NetHttpTransport}.
@@ -159,4 +169,61 @@ private void setContent(NetHttpRequest request, String type, String value) throw
159169
request.setContentType(type);
160170
request.setContentLength(bytes.length);
161171
}
172+
173+
static class FakeServer implements AutoCloseable {
174+
private final HttpServer server;
175+
private final ExecutorService executorService;
176+
177+
public FakeServer(HttpHandler httpHandler) throws IOException {
178+
this.server = HttpServer.create(new InetSocketAddress(0), 0);
179+
this.executorService = Executors.newFixedThreadPool(1);
180+
server.setExecutor(this.executorService);
181+
server.createContext("/", httpHandler);
182+
server.start();
183+
}
184+
185+
public int getPort() {
186+
return server.getAddress().getPort();
187+
}
188+
189+
@Override
190+
public void close() {
191+
this.server.stop(0);
192+
this.executorService.shutdownNow();
193+
}
194+
}
195+
196+
@Test(timeout = 10_000L)
197+
public void testDisconnectShouldNotWaitToReadResponse() throws IOException {
198+
// This handler waits for 100s before returning writing content. The test should
199+
// timeout if disconnect waits for the response before closing the connection.
200+
final HttpHandler handler =
201+
new HttpHandler() {
202+
@Override
203+
public void handle(HttpExchange httpExchange) throws IOException {
204+
byte[] response = httpExchange.getRequestURI().toString().getBytes();
205+
httpExchange.sendResponseHeaders(200, response.length);
206+
207+
// Sleep for longer than the test timeout
208+
try {
209+
Thread.sleep(100_000);
210+
} catch (InterruptedException e) {
211+
throw new IOException("interrupted", e);
212+
}
213+
try (OutputStream out = httpExchange.getResponseBody()) {
214+
out.write(response);
215+
}
216+
}
217+
};
218+
219+
try (FakeServer server = new FakeServer(handler)) {
220+
HttpTransport transport = new NetHttpTransport();
221+
GenericUrl testUrl = new GenericUrl("http://localhost/foo//bar");
222+
testUrl.setPort(server.getPort());
223+
com.google.api.client.http.HttpResponse response =
224+
transport.createRequestFactory().buildGetRequest(testUrl).execute();
225+
// disconnect should not wait to read the entire content
226+
response.disconnect();
227+
}
228+
}
162229
}

0 commit comments

Comments
 (0)