Added RCTFileRequestHandler

Summary: @​public

We previously discovered that using an NSURLSessionDataTask to load local files is noticably less efficient than using regular filesystem methods.

This diff adds RCTFileRequestHandler as a replacement for RCTHTTPRequestHandler when loading local files. This reduces loading time when loading local files via XMLHttpRequest, as well as improving the performance for some image load requests.

Reviewed By: @javache

Differential Revision: D2531710

fb-gh-sync-id: 259714baac131784de494d24939f42ad52bff41a
This commit is contained in:
Nick Lockwood 2015-10-13 08:12:54 -07:00 committed by facebook-github-bot-4
parent 28f6eba22d
commit a92f107712
7 changed files with 135 additions and 40 deletions

View File

@ -86,7 +86,7 @@ RCT_EXPORT_MODULE()
}
RCTDownloadTask *task = [_bridge.networking downloadTaskWithRequest:request completionBlock:^(NSURLResponse *response, NSData *data, NSError *error) {
if (response && !error) {
if (response && !error && [response.URL.scheme hasPrefix:@"http"]) {
RCTImageDownloader *strongSelf = weakSelf;
NSCachedURLResponse *cachedResponse = [[NSCachedURLResponse alloc] initWithResponse:response data:data userInfo:nil storagePolicy:NSURLCacheStorageAllowed];
[strongSelf->_cache storeCachedResponse:cachedResponse forRequest:request];
@ -106,9 +106,15 @@ RCT_EXPORT_MODULE()
progressHandler:(RCTImageLoaderProgressBlock)progressHandler
completionHandler:(RCTImageLoaderCompletionBlock)completionHandler
{
if ([imageURL.scheme.lowercaseString hasPrefix:@"http"]) {
if ([imageURL.scheme.lowercaseString hasPrefix:@"http"] ||
[imageURL.scheme caseInsensitiveCompare:@"file"] == NSOrderedSame) {
__block RCTImageLoaderCancellationBlock decodeCancel = nil;
// Add missing png extension
if (imageURL.fileURL && imageURL.pathExtension.length == 0) {
imageURL = [NSURL fileURLWithPath:[imageURL.path stringByAppendingPathExtension:@"png"]];
}
__weak RCTImageDownloader *weakSelf = self;
RCTImageLoaderCancellationBlock downloadCancel = [self downloadDataForURL:imageURL progressHandler:progressHandler completionHandler:^(NSError *error, NSData *imageData) {
if (error) {
@ -153,31 +159,6 @@ RCT_EXPORT_MODULE()
return ^{
cancelled = YES;
};
} else if ([imageURL.scheme isEqualToString:@"file"]) {
__block BOOL cancelled = NO;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
if (cancelled) {
return;
}
UIImage *image = [UIImage imageWithContentsOfFile:imageURL.resourceSpecifier];
if (image) {
if (progressHandler) {
progressHandler(1, 1);
}
if (completionHandler) {
completionHandler(nil, image);
}
} else {
if (completionHandler) {
NSString *message = [NSString stringWithFormat:@"Could not find image at path: %@", imageURL.absoluteString];
completionHandler(RCTErrorWithMessage(message), nil);
}
}
});
return ^{
cancelled = YES;
};
} else {
RCTLogError(@"Unexpected image schema %@", imageURL.scheme);
return ^{};

View File

@ -9,7 +9,7 @@
#import "RCTDownloadTask.h"
#import "RCTAssert.h"
#import "RCTLog.h"
@implementation RCTDownloadTask
{
@ -65,8 +65,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
{
if (![requestToken isEqual:_requestToken]) {
if (RCT_DEBUG) {
RCTAssert([requestToken isEqual:_requestToken],
@"Unrecognized request token: %@", requestToken);
RCTLogError(@"Unrecognized request token: %@ expected: %@", requestToken, _requestToken);
}
if (_completionBlock) {
_completionBlock(_response, _data, [NSError errorWithDomain:RCTErrorDomain code:0

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 file requests.
*/
@interface RCTFileRequestHandler : NSObject <RCTURLRequestHandler, RCTInvalidating>
@end

View File

@ -0,0 +1,88 @@
/**
* 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 "RCTFileRequestHandler.h"
#import <MobileCoreServices/MobileCoreServices.h>
@implementation RCTFileRequestHandler
{
NSOperationQueue *_fileQueue;
}
@synthesize methodQueue = _methodQueue;
RCT_EXPORT_MODULE()
- (void)invalidate
{
[_fileQueue cancelAllOperations];
_fileQueue = nil;
}
- (BOOL)canHandleRequest:(NSURLRequest *)request
{
return [request.URL.scheme caseInsensitiveCompare:@"file"] == NSOrderedSame;
}
- (NSOperation *)sendRequest:(NSURLRequest *)request
withDelegate:(id<RCTURLRequestDelegate>)delegate
{
// Lazy setup
if (!_fileQueue) {
_fileQueue = [NSOperationQueue new];
_fileQueue.maxConcurrentOperationCount = 4;
}
__block NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
// Get content length
NSError *error = nil;
NSFileManager *fileManager = [NSFileManager new];
NSDictionary *fileAttributes = [fileManager attributesOfItemAtPath:request.URL.path error:&error];
if (error) {
[delegate URLRequest:op didCompleteWithError:error];
return;
}
// Get mime type
NSString *fileExtension = [request.URL pathExtension];
NSString *UTI = (__bridge_transfer NSString *)UTTypeCreatePreferredIdentifierForTag(
kUTTagClassFilenameExtension, (__bridge CFStringRef)fileExtension, NULL);
NSString *contentType = (__bridge_transfer NSString *)UTTypeCopyPreferredTagWithClass(
(__bridge CFStringRef)UTI, kUTTagClassMIMEType);
// Send response
NSURLResponse *response = [[NSURLResponse alloc] initWithURL:request.URL
MIMEType:contentType
expectedContentLength:[fileAttributes[NSFileSize] ?: @-1 integerValue]
textEncodingName:nil];
[delegate URLRequest:op didReceiveResponse:response];
// Load data
NSData *data = [NSData dataWithContentsOfURL:request.URL
options:NSDataReadingMappedIfSafe
error:&error];
if (data) {
[delegate URLRequest:op didReceiveData:data];
}
[delegate URLRequest:op didCompleteWithError:error];
}];
[_fileQueue addOperation:op];
return op;
}
- (void)cancelRequest:(NSOperation *)op
{
[op cancel];
}
@end

View File

@ -50,7 +50,9 @@ RCT_EXPORT_MODULE()
static NSSet *schemes = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
schemes = [[NSSet alloc] initWithObjects:@"http", @"https", @"file", nil];
// technically, RCTHTTPRequestHandler can handle file:// as well,
// but it's less efficient than using RCTFileRequestHandler
schemes = [[NSSet alloc] initWithObjects:@"http", @"https", nil];
});
return [schemes containsObject:request.URL.scheme.lowercaseString];
}

View File

@ -9,6 +9,7 @@
/* Begin PBXBuildFile section */
1372B7371AB03E7B00659ED6 /* RCTNetInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = 1372B7361AB03E7B00659ED6 /* RCTNetInfo.m */; };
13D6D66A1B5FCF8200883BE9 /* RCTDownloadTask.m in Sources */ = {isa = PBXBuildFile; fileRef = 13D6D6691B5FCF8200883BE9 /* RCTDownloadTask.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 */; };
/* End PBXBuildFile section */
@ -30,6 +31,8 @@
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>"; };
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>"; };
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; };
@ -55,6 +58,8 @@
13D6D6691B5FCF8200883BE9 /* RCTDownloadTask.m */,
352DA0B71B17855800AA15A8 /* RCTHTTPRequestHandler.h */,
352DA0B81B17855800AA15A8 /* RCTHTTPRequestHandler.m */,
13EF800C1BCBE015003F47DD /* RCTFileRequestHandler.h */,
13EF800D1BCBE015003F47DD /* RCTFileRequestHandler.m */,
1372B7351AB03E7B00659ED6 /* RCTNetInfo.h */,
1372B7361AB03E7B00659ED6 /* RCTNetInfo.m */,
58B512061A9E6CE300147676 /* RCTNetworking.h */,
@ -130,6 +135,7 @@
buildActionMask = 2147483647;
files = (
13D6D66A1B5FCF8200883BE9 /* RCTDownloadTask.m in Sources */,
13EF800E1BCBE015003F47DD /* RCTFileRequestHandler.m in Sources */,
1372B7371AB03E7B00659ED6 /* RCTNetInfo.m in Sources */,
58B512081A9E6CE300147676 /* RCTNetworking.m in Sources */,
352DA0BA1B17855800AA15A8 /* RCTHTTPRequestHandler.m in Sources */,

View File

@ -314,16 +314,17 @@ RCT_EXPORT_MODULE()
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;
NSDictionary *headers;
NSInteger status;
if ([response isKindOfClass:[NSHTTPURLResponse class]]) { // Might be a local file request
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
headers = httpResponse.allHeaderFields ?: @{};
status = httpResponse.statusCode;
} else {
headers = response.MIMEType ? @{@"Content-Type": response.MIMEType} : @{};
status = 200;
}
NSArray *responseJSON = @[task.requestID,
@(httpResponse.statusCode ?: 200),
httpResponse.allHeaderFields ?: @{},
];
NSArray *responseJSON = @[task.requestID, @(status), headers];
[_bridge.eventDispatcher sendDeviceEventWithName:@"didReceiveNetworkResponse"
body:responseJSON];
});