Send HEADERS_RECEIVED and LOADING events on Android

Summary: Send part of the response body every 100 ms if the client has set onreadystatechange. This
is done by using the same events as the iOS code and removing the callback that Android previously
used.

Reconsolidate iOS and Android implementations.

Closes #3772

(The previous commit was reverted)

public

Reviewed By: astreet

Differential Revision: D2658153

fb-gh-sync-id: b1a32d22db7cc2995c673edd31f4bbaf16ca36cb
This commit is contained in:
Alexander Blom 2015-11-17 06:28:44 -08:00 committed by facebook-github-bot-0
parent 337dc7e093
commit 532c9112b4
7 changed files with 245 additions and 164 deletions

View File

@ -18,6 +18,7 @@
var React = require('react-native'); var React = require('react-native');
var { var {
PixelRatio, PixelRatio,
ProgressBarAndroid,
StyleSheet, StyleSheet,
Text, Text,
TextInput, TextInput,
@ -61,7 +62,6 @@ class Downloader extends React.Component {
this.setState({ this.setState({
downloaded: xhr.responseText.length, downloaded: xhr.responseText.length,
}); });
console.log(xhr.responseText.length);
} else if (xhr.readyState === xhr.DONE) { } else if (xhr.readyState === xhr.DONE) {
if (this.cancelled) { if (this.cancelled) {
this.cancelled = false; this.cancelled = false;
@ -83,6 +83,8 @@ class Downloader extends React.Component {
} }
}; };
xhr.open('GET', 'http://www.gutenberg.org/cache/epub/100/pg100.txt'); xhr.open('GET', 'http://www.gutenberg.org/cache/epub/100/pg100.txt');
// Avoid gzip so we can actually show progress
xhr.setRequestHeader('Accept-Encoding', '');
xhr.send(); xhr.send();
this.xhr = xhr; this.xhr = xhr;
@ -114,6 +116,8 @@ class Downloader extends React.Component {
return ( return (
<View> <View>
{button} {button}
<ProgressBarAndroid progress={(this.state.downloaded / this.state.contentSize)}
styleAttr="Horizontal" indeterminate={false} />
<Text>{this.state.status}</Text> <Text>{this.state.status}</Text>
</View> </View>
); );

View File

@ -25,7 +25,7 @@ var generateRequestId = function() {
*/ */
class RCTNetworking { class RCTNetworking {
static sendRequest(method, url, headers, data, callback) { static sendRequest(method, url, headers, data, useIncrementalUpdates) {
var requestId = generateRequestId(); var requestId = generateRequestId();
RCTNetworkingNative.sendRequest( RCTNetworkingNative.sendRequest(
method, method,
@ -33,7 +33,7 @@ class RCTNetworking {
requestId, requestId,
headers, headers,
data, data,
callback); useIncrementalUpdates);
return requestId; return requestId;
} }

View File

@ -0,0 +1,13 @@
/**
* 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.
*
* @providesModule RCTNetworking
*/
'use strict';
module.exports = require('NativeModules').Networking;

View File

@ -26,14 +26,6 @@ function convertHeadersMapToArray(headers: Object): Array<Header> {
} }
class XMLHttpRequest extends XMLHttpRequestBase { class XMLHttpRequest extends XMLHttpRequestBase {
_requestId: ?number;
constructor() {
super();
this._requestId = null;
}
sendImpl(method: ?string, url: ?string, headers: Object, data: any): void { sendImpl(method: ?string, url: ?string, headers: Object, data: any): void {
var body; var body;
if (typeof data === 'string') { if (typeof data === 'string') {
@ -49,17 +41,15 @@ class XMLHttpRequest extends XMLHttpRequestBase {
body = data; body = data;
} }
this._requestId = RCTNetworking.sendRequest( var useIncrementalUpdates = this.onreadystatechange ? true : false;
var requestId = RCTNetworking.sendRequest(
method, method,
url, url,
convertHeadersMapToArray(headers), convertHeadersMapToArray(headers),
body, body,
this.callback.bind(this) useIncrementalUpdates
); );
} this.didCreateRequest(requestId);
abortImpl(): void {
this._requestId && RCTNetworking.abortRequest(this._requestId);
} }
} }

View File

@ -12,95 +12,18 @@
'use strict'; 'use strict';
var FormData = require('FormData'); var FormData = require('FormData');
var RCTNetworking = require('NativeModules').Networking; var RCTNetworking = require('RCTNetworking');
var RCTDeviceEventEmitter = require('RCTDeviceEventEmitter'); var RCTDeviceEventEmitter = require('RCTDeviceEventEmitter');
var XMLHttpRequestBase = require('XMLHttpRequestBase'); var XMLHttpRequestBase = require('XMLHttpRequestBase');
class XMLHttpRequest extends XMLHttpRequestBase { class XMLHttpRequest extends XMLHttpRequestBase {
_requestId: ?number;
_subscriptions: [any];
upload: {
onprogress?: (event: Object) => void;
};
constructor() { constructor() {
super(); super();
this._requestId = null; // iOS supports upload
this._subscriptions = [];
this.upload = {}; this.upload = {};
} }
_didCreateRequest(requestId: number): void {
this._requestId = requestId;
this._subscriptions.push(RCTDeviceEventEmitter.addListener(
'didSendNetworkData',
(args) => this._didUploadProgress.call(this, args[0], args[1], args[2])
));
this._subscriptions.push(RCTDeviceEventEmitter.addListener(
'didReceiveNetworkResponse',
(args) => this._didReceiveResponse.call(this, args[0], args[1], args[2])
));
this._subscriptions.push(RCTDeviceEventEmitter.addListener(
'didReceiveNetworkData',
(args) => this._didReceiveData.call(this, args[0], args[1])
));
this._subscriptions.push(RCTDeviceEventEmitter.addListener(
'didCompleteNetworkResponse',
(args) => this._didCompleteResponse.call(this, args[0], args[1])
));
}
_didUploadProgress(requestId: number, progress: number, total: number): void {
if (requestId === this._requestId && this.upload.onprogress) {
var event = {
lengthComputable: true,
loaded: progress,
total,
};
this.upload.onprogress(event);
}
}
_didReceiveResponse(requestId: number, status: number, responseHeaders: ?Object): void {
if (requestId === this._requestId) {
this.status = status;
this.setResponseHeaders(responseHeaders);
this.setReadyState(this.HEADERS_RECEIVED);
}
}
_didReceiveData(requestId: number, responseText: string): void {
if (requestId === this._requestId) {
if (!this.responseText) {
this.responseText = responseText;
} else {
this.responseText += responseText;
}
this.setReadyState(this.LOADING);
}
}
_didCompleteResponse(requestId: number, error: string): void {
if (requestId === this._requestId) {
if (error) {
this.responseText = error;
}
this._clearSubscriptions();
this._requestId = null;
this.setReadyState(this.DONE);
}
}
_clearSubscriptions(): void {
for (var i = 0; i < this._subscriptions.length; i++) {
var sub = this._subscriptions[i];
sub.remove();
}
this._subscriptions = [];
}
sendImpl(method: ?string, url: ?string, headers: Object, data: any): void { sendImpl(method: ?string, url: ?string, headers: Object, data: any): void {
if (typeof data === 'string') { if (typeof data === 'string') {
data = {string: data}; data = {string: data};
@ -115,17 +38,9 @@ class XMLHttpRequest extends XMLHttpRequestBase {
headers, headers,
incrementalUpdates: this.onreadystatechange ? true : false, incrementalUpdates: this.onreadystatechange ? true : false,
}, },
this._didCreateRequest.bind(this) this.didCreateRequest.bind(this)
); );
} }
abortImpl(): void {
if (this._requestId) {
RCTNetworking.cancelRequest(this._requestId);
this._clearSubscriptions();
this._requestId = null;
}
}
} }
module.exports = XMLHttpRequest; module.exports = XMLHttpRequest;

View File

@ -11,6 +11,9 @@
*/ */
'use strict'; 'use strict';
var RCTNetworking = require('RCTNetworking');
var RCTDeviceEventEmitter = require('RCTDeviceEventEmitter');
/** /**
* Shared base for platform-specific XMLHttpRequest implementations. * Shared base for platform-specific XMLHttpRequest implementations.
*/ */
@ -30,6 +33,13 @@ class XMLHttpRequestBase {
responseText: ?string; responseText: ?string;
status: number; status: number;
upload: ?{
onprogress?: (event: Object) => void;
};
_requestId: ?number;
_subscriptions: [any];
_method: ?string; _method: ?string;
_url: ?string; _url: ?string;
_headers: Object; _headers: Object;
@ -60,9 +70,81 @@ class XMLHttpRequestBase {
this.responseText = ''; this.responseText = '';
this.status = 0; this.status = 0;
this._requestId = null;
this._headers = {}; this._headers = {};
this._sent = false; this._sent = false;
this._lowerCaseResponseHeaders = {}; this._lowerCaseResponseHeaders = {};
this._clearSubscriptions();
}
didCreateRequest(requestId: number): void {
this._requestId = requestId;
this._subscriptions.push(RCTDeviceEventEmitter.addListener(
'didSendNetworkData',
(args) => this._didUploadProgress.call(this, ...args)
));
this._subscriptions.push(RCTDeviceEventEmitter.addListener(
'didReceiveNetworkResponse',
(args) => this._didReceiveResponse.call(this, ...args)
));
this._subscriptions.push(RCTDeviceEventEmitter.addListener(
'didReceiveNetworkData',
(args) => this._didReceiveData.call(this, ...args)
));
this._subscriptions.push(RCTDeviceEventEmitter.addListener(
'didCompleteNetworkResponse',
(args) => this._didCompleteResponse.call(this, ...args)
));
}
_didUploadProgress(requestId: number, progress: number, total: number): void {
if (requestId === this._requestId && this.upload && this.upload.onprogress) {
var event = {
lengthComputable: true,
loaded: progress,
total,
};
this.upload.onprogress(event);
}
}
_didReceiveResponse(requestId: number, status: number, responseHeaders: ?Object): void {
if (requestId === this._requestId) {
this.status = status;
this.setResponseHeaders(responseHeaders);
this.setReadyState(this.HEADERS_RECEIVED);
}
}
_didReceiveData(requestId: number, responseText: string): void {
if (requestId === this._requestId) {
if (!this.responseText) {
this.responseText = responseText;
} else {
this.responseText += responseText;
}
this.setReadyState(this.LOADING);
}
}
_didCompleteResponse(requestId: number, error: string): void {
if (requestId === this._requestId) {
if (error) {
this.responseText = error;
}
this._clearSubscriptions();
this._requestId = null;
this.setReadyState(this.DONE);
}
}
_clearSubscriptions(): void {
(this._subscriptions || []).forEach(sub => {
sub.remove();
});
this._subscriptions = [];
} }
getAllResponseHeaders(): ?string { getAllResponseHeaders(): ?string {
@ -108,10 +190,6 @@ class XMLHttpRequestBase {
throw new Error('Subclass must define sendImpl method'); throw new Error('Subclass must define sendImpl method');
} }
abortImpl(): void {
throw new Error('Subclass must define abortImpl method');
}
send(data: any): void { send(data: any): void {
if (this.readyState !== this.OPENED) { if (this.readyState !== this.OPENED) {
throw new Error('Request has not been opened'); throw new Error('Request has not been opened');
@ -125,7 +203,9 @@ class XMLHttpRequestBase {
abort(): void { abort(): void {
this._aborted = true; this._aborted = true;
this.abortImpl(); if (this._requestId) {
RCTNetworking.abortRequest(this._requestId);
}
// only call onreadystatechange if there is something to abort, // only call onreadystatechange if there is something to abort,
// below logic is per spec // below logic is per spec
if (!(this.readyState === this.UNSENT || if (!(this.readyState === this.UNSENT ||
@ -138,16 +218,6 @@ class XMLHttpRequestBase {
this._reset(); this._reset();
} }
callback(status: number, responseHeaders: ?Object, responseText: string): void {
if (this._aborted) {
return;
}
this.status = status;
this.setResponseHeaders(responseHeaders || {});
this.responseText = responseText;
this.setReadyState(this.DONE);
}
setResponseHeaders(responseHeaders: ?Object): void { setResponseHeaders(responseHeaders: ?Object): void {
this.responseHeaders = responseHeaders || null; this.responseHeaders = responseHeaders || null;
var headers = responseHeaders || {}; var headers = responseHeaders || {};

View File

@ -13,17 +13,20 @@ import javax.annotation.Nullable;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.Reader;
import com.facebook.react.bridge.Arguments; import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.Callback;
import com.facebook.react.bridge.GuardedAsyncTask; import com.facebook.react.bridge.GuardedAsyncTask;
import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule; import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod; import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReadableArray; import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap; import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.WritableMap; 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.Headers;
import com.squareup.okhttp.MediaType; import com.squareup.okhttp.MediaType;
import com.squareup.okhttp.MultipartBuilder; import com.squareup.okhttp.MultipartBuilder;
@ -31,6 +34,9 @@ import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.Request; import com.squareup.okhttp.Request;
import com.squareup.okhttp.RequestBody; import com.squareup.okhttp.RequestBody;
import com.squareup.okhttp.Response; import com.squareup.okhttp.Response;
import com.squareup.okhttp.ResponseBody;
import static java.lang.Math.min;
/** /**
* Implements the XMLHttpRequest JavaScript interface. * Implements the XMLHttpRequest JavaScript interface.
@ -44,6 +50,11 @@ public final class NetworkingModule extends ReactContextBaseJavaModule {
private static final String REQUEST_BODY_KEY_FORMDATA = "formData"; private static final String REQUEST_BODY_KEY_FORMDATA = "formData";
private static final String USER_AGENT_HEADER_NAME = "user-agent"; 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 OkHttpClient mClient;
private final @Nullable String mDefaultUserAgent; private final @Nullable String mDefaultUserAgent;
private boolean mShuttingDown; private boolean mShuttingDown;
@ -93,15 +104,10 @@ public final class NetworkingModule extends ReactContextBaseJavaModule {
public void sendRequest( public void sendRequest(
String method, String method,
String url, String url,
int requestId, final int requestId,
ReadableArray headers, ReadableArray headers,
ReadableMap data, ReadableMap data,
final Callback callback) { final boolean useIncrementalUpdates) {
// 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); Request.Builder requestBuilder = new Request.Builder().url(url);
if (requestId != 0) { if (requestId != 0) {
@ -110,7 +116,7 @@ public final class NetworkingModule extends ReactContextBaseJavaModule {
Headers requestHeaders = extractHeaders(headers, data); Headers requestHeaders = extractHeaders(headers, data);
if (requestHeaders == null) { if (requestHeaders == null) {
callback.invoke(0, null, "Unrecognized headers format"); onRequestError(requestId, "Unrecognized headers format");
return; return;
} }
String contentType = requestHeaders.get(CONTENT_TYPE_HEADER_NAME); String contentType = requestHeaders.get(CONTENT_TYPE_HEADER_NAME);
@ -121,7 +127,7 @@ public final class NetworkingModule extends ReactContextBaseJavaModule {
requestBuilder.method(method, null); requestBuilder.method(method, null);
} else if (data.hasKey(REQUEST_BODY_KEY_STRING)) { } else if (data.hasKey(REQUEST_BODY_KEY_STRING)) {
if (contentType == null) { if (contentType == null) {
callback.invoke(0, null, "Payload is set but no content-type header specified"); onRequestError(requestId, "Payload is set but no content-type header specified");
return; return;
} }
String body = data.getString(REQUEST_BODY_KEY_STRING); String body = data.getString(REQUEST_BODY_KEY_STRING);
@ -129,7 +135,7 @@ public final class NetworkingModule extends ReactContextBaseJavaModule {
if (RequestBodyUtil.isGzipEncoding(contentEncoding)) { if (RequestBodyUtil.isGzipEncoding(contentEncoding)) {
RequestBody requestBody = RequestBodyUtil.createGzip(contentMediaType, body); RequestBody requestBody = RequestBodyUtil.createGzip(contentMediaType, body);
if (requestBody == null) { if (requestBody == null) {
callback.invoke(0, null, "Failed to gzip request body"); onRequestError(requestId, "Failed to gzip request body");
return; return;
} }
requestBuilder.method(method, requestBody); requestBuilder.method(method, requestBody);
@ -138,14 +144,14 @@ public final class NetworkingModule extends ReactContextBaseJavaModule {
} }
} else if (data.hasKey(REQUEST_BODY_KEY_URI)) { } else if (data.hasKey(REQUEST_BODY_KEY_URI)) {
if (contentType == null) { if (contentType == null) {
callback.invoke(0, null, "Payload is set but no content-type header specified"); onRequestError(requestId, "Payload is set but no content-type header specified");
return; return;
} }
String uri = data.getString(REQUEST_BODY_KEY_URI); String uri = data.getString(REQUEST_BODY_KEY_URI);
InputStream fileInputStream = InputStream fileInputStream =
RequestBodyUtil.getFileInputStream(getReactApplicationContext(), uri); RequestBodyUtil.getFileInputStream(getReactApplicationContext(), uri);
if (fileInputStream == null) { if (fileInputStream == null) {
callback.invoke(0, null, "Could not retrieve file for uri " + uri); onRequestError(requestId, "Could not retrieve file for uri " + uri);
return; return;
} }
requestBuilder.method( requestBuilder.method(
@ -156,7 +162,7 @@ public final class NetworkingModule extends ReactContextBaseJavaModule {
contentType = "multipart/form-data"; contentType = "multipart/form-data";
} }
ReadableArray parts = data.getArray(REQUEST_BODY_KEY_FORMDATA); ReadableArray parts = data.getArray(REQUEST_BODY_KEY_FORMDATA);
MultipartBuilder multipartBuilder = constructMultipartBody(parts, contentType, callback); MultipartBuilder multipartBuilder = constructMultipartBody(parts, contentType, requestId);
if (multipartBuilder == null) { if (multipartBuilder == null) {
return; return;
} }
@ -168,16 +174,13 @@ public final class NetworkingModule extends ReactContextBaseJavaModule {
} }
mClient.newCall(requestBuilder.build()).enqueue( mClient.newCall(requestBuilder.build()).enqueue(
new com.squareup.okhttp.Callback() { new Callback() {
@Override @Override
public void onFailure(Request request, IOException e) { public void onFailure(Request request, IOException e) {
if (mShuttingDown) { if (mShuttingDown) {
return; return;
} }
// We need to call the callback to avoid leaking memory on JS even when input for onRequestError(requestId, e.getMessage());
// 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 @Override
@ -185,17 +188,101 @@ public final class NetworkingModule extends ReactContextBaseJavaModule {
if (mShuttingDown) { if (mShuttingDown) {
return; return;
} }
String responseBody;
// Before we touch the body send headers to JS
onResponseReceived(requestId, response);
ResponseBody responseBody = response.body();
try { try {
responseBody = response.body().string(); if (useIncrementalUpdates) {
readWithProgress(requestId, responseBody);
onRequestSuccess(requestId);
} else {
onDataReceived(requestId, responseBody.string());
onRequestSuccess(requestId);
}
} catch (IOException e) { } catch (IOException e) {
// The stream has been cancelled or closed, nothing we can do onRequestError(requestId, e.getMessage());
callback.invoke(0, null, e.getMessage()); }
return; }
});
} }
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(); WritableMap responseHeaders = Arguments.createMap();
Headers headers = response.headers();
for (int i = 0; i < headers.size(); i++) { for (int i = 0; i < headers.size(); i++) {
String headerName = headers.name(i); String headerName = headers.name(i);
// multiple values for the same header // multiple values for the same header
@ -207,10 +294,7 @@ public final class NetworkingModule extends ReactContextBaseJavaModule {
responseHeaders.putString(headerName, headers.value(i)); responseHeaders.putString(headerName, headers.value(i));
} }
} }
return responseHeaders;
callback.invoke(response.code(), responseHeaders, responseBody);
}
});
} }
@ReactMethod @ReactMethod
@ -228,7 +312,7 @@ public final class NetworkingModule extends ReactContextBaseJavaModule {
private @Nullable MultipartBuilder constructMultipartBody( private @Nullable MultipartBuilder constructMultipartBody(
ReadableArray body, ReadableArray body,
String contentType, String contentType,
Callback callback) { int requestId) {
MultipartBuilder multipartBuilder = new MultipartBuilder(); MultipartBuilder multipartBuilder = new MultipartBuilder();
multipartBuilder.type(MediaType.parse(contentType)); multipartBuilder.type(MediaType.parse(contentType));
@ -239,7 +323,7 @@ public final class NetworkingModule extends ReactContextBaseJavaModule {
ReadableArray headersArray = bodyPart.getArray("headers"); ReadableArray headersArray = bodyPart.getArray("headers");
Headers headers = extractHeaders(headersArray, null); Headers headers = extractHeaders(headersArray, null);
if (headers == null) { if (headers == null) {
callback.invoke(0, null, "Missing or invalid header format for FormData part."); onRequestError(requestId, "Missing or invalid header format for FormData part.");
return null; return null;
} }
MediaType partContentType = null; MediaType partContentType = null;
@ -256,19 +340,19 @@ public final class NetworkingModule extends ReactContextBaseJavaModule {
multipartBuilder.addPart(headers, RequestBody.create(partContentType, bodyValue)); multipartBuilder.addPart(headers, RequestBody.create(partContentType, bodyValue));
} else if (bodyPart.hasKey(REQUEST_BODY_KEY_URI)) { } else if (bodyPart.hasKey(REQUEST_BODY_KEY_URI)) {
if (partContentType == null) { if (partContentType == null) {
callback.invoke(0, null, "Binary FormData part needs a content-type header."); onRequestError(requestId, "Binary FormData part needs a content-type header.");
return null; return null;
} }
String fileContentUriStr = bodyPart.getString(REQUEST_BODY_KEY_URI); String fileContentUriStr = bodyPart.getString(REQUEST_BODY_KEY_URI);
InputStream fileInputStream = InputStream fileInputStream =
RequestBodyUtil.getFileInputStream(getReactApplicationContext(), fileContentUriStr); RequestBodyUtil.getFileInputStream(getReactApplicationContext(), fileContentUriStr);
if (fileInputStream == null) { if (fileInputStream == null) {
callback.invoke(0, null, "Could not retrieve file for uri " + fileContentUriStr); onRequestError(requestId, "Could not retrieve file for uri " + fileContentUriStr);
return null; return null;
} }
multipartBuilder.addPart(headers, RequestBodyUtil.create(partContentType, fileInputStream)); multipartBuilder.addPart(headers, RequestBodyUtil.create(partContentType, fileInputStream));
} else { } else {
callback.invoke(0, null, "Unrecognized FormData part."); onRequestError(requestId, "Unrecognized FormData part.");
} }
} }
return multipartBuilder; return multipartBuilder;
@ -305,4 +389,9 @@ public final class NetworkingModule extends ReactContextBaseJavaModule {
return headersBuilder.build(); return headersBuilder.build();
} }
private DeviceEventManagerModule.RCTDeviceEventEmitter getEventEmitter() {
return getReactApplicationContext()
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class);
}
} }