Implement XHR timeout for Android and IOS natively.

Summary:
Opening this in a separate PR but the discussion can be viewed on #4832.

Basically, this is a native implementation and is a bit more elegant. The consensus on my previous PR was that it should be done natively rather than in JS.

There's now no maximum valid timeout value and a timeout of 0 will never time out.

ontimeout isn't implemented (yet) in this PR.

cc nicklockwood ide philikon
Closes https://github.com/facebook/react-native/pull/5038

Reviewed By: svcscm

Differential Revision: D2838743

Pulled By: nicklockwood

fb-gh-sync-id: 774f864ac35082bf522f7665f4311bd3affbe82c
This commit is contained in:
Alex Roman 2016-01-18 07:47:04 -08:00 committed by facebook-github-bot-8
parent a3706411ab
commit 1303e6d039
7 changed files with 38 additions and 18 deletions

View File

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

View File

@ -210,7 +210,7 @@ RCT_EXPORT_MODULE()
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:URL];
request.HTTPMethod = [RCTConvert NSString:RCTNilIfNull(query[@"method"])].uppercaseString ?: @"GET";
request.allHTTPHeaderFields = [RCTConvert NSDictionary:query[@"headers"]];
request.timeoutInterval = [RCTConvert NSTimeInterval:query[@"timeout"]];
NSDictionary<NSString *, id> *data = [RCTConvert NSDictionary:RCTNilIfNull(query[@"data"])];
return [self processDataForHTTPQuery:data callback:^(NSError *error, NSDictionary<NSString *, id> *result) {
if (error) {

View File

@ -26,7 +26,7 @@ function convertHeadersMapToArray(headers: Object): Array<Header> {
}
class XMLHttpRequest extends XMLHttpRequestBase {
sendImpl(method: ?string, url: ?string, headers: Object, data: any): void {
sendImpl(method: ?string, url: ?string, headers: Object, data: any, timeout: number): void {
var body;
if (typeof data === 'string') {
body = {string: data};
@ -40,14 +40,14 @@ class XMLHttpRequest extends XMLHttpRequestBase {
} else {
body = data;
}
var useIncrementalUpdates = this.onreadystatechange ? true : false;
var requestId = RCTNetworking.sendRequest(
method,
url,
convertHeadersMapToArray(headers),
body,
useIncrementalUpdates
useIncrementalUpdates,
timeout
);
this.didCreateRequest(requestId);
}

View File

@ -23,7 +23,7 @@ class XMLHttpRequest extends XMLHttpRequestBase {
this.upload = {};
}
sendImpl(method: ?string, url: ?string, headers: Object, data: any): void {
sendImpl(method: ?string, url: ?string, headers: Object, data: any, timeout: number): void {
if (typeof data === 'string') {
data = {string: data};
} else if (data instanceof FormData) {
@ -36,6 +36,7 @@ class XMLHttpRequest extends XMLHttpRequestBase {
data,
headers,
incrementalUpdates: this.onreadystatechange ? true : false,
timeout
},
this.didCreateRequest.bind(this)
);

View File

@ -32,6 +32,7 @@ class XMLHttpRequestBase {
responseHeaders: ?Object;
responseText: ?string;
status: number;
timeout: number;
responseURL: ?string;
upload: ?{
@ -58,6 +59,7 @@ class XMLHttpRequestBase {
this.onreadystatechange = null;
this.onload = null;
this.upload = undefined; /* Upload not supported yet */
this.timeout = 0;
this._reset();
this._method = null;
@ -196,7 +198,7 @@ class XMLHttpRequestBase {
this.setReadyState(this.OPENED);
}
sendImpl(method: ?string, url: ?string, headers: Object, data: any): void {
sendImpl(method: ?string, url: ?string, headers: Object, data: any, timeout: number): void {
throw new Error('Subclass must define sendImpl method');
}
@ -208,7 +210,7 @@ class XMLHttpRequestBase {
throw new Error('Request has already been sent');
}
this._sent = true;
this.sendImpl(this._method, this._url, this._headers, data);
this.sendImpl(this._method, this._url, this._headers, data, this.timeout);
}
abort(): void {

View File

@ -15,6 +15,8 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.util.concurrent.TimeUnit;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.GuardedAsyncTask;
import com.facebook.react.bridge.ReactApplicationContext;
@ -113,19 +115,25 @@ public final class NetworkingModule extends ReactContextBaseJavaModule {
}
@ReactMethod
/**
* @param timeout value of 0 results in no timeout
*/
public void sendRequest(
String method,
String url,
final int requestId,
ReadableArray headers,
ReadableMap data,
final boolean useIncrementalUpdates) {
final boolean useIncrementalUpdates,
int timeout) {
Request.Builder requestBuilder = new Request.Builder().url(url);
if (requestId != 0) {
requestBuilder.tag(requestId);
}
mClient.setConnectTimeout(timeout, TimeUnit.MILLISECONDS);
Headers requestHeaders = extractHeaders(headers, data);
if (requestHeaders == null) {
onRequestError(requestId, "Unrecognized headers format");

View File

@ -93,7 +93,8 @@ public class NetworkingModuleTest {
0,
SimpleArray.of(),
null,
true);
true,
0);
ArgumentCaptor<Request> argumentCaptor = ArgumentCaptor.forClass(Request.class);
verify(httpClient).newCall(argumentCaptor.capture());
@ -122,7 +123,8 @@ public class NetworkingModuleTest {
0,
SimpleArray.from(invalidHeaders),
null,
true);
true,
0);
verifyErrorEmit(emitter, 0);
}
@ -147,7 +149,8 @@ public class NetworkingModuleTest {
0,
SimpleArray.of(),
body,
true);
true,
0);
verifyErrorEmit(emitter, 0);
}
@ -202,7 +205,8 @@ public class NetworkingModuleTest {
0,
SimpleArray.of(SimpleArray.of("Content-Type", "text/plain")),
body,
true);
true,
0);
ArgumentCaptor<Request> argumentCaptor = ArgumentCaptor.forClass(Request.class);
verify(httpClient).newCall(argumentCaptor.capture());
@ -238,7 +242,8 @@ public class NetworkingModuleTest {
0,
SimpleArray.from(headers),
null,
true);
true,
0);
ArgumentCaptor<Request> argumentCaptor = ArgumentCaptor.forClass(Request.class);
verify(httpClient).newCall(argumentCaptor.capture());
Headers requestHeaders = argumentCaptor.getValue().headers();
@ -284,7 +289,8 @@ public class NetworkingModuleTest {
0,
new SimpleArray(),
body,
true);
true,
0);
// verify url, method, headers
ArgumentCaptor<Request> argumentCaptor = ArgumentCaptor.forClass(Request.class);
@ -341,7 +347,8 @@ public class NetworkingModuleTest {
0,
SimpleArray.from(headers),
body,
true);
true,
0);
// verify url, method, headers
ArgumentCaptor<Request> argumentCaptor = ArgumentCaptor.forClass(Request.class);
@ -435,7 +442,8 @@ public class NetworkingModuleTest {
0,
SimpleArray.from(headers),
body,
true);
true,
0);
// verify RequestBodyPart for image
PowerMockito.verifyStatic(times(1));