From 9a3f11d3e701e36dfae1cb55864f7efebc171935 Mon Sep 17 00:00:00 2001 From: Andy Street Date: Thu, 3 Mar 2016 11:06:45 -0800 Subject: [PATCH] WebWorkers: Convert NetworkingModule to support web workers Summary: Converts NetworkingModule to dispatch callbacks to the appropriate ExecutionContext. Reviewed By: lexs Differential Revision: D2932170 fb-gh-sync-id: ac77eec1a176ede4d2257bfd8ddb13153331c8a4 shipit-source-id: ac77eec1a176ede4d2257bfd8ddb13153331c8a4 --- .../core/DeviceEventManagerModule.java | 5 +- .../modules/network/NetworkingModule.java | 108 ++++++++++----- .../modules/network/NetworkingModuleTest.java | 127 ++++++++++-------- 3 files changed, 143 insertions(+), 97 deletions(-) diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/core/DeviceEventManagerModule.java b/ReactAndroid/src/main/java/com/facebook/react/modules/core/DeviceEventManagerModule.java index 1329a5b7c..37a34283c 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/modules/core/DeviceEventManagerModule.java +++ b/ReactAndroid/src/main/java/com/facebook/react/modules/core/DeviceEventManagerModule.java @@ -12,10 +12,10 @@ package com.facebook.react.modules.core; import javax.annotation.Nullable; import com.facebook.react.bridge.ReactApplicationContext; -import com.facebook.react.bridge.ReactContext; import com.facebook.react.bridge.ReactContextBaseJavaModule; import com.facebook.react.bridge.ReactMethod; import com.facebook.react.bridge.JavaScriptModule; +import com.facebook.react.bridge.SupportsWebWorkers; import com.facebook.react.bridge.UiThreadUtil; /** @@ -23,7 +23,8 @@ import com.facebook.react.bridge.UiThreadUtil; */ public class DeviceEventManagerModule extends ReactContextBaseJavaModule { - public static interface RCTDeviceEventEmitter extends JavaScriptModule { + @SupportsWebWorkers + public interface RCTDeviceEventEmitter extends JavaScriptModule { void emit(String eventName, @Nullable Object data); } 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 2bdcd82d6..cd9e6b98c 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 @@ -18,6 +18,7 @@ import java.io.Reader; import java.util.concurrent.TimeUnit; import com.facebook.react.bridge.Arguments; +import com.facebook.react.bridge.ExecutorToken; import com.facebook.react.bridge.GuardedAsyncTask; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactContextBaseJavaModule; @@ -116,9 +117,10 @@ public final class NetworkingModule extends ReactContextBaseJavaModule { @ReactMethod /** - * @param timeout value of 0 results in no timeout - */ + * @param timeout value of 0 results in no timeout + */ public void sendRequest( + final ExecutorToken executorToken, String method, String url, final int requestId, @@ -144,7 +146,7 @@ public final class NetworkingModule extends ReactContextBaseJavaModule { Headers requestHeaders = extractHeaders(headers, data); if (requestHeaders == null) { - onRequestError(requestId, "Unrecognized headers format"); + onRequestError(executorToken, requestId, "Unrecognized headers format"); return; } String contentType = requestHeaders.get(CONTENT_TYPE_HEADER_NAME); @@ -155,7 +157,10 @@ public final class NetworkingModule extends ReactContextBaseJavaModule { requestBuilder.method(method, RequestBodyUtil.getEmptyBody(method)); } else if (data.hasKey(REQUEST_BODY_KEY_STRING)) { if (contentType == null) { - onRequestError(requestId, "Payload is set but no content-type header specified"); + onRequestError( + executorToken, + requestId, + "Payload is set but no content-type header specified"); return; } String body = data.getString(REQUEST_BODY_KEY_STRING); @@ -163,7 +168,7 @@ public final class NetworkingModule extends ReactContextBaseJavaModule { if (RequestBodyUtil.isGzipEncoding(contentEncoding)) { RequestBody requestBody = RequestBodyUtil.createGzip(contentMediaType, body); if (requestBody == null) { - onRequestError(requestId, "Failed to gzip request body"); + onRequestError(executorToken, requestId, "Failed to gzip request body"); return; } requestBuilder.method(method, requestBody); @@ -172,14 +177,17 @@ public final class NetworkingModule extends ReactContextBaseJavaModule { } } else if (data.hasKey(REQUEST_BODY_KEY_URI)) { if (contentType == null) { - onRequestError(requestId, "Payload is set but no content-type header specified"); + onRequestError( + executorToken, + requestId, + "Payload is set but no content-type header specified"); return; } String uri = data.getString(REQUEST_BODY_KEY_URI); InputStream fileInputStream = RequestBodyUtil.getFileInputStream(getReactApplicationContext(), uri); if (fileInputStream == null) { - onRequestError(requestId, "Could not retrieve file for uri " + uri); + onRequestError(executorToken, requestId, "Could not retrieve file for uri " + uri); return; } requestBuilder.method( @@ -190,7 +198,8 @@ public final class NetworkingModule extends ReactContextBaseJavaModule { contentType = "multipart/form-data"; } ReadableArray parts = data.getArray(REQUEST_BODY_KEY_FORMDATA); - MultipartBuilder multipartBuilder = constructMultipartBody(parts, contentType, requestId); + MultipartBuilder multipartBuilder = + constructMultipartBody(executorToken, parts, contentType, requestId); if (multipartBuilder == null) { return; } @@ -207,7 +216,7 @@ public final class NetworkingModule extends ReactContextBaseJavaModule { if (mShuttingDown) { return; } - onRequestError(requestId, e.getMessage()); + onRequestError(executorToken, requestId, e.getMessage()); } @Override @@ -217,25 +226,28 @@ public final class NetworkingModule extends ReactContextBaseJavaModule { } // Before we touch the body send headers to JS - onResponseReceived(requestId, response); + onResponseReceived(executorToken, requestId, response); ResponseBody responseBody = response.body(); try { if (useIncrementalUpdates) { - readWithProgress(requestId, responseBody); - onRequestSuccess(requestId); + readWithProgress(executorToken, requestId, responseBody); + onRequestSuccess(executorToken, requestId); } else { - onDataReceived(requestId, responseBody.string()); - onRequestSuccess(requestId); + onDataReceived(executorToken, requestId, responseBody.string()); + onRequestSuccess(executorToken, requestId); } } catch (IOException e) { - onRequestError(requestId, e.getMessage()); + onRequestError(executorToken, requestId, e.getMessage()); } } }); } - private void readWithProgress(int requestId, ResponseBody responseBody) throws IOException { + private void readWithProgress( + ExecutorToken executorToken, + int requestId, + ResponseBody responseBody) throws IOException { Reader reader = responseBody.charStream(); try { StringBuilder sb = new StringBuilder(getBufferSize(responseBody)); @@ -246,14 +258,14 @@ public final class NetworkingModule extends ReactContextBaseJavaModule { sb.append(buffer, 0, read); long now = System.nanoTime(); if (shouldDispatch(now, last)) { - onDataReceived(requestId, sb.toString()); + onDataReceived(executorToken, requestId, sb.toString()); sb.setLength(0); last = now; } } if (sb.length() > 0) { - onDataReceived(requestId, sb.toString()); + onDataReceived(executorToken, requestId, sb.toString()); } } finally { reader.close(); @@ -273,31 +285,34 @@ public final class NetworkingModule extends ReactContextBaseJavaModule { } } - private void onDataReceived(int requestId, String data) { + private void onDataReceived(ExecutorToken ExecutorToken, int requestId, String data) { WritableArray args = Arguments.createArray(); args.pushInt(requestId); args.pushString(data); - getEventEmitter().emit("didReceiveNetworkData", args); + getEventEmitter(ExecutorToken).emit("didReceiveNetworkData", args); } - private void onRequestError(int requestId, String error) { + private void onRequestError(ExecutorToken ExecutorToken, int requestId, String error) { WritableArray args = Arguments.createArray(); args.pushInt(requestId); args.pushString(error); - getEventEmitter().emit("didCompleteNetworkResponse", args); + getEventEmitter(ExecutorToken).emit("didCompleteNetworkResponse", args); } - private void onRequestSuccess(int requestId) { + private void onRequestSuccess(ExecutorToken ExecutorToken, int requestId) { WritableArray args = Arguments.createArray(); args.pushInt(requestId); args.pushNull(); - getEventEmitter().emit("didCompleteNetworkResponse", args); + getEventEmitter(ExecutorToken).emit("didCompleteNetworkResponse", args); } - private void onResponseReceived(int requestId, Response response) { + private void onResponseReceived( + ExecutorToken ExecutorToken, + int requestId, + Response response) { WritableMap headers = translateHeaders(response.headers()); WritableArray args = Arguments.createArray(); @@ -306,7 +321,7 @@ public final class NetworkingModule extends ReactContextBaseJavaModule { args.pushMap(headers); args.pushString(response.request().urlString()); - getEventEmitter().emit("didReceiveNetworkResponse", args); + getEventEmitter(ExecutorToken).emit("didReceiveNetworkResponse", args); } private static WritableMap translateHeaders(Headers headers) { @@ -326,7 +341,7 @@ public final class NetworkingModule extends ReactContextBaseJavaModule { } @ReactMethod - public void abortRequest(final int requestId) { + public void abortRequest(ExecutorToken executorToken, final int requestId) { // We have to use AsyncTask since this might trigger a NetworkOnMainThreadException, this is an // open issue on OkHttp: https://github.com/square/okhttp/issues/869 new GuardedAsyncTask(getReactApplicationContext()) { @@ -338,11 +353,21 @@ public final class NetworkingModule extends ReactContextBaseJavaModule { } @ReactMethod - public void clearCookies(com.facebook.react.bridge.Callback callback) { + public void clearCookies( + ExecutorToken executorToken, + com.facebook.react.bridge.Callback callback) { mCookieHandler.clearCookies(callback); } - private @Nullable MultipartBuilder constructMultipartBody( + @Override + public boolean supportsWebWorkers() { + return true; + } + + private + @Nullable + MultipartBuilder constructMultipartBody( + ExecutorToken ExecutorToken, ReadableArray body, String contentType, int requestId) { @@ -356,7 +381,10 @@ public final class NetworkingModule extends ReactContextBaseJavaModule { ReadableArray headersArray = bodyPart.getArray("headers"); Headers headers = extractHeaders(headersArray, null); if (headers == null) { - onRequestError(requestId, "Missing or invalid header format for FormData part."); + onRequestError( + ExecutorToken, + requestId, + "Missing or invalid header format for FormData part."); return null; } MediaType partContentType = null; @@ -373,19 +401,25 @@ public final class NetworkingModule extends ReactContextBaseJavaModule { multipartBuilder.addPart(headers, RequestBody.create(partContentType, bodyValue)); } else if (bodyPart.hasKey(REQUEST_BODY_KEY_URI)) { if (partContentType == null) { - onRequestError(requestId, "Binary FormData part needs a content-type header."); + onRequestError( + ExecutorToken, + requestId, + "Binary FormData part needs a content-type header."); return null; } String fileContentUriStr = bodyPart.getString(REQUEST_BODY_KEY_URI); InputStream fileInputStream = RequestBodyUtil.getFileInputStream(getReactApplicationContext(), fileContentUriStr); if (fileInputStream == null) { - onRequestError(requestId, "Could not retrieve file for uri " + fileContentUriStr); + onRequestError( + ExecutorToken, + requestId, + "Could not retrieve file for uri " + fileContentUriStr); return null; } multipartBuilder.addPart(headers, RequestBodyUtil.create(partContentType, fileInputStream)); } else { - onRequestError(requestId, "Unrecognized FormData part."); + onRequestError(ExecutorToken, requestId, "Unrecognized FormData part."); } } return multipartBuilder; @@ -394,7 +428,9 @@ public final class NetworkingModule extends ReactContextBaseJavaModule { /** * Extracts the headers from the Array. If the format is invalid, this method will return null. */ - private @Nullable Headers extractHeaders( + private + @Nullable + Headers extractHeaders( @Nullable ReadableArray headersArray, @Nullable ReadableMap requestData) { if (headersArray == null) { @@ -423,8 +459,8 @@ public final class NetworkingModule extends ReactContextBaseJavaModule { return headersBuilder.build(); } - private DeviceEventManagerModule.RCTDeviceEventEmitter getEventEmitter() { + private DeviceEventManagerModule.RCTDeviceEventEmitter getEventEmitter(ExecutorToken ExecutorToken) { return getReactApplicationContext() - .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class); + .getJSModule(ExecutorToken, DeviceEventManagerModule.RCTDeviceEventEmitter.class); } } 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 63707bae0..ce3896123 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 @@ -14,6 +14,7 @@ import java.util.Arrays; import java.util.List; import com.facebook.react.bridge.Arguments; +import com.facebook.react.bridge.ExecutorToken; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactContext; import com.facebook.react.bridge.JavaOnlyArray; @@ -88,13 +89,14 @@ public class NetworkingModuleTest { NetworkingModule networkingModule = new NetworkingModule(null, "", httpClient); networkingModule.sendRequest( - "GET", - "http://somedomain/foo", - 0, - JavaOnlyArray.of(), - null, - true, - 0); + mock(ExecutorToken.class), + "GET", + "http://somedomain/foo", + 0, + JavaOnlyArray.of(), + null, + true, + 0); ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(Request.class); verify(httpClient).newCall(argumentCaptor.capture()); @@ -108,7 +110,7 @@ public class NetworkingModuleTest { public void testFailGetWithInvalidHeadersStruct() throws Exception { RCTDeviceEventEmitter emitter = mock(RCTDeviceEventEmitter.class); ReactApplicationContext context = mock(ReactApplicationContext.class); - when(context.getJSModule(any(Class.class))).thenReturn(emitter); + when(context.getJSModule(any(ExecutorToken.class), any(Class.class))).thenReturn(emitter); OkHttpClient httpClient = mock(OkHttpClient.class); NetworkingModule networkingModule = new NetworkingModule(context, "", httpClient); @@ -118,13 +120,14 @@ public class NetworkingModuleTest { mockEvents(); networkingModule.sendRequest( - "GET", - "http://somedoman/foo", - 0, - JavaOnlyArray.from(invalidHeaders), - null, - true, - 0); + mock(ExecutorToken.class), + "GET", + "http://somedoman/foo", + 0, + JavaOnlyArray.from(invalidHeaders), + null, + true, + 0); verifyErrorEmit(emitter, 0); } @@ -133,7 +136,7 @@ public class NetworkingModuleTest { public void testFailPostWithoutContentType() throws Exception { RCTDeviceEventEmitter emitter = mock(RCTDeviceEventEmitter.class); ReactApplicationContext context = mock(ReactApplicationContext.class); - when(context.getJSModule(any(Class.class))).thenReturn(emitter); + when(context.getJSModule(any(ExecutorToken.class), any(Class.class))).thenReturn(emitter); OkHttpClient httpClient = mock(OkHttpClient.class); NetworkingModule networkingModule = new NetworkingModule(context, "", httpClient); @@ -144,13 +147,14 @@ public class NetworkingModuleTest { mockEvents(); networkingModule.sendRequest( - "POST", - "http://somedomain/bar", - 0, - JavaOnlyArray.of(), - body, - true, - 0); + mock(ExecutorToken.class), + "POST", + "http://somedomain/bar", + 0, + JavaOnlyArray.of(), + body, + true, + 0); verifyErrorEmit(emitter, 0); } @@ -184,7 +188,7 @@ public class NetworkingModuleTest { } @Test - public void testSuccessfullPostRequest() throws Exception { + public void testSuccessfulPostRequest() throws Exception { OkHttpClient httpClient = mock(OkHttpClient.class); when(httpClient.newCall(any(Request.class))).thenAnswer(new Answer() { @Override @@ -200,13 +204,14 @@ public class NetworkingModuleTest { body.putString("string", "This is request body"); networkingModule.sendRequest( - "POST", - "http://somedomain/bar", - 0, - JavaOnlyArray.of(JavaOnlyArray.of("Content-Type", "text/plain")), - body, - true, - 0); + mock(ExecutorToken.class), + "POST", + "http://somedomain/bar", + 0, + JavaOnlyArray.of(JavaOnlyArray.of("Content-Type", "text/plain")), + body, + true, + 0); ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(Request.class); verify(httpClient).newCall(argumentCaptor.capture()); @@ -237,13 +242,14 @@ public class NetworkingModuleTest { JavaOnlyArray.of("User-Agent", "React test agent/1.0")); networkingModule.sendRequest( - "GET", - "http://someurl/baz", - 0, - JavaOnlyArray.from(headers), - null, - true, - 0); + mock(ExecutorToken.class), + "GET", + "http://someurl/baz", + 0, + JavaOnlyArray.from(headers), + null, + true, + 0); ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(Request.class); verify(httpClient).newCall(argumentCaptor.capture()); Headers requestHeaders = argumentCaptor.getValue().headers(); @@ -284,13 +290,14 @@ public class NetworkingModuleTest { NetworkingModule networkingModule = new NetworkingModule(null, "", httpClient); networkingModule.sendRequest( - "POST", - "http://someurl/uploadFoo", - 0, - new JavaOnlyArray(), - body, - true, - 0); + mock(ExecutorToken.class), + "POST", + "http://someurl/uploadFoo", + 0, + new JavaOnlyArray(), + body, + true, + 0); // verify url, method, headers ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(Request.class); @@ -342,13 +349,14 @@ public class NetworkingModuleTest { NetworkingModule networkingModule = new NetworkingModule(null, "", httpClient); networkingModule.sendRequest( - "POST", - "http://someurl/uploadFoo", - 0, - JavaOnlyArray.from(headers), - body, - true, - 0); + mock(ExecutorToken.class), + "POST", + "http://someurl/uploadFoo", + 0, + JavaOnlyArray.from(headers), + body, + true, + 0); // verify url, method, headers ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(Request.class); @@ -437,13 +445,14 @@ public class NetworkingModuleTest { NetworkingModule networkingModule = new NetworkingModule(null, "", httpClient); networkingModule.sendRequest( - "POST", - "http://someurl/uploadFoo", - 0, - JavaOnlyArray.from(headers), - body, - true, - 0); + mock(ExecutorToken.class), + "POST", + "http://someurl/uploadFoo", + 0, + JavaOnlyArray.from(headers), + body, + true, + 0); // verify RequestBodyPart for image PowerMockito.verifyStatic(times(1));