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

@@ -25,7 +25,7 @@ var generateRequestId = function() {
*/
class RCTNetworking {
static sendRequest(method, url, headers, data, callback) {
static sendRequest(method, url, headers, data, useIncrementalUpdates) {
var requestId = generateRequestId();
RCTNetworkingNative.sendRequest(
method,
@@ -33,7 +33,7 @@ class RCTNetworking {
requestId,
headers,
data,
callback);
useIncrementalUpdates);
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 {
_requestId: ?number;
constructor() {
super();
this._requestId = null;
}
sendImpl(method: ?string, url: ?string, headers: Object, data: any): void {
var body;
if (typeof data === 'string') {
@@ -49,17 +41,15 @@ class XMLHttpRequest extends XMLHttpRequestBase {
body = data;
}
this._requestId = RCTNetworking.sendRequest(
var useIncrementalUpdates = this.onreadystatechange ? true : false;
var requestId = RCTNetworking.sendRequest(
method,
url,
convertHeadersMapToArray(headers),
body,
this.callback.bind(this)
useIncrementalUpdates
);
}
abortImpl(): void {
this._requestId && RCTNetworking.abortRequest(this._requestId);
this.didCreateRequest(requestId);
}
}

View File

@@ -12,95 +12,18 @@
'use strict';
var FormData = require('FormData');
var RCTNetworking = require('NativeModules').Networking;
var RCTNetworking = require('RCTNetworking');
var RCTDeviceEventEmitter = require('RCTDeviceEventEmitter');
var XMLHttpRequestBase = require('XMLHttpRequestBase');
class XMLHttpRequest extends XMLHttpRequestBase {
_requestId: ?number;
_subscriptions: [any];
upload: {
onprogress?: (event: Object) => void;
};
constructor() {
super();
this._requestId = null;
this._subscriptions = [];
// iOS supports 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 {
if (typeof data === 'string') {
data = {string: data};
@@ -115,17 +38,9 @@ class XMLHttpRequest extends XMLHttpRequestBase {
headers,
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;

View File

@@ -11,6 +11,9 @@
*/
'use strict';
var RCTNetworking = require('RCTNetworking');
var RCTDeviceEventEmitter = require('RCTDeviceEventEmitter');
/**
* Shared base for platform-specific XMLHttpRequest implementations.
*/
@@ -30,6 +33,13 @@ class XMLHttpRequestBase {
responseText: ?string;
status: number;
upload: ?{
onprogress?: (event: Object) => void;
};
_requestId: ?number;
_subscriptions: [any];
_method: ?string;
_url: ?string;
_headers: Object;
@@ -60,9 +70,81 @@ class XMLHttpRequestBase {
this.responseText = '';
this.status = 0;
this._requestId = null;
this._headers = {};
this._sent = false;
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 {
@@ -108,10 +190,6 @@ class XMLHttpRequestBase {
throw new Error('Subclass must define sendImpl method');
}
abortImpl(): void {
throw new Error('Subclass must define abortImpl method');
}
send(data: any): void {
if (this.readyState !== this.OPENED) {
throw new Error('Request has not been opened');
@@ -125,7 +203,9 @@ class XMLHttpRequestBase {
abort(): void {
this._aborted = true;
this.abortImpl();
if (this._requestId) {
RCTNetworking.abortRequest(this._requestId);
}
// only call onreadystatechange if there is something to abort,
// below logic is per spec
if (!(this.readyState === this.UNSENT ||
@@ -138,16 +218,6 @@ class XMLHttpRequestBase {
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 {
this.responseHeaders = responseHeaders || null;
var headers = responseHeaders || {};