revert D2631410

Differential Revision: D2655673

fb-gh-sync-id: 115247373767690e63a0d6ce812a578d26b47289
This commit is contained in:
Nathan Spaun
2015-11-13 17:35:25 -08:00
committed by facebook-github-bot-5
parent e406dccaf9
commit 39ec693866
7 changed files with 165 additions and 247 deletions

View File

@@ -13,20 +13,17 @@ import javax.annotation.Nullable;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.Callback;
import com.facebook.react.bridge.GuardedAsyncTask;
import com.facebook.react.bridge.ReactApplicationContext;
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.modules.core.DeviceEventManagerModule;
import com.squareup.okhttp.Callback;
import com.squareup.okhttp.Headers;
import com.squareup.okhttp.MediaType;
import com.squareup.okhttp.MultipartBuilder;
@@ -34,9 +31,6 @@ import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.Request;
import com.squareup.okhttp.RequestBody;
import com.squareup.okhttp.Response;
import com.squareup.okhttp.ResponseBody;
import static java.lang.Math.min;
/**
* Implements the XMLHttpRequest JavaScript interface.
@@ -50,11 +44,6 @@ public final class NetworkingModule extends ReactContextBaseJavaModule {
private static final String REQUEST_BODY_KEY_FORMDATA = "formData";
private static final String USER_AGENT_HEADER_NAME = "user-agent";
private static final int MIN_BUFFER_SIZE = 8 * 1024; // 8kb
private static final int MAX_BUFFER_SIZE = 512 * 1024; // 512kb
private static final int CHUNK_TIMEOUT_NS = 100 * 1000000; // 100ms
private final OkHttpClient mClient;
private final @Nullable String mDefaultUserAgent;
private boolean mShuttingDown;
@@ -104,10 +93,15 @@ public final class NetworkingModule extends ReactContextBaseJavaModule {
public void sendRequest(
String method,
String url,
final int requestId,
int requestId,
ReadableArray headers,
ReadableMap data,
final boolean useIncrementalUpdates) {
final Callback callback) {
// We need to call the callback to avoid leaking memory on JS even when input for sending
// request is erroneous or insufficient. For non-http based failures we use code 0, which is
// interpreted as a transport error.
// Callback accepts following arguments: responseCode, headersString, responseBody
Request.Builder requestBuilder = new Request.Builder().url(url);
if (requestId != 0) {
@@ -116,7 +110,7 @@ public final class NetworkingModule extends ReactContextBaseJavaModule {
Headers requestHeaders = extractHeaders(headers, data);
if (requestHeaders == null) {
onRequestError(requestId, "Unrecognized headers format");
callback.invoke(0, null, "Unrecognized headers format");
return;
}
String contentType = requestHeaders.get(CONTENT_TYPE_HEADER_NAME);
@@ -127,7 +121,7 @@ public final class NetworkingModule extends ReactContextBaseJavaModule {
requestBuilder.method(method, null);
} else if (data.hasKey(REQUEST_BODY_KEY_STRING)) {
if (contentType == null) {
onRequestError(requestId, "Payload is set but no content-type header specified");
callback.invoke(0, null, "Payload is set but no content-type header specified");
return;
}
String body = data.getString(REQUEST_BODY_KEY_STRING);
@@ -135,7 +129,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");
callback.invoke(0, null, "Failed to gzip request body");
return;
}
requestBuilder.method(method, requestBody);
@@ -144,14 +138,14 @@ 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");
callback.invoke(0, null, "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);
callback.invoke(0, null, "Could not retrieve file for uri " + uri);
return;
}
requestBuilder.method(
@@ -162,7 +156,7 @@ 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(parts, contentType, callback);
if (multipartBuilder == null) {
return;
}
@@ -174,13 +168,16 @@ public final class NetworkingModule extends ReactContextBaseJavaModule {
}
mClient.newCall(requestBuilder.build()).enqueue(
new Callback() {
new com.squareup.okhttp.Callback() {
@Override
public void onFailure(Request request, IOException e) {
if (mShuttingDown) {
return;
}
onRequestError(requestId, e.getMessage());
// We need to call the callback to avoid leaking memory on JS even when input for
// sending request is erronous or insufficient. For non-http based failures we use
// code 0, which is interpreted as a transport error
callback.invoke(0, null, e.getMessage());
}
@Override
@@ -188,115 +185,34 @@ public final class NetworkingModule extends ReactContextBaseJavaModule {
if (mShuttingDown) {
return;
}
// Before we touch the body send headers to JS
onResponseReceived(requestId, response);
ResponseBody responseBody = response.body();
String responseBody;
try {
if (useIncrementalUpdates) {
readWithProgress(requestId, responseBody);
onRequestSuccess(requestId);
} else {
onDataReceived(requestId, responseBody.string());
onRequestSuccess(requestId);
}
responseBody = response.body().string();
} catch (IOException e) {
onRequestError(requestId, e.getMessage());
// The stream has been cancelled or closed, nothing we can do
callback.invoke(0, null, e.getMessage());
return;
}
WritableMap responseHeaders = Arguments.createMap();
Headers headers = response.headers();
for (int i = 0; i < headers.size(); i++) {
String headerName = headers.name(i);
// multiple values for the same header
if (responseHeaders.hasKey(headerName)) {
responseHeaders.putString(
headerName,
responseHeaders.getString(headerName) + ", " + headers.value(i));
} else {
responseHeaders.putString(headerName, headers.value(i));
}
}
callback.invoke(response.code(), responseHeaders, responseBody);
}
});
}
private void readWithProgress(int requestId, ResponseBody responseBody) throws IOException {
Reader reader = responseBody.charStream();
try {
StringBuilder sb = new StringBuilder(getBufferSize(responseBody));
char[] buffer = new char[MIN_BUFFER_SIZE];
int read;
long last = System.nanoTime();
while ((read = reader.read(buffer)) != -1) {
sb.append(buffer, 0, read);
long now = System.nanoTime();
if (shouldDispatch(now, last)) {
onDataReceived(requestId, sb.toString());
sb.setLength(0);
last = now;
}
}
if (sb.length() > 0) {
onDataReceived(requestId, sb.toString());
}
} finally {
reader.close();
}
}
private static boolean shouldDispatch(long now, long last) {
return last + CHUNK_TIMEOUT_NS < now;
}
private static int getBufferSize(ResponseBody responseBody) throws IOException {
long length = responseBody.contentLength();
if (length == -1) {
return MIN_BUFFER_SIZE;
} else {
return (int) min(length, MAX_BUFFER_SIZE);
}
}
private void onDataReceived(int requestId, String data) {
WritableArray args = Arguments.createArray();
args.pushInt(requestId);
args.pushString(data);
getEventEmitter().emit("didReceiveNetworkData", args);
}
private void onRequestError(int requestId, String error) {
WritableArray args = Arguments.createArray();
args.pushInt(requestId);
args.pushString(error);
getEventEmitter().emit("didCompleteNetworkResponse", args);
}
private void onRequestSuccess(int requestId) {
WritableArray args = Arguments.createArray();
args.pushInt(requestId);
args.pushNull();
getEventEmitter().emit("didCompleteNetworkResponse", args);
}
private void onResponseReceived(int requestId, Response response) {
WritableMap headers = translateHeaders(response.headers());
WritableArray args = Arguments.createArray();
args.pushInt(requestId);
args.pushInt(response.code());
args.pushMap(headers);
getEventEmitter().emit("didReceiveNetworkResponse", args);
}
private static WritableMap translateHeaders(Headers headers) {
WritableMap responseHeaders = Arguments.createMap();
for (int i = 0; i < headers.size(); i++) {
String headerName = headers.name(i);
// multiple values for the same header
if (responseHeaders.hasKey(headerName)) {
responseHeaders.putString(
headerName,
responseHeaders.getString(headerName) + ", " + headers.value(i));
} else {
responseHeaders.putString(headerName, headers.value(i));
}
}
return responseHeaders;
}
@ReactMethod
public void abortRequest(final int requestId) {
// We have to use AsyncTask since this might trigger a NetworkOnMainThreadException, this is an
@@ -312,7 +228,7 @@ public final class NetworkingModule extends ReactContextBaseJavaModule {
private @Nullable MultipartBuilder constructMultipartBody(
ReadableArray body,
String contentType,
int requestId) {
Callback callback) {
MultipartBuilder multipartBuilder = new MultipartBuilder();
multipartBuilder.type(MediaType.parse(contentType));
@@ -323,7 +239,7 @@ 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.");
callback.invoke(0, null, "Missing or invalid header format for FormData part.");
return null;
}
MediaType partContentType = null;
@@ -340,19 +256,19 @@ 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.");
callback.invoke(0, null, "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);
callback.invoke(0, null, "Could not retrieve file for uri " + fileContentUriStr);
return null;
}
multipartBuilder.addPart(headers, RequestBodyUtil.create(partContentType, fileInputStream));
} else {
onRequestError(requestId, "Unrecognized FormData part.");
callback.invoke(0, null, "Unrecognized FormData part.");
}
}
return multipartBuilder;
@@ -389,9 +305,4 @@ public final class NetworkingModule extends ReactContextBaseJavaModule {
return headersBuilder.build();
}
private DeviceEventManagerModule.RCTDeviceEventEmitter getEventEmitter() {
return getReactApplicationContext()
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class);
}
}