Skip to content

Commit

Permalink
Added progress updates for all XMLHttpRequest upload types
Browse files Browse the repository at this point in the history
Summary:
Previously, only form-data request bodies emitted upload progress updates. Now,
other request body types will also emit updates.

Addresses issues:
facebook#15724
facebook#11853

This is a bug fix for functionality that's missing on Android. These events are already working correctly on iOS.

Run the following code on Android, and ensure that events are being sent:
```
const fileUri = 'file:///my_file.dat';
const url = 'http://my_post_url.com/';
const xhr = new XMLHttpRequest();

xhr.upload.onprogress = (event) => {
    console.log('progress: ' + event.loaded + ' / ' + event.total);
}

xhr.onreadystatechange = () => {if (xhr.readyState === 4) console.log('done');}

console.log('start');

xhr.open('POST', url);

xhr.send({ uri: fileUri }); // sending a file (wasn't sending progress)
xhr.send("some big string"); // sending a string (wasn't sending progress)

const formData = new FormData(); formData.set('test', 'data');
xhr.send(formData); // sending form data (was already working)
```

[ANDROID] [BUGFIX] [XMLHttpRequest] - Added progress updates for all XMLHttpRequest upload types

Previously, only form-data request bodies emitted upload progress updates. Now,
other request body types will also emit updates.

Addresses issues:
facebook#15724
facebook#11853
Closes facebook#16541

Differential Revision: D6325252

Pulled By: hramos

fbshipit-source-id: 4fe617216293e6f451e2a1af4fa872e8f56d4f93
  • Loading branch information
allengleyzer authored and cdlewis committed Nov 19, 2017
1 parent 7a24a96 commit a751a7d
Show file tree
Hide file tree
Showing 2 changed files with 42 additions and 30 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -235,8 +235,9 @@ public void onProgress(long bytesWritten, long contentLength, boolean done) {
String contentEncoding = requestHeaders.get(CONTENT_ENCODING_HEADER_NAME);
requestBuilder.headers(requestHeaders);

RequestBody requestBody;
if (data == null) {
requestBuilder.method(method, RequestBodyUtil.getEmptyBody(method));
requestBody = RequestBodyUtil.getEmptyBody(method);
} else if (data.hasKey(REQUEST_BODY_KEY_STRING)) {
if (contentType == null) {
ResponseUtil.onRequestError(
Expand All @@ -249,14 +250,13 @@ public void onProgress(long bytesWritten, long contentLength, boolean done) {
String body = data.getString(REQUEST_BODY_KEY_STRING);
MediaType contentMediaType = MediaType.parse(contentType);
if (RequestBodyUtil.isGzipEncoding(contentEncoding)) {
RequestBody requestBody = RequestBodyUtil.createGzip(contentMediaType, body);
requestBody = RequestBodyUtil.createGzip(contentMediaType, body);
if (requestBody == null) {
ResponseUtil.onRequestError(eventEmitter, requestId, "Failed to gzip request body", null);
return;
}
requestBuilder.method(method, requestBody);
} else {
requestBuilder.method(method, RequestBody.create(contentMediaType, body));
requestBody = RequestBody.create(contentMediaType, body);
}
} else if (data.hasKey(REQUEST_BODY_KEY_BASE64)) {
if (contentType == null) {
Expand All @@ -269,9 +269,7 @@ public void onProgress(long bytesWritten, long contentLength, boolean done) {
}
String base64String = data.getString(REQUEST_BODY_KEY_BASE64);
MediaType contentMediaType = MediaType.parse(contentType);
requestBuilder.method(
method,
RequestBody.create(contentMediaType, ByteString.decodeBase64(base64String)));
requestBody = RequestBody.create(contentMediaType, ByteString.decodeBase64(base64String));
} else if (data.hasKey(REQUEST_BODY_KEY_URI)) {
if (contentType == null) {
ResponseUtil.onRequestError(
Expand All @@ -292,9 +290,7 @@ public void onProgress(long bytesWritten, long contentLength, boolean done) {
null);
return;
}
requestBuilder.method(
method,
RequestBodyUtil.create(MediaType.parse(contentType), fileInputStream));
requestBody = RequestBodyUtil.create(MediaType.parse(contentType), fileInputStream);
} else if (data.hasKey(REQUEST_BODY_KEY_FORMDATA)) {
if (contentType == null) {
contentType = "multipart/form-data";
Expand All @@ -305,28 +301,16 @@ public void onProgress(long bytesWritten, long contentLength, boolean done) {
if (multipartBuilder == null) {
return;
}

requestBuilder.method(
method,
RequestBodyUtil.createProgressRequest(
multipartBuilder.build(),
new ProgressListener() {
long last = System.nanoTime();

@Override
public void onProgress(long bytesWritten, long contentLength, boolean done) {
long now = System.nanoTime();
if (done || shouldDispatch(now, last)) {
ResponseUtil.onDataSend(eventEmitter, requestId, bytesWritten, contentLength);
last = now;
}
}
}));
requestBody = multipartBuilder.build();
} else {
// Nothing in data payload, at least nothing we could understand anyway.
requestBuilder.method(method, RequestBodyUtil.getEmptyBody(method));
requestBody = RequestBodyUtil.getEmptyBody(method);
}

requestBuilder.method(
method,
wrapRequestBodyWithProgressEmitter(requestBody, eventEmitter, requestId));

addRequest(requestId);
client.newCall(requestBuilder.build()).enqueue(
new Callback() {
Expand Down Expand Up @@ -394,6 +378,29 @@ public void onResponse(Call call, Response response) throws IOException {
});
}

private RequestBody wrapRequestBodyWithProgressEmitter(
final RequestBody requestBody,
final RCTDeviceEventEmitter eventEmitter,
final int requestId) {
if(requestBody == null) {
return null;
}
return RequestBodyUtil.createProgressRequest(
requestBody,
new ProgressListener() {
long last = System.nanoTime();

@Override
public void onProgress(long bytesWritten, long contentLength, boolean done) {
long now = System.nanoTime();
if (done || shouldDispatch(now, last)) {
ResponseUtil.onDataSend(eventEmitter, requestId, bytesWritten, contentLength);
last = now;
}
}
});
}

private void readWithProgress(
RCTDeviceEventEmitter eventEmitter,
int requestId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,10 @@ public WritableMap answer(InvocationOnMock invocation) throws Throwable {

@Test
public void testSuccessfulPostRequest() throws Exception {
RCTDeviceEventEmitter emitter = mock(RCTDeviceEventEmitter.class);
ReactApplicationContext context = mock(ReactApplicationContext.class);
when(context.getJSModule(any(Class.class))).thenReturn(emitter);

OkHttpClient httpClient = mock(OkHttpClient.class);
when(httpClient.newCall(any(Request.class))).thenAnswer(new Answer<Object>() {
@Override
Expand All @@ -211,12 +215,13 @@ public Object answer(InvocationOnMock invocation) throws Throwable {
OkHttpClient.Builder clientBuilder = mock(OkHttpClient.Builder.class);
when(clientBuilder.build()).thenReturn(httpClient);
when(httpClient.newBuilder()).thenReturn(clientBuilder);
NetworkingModule networkingModule =
new NetworkingModule(mock(ReactApplicationContext.class), "", httpClient);
NetworkingModule networkingModule = new NetworkingModule(context, "", httpClient);

JavaOnlyMap body = new JavaOnlyMap();
body.putString("string", "This is request body");

mockEvents();

networkingModule.sendRequest(
"POST",
"http://somedomain/bar",
Expand Down

0 comments on commit a751a7d

Please sign in to comment.