diff --git a/Libraries/Network/RCTNetworking.android.js b/Libraries/Network/RCTNetworking.android.js index 351524eb9..38f694d54 100644 --- a/Libraries/Network/RCTNetworking.android.js +++ b/Libraries/Network/RCTNetworking.android.js @@ -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; } diff --git a/Libraries/Network/RCTNetworking.m b/Libraries/Network/RCTNetworking.m index 749541991..5e3081343 100644 --- a/Libraries/Network/RCTNetworking.m +++ b/Libraries/Network/RCTNetworking.m @@ -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 *data = [RCTConvert NSDictionary:RCTNilIfNull(query[@"data"])]; return [self processDataForHTTPQuery:data callback:^(NSError *error, NSDictionary *result) { if (error) { diff --git a/Libraries/Network/XMLHttpRequest.android.js b/Libraries/Network/XMLHttpRequest.android.js index e0ebc77d0..165b6d316 100644 --- a/Libraries/Network/XMLHttpRequest.android.js +++ b/Libraries/Network/XMLHttpRequest.android.js @@ -26,7 +26,7 @@ function convertHeadersMapToArray(headers: Object): Array
{ } 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); } diff --git a/Libraries/Network/XMLHttpRequest.ios.js b/Libraries/Network/XMLHttpRequest.ios.js index 21614ff36..d79e38019 100644 --- a/Libraries/Network/XMLHttpRequest.ios.js +++ b/Libraries/Network/XMLHttpRequest.ios.js @@ -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) ); diff --git a/Libraries/Network/XMLHttpRequestBase.js b/Libraries/Network/XMLHttpRequestBase.js index f4c8bc5e2..1fffb713f 100644 --- a/Libraries/Network/XMLHttpRequestBase.js +++ b/Libraries/Network/XMLHttpRequestBase.js @@ -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 { diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/network/NetworkingModule.java b/ReactAndroid/src/main/java/com/facebook/react/modules/network/NetworkingModule.java index d1f32616e..9374683b9 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/modules/network/NetworkingModule.java +++ b/ReactAndroid/src/main/java/com/facebook/react/modules/network/NetworkingModule.java @@ -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"); diff --git a/ReactAndroid/src/test/java/com/facebook/react/modules/network/NetworkingModuleTest.java b/ReactAndroid/src/test/java/com/facebook/react/modules/network/NetworkingModuleTest.java index 454a3cb98..64884aabd 100644 --- a/ReactAndroid/src/test/java/com/facebook/react/modules/network/NetworkingModuleTest.java +++ b/ReactAndroid/src/test/java/com/facebook/react/modules/network/NetworkingModuleTest.java @@ -93,7 +93,8 @@ public class NetworkingModuleTest { 0, SimpleArray.of(), null, - true); + true, + 0); ArgumentCaptor 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 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 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 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 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));