Added RCTDataRequestHandler

Summary: public

Added RCTDataRequestHandler, which is responsible for loading data URLs. This moves the logic for data URL handling out of RCTImageDownloader (no longer needed) and into the RCTNetwork library, where it makes more sense.

This also means that it is now possible to load data URLs via XHR, and use them for purposes other than just images.

Reviewed By: javache

Differential Revision: D2540964

fb-gh-sync-id: 4f0418bd6b9186f047cc8297276bb970795af104
This commit is contained in:
Nick Lockwood
2015-10-19 09:04:54 -07:00
committed by facebook-github-bot-4
parent 31f9a690f3
commit 1076f4a172
19 changed files with 516 additions and 397 deletions

View File

@@ -0,0 +1,18 @@
/**
* 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 "RCTURLRequestHandler.h"
#import "RCTInvalidating.h"
/**
* This is the default RCTURLRequestHandler implementation for data URL requests.
*/
@interface RCTDataRequestHandler : NSObject <RCTURLRequestHandler, RCTInvalidating>
@end

View File

@@ -0,0 +1,73 @@
/**
* 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 "RCTDataRequestHandler.h"
@implementation RCTDataRequestHandler
{
NSOperationQueue *_queue;
}
RCT_EXPORT_MODULE()
- (void)invalidate
{
[_queue cancelAllOperations];
_queue = nil;
}
- (BOOL)canHandleRequest:(NSURLRequest *)request
{
return [request.URL.scheme caseInsensitiveCompare:@"data"] == NSOrderedSame;
}
- (NSOperation *)sendRequest:(NSURLRequest *)request
withDelegate:(id<RCTURLRequestDelegate>)delegate
{
// Lazy setup
if (!_queue) {
_queue = [NSOperationQueue new];
_queue.maxConcurrentOperationCount = 2;
}
__block NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
// Get mime type
NSRange firstSemicolon = [request.URL.resourceSpecifier rangeOfString:@";"];
NSString *mimeType = firstSemicolon.length ? [request.URL.resourceSpecifier substringToIndex:firstSemicolon.location] : nil;
// Send response
NSURLResponse *response = [[NSURLResponse alloc] initWithURL:request.URL
MIMEType:mimeType
expectedContentLength:-1
textEncodingName:nil];
[delegate URLRequest:op didReceiveResponse:response];
// Load data
NSError *error;
NSData *data = [NSData dataWithContentsOfURL:request.URL
options:NSDataReadingMappedIfSafe
error:&error];
if (data) {
[delegate URLRequest:op didReceiveData:data];
}
[delegate URLRequest:op didCompleteWithError:error];
}];
[_queue addOperation:op];
return op;
}
- (void)cancelRequest:(NSOperation *)op
{
[op cancel];
}
@end

View File

@@ -11,13 +11,13 @@
#import <MobileCoreServices/MobileCoreServices.h>
#import "RCTUtils.h"
@implementation RCTFileRequestHandler
{
NSOperationQueue *_fileQueue;
}
@synthesize methodQueue = _methodQueue;
RCT_EXPORT_MODULE()
- (void)invalidate
@@ -28,7 +28,9 @@ RCT_EXPORT_MODULE()
- (BOOL)canHandleRequest:(NSURLRequest *)request
{
return [request.URL.scheme caseInsensitiveCompare:@"file"] == NSOrderedSame;
return
[request.URL.scheme caseInsensitiveCompare:@"file"] == NSOrderedSame
&& !RCTIsXCAssetURL(request.URL);
}
- (NSOperation *)sendRequest:(NSURLRequest *)request

View File

@@ -7,8 +7,9 @@
objects = {
/* Begin PBXBuildFile section */
134E969A1BCEB7F800AFFDA1 /* RCTDataRequestHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 134E96991BCEB7F800AFFDA1 /* RCTDataRequestHandler.m */; settings = {ASSET_TAGS = (); }; };
1372B7371AB03E7B00659ED6 /* RCTNetInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = 1372B7361AB03E7B00659ED6 /* RCTNetInfo.m */; };
13D6D66A1B5FCF8200883BE9 /* RCTDownloadTask.m in Sources */ = {isa = PBXBuildFile; fileRef = 13D6D6691B5FCF8200883BE9 /* RCTDownloadTask.m */; };
13D6D66A1B5FCF8200883BE9 /* RCTNetworkTask.m in Sources */ = {isa = PBXBuildFile; fileRef = 13D6D6691B5FCF8200883BE9 /* RCTNetworkTask.m */; };
13EF800E1BCBE015003F47DD /* RCTFileRequestHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 13EF800D1BCBE015003F47DD /* RCTFileRequestHandler.m */; settings = {ASSET_TAGS = (); }; };
352DA0BA1B17855800AA15A8 /* RCTHTTPRequestHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 352DA0B81B17855800AA15A8 /* RCTHTTPRequestHandler.m */; };
58B512081A9E6CE300147676 /* RCTNetworking.m in Sources */ = {isa = PBXBuildFile; fileRef = 58B512071A9E6CE300147676 /* RCTNetworking.m */; };
@@ -27,10 +28,12 @@
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
134E96981BCEB7F800AFFDA1 /* RCTDataRequestHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTDataRequestHandler.h; sourceTree = "<group>"; };
134E96991BCEB7F800AFFDA1 /* RCTDataRequestHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTDataRequestHandler.m; sourceTree = "<group>"; };
1372B7351AB03E7B00659ED6 /* RCTNetInfo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = RCTNetInfo.h; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; };
1372B7361AB03E7B00659ED6 /* RCTNetInfo.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = RCTNetInfo.m; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.objc; };
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>"; };
13D6D6681B5FCF8200883BE9 /* RCTNetworkTask.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTNetworkTask.h; sourceTree = "<group>"; };
13D6D6691B5FCF8200883BE9 /* RCTNetworkTask.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTNetworkTask.m; sourceTree = "<group>"; };
13EF800C1BCBE015003F47DD /* RCTFileRequestHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTFileRequestHandler.h; sourceTree = "<group>"; };
13EF800D1BCBE015003F47DD /* RCTFileRequestHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTFileRequestHandler.m; sourceTree = "<group>"; };
352DA0B71B17855800AA15A8 /* RCTHTTPRequestHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTHTTPRequestHandler.h; sourceTree = "<group>"; };
@@ -54,12 +57,14 @@
58B511D21A9E6C8500147676 = {
isa = PBXGroup;
children = (
13D6D6681B5FCF8200883BE9 /* RCTDownloadTask.h */,
13D6D6691B5FCF8200883BE9 /* RCTDownloadTask.m */,
13D6D6681B5FCF8200883BE9 /* RCTNetworkTask.h */,
13D6D6691B5FCF8200883BE9 /* RCTNetworkTask.m */,
352DA0B71B17855800AA15A8 /* RCTHTTPRequestHandler.h */,
352DA0B81B17855800AA15A8 /* RCTHTTPRequestHandler.m */,
13EF800C1BCBE015003F47DD /* RCTFileRequestHandler.h */,
13EF800D1BCBE015003F47DD /* RCTFileRequestHandler.m */,
134E96981BCEB7F800AFFDA1 /* RCTDataRequestHandler.h */,
134E96991BCEB7F800AFFDA1 /* RCTDataRequestHandler.m */,
1372B7351AB03E7B00659ED6 /* RCTNetInfo.h */,
1372B7361AB03E7B00659ED6 /* RCTNetInfo.m */,
58B512061A9E6CE300147676 /* RCTNetworking.h */,
@@ -134,8 +139,9 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
13D6D66A1B5FCF8200883BE9 /* RCTDownloadTask.m in Sources */,
13D6D66A1B5FCF8200883BE9 /* RCTNetworkTask.m in Sources */,
13EF800E1BCBE015003F47DD /* RCTFileRequestHandler.m in Sources */,
134E969A1BCEB7F800AFFDA1 /* RCTDataRequestHandler.m in Sources */,
1372B7371AB03E7B00659ED6 /* RCTNetInfo.m in Sources */,
58B512081A9E6CE300147676 /* RCTNetworking.m in Sources */,
352DA0BA1B17855800AA15A8 /* RCTHTTPRequestHandler.m in Sources */,

