diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/network/NetworkingModule.java b/ReactAndroid/src/main/java/com/facebook/react/modules/network/NetworkingModule.java index 3f483798c..fe379bb1f 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/modules/network/NetworkingModule.java +++ b/ReactAndroid/src/main/java/com/facebook/react/modules/network/NetworkingModule.java @@ -343,11 +343,11 @@ public final class NetworkingModule extends ReactContextBaseJavaModule { } } + RequestBody requestBody; if (data == null) { - requestBuilder.method(method, RequestBodyUtil.getEmptyBody(method)); + requestBody = RequestBodyUtil.getEmptyBody(method); } else if (handler != null) { - RequestBody requestBody = handler.toRequestBody(data, contentType); - requestBuilder.method(method, requestBody); + requestBody = handler.toRequestBody(data, contentType); } else if (data.hasKey(REQUEST_BODY_KEY_STRING)) { if (contentType == null) { ResponseUtil.onRequestError( @@ -360,14 +360,13 @@ public final class NetworkingModule extends ReactContextBaseJavaModule { String body = data.getString(REQUEST_BODY_KEY_STRING); MediaType contentMediaType = MediaType.parse(contentType); if (RequestBodyUtil.isGzipEncoding(contentEncoding)) { - RequestBody requestBody = RequestBodyUtil.createGzip(contentMediaType, body); + requestBody = RequestBodyUtil.createGzip(contentMediaType, body); if (requestBody == null) { ResponseUtil.onRequestError(eventEmitter, requestId, "Failed to gzip request body", null); return; } - requestBuilder.method(method, requestBody); } else { - requestBuilder.method(method, RequestBody.create(contentMediaType, body)); + requestBody = RequestBody.create(contentMediaType, body); } } else if (data.hasKey(REQUEST_BODY_KEY_BASE64)) { if (contentType == null) { @@ -380,9 +379,7 @@ public final class NetworkingModule extends ReactContextBaseJavaModule { } String base64String = data.getString(REQUEST_BODY_KEY_BASE64); MediaType contentMediaType = MediaType.parse(contentType); - requestBuilder.method( - method, - RequestBody.create(contentMediaType, ByteString.decodeBase64(base64String))); + requestBody = RequestBody.create(contentMediaType, ByteString.decodeBase64(base64String)); } else if (data.hasKey(REQUEST_BODY_KEY_URI)) { if (contentType == null) { ResponseUtil.onRequestError( @@ -403,9 +400,7 @@ public final class NetworkingModule extends ReactContextBaseJavaModule { null); return; } - requestBuilder.method( - method, - RequestBodyUtil.create(MediaType.parse(contentType), fileInputStream)); + requestBody = RequestBodyUtil.create(MediaType.parse(contentType), fileInputStream); } else if (data.hasKey(REQUEST_BODY_KEY_FORMDATA)) { if (contentType == null) { contentType = "multipart/form-data"; @@ -416,28 +411,16 @@ public final class NetworkingModule extends ReactContextBaseJavaModule { if (multipartBuilder == null) { return; } - - requestBuilder.method( - method, - RequestBodyUtil.createProgressRequest( - multipartBuilder.build(), - new ProgressListener() { - long last = System.nanoTime(); - - @Override - public void onProgress(long bytesWritten, long contentLength, boolean done) { - long now = System.nanoTime(); - if (done || shouldDispatch(now, last)) { - ResponseUtil.onDataSend(eventEmitter, requestId, bytesWritten, contentLength); - last = now; - } - } - })); + requestBody = multipartBuilder.build(); } else { // Nothing in data payload, at least nothing we could understand anyway. - requestBuilder.method(method, RequestBodyUtil.getEmptyBody(method)); + requestBody = RequestBodyUtil.getEmptyBody(method); } + requestBuilder.method( + method, + wrapRequestBodyWithProgressEmitter(requestBody, eventEmitter, requestId)); + addRequest(requestId); client.newCall(requestBuilder.build()).enqueue( new Callback() { @@ -515,6 +498,29 @@ public final class NetworkingModule extends ReactContextBaseJavaModule { }); } + private RequestBody wrapRequestBodyWithProgressEmitter( + final RequestBody requestBody, + final RCTDeviceEventEmitter eventEmitter, + final int requestId) { + if(requestBody == null) { + return null; + } + return RequestBodyUtil.createProgressRequest( + requestBody, + new ProgressListener() { + long last = System.nanoTime(); + + @Override + public void onProgress(long bytesWritten, long contentLength, boolean done) { + long now = System.nanoTime(); + if (done || shouldDispatch(now, last)) { + ResponseUtil.onDataSend(eventEmitter, requestId, bytesWritten, contentLength); + last = now; + } + } + }); + } + private void readWithProgress( RCTDeviceEventEmitter eventEmitter, int requestId, diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/network/ProgressRequestBody.java b/ReactAndroid/src/main/java/com/facebook/react/modules/network/ProgressRequestBody.java index c519cfeb4..0e478228f 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/modules/network/ProgressRequestBody.java +++ b/ReactAndroid/src/main/java/com/facebook/react/modules/network/ProgressRequestBody.java @@ -10,59 +10,72 @@ package com.facebook.react.modules.network; import java.io.IOException; + import okhttp3.MediaType; import okhttp3.RequestBody; import okio.BufferedSink; -import okio.Buffer; -import okio.Sink; -import okio.ForwardingSink; import okio.Okio; +import okio.Sink; public class ProgressRequestBody extends RequestBody { private final RequestBody mRequestBody; private final ProgressListener mProgressListener; private BufferedSink mBufferedSink; + private long mContentLength = 0L; public ProgressRequestBody(RequestBody requestBody, ProgressListener progressListener) { - mRequestBody = requestBody; - mProgressListener = progressListener; + mRequestBody = requestBody; + mProgressListener = progressListener; } @Override public MediaType contentType() { - return mRequestBody.contentType(); + return mRequestBody.contentType(); } @Override public long contentLength() throws IOException { - return mRequestBody.contentLength(); + if (mContentLength == 0) { + mContentLength = mRequestBody.contentLength(); + } + return mContentLength; } @Override public void writeTo(BufferedSink sink) throws IOException { - if (mBufferedSink == null) { - mBufferedSink = Okio.buffer(sink(sink)); + if (mBufferedSink == null) { + mBufferedSink = Okio.buffer(outputStreamSink(sink)); + } + + // contentLength changes for input streams, since we're using inputStream.available(), + // so get the length before writing to the sink + contentLength(); + + mRequestBody.writeTo(mBufferedSink); + mBufferedSink.flush(); + } + + private Sink outputStreamSink(BufferedSink sink) { + return Okio.sink(new CountingOutputStream(sink.outputStream()) { + @Override + public void write(byte[] data, int offset, int byteCount) throws IOException { + super.write(data, offset, byteCount); + sendProgressUpdate(); } - mRequestBody.writeTo(mBufferedSink); - mBufferedSink.flush(); - } - private Sink sink(Sink sink) { - return new ForwardingSink(sink) { - long bytesWritten = 0L; - long contentLength = 0L; + @Override + public void write(int data) throws IOException { + super.write(data); + sendProgressUpdate(); + } - @Override - public void write(Buffer source, long byteCount) throws IOException { - super.write(source, byteCount); - if (contentLength == 0) { - contentLength = contentLength(); - } - bytesWritten += byteCount; - mProgressListener.onProgress( - bytesWritten, contentLength, bytesWritten == contentLength); - } - }; + private void sendProgressUpdate() throws IOException { + long bytesWritten = getCount(); + long contentLength = contentLength(); + mProgressListener.onProgress( + bytesWritten, contentLength, bytesWritten == contentLength); + } + }); } -} \ No newline at end of file +} diff --git a/ReactAndroid/src/test/java/com/facebook/react/modules/network/NetworkingModuleTest.java b/ReactAndroid/src/test/java/com/facebook/react/modules/network/NetworkingModuleTest.java index a42231b55..8ad39cd16 100644 --- a/ReactAndroid/src/test/java/com/facebook/react/modules/network/NetworkingModuleTest.java +++ b/ReactAndroid/src/test/java/com/facebook/react/modules/network/NetworkingModuleTest.java @@ -200,6 +200,10 @@ public class NetworkingModuleTest { @Test public void testSuccessfulPostRequest() throws Exception { + RCTDeviceEventEmitter emitter = mock(RCTDeviceEventEmitter.class); + ReactApplicationContext context = mock(ReactApplicationContext.class); + when(context.getJSModule(any(Class.class))).thenReturn(emitter); + OkHttpClient httpClient = mock(OkHttpClient.class); when(httpClient.newCall(any(Request.class))).thenAnswer(new Answer() { @Override @@ -211,12 +215,13 @@ public class NetworkingModuleTest { OkHttpClient.Builder clientBuilder = mock(OkHttpClient.Builder.class); when(clientBuilder.build()).thenReturn(httpClient); when(httpClient.newBuilder()).thenReturn(clientBuilder); - NetworkingModule networkingModule = - new NetworkingModule(mock(ReactApplicationContext.class), "", httpClient); + NetworkingModule networkingModule = new NetworkingModule(context, "", httpClient); JavaOnlyMap body = new JavaOnlyMap(); body.putString("string", "This is request body"); + mockEvents(); + networkingModule.sendRequest( "POST", "http://somedomain/bar", @@ -630,4 +635,4 @@ public class NetworkingModuleTest { assertThat(requestIdArguments.getAllValues().contains(idx + 1)).isTrue(); } } -} \ No newline at end of file +}