From 3e4cf0e8d278d3c9fdb4d3aec981a257baf4d24d Mon Sep 17 00:00:00 2001 From: allengleyzer Date: Tue, 14 Nov 2017 10:50:52 -0800 Subject: [PATCH] Added progress updates for all XMLHttpRequest upload types Summary: Previously, only form-data request bodies emitted upload progress updates. Now, other request body types will also emit updates. Addresses issues: https://github.com/facebook/react-native/issues/15724 https://github.com/facebook/react-native/issues/11853 This is a bug fix for functionality that's missing on Android. These events are already working correctly on iOS. Run the following code on Android, and ensure that events are being sent: ``` const fileUri = 'file:///my_file.dat'; const url = 'http://my_post_url.com/'; const xhr = new XMLHttpRequest(); xhr.upload.onprogress = (event) => { console.log('progress: ' + event.loaded + ' / ' + event.total); } xhr.onreadystatechange = () => {if (xhr.readyState === 4) console.log('done');} console.log('start'); xhr.open('POST', url); xhr.send({ uri: fileUri }); // sending a file (wasn't sending progress) xhr.send("some big string"); // sending a string (wasn't sending progress) const formData = new FormData(); formData.set('test', 'data'); xhr.send(formData); // sending form data (was already working) ``` [ANDROID] [BUGFIX] [XMLHttpRequest] - Added progress updates for all XMLHttpRequest upload types Previously, only form-data request bodies emitted upload progress updates. Now, other request body types will also emit updates. Addresses issues: https://github.com/facebook/react-native/issues/15724 https://github.com/facebook/react-native/issues/11853 Closes https://github.com/facebook/react-native/pull/16541 Differential Revision: D6325252 Pulled By: hramos fbshipit-source-id: 4fe617216293e6f451e2a1af4fa872e8f56d4f93 --- .../modules/network/NetworkingModule.java | 63 ++++++++++--------- .../modules/network/NetworkingModuleTest.java | 9 ++- 2 files changed, 42 insertions(+), 30 deletions(-) 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 297cd1ef4..58fd60545 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 @@ -235,8 +235,9 @@ public final class NetworkingModule extends ReactContextBaseJavaModule { String contentEncoding = requestHeaders.get(CONTENT_ENCODING_HEADER_NAME); requestBuilder.headers(requestHeaders); + RequestBody requestBody; if (data == null) { - requestBuilder.method(method, RequestBodyUtil.getEmptyBody(method)); + requestBody = RequestBodyUtil.getEmptyBody(method); } else if (data.hasKey(REQUEST_BODY_KEY_STRING)) { if (contentType == null) { ResponseUtil.onRequestError( @@ -249,14 +250,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) { @@ -269,9 +269,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( @@ -292,9 +290,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"; @@ -305,28 +301,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() { @@ -394,6 +378,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/test/java/com/facebook/react/modules/network/NetworkingModuleTest.java b/ReactAndroid/src/test/java/com/facebook/react/modules/network/NetworkingModuleTest.java index 7b512bc25..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",