View File

@@ -18,7 +18,7 @@ typedef void (^RCTURLRequestIncrementalDataBlock)(NSData *data);
typedef void (^RCTURLRequestProgressBlock)(int64_t progress, int64_t total);
typedef void (^RCTURLRequestResponseBlock)(NSURLResponse *response);
@interface RCTDownloadTask : NSObject <RCTURLRequestDelegate>
@interface RCTNetworkTask : NSObject <RCTURLRequestDelegate>
@property (nonatomic, readonly) NSURLRequest *request;
@property (nonatomic, readonly) NSNumber *requestID;

View File

@@ -7,15 +7,15 @@
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import "RCTDownloadTask.h"
#import "RCTNetworkTask.h"
#import "RCTLog.h"
@implementation RCTDownloadTask
@implementation RCTNetworkTask
{
NSMutableData *_data;
id<RCTURLRequestHandler> _handler;
RCTDownloadTask *_selfReference;
RCTNetworkTask *_selfReference;
}
- (instancetype)initWithRequest:(NSURLRequest *)request

View File

@@ -10,12 +10,21 @@
#import <Foundation/Foundation.h>
#import "RCTBridge.h"
#import "RCTDownloadTask.h"
#import "RCTNetworkTask.h"
@interface RCTNetworking : NSObject <RCTBridgeModule>
- (RCTDownloadTask *)downloadTaskWithRequest:(NSURLRequest *)request
completionBlock:(RCTURLRequestCompletionBlock)completionBlock;
/**
* Does a handler exist for the specified request?
*/
- (BOOL)canHandleRequest:(NSURLRequest *)request;
/**
* Return an RCTNetworkTask for the specified request. This is useful for
* invoking the React Native networking stack from within native code.
*/
- (RCTNetworkTask *)networkTaskWithRequest:(NSURLRequest *)request
completionBlock:(RCTURLRequestCompletionBlock)completionBlock;
@end

