Add multipart response download task
Reviewed By: mmmulani Differential Revision: D3940132 fbshipit-source-id: 7a6543223cea2523bedc585f890c9f64df0509ff
This commit is contained in:
parent
f7cbd56d8e
commit
69ec19c61e
|
@ -14,6 +14,7 @@
|
|||
#import "RCTSourceCode.h"
|
||||
#import "RCTUtils.h"
|
||||
#import "RCTPerformanceLogger.h"
|
||||
#import "RCTMultipartDataTask.h"
|
||||
|
||||
#include <sys/stat.h>
|
||||
|
||||
|
@ -151,50 +152,51 @@ static void attemptAsynchronousLoadOfBundleAtURL(NSURL *scriptURL, RCTSourceLoad
|
|||
return;
|
||||
}
|
||||
|
||||
// Load remote script file
|
||||
NSURLSessionDataTask *task =
|
||||
[[NSURLSession sharedSession] dataTaskWithURL:scriptURL completionHandler:
|
||||
^(NSData *data, NSURLResponse *response, NSError *error) {
|
||||
|
||||
// Handle general request errors
|
||||
if (error) {
|
||||
if ([error.domain isEqualToString:NSURLErrorDomain]) {
|
||||
error = [NSError errorWithDomain:RCTJavaScriptLoaderErrorDomain
|
||||
code:RCTJavaScriptLoaderErrorURLLoadFailed
|
||||
userInfo:
|
||||
@{
|
||||
NSLocalizedDescriptionKey:
|
||||
[@"Could not connect to development server.\n\n"
|
||||
"Ensure the following:\n"
|
||||
"- Node server is running and available on the same network - run 'npm start' from react-native root\n"
|
||||
"- Node server URL is correctly set in AppDelegate\n\n"
|
||||
"URL: " stringByAppendingString:scriptURL.absoluteString],
|
||||
NSLocalizedFailureReasonErrorKey: error.localizedDescription,
|
||||
NSUnderlyingErrorKey: error,
|
||||
}];
|
||||
}
|
||||
onComplete(error, nil, 0);
|
||||
return;
|
||||
}
|
||||
RCTMultipartDataTask *task = [[RCTMultipartDataTask alloc] initWithURL:scriptURL partHandler:^(NSInteger statusCode, NSDictionary *headers, NSData *data, NSError *error, BOOL done) {
|
||||
if (!done) {
|
||||
// TODO(frantic): Emit progress event
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle general request errors
|
||||
if (error) {
|
||||
if ([error.domain isEqualToString:NSURLErrorDomain]) {
|
||||
error = [NSError errorWithDomain:RCTJavaScriptLoaderErrorDomain
|
||||
code:RCTJavaScriptLoaderErrorURLLoadFailed
|
||||
userInfo:
|
||||
@{
|
||||
NSLocalizedDescriptionKey:
|
||||
[@"Could not connect to development server.\n\n"
|
||||
"Ensure the following:\n"
|
||||
"- Node server is running and available on the same network - run 'npm start' from react-native root\n"
|
||||
"- Node server URL is correctly set in AppDelegate\n\n"
|
||||
"URL: " stringByAppendingString:scriptURL.absoluteString],
|
||||
NSLocalizedFailureReasonErrorKey: error.localizedDescription,
|
||||
NSUnderlyingErrorKey: error,
|
||||
}];
|
||||
}
|
||||
onComplete(error, nil, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
// For multipart responses packager sets X-Http-Status header in case HTTP status code
|
||||
// is different from 200 OK
|
||||
NSString *statusCodeHeader = [headers valueForKey:@"X-Http-Status"];
|
||||
if (statusCodeHeader) {
|
||||
statusCode = [statusCodeHeader integerValue];
|
||||
}
|
||||
|
||||
if (statusCode != 200) {
|
||||
error = [NSError errorWithDomain:@"JSServer"
|
||||
code:statusCode
|
||||
userInfo:userInfoForRawResponse([[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding])];
|
||||
onComplete(error, nil, 0);
|
||||
return;
|
||||
}
|
||||
onComplete(nil, data, data.length);
|
||||
}];
|
||||
|
||||
// Parse response as text
|
||||
NSStringEncoding encoding = NSUTF8StringEncoding;
|
||||
if (response.textEncodingName != nil) {
|
||||
CFStringEncoding cfEncoding = CFStringConvertIANACharSetNameToEncoding((CFStringRef)response.textEncodingName);
|
||||
if (cfEncoding != kCFStringEncodingInvalidId) {
|
||||
encoding = CFStringConvertEncodingToNSStringEncoding(cfEncoding);
|
||||
}
|
||||
}
|
||||
// Handle HTTP errors
|
||||
if ([response isKindOfClass:[NSHTTPURLResponse class]] && ((NSHTTPURLResponse *)response).statusCode != 200) {
|
||||
error = [NSError errorWithDomain:@"JSServer"
|
||||
code:((NSHTTPURLResponse *)response).statusCode
|
||||
userInfo:userInfoForRawResponse([[NSString alloc] initWithData:data encoding:encoding])];
|
||||
onComplete(error, nil, 0);
|
||||
return;
|
||||
}
|
||||
onComplete(nil, data, data.length);
|
||||
}];
|
||||
[task resume];
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
/**
|
||||
* 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 "RCTMultipartStreamReader.h"
|
||||
|
||||
typedef void (^RCTMultipartDataTaskCallback)(NSInteger statusCode, NSDictionary *headers, NSData *content, NSError *error, BOOL done);
|
||||
|
||||
@interface RCTMultipartDataTask : NSObject
|
||||
|
||||
- (instancetype)initWithURL:(NSURL *)url partHandler:(RCTMultipartDataTaskCallback)partHandler;
|
||||
- (void)resume;
|
||||
|
||||
@end
|
|
@ -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 "RCTMultipartDataTask.h"
|
||||
|
||||
@interface RCTMultipartDataTask () <NSURLSessionDataDelegate, NSURLSessionDataDelegate>
|
||||
|
||||
@end
|
||||
|
||||
@implementation RCTMultipartDataTask {
|
||||
NSURLSessionDataTask *_dataTask;
|
||||
RCTMultipartDataTaskCallback _partHandler;
|
||||
NSInteger _statusCode;
|
||||
NSDictionary *_headers;
|
||||
NSString *_boundary;
|
||||
NSMutableData *_data;
|
||||
}
|
||||
|
||||
- (instancetype)initWithURL:(NSURL *)url partHandler:(RCTMultipartDataTaskCallback)partHandler
|
||||
{
|
||||
if (self = [super init]) {
|
||||
NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]
|
||||
delegate:self delegateQueue:nil];
|
||||
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
|
||||
[request addValue:@"multipart/mixed" forHTTPHeaderField:@"Accept"];
|
||||
_dataTask = [session dataTaskWithRequest:request];
|
||||
_partHandler = [partHandler copy];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)resume
|
||||
{
|
||||
[_dataTask resume];
|
||||
}
|
||||
|
||||
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler
|
||||
{
|
||||
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
|
||||
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
|
||||
_headers = [httpResponse allHeaderFields];
|
||||
_statusCode = [httpResponse statusCode];
|
||||
|
||||
NSString *contentType = @"";
|
||||
for (NSString *key in [_headers keyEnumerator]) {
|
||||
if ([[key lowercaseString] isEqualToString:@"content-type"]) {
|
||||
contentType = [_headers valueForKey:key];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"multipart/mixed;.*boundary=\"([^\"]+)\"" options:0 error:nil];
|
||||
NSTextCheckingResult *match = [regex firstMatchInString:contentType options:0 range:NSMakeRange(0, contentType.length)];
|
||||
if (match) {
|
||||
_boundary = [contentType substringWithRange:[match rangeAtIndex:1]];
|
||||
completionHandler(NSURLSessionResponseBecomeStream);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// In case the server doesn't support multipart/mixed responses, fallback to normal download
|
||||
_data = [[NSMutableData alloc] initWithCapacity:1024 * 1024];
|
||||
completionHandler(NSURLSessionResponseAllow);
|
||||
}
|
||||
|
||||
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
|
||||
{
|
||||
_partHandler(_statusCode, _headers, _data, error, YES);
|
||||
}
|
||||
|
||||
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data
|
||||
{
|
||||
[_data appendData:data];
|
||||
}
|
||||
|
||||
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didBecomeStreamTask:(NSURLSessionStreamTask *)streamTask
|
||||
{
|
||||
[streamTask captureStreams];
|
||||
}
|
||||
|
||||
- (void)URLSession:(NSURLSession *)session streamTask:(NSURLSessionStreamTask *)streamTask didBecomeInputStream:(NSInputStream *)inputStream outputStream:(NSOutputStream *)outputStream
|
||||
{
|
||||
RCTMultipartStreamReader *reader = [[RCTMultipartStreamReader alloc] initWithInputStream:inputStream boundary:_boundary];
|
||||
RCTMultipartDataTaskCallback partHandler = _partHandler;
|
||||
NSInteger statusCode = _statusCode;
|
||||
|
||||
BOOL completed = [reader readAllParts:^(NSDictionary *headers, NSData *content, BOOL done) {
|
||||
partHandler(statusCode, headers, content, nil, done);
|
||||
}];
|
||||
if (!completed) {
|
||||
partHandler(statusCode, nil, nil, [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorCancelled userInfo:nil], YES);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@end
|
|
@ -9,6 +9,7 @@
|
|||
/* Begin PBXBuildFile section */
|
||||
000E6CEB1AB0E980000CDF4D /* RCTSourceCode.m in Sources */ = {isa = PBXBuildFile; fileRef = 000E6CEA1AB0E980000CDF4D /* RCTSourceCode.m */; };
|
||||
001BFCD01D8381DE008E587E /* RCTMultipartStreamReader.m in Sources */ = {isa = PBXBuildFile; fileRef = 001BFCCF1D8381DE008E587E /* RCTMultipartStreamReader.m */; };
|
||||
006FC4141D9B20820057AAAD /* RCTMultipartDataTask.m in Sources */ = {isa = PBXBuildFile; fileRef = 006FC4131D9B20820057AAAD /* RCTMultipartDataTask.m */; };
|
||||
008341F61D1DB34400876D9A /* RCTJSStackFrame.m in Sources */ = {isa = PBXBuildFile; fileRef = 008341F41D1DB34400876D9A /* RCTJSStackFrame.m */; };
|
||||
131B6AF41AF1093D00FFC3E0 /* RCTSegmentedControl.m in Sources */ = {isa = PBXBuildFile; fileRef = 131B6AF11AF1093D00FFC3E0 /* RCTSegmentedControl.m */; };
|
||||
131B6AF51AF1093D00FFC3E0 /* RCTSegmentedControlManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 131B6AF31AF1093D00FFC3E0 /* RCTSegmentedControlManager.m */; };
|
||||
|
@ -125,6 +126,8 @@
|
|||
000E6CEA1AB0E980000CDF4D /* RCTSourceCode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTSourceCode.m; sourceTree = "<group>"; };
|
||||
001BFCCE1D8381DE008E587E /* RCTMultipartStreamReader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTMultipartStreamReader.h; sourceTree = "<group>"; };
|
||||
001BFCCF1D8381DE008E587E /* RCTMultipartStreamReader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTMultipartStreamReader.m; sourceTree = "<group>"; };
|
||||
006FC4121D9B20820057AAAD /* RCTMultipartDataTask.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTMultipartDataTask.h; sourceTree = "<group>"; };
|
||||
006FC4131D9B20820057AAAD /* RCTMultipartDataTask.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTMultipartDataTask.m; sourceTree = "<group>"; };
|
||||
008341F41D1DB34400876D9A /* RCTJSStackFrame.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTJSStackFrame.m; sourceTree = "<group>"; };
|
||||
008341F51D1DB34400876D9A /* RCTJSStackFrame.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTJSStackFrame.h; sourceTree = "<group>"; };
|
||||
131541CF1D3E4893006A0E08 /* CSSLayout-internal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "CSSLayout-internal.h"; sourceTree = "<group>"; };
|
||||
|
@ -617,6 +620,8 @@
|
|||
14C2CA731B3AC64300E6CBB2 /* RCTModuleData.mm */,
|
||||
14C2CA6F1B3AC63800E6CBB2 /* RCTModuleMethod.h */,
|
||||
14C2CA701B3AC63800E6CBB2 /* RCTModuleMethod.m */,
|
||||
006FC4121D9B20820057AAAD /* RCTMultipartDataTask.h */,
|
||||
006FC4131D9B20820057AAAD /* RCTMultipartDataTask.m */,
|
||||
001BFCCE1D8381DE008E587E /* RCTMultipartStreamReader.h */,
|
||||
001BFCCF1D8381DE008E587E /* RCTMultipartStreamReader.m */,
|
||||
13A6E20F1C19ABC700845B82 /* RCTNullability.h */,
|
||||
|
@ -749,6 +754,7 @@
|
|||
13A0C28A1B74F71200B29F6F /* RCTDevMenu.m in Sources */,
|
||||
13BCE8091C99CB9D00DD7AAD /* RCTRootShadowView.m in Sources */,
|
||||
14C2CA711B3AC63800E6CBB2 /* RCTModuleMethod.m in Sources */,
|
||||
006FC4141D9B20820057AAAD /* RCTMultipartDataTask.m in Sources */,
|
||||
1321C8D01D3EB50800D58318 /* CSSNodeList.c in Sources */,
|
||||
13CC8A821B17642100940AE7 /* RCTBorderDrawing.m in Sources */,
|
||||
83CBBA511A601E3B00E9B192 /* RCTAssert.m in Sources */,
|
||||
|
|
Loading…
Reference in New Issue