EMASCurlWeb/EMASCurlWebRequestExecutor.m (172 lines of code) (raw):

// // EMASCurlNetworkManager.m // #import "EMASCurlWebRequestExecutor.h" #import "EMASCurlWebUtils.h" #import <WebKit/Webkit.h> @interface EMASCurlWebNetworkCallbackPack : NSObject @property (nonatomic, copy) EMASCurlNetResponseCallback responseCallback; @property (nonatomic, copy) EMASCurlNetDataCallback dataCallback; @property (nonatomic, copy) EMASCurlNetSuccessCallback successCallback; @property (nonatomic, copy) EMASCurlNetFailCallback failCallback; @property (nonatomic, copy) EMASCurlNetRedirectCallback redirectCallback; - (instancetype)initWithResponseCallback:(EMASCurlNetResponseCallback)responseCallback dataCallback:(EMASCurlNetDataCallback)dataCallback successCallback:(EMASCurlNetSuccessCallback)successCallback failCallback:(EMASCurlNetFailCallback)failCallback redirectCallback:(EMASCurlNetRedirectCallback)redirectCallback; @end @implementation EMASCurlWebNetworkCallbackPack - (instancetype)initWithResponseCallback:(EMASCurlNetResponseCallback)responseCallback dataCallback:(EMASCurlNetDataCallback)dataCallback successCallback:(EMASCurlNetSuccessCallback)successCallback failCallback:(EMASCurlNetFailCallback)failCallback redirectCallback:(EMASCurlNetRedirectCallback)redirectCallback { self = [super init]; if (self) { _responseCallback = responseCallback; _dataCallback = dataCallback; _successCallback = successCallback; _failCallback = failCallback; _redirectCallback = redirectCallback; } return self; } @end @interface EMASCurlWebRequestExecutor ()<NSURLSessionTaskDelegate, NSURLSessionDataDelegate> @property (nonatomic, strong) NSURLSession *URLSession; @property (nonatomic, strong) NSOperationQueue *requestCallbackQueue; @property (nonatomic, strong) EMASCurlSafeDictionary *taskToCallbackPackMap; @property (nonatomic, strong) EMASCurlSafeDictionary *taskidToDataTaskMap; @end @implementation EMASCurlWebRequestExecutor - (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)sessionConfiguration { if (self = [super init]) { sessionConfiguration.HTTPShouldUsePipelining = YES; sessionConfiguration.requestCachePolicy = NSURLRequestReloadIgnoringLocalCacheData; self.URLSession = [NSURLSession sessionWithConfiguration:sessionConfiguration delegate:self delegateQueue:self.requestCallbackQueue]; } return self; } - (RequestTaskIdentifier)startWithRequest:(NSURLRequest *)request responseCallback:(EMASCurlNetResponseCallback)responseCallback dataCallback:(EMASCurlNetDataCallback)dataCallback successCallback:(EMASCurlNetSuccessCallback)successCallback failCallback:(EMASCurlNetFailCallback)failCallback redirectCallback:(EMASCurlNetRedirectCallback)redirectCallback { NSURLSessionDataTask *dataTask = [self.URLSession dataTaskWithRequest:request]; EMASCurlWebNetworkCallbackPack *cbPack = [[EMASCurlWebNetworkCallbackPack alloc] initWithResponseCallback:responseCallback dataCallback:dataCallback successCallback:successCallback failCallback:failCallback redirectCallback:redirectCallback]; [self.taskToCallbackPackMap setObject:cbPack forKey:@(dataTask.taskIdentifier)]; [self.taskidToDataTaskMap setObject:dataTask forKey:@(dataTask.taskIdentifier)]; [dataTask resume]; return dataTask.taskIdentifier; } - (void)cancelWithRequestIdentifier:(RequestTaskIdentifier)requestTaskIdentifier { if (requestTaskIdentifier < 0) { return; } [self.taskToCallbackPackMap removeObjectForKey:@(requestTaskIdentifier)]; NSURLSessionDataTask *dataTask = [self.taskidToDataTaskMap objectForKey:@(requestTaskIdentifier)]; if (dataTask) { [dataTask cancel]; [self.taskidToDataTaskMap removeObjectForKey:@(requestTaskIdentifier)]; } } #pragma mark - Lazy - (NSOperationQueue *)requestCallbackQueue { if (!_requestCallbackQueue) { _requestCallbackQueue = [NSOperationQueue new]; _requestCallbackQueue.qualityOfService = NSQualityOfServiceUserInitiated; _requestCallbackQueue.maxConcurrentOperationCount = 1; _requestCallbackQueue.name = @"com.alicloud.emascurl.networkcallback"; } return _requestCallbackQueue; } - (EMASCurlSafeDictionary *)taskToCallbackPackMap { if (!_taskToCallbackPackMap) { _taskToCallbackPackMap = [EMASCurlSafeDictionary new]; } return _taskToCallbackPackMap; } - (EMASCurlSafeDictionary *)taskidToDataTaskMap { if (!_taskidToDataTaskMap) { _taskidToDataTaskMap = [EMASCurlSafeDictionary new]; } return _taskidToDataTaskMap; } #pragma mark - <NSURLSessionTaskDelegate, NSURLSessionDataDelegate> - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSHTTPURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler { [self syncCookieToWKWithResponse:response]; EMASCurlWebNetworkCallbackPack *cbPack = [self.taskToCallbackPackMap objectForKey:@(dataTask.taskIdentifier)]; if (cbPack && cbPack.responseCallback) { cbPack.responseCallback(response); } completionHandler(NSURLSessionResponseAllow); } - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data { EMASCurlWebNetworkCallbackPack *cbPack = [self.taskToCallbackPackMap objectForKey:@(dataTask.taskIdentifier)]; if (cbPack && cbPack.dataCallback) { cbPack.dataCallback(data); } } - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error { EMASCurlWebNetworkCallbackPack *cbPack = [self.taskToCallbackPackMap objectForKey:@(task.taskIdentifier)]; if (!cbPack) { return; } if (error) { if (cbPack.failCallback) { cbPack.failCallback(error); } } else { if (cbPack.successCallback) { cbPack.successCallback(); } } [self.taskToCallbackPackMap removeObjectForKey:@(task.taskIdentifier)]; [self.taskidToDataTaskMap removeObjectForKey:@(task.taskIdentifier)]; } - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task willPerformHTTPRedirection:(NSHTTPURLResponse *)response newRequest:(NSURLRequest *)request completionHandler:(void (^)(NSURLRequest * _Nullable))completionHandler { [self syncCookieToWKWithResponse:response]; EMASCurlWebNetworkCallbackPack *cbworker = [self.taskToCallbackPackMap objectForKey:@(task.taskIdentifier)]; void(^redirectDecisionCallback)(BOOL) = ^(BOOL canPass) { if (canPass) { completionHandler(request); } else { [task cancel]; completionHandler(nil); } }; if (cbworker && cbworker.redirectCallback) { cbworker.redirectCallback(response, request, redirectDecisionCallback); } else { completionHandler(request); } } -(void)syncCookieToWKWithResponse:(NSHTTPURLResponse *)response { NSArray<NSHTTPCookie *> *responseCookies = [NSHTTPCookie cookiesWithResponseHeaderFields:[response allHeaderFields] forURL:response.URL]; if ([responseCookies isKindOfClass:[NSArray class]] && responseCookies.count > 0) { dispatch_async(dispatch_get_main_queue(), ^{ [responseCookies enumerateObjectsUsingBlock:^(NSHTTPCookie * _Nonnull cookie, NSUInteger idx, BOOL * _Nonnull stop) { if (@available(iOS 11.0, *)) { [[WKWebsiteDataStore defaultDataStore].httpCookieStore setCookie:cookie completionHandler:nil]; } }]; }); } } @end