Skip to content

Commit

Permalink
WebWorkers: Convert NetworkingModule to support web workers
Browse files Browse the repository at this point in the history
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
  • Loading branch information
astreet authored and Facebook Github Bot 9 committed Mar 3, 2016
1 parent 532e411 commit 9a3f11d
Show file tree
Hide file tree
Showing 3 changed files with 143 additions and 97 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,19 @@
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;

/**
* Native module that handles device hardware events like hardware back presses.
*/
public class DeviceEventManagerModule extends ReactContextBaseJavaModule {

public static interface RCTDeviceEventEmitter extends JavaScriptModule {
@SupportsWebWorkers
public interface RCTDeviceEventEmitter extends JavaScriptModule {
void emit(String eventName, @Nullable Object data);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
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;
Expand Down Expand Up @@ -116,9 +117,10 @@ public void onCatalystInstanceDestroy() {

@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,
Expand All @@ -144,7 +146,7 @@ public void sendRequest(

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);
Expand All @@ -155,15 +157,18 @@ public void sendRequest(
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);
MediaType contentMediaType = MediaType.parse(contentType);
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);
Expand All @@ -172,14 +177,17 @@ public void sendRequest(
}
} 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(
Expand All @@ -190,7 +198,8 @@ public void sendRequest(
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;
}
Expand All @@ -207,7 +216,7 @@ public void onFailure(Request request, IOException e) {
if (mShuttingDown) {
return;
}
onRequestError(requestId, e.getMessage());
onRequestError(executorToken, requestId, e.getMessage());
}

@Override
Expand All @@ -217,25 +226,28 @@ public void onResponse(Response response) throws IOException {
}

// 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));
Expand All @@ -246,14 +258,14 @@ private void readWithProgress(int requestId, ResponseBody responseBody) throws I
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();
Expand All @@ -273,31 +285,34 @@ private static int getBufferSize(ResponseBody responseBody) throws IOException {
}
}

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();
Expand All @@ -306,7 +321,7 @@ private void onResponseReceived(int requestId, Response response) {
args.pushMap(headers);
args.pushString(response.request().urlString());

getEventEmitter().emit("didReceiveNetworkResponse", args);
getEventEmitter(ExecutorToken).emit("didReceiveNetworkResponse", args);
}

private static WritableMap translateHeaders(Headers headers) {
Expand All @@ -326,7 +341,7 @@ private static WritableMap translateHeaders(Headers headers) {
}

@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<Void, Void>(getReactApplicationContext()) {
Expand All @@ -338,11 +353,21 @@ protected void doInBackgroundGuarded(Void... params) {
}

@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) {
Expand All @@ -356,7 +381,10 @@ public void clearCookies(com.facebook.react.bridge.Callback callback) {
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;
Expand All @@ -373,19 +401,25 @@ public void clearCookies(com.facebook.react.bridge.Callback callback) {
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;
Expand All @@ -394,7 +428,9 @@ public void clearCookies(com.facebook.react.bridge.Callback callback) {
/**
* 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) {
Expand Down Expand Up @@ -423,8 +459,8 @@ public void clearCookies(com.facebook.react.bridge.Callback callback) {
return headersBuilder.build();
}

private DeviceEventManagerModule.RCTDeviceEventEmitter getEventEmitter() {
private DeviceEventManagerModule.RCTDeviceEventEmitter getEventEmitter(ExecutorToken ExecutorToken) {
return getReactApplicationContext()
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class);
.getJSModule(ExecutorToken, DeviceEventManagerModule.RCTDeviceEventEmitter.class);
}
}
Loading

0 comments on commit 9a3f11d

Please sign in to comment.