View File

@@ -11,7 +11,7 @@
#import "RCTAssert.h"
#import "RCTConvert.h"
#import "RCTDownloadTask.h"
#import "RCTNetworkTask.h"
#import "RCTURLRequestHandler.h"
#import "RCTEventDispatcher.h"
#import "RCTHTTPRequestHandler.h"
@@ -37,10 +37,10 @@ typedef RCTURLRequestCancellationBlock (^RCTHTTPQueryResult)(NSError *error, NSD
@implementation RCTHTTPFormDataHelper
{
NSMutableArray *parts;
NSMutableData *multipartBody;
NSMutableArray *_parts;
NSMutableData *_multipartBody;
RCTHTTPQueryResult _callback;
NSString *boundary;
NSString *_boundary;
}
static NSString *RCTGenerateFormBoundary()
@@ -56,19 +56,19 @@ static NSString *RCTGenerateFormBoundary()
return [[NSString alloc] initWithBytesNoCopy:bytes length:boundaryLength encoding:NSUTF8StringEncoding freeWhenDone:YES];
}
- (RCTURLRequestCancellationBlock)process:(NSArray *)formData
- (RCTURLRequestCancellationBlock)process:(NSDictionaryArray *)formData
callback:(RCTHTTPQueryResult)callback
{
if (formData.count == 0) {
return callback(nil, nil);
}
parts = [formData mutableCopy];
_parts = [formData mutableCopy];
_callback = callback;
multipartBody = [NSMutableData new];
boundary = RCTGenerateFormBoundary();
_multipartBody = [NSMutableData new];
_boundary = RCTGenerateFormBoundary();
return [_networker processDataForHTTPQuery:parts[0] callback:^(NSError *error, NSDictionary *result) {
return [_networker processDataForHTTPQuery:_parts[0] callback:^(NSError *error, NSDictionary *result) {
return [self handleResult:result error:error];
}];
}
@@ -81,37 +81,37 @@ static NSString *RCTGenerateFormBoundary()
}
// Start with boundary.
[multipartBody appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary]
dataUsingEncoding:NSUTF8StringEncoding]];
[_multipartBody appendData:[[NSString stringWithFormat:@"--%@\r\n", _boundary]
dataUsingEncoding:NSUTF8StringEncoding]];
// Print headers.
NSMutableDictionary *headers = [parts[0][@"headers"] mutableCopy];
NSMutableDictionary *headers = [_parts[0][@"headers"] mutableCopy];
NSString *partContentType = result[@"contentType"];
if (partContentType != nil) {
headers[@"content-type"] = partContentType;
}
[headers enumerateKeysAndObjectsUsingBlock:^(NSString *parameterKey, NSString *parameterValue, BOOL *stop) {
[multipartBody appendData:[[NSString stringWithFormat:@"%@: %@\r\n", parameterKey, parameterValue]
dataUsingEncoding:NSUTF8StringEncoding]];
[_multipartBody appendData:[[NSString stringWithFormat:@"%@: %@\r\n", parameterKey, parameterValue]
dataUsingEncoding:NSUTF8StringEncoding]];
}];
// Add the body.
[multipartBody appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
[multipartBody appendData:result[@"body"]];
[multipartBody appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
[_multipartBody appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
[_multipartBody appendData:result[@"body"]];
[_multipartBody appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
[parts removeObjectAtIndex:0];
if (parts.count) {
return [_networker processDataForHTTPQuery:parts[0] callback:^(NSError *err, NSDictionary *res) {
[_parts removeObjectAtIndex:0];
if (_parts.count) {
return [_networker processDataForHTTPQuery:_parts[0] callback:^(NSError *err, NSDictionary *res) {
return [self handleResult:res error:err];
}];
}
// 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];
return _callback(nil, @{@"body": multipartBody, @"contentType": contentType});
[_multipartBody appendData:[[NSString stringWithFormat:@"--%@--\r\n", _boundary]
dataUsingEncoding:NSUTF8StringEncoding]];
NSString *contentType = [NSString stringWithFormat:@"multipart/form-data; boundary=\"%@\"", _boundary];
return _callback(nil, @{@"body": _multipartBody, @"contentType": contentType});
}
@end
@@ -122,6 +122,7 @@ static NSString *RCTGenerateFormBoundary()
@implementation RCTNetworking
{
NSMutableDictionary *_tasksByRequestID;
NSArray *_handlers;
}
@synthesize bridge = _bridge;
@@ -129,12 +130,65 @@ static NSString *RCTGenerateFormBoundary()
RCT_EXPORT_MODULE()
- (instancetype)init
- (void)setBridge:(RCTBridge *)bridge
{
if ((self = [super init])) {
_tasksByRequestID = [NSMutableDictionary new];
// get handlers
NSMutableArray *handlers = [NSMutableArray array];
for (id<RCTBridgeModule> module in bridge.modules.allValues) {
if ([module conformsToProtocol:@protocol(RCTURLRequestHandler)]) {
[handlers addObject:module];
}
}
return self;
// Sort handlers in reverse priority order (highest priority first)
[handlers sortUsingComparator:^NSComparisonResult(id<RCTURLRequestHandler> a, id<RCTURLRequestHandler> b) {
float priorityA = [a respondsToSelector:@selector(handlerPriority)] ? [a handlerPriority] : 0;
float priorityB = [b respondsToSelector:@selector(handlerPriority)] ? [b handlerPriority] : 0;
if (priorityA > priorityB) {
return NSOrderedAscending;
} else if (priorityA < priorityB) {
return NSOrderedDescending;
} else {
return NSOrderedSame;
}
}];
_bridge = bridge;
_handlers = handlers;
_tasksByRequestID = [NSMutableDictionary new];
}
- (id<RCTURLRequestHandler>)handlerForRequest:(NSURLRequest *)request
{
if (RCT_DEBUG) {
// Check for handler conflicts
float previousPriority = 0;
id<RCTURLRequestHandler> previousHandler = nil;
for (id<RCTURLRequestHandler> handler in _handlers) {
if ([handler canHandleRequest:request]) {
float priority = [handler respondsToSelector:@selector(handlerPriority)] ? [handler handlerPriority] : 0;
if (previousHandler) {
if (priority == previousPriority) {
RCTLogError(@"The RCTURLRequestHandlers %@ and %@ both reported that"
" they can handle the request %@, and have equal priority"
" (%g). This could result in non-deterministic behavior.",
handler, previousHandler, request, priority);
}
} else {
previousHandler = handler;
previousPriority = priority;
}
}
}
}
// Normal code path
for (id<RCTURLRequestHandler> handler in _handlers) {
if ([handler canHandleRequest:request]) {
return handler;
}
}
return nil;
}
- (RCTURLRequestCancellationBlock)buildRequest:(NSDictionary *)query
@@ -169,37 +223,9 @@ RCT_EXPORT_MODULE()
}];
}
- (id<RCTURLRequestHandler>)handlerForRequest:(NSURLRequest *)request
- (BOOL)canHandleRequest:(NSURLRequest *)request
{
NSMutableArray *handlers = [NSMutableArray array];
for (id<RCTBridgeModule> module in _bridge.modules.allValues) {
if ([module conformsToProtocol:@protocol(RCTURLRequestHandler)]) {
if ([(id<RCTURLRequestHandler>)module canHandleRequest:request]) {
[handlers addObject:module];
}
}
}
[handlers sortUsingComparator:^NSComparisonResult(id<RCTURLRequestHandler> a, id<RCTURLRequestHandler> b) {
float priorityA = [a respondsToSelector:@selector(handlerPriority)] ? [a handlerPriority] : 0;
float priorityB = [b respondsToSelector:@selector(handlerPriority)] ? [b handlerPriority] : 0;
if (priorityA < priorityB) {
return NSOrderedAscending;
} else if (priorityA > priorityB) {
return NSOrderedDescending;
} else {
RCTLogError(@"The RCTURLRequestHandlers %@ and %@ both reported that"
" they can handle the request %@, and have equal priority"
" (%g). This could result in non-deterministic behavior.",
a, b, request, priorityA);
return NSOrderedSame;
}
}];
id<RCTURLRequestHandler> handler = handlers.lastObject;
if (!handler) {
RCTLogError(@"No suitable request handler found for %@", request.URL);
}
return handler;
return [self handlerForRequest:request] != nil;
}
/**
@@ -234,13 +260,13 @@ RCT_EXPORT_MODULE()
if (request) {
__block RCTURLRequestCancellationBlock cancellationBlock = nil;
RCTDownloadTask *task = [self downloadTaskWithRequest:request completionBlock:^(NSURLResponse *response, NSData *data, NSError *error) {
RCTNetworkTask *task = [self networkTaskWithRequest:request completionBlock:^(NSURLResponse *response, NSData *data, NSError *error) {
cancellationBlock = callback(error, data ? @{@"body": data, @"contentType": RCTNullIfNil(response.MIMEType)} : nil);
}];
[task start];
__weak RCTDownloadTask *weakTask = task;
__weak RCTNetworkTask *weakTask = task;
return ^{
[weakTask cancel];
if (cancellationBlock) {
@@ -259,7 +285,7 @@ RCT_EXPORT_MODULE()
return callback(nil, nil);
}
- (void)sendData:(NSData *)data forTask:(RCTDownloadTask *)task
- (void)sendData:(NSData *)data forTask:(RCTNetworkTask *)task
{
if (data.length == 0) {
return;
@@ -278,7 +304,7 @@ RCT_EXPORT_MODULE()
if (!responseText && data.length) {
// We don't have an encoding, or the encoding is incorrect, so now we
// try to guess (unfortunately, this feature is available of iOS 8+ only)
// try to guess (unfortunately, this feature is available in iOS 8+ only)
if ([NSString respondsToSelector:@selector(stringEncodingForData:
encodingOptions:
convertedString:
@@ -305,7 +331,7 @@ RCT_EXPORT_MODULE()
incrementalUpdates:(BOOL)incrementalUpdates
responseSender:(RCTResponseSenderBlock)responseSender
{
__block RCTDownloadTask *task;
__block RCTNetworkTask *task;
RCTURLRequestProgressBlock uploadProgressBlock = ^(int64_t progress, int64_t total) {
dispatch_async(_methodQueue, ^{
@@ -355,7 +381,7 @@ RCT_EXPORT_MODULE()
});
};
task = [self downloadTaskWithRequest:request completionBlock:completionBlock];
task = [self networkTaskWithRequest:request completionBlock:completionBlock];
task.incrementalDataBlock = incrementalDataBlock;
task.responseBlock = responseBlock;
task.uploadProgressBlock = uploadProgressBlock;
@@ -370,15 +396,16 @@ RCT_EXPORT_MODULE()
#pragma mark - Public API
- (RCTDownloadTask *)downloadTaskWithRequest:(NSURLRequest *)request
- (RCTNetworkTask *)networkTaskWithRequest:(NSURLRequest *)request
completionBlock:(RCTURLRequestCompletionBlock)completionBlock
{
id<RCTURLRequestHandler> handler = [self handlerForRequest:request];
if (!handler) {
RCTLogError(@"No suitable URL request handler found for %@", request.URL);
return nil;
}
return [[RCTDownloadTask alloc] initWithRequest:request
return [[RCTNetworkTask alloc] initWithRequest:request
handler:handler
completionBlock:completionBlock];
}