Refactored networking logic out into RCTDownloadTask

This commit is contained in:
Nick Lockwood 2015-07-23 03:55:12 -07:00
parent 6fea7c2846
commit 9d19829742
9 changed files with 294 additions and 265 deletions

View File

@ -0,0 +1,40 @@
/**
* 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.
*/
#import <Foundation/Foundation.h>
#import "RCTURLRequestDelegate.h"
#import "RCTURLRequestHandler.h"
typedef void (^RCTURLRequestCompletionBlock)(NSURLResponse *response, NSData *data, NSError *error);
typedef void (^RCTURLRequestCancellationBlock)(void);
typedef void (^RCTURLRequestIncrementalDataBlock)(NSData *data);
typedef void (^RCTURLRequestProgressBlock)(double progress, double total);
typedef void (^RCTURLRequestResponseBlock)(NSURLResponse *response);
@interface RCTDownloadTask : NSObject <RCTURLRequestDelegate>
@property (nonatomic, readonly) NSURLRequest *request;
@property (nonatomic, readonly) NSNumber *requestID;
@property (nonatomic, readonly) id requestToken;
@property (nonatomic, readonly) NSURLResponse *response;
@property (nonatomic, readonly) RCTURLRequestCompletionBlock completionBlock;
@property (nonatomic, copy) RCTURLRequestProgressBlock downloadProgressBlock;
@property (nonatomic, copy) RCTURLRequestIncrementalDataBlock incrementalDataBlock;
@property (nonatomic, copy) RCTURLRequestResponseBlock responseBlock;
@property (nonatomic, copy) RCTURLRequestProgressBlock uploadProgressBlock;
- (instancetype)initWithRequest:(NSURLRequest *)request
handler:(id<RCTURLRequestHandler>)handler
completionBlock:(RCTURLRequestCompletionBlock)completionBlock NS_DESIGNATED_INITIALIZER;
- (void)cancel;
@end

View File

@ -0,0 +1,102 @@
/**
* 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.
*/
#import "RCTDownloadTask.h"
#import "RCTAssert.h"
@implementation RCTDownloadTask
{
NSMutableData *_data;
id<RCTURLRequestHandler> _handler;
RCTDownloadTask *_selfReference;
}
- (instancetype)initWithRequest:(NSURLRequest *)request
handler:(id<RCTURLRequestHandler>)handler
completionBlock:(RCTURLRequestCompletionBlock)completionBlock
{
RCTAssertParam(request);
RCTAssertParam(handler);
RCTAssertParam(completionBlock);
static NSUInteger requestID = 0;
if ((self = [super init])) {
if (!(_requestToken = [handler sendRequest:request withDelegate:self])) {
return nil;
}
_requestID = @(requestID++);
_request = request;
_handler = handler;
_completionBlock = completionBlock;
_selfReference = self;
}
return self;
}
- (void)invalidate
{
_selfReference = nil;
_completionBlock = nil;
_downloadProgressBlock = nil;
_incrementalDataBlock = nil;
_responseBlock = nil;
_uploadProgressBlock = nil;
}
RCT_NOT_IMPLEMENTED(-init)
- (void)cancel
{
if ([_handler respondsToSelector:@selector(cancelRequest:)]) {
[_handler cancelRequest:_requestToken];
}
[self invalidate];
}
- (void)URLRequest:(id)requestToken didSendDataWithProgress:(int64_t)bytesSent
{
RCTAssert([requestToken isEqual:_requestToken], @"Unrecognized request token: %@", requestToken);
if (_uploadProgressBlock) {
_uploadProgressBlock(bytesSent, _request.HTTPBody.length);
}
}
- (void)URLRequest:(id)requestToken didReceiveResponse:(NSURLResponse *)response
{
RCTAssert([requestToken isEqual:_requestToken], @"Unrecognized request token: %@", requestToken);
_response = response;
if (_responseBlock) {
_responseBlock(response);
}
}
- (void)URLRequest:(id)requestToken didReceiveData:(NSData *)data
{
RCTAssert([requestToken isEqual:_requestToken], @"Unrecognized request token: %@", requestToken);
if (!_data) {
_data = [[NSMutableData alloc] init];
}
[_data appendData:data];
if (_incrementalDataBlock) {
_incrementalDataBlock(data);
}
if (_downloadProgressBlock && _response.expectedContentLength > 0) {
_downloadProgressBlock(_data.length, _response.expectedContentLength);
}
}
- (void)URLRequest:(id)requestToken didCompleteWithError:(NSError *)error
{
_completionBlock(_response, _data, error);
[self invalidate];
}
@end

View File

@ -10,6 +10,9 @@
#import "RCTURLRequestHandler.h"
#import "RCTInvalidating.h"
/**
* This is the default RCTURLRequestHandler implementation for HTTP requests.
*/
@interface RCTHTTPRequestHandler : NSObject <RCTURLRequestHandler, RCTInvalidating>
@end

View File

@ -50,8 +50,8 @@ RCT_EXPORT_MODULE()
return [@[@"http", @"https", @"file"] containsObject:[request.URL.scheme lowercaseString]];
}
- (id)sendRequest:(NSURLRequest *)request
withDelegate:(id<RCTURLRequestDelegate>)delegate
- (NSURLSessionDataTask *)sendRequest:(NSURLRequest *)request
withDelegate:(id<RCTURLRequestDelegate>)delegate
{
// Lazy setup
if (!_session && [self isValid]) {
@ -68,9 +68,10 @@ RCT_EXPORT_MODULE()
return task;
}
- (void)cancelRequest:(NSURLSessionDataTask *)requestToken
- (void)cancelRequest:(NSURLSessionDataTask *)task
{
[requestToken cancel];
[task cancel];
[_delegates removeObjectForKey:task];
}
#pragma mark - NSURLSession delegate
@ -81,10 +82,9 @@ RCT_EXPORT_MODULE()
totalBytesSent:(int64_t)totalBytesSent
totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend
{
[[_delegates objectForKey:task] URLRequest:task didUploadProgress:(double)totalBytesSent total:(double)totalBytesExpectedToSend];
[[_delegates objectForKey:task] URLRequest:task didSendDataWithProgress:totalBytesSent];
}
- (void)URLSession:(NSURLSession *)session
dataTask:(NSURLSessionDataTask *)task
didReceiveResponse:(NSURLResponse *)response

View File

@ -8,6 +8,7 @@
/* Begin PBXBuildFile section */
1372B7371AB03E7B00659ED6 /* RCTReachability.m in Sources */ = {isa = PBXBuildFile; fileRef = 1372B7361AB03E7B00659ED6 /* RCTReachability.m */; };
13D6D66A1B5FCF8200883BE9 /* RCTDownloadTask.m in Sources */ = {isa = PBXBuildFile; fileRef = 13D6D6691B5FCF8200883BE9 /* RCTDownloadTask.m */; };
352DA0BA1B17855800AA15A8 /* RCTHTTPRequestHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 352DA0B81B17855800AA15A8 /* RCTHTTPRequestHandler.m */; };
58B512081A9E6CE300147676 /* RCTNetworking.m in Sources */ = {isa = PBXBuildFile; fileRef = 58B512071A9E6CE300147676 /* RCTNetworking.m */; };
/* End PBXBuildFile section */
@ -27,6 +28,8 @@
/* Begin PBXFileReference section */
1372B7351AB03E7B00659ED6 /* RCTReachability.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTReachability.h; sourceTree = "<group>"; };
1372B7361AB03E7B00659ED6 /* RCTReachability.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTReachability.m; sourceTree = "<group>"; };
13D6D6681B5FCF8200883BE9 /* RCTDownloadTask.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTDownloadTask.h; sourceTree = "<group>"; };
13D6D6691B5FCF8200883BE9 /* RCTDownloadTask.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTDownloadTask.m; sourceTree = "<group>"; };
352DA0B71B17855800AA15A8 /* RCTHTTPRequestHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTHTTPRequestHandler.h; sourceTree = "<group>"; };
352DA0B81B17855800AA15A8 /* RCTHTTPRequestHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTHTTPRequestHandler.m; sourceTree = "<group>"; };
58B511DB1A9E6C8500147676 /* libRCTNetwork.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRCTNetwork.a; sourceTree = BUILT_PRODUCTS_DIR; };
@ -48,6 +51,8 @@
58B511D21A9E6C8500147676 = {
isa = PBXGroup;
children = (
13D6D6681B5FCF8200883BE9 /* RCTDownloadTask.h */,
13D6D6691B5FCF8200883BE9 /* RCTDownloadTask.m */,
352DA0B71B17855800AA15A8 /* RCTHTTPRequestHandler.h */,
352DA0B81B17855800AA15A8 /* RCTHTTPRequestHandler.m */,
58B512061A9E6CE300147676 /* RCTNetworking.h */,
@ -124,6 +129,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
13D6D66A1B5FCF8200883BE9 /* RCTDownloadTask.m in Sources */,
1372B7371AB03E7B00659ED6 /* RCTReachability.m in Sources */,
58B512081A9E6CE300147676 /* RCTNetworking.m in Sources */,
352DA0BA1B17855800AA15A8 /* RCTHTTPRequestHandler.m in Sources */,

View File

@ -14,4 +14,3 @@
@interface RCTNetworking : NSObject <RCTBridgeModule>
@end

View File

@ -11,18 +11,19 @@
#import "RCTAssert.h"
#import "RCTConvert.h"
#import "RCTDownloadTask.h"
#import "RCTURLRequestHandler.h"
#import "RCTEventDispatcher.h"
#import "RCTHTTPRequestHandler.h"
#import "RCTLog.h"
#import "RCTUtils.h"
typedef void (^RCTHTTPQueryResult)(NSError *error, NSDictionary *result);
typedef RCTURLRequestCancellationBlock (^RCTHTTPQueryResult)(NSError *error, NSDictionary *result);
@interface RCTNetworking ()<RCTURLRequestDelegate>
- (void)processDataForHTTPQuery:(NSDictionary *)data callback:(void (^)(NSError *error, NSDictionary *result))callback;
@interface RCTNetworking ()
- (RCTURLRequestCancellationBlock)processDataForHTTPQuery:(NSDictionary *)data
callback:(RCTHTTPQueryResult)callback;
@end
/**
@ -30,7 +31,7 @@ typedef void (^RCTHTTPQueryResult)(NSError *error, NSDictionary *result);
*/
@interface RCTHTTPFormDataHelper : NSObject
@property (nonatomic, weak) RCTNetworking *dataManager;
@property (nonatomic, weak) RCTNetworking *networker;
@end
@ -42,51 +43,49 @@ typedef void (^RCTHTTPQueryResult)(NSError *error, NSDictionary *result);
NSString *boundary;
}
- (void)process:(NSArray *)formData callback:(void (^)(NSError *error, NSDictionary *result))callback
static NSString *RCTGenerateFormBoundary()
{
if (![formData count]) {
callback(nil, nil);
return;
const size_t boundaryLength = 70;
const char *boundaryChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_./";
char *bytes = malloc(boundaryLength);
size_t charCount = strlen(boundaryChars);
for (int i = 0; i < boundaryLength; i++) {
bytes[i] = boundaryChars[arc4random_uniform((u_int32_t)charCount)];
}
return [[NSString alloc] initWithBytesNoCopy:bytes length:boundaryLength encoding:NSUTF8StringEncoding freeWhenDone:YES];
}
- (RCTURLRequestCancellationBlock)process:(NSArray *)formData
callback:(RCTHTTPQueryResult)callback
{
if (formData.count == 0) {
return callback(nil, nil);
}
parts = [formData mutableCopy];
_callback = callback;
multipartBody = [[NSMutableData alloc] init];
boundary = [self generateBoundary];
boundary = RCTGenerateFormBoundary();
NSDictionary *currentPart = [parts objectAtIndex: 0];
[_dataManager processDataForHTTPQuery:currentPart callback:^(NSError *e, NSDictionary *r) {
[self handleResult:r error:e];
return [_networker processDataForHTTPQuery:parts[0] callback:^(NSError *error, NSDictionary *result) {
return [self handleResult:result error:error];
}];
}
- (NSString *)generateBoundary
{
NSString *const boundaryChars = @"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_./";
const NSUInteger boundaryLength = 70;
NSMutableString *output = [NSMutableString stringWithCapacity:boundaryLength];
NSUInteger numchars = [boundaryChars length];
for (NSUInteger i = 0; i < boundaryLength; i++) {
[output appendFormat:@"%C", [boundaryChars characterAtIndex:arc4random_uniform((u_int32_t)numchars)]];
}
return output;
}
- (void)handleResult:(NSDictionary *)result error:(NSError *)error
- (RCTURLRequestCancellationBlock)handleResult:(NSDictionary *)result
error:(NSError *)error
{
if (error) {
_callback(error, nil);
return;
return _callback(error, nil);
}
NSDictionary *currentPart = parts[0];
[parts removeObjectAtIndex:0];
// Start with boundary.
[multipartBody appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary]
dataUsingEncoding:NSUTF8StringEncoding]];
// Print headers.
NSMutableDictionary *headers = [(NSDictionary*)currentPart[@"headers"] mutableCopy];
NSMutableDictionary *headers = [parts[0][@"headers"] mutableCopy];
NSString *partContentType = result[@"contentType"];
if (partContentType != nil) {
[headers setObject:partContentType forKey:@"content-type"];
@ -101,110 +100,18 @@ typedef void (^RCTHTTPQueryResult)(NSError *error, NSDictionary *result);
[multipartBody appendData:result[@"body"]];
[multipartBody appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
if ([parts count]) {
NSDictionary *nextPart = [parts objectAtIndex: 0];
[_dataManager processDataForHTTPQuery:nextPart callback:^(NSError *e, NSDictionary *r) {
[self handleResult:r error:e];
[parts removeObjectAtIndex:0];
if (parts.count) {
return [_networker processDataForHTTPQuery:parts[0] callback:^(NSError *err, NSDictionary *res) {
return [self handleResult:res error:err];
}];
return;
}
// We've processed the last item. Finish and return.
[multipartBody appendData:[[NSString stringWithFormat:@"--%@--\r\n", boundary]
dataUsingEncoding:NSUTF8StringEncoding]];
NSString *contentType = [NSString stringWithFormat:@"multipart/form-data; boundary=\"%@\"", boundary];
_callback(nil, @{@"body": multipartBody, @"contentType": contentType});
}
@end
/**
* Helper to package in-flight requests together with their response data.
*/
@interface RCTActiveURLRequest : NSObject
@property (nonatomic, strong) NSNumber *requestID;
@property (nonatomic, strong) NSURLRequest *request;
@property (nonatomic, strong) id<RCTURLRequestHandler> handler;
@property (nonatomic, assign) BOOL incrementalUpdates;
@property (nonatomic, strong) NSURLResponse *response;
@property (nonatomic, strong) NSMutableData *data;
@end
@implementation RCTActiveURLRequest
- (instancetype)init
{
if ((self = [super init])) {
_data = [[NSMutableData alloc] init];
}
return self;
}
@end
/**
* Helper to load request body data using a handler.
*/
@interface RCTDataLoader : NSObject <RCTURLRequestDelegate>
@end
typedef void (^RCTDataLoaderCallback)(NSData *data, NSString *MIMEType, NSError *error);
@implementation RCTDataLoader
{
RCTDataLoaderCallback _callback;
RCTActiveURLRequest *_request;
id _requestToken;
}
- (instancetype)initWithRequest:(NSURLRequest *)request
handler:(id<RCTURLRequestHandler>)handler
callback:(RCTDataLoaderCallback)callback
{
RCTAssertParam(request);
RCTAssertParam(handler);
RCTAssertParam(callback);
if ((self = [super init])) {
_callback = callback;
_request = [[RCTActiveURLRequest alloc] init];
_request.request = request;
_request.handler = handler;
_request.incrementalUpdates = NO;
_requestToken = [handler sendRequest:request withDelegate:self];
}
return self;
}
- (instancetype)init
{
return [self initWithRequest:nil handler:nil callback:nil];
}
- (void)URLRequest:(id)requestToken didUploadProgress:(double)progress total:(double)total
{
RCTAssert([requestToken isEqual:_requestToken], @"Shouldn't ever happen");
}
- (void)URLRequest:(id)requestToken didReceiveResponse:(NSURLResponse *)response
{
RCTAssert([requestToken isEqual:_requestToken], @"Shouldn't ever happen");
_request.response = response;
}
- (void)URLRequest:(id)requestToken didReceiveData:(NSData *)data
{
RCTAssert([requestToken isEqual:_requestToken], @"Shouldn't ever happen");
[_request.data appendData:data];
}
- (void)URLRequest:(id)requestToken didCompleteWithError:(NSError *)error
{
RCTAssert(_callback != nil, @"The callback property must be set");
_callback(_request.data, _request.response.MIMEType, error);
return _callback(nil, @{@"body": multipartBody, @"contentType": contentType});
}
@end
@ -214,8 +121,7 @@ typedef void (^RCTDataLoaderCallback)(NSData *data, NSString *MIMEType, NSError
*/
@implementation RCTNetworking
{
NSInteger _currentRequestID;
NSMapTable *_activeRequests;
NSMutableDictionary *_tasksByRequestID;
}
@synthesize bridge = _bridge;
@ -226,16 +132,13 @@ RCT_EXPORT_MODULE()
- (instancetype)init
{
if ((self = [super init])) {
_currentRequestID = 0;
_activeRequests = [[NSMapTable alloc] initWithKeyOptions:NSPointerFunctionsStrongMemory
valueOptions:NSPointerFunctionsStrongMemory
capacity:0];
_tasksByRequestID = [[NSMutableDictionary alloc] init];
}
return self;
}
- (void)buildRequest:(NSDictionary *)query
completionBlock:(void (^)(NSURLRequest *request))block
- (RCTURLRequestCancellationBlock)buildRequest:(NSDictionary *)query
completionBlock:(void (^)(NSURLRequest *request))block
{
NSURL *URL = [RCTConvert NSURL:query[@"url"]];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:URL];
@ -243,11 +146,11 @@ RCT_EXPORT_MODULE()
request.allHTTPHeaderFields = [RCTConvert NSDictionary:query[@"headers"]];
NSDictionary *data = [RCTConvert NSDictionary:query[@"data"]];
[self processDataForHTTPQuery:data callback:^(NSError *error, NSDictionary *result) {
return [self processDataForHTTPQuery:data callback:^(NSError *error, NSDictionary *result) {
if (error) {
RCTLogError(@"Error processing request body: %@", error);
// Ideally we'd circle back to JS here and notify an error/abort on the request.
return;
return (RCTURLRequestCancellationBlock)nil;
}
request.HTTPBody = result[@"body"];
NSString *contentType = result[@"contentType"];
@ -262,6 +165,7 @@ RCT_EXPORT_MODULE()
}
block(request);
return (RCTURLRequestCancellationBlock)nil;
}];
}
@ -316,71 +220,56 @@ RCT_EXPORT_MODULE()
* - @"contentType" (NSString): the content type header of the request
*
*/
- (void)processDataForHTTPQuery:(NSDictionary *)query callback:(void (^)(NSError *error, NSDictionary *result))callback
- (RCTURLRequestCancellationBlock)processDataForHTTPQuery:(NSDictionary *)query callback:
(RCTURLRequestCancellationBlock (^)(NSError *error, NSDictionary *result))callback
{
if (!query) {
callback(nil, nil);
return;
return callback(nil, nil);
}
NSData *body = [RCTConvert NSData:query[@"string"]];
if (body) {
callback(nil, @{@"body": body});
return;
return callback(nil, @{@"body": body});
}
NSURLRequest *request = [RCTConvert NSURLRequest:query[@"uri"]];
if (request) {
id<RCTURLRequestHandler> handler = [self handlerForRequest:request];
if (!handler) {
return;
return callback(nil, nil);
}
(void)[[RCTDataLoader alloc] initWithRequest:request handler:handler callback:^(NSData *data, NSString *MIMEType, NSError *error) {
if (data) {
callback(nil, @{@"body": data, @"contentType": MIMEType});
} else {
callback(error, nil);
}
__block RCTURLRequestCancellationBlock cancellationBlock = nil;
RCTDownloadTask *task = [[RCTDownloadTask alloc] initWithRequest:request handler:handler completionBlock:^(NSURLResponse *response, NSData *data, NSError *error) {
cancellationBlock = callback(error, data ? @{@"body": data, @"contentType": RCTNullIfNil(response.MIMEType)} : nil);
}];
return;
__weak RCTDownloadTask *weakTask = task;
return ^{
[weakTask cancel];
if (cancellationBlock) {
cancellationBlock();
}
};
}
NSDictionaryArray *formData = [RCTConvert NSDictionaryArray:query[@"formData"]];
if (formData != nil) {
if (formData) {
RCTHTTPFormDataHelper *formDataHelper = [[RCTHTTPFormDataHelper alloc] init];
formDataHelper.dataManager = self;
[formDataHelper process:formData callback:callback];
return;
formDataHelper.networker = self;
return [formDataHelper process:formData callback:callback];
}
// Nothing in the data payload, at least nothing we could understand anyway.
// Ignore and treat it as if it were null.
callback(nil, nil);
return callback(nil, nil);
}
- (void)sendRequest:(NSURLRequest *)request
incrementalUpdates:(BOOL)incrementalUpdates
responseSender:(RCTResponseSenderBlock)responseSender
{
id<RCTURLRequestHandler> handler = [self handlerForRequest:request];
id token = [handler sendRequest:request withDelegate:self];
if (token) {
RCTActiveURLRequest *activeRequest = [[RCTActiveURLRequest alloc] init];
activeRequest.requestID = @(++_currentRequestID);
activeRequest.request = request;
activeRequest.handler = handler;
activeRequest.incrementalUpdates = incrementalUpdates;
[_activeRequests setObject:activeRequest forKey:token];
responseSender(@[activeRequest.requestID]);
}
}
- (void)sendData:(NSData *)data forRequestToken:(id)requestToken
- (void)sendData:(NSData *)data forTask:(RCTDownloadTask *)task
{
if (data.length == 0) {
return;
}
RCTActiveURLRequest *request = [_activeRequests objectForKey:requestToken];
// Get text encoding
NSURLResponse *response = request.response;
NSURLResponse *response = task.response;
NSStringEncoding encoding = NSUTF8StringEncoding;
if (response.textEncodingName) {
CFStringEncoding cfEncoding = CFStringConvertIANACharSetNameToEncoding((CFStringRef)response.textEncodingName);
@ -393,82 +282,81 @@ RCT_EXPORT_MODULE()
return;
}
NSArray *responseJSON = @[request.requestID, responseText ?: @""];
NSArray *responseJSON = @[task.requestID, responseText ?: @""];
[_bridge.eventDispatcher sendDeviceEventWithName:@"didReceiveNetworkData"
body:responseJSON];
}
#pragma mark - RCTURLRequestDelegate
- (void)URLRequest:(id)requestToken didUploadProgress:(double)progress total:(double)total
- (void)sendRequest:(NSURLRequest *)request
incrementalUpdates:(BOOL)incrementalUpdates
responseSender:(RCTResponseSenderBlock)responseSender
{
dispatch_async(_methodQueue, ^{
RCTActiveURLRequest *request = [_activeRequests objectForKey:requestToken];
RCTAssert(request != nil, @"Unrecognized request token: %@", requestToken);
id<RCTURLRequestHandler> handler = [self handlerForRequest:request];
if (!handler) {
return;
}
NSArray *responseJSON = @[request.requestID, @(progress), @(total)];
[_bridge.eventDispatcher sendDeviceEventWithName:@"didUploadProgress" body:responseJSON];
});
}
__block RCTDownloadTask *task;
- (void)URLRequest:(id)requestToken didReceiveResponse:(NSURLResponse *)response
{
dispatch_async(_methodQueue, ^{
RCTActiveURLRequest *request = [_activeRequests objectForKey:requestToken];
RCTAssert(request != nil, @"Unrecognized request token: %@", requestToken);
RCTURLRequestProgressBlock uploadProgressBlock = ^(double progress, double total) {
dispatch_async(_methodQueue, ^{
NSArray *responseJSON = @[task.requestID, @(progress), @(total)];
[_bridge.eventDispatcher sendDeviceEventWithName:@"didSendNetworkData" body:responseJSON];
});
};
request.response = response;
void (^responseBlock)(NSURLResponse *) = ^(NSURLResponse *response) {
dispatch_async(_methodQueue, ^{
NSHTTPURLResponse *httpResponse = nil;
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
// Might be a local file request
httpResponse = (NSHTTPURLResponse *)response;
}
NSArray *responseJSON = @[task.requestID,
@(httpResponse.statusCode ?: 200),
httpResponse.allHeaderFields ?: @{},
];
NSHTTPURLResponse *httpResponse = nil;
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
// Might be a local file request
httpResponse = (NSHTTPURLResponse *)response;
}
[_bridge.eventDispatcher sendDeviceEventWithName:@"didReceiveNetworkResponse"
body:responseJSON];
});
};
NSArray *responseJSON = @[request.requestID,
@(httpResponse.statusCode ?: 200),
httpResponse.allHeaderFields ?: @{},
];
void (^incrementalDataBlock)(NSData *) = incrementalUpdates ? ^(NSData *data) {
dispatch_async(_methodQueue, ^{
[self sendData:data forTask:task];
});
} : nil;
[_bridge.eventDispatcher sendDeviceEventWithName:@"didReceiveNetworkResponse"
body:responseJSON];
});
}
RCTURLRequestCompletionBlock completionBlock =
^(NSURLResponse *response, NSData *data, NSError *error) {
dispatch_async(_methodQueue, ^{
if (!incrementalUpdates) {
[self sendData:data forTask:task];
}
NSArray *responseJSON = @[task.requestID,
RCTNullIfNil(error.localizedDescription),
];
- (void)URLRequest:(id)requestToken didReceiveData:(NSData *)data
{
dispatch_async(_methodQueue, ^{
RCTActiveURLRequest *request = [_activeRequests objectForKey:requestToken];
RCTAssert(request != nil, @"Unrecognized request token: %@", requestToken);
[_bridge.eventDispatcher sendDeviceEventWithName:@"didCompleteNetworkResponse"
body:responseJSON];
if (request.incrementalUpdates) {
[self sendData:data forRequestToken:requestToken];
} else {
[request.data appendData:data];
}
});
}
[_tasksByRequestID removeObjectForKey:task.requestID];
});
};
- (void)URLRequest:(id)requestToken didCompleteWithError:(NSError *)error
{
dispatch_async(_methodQueue, ^{
RCTActiveURLRequest *request = [_activeRequests objectForKey:requestToken];
RCTAssert(request != nil, @"Unrecognized request token: %@", requestToken);
task = [[RCTDownloadTask alloc] initWithRequest:request
handler:handler
completionBlock:completionBlock];
if (!request.incrementalUpdates) {
[self sendData:request.data forRequestToken:requestToken];
}
task.incrementalDataBlock = incrementalDataBlock;
task.responseBlock = responseBlock;
task.uploadProgressBlock = uploadProgressBlock;
NSArray *responseJSON = @[
request.requestID,
RCTNullIfNil(error.localizedDescription),
];
[_bridge.eventDispatcher sendDeviceEventWithName:@"didCompleteNetworkResponse"
body:responseJSON];
[_activeRequests removeObjectForKey:requestToken];
});
if (task.requestID) {
_tasksByRequestID[task.requestID] = task;
responseSender(@[task.requestID]);
}
}
#pragma mark - JS API
@ -476,31 +364,22 @@ RCT_EXPORT_MODULE()
RCT_EXPORT_METHOD(sendRequest:(NSDictionary *)query
responseSender:(RCTResponseSenderBlock)responseSender)
{
// TODO: buildRequest returns a cancellation block, but there's currently
// no way to invoke it, if, for example the request is cancelled while
// loading a large file to build the request body
[self buildRequest:query completionBlock:^(NSURLRequest *request) {
BOOL incrementalUpdates = [RCTConvert BOOL:query[@"incrementalUpdates"]];
[self sendRequest:request incrementalUpdates:incrementalUpdates
[self sendRequest:request
incrementalUpdates:incrementalUpdates
responseSender:responseSender];
}];
}
RCT_EXPORT_METHOD(cancelRequest:(NSNumber *)requestID)
{
id requestToken = nil;
RCTActiveURLRequest *activeRequest = nil;
for (id token in _activeRequests) {
RCTActiveURLRequest *request = [_activeRequests objectForKey:token];
if ([request.requestID isEqualToNumber:requestID]) {
activeRequest = request;
requestToken = token;
break;
}
}
id<RCTURLRequestHandler> handler = activeRequest.handler;
if ([handler respondsToSelector:@selector(cancelRequest:)]) {
[activeRequest.handler cancelRequest:requestToken];
}
[_tasksByRequestID[requestID] cancel];
[_tasksByRequestID removeObjectForKey:requestID];
}
@end

View File

@ -35,7 +35,7 @@ class XMLHttpRequest extends XMLHttpRequestBase {
_didCreateRequest(requestId: number): void {
this._requestId = requestId;
this._subscriptions.push(RCTDeviceEventEmitter.addListener(
'didUploadProgress',
'didSendNetworkData',
(args) => this._didUploadProgress.call(this, args[0], args[1], args[2])
));
this._subscriptions.push(RCTDeviceEventEmitter.addListener(

View File

@ -16,10 +16,10 @@
@protocol RCTURLRequestDelegate <NSObject>
/**
* Call this when you first receives a response from the server. This should
* include response headers, etc.
* Call this when you send request data to the server. This is used to track
* upload progress, so should be called multiple times for large request bodies.
*/
- (void)URLRequest:(id)requestToken didUploadProgress:(double)progress total:(double)total;
- (void)URLRequest:(id)requestToken didSendDataWithProgress:(int64_t)bytesSent;
/**
* Call this when you first receives a response from the server. This should