Skip to content

Commit f0d4f6a

Browse files
committed
OutputStream capabilities in connection adapter
1 parent 8b9e072 commit f0d4f6a

File tree

2 files changed

+108
-6
lines changed

2 files changed

+108
-6
lines changed

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

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

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

56
import java.io.ByteArrayInputStream;
7+
import java.io.ByteArrayOutputStream;
68
import java.io.IOException;
79
import java.io.InputStream;
810
import java.io.OutputStream;
@@ -22,13 +24,15 @@
2224
/**
2325
* Adapter that wraps an HttpResponse as an HttpURLConnection.
2426
* <p>
25-
* This is only used to adapt the response from the CONNECT method.
27+
* This is only used to adapt the response from request through the TLS tunnel.
2628
*/
2729
class HttpResponseConnectionAdapter extends HttpsURLConnection {
2830

2931
private final HttpResponse mResponse;
3032
private final URL mUrl;
3133
private final Certificate[] mServerCertificates;
34+
private OutputStream mOutputStream;
35+
private boolean mDoOutput = false;
3236

3337
/**
3438
* Creates an adapter that wraps an HttpResponse as an HttpURLConnection.
@@ -38,12 +42,21 @@ class HttpResponseConnectionAdapter extends HttpsURLConnection {
3842
* @param serverCertificates The server certificates from the SSL connection
3943
*/
4044
HttpResponseConnectionAdapter(@NonNull URL url,
41-
@NonNull HttpResponse response,
42-
Certificate[] serverCertificates) {
45+
@NonNull HttpResponse response,
46+
Certificate[] serverCertificates) {
47+
this(url, response, serverCertificates, new ByteArrayOutputStream());
48+
}
49+
50+
@VisibleForTesting
51+
HttpResponseConnectionAdapter(@NonNull URL url,
52+
@NonNull HttpResponse response,
53+
Certificate[] serverCertificates,
54+
@NonNull OutputStream outputStream) {
4355
super(url);
4456
mUrl = url;
4557
mResponse = response;
4658
mServerCertificates = serverCertificates;
59+
mOutputStream = outputStream;
4760
}
4861

4962
@Override
@@ -108,6 +121,13 @@ public boolean usingProxy() {
108121

109122
@Override
110123
public void disconnect() {
124+
try {
125+
if (mOutputStream != null) {
126+
mOutputStream.close();
127+
}
128+
} catch (IOException e) {
129+
// Ignore exception during disconnect
130+
}
111131
}
112132

113133
// Required abstract method implementations for HTTPS connection
@@ -148,11 +168,12 @@ public boolean getInstanceFollowRedirects() {
148168

149169
@Override
150170
public void setDoOutput(boolean doOutput) {
171+
mDoOutput = doOutput;
151172
}
152173

153174
@Override
154175
public boolean getDoOutput() {
155-
return false;
176+
return mDoOutput;
156177
}
157178

158179
@Override
@@ -350,7 +371,10 @@ public Permission getPermission() throws IOException {
350371

351372
@Override
352373
public OutputStream getOutputStream() throws IOException {
353-
throw new IOException("Output not supported for SSL proxy responses");
374+
if (!mDoOutput) {
375+
throw new IOException("Output not enabled for this connection. Call setDoOutput(true) first.");
376+
}
377+
return mOutputStream;
354378
}
355379

356380
@Override

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

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

33
import static org.junit.Assert.assertEquals;
4+
import static org.junit.Assert.assertFalse;
45
import static org.junit.Assert.assertNotNull;
56
import static org.junit.Assert.assertNull;
67
import static org.junit.Assert.assertSame;
@@ -12,6 +13,7 @@
1213
import org.junit.Test;
1314
import org.mockito.Mock;
1415

16+
import java.io.ByteArrayOutputStream;
1517
import java.io.IOException;
1618
import java.io.InputStream;
1719
import java.net.MalformedURLException;
@@ -367,8 +369,84 @@ public void urlCanBeRetrieved() {
367369
}
368370

369371
@Test(expected = IOException.class)
370-
public void getOutputStreamThrows() throws IOException {
372+
public void getOutputStreamThrowsWhenNotEnabled() throws IOException {
371373
mAdapter = new HttpResponseConnectionAdapter(mTestUrl, mMockResponse, mTestCertificates);
374+
// Should throw exception since doOutput is not enabled
372375
mAdapter.getOutputStream();
373376
}
377+
378+
@Test
379+
public void setDoOutputEnablesOutput() {
380+
mAdapter = new HttpResponseConnectionAdapter(mTestUrl, mMockResponse, mTestCertificates);
381+
382+
// Initially doOutput should be false
383+
assertEquals(false, mAdapter.getDoOutput());
384+
385+
// After setting doOutput to true, getDoOutput should return true
386+
mAdapter.setDoOutput(true);
387+
assertEquals(true, mAdapter.getDoOutput());
388+
}
389+
390+
@Test
391+
public void getOutputStreamAfterEnablingOutput() throws IOException {
392+
mAdapter = new HttpResponseConnectionAdapter(mTestUrl, mMockResponse, mTestCertificates);
393+
mAdapter.setDoOutput(true);
394+
395+
assertNotNull("Output stream should not be null when doOutput is enabled", mAdapter.getOutputStream());
396+
}
397+
398+
@Test
399+
public void writeToOutputStream() throws IOException {
400+
// Create a ByteArrayOutputStream to capture the written data
401+
ByteArrayOutputStream testOutputStream = new ByteArrayOutputStream();
402+
403+
// Use the constructor that accepts a custom OutputStream
404+
mAdapter = new HttpResponseConnectionAdapter(mTestUrl, mMockResponse, mTestCertificates, testOutputStream);
405+
mAdapter.setDoOutput(true);
406+
407+
// Write test data to the output stream
408+
String testData = "Test output data";
409+
mAdapter.getOutputStream().write(testData.getBytes(StandardCharsets.UTF_8));
410+
411+
// Verify that the data was written correctly
412+
assertEquals("Written data should match the input", testData, testOutputStream.toString(StandardCharsets.UTF_8.name()));
413+
}
414+
415+
@Test
416+
public void disconnectClosesOutputStream() throws IOException {
417+
// Create a custom OutputStream that tracks if it's been closed
418+
TestOutputStream testOutputStream = new TestOutputStream();
419+
420+
mAdapter = new HttpResponseConnectionAdapter(mTestUrl, mMockResponse, mTestCertificates, testOutputStream);
421+
mAdapter.setDoOutput(true);
422+
423+
// Get the output stream and write some data
424+
mAdapter.getOutputStream().write("Test".getBytes(StandardCharsets.UTF_8));
425+
426+
// Verify the stream is not closed yet
427+
assertFalse("Output stream should not be closed before disconnect", testOutputStream.isClosed());
428+
429+
// Disconnect should close the output stream
430+
mAdapter.disconnect();
431+
432+
// Verify the stream was closed
433+
assertTrue("Output stream should be closed after disconnect", testOutputStream.isClosed());
434+
}
435+
436+
/**
437+
* Custom OutputStream implementation for testing that tracks if it's been closed.
438+
*/
439+
private static class TestOutputStream extends ByteArrayOutputStream {
440+
private boolean mClosed = false;
441+
442+
@Override
443+
public void close() throws IOException {
444+
super.close();
445+
mClosed = true;
446+
}
447+
448+
public boolean isClosed() {
449+
return mClosed;
450+
}
451+
}
374452
}

0 commit comments

Comments
 (0)