From 7e7abff55ae671442502574ad26e46a5001bea09 Mon Sep 17 00:00:00 2001 From: Saurabh Aggarwal Date: Tue, 7 Jun 2016 09:32:47 -0700 Subject: [PATCH] 4.5 / N [RNFeed] Isolate reusable util methods out of NetworkingModule Reviewed By: lexs Differential Revision: D3394808 fbshipit-source-id: 19c916be693377651f2c8e21eb3dd490ec68f74c --- .../modules/network/NetworkingModule.java | 154 +++++++----------- .../react/modules/network/ResponseUtil.java | 85 ++++++++++ .../modules/network/NetworkingModuleTest.java | 28 ++-- 3 files changed, 159 insertions(+), 108 deletions(-) create mode 100644 ReactAndroid/src/main/java/com/facebook/react/modules/network/ResponseUtil.java 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 91afa78622..9c42dfcd59 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 @@ -14,7 +14,6 @@ import javax.annotation.Nullable; import java.io.IOException; import java.io.InputStream; import java.io.Reader; -import java.net.SocketTimeoutException; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -28,10 +27,9 @@ import com.facebook.react.bridge.ReactContextBaseJavaModule; import com.facebook.react.bridge.ReactMethod; import com.facebook.react.bridge.ReadableArray; import com.facebook.react.bridge.ReadableMap; -import com.facebook.react.bridge.WritableArray; import com.facebook.react.bridge.WritableMap; import com.facebook.react.common.network.OkHttpCallUtil; -import com.facebook.react.modules.core.DeviceEventManagerModule; +import com.facebook.react.modules.core.DeviceEventManagerModule.RCTDeviceEventEmitter; import okhttp3.Call; import okhttp3.Callback; @@ -178,9 +176,10 @@ public final class NetworkingModule extends ReactContextBaseJavaModule { .build(); } + final RCTDeviceEventEmitter eventEmitter = getEventEmitter(executorToken); Headers requestHeaders = extractHeaders(headers, data); if (requestHeaders == null) { - onRequestError(executorToken, requestId, "Unrecognized headers format", null); + ResponseUtil.onRequestError(eventEmitter, requestId, "Unrecognized headers format", null); return; } String contentType = requestHeaders.get(CONTENT_TYPE_HEADER_NAME); @@ -191,11 +190,11 @@ public final class NetworkingModule extends ReactContextBaseJavaModule { requestBuilder.method(method, RequestBodyUtil.getEmptyBody(method)); } else if (data.hasKey(REQUEST_BODY_KEY_STRING)) { if (contentType == null) { - onRequestError( - executorToken, - requestId, - "Payload is set but no content-type header specified", - null); + ResponseUtil.onRequestError( + eventEmitter, + requestId, + "Payload is set but no content-type header specified", + null); return; } String body = data.getString(REQUEST_BODY_KEY_STRING); @@ -203,7 +202,7 @@ public final class NetworkingModule extends ReactContextBaseJavaModule { if (RequestBodyUtil.isGzipEncoding(contentEncoding)) { RequestBody requestBody = RequestBodyUtil.createGzip(contentMediaType, body); if (requestBody == null) { - onRequestError(executorToken, requestId, "Failed to gzip request body", null); + ResponseUtil.onRequestError(eventEmitter, requestId, "Failed to gzip request body", null); return; } requestBuilder.method(method, requestBody); @@ -212,18 +211,22 @@ public final class NetworkingModule extends ReactContextBaseJavaModule { } } else if (data.hasKey(REQUEST_BODY_KEY_URI)) { if (contentType == null) { - onRequestError( - executorToken, - requestId, - "Payload is set but no content-type header specified", - null); + ResponseUtil.onRequestError( + eventEmitter, + requestId, + "Payload is set but no content-type header specified", + null); return; } String uri = data.getString(REQUEST_BODY_KEY_URI); InputStream fileInputStream = RequestBodyUtil.getFileInputStream(getReactApplicationContext(), uri); if (fileInputStream == null) { - onRequestError(executorToken, requestId, "Could not retrieve file for uri " + uri, null); + ResponseUtil.onRequestError( + eventEmitter, + requestId, + "Could not retrieve file for uri " + uri, + null); return; } requestBuilder.method( @@ -240,14 +243,18 @@ public final class NetworkingModule extends ReactContextBaseJavaModule { return; } - requestBuilder.method(method, RequestBodyUtil.createProgressRequest(multipartBuilder.build(), new ProgressRequestListener() { + requestBuilder.method( + method, + RequestBodyUtil.createProgressRequest( + multipartBuilder.build(), + new ProgressRequestListener() { long last = System.nanoTime(); @Override public void onRequestProgress(long bytesWritten, long contentLength, boolean done) { long now = System.nanoTime(); if (done || shouldDispatch(now, last)) { - onDataSend(executorToken, requestId, bytesWritten,contentLength); + ResponseUtil.onDataSend(eventEmitter, requestId, bytesWritten, contentLength); last = now; } } @@ -266,7 +273,7 @@ public final class NetworkingModule extends ReactContextBaseJavaModule { return; } removeRequest(requestId); - onRequestError(executorToken, requestId, e.getMessage(), e); + ResponseUtil.onRequestError(eventEmitter, requestId, e.getMessage(), e); } @Override @@ -276,26 +283,31 @@ public final class NetworkingModule extends ReactContextBaseJavaModule { } removeRequest(requestId); // Before we touch the body send headers to JS - onResponseReceived(executorToken, requestId, response); + ResponseUtil.onResponseReceived( + eventEmitter, + requestId, + response.code(), + translateHeaders(response.headers()), + response.request().url().toString()); ResponseBody responseBody = response.body(); try { if (useIncrementalUpdates) { - readWithProgress(executorToken, requestId, responseBody); - onRequestSuccess(executorToken, requestId); + readWithProgress(eventEmitter, requestId, responseBody); + ResponseUtil.onRequestSuccess(eventEmitter, requestId); } else { - onDataReceived(executorToken, requestId, responseBody.string()); - onRequestSuccess(executorToken, requestId); + ResponseUtil.onDataReceived(eventEmitter, requestId, responseBody.string()); + ResponseUtil.onRequestSuccess(eventEmitter, requestId); } } catch (IOException e) { - onRequestError(executorToken, requestId, e.getMessage(), e); + ResponseUtil.onRequestError(eventEmitter, requestId, e.getMessage(), e); } } }); } private void readWithProgress( - ExecutorToken executorToken, + RCTDeviceEventEmitter eventEmitter, int requestId, ResponseBody responseBody) throws IOException { Reader reader = responseBody.charStream(); @@ -303,7 +315,7 @@ public final class NetworkingModule extends ReactContextBaseJavaModule { char[] buffer = new char[MAX_CHUNK_SIZE_BETWEEN_FLUSHES]; int read; while ((read = reader.read(buffer)) != -1) { - onDataReceived(executorToken, requestId, new String(buffer, 0, read)); + ResponseUtil.onDataReceived(eventEmitter, requestId, new String(buffer, 0, read)); } } finally { reader.close(); @@ -314,57 +326,6 @@ public final class NetworkingModule extends ReactContextBaseJavaModule { return last + CHUNK_TIMEOUT_NS < now; } - private void onDataSend(ExecutorToken ExecutorToken, int requestId, long progress, long total) { - WritableArray args = Arguments.createArray(); - args.pushInt(requestId); - args.pushInt((int) progress); - args.pushInt((int) total); - getEventEmitter(ExecutorToken).emit("didSendNetworkData", args); - } - - private void onDataReceived(ExecutorToken ExecutorToken, int requestId, String data) { - WritableArray args = Arguments.createArray(); - args.pushInt(requestId); - args.pushString(data); - - getEventEmitter(ExecutorToken).emit("didReceiveNetworkData", args); - } - - private void onRequestError(ExecutorToken ExecutorToken, int requestId, String error, IOException e) { - WritableArray args = Arguments.createArray(); - args.pushInt(requestId); - args.pushString(error); - - if ((e != null) && (e.getClass() == SocketTimeoutException.class)) { - args.pushBoolean(true); // last argument is a time out boolean - } - - getEventEmitter(ExecutorToken).emit("didCompleteNetworkResponse", args); - } - - private void onRequestSuccess(ExecutorToken ExecutorToken, int requestId) { - WritableArray args = Arguments.createArray(); - args.pushInt(requestId); - args.pushNull(); - - getEventEmitter(ExecutorToken).emit("didCompleteNetworkResponse", args); - } - - private void onResponseReceived( - ExecutorToken ExecutorToken, - int requestId, - Response response) { - WritableMap headers = translateHeaders(response.headers()); - - WritableArray args = Arguments.createArray(); - args.pushInt(requestId); - args.pushInt(response.code()); - args.pushMap(headers); - args.pushString(response.request().url().toString()); - - getEventEmitter(ExecutorToken).emit("didReceiveNetworkResponse", args); - } - private synchronized void addRequest(int requestId) { mRequestIds.add(requestId); } @@ -430,6 +391,7 @@ public final class NetworkingModule extends ReactContextBaseJavaModule { ReadableArray body, String contentType, int requestId) { + RCTDeviceEventEmitter eventEmitter = getEventEmitter(ExecutorToken); MultipartBody.Builder multipartBuilder = new MultipartBody.Builder(); multipartBuilder.setType(MediaType.parse(contentType)); @@ -440,11 +402,11 @@ public final class NetworkingModule extends ReactContextBaseJavaModule { ReadableArray headersArray = bodyPart.getArray("headers"); Headers headers = extractHeaders(headersArray, null); if (headers == null) { - onRequestError( - ExecutorToken, - requestId, - "Missing or invalid header format for FormData part.", - null); + ResponseUtil.onRequestError( + eventEmitter, + requestId, + "Missing or invalid header format for FormData part.", + null); return null; } MediaType partContentType = null; @@ -461,27 +423,27 @@ 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( - ExecutorToken, - requestId, - "Binary FormData part needs a content-type header.", - null); + ResponseUtil.onRequestError( + eventEmitter, + requestId, + "Binary FormData part needs a content-type header.", + null); return null; } String fileContentUriStr = bodyPart.getString(REQUEST_BODY_KEY_URI); InputStream fileInputStream = RequestBodyUtil.getFileInputStream(getReactApplicationContext(), fileContentUriStr); if (fileInputStream == null) { - onRequestError( - ExecutorToken, - requestId, - "Could not retrieve file for uri " + fileContentUriStr, - null); + ResponseUtil.onRequestError( + eventEmitter, + requestId, + "Could not retrieve file for uri " + fileContentUriStr, + null); return null; } multipartBuilder.addPart(headers, RequestBodyUtil.create(partContentType, fileInputStream)); } else { - onRequestError(ExecutorToken, requestId, "Unrecognized FormData part.", null); + ResponseUtil.onRequestError(eventEmitter, requestId, "Unrecognized FormData part.", null); } } return multipartBuilder; @@ -519,8 +481,8 @@ public final class NetworkingModule extends ReactContextBaseJavaModule { return headersBuilder.build(); } - private DeviceEventManagerModule.RCTDeviceEventEmitter getEventEmitter(ExecutorToken ExecutorToken) { + private RCTDeviceEventEmitter getEventEmitter(ExecutorToken ExecutorToken) { return getReactApplicationContext() - .getJSModule(ExecutorToken, DeviceEventManagerModule.RCTDeviceEventEmitter.class); + .getJSModule(ExecutorToken, RCTDeviceEventEmitter.class); } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/network/ResponseUtil.java b/ReactAndroid/src/main/java/com/facebook/react/modules/network/ResponseUtil.java new file mode 100644 index 0000000000..eb2b6e2bbb --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/modules/network/ResponseUtil.java @@ -0,0 +1,85 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +package com.facebook.react.modules.network; + +import java.io.IOException; +import java.net.SocketTimeoutException; + +import com.facebook.react.bridge.Arguments; +import com.facebook.react.bridge.WritableArray; +import com.facebook.react.bridge.WritableMap; +import com.facebook.react.modules.core.DeviceEventManagerModule.RCTDeviceEventEmitter; + +/** + * Util methods to send network responses to JS. + */ +public class ResponseUtil { + public static void onDataSend( + RCTDeviceEventEmitter eventEmitter, + int requestId, + long progress, + long total) { + WritableArray args = Arguments.createArray(); + args.pushInt(requestId); + args.pushInt((int) progress); + args.pushInt((int) total); + eventEmitter.emit("didSendNetworkData", args); + } + + public static void onDataReceived( + RCTDeviceEventEmitter eventEmitter, + int requestId, + String data) { + WritableArray args = Arguments.createArray(); + args.pushInt(requestId); + args.pushString(data); + + eventEmitter.emit("didReceiveNetworkData", args); + } + + public static void onRequestError( + RCTDeviceEventEmitter eventEmitter, + int requestId, + String error, + IOException e) { + WritableArray args = Arguments.createArray(); + args.pushInt(requestId); + args.pushString(error); + + if ((e != null) && (e.getClass() == SocketTimeoutException.class)) { + args.pushBoolean(true); // last argument is a time out boolean + } + + eventEmitter.emit("didCompleteNetworkResponse", args); + } + + public static void onRequestSuccess(RCTDeviceEventEmitter eventEmitter, int requestId) { + WritableArray args = Arguments.createArray(); + args.pushInt(requestId); + args.pushNull(); + + eventEmitter.emit("didCompleteNetworkResponse", args); + } + + public static void onResponseReceived( + RCTDeviceEventEmitter eventEmitter, + int requestId, + int statusCode, + WritableMap headers, + String url) { + WritableArray args = Arguments.createArray(); + args.pushInt(requestId); + args.pushInt(statusCode); + args.pushMap(headers); + args.pushString(url); + + eventEmitter.emit("didReceiveNetworkResponse", args); + } +} 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 8e5f40cb78..c06a99f9ad 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 @@ -84,8 +84,8 @@ public class NetworkingModuleTest { return callMock; } }); - - NetworkingModule networkingModule = new NetworkingModule(null, "", httpClient); + NetworkingModule networkingModule = + new NetworkingModule(mock(ReactApplicationContext.class), "", httpClient); networkingModule.sendRequest( mock(ExecutorToken.class), @@ -196,8 +196,8 @@ public class NetworkingModuleTest { return callMock; } }); - - NetworkingModule networkingModule = new NetworkingModule(null, "", httpClient); + NetworkingModule networkingModule = + new NetworkingModule(mock(ReactApplicationContext.class), "", httpClient); JavaOnlyMap body = new JavaOnlyMap(); body.putString("string", "This is request body"); @@ -234,7 +234,8 @@ public class NetworkingModuleTest { return callMock; } }); - NetworkingModule networkingModule = new NetworkingModule(null, "", httpClient); + NetworkingModule networkingModule = + new NetworkingModule(mock(ReactApplicationContext.class), "", httpClient); List headers = Arrays.asList( JavaOnlyArray.of("Accept", "text/plain"), @@ -287,8 +288,8 @@ public class NetworkingModuleTest { return callMock; } }); - - NetworkingModule networkingModule = new NetworkingModule(null, "", httpClient); + NetworkingModule networkingModule = + new NetworkingModule(mock(ReactApplicationContext.class), "", httpClient); networkingModule.sendRequest( mock(ExecutorToken.class), "POST", @@ -347,8 +348,8 @@ public class NetworkingModuleTest { return callMock; } }); - - NetworkingModule networkingModule = new NetworkingModule(null, "", httpClient); + NetworkingModule networkingModule = + new NetworkingModule(mock(ReactApplicationContext.class), "", httpClient); networkingModule.sendRequest( mock(ExecutorToken.class), "POST", @@ -445,7 +446,8 @@ public class NetworkingModuleTest { } }); - NetworkingModule networkingModule = new NetworkingModule(null, "", httpClient); + NetworkingModule networkingModule = + new NetworkingModule(mock(ReactApplicationContext.class), "", httpClient); networkingModule.sendRequest( mock(ExecutorToken.class), "POST", @@ -501,7 +503,8 @@ public class NetworkingModuleTest { return calls[(Integer) request.tag() - 1]; } }); - NetworkingModule networkingModule = new NetworkingModule(null, "", httpClient); + NetworkingModule networkingModule = + new NetworkingModule(mock(ReactApplicationContext.class), "", httpClient); networkingModule.initialize(); for (int idx = 0; idx < requests; idx++) { @@ -547,7 +550,8 @@ public class NetworkingModuleTest { return calls[(Integer) request.tag() - 1]; } }); - NetworkingModule networkingModule = new NetworkingModule(null, "", httpClient); + NetworkingModule networkingModule = + new NetworkingModule(mock(ReactApplicationContext.class), "", httpClient); for (int idx = 0; idx < requests; idx++) { networkingModule.sendRequest(