Skip to content

Commit 84e967a

Browse files
committed
Close all io streams on disconnect
1 parent f0d4f6a commit 84e967a

File tree

2 files changed

+130
-14
lines changed

2 files changed

+130
-14
lines changed

src/main/java/io/split/android/client/network/HttpResponseConnectionAdapter.java

Lines changed: 48 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package io.split.android.client.network;
22

33
import androidx.annotation.NonNull;
4+
import androidx.annotation.Nullable;
45
import androidx.annotation.VisibleForTesting;
56

67
import java.io.ByteArrayInputStream;
@@ -32,6 +33,8 @@ class HttpResponseConnectionAdapter extends HttpsURLConnection {
3233
private final URL mUrl;
3334
private final Certificate[] mServerCertificates;
3435
private OutputStream mOutputStream;
36+
private InputStream mInputStream;
37+
private InputStream mErrorStream;
3538
private boolean mDoOutput = false;
3639

3740
/**
@@ -52,11 +55,23 @@ class HttpResponseConnectionAdapter extends HttpsURLConnection {
5255
@NonNull HttpResponse response,
5356
Certificate[] serverCertificates,
5457
@NonNull OutputStream outputStream) {
58+
this(url, response, serverCertificates, outputStream, null, null);
59+
}
60+
61+
@VisibleForTesting
62+
HttpResponseConnectionAdapter(@NonNull URL url,
63+
@NonNull HttpResponse response,
64+
Certificate[] serverCertificates,
65+
@NonNull OutputStream outputStream,
66+
@Nullable InputStream inputStream,
67+
@Nullable InputStream errorStream) {
5568
super(url);
5669
mUrl = url;
5770
mResponse = response;
5871
mServerCertificates = serverCertificates;
5972
mOutputStream = outputStream;
73+
mInputStream = inputStream;
74+
mErrorStream = errorStream;
6075
}
6176

6277
@Override
@@ -90,21 +105,27 @@ public InputStream getInputStream() throws IOException {
90105
if (mResponse.getHttpStatus() >= 400) {
91106
throw new IOException("HTTP " + mResponse.getHttpStatus());
92107
}
93-
String data = mResponse.getData();
94-
if (data == null) {
95-
data = "";
108+
if (mInputStream == null) {
109+
String data = mResponse.getData();
110+
if (data == null) {
111+
data = "";
112+
}
113+
mInputStream = new ByteArrayInputStream(data.getBytes(StandardCharsets.UTF_8));
96114
}
97-
return new ByteArrayInputStream(data.getBytes(StandardCharsets.UTF_8));
115+
return mInputStream;
98116
}
99117

100118
@Override
101119
public InputStream getErrorStream() {
102120
if (mResponse.getHttpStatus() >= 400) {
103-
String data = mResponse.getData();
104-
if (data == null) {
105-
data = "";
121+
if (mErrorStream == null) {
122+
String data = mResponse.getData();
123+
if (data == null) {
124+
data = "";
125+
}
126+
mErrorStream = new ByteArrayInputStream(data.getBytes(StandardCharsets.UTF_8));
106127
}
107-
return new ByteArrayInputStream(data.getBytes(StandardCharsets.UTF_8));
128+
return mErrorStream;
108129
}
109130
return null;
110131
}
@@ -121,13 +142,32 @@ public boolean usingProxy() {
121142

122143
@Override
123144
public void disconnect() {
145+
// Close output stream if it exists
124146
try {
125147
if (mOutputStream != null) {
126148
mOutputStream.close();
127149
}
128150
} catch (IOException e) {
129151
// Ignore exception during disconnect
130152
}
153+
154+
// Close input stream if it exists
155+
try {
156+
if (mInputStream != null) {
157+
mInputStream.close();
158+
}
159+
} catch (IOException e) {
160+
// Ignore exception during disconnect
161+
}
162+
163+
// Close error stream if it exists
164+
try {
165+
if (mErrorStream != null) {
166+
mErrorStream.close();
167+
}
168+
} catch (IOException e) {
169+
// Ignore exception during disconnect
170+
}
131171
}
132172

133173
// Required abstract method implementations for HTTPS connection

src/test/java/io/split/android/client/network/HttpResponseConnectionAdapterTest.java

Lines changed: 82 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import org.junit.Test;
1414
import org.mockito.Mock;
1515

16+
import java.io.ByteArrayInputStream;
1617
import java.io.ByteArrayOutputStream;
1718
import java.io.IOException;
1819
import java.io.InputStream;
@@ -374,7 +375,7 @@ public void getOutputStreamThrowsWhenNotEnabled() throws IOException {
374375
// Should throw exception since doOutput is not enabled
375376
mAdapter.getOutputStream();
376377
}
377-
378+
378379
@Test
379380
public void setDoOutputEnablesOutput() {
380381
mAdapter = new HttpResponseConnectionAdapter(mTestUrl, mMockResponse, mTestCertificates);
@@ -386,15 +387,15 @@ public void setDoOutputEnablesOutput() {
386387
mAdapter.setDoOutput(true);
387388
assertEquals(true, mAdapter.getDoOutput());
388389
}
389-
390+
390391
@Test
391392
public void getOutputStreamAfterEnablingOutput() throws IOException {
392393
mAdapter = new HttpResponseConnectionAdapter(mTestUrl, mMockResponse, mTestCertificates);
393394
mAdapter.setDoOutput(true);
394395

395396
assertNotNull("Output stream should not be null when doOutput is enabled", mAdapter.getOutputStream());
396397
}
397-
398+
398399
@Test
399400
public void writeToOutputStream() throws IOException {
400401
// Create a ByteArrayOutputStream to capture the written data
@@ -411,15 +412,15 @@ public void writeToOutputStream() throws IOException {
411412
// Verify that the data was written correctly
412413
assertEquals("Written data should match the input", testData, testOutputStream.toString(StandardCharsets.UTF_8.name()));
413414
}
414-
415+
415416
@Test
416417
public void disconnectClosesOutputStream() throws IOException {
417418
// Create a custom OutputStream that tracks if it's been closed
418419
TestOutputStream testOutputStream = new TestOutputStream();
419-
420+
420421
mAdapter = new HttpResponseConnectionAdapter(mTestUrl, mMockResponse, mTestCertificates, testOutputStream);
421422
mAdapter.setDoOutput(true);
422-
423+
423424
// Get the output stream and write some data
424425
mAdapter.getOutputStream().write("Test".getBytes(StandardCharsets.UTF_8));
425426

@@ -433,6 +434,37 @@ public void disconnectClosesOutputStream() throws IOException {
433434
assertTrue("Output stream should be closed after disconnect", testOutputStream.isClosed());
434435
}
435436

437+
@Test
438+
public void disconnectClosesInputStream() throws IOException {
439+
// Create a custom InputStream that tracks if it's been closed
440+
TestInputStream testInputStream = new TestInputStream("Test response data".getBytes(StandardCharsets.UTF_8));
441+
TestOutputStream testOutputStream = new TestOutputStream();
442+
443+
// Create adapter with injected test input stream
444+
when(mMockResponse.getHttpStatus()).thenReturn(200);
445+
mAdapter = new HttpResponseConnectionAdapter(
446+
mTestUrl,
447+
mMockResponse,
448+
mTestCertificates,
449+
testOutputStream,
450+
testInputStream,
451+
null);
452+
453+
// Get the input stream and read some data to simulate usage
454+
InputStream stream = mAdapter.getInputStream();
455+
byte[] buffer = new byte[10];
456+
stream.read(buffer);
457+
458+
// Verify the stream is not closed yet
459+
assertFalse("Input stream should not be closed before disconnect", testInputStream.isClosed());
460+
461+
// Disconnect should close the input stream
462+
mAdapter.disconnect();
463+
464+
// Verify the stream was closed
465+
assertTrue("Input stream should be closed after disconnect", testInputStream.isClosed());
466+
}
467+
436468
/**
437469
* Custom OutputStream implementation for testing that tracks if it's been closed.
438470
*/
@@ -449,4 +481,48 @@ public boolean isClosed() {
449481
return mClosed;
450482
}
451483
}
484+
485+
private static class TestInputStream extends ByteArrayInputStream {
486+
private boolean mClosed = false;
487+
488+
public TestInputStream(byte[] data) {
489+
super(data);
490+
}
491+
492+
@Override
493+
public void close() throws IOException {
494+
super.close();
495+
mClosed = true;
496+
}
497+
498+
public boolean isClosed() {
499+
return mClosed;
500+
}
501+
}
502+
503+
@Test
504+
public void disconnectClosesErrorStream() throws IOException {
505+
TestInputStream testErrorStream = new TestInputStream("Error data".getBytes(StandardCharsets.UTF_8));
506+
TestOutputStream testOutputStream = new TestOutputStream();
507+
508+
when(mMockResponse.getHttpStatus()).thenReturn(404); // Error status
509+
mAdapter = new HttpResponseConnectionAdapter(
510+
mTestUrl,
511+
mMockResponse,
512+
mTestCertificates,
513+
testOutputStream,
514+
null,
515+
testErrorStream);
516+
517+
// Get the error stream and read some data to simulate usage
518+
InputStream stream = mAdapter.getErrorStream();
519+
byte[] buffer = new byte[10];
520+
stream.read(buffer);
521+
522+
assertFalse("Error stream should not be closed before disconnect", testErrorStream.isClosed());
523+
524+
mAdapter.disconnect();
525+
526+
assertTrue("Error stream should be closed after disconnect", testErrorStream.isClosed());
527+
}
452528
}

0 commit comments

Comments
 (0)