[ReactNative] Refactor RCTDataManager to support pluggable data source modules (RCTURLRequestHandlers)
Summary: @public This is a refactor of @philikon's original diff that decouples the dependencies between the Network and Image modules, and replaces RCTDataQueryExecutor with a more useful abstraction. I've introduced the RCTURLRequestHandler protocol, which is a new type of bridge module used for loading data using an NSURLRequest. RCTURLRequestHandlers can be registered using RCT_EXPORT_MODULE() and are then available at runtime for use by the RCTDataManager, which will automatically select the appropriate handler for a given request based on the handler's self-reported capabilities. The currently implemented handlers are: - RCTHTTPRequestHandler - the standard open source HTTP request handler that uses NSURLSession - RKHTTPRequestHandler - the internal FB HTTP request handler that uses FBNetworking - RCTImageRequestHandler - a handler for loading local images from the iOS asset-library Depends on D2108193 Test Plan: - Internal apps still work - OSS port still compiles, Movies app and a sample Parse app still work - uploading image to Parse using the above code snippet works - tested `FormData` with string and image parameters using http://www.posttestserver.com/
This commit is contained in:
parent
f4bf80f3ea
commit
f88bc3eb73
|
@ -10,6 +10,7 @@
|
|||
1304D5AB1AA8C4A30002E2BE /* RCTStaticImage.m in Sources */ = {isa = PBXBuildFile; fileRef = 1304D5A81AA8C4A30002E2BE /* RCTStaticImage.m */; };
|
||||
1304D5AC1AA8C4A30002E2BE /* RCTStaticImageManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 1304D5AA1AA8C4A30002E2BE /* RCTStaticImageManager.m */; };
|
||||
1304D5B21AA8C50D0002E2BE /* RCTGIFImage.m in Sources */ = {isa = PBXBuildFile; fileRef = 1304D5B11AA8C50D0002E2BE /* RCTGIFImage.m */; };
|
||||
1345A8391B26592900583190 /* RCTImageRequestHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 1345A8381B26592900583190 /* RCTImageRequestHandler.m */; };
|
||||
143879351AAD238D00F088A5 /* RCTCameraRollManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 143879341AAD238D00F088A5 /* RCTCameraRollManager.m */; };
|
||||
143879381AAD32A300F088A5 /* RCTImageLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = 143879371AAD32A300F088A5 /* RCTImageLoader.m */; };
|
||||
58B5118F1A9E6BD600147676 /* RCTImageDownloader.m in Sources */ = {isa = PBXBuildFile; fileRef = 58B5118A1A9E6BD600147676 /* RCTImageDownloader.m */; };
|
||||
|
@ -36,6 +37,8 @@
|
|||
1304D5AA1AA8C4A30002E2BE /* RCTStaticImageManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTStaticImageManager.m; sourceTree = "<group>"; };
|
||||
1304D5B01AA8C50D0002E2BE /* RCTGIFImage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTGIFImage.h; sourceTree = "<group>"; };
|
||||
1304D5B11AA8C50D0002E2BE /* RCTGIFImage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTGIFImage.m; sourceTree = "<group>"; };
|
||||
1345A8371B26592900583190 /* RCTImageRequestHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTImageRequestHandler.h; sourceTree = "<group>"; };
|
||||
1345A8381B26592900583190 /* RCTImageRequestHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTImageRequestHandler.m; sourceTree = "<group>"; };
|
||||
143879331AAD238D00F088A5 /* RCTCameraRollManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTCameraRollManager.h; sourceTree = "<group>"; };
|
||||
143879341AAD238D00F088A5 /* RCTCameraRollManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTCameraRollManager.m; sourceTree = "<group>"; };
|
||||
143879361AAD32A300F088A5 /* RCTImageLoader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTImageLoader.h; sourceTree = "<group>"; };
|
||||
|
@ -71,6 +74,8 @@
|
|||
1304D5B11AA8C50D0002E2BE /* RCTGIFImage.m */,
|
||||
58B511891A9E6BD600147676 /* RCTImageDownloader.h */,
|
||||
58B5118A1A9E6BD600147676 /* RCTImageDownloader.m */,
|
||||
1345A8371B26592900583190 /* RCTImageRequestHandler.h */,
|
||||
1345A8381B26592900583190 /* RCTImageRequestHandler.m */,
|
||||
58B5118B1A9E6BD600147676 /* RCTNetworkImageView.h */,
|
||||
58B5118C1A9E6BD600147676 /* RCTNetworkImageView.m */,
|
||||
58B5118D1A9E6BD600147676 /* RCTNetworkImageViewManager.h */,
|
||||
|
@ -152,6 +157,7 @@
|
|||
58B5118F1A9E6BD600147676 /* RCTImageDownloader.m in Sources */,
|
||||
58B511911A9E6BD600147676 /* RCTNetworkImageViewManager.m in Sources */,
|
||||
1304D5AC1AA8C4A30002E2BE /* RCTStaticImageManager.m in Sources */,
|
||||
1345A8391B26592900583190 /* RCTImageRequestHandler.m in Sources */,
|
||||
58B511901A9E6BD600147676 /* RCTNetworkImageView.m in Sources */,
|
||||
1304D5B21AA8C50D0002E2BE /* RCTGIFImage.m in Sources */,
|
||||
143879351AAD238D00F088A5 /* RCTCameraRollManager.m in Sources */,
|
||||
|
|
|
@ -23,6 +23,4 @@
|
|||
+ (void)loadImageWithTag:(NSString *)tag
|
||||
callback:(void (^)(NSError *error, id /* UIImage or CAAnimation */ image))callback;
|
||||
|
||||
+ (BOOL)isSystemImageURI:(NSString *)uri;
|
||||
|
||||
@end
|
||||
|
|
|
@ -16,11 +16,23 @@
|
|||
#import <UIKit/UIKit.h>
|
||||
|
||||
#import "RCTConvert.h"
|
||||
#import "RCTDefines.h"
|
||||
#import "RCTGIFImage.h"
|
||||
#import "RCTImageDownloader.h"
|
||||
#import "RCTLog.h"
|
||||
#import "RCTUtils.h"
|
||||
|
||||
static void RCTDispatchCallbackOnMainQueue(void (^ __nonnull callback)(NSError *, id), NSError *error, UIImage *image)
|
||||
{
|
||||
if ([NSThread isMainThread]) {
|
||||
callback(error, image);
|
||||
} else {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
callback(error, image);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
static dispatch_queue_t RCTImageLoaderQueue(void)
|
||||
{
|
||||
static dispatch_queue_t queue = NULL;
|
||||
|
@ -137,11 +149,4 @@ static dispatch_queue_t RCTImageLoaderQueue(void)
|
|||
}
|
||||
}
|
||||
|
||||
+ (BOOL)isSystemImageURI:(NSString *)uri
|
||||
{
|
||||
return uri != nil && (
|
||||
[uri hasPrefix:@"assets-library"] ||
|
||||
[uri hasPrefix:@"ph://"]);
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
@ -7,15 +7,8 @@
|
|||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import "RCTBridgeModule.h"
|
||||
#import "RCTURLRequestHandler.h"
|
||||
|
||||
@protocol RCTDataQueryExecutor <NSObject>
|
||||
|
||||
- (void)addQuery:(NSDictionary *)query responseSender:(RCTResponseSenderBlock)responseSender;
|
||||
|
||||
@optional
|
||||
|
||||
@property (nonatomic, weak) RCTBridge *bridge;
|
||||
@property (nonatomic, assign) BOOL sendIncrementalUpdates;
|
||||
@interface RCTImageRequestHandler : NSObject <RCTURLRequestHandler>
|
||||
|
||||
@end
|
|
@ -0,0 +1,62 @@
|
|||
//
|
||||
// RCTImageRequestHandler.m
|
||||
// RCTImage
|
||||
//
|
||||
// Created by Nick Lockwood on 09/06/2015.
|
||||
// Copyright (c) 2015 Facebook. All rights reserved.
|
||||
//
|
||||
|
||||
#import "RCTImageRequestHandler.h"
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#import "RCTImageLoader.h"
|
||||
#import "RCTUtils.h"
|
||||
|
||||
@implementation RCTImageRequestHandler
|
||||
{
|
||||
NSInteger _currentToken;
|
||||
}
|
||||
|
||||
RCT_EXPORT_MODULE()
|
||||
|
||||
- (BOOL)canHandleRequest:(NSURLRequest *)request
|
||||
{
|
||||
return [@[@"assets-library", @"ph"] containsObject:[request.URL.scheme lowercaseString]];
|
||||
}
|
||||
|
||||
- (id)sendRequest:(NSURLRequest *)request
|
||||
withDelegate:(id<RCTURLRequestDelegate>)delegate
|
||||
{
|
||||
NSNumber *requestToken = @(++_currentToken);
|
||||
NSString *URLString = [request.URL absoluteString];
|
||||
[RCTImageLoader loadImageWithTag:URLString callback:^(NSError *error, UIImage *image) {
|
||||
if (error) {
|
||||
[delegate URLRequest:requestToken didCompleteWithError:error];
|
||||
return;
|
||||
}
|
||||
|
||||
NSString *mimeType = nil;
|
||||
NSData *imageData = nil;
|
||||
if (RCTImageHasAlpha(image.CGImage)) {
|
||||
mimeType = @"image/png";
|
||||
imageData = UIImagePNGRepresentation(image);
|
||||
} else {
|
||||
mimeType = @"image/jpeg";
|
||||
imageData = UIImageJPEGRepresentation(image, 1.0);
|
||||
}
|
||||
|
||||
NSURLResponse *response = [[NSURLResponse alloc] initWithURL:request.URL
|
||||
MIMEType:mimeType
|
||||
expectedContentLength:imageData.length
|
||||
textEncodingName:nil];
|
||||
|
||||
[delegate URLRequest:requestToken didReceiveResponse:response];
|
||||
[delegate URLRequest:requestToken didReceiveData:imageData];
|
||||
[delegate URLRequest:requestToken didCompleteWithError:nil];
|
||||
}];
|
||||
|
||||
return requestToken;
|
||||
}
|
||||
|
||||
@end
|
|
@ -11,44 +11,452 @@
|
|||
|
||||
#import "RCTAssert.h"
|
||||
#import "RCTConvert.h"
|
||||
#import "RCTDataQuery.h"
|
||||
#import "RCTURLRequestHandler.h"
|
||||
#import "RCTEventDispatcher.h"
|
||||
#import "RCTHTTPQueryExecutor.h"
|
||||
#import "RCTHTTPRequestHandler.h"
|
||||
#import "RCTLog.h"
|
||||
#import "RCTUtils.h"
|
||||
|
||||
typedef void (^RCTHTTPQueryResult)(NSError *error, NSDictionary *result);
|
||||
|
||||
@interface RCTDataManager ()<RCTURLRequestDelegate>
|
||||
|
||||
- (void)processDataForHTTPQuery:(NSDictionary *)data callback:(void (^)(NSError *error, NSDictionary *result))callback;
|
||||
|
||||
@end
|
||||
|
||||
/**
|
||||
* Helper to convert FormData payloads into multipart/formdata requests.
|
||||
*/
|
||||
@interface RCTHTTPFormDataHelper : NSObject
|
||||
|
||||
@property (nonatomic, weak) RCTDataManager *dataManager;
|
||||
|
||||
@end
|
||||
|
||||
@implementation RCTHTTPFormDataHelper
|
||||
{
|
||||
NSMutableArray *parts;
|
||||
NSMutableData *multipartBody;
|
||||
RCTHTTPQueryResult _callback;
|
||||
NSString *boundary;
|
||||
}
|
||||
|
||||
- (void)process:(NSArray *)formData callback:(nonnull void (^)(NSError *error, NSDictionary *result))callback
|
||||
{
|
||||
if (![formData count]) {
|
||||
callback(nil, nil);
|
||||
return;
|
||||
}
|
||||
parts = [formData mutableCopy];
|
||||
_callback = callback;
|
||||
multipartBody = [[NSMutableData alloc] init];
|
||||
boundary = [self generateBoundary];
|
||||
|
||||
NSDictionary *currentPart = [parts objectAtIndex: 0];
|
||||
[_dataManager processDataForHTTPQuery:currentPart callback:^(NSError *e, NSDictionary *r) {
|
||||
[self handleResult:r error:e];
|
||||
}];
|
||||
}
|
||||
|
||||
- (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
|
||||
{
|
||||
if (error) {
|
||||
_callback(error, nil);
|
||||
return;
|
||||
}
|
||||
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];
|
||||
NSString *partContentType = result[@"contentType"];
|
||||
if (partContentType != nil) {
|
||||
[headers setObject:partContentType forKey:@"content-type"];
|
||||
}
|
||||
[headers enumerateKeysAndObjectsUsingBlock:^(NSString *parameterKey, NSString *parameterValue, BOOL *stop) {
|
||||
[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]];
|
||||
|
||||
if ([parts count]) {
|
||||
NSDictionary *nextPart = [parts objectAtIndex: 0];
|
||||
[_dataManager processDataForHTTPQuery:nextPart callback:^(NSError *e, NSDictionary *r) {
|
||||
[self handleResult:r error:e];
|
||||
}];
|
||||
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
|
||||
|
||||
- (void)setResponse:(NSURLResponse *)response;
|
||||
{
|
||||
_response = response;
|
||||
if (!_incrementalUpdates) {
|
||||
_data = [[NSMutableData alloc] initWithCapacity:(NSUInteger)MAX(0, response.expectedContentLength)];
|
||||
}
|
||||
}
|
||||
|
||||
@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
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
- (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);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
/**
|
||||
* Bridge module that provides the JS interface to the network stack.
|
||||
*/
|
||||
@implementation RCTDataManager
|
||||
{
|
||||
NSInteger _currentRequestID;
|
||||
NSMapTable *_activeRequests;
|
||||
}
|
||||
|
||||
@synthesize bridge = _bridge;
|
||||
|
||||
RCT_EXPORT_MODULE()
|
||||
|
||||
/**
|
||||
* Executes a network request.
|
||||
* The responseSender block won't be called on same thread as called.
|
||||
*/
|
||||
RCT_EXPORT_METHOD(queryData:(NSString *)queryType
|
||||
withQuery:(NSDictionary *)query
|
||||
sendIncrementalUpdates:(BOOL)incrementalUpdates
|
||||
responseSender:(RCTResponseSenderBlock)responseSender)
|
||||
- (instancetype)init
|
||||
{
|
||||
id<RCTDataQueryExecutor> executor = nil;
|
||||
if ([queryType isEqualToString:@"http"]) {
|
||||
executor = [RCTHTTPQueryExecutor sharedInstance];
|
||||
} else {
|
||||
RCTLogError(@"unsupported query type %@", queryType);
|
||||
if ((self = [super init])) {
|
||||
_currentRequestID = 0;
|
||||
_activeRequests = [[NSMapTable alloc] initWithKeyOptions:NSPointerFunctionsStrongMemory
|
||||
valueOptions:NSPointerFunctionsStrongMemory
|
||||
capacity:0];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)buildRequest:(NSDictionary *)query
|
||||
responseSender:(RCTResponseSenderBlock)responseSender
|
||||
{
|
||||
NSURL *URL = [RCTConvert NSURL:query[@"url"]];
|
||||
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:URL];
|
||||
request.HTTPMethod = [[RCTConvert NSString:query[@"method"]] uppercaseString] ?: @"GET";
|
||||
request.allHTTPHeaderFields = [RCTConvert NSDictionary:query[@"headers"]];
|
||||
|
||||
BOOL incrementalUpdates = [RCTConvert BOOL:query[@"incrementalUpdates"]];
|
||||
|
||||
NSDictionary *data = [RCTConvert NSDictionary:query[@"data"]];
|
||||
[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;
|
||||
}
|
||||
request.HTTPBody = result[@"body"];
|
||||
NSString *contentType = result[@"contentType"];
|
||||
if (contentType) {
|
||||
[request setValue:contentType forHTTPHeaderField:@"content-type"];
|
||||
}
|
||||
[self sendRequest:request
|
||||
incrementalUpdates:incrementalUpdates
|
||||
responseSender:responseSender];
|
||||
}];
|
||||
}
|
||||
|
||||
- (id<RCTURLRequestHandler>)handlerForRequest:(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);
|
||||
}
|
||||
return handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the 'data' part of an HTTP query.
|
||||
*
|
||||
* 'data' can be a JSON value of the following forms:
|
||||
*
|
||||
* - {"string": "..."}: a simple JS string that will be UTF-8 encoded and sent as the body
|
||||
*
|
||||
* - {"uri": "some-uri://..."}: reference to a system resource, e.g. an image in the asset library
|
||||
*
|
||||
* - {"formData": [...]}: list of data payloads that will be combined into a multipart/form-data request
|
||||
*
|
||||
* If successful, the callback be called with a result dictionary containing the following (optional) keys:
|
||||
*
|
||||
* - @"body" (NSData): the body of the request
|
||||
*
|
||||
* - @"contentType" (NSString): the content type header of the request
|
||||
*
|
||||
*/
|
||||
- (void)processDataForHTTPQuery:(NSDictionary *)query callback:(nonnull void (^)(NSError *error, NSDictionary *result))callback
|
||||
{
|
||||
if (!query) {
|
||||
callback(nil, nil);
|
||||
return;
|
||||
}
|
||||
NSData *body = [RCTConvert NSData:query[@"string"]];
|
||||
if (body) {
|
||||
callback(nil, @{@"body": body});
|
||||
return;
|
||||
}
|
||||
NSURLRequest *request = [RCTConvert NSURLRequest:query[@"uri"]];
|
||||
if (request) {
|
||||
id<RCTURLRequestHandler> handler = [self handlerForRequest:request];
|
||||
(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);
|
||||
}
|
||||
}];
|
||||
return;
|
||||
}
|
||||
NSDictionaryArray *formData = [RCTConvert NSDictionaryArray:query[@"formData"]];
|
||||
if (formData != nil) {
|
||||
RCTHTTPFormDataHelper *formDataHelper = [[RCTHTTPFormDataHelper alloc] init];
|
||||
formDataHelper.dataManager = self;
|
||||
[formDataHelper process:formData callback:callback];
|
||||
return;
|
||||
}
|
||||
// Nothing in the data payload, at least nothing we could understand anyway.
|
||||
// Ignore and treat it as if it were null.
|
||||
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
|
||||
{
|
||||
if (data.length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
RCTAssert(executor != nil, @"executor must be defined");
|
||||
RCTActiveURLRequest *request = [_activeRequests objectForKey:requestToken];
|
||||
|
||||
if ([executor respondsToSelector:@selector(setBridge:)]) {
|
||||
executor.bridge = _bridge;
|
||||
// Get text encoding
|
||||
NSURLResponse *response = request.response;
|
||||
NSStringEncoding encoding = NSUTF8StringEncoding;
|
||||
if (response.textEncodingName) {
|
||||
CFStringEncoding cfEncoding = CFStringConvertIANACharSetNameToEncoding((CFStringRef)response.textEncodingName);
|
||||
encoding = CFStringConvertEncodingToNSStringEncoding(cfEncoding);
|
||||
}
|
||||
if ([executor respondsToSelector:@selector(setSendIncrementalUpdates:)]) {
|
||||
executor.sendIncrementalUpdates = incrementalUpdates;
|
||||
|
||||
NSString *responseText = [[NSString alloc] initWithData:data encoding:encoding];
|
||||
if (!responseText && data.length) {
|
||||
RCTLogError(@"Received data was invalid.");
|
||||
return;
|
||||
}
|
||||
|
||||
NSArray *responseJSON = @[request.requestID, responseText ?: @""];
|
||||
[_bridge.eventDispatcher sendDeviceEventWithName:@"didReceiveNetworkData"
|
||||
body:responseJSON];
|
||||
}
|
||||
|
||||
#pragma mark - RCTURLRequestDelegate
|
||||
|
||||
- (void)URLRequest:(id)requestToken didReceiveResponse:(NSURLResponse *)response
|
||||
{
|
||||
RCTActiveURLRequest *request = [_activeRequests objectForKey:requestToken];
|
||||
RCTAssert(request != nil, @"Unrecognized request token: %@", requestToken);
|
||||
|
||||
request.response = response;
|
||||
|
||||
NSHTTPURLResponse *httpResponse = nil;
|
||||
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
|
||||
// Might be a local file request
|
||||
httpResponse = (NSHTTPURLResponse *)response;
|
||||
}
|
||||
|
||||
NSArray *responseJSON = @[request.requestID,
|
||||
@(httpResponse.statusCode ?: 200),
|
||||
httpResponse.allHeaderFields ?: @{},
|
||||
];
|
||||
|
||||
[_bridge.eventDispatcher sendDeviceEventWithName:@"didReceiveNetworkResponse"
|
||||
body:responseJSON];
|
||||
}
|
||||
|
||||
- (void)URLRequest:(id)requestToken didReceiveData:(NSData *)data
|
||||
{
|
||||
RCTActiveURLRequest *request = [_activeRequests objectForKey:requestToken];
|
||||
RCTAssert(request != nil, @"Unrecognized request token: %@", requestToken);
|
||||
|
||||
if (request.incrementalUpdates) {
|
||||
[self sendData:data forRequestToken:requestToken];
|
||||
} else {
|
||||
[request.data appendData:data];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)URLRequest:(id)requestToken didCompleteWithError:(NSError *)error
|
||||
{
|
||||
RCTActiveURLRequest *request = [_activeRequests objectForKey:requestToken];
|
||||
RCTAssert(request != nil, @"Unrecognized request token: %@", requestToken);
|
||||
|
||||
if (!request.incrementalUpdates) {
|
||||
[self sendData:request.data forRequestToken:requestToken];
|
||||
}
|
||||
|
||||
NSArray *responseJSON = @[request.requestID,
|
||||
error.localizedDescription ?: [NSNull null]
|
||||
];
|
||||
|
||||
[_bridge.eventDispatcher sendDeviceEventWithName:@"didCompleteNetworkResponse"
|
||||
body:responseJSON];
|
||||
|
||||
[_activeRequests removeObjectForKey:requestToken];
|
||||
}
|
||||
|
||||
#pragma mark - JS API
|
||||
|
||||
RCT_EXPORT_METHOD(sendRequest:(NSDictionary *)query
|
||||
responseSender:(RCTResponseSenderBlock)responseSender)
|
||||
{
|
||||
[self buildRequest:query 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];
|
||||
}
|
||||
[executor addQuery:query responseSender:responseSender];
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
@ -1,39 +0,0 @@
|
|||
/**
|
||||
* 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 "RCTDataQuery.h"
|
||||
|
||||
@interface RCTHTTPQueryExecutor : NSObject <RCTDataQueryExecutor>
|
||||
|
||||
+ (instancetype)sharedInstance;
|
||||
|
||||
/**
|
||||
* Process the 'data' part of an HTTP query.
|
||||
*
|
||||
* 'data' can be a JSON value of the following forms:
|
||||
*
|
||||
* - {"string": "..."}: a simple JS string that will be UTF-8 encoded and sent as the body
|
||||
*
|
||||
* - {"uri": "some-uri://..."}: reference to a system resource, e.g. an image in the asset library
|
||||
*
|
||||
* - {"formData": [...]}: list of data payloads that will be combined into a multipart/form-data request
|
||||
*
|
||||
* If successful, the callback be called with a result dictionary containing the following (optional) keys:
|
||||
*
|
||||
* - @"body" (NSData): the body of the request
|
||||
*
|
||||
* - @"contentType" (NSString): the content type header of the request
|
||||
*
|
||||
*/
|
||||
+ (void)processDataForHTTPQuery:(NSDictionary *)data
|
||||
callback:(void (^)(NSError *error, NSDictionary *result))callback;
|
||||
|
||||
@end
|
|
@ -1,314 +0,0 @@
|
|||
/**
|
||||
* 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 "RCTHTTPQueryExecutor.h"
|
||||
|
||||
#import "RCTAssert.h"
|
||||
#import "RCTConvert.h"
|
||||
#import "RCTEventDispatcher.h"
|
||||
#import "RCTImageLoader.h"
|
||||
#import "RCTLog.h"
|
||||
#import "RCTUtils.h"
|
||||
|
||||
/**
|
||||
* Helper to convert FormData payloads into multipart/formdata requests.
|
||||
*/
|
||||
@interface RCTHTTPFormDataHelper : NSObject
|
||||
|
||||
- (void)process:(NSArray *)formData callback:(void (^)(NSError *error, NSDictionary *result))callback;
|
||||
|
||||
@end
|
||||
|
||||
@implementation RCTHTTPFormDataHelper
|
||||
{
|
||||
NSMutableArray *parts;
|
||||
NSMutableData *multipartBody;
|
||||
RCTResultOrErrorBlock callback;
|
||||
NSString *boundary;
|
||||
}
|
||||
|
||||
- (void)process:(NSArray *)formData callback:(void (^)(NSError *error, NSDictionary *result))cb
|
||||
{
|
||||
if (![formData count]) {
|
||||
RCTDispatchCallbackOnMainQueue(cb, nil, nil);
|
||||
return;
|
||||
}
|
||||
parts = [formData mutableCopy];
|
||||
callback = cb;
|
||||
multipartBody = [[NSMutableData alloc] init];
|
||||
boundary = [self generateBoundary];
|
||||
|
||||
NSDictionary *currentPart = [parts objectAtIndex: 0];
|
||||
[RCTHTTPQueryExecutor processDataForHTTPQuery:currentPart callback:^(NSError *e, NSDictionary *r) {
|
||||
[self handleResult:r error:e];
|
||||
}];
|
||||
}
|
||||
|
||||
- (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
|
||||
{
|
||||
if (error) {
|
||||
RCTDispatchCallbackOnMainQueue(callback, error, nil);
|
||||
return;
|
||||
}
|
||||
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];
|
||||
NSString *partContentType = result[@"contentType"];
|
||||
if (partContentType != nil) {
|
||||
[headers setObject:partContentType forKey:@"content-type"];
|
||||
}
|
||||
[headers enumerateKeysAndObjectsUsingBlock:^(NSString *parameterKey, NSString *parameterValue, BOOL *stop) {
|
||||
[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]];
|
||||
|
||||
if ([parts count]) {
|
||||
NSDictionary *nextPart = [parts objectAtIndex: 0];
|
||||
[RCTHTTPQueryExecutor processDataForHTTPQuery:nextPart callback:^(NSError *e, NSDictionary *r) {
|
||||
[self handleResult:r error:e];
|
||||
}];
|
||||
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
|
||||
|
||||
@interface RCTHTTPQueryExecutor () <NSURLSessionDataDelegate>
|
||||
|
||||
@end
|
||||
|
||||
@implementation RCTHTTPQueryExecutor
|
||||
{
|
||||
NSURLSession *_session;
|
||||
NSOperationQueue *_callbackQueue;
|
||||
}
|
||||
|
||||
@synthesize bridge = _bridge;
|
||||
@synthesize sendIncrementalUpdates = _sendIncrementalUpdates;
|
||||
|
||||
+ (instancetype)sharedInstance
|
||||
{
|
||||
static RCTHTTPQueryExecutor *_sharedInstance = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
_sharedInstance = [[RCTHTTPQueryExecutor alloc] init];
|
||||
});
|
||||
return _sharedInstance;
|
||||
}
|
||||
|
||||
- (void)addQuery:(NSDictionary *)query responseSender:(RCTResponseSenderBlock)responseSender
|
||||
{
|
||||
[self makeRequest:query responseSender:responseSender];
|
||||
}
|
||||
|
||||
- (void)makeRequest:(NSDictionary *)query responseSender:(RCTResponseSenderBlock)responseSender
|
||||
{
|
||||
// Build request
|
||||
NSURL *URL = [RCTConvert NSURL:query[@"url"]];
|
||||
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:URL];
|
||||
request.HTTPMethod = [RCTConvert NSString:query[@"method"]] ?: @"GET";
|
||||
request.allHTTPHeaderFields = [RCTConvert NSDictionary:query[@"headers"]];
|
||||
|
||||
NSDictionary *data = [RCTConvert NSDictionary:query[@"data"]];
|
||||
|
||||
[[self class] processDataForHTTPQuery:data callback:^(NSError *error, NSDictionary *result) {
|
||||
if (error != nil) {
|
||||
RCTLogError(@"Error processing request body: %@", error);
|
||||
// Ideally we'd circle back to JS here and notify an error/abort on the request.
|
||||
return;
|
||||
}
|
||||
request.HTTPBody = result[@"body"];
|
||||
NSString *contentType = result[@"contentType"];
|
||||
if (contentType != nil) {
|
||||
[request setValue:contentType forHTTPHeaderField:@"content-type"];
|
||||
}
|
||||
[self sendRequest:request responseSender:responseSender];
|
||||
}];
|
||||
}
|
||||
|
||||
+ (void)processURIDataForHTTPQuery:(NSString *)uri callback:(void (^)(NSError *error, NSDictionary *result))callback
|
||||
{
|
||||
if ([RCTImageLoader isSystemImageURI:uri]) {
|
||||
[RCTImageLoader loadImageWithTag:(NSString *)uri callback:^(NSError *error, UIImage *image) {
|
||||
if (error) {
|
||||
RCTDispatchCallbackOnMainQueue(callback, error, nil);
|
||||
return;
|
||||
}
|
||||
NSData *imageData = UIImageJPEGRepresentation(image, 1.0);
|
||||
RCTDispatchCallbackOnMainQueue(callback, nil, @{@"body": imageData, @"contentType": @"image/jpeg"});
|
||||
}];
|
||||
return;
|
||||
}
|
||||
NSString *errorText = [NSString stringWithFormat:@"Cannot resolve URI: %@", uri];
|
||||
NSError *error = RCTErrorWithMessage(errorText);
|
||||
RCTDispatchCallbackOnMainQueue(callback, error, nil);
|
||||
}
|
||||
|
||||
+ (void)processDataForHTTPQuery:(NSDictionary *)data callback:(void (^)(NSError *error, NSDictionary *result))callback
|
||||
{
|
||||
if (data == nil) {
|
||||
RCTDispatchCallbackOnMainQueue(callback, nil, nil);
|
||||
return;
|
||||
}
|
||||
|
||||
NSData *body = [RCTConvert NSData:data[@"string"]];
|
||||
if (body != nil) {
|
||||
RCTDispatchCallbackOnMainQueue(callback, nil, @{@"body": body});
|
||||
return;
|
||||
}
|
||||
NSString *uri = [RCTConvert NSString:data[@"uri"]];
|
||||
if (uri != nil) {
|
||||
[RCTHTTPQueryExecutor processURIDataForHTTPQuery:uri callback:callback];
|
||||
return;
|
||||
}
|
||||
NSDictionaryArray *formData = [RCTConvert NSDictionaryArray:data[@"formData"]];
|
||||
if (formData != nil) {
|
||||
RCTHTTPFormDataHelper *formDataHelper = [[RCTHTTPFormDataHelper alloc] init];
|
||||
[formDataHelper process:formData callback:callback];
|
||||
return;
|
||||
}
|
||||
// Nothing in the data payload, at least nothing we could understand anyway.
|
||||
// Ignore and treat it as if it were null.
|
||||
RCTDispatchCallbackOnMainQueue(callback, nil, nil);
|
||||
}
|
||||
|
||||
- (void)sendRequest:(NSURLRequest *)request responseSender:(RCTResponseSenderBlock)responseSender
|
||||
{
|
||||
// Create session if one doesn't already exist
|
||||
if (!_session) {
|
||||
_callbackQueue = [[NSOperationQueue alloc] init];
|
||||
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
|
||||
_session = [NSURLSession sessionWithConfiguration:configuration
|
||||
delegate:self
|
||||
delegateQueue:_callbackQueue];
|
||||
}
|
||||
|
||||
__block NSURLSessionDataTask *task;
|
||||
if (_sendIncrementalUpdates) {
|
||||
task = [_session dataTaskWithRequest:request];
|
||||
} else {
|
||||
task = [_session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
|
||||
RCTSendResponseEvent(_bridge, task);
|
||||
if (!error) {
|
||||
RCTSendDataEvent(_bridge, task, data);
|
||||
}
|
||||
RCTSendCompletionEvent(_bridge, task, error);
|
||||
}];
|
||||
}
|
||||
|
||||
// Build data task
|
||||
responseSender(@[@(task.taskIdentifier)]);
|
||||
[task resume];
|
||||
}
|
||||
|
||||
#pragma mark - URLSession delegate
|
||||
|
||||
- (void)URLSession:(NSURLSession *)session
|
||||
dataTask:(NSURLSessionDataTask *)task
|
||||
didReceiveResponse:(NSURLResponse *)response
|
||||
completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler
|
||||
{
|
||||
RCTSendResponseEvent(_bridge, task);
|
||||
completionHandler(NSURLSessionResponseAllow);
|
||||
}
|
||||
|
||||
- (void)URLSession:(NSURLSession *)session
|
||||
dataTask:(NSURLSessionDataTask *)task
|
||||
didReceiveData:(NSData *)data
|
||||
{
|
||||
RCTSendDataEvent(_bridge, task, data);
|
||||
}
|
||||
|
||||
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
|
||||
{
|
||||
RCTSendCompletionEvent(_bridge, task, error);
|
||||
}
|
||||
|
||||
#pragma mark - Build responses
|
||||
|
||||
static void RCTSendResponseEvent(RCTBridge *bridge, NSURLSessionTask *task)
|
||||
{
|
||||
NSURLResponse *response = task.response;
|
||||
NSHTTPURLResponse *httpResponse = nil;
|
||||
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
|
||||
// Might be a local file request
|
||||
httpResponse = (NSHTTPURLResponse *)response;
|
||||
}
|
||||
|
||||
NSArray *responseJSON = @[@(task.taskIdentifier),
|
||||
@(httpResponse.statusCode ?: 200),
|
||||
httpResponse.allHeaderFields ?: @{},
|
||||
];
|
||||
|
||||
[bridge.eventDispatcher sendDeviceEventWithName:@"didReceiveNetworkResponse"
|
||||
body:responseJSON];
|
||||
}
|
||||
|
||||
static void RCTSendDataEvent(RCTBridge *bridge, NSURLSessionDataTask *task, NSData *data)
|
||||
{
|
||||
// Get text encoding
|
||||
NSURLResponse *response = task.response;
|
||||
NSStringEncoding encoding = NSUTF8StringEncoding;
|
||||
if (response.textEncodingName) {
|
||||
CFStringEncoding cfEncoding = CFStringConvertIANACharSetNameToEncoding((CFStringRef)response.textEncodingName);
|
||||
encoding = CFStringConvertEncodingToNSStringEncoding(cfEncoding);
|
||||
}
|
||||
|
||||
NSString *responseText = [[NSString alloc] initWithData:data encoding:encoding];
|
||||
if (!responseText && data.length) {
|
||||
RCTLogError(@"Received data was invalid.");
|
||||
return;
|
||||
}
|
||||
|
||||
NSArray *responseJSON = @[@(task.taskIdentifier), responseText ?: @""];
|
||||
[bridge.eventDispatcher sendDeviceEventWithName:@"didReceiveNetworkData"
|
||||
body:responseJSON];
|
||||
}
|
||||
|
||||
static void RCTSendCompletionEvent(RCTBridge *bridge, NSURLSessionTask *task, NSError *error)
|
||||
{
|
||||
NSArray *responseJSON = @[@(task.taskIdentifier),
|
||||
error.localizedDescription ?: [NSNull null],
|
||||
];
|
||||
|
||||
[bridge.eventDispatcher sendDeviceEventWithName:@"didCompleteNetworkResponse"
|
||||
body:responseJSON];
|
||||
}
|
||||
|
||||
@end
|
|
@ -0,0 +1,15 @@
|
|||
/**
|
||||
* 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"
|
||||
|
||||
@interface RCTHTTPRequestHandler : NSObject <RCTURLRequestHandler, RCTInvalidating>
|
||||
|
||||
@end
|
|
@ -0,0 +1,107 @@
|
|||
/**
|
||||
* 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 "RCTHTTPRequestHandler.h"
|
||||
|
||||
#import "RCTAssert.h"
|
||||
#import "RCTConvert.h"
|
||||
#import "RCTEventDispatcher.h"
|
||||
#import "RCTImageLoader.h"
|
||||
#import "RCTLog.h"
|
||||
#import "RCTUtils.h"
|
||||
|
||||
@interface RCTHTTPRequestHandler () <NSURLSessionDataDelegate>
|
||||
|
||||
@end
|
||||
|
||||
@implementation RCTHTTPRequestHandler
|
||||
{
|
||||
NSMapTable *_delegates;
|
||||
NSURLSession *_session;
|
||||
}
|
||||
|
||||
RCT_EXPORT_MODULE()
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
if ((self = [super init])) {
|
||||
_delegates = [[NSMapTable alloc] initWithKeyOptions:NSPointerFunctionsStrongMemory
|
||||
valueOptions:NSPointerFunctionsStrongMemory
|
||||
capacity:0];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)invalidate
|
||||
{
|
||||
[_session invalidateAndCancel];
|
||||
_session = nil;
|
||||
_delegates = nil;
|
||||
}
|
||||
|
||||
- (BOOL)isValid
|
||||
{
|
||||
return _delegates != nil;
|
||||
}
|
||||
|
||||
#pragma mark - NSURLRequestHandler
|
||||
|
||||
- (BOOL)canHandleRequest:(NSURLRequest *)request
|
||||
{
|
||||
return [@[@"http", @"https", @"file"] containsObject:[request.URL.scheme lowercaseString]];
|
||||
}
|
||||
|
||||
- (id)sendRequest:(NSURLRequest *)request
|
||||
withDelegate:(id<RCTURLRequestDelegate>)delegate
|
||||
{
|
||||
// Lazy setup
|
||||
if (!_session && [self isValid]) {
|
||||
NSOperationQueue *callbackQueue = [[NSOperationQueue alloc] init];
|
||||
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
|
||||
_session = [NSURLSession sessionWithConfiguration:configuration
|
||||
delegate:self
|
||||
delegateQueue:callbackQueue];
|
||||
}
|
||||
|
||||
NSURLSessionDataTask *task = [_session dataTaskWithRequest:request];
|
||||
[_delegates setObject:delegate forKey:task];
|
||||
[task resume];
|
||||
return task;
|
||||
}
|
||||
|
||||
- (void)cancelRequest:(NSURLSessionDataTask *)requestToken
|
||||
{
|
||||
[requestToken cancel];
|
||||
}
|
||||
|
||||
#pragma mark - NSURLSession delegate
|
||||
|
||||
- (void)URLSession:(NSURLSession *)session
|
||||
dataTask:(NSURLSessionDataTask *)task
|
||||
didReceiveResponse:(NSURLResponse *)response
|
||||
completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler
|
||||
{
|
||||
[[_delegates objectForKey:task] URLRequest:task didReceiveResponse:response];
|
||||
completionHandler(NSURLSessionResponseAllow);
|
||||
}
|
||||
|
||||
- (void)URLSession:(NSURLSession *)session
|
||||
dataTask:(NSURLSessionDataTask *)task
|
||||
didReceiveData:(NSData *)data
|
||||
{
|
||||
[[_delegates objectForKey:task] URLRequest:task didReceiveData:data];
|
||||
}
|
||||
|
||||
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
|
||||
{
|
||||
[[_delegates objectForKey:task] URLRequest:task didCompleteWithError:error];
|
||||
[_delegates removeObjectForKey:task];
|
||||
}
|
||||
|
||||
@end
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
/* Begin PBXBuildFile section */
|
||||
1372B7371AB03E7B00659ED6 /* RCTReachability.m in Sources */ = {isa = PBXBuildFile; fileRef = 1372B7361AB03E7B00659ED6 /* RCTReachability.m */; };
|
||||
352DA0BA1B17855800AA15A8 /* RCTHTTPQueryExecutor.m in Sources */ = {isa = PBXBuildFile; fileRef = 352DA0B81B17855800AA15A8 /* RCTHTTPQueryExecutor.m */; };
|
||||
352DA0BA1B17855800AA15A8 /* RCTHTTPRequestHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 352DA0B81B17855800AA15A8 /* RCTHTTPRequestHandler.m */; };
|
||||
58B512081A9E6CE300147676 /* RCTDataManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 58B512071A9E6CE300147676 /* RCTDataManager.m */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
|
@ -27,9 +27,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>"; };
|
||||
352DA0B51B17855800AA15A8 /* RCTDataQuery.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTDataQuery.h; sourceTree = "<group>"; };
|
||||
352DA0B71B17855800AA15A8 /* RCTHTTPQueryExecutor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTHTTPQueryExecutor.h; sourceTree = "<group>"; };
|
||||
352DA0B81B17855800AA15A8 /* RCTHTTPQueryExecutor.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTHTTPQueryExecutor.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; };
|
||||
58B512061A9E6CE300147676 /* RCTDataManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTDataManager.h; sourceTree = "<group>"; };
|
||||
58B512071A9E6CE300147676 /* RCTDataManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTDataManager.m; sourceTree = "<group>"; };
|
||||
|
@ -51,9 +50,8 @@
|
|||
children = (
|
||||
58B512061A9E6CE300147676 /* RCTDataManager.h */,
|
||||
58B512071A9E6CE300147676 /* RCTDataManager.m */,
|
||||
352DA0B51B17855800AA15A8 /* RCTDataQuery.h */,
|
||||
352DA0B71B17855800AA15A8 /* RCTHTTPQueryExecutor.h */,
|
||||
352DA0B81B17855800AA15A8 /* RCTHTTPQueryExecutor.m */,
|
||||
352DA0B71B17855800AA15A8 /* RCTHTTPRequestHandler.h */,
|
||||
352DA0B81B17855800AA15A8 /* RCTHTTPRequestHandler.m */,
|
||||
1372B7351AB03E7B00659ED6 /* RCTReachability.h */,
|
||||
1372B7361AB03E7B00659ED6 /* RCTReachability.m */,
|
||||
58B511DC1A9E6C8500147676 /* Products */,
|
||||
|
@ -128,7 +126,7 @@
|
|||
files = (
|
||||
1372B7371AB03E7B00659ED6 /* RCTReachability.m in Sources */,
|
||||
58B512081A9E6CE300147676 /* RCTDataManager.m in Sources */,
|
||||
352DA0BA1B17855800AA15A8 /* RCTHTTPQueryExecutor.m in Sources */,
|
||||
352DA0BA1B17855800AA15A8 /* RCTHTTPRequestHandler.m in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
|
|
@ -89,28 +89,24 @@ class XMLHttpRequest extends XMLHttpRequestBase {
|
|||
if (data instanceof FormData) {
|
||||
data = {formData: data.getParts()};
|
||||
}
|
||||
RCTDataManager.queryData(
|
||||
'http',
|
||||
RCTDataManager.sendRequest(
|
||||
{
|
||||
method,
|
||||
url,
|
||||
data,
|
||||
headers,
|
||||
incrementalUpdates: this.onreadystatechange ? true : false,
|
||||
},
|
||||
this.onreadystatechange ? true : false,
|
||||
this._didCreateRequest.bind(this)
|
||||
);
|
||||
}
|
||||
|
||||
abortImpl(): void {
|
||||
if (this._requestId) {
|
||||
RCTDataManager.cancelRequest(this._requestId);
|
||||
this._clearSubscriptions();
|
||||
this._requestId = null;
|
||||
}
|
||||
console.warn(
|
||||
'XMLHttpRequest: abort() cancels JS callbacks ' +
|
||||
'but not native HTTP request.'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -118,7 +118,8 @@ RCT_CONVERTER(NSString *, NSString, description)
|
|||
|
||||
+ (NSURLRequest *)NSURLRequest:(id)json
|
||||
{
|
||||
return [NSURLRequest requestWithURL:[self NSURL:json]];
|
||||
NSURL *URL = [self NSURL:json];
|
||||
return URL ? [NSURLRequest requestWithURL:URL] : nil;
|
||||
}
|
||||
|
||||
+ (NSDate *)NSDate:(id)json
|
||||
|
|
|
@ -18,6 +18,21 @@
|
|||
#define RCT_EXTERN extern __attribute__((visibility("default")))
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Nullability for Xcode 6.2
|
||||
*/
|
||||
#if !__has_feature(nullability)
|
||||
#define NS_ASSUME_NONNULL_BEGIN
|
||||
#define NS_ASSUME_NONNULL_END
|
||||
#define nullable
|
||||
#define nonnull
|
||||
#define null_unspecified
|
||||
#define null_resettable
|
||||
#define __nullable
|
||||
#define __nonnull
|
||||
#define __null_unspecified
|
||||
#endif
|
||||
|
||||
/**
|
||||
* The RCT_DEBUG macro can be used to exclude error checking and logging code
|
||||
* from release builds to improve performance and reduce binary size.
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
/**
|
||||
* 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>
|
||||
|
||||
/**
|
||||
* An abstract interface used by request handler modules to send
|
||||
* data back over the bridge back to JS.
|
||||
*/
|
||||
@protocol RCTURLRequestDelegate <NSObject>
|
||||
|
||||
/**
|
||||
* Call this when you first receives a response from the server. This should
|
||||
* include response headers, etc.
|
||||
*/
|
||||
- (void)URLRequest:(id)requestToken didReceiveResponse:(NSURLResponse *)response;
|
||||
|
||||
/**
|
||||
* Call this when you receive data from the server. This can be called multiple
|
||||
* times with partial data chunks, or just once with the full data packet.
|
||||
*/
|
||||
- (void)URLRequest:(id)requestToken didReceiveData:(NSData *)data;
|
||||
|
||||
/**
|
||||
* Call this when the request is complete and/or if an error is encountered.
|
||||
* For a successful request, the error parameter should be nil.
|
||||
*/
|
||||
- (void)URLRequest:(id)requestToken didCompleteWithError:(NSError *)error;
|
||||
|
||||
@end
|
|
@ -0,0 +1,54 @@
|
|||
/**
|
||||
* 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 "RCTBridgeModule.h"
|
||||
#import "RCTURLRequestDelegate.h"
|
||||
|
||||
/**
|
||||
* Provides the interface needed to register a request handler. Request handlers
|
||||
* are also bridge modules, so should be registered using RCT_EXPORT_MODULE().
|
||||
*/
|
||||
@protocol RCTURLRequestHandler <RCTBridgeModule>
|
||||
|
||||
/**
|
||||
* Indicates whether this handler is capable of processing the specified
|
||||
* request. Typically the handler would examine the scheme/protocol of the
|
||||
* request URL (and possibly the HTTP method and/or headers) to determine this.
|
||||
*/
|
||||
- (BOOL)canHandleRequest:(NSURLRequest *)request;
|
||||
|
||||
/**
|
||||
* Send a network request and call the delegate with the response data. The
|
||||
* method should return a token, which can be anything, including the request
|
||||
* itself. This will be used later to refer to the request in callbacks. The
|
||||
* `sendRequest:withDelegate:` method *must* return before calling any of the
|
||||
* delegate methods, or the delegate won't recognize the token.
|
||||
*/
|
||||
- (id)sendRequest:(NSURLRequest *)request
|
||||
withDelegate:(id<RCTURLRequestDelegate>)delegate;
|
||||
|
||||
@optional
|
||||
|
||||
/**
|
||||
* Not all request types can be cancelled, but this method can be implemented
|
||||
* for ones that can. It should be used to free up any resources on ongoing
|
||||
* processes associated with the request.
|
||||
*/
|
||||
- (void)cancelRequest:(id)requestToken;
|
||||
|
||||
/**
|
||||
* If more than one RCTURLRequestHandler responds YES to `canHandleRequest:`
|
||||
* then `handlerPriority` is used to determine which one to use. The handler
|
||||
* with the highest priority will be selected. Default priority is zero. If
|
||||
* two or more valid handlers have the same priority, the selection order is
|
||||
* undefined.
|
||||
*/
|
||||
- (float)handlerPriority;
|
||||
|
||||
@end
|
|
@ -61,10 +61,6 @@ RCT_EXTERN BOOL RCTImageHasAlpha(CGImageRef image);
|
|||
// Create an NSError in the NCTErrorDomain
|
||||
RCT_EXTERN NSError *RCTErrorWithMessage(NSString *message);
|
||||
|
||||
// Dispatch an error + result callback on the main thread.
|
||||
typedef void (^RCTResultOrErrorBlock)(NSError *error, id result);
|
||||
RCT_EXTERN void RCTDispatchCallbackOnMainQueue(RCTResultOrErrorBlock callback, NSError *error, id result);
|
||||
|
||||
// Convert nil values to NSNull, and vice-versa
|
||||
RCT_EXTERN id RCTNullIfNil(id value);
|
||||
RCT_EXTERN id RCTNilIfNull(id value);
|
||||
|
|
|
@ -289,17 +289,6 @@ NSError *RCTErrorWithMessage(NSString *message)
|
|||
return error;
|
||||
}
|
||||
|
||||
void RCTDispatchCallbackOnMainQueue(RCTResultOrErrorBlock callback, NSError *error, id result)
|
||||
{
|
||||
if ([NSThread isMainThread]) {
|
||||
callback(error, result);
|
||||
} else {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
callback(error, result);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
id RCTNullIfNil(id value)
|
||||
{
|
||||
return value ?: (id)kCFNull;
|
||||
|
|
|
@ -101,6 +101,8 @@
|
|||
13456E921ADAD2DE009F94A7 /* RCTConvert+CoreLocation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "RCTConvert+CoreLocation.m"; sourceTree = "<group>"; };
|
||||
13456E941ADAD482009F94A7 /* RCTConvert+MapKit.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "RCTConvert+MapKit.h"; sourceTree = "<group>"; };
|
||||
13456E951ADAD482009F94A7 /* RCTConvert+MapKit.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "RCTConvert+MapKit.m"; sourceTree = "<group>"; };
|
||||
1345A83A1B265A0E00583190 /* RCTURLRequestDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTURLRequestDelegate.h; sourceTree = "<group>"; };
|
||||
1345A83B1B265A0E00583190 /* RCTURLRequestHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTURLRequestHandler.h; sourceTree = "<group>"; };
|
||||
134FCB391A6E7F0800051CC8 /* RCTContextExecutor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTContextExecutor.h; sourceTree = "<group>"; };
|
||||
134FCB3A1A6E7F0800051CC8 /* RCTContextExecutor.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTContextExecutor.m; sourceTree = "<group>"; };
|
||||
134FCB3B1A6E7F0800051CC8 /* RCTWebViewExecutor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTWebViewExecutor.h; sourceTree = "<group>"; };
|
||||
|
@ -396,6 +398,8 @@
|
|||
83CBBA491A601E3B00E9B192 /* Base */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
1345A83A1B265A0E00583190 /* RCTURLRequestDelegate.h */,
|
||||
1345A83B1B265A0E00583190 /* RCTURLRequestHandler.h */,
|
||||
14200DA81AC179B3008EE6BA /* RCTJavaScriptLoader.h */,
|
||||
14200DA91AC179B3008EE6BA /* RCTJavaScriptLoader.m */,
|
||||
83CBBA4A1A601E3B00E9B192 /* RCTAssert.h */,
|
||||
|
|
Loading…
Reference in New Issue