libs-base/Source/NSURLSession.m
rfm ed4e305026
NSURLSession rewrite (#422)
* clang-format: Do not use tabs

* Ignore clangd cache and compile_commands

* NSBlockOperation: Fix memory leak

* NSHTTPCookie: Fix expires date parsing

* NSHTTPCookie: Release DateFormatter after use

* NSOperation: Remove all objects at end of execution

* Reimplementation of NSURLSession

* NSURLSession: Update expiration dates in test

* Update ChangeLog

* Fix trivial compiler warning caused by missing import

* Import GSDispatch.h for definition of DISPATCH_QUEUE_SERIAL

* Import common.h early to avoid header conflict

* Fix import order to avoid conflicts and ensure we have correct localisation macro

* Check for presence of dispatch_cancel

* Cancel timer using dispatch_source_cancel() if dispatch_cancel() is missing.

* NSURLSession: Replace dispatch_io with dispatch_source in unit test HTTP server

---------

Co-authored-by: hmelder <service@hugomelder.com>
2024-08-16 14:08:41 +02:00

1065 lines
31 KiB
Objective-C

/**
NSURLSession.m
Copyright (C) 2017-2024 Free Software Foundation, Inc.
Written by: Hugo Melder <hugo@algoriddim.com>
Date: May 2024
Author: Hugo Melder <hugo@algoriddim.com>
This file is part of GNUStep-base
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
If you are interested in a warranty or support for this source code,
contact Scott Christley <scottc@net-community.com> for more information.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free
Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110 USA.
*/
#import "NSURLSessionPrivate.h"
#import "NSURLSessionTaskPrivate.h"
#import "Foundation/NSString.h"
#import "Foundation/NSArray.h"
#import "Foundation/NSStream.h"
#import "Foundation/NSUserDefaults.h"
#import "Foundation/NSBundle.h"
#import "Foundation/NSData.h"
#import "GNUstepBase/NSDebug+GNUstepBase.h" /* For NSDebugMLLog */
#import "GNUstepBase/NSObject+GNUstepBase.h" /* For -notImplemented */
#import "GSPThread.h" /* For nextSessionIdentifier() */
#import "GSDispatch.h" /* For dispatch compatibility */
NSString *GS_NSURLSESSION_DEBUG_KEY = @"NSURLSession";
/* We need a globably unique label for the NSURLSession workQueues.
*/
static NSUInteger
nextSessionIdentifier()
{
static gs_mutex_t lock = GS_MUTEX_INIT_STATIC;
static NSUInteger sessionCounter = 0;
GS_MUTEX_LOCK(lock);
sessionCounter += 1;
GS_MUTEX_UNLOCK(lock);
return sessionCounter;
}
#pragma mark - libcurl callbacks
/* CURLMOPT_TIMERFUNCTION: Callback to receive timer requests from libcurl */
static int
timer_callback(CURLM *multi, /* multi handle */
long timeout_ms, /* timeout in number of ms */
void *clientp) /* private callback pointer */
{
NSURLSession *session = (NSURLSession *) clientp;
NSDebugLLog(GS_NSURLSESSION_DEBUG_KEY,
@"Timer Callback for Session %@: multi=%p timeout_ms=%ld",
session, multi, timeout_ms);
/*
* if timeout_ms is -1, just delete the timer
*
* For all other values of timeout_ms, this should set or *update* the timer
* to the new value
*/
if (timeout_ms == -1)
[session _suspendTimer];
else
[session _setTimer:timeout_ms];
return 0;
}
/* CURLMOPT_SOCKETFUNCTION: libcurl requests socket monitoring using this
* callback */
static int
socket_callback(CURL *easy, /* easy handle */
curl_socket_t s, /* socket */
int what, /* describes the socket */
void *clientp, /* private callback pointer */
void *socketp) /* private socket pointer */
{
NSURLSession *session = clientp;
const char *whatstr[] = {"none", "IN", "OUT", "INOUT", "REMOVE"};
NSDebugLLog(GS_NSURLSESSION_DEBUG_KEY,
@"Socket Callback for Session %@: socket=%d easy:%p what=%s",
session, s, easy, whatstr[what]);
if (NULL == socketp)
{
return [session _addSocket:s easyHandle:easy what:what];
}
else if (CURL_POLL_REMOVE == what)
{
[session _removeSocket:(struct SourceInfo *) socketp];
return 0;
}
else
{
return [session _setSocket:s
sources:(struct SourceInfo *) socketp
what:what];
}
}
#pragma mark - NSURLSession Implementation
@implementation NSURLSession
{
/* The libcurl multi handle associated with this session.
* We use the curl_multi_socket_action API as we utilise our
* own event-handling system based on libdispatch.
*
* Event creation and deletion is driven by the various callbacks
* registered during initialisation of the multi handle.
*/
CURLM *_multiHandle;
/* A serial work queue for timer and socket sources
* created on libcurl's behalf.
*/
dispatch_queue_t _workQueue;
/* This timer is driven by libcurl and used by
* libcurl's multi API.
*
* The handler notifies libcurl using curl_multi_socket_action
* and checks for completed requests by calling
* _checkForCompletion.
*
* See https://curl.se/libcurl/c/CURLMOPT_TIMERFUNCTION.html
* and https://curl.se/libcurl/c/curl_multi_socket_action.html
* respectively.
*/
dispatch_source_t _timer;
/* The timer may be suspended upon request by libcurl.
*/
BOOL _isTimerSuspended;
/* Only set when session originates from +[NSURLSession sharedSession] */
BOOL _isSharedSession;
BOOL _invalidated;
/*
* Number of currently running handles.
* This number is updated by curl_multi_socket_action
* in the socket source handlers.
*/
int _stillRunning;
/* List of active tasks. Access is synchronised via the _workQueue.
*/
NSMutableArray<NSURLSessionTask *> *_tasks;
/* PEM encoded blob of one or more certificates.
*
* See GSCACertificateFilePath in NSUserDefaults.h
*/
NSData *_certificateBlob;
/* Path to PEM encoded CA certificate file. */
NSString *_certificatePath;
/* The task identifier for the next task
*/
_Atomic(NSInteger) _taskIdentifier;
/* Lock for _taskIdentifier and _tasks
*/
gs_mutex_t _taskLock;
}
+ (NSURLSession *)sharedSession
{
static NSURLSession *session = nil;
static dispatch_once_t predicate;
dispatch_once(&predicate, ^{
NSURLSessionConfiguration *configuration =
[NSURLSessionConfiguration defaultSessionConfiguration];
session = [[NSURLSession alloc] initWithConfiguration:configuration
delegate:nil
delegateQueue:nil];
[session _setSharedSession:YES];
});
return session;
}
+ (NSURLSession *)sessionWithConfiguration:
(NSURLSessionConfiguration *)configuration
{
NSURLSession *session;
session = [[NSURLSession alloc] initWithConfiguration:configuration
delegate:nil
delegateQueue:nil];
return AUTORELEASE(session);
}
+ (NSURLSession *)sessionWithConfiguration:
(NSURLSessionConfiguration *)configuration
delegate:(id<NSURLSessionDelegate>)delegate
delegateQueue:(NSOperationQueue *)queue
{
NSURLSession *session;
session = [[NSURLSession alloc] initWithConfiguration:configuration
delegate:delegate
delegateQueue:queue];
return AUTORELEASE(session);
}
- (instancetype)initWithConfiguration:(NSURLSessionConfiguration *)configuration
delegate:(id<NSURLSessionDelegate>)delegate
delegateQueue:(NSOperationQueue *)queue
{
self = [super init];
if (self)
{
NSString *queueLabel;
NSString *caPath;
NSUInteger sessionIdentifier;
/* To avoid a retain cycle in blocks referencing this object */
__block typeof(self) this = self;
sessionIdentifier = nextSessionIdentifier();
queueLabel = [[NSString alloc]
initWithFormat:@"org.gnustep.NSURLSession.WorkQueue%ld",
sessionIdentifier];
ASSIGN(_delegate, delegate);
ASSIGNCOPY(_configuration, configuration);
_tasks = [[NSMutableArray alloc] init];
GS_MUTEX_INIT(_taskLock);
/* label is strdup'ed by libdispatch */
_workQueue
= dispatch_queue_create([queueLabel UTF8String], DISPATCH_QUEUE_SERIAL);
[queueLabel release];
if (!_workQueue)
return nil;
_isTimerSuspended = YES;
_timer
= dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, _workQueue);
if (!_timer)
{
return nil;
}
dispatch_source_set_cancel_handler(_timer, ^{
dispatch_release(this->_timer);
});
// Called after timeout set by libcurl is reached
dispatch_source_set_event_handler(_timer, ^{
// TODO: Check for return values
curl_multi_socket_action(this->_multiHandle, CURL_SOCKET_TIMEOUT, 0,
&this->_stillRunning);
[this _checkForCompletion];
});
/* Use the provided delegateQueue if available */
if (queue)
{
_delegateQueue = queue;
}
else
{
/* This (serial) NSOperationQueue is only used for dispatching
* delegate callbacks and is orthogonal to the workQueue.
*/
_delegateQueue = [[NSOperationQueue alloc] init];
[_delegateQueue setMaxConcurrentOperationCount:1];
}
/* libcurl Configuration */
curl_global_init(CURL_GLOBAL_SSL);
_multiHandle = curl_multi_init();
// Set up CURL multi callbacks
curl_multi_setopt(_multiHandle, CURLMOPT_SOCKETFUNCTION, socket_callback);
curl_multi_setopt(_multiHandle, CURLMOPT_SOCKETDATA, self);
curl_multi_setopt(_multiHandle, CURLMOPT_TIMERFUNCTION, timer_callback);
curl_multi_setopt(_multiHandle, CURLMOPT_TIMERDATA, self);
// Configure Multi Handle
curl_multi_setopt(_multiHandle, CURLMOPT_MAX_HOST_CONNECTIONS,
[_configuration HTTPMaximumConnectionsPerHost]);
/* Check if GSCACertificateFilePath is set */
caPath = [[NSUserDefaults standardUserDefaults]
objectForKey:GSCACertificateFilePath];
if (caPath)
{
NSDebugMLLog(
GS_NSURLSESSION_DEBUG_KEY,
@"Found a GSCACertificateFilePath entry in UserDefaults");
_certificateBlob = [[NSData alloc] initWithContentsOfFile:caPath];
if (!_certificateBlob)
{
NSDebugMLLog(GS_NSURLSESSION_DEBUG_KEY,
@"Could not open file at GSCACertificateFilePath=%@",
caPath);
}
else
{
ASSIGN(_certificatePath, caPath);
}
}
}
return self;
}
#pragma mark - Private Methods
- (NSData *)_certificateBlob
{
return _certificateBlob;
}
- (NSString *)_certificatePath
{
return _certificatePath;
}
- (void)_setSharedSession:(BOOL)flag
{
_isSharedSession = flag;
}
- (NSInteger)_nextTaskIdentifier
{
NSInteger identifier;
GS_MUTEX_LOCK(_taskLock);
identifier = _taskIdentifier;
_taskIdentifier += 1;
GS_MUTEX_UNLOCK(_taskLock);
return identifier;
}
- (void)_resumeTask:(NSURLSessionTask *)task
{
dispatch_async(_workQueue, ^{
CURLMcode code;
CURLM *multiHandle = _multiHandle;
code = curl_multi_add_handle(multiHandle, [task _easyHandle]);
NSDebugMLLog(GS_NSURLSESSION_DEBUG_KEY,
@"Added task=%@ easy=%p to multi=%p with return value %d",
task, [task _easyHandle], multiHandle, code);
});
}
- (void)_addHandle:(CURL *)easy
{
curl_multi_add_handle(_multiHandle, easy);
}
- (void)_removeHandle:(CURL *)easy
{
curl_multi_remove_handle(_multiHandle, easy);
}
- (void)_setTimer:(NSInteger)timeoutMs
{
dispatch_source_set_timer(_timer,
dispatch_time(DISPATCH_TIME_NOW,
timeoutMs * NSEC_PER_MSEC),
DISPATCH_TIME_FOREVER, // don't repeat
timeoutMs * 0.05); // 5% leeway
if (_isTimerSuspended)
{
_isTimerSuspended = NO;
dispatch_resume(_timer);
}
}
- (void)_suspendTimer
{
if (!_isTimerSuspended)
{
_isTimerSuspended = YES;
dispatch_suspend(_timer);
}
}
- (dispatch_queue_t)_workQueue
{
return _workQueue;
}
/* This method is called when receiving CURL_POLL_REMOVE in socket_callback.
* We cancel all active dispatch sources and release the SourceInfo structure
* previously allocated in _addSocket:easyHandle:what:
*/
- (void)_removeSocket:(struct SourceInfo *)sources
{
NSDebugMLLog(GS_NSURLSESSION_DEBUG_KEY, @"Remove socket with SourceInfo: %p",
sources);
if (sources->readSocket)
{
dispatch_source_cancel(sources->readSocket);
dispatch_release(sources->readSocket);
}
if (sources->writeSocket)
{
dispatch_source_cancel(sources->writeSocket);
dispatch_release(sources->writeSocket);
}
free(sources);
}
/* A socket needs to be configured and the private socket pointer
* (socketp) in socket_callback is NULL, meaning we first need to
* allocate our SourceInfo structure.
*/
- (int)_addSocket:(curl_socket_t)socket easyHandle:(CURL *)easy what:(int)what
{
struct SourceInfo *info;
NSDebugMLLog(GS_NSURLSESSION_DEBUG_KEY, @"Add Socket: %d easy: %p", socket,
easy);
/* Allocate a new SourceInfo structure on the heap */
if (!(info = calloc(1, sizeof(struct SourceInfo))))
{
NSDebugMLLog(GS_NSURLSESSION_DEBUG_KEY,
@"Failed to allocate SourceInfo structure!");
return -1;
}
/* We can now configure the dispatch sources */
if (-1 == [self _setSocket:socket sources:info what:what])
{
NSDebugMLLog(GS_NSURLSESSION_DEBUG_KEY, @"Failed to setup sockets!");
return -1;
}
/* Assign the SourceInfo for access in subsequent socket_callback calls */
curl_multi_assign(_multiHandle, socket, info);
return 0;
}
- (int)_setSocket:(curl_socket_t)socket
sources:(struct SourceInfo *)sources
what:(int)what
{
/* Create a Reading Dispatch Source that listens on socket */
if (CURL_POLL_IN == what || CURL_POLL_INOUT == what)
{
/* Reset Dispatch Source if previously initialised */
if (sources->readSocket)
{
dispatch_source_cancel(sources->readSocket);
dispatch_release(sources->readSocket);
sources->readSocket = NULL;
}
NSDebugMLLog(
GS_NSURLSESSION_DEBUG_KEY,
@"Creating a reading dispatch source: socket=%d sources=%p what=%d",
socket, sources, what);
sources->readSocket = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ,
socket, 0, _workQueue);
if (!sources->readSocket)
{
NSDebugMLLog(GS_NSURLSESSION_DEBUG_KEY,
@"Unable to create dispatch source for read socket!");
return -1;
}
dispatch_source_set_event_handler(sources->readSocket, ^{
int action;
action = CURL_CSELECT_IN;
curl_multi_socket_action(_multiHandle, socket, action, &_stillRunning);
/* Check if the transfer is complete */
[self _checkForCompletion];
/* When _stillRunning reaches zero, all transfers are complete/done */
if (_stillRunning <= 0)
{
[self _suspendTimer];
}
});
dispatch_resume(sources->readSocket);
}
/* Create a Writing Dispatch Source that listens on socket */
if (CURL_POLL_OUT == what || CURL_POLL_INOUT == what)
{
/* Reset Dispatch Source if previously initialised */
if (sources->writeSocket)
{
dispatch_source_cancel(sources->writeSocket);
dispatch_release(sources->writeSocket);
sources->writeSocket = NULL;
}
NSDebugMLLog(
GS_NSURLSESSION_DEBUG_KEY,
@"Creating a writing dispatch source: socket=%d sources=%p what=%d",
socket, sources, what);
sources->writeSocket = dispatch_source_create(DISPATCH_SOURCE_TYPE_WRITE,
socket, 0, _workQueue);
if (!sources->writeSocket)
{
NSDebugMLLog(GS_NSURLSESSION_DEBUG_KEY,
@"Unable to create dispatch source for write socket!");
return -1;
}
dispatch_source_set_event_handler(sources->writeSocket, ^{
int action;
action = CURL_CSELECT_OUT;
curl_multi_socket_action(_multiHandle, socket, action, &_stillRunning);
/* Check if the tranfer is complete */
[self _checkForCompletion];
/* When _stillRunning reaches zero, all transfers are complete/done */
if (_stillRunning <= 0)
{
[self _suspendTimer];
}
});
dispatch_resume(sources->writeSocket);
}
return 0;
}
/* Called by a socket event handler or by a firing timer set by timer_callback.
*
* The socket event handler is executed on the _workQueue.
*/
- (void)_checkForCompletion
{
CURLMsg *msg;
int msgs_left;
CURL *easyHandle;
CURLcode res;
char *eff_url = NULL;
NSURLSessionTask *task = nil;
/* Ask the multi handle if there are any messages from the individual
* transfers.
*
* Remove the associated easy handle and release the task if the transfer is
* done. This completes the life-cycle of a task added to NSURLSession.
*/
while ((msg = curl_multi_info_read(_multiHandle, &msgs_left)))
{
if (msg->msg == CURLMSG_DONE)
{
CURLcode rc;
easyHandle = msg->easy_handle;
res = msg->data.result;
/* Get the NSURLSessionTask instance */
rc = curl_easy_getinfo(easyHandle, CURLINFO_PRIVATE, &task);
if (CURLE_OK != rc)
{
NSDebugMLLog(GS_NSURLSESSION_DEBUG_KEY,
@"Failed to retrieve task from easy handle %p using "
@"CURLINFO_PRIVATE",
easyHandle);
}
rc = curl_easy_getinfo(easyHandle, CURLINFO_EFFECTIVE_URL, &eff_url);
if (CURLE_OK != rc)
{
NSDebugMLLog(
GS_NSURLSESSION_DEBUG_KEY,
@"Failed to retrieve effective URL from easy handle %p using "
@"CURLINFO_PRIVATE",
easyHandle);
}
NSDebugMLLog(GS_NSURLSESSION_DEBUG_KEY,
@"Transfer finished for Task %@ with effective url %s "
@"and CURLcode: %s",
task, eff_url, curl_easy_strerror(res));
curl_multi_remove_handle(_multiHandle, easyHandle);
/* This session might be released in _transferFinishedWithCode. Better
* retain it first. */
RETAIN(self);
RETAIN(task);
[_tasks removeObject:task];
[task _transferFinishedWithCode:res];
RELEASE(task);
/* Send URLSession:didBecomeInvalidWithError: to delegate if this
* session was invalidated */
if (_invalidated && [_tasks count] == 0 &&
[_delegate respondsToSelector:@selector(URLSession:
didBecomeInvalidWithError:)])
{
[_delegateQueue addOperationWithBlock:^{
/* We only support explicit Invalidation for now. Error is set
* to nil in this case. */
[_delegate URLSession:self didBecomeInvalidWithError:nil];
}];
}
RELEASE(self);
}
}
}
/* Adds task to _tasks and updates the delegate */
- (void)_didCreateTask:(NSURLSessionTask *)task
{
dispatch_async(_workQueue, ^{
[_tasks addObject:task];
});
if ([_delegate respondsToSelector:@selector(URLSession:didCreateTask:)])
{
[_delegateQueue addOperationWithBlock:^{
[(id<NSURLSessionTaskDelegate>) _delegate URLSession:self
didCreateTask:task];
}];
}
}
#pragma mark - Public API
- (void)finishTasksAndInvalidate
{
if (_isSharedSession)
{
return;
}
dispatch_async(_workQueue, ^{
_invalidated = YES;
});
}
- (void)invalidateAndCancel
{
if (_isSharedSession)
{
return;
}
dispatch_async(_workQueue, ^{
_invalidated = YES;
/* Cancel all tasks */
for (NSURLSessionTask *task in _tasks)
{
[task cancel];
}
});
}
- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request
{
NSURLSessionDataTask *task;
NSInteger identifier;
identifier = [self _nextTaskIdentifier];
task = [[NSURLSessionDataTask alloc] initWithSession:self
request:request
taskIdentifier:identifier];
/* We use the session delegate by default. NSURLSessionTaskDelegate
* is a purely optional protocol.
*/
[task setDelegate:(id<NSURLSessionTaskDelegate>) _delegate];
[task _setProperties:GSURLSessionUpdatesDelegate];
[self _didCreateTask:task];
return AUTORELEASE(task);
}
- (NSURLSessionDataTask *)dataTaskWithURL:(NSURL *)url
{
NSURLRequest *request;
request = [NSURLRequest requestWithURL:url];
return [self dataTaskWithRequest:request];
}
- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request
fromFile:(NSURL *)fileURL
{
NSURLSessionUploadTask *task;
NSInputStream *stream;
NSInteger identifier;
identifier = [self _nextTaskIdentifier];
stream = [NSInputStream inputStreamWithURL:fileURL];
task = [[NSURLSessionUploadTask alloc] initWithSession:self
request:request
taskIdentifier:identifier];
/* We use the session delegate by default. NSURLSessionTaskDelegate
* is a purely optional protocol.
*/
[task setDelegate:(id<NSURLSessionTaskDelegate>) _delegate];
[task
_setProperties:GSURLSessionUpdatesDelegate | GSURLSessionHasInputStream];
[task _setBodyStream:stream];
[task _enableUploadWithSize:0];
[self _didCreateTask:task];
return AUTORELEASE(task);
}
- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request
fromData:(NSData *)bodyData
{
NSURLSessionUploadTask *task;
NSInteger identifier;
identifier = [self _nextTaskIdentifier];
task = [[NSURLSessionUploadTask alloc] initWithSession:self
request:request
taskIdentifier:identifier];
/* We use the session delegate by default. NSURLSessionTaskDelegate
* is a purely optional protocol.
*/
[task setDelegate:(id<NSURLSessionTaskDelegate>) _delegate];
[task _setProperties:GSURLSessionUpdatesDelegate];
[task _enableUploadWithData:bodyData];
[self _didCreateTask:task];
return AUTORELEASE(task);
}
- (NSURLSessionUploadTask *)uploadTaskWithStreamedRequest:
(NSURLRequest *)request
{
NSURLSessionUploadTask *task;
NSInteger identifier;
identifier = [self _nextTaskIdentifier];
task = [[NSURLSessionUploadTask alloc] initWithSession:self
request:request
taskIdentifier:identifier];
/* We use the session delegate by default. NSURLSessionTaskDelegate
* is a purely optional protocol.
*/
[task setDelegate:(id<NSURLSessionTaskDelegate>) _delegate];
[task
_setProperties:GSURLSessionUpdatesDelegate | GSURLSessionHasInputStream];
[task _enableUploadWithSize:0];
[self _didCreateTask:task];
return AUTORELEASE(task);
}
- (NSURLSessionDownloadTask *)downloadTaskWithRequest:(NSURLRequest *)request
{
NSURLSessionDownloadTask *task;
NSInteger identifier;
identifier = [self _nextTaskIdentifier];
task = [[NSURLSessionDownloadTask alloc] initWithSession:self
request:request
taskIdentifier:identifier];
/* We use the session delegate by default. NSURLSessionTaskDelegate
* is a purely optional protocol.
*/
[task setDelegate:(id<NSURLSessionTaskDelegate>) _delegate];
[task
_setProperties:GSURLSessionWritesDataToFile | GSURLSessionUpdatesDelegate];
[self _didCreateTask:task];
return AUTORELEASE(task);
}
- (NSURLSessionDownloadTask *)downloadTaskWithURL:(NSURL *)url
{
NSURLRequest *request;
request = [NSURLRequest requestWithURL:url];
return [self downloadTaskWithRequest:request];
}
- (NSURLSessionDownloadTask *)downloadTaskWithResumeData:(NSData *)resumeData
{
return [self notImplemented:_cmd];
}
- (void)getTasksWithCompletionHandler:
(void (^)(NSArray<NSURLSessionDataTask *> *dataTasks,
NSArray<NSURLSessionUploadTask *> *uploadTasks,
NSArray<NSURLSessionDownloadTask *> *downloadTasks))
completionHandler
{
dispatch_async(_workQueue, ^{
NSMutableArray<NSURLSessionDataTask *> *dataTasks;
NSMutableArray<NSURLSessionUploadTask *> *uploadTasks;
NSMutableArray<NSURLSessionDownloadTask *> *downloadTasks;
NSInteger numberOfTasks;
Class dataTaskClass;
Class uploadTaskClass;
Class downloadTaskClass;
numberOfTasks = [_tasks count];
dataTasks = [NSMutableArray arrayWithCapacity:numberOfTasks / 2];
uploadTasks = [NSMutableArray arrayWithCapacity:numberOfTasks / 2];
downloadTasks = [NSMutableArray arrayWithCapacity:numberOfTasks / 2];
dataTaskClass = [NSURLSessionDataTask class];
uploadTaskClass = [NSURLSessionUploadTask class];
downloadTaskClass = [NSURLSessionDownloadTask class];
for (NSURLSessionTask *task in _tasks)
{
if ([task isKindOfClass:dataTaskClass])
{
[dataTasks addObject:(NSURLSessionDataTask *) task];
}
else if ([task isKindOfClass:uploadTaskClass])
{
[uploadTasks addObject:(NSURLSessionUploadTask *) task];
}
else if ([task isKindOfClass:downloadTaskClass])
{
[downloadTasks addObject:(NSURLSessionDownloadTask *) task];
}
}
completionHandler(dataTasks, uploadTasks, downloadTasks);
});
}
- (void)getAllTasksWithCompletionHandler:
(void (^)(NSArray<__kindof NSURLSessionTask *> *tasks))completionHandler
{
dispatch_async(_workQueue, ^{
completionHandler(_tasks);
});
}
#pragma mark - Getter and Setter
- (NSOperationQueue *)delegateQueue
{
return _delegateQueue;
}
- (id<NSURLSessionDelegate>)delegate
{
return _delegate;
}
- (NSURLSessionConfiguration *)configuration
{
return AUTORELEASE([_configuration copy]);
}
- (NSString *)sessionDescription
{
return _sessionDescription;
}
- (void)setSessionDescription:(NSString *)sessionDescription
{
ASSIGNCOPY(_sessionDescription, sessionDescription);
}
- (void)dealloc
{
RELEASE(_delegateQueue);
RELEASE(_delegate);
RELEASE(_configuration);
RELEASE(_tasks);
RELEASE(_certificateBlob);
RELEASE(_certificatePath);
curl_multi_cleanup(_multiHandle);
#if defined(HAVE_DISPATCH_CANCEL)
dispatch_cancel(_timer);
#else
dispatch_source_cancel(_timer);
#endif
dispatch_release(_workQueue);
[super dealloc];
}
@end
@implementation
NSURLSession (NSURLSessionAsynchronousConvenience)
- (NSURLSessionDataTask *)
dataTaskWithRequest:(NSURLRequest *)request
completionHandler:(GSNSURLSessionDataCompletionHandler)completionHandler
{
NSURLSessionDataTask *task;
NSInteger identifier;
identifier = [self _nextTaskIdentifier];
task = [[NSURLSessionDataTask alloc] initWithSession:self
request:request
taskIdentifier:identifier];
[task setDelegate:(id<NSURLSessionTaskDelegate>) _delegate];
[task _setCompletionHandler:completionHandler];
[task _enableAutomaticRedirects:YES];
[task _setProperties:GSURLSessionStoresDataInMemory |
GSURLSessionHasCompletionHandler];
[self _didCreateTask:task];
return AUTORELEASE(task);
}
- (NSURLSessionDataTask *)dataTaskWithURL:(NSURL *)url
completionHandler:
(GSNSURLSessionDataCompletionHandler)completionHandler
{
NSURLRequest *request = [NSURLRequest requestWithURL:url];
return [self dataTaskWithRequest:request completionHandler:completionHandler];
}
- (NSURLSessionUploadTask *)
uploadTaskWithRequest:(NSURLRequest *)request
fromFile:(NSURL *)fileURL
completionHandler:(GSNSURLSessionDataCompletionHandler)completionHandler
{
NSURLSessionUploadTask *task;
NSInputStream *stream;
NSInteger identifier;
identifier = [self _nextTaskIdentifier];
stream = [NSInputStream inputStreamWithURL:fileURL];
task = [[NSURLSessionUploadTask alloc] initWithSession:self
request:request
taskIdentifier:identifier];
[task setDelegate:(id<NSURLSessionTaskDelegate>) _delegate];
[task _setProperties:GSURLSessionStoresDataInMemory
| GSURLSessionHasInputStream |
GSURLSessionHasCompletionHandler];
[task _setCompletionHandler:completionHandler];
[task _enableAutomaticRedirects:YES];
[task _setBodyStream:stream];
[task _enableUploadWithSize:0];
[self _didCreateTask:task];
return AUTORELEASE(task);
}
- (NSURLSessionUploadTask *)
uploadTaskWithRequest:(NSURLRequest *)request
fromData:(NSData *)bodyData
completionHandler:(GSNSURLSessionDataCompletionHandler)completionHandler
{
NSURLSessionUploadTask *task;
NSInteger identifier;
identifier = [self _nextTaskIdentifier];
task = [[NSURLSessionUploadTask alloc] initWithSession:self
request:request
taskIdentifier:identifier];
[task setDelegate:(id<NSURLSessionTaskDelegate>) _delegate];
[task _setProperties:GSURLSessionStoresDataInMemory |
GSURLSessionHasCompletionHandler];
[task _setCompletionHandler:completionHandler];
[task _enableAutomaticRedirects:YES];
[task _enableUploadWithData:bodyData];
[self _didCreateTask:task];
return AUTORELEASE(task);
}
- (NSURLSessionDownloadTask *)downloadTaskWithRequest:(NSURLRequest *)request
completionHandler:
(GSNSURLSessionDownloadCompletionHandler)
completionHandler
{
NSURLSessionDownloadTask *task;
NSInteger identifier;
identifier = [self _nextTaskIdentifier];
task = [[NSURLSessionDownloadTask alloc] initWithSession:self
request:request
taskIdentifier:identifier];
[task setDelegate:(id<NSURLSessionTaskDelegate>) _delegate];
[task _setProperties:GSURLSessionWritesDataToFile |
GSURLSessionHasCompletionHandler];
[task _enableAutomaticRedirects:YES];
[task _setCompletionHandler:completionHandler];
[self _didCreateTask:task];
return AUTORELEASE(task);
}
- (NSURLSessionDownloadTask *)
downloadTaskWithURL:(NSURL *)url
completionHandler:(GSNSURLSessionDownloadCompletionHandler)completionHandler
{
NSURLRequest *request = [NSURLRequest requestWithURL:url];
return [self downloadTaskWithRequest:request
completionHandler:completionHandler];
}
- (NSURLSessionDownloadTask *)
downloadTaskWithResumeData:(NSData *)resumeData
completionHandler:
(GSNSURLSessionDownloadCompletionHandler)completionHandler
{
return [self notImplemented:_cmd];
}
@end