mirror of
https://github.com/gnustep/libs-base.git
synced 2025-04-23 17:10:48 +00:00
Revert "NSURLSession Reimplementation (#411)"
This reverts commit 07233534e6
.
This commit is contained in:
parent
07233534e6
commit
3fedf31c2d
50 changed files with 7197 additions and 5391 deletions
|
@ -65,6 +65,7 @@ SortIncludes: false
|
|||
SpaceAfterCStyleCast: true
|
||||
SpaceBeforeParens: ControlStatements
|
||||
SpacesBeforeTrailingComments: 1
|
||||
UseTab: Always
|
||||
AlignEscapedNewlines: Right
|
||||
AlignTrailingComments: true
|
||||
AllowShortFunctionsOnASingleLine: All
|
||||
|
|
6
.gitignore
vendored
6
.gitignore
vendored
|
@ -83,12 +83,6 @@ DerivedData/
|
|||
### Xcode Patch ###
|
||||
**/xcshareddata/WorkspaceSettings.xcsettings
|
||||
|
||||
# clangd cache
|
||||
.cache
|
||||
|
||||
# compile commands
|
||||
compile_commands.json
|
||||
|
||||
# End of https://www.gitignore.io/api/xcode
|
||||
|
||||
.cache
|
||||
|
|
53
ChangeLog
53
ChangeLog
|
@ -1,56 +1,3 @@
|
|||
2024-07-02 Hugo Melder <hugo@algoriddim.com>
|
||||
|
||||
* Source/GSEasyHandle.h:
|
||||
* Source/GSEasyHandle.m:
|
||||
* Source/GSHTTPURLProtocol.h:
|
||||
* Source/GSHTTPURLProtocol.m:
|
||||
* Source/GSMultiHandle.h:
|
||||
* Source/GSMultiHandle.m:
|
||||
* Source/GSNativeProtocol.h:
|
||||
* Source/GSNativeProtocol.m:
|
||||
* Source/GSTaskRegistry.h:
|
||||
* Source/GSTaskRegistry.m:
|
||||
* Source/GSTimeoutSource.h:
|
||||
* Source/GSTimeoutSource.m:
|
||||
* Source/GSTransferState.h:
|
||||
* Source/GSTransferState.m:
|
||||
* Source/GSURLPrivate.h:
|
||||
* Source/GSURLSessionTaskBody.h:
|
||||
* Source/GSURLSessionTaskBody.m:
|
||||
* Source/GSURLSessionTaskBodySource.h:
|
||||
* Source/GSURLSessionTaskBodySource.m:
|
||||
Deleted old NSURLSession implementation.
|
||||
|
||||
* Headers/Foundation/NSURLSession.h:
|
||||
* Source/NSURLSessionPrivate.h:
|
||||
* Source/NSURLSession.m:
|
||||
* Source/NSURLSessionConfiguration.m:
|
||||
* Source/NSURLSessionTaskPrivate.h:
|
||||
* Source/NSURLSessionTask.m:
|
||||
* Source/NSURLSessionConfiguration.m:
|
||||
The new NSURLSession implementation was reduced to just two main classes:
|
||||
NSURLSession and NSURLSessionTask. URLSession manages a libcurl multi
|
||||
handle and the required plumming via libdispatch. Timeout and socket actions
|
||||
are handled in the libdispatch workqueue. Delegate messages and completion
|
||||
handlers are submitted to a seperate serial NSOperationQueue.
|
||||
|
||||
* Headers/Foundation/NSUserDefaults.h:
|
||||
* Source/externs.m:
|
||||
Added GSCACertificateFilePath for custom CA certificate file configuration.
|
||||
|
||||
* Source/NSHTTPCookie.m:
|
||||
Reworked cookie expiration date parsing.
|
||||
|
||||
* Source/NSOperation.m:
|
||||
Clear all blocks in _executionBlocks after completion.
|
||||
|
||||
* Headers/Foundation/NSURLRequest.h:
|
||||
* Source/NSURLRequest.m:
|
||||
Implemented -[NSURLRequest assumesHTTP3Capable] and -[NSURLRequest
|
||||
setAssumesHTTP3Capable:] respectively. The internal case-insensitive header
|
||||
dictionary is now exposed for private usage with -[NSURLRequest
|
||||
_insensitiveHeaders].
|
||||
|
||||
2024-06-21 Richard Frith-Macdonald <rfm@gnu.org>
|
||||
|
||||
* Source/NSDistantObject.m: Use standard method name conventions
|
||||
|
|
|
@ -271,16 +271,6 @@ GS_EXPORT_CLASS
|
|||
*/
|
||||
- (NSString *) valueForHTTPHeaderField: (NSString *)field;
|
||||
|
||||
#if OS_API_VERSION(MAC_OS_VERSION_11_0, GS_API_LATEST)
|
||||
/**
|
||||
* Indicates whether the URL loading system assumes the host is HTTP/3 capable.
|
||||
*
|
||||
* This method returns the current assumption of the URL loading system regarding
|
||||
* the server's HTTP capabilities.
|
||||
*/
|
||||
- (BOOL) assumesHTTP3Capable;
|
||||
#endif
|
||||
|
||||
@end
|
||||
|
||||
|
||||
|
@ -337,17 +327,6 @@ GS_EXPORT_CLASS
|
|||
*/
|
||||
- (void) setValue: (NSString *)value forHTTPHeaderField: (NSString *)field;
|
||||
|
||||
#if OS_API_VERSION(MAC_OS_VERSION_11_0, GS_API_LATEST)
|
||||
/**
|
||||
* Sets whether the URL loading system should assume the host is HTTP/3 capable.
|
||||
*
|
||||
* This method configures the URL loading system's assumptions about the
|
||||
* server's HTTP capabilities, optimizing the connection process if HTTP/3 is
|
||||
* supported.
|
||||
*/
|
||||
- (void) setAssumesHTTP3Capable: (BOOL)capable;
|
||||
#endif
|
||||
|
||||
@end
|
||||
|
||||
@protocol GSLogDelegate;
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
/**
|
||||
NSURLSession.h
|
||||
|
||||
Copyright (C) 2017-2024 Free Software Foundation, Inc.
|
||||
Copyright (C) 2017-2023 Free Software Foundation, Inc.
|
||||
|
||||
Written by: Hugo Melder <hugo@algoriddim.com>
|
||||
Date: May 2024
|
||||
Written by: Daniel Ferreira <dtf@stanford.edu>
|
||||
Date: November 2017
|
||||
Author: Richard Frith-Macdonald <richard@brainstorm.co.uk>
|
||||
|
||||
This file is part of GNUStep-base
|
||||
|
||||
|
@ -33,18 +34,14 @@
|
|||
#import <Foundation/NSObject.h>
|
||||
#import <Foundation/NSURLRequest.h>
|
||||
#import <Foundation/NSHTTPCookieStorage.h>
|
||||
#import <Foundation/NSArray.h>
|
||||
#import <Foundation/NSDictionary.h>
|
||||
#import <Foundation/NSOperation.h>
|
||||
#import <Foundation/NSProgress.h>
|
||||
#import <Foundation/NSDate.h>
|
||||
|
||||
#if GS_HAVE_NSURLSESSION
|
||||
#if OS_API_VERSION(MAC_OS_X_VERSION_10_9, GS_API_LATEST)
|
||||
|
||||
#if OS_API_VERSION(MAC_OS_X_VERSION_10_9,GS_API_LATEST)
|
||||
@protocol NSURLSessionDelegate;
|
||||
@protocol NSURLSessionTaskDelegate;
|
||||
|
||||
@class GSMultiHandle;
|
||||
@class GSURLSessionTaskBody;
|
||||
@class NSError;
|
||||
@class NSHTTPURLResponse;
|
||||
@class NSOperationQueue;
|
||||
|
@ -61,15 +58,6 @@
|
|||
@class NSURLSessionUploadTask;
|
||||
@class NSURLSessionDownloadTask;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
typedef void (^GSNSURLSessionDataCompletionHandler)(
|
||||
NSData *_Nullable data, NSURLResponse *_Nullable response,
|
||||
NSError *_Nullable error);
|
||||
|
||||
typedef void (^GSNSURLSessionDownloadCompletionHandler)(
|
||||
NSURL *_Nullable location, NSURLResponse *_Nullable response,
|
||||
NSError *_Nullable error);
|
||||
|
||||
/**
|
||||
* NSURLSession is a replacement API for NSURLConnection. It provides
|
||||
|
@ -79,18 +67,18 @@ typedef void (^GSNSURLSessionDownloadCompletionHandler)(
|
|||
*
|
||||
* An NSURLSession may be bound to a delegate object. The delegate is
|
||||
* invoked for certain events during the lifetime of a session.
|
||||
*
|
||||
*
|
||||
* NSURLSession instances are threadsafe.
|
||||
*
|
||||
* An NSURLSession creates NSURLSessionTask objects which represent the
|
||||
* action of a resource being loaded.
|
||||
*
|
||||
*
|
||||
* NSURLSessionTask objects are always created in a suspended state and
|
||||
* must be sent the -resume message before they will execute.
|
||||
*
|
||||
* Subclasses of NSURLSessionTask are used to syntactically
|
||||
* differentiate between data and file downloads.
|
||||
*
|
||||
*
|
||||
* An NSURLSessionDataTask receives the resource as a series of calls to
|
||||
* the URLSession:dataTask:didReceiveData: delegate method. This is type of
|
||||
* task most commonly associated with retrieving objects for immediate parsing
|
||||
|
@ -99,156 +87,125 @@ typedef void (^GSNSURLSessionDownloadCompletionHandler)(
|
|||
GS_EXPORT_CLASS
|
||||
@interface NSURLSession : NSObject
|
||||
{
|
||||
@private
|
||||
NSOperationQueue *_delegateQueue;
|
||||
id<NSURLSessionDelegate> _delegate;
|
||||
NSURLSessionConfiguration *_configuration;
|
||||
|
||||
NSString *_sessionDescription;
|
||||
NSOperationQueue *_delegateQueue;
|
||||
id <NSURLSessionDelegate> _delegate;
|
||||
NSURLSessionConfiguration *_configuration;
|
||||
NSString *_sessionDescription;
|
||||
GSMultiHandle *_multiHandle;
|
||||
}
|
||||
|
||||
+ (NSURLSession *)sharedSession;
|
||||
+ (NSURLSession*) sharedSession;
|
||||
|
||||
+ (NSURLSession *)sessionWithConfiguration:
|
||||
(NSURLSessionConfiguration *)configuration;
|
||||
+ (NSURLSession*) sessionWithConfiguration: (NSURLSessionConfiguration*)configuration;
|
||||
|
||||
/*
|
||||
* Customization of NSURLSession occurs during creation of a new session.
|
||||
* If you do specify a delegate, the delegate will be retained until after
|
||||
* the delegate has been sent the URLSession:didBecomeInvalidWithError: message.
|
||||
*/
|
||||
+ (NSURLSession *)
|
||||
sessionWithConfiguration:(NSURLSessionConfiguration *)configuration
|
||||
delegate:(nullable id<NSURLSessionDelegate>)delegate
|
||||
delegateQueue:(nullable NSOperationQueue *)queue;
|
||||
+ (NSURLSession*) sessionWithConfiguration: (NSURLSessionConfiguration*)configuration
|
||||
delegate: (id <NSURLSessionDelegate>)delegate
|
||||
delegateQueue: (NSOperationQueue*)queue;
|
||||
|
||||
/* -finishTasksAndInvalidate returns immediately and existing tasks will be
|
||||
- (NSOperationQueue*) delegateQueue;
|
||||
|
||||
- (id <NSURLSessionDelegate>) delegate;
|
||||
|
||||
- (NSURLSessionConfiguration*) configuration;
|
||||
|
||||
- (NSString*) sessionDescription;
|
||||
|
||||
- (void) setSessionDescription: (NSString*)sessionDescription;
|
||||
|
||||
/* -finishTasksAndInvalidate returns immediately and existing tasks will be
|
||||
* allowed to run to completion. New tasks may not be created. The session
|
||||
* will continue to make delegate callbacks until
|
||||
* URLSession:didBecomeInvalidWithError: has been issued.
|
||||
* will continue to make delegate callbacks until
|
||||
* URLSession:didBecomeInvalidWithError: has been issued.
|
||||
*
|
||||
* When invalidating a background session, it is not safe to create another
|
||||
* background session with the same identifier until
|
||||
* When invalidating a background session, it is not safe to create another
|
||||
* background session with the same identifier until
|
||||
* URLSession:didBecomeInvalidWithError: has been issued.
|
||||
*/
|
||||
- (void)finishTasksAndInvalidate;
|
||||
- (void) finishTasksAndInvalidate;
|
||||
|
||||
/* -invalidateAndCancel acts as -finishTasksAndInvalidate, but issues
|
||||
* -cancel to all outstanding tasks for this session. Note task
|
||||
* -cancel to all outstanding tasks for this session. Note task
|
||||
* cancellation is subject to the state of the task, and some tasks may
|
||||
* have already have completed at the time they are sent -cancel.
|
||||
* have already have completed at the time they are sent -cancel.
|
||||
*/
|
||||
- (void)invalidateAndCancel;
|
||||
- (void) invalidateAndCancel;
|
||||
|
||||
/*
|
||||
/*
|
||||
* NSURLSessionTask objects are always created in a suspended state and
|
||||
* must be sent the -resume message before they will execute.
|
||||
*/
|
||||
|
||||
/** Creates a data task with the given request.
|
||||
/* Creates a data task with the given request.
|
||||
* The request may have a body stream. */
|
||||
- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request;
|
||||
- (NSURLSessionDataTask*) dataTaskWithRequest: (NSURLRequest*)request;
|
||||
|
||||
/** Creates a data task to retrieve the contents of the given URL. */
|
||||
- (NSURLSessionDataTask *)dataTaskWithURL:(NSURL *)url;
|
||||
/* Creates a data task to retrieve the contents of the given URL. */
|
||||
- (NSURLSessionDataTask*) dataTaskWithURL: (NSURL*)url;
|
||||
|
||||
- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request
|
||||
fromFile:(NSURL *)fileURL;
|
||||
/* Not implemented */
|
||||
- (NSURLSessionUploadTask*) uploadTaskWithRequest: (NSURLRequest*)request
|
||||
fromFile: (NSURL*)fileURL;
|
||||
|
||||
- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request
|
||||
fromData:(NSData *)bodyData;
|
||||
/* Not implemented */
|
||||
- (NSURLSessionUploadTask*) uploadTaskWithRequest: (NSURLRequest*)request
|
||||
fromData: (NSData*)bodyData;
|
||||
|
||||
- (NSURLSessionUploadTask *)uploadTaskWithStreamedRequest:
|
||||
(NSURLRequest *)request;
|
||||
/* Not implemented */
|
||||
- (NSURLSessionUploadTask*) uploadTaskWithStreamedRequest: (NSURLRequest*)request;
|
||||
|
||||
/* Creates a download task with the given request. */
|
||||
- (NSURLSessionDownloadTask *)downloadTaskWithRequest:(NSURLRequest *)request;
|
||||
- (NSURLSessionDownloadTask*) downloadTaskWithRequest: (NSURLRequest*)request;
|
||||
|
||||
/* Creates a download task to download the contents of the given URL. */
|
||||
- (NSURLSessionDownloadTask *)downloadTaskWithURL:(NSURL *)url;
|
||||
- (NSURLSessionDownloadTask*) downloadTaskWithURL: (NSURL*)url;
|
||||
|
||||
- (NSURLSessionDownloadTask *)downloadTaskWithResumeData:(NSData *)resumeData;
|
||||
/* Not implemented */
|
||||
- (NSURLSessionDownloadTask *) downloadTaskWithResumeData: (NSData *)resumeData;
|
||||
|
||||
- (void)getTasksWithCompletionHandler:
|
||||
(void (^)(NSArray<NSURLSessionDataTask *> *dataTasks,
|
||||
NSArray<NSURLSessionUploadTask *> *uploadTasks,
|
||||
NSArray<NSURLSessionDownloadTask *> *downloadTasks))
|
||||
completionHandler;
|
||||
- (void) getTasksWithCompletionHandler: (void (^)(GS_GENERIC_CLASS(NSArray, NSURLSessionDataTask*) *dataTasks, GS_GENERIC_CLASS(NSArray, NSURLSessionUploadTask*) *uploadTasks, GS_GENERIC_CLASS(NSArray, NSURLSessionDownloadTask*) *downloadTasks))completionHandler;
|
||||
|
||||
- (void)getAllTasksWithCompletionHandler:
|
||||
(void (^)(GS_GENERIC_CLASS(NSArray, __kindof NSURLSessionTask *) * tasks))
|
||||
completionHandler;
|
||||
|
||||
/**
|
||||
* This serial NSOperationQueue queue is used for dispatching delegate messages
|
||||
* and completion handlers.
|
||||
*/
|
||||
- (NSOperationQueue *)delegateQueue;
|
||||
|
||||
/**
|
||||
* The delegate for the session. This is the object to which delegate messages
|
||||
* will be sent.
|
||||
*
|
||||
* The session keeps a strong reference to the delegate.
|
||||
*/
|
||||
- (nullable id<NSURLSessionDelegate>)delegate;
|
||||
|
||||
/**
|
||||
* The configuration object used to create the session.
|
||||
*
|
||||
* A copy of the configuration object is made.
|
||||
* Changes to the configuration object after the session is created have no
|
||||
* effect.
|
||||
*/
|
||||
- (NSURLSessionConfiguration *)configuration;
|
||||
|
||||
/**
|
||||
* An App-specific description of the session.
|
||||
*/
|
||||
- (nullable NSString *)sessionDescription;
|
||||
|
||||
/**
|
||||
* Sets an app-specific description of the session.
|
||||
*/
|
||||
- (void)setSessionDescription:(NSString *)description;
|
||||
- (void) getAllTasksWithCompletionHandler: (void (^)(GS_GENERIC_CLASS(NSArray, __kindof NSURLSessionTask*) *tasks))completionHandler;
|
||||
|
||||
@end
|
||||
|
||||
/*
|
||||
* NSURLSession convenience routines deliver results to
|
||||
* NSURLSession convenience routines deliver results to
|
||||
* a completion handler block. These convenience routines
|
||||
* are not available to NSURLSessions that are configured
|
||||
* as background sessions.
|
||||
*
|
||||
* Task objects are always created in a suspended state and
|
||||
* Task objects are always created in a suspended state and
|
||||
* must be sent the -resume message before they will execute.
|
||||
*/
|
||||
@interface
|
||||
NSURLSession (NSURLSessionAsynchronousConvenience)
|
||||
@interface NSURLSession (NSURLSessionAsynchronousConvenience)
|
||||
/*
|
||||
* data task convenience methods. These methods create tasks that
|
||||
* bypass the normal delegate calls for response and data delivery,
|
||||
* and provide a simple cancelable asynchronous interface to receiving
|
||||
* data. Errors will be returned in the NSURLErrorDomain,
|
||||
* data. Errors will be returned in the NSURLErrorDomain,
|
||||
* see <Foundation/NSURLError.h>. The delegate, if any, will still be
|
||||
* called for authentication challenges.
|
||||
*/
|
||||
- (NSURLSessionDataTask *)
|
||||
dataTaskWithRequest:(NSURLRequest *)request
|
||||
completionHandler:(GSNSURLSessionDataCompletionHandler)completionHandler;
|
||||
- (NSURLSessionDataTask *)dataTaskWithURL:(NSURL *)url
|
||||
completionHandler:(GSNSURLSessionDataCompletionHandler)
|
||||
completionHandler;
|
||||
- (NSURLSessionDataTask*) dataTaskWithRequest: (NSURLRequest*)request
|
||||
completionHandler: (void (^)(NSData *data, NSURLResponse *response, NSError *error))completionHandler;
|
||||
- (NSURLSessionDataTask*) dataTaskWithURL: (NSURL*)url
|
||||
completionHandler: (void (^)(NSData *data, NSURLResponse *response, NSError *error))completionHandler;
|
||||
|
||||
- (NSURLSessionUploadTask *)
|
||||
uploadTaskWithRequest:(NSURLRequest *)request
|
||||
fromFile:(NSURL *)fileURL
|
||||
completionHandler:(GSNSURLSessionDataCompletionHandler)completionHandler;
|
||||
|
||||
- (NSURLSessionUploadTask *)
|
||||
uploadTaskWithRequest:(NSURLRequest *)request
|
||||
fromData:(NSData *)bodyData
|
||||
completionHandler:(GSNSURLSessionDataCompletionHandler)completionHandler;
|
||||
/* Not implemented */
|
||||
- (NSURLSessionUploadTask*) uploadTaskWithRequest: (NSURLRequest*)request
|
||||
fromFile: (NSURL*)fileURL
|
||||
completionHandler: (void (^)(NSData *data, NSURLResponse *response, NSError *error))completionHandler;
|
||||
|
||||
/* Not implemented */
|
||||
- (NSURLSessionUploadTask*) uploadTaskWithRequest: (NSURLRequest*)request
|
||||
fromData: (NSData*)bodyData
|
||||
completionHandler: (void (^)(NSData *data, NSURLResponse *response, NSError *error))completionHandler;
|
||||
|
||||
/*
|
||||
* download task convenience methods. When a download successfully
|
||||
|
@ -256,32 +213,27 @@ NSURLSession (NSURLSessionAsynchronousConvenience)
|
|||
* copied during the invocation of the completion routine. The file
|
||||
* will be removed automatically.
|
||||
*/
|
||||
- (NSURLSessionDownloadTask *)downloadTaskWithRequest:(NSURLRequest *)request
|
||||
completionHandler:
|
||||
(GSNSURLSessionDownloadCompletionHandler)
|
||||
completionHandler;
|
||||
- (NSURLSessionDownloadTask *)downloadTaskWithURL:(NSURL *)url
|
||||
completionHandler:
|
||||
(GSNSURLSessionDownloadCompletionHandler)
|
||||
completionHandler;
|
||||
- (NSURLSessionDownloadTask*) downloadTaskWithRequest: (NSURLRequest*)request
|
||||
completionHandler: (void (^)(NSURL *location, NSURLResponse *response, NSError *error))completionHandler;
|
||||
- (NSURLSessionDownloadTask*) downloadTaskWithURL: (NSURL *)url
|
||||
completionHandler: (void (^)(NSURL *location, NSURLResponse *response, NSError *error))completionHandler;
|
||||
|
||||
- (NSURLSessionDownloadTask *)
|
||||
downloadTaskWithResumeData:(NSData *)resumeData
|
||||
completionHandler:
|
||||
(GSNSURLSessionDownloadCompletionHandler)completionHandler;
|
||||
/* Not implemented */
|
||||
- (NSURLSessionDownloadTask*) downloadTaskWithResumeData: (NSData*)resumeData
|
||||
completionHandler: (void (^)(NSURL *location, NSURLResponse *response, NSError *error))completionHandler;
|
||||
|
||||
@end
|
||||
|
||||
typedef NS_ENUM(NSUInteger, NSURLSessionTaskState) {
|
||||
/* The task is currently being serviced by the session */
|
||||
NSURLSessionTaskStateRunning = 0,
|
||||
NSURLSessionTaskStateRunning = 0,
|
||||
NSURLSessionTaskStateSuspended = 1,
|
||||
/* The task has been told to cancel.
|
||||
/* The task has been told to cancel.
|
||||
* The session will receive URLSession:task:didCompleteWithError:. */
|
||||
NSURLSessionTaskStateCanceling = 2,
|
||||
/* The task has completed and the session will receive no more
|
||||
NSURLSessionTaskStateCanceling = 2,
|
||||
/* The task has completed and the session will receive no more
|
||||
* delegate notifications */
|
||||
NSURLSessionTaskStateCompleted = 3,
|
||||
NSURLSessionTaskStateCompleted = 3,
|
||||
};
|
||||
|
||||
GS_EXPORT const float NSURLSessionTaskPriorityDefault;
|
||||
|
@ -295,81 +247,125 @@ GS_EXPORT const int64_t NSURLSessionTransferSizeUnknown;
|
|||
* of processing a given request.
|
||||
*/
|
||||
GS_EXPORT_CLASS
|
||||
@interface NSURLSessionTask : NSObject <NSCopying, NSProgressReporting>
|
||||
@interface NSURLSessionTask : NSObject <NSCopying>
|
||||
{
|
||||
/** An identifier for this task, assigned by and unique
|
||||
* to the owning session
|
||||
*/
|
||||
NSUInteger _taskIdentifier;
|
||||
NSURLRequest *_originalRequest;
|
||||
|
||||
id<NSURLSessionTaskDelegate> _delegate;
|
||||
NSURLSessionTaskState _state;
|
||||
NSURLRequest *_currentRequest;
|
||||
NSURLResponse *_response;
|
||||
NSProgress *_progress;
|
||||
NSDate *_earliestBeginDate;
|
||||
/** The request this task was created to handle.
|
||||
*/
|
||||
NSURLRequest *_originalRequest;
|
||||
|
||||
_Atomic(int64_t) _countOfBytesClientExpectsToSend;
|
||||
_Atomic(int64_t) _countOfBytesClientExpectsToReceive;
|
||||
_Atomic(int64_t) _countOfBytesSent;
|
||||
_Atomic(int64_t) _countOfBytesReceived;
|
||||
_Atomic(int64_t) _countOfBytesExpectedToSend;
|
||||
_Atomic(int64_t) _countOfBytesExpectedToReceive;
|
||||
_Atomic(double) _priority;
|
||||
/** The request this task is currently handling. This may differ from
|
||||
* originalRequest due to http server redirection
|
||||
*/
|
||||
NSURLRequest *_currentRequest;
|
||||
|
||||
NSString *_taskDescription;
|
||||
NSError *_error;
|
||||
/** The response to the current request, which may be nil if no response
|
||||
* has been received
|
||||
*/
|
||||
NSURLResponse *_response;
|
||||
|
||||
/** number of body bytes already received
|
||||
*/
|
||||
int64_t _countOfBytesReceived;
|
||||
|
||||
/** number of body bytes already sent
|
||||
*/
|
||||
int64_t _countOfBytesSent;
|
||||
|
||||
/** number of body bytes we expect to send, derived from
|
||||
* the Content-Length of the HTTP request
|
||||
*/
|
||||
int64_t _countOfBytesExpectedToSend;
|
||||
|
||||
/** number of byte bytes we expect to receive, usually derived from the
|
||||
* Content-Length header of an HTTP response.
|
||||
*/
|
||||
int64_t _countOfBytesExpectedToReceive;
|
||||
|
||||
/** a description of the current task for diagnostic purposes
|
||||
*/
|
||||
NSString *_taskDescription;
|
||||
|
||||
/** The current state of the task within the session.
|
||||
*/
|
||||
NSURLSessionTaskState _state;
|
||||
|
||||
/** The error, if any, delivered via -URLSession:task:didCompleteWithError:
|
||||
* This is nil until an error has occured.
|
||||
*/
|
||||
NSError *_error;
|
||||
|
||||
/** The dispatch queue used to handle this request/response.
|
||||
* This is actualy a libdispatch queue of type dispatch_queue_t, but on all
|
||||
* known implementations this is a pointer, so void* is the correct size.
|
||||
*/
|
||||
void *_workQueue;
|
||||
|
||||
NSUInteger _suspendCount;
|
||||
|
||||
GSURLSessionTaskBody *_knownBody;
|
||||
|
||||
void (^_dataCompletionHandler)(NSData *data, NSURLResponse *response, NSError *error);
|
||||
void (^_downloadCompletionHandler)(NSURL *location, NSURLResponse *response, NSError *error);
|
||||
}
|
||||
|
||||
- (NSUInteger)taskIdentifier;
|
||||
- (NSUInteger) taskIdentifier;
|
||||
|
||||
- (nullable NSURLRequest *)originalRequest;
|
||||
- (nullable NSURLRequest *)currentRequest;
|
||||
- (nullable NSURLResponse *)response;
|
||||
- (NSURLRequest*) originalRequest;
|
||||
|
||||
- (NSURLSessionTaskState)state;
|
||||
- (NSProgress *)progress;
|
||||
- (nullable NSError *)error;
|
||||
- (NSURLRequest*) currentRequest;
|
||||
|
||||
- (nullable id<NSURLSessionTaskDelegate>)delegate;
|
||||
- (void)setDelegate:(nullable id<NSURLSessionTaskDelegate>)delegate;
|
||||
- (NSURLResponse*) response;
|
||||
- (void) setResponse: (NSURLResponse*)response;
|
||||
|
||||
- (nullable NSDate *)earliestBeginDate;
|
||||
- (void)setEarliestBeginDate:(nullable NSDate *)date;
|
||||
- (int64_t) countOfBytesReceived;
|
||||
|
||||
- (int64_t)countOfBytesClientExpectsToSend;
|
||||
- (int64_t)countOfBytesClientExpectsToReceive;
|
||||
- (int64_t)countOfBytesSent;
|
||||
- (int64_t)countOfBytesReceived;
|
||||
- (int64_t)countOfBytesExpectedToSend;
|
||||
- (int64_t)countOfBytesExpectedToReceive;
|
||||
- (int64_t) countOfBytesSent;
|
||||
|
||||
/**
|
||||
* App-specific description of the task.
|
||||
- (int64_t) countOfBytesExpectedToSend;
|
||||
|
||||
- (int64_t) countOfBytesExpectedToReceive;
|
||||
|
||||
- (NSString*) taskDescription;
|
||||
|
||||
- (void) setTaskDescription: (NSString*)taskDescription;
|
||||
|
||||
- (NSURLSessionTaskState) state;
|
||||
|
||||
- (NSError*) error;
|
||||
|
||||
- (NSURLSession*) session;
|
||||
|
||||
/* -cancel returns immediately, but marks a task as being canceled.
|
||||
* The task will signal -URLSession:task:didCompleteWithError: with an
|
||||
* error value of { NSURLErrorDomain, NSURLErrorCancelled }. In some
|
||||
* cases, the task may signal other work before it acknowledges the
|
||||
* cancelation. -cancel may be sent to a task that has been suspended.
|
||||
*/
|
||||
- (nullable NSString *)taskDescription;
|
||||
- (void) cancel;
|
||||
|
||||
/**
|
||||
* Sets an app-specific description of the task.
|
||||
/*
|
||||
* Suspending a task will prevent the NSURLSession from continuing to
|
||||
* load data. There may still be delegate calls made on behalf of
|
||||
* this task (for instance, to report data received while suspending)
|
||||
* but no further transmissions will be made on behalf of the task
|
||||
* until -resume is sent. The timeout timer associated with the task
|
||||
* will be disabled while a task is suspended.
|
||||
*/
|
||||
- (void)setTaskDescription:(nullable NSString *)description;
|
||||
- (void) suspend;
|
||||
- (void) resume;
|
||||
|
||||
/**
|
||||
* Cancels the task and the ongoing transfer.
|
||||
*/
|
||||
- (void)cancel;
|
||||
|
||||
- (void)suspend;
|
||||
- (void)resume;
|
||||
|
||||
- (float)priority;
|
||||
- (void)setPriority:(float)priority;
|
||||
- (float) priority;
|
||||
- (void) setPriority: (float)priority;
|
||||
|
||||
@end
|
||||
|
||||
GS_EXPORT_CLASS
|
||||
@interface NSURLSessionDataTask : NSURLSessionTask
|
||||
{
|
||||
void *_completionHandler;
|
||||
}
|
||||
@end
|
||||
|
||||
GS_EXPORT_CLASS
|
||||
|
@ -378,13 +374,9 @@ GS_EXPORT_CLASS
|
|||
|
||||
GS_EXPORT_CLASS
|
||||
@interface NSURLSessionDownloadTask : NSURLSessionTask
|
||||
{
|
||||
void *_completionHandler;
|
||||
int64_t _countOfBytesWritten;
|
||||
}
|
||||
@end
|
||||
|
||||
#if OS_API_VERSION(MAC_OS_X_VERSION_10_11, GS_API_LATEST)
|
||||
#if OS_API_VERSION(MAC_OS_X_VERSION_10_11,GS_API_LATEST)
|
||||
GS_EXPORT_CLASS
|
||||
@interface NSURLSessionStreamTask : NSURLSessionTask
|
||||
@end
|
||||
|
@ -398,139 +390,65 @@ GS_EXPORT_CLASS
|
|||
GS_EXPORT_CLASS
|
||||
@interface NSURLSessionConfiguration : NSObject <NSCopying>
|
||||
{
|
||||
NSString *_identifier;
|
||||
NSURLCache *_URLCache;
|
||||
NSString *_identifier;
|
||||
NSURLCache *_URLCache;
|
||||
NSURLRequestCachePolicy _requestCachePolicy;
|
||||
NSArray *_protocolClasses;
|
||||
NSArray *_protocolClasses;
|
||||
NSInteger _HTTPMaximumConnectionLifetime;
|
||||
NSInteger _HTTPMaximumConnectionsPerHost;
|
||||
BOOL _HTTPShouldUsePipelining;
|
||||
NSHTTPCookieAcceptPolicy _HTTPCookieAcceptPolicy;
|
||||
NSHTTPCookieStorage *_HTTPCookieStorage;
|
||||
NSURLCredentialStorage *_URLCredentialStorage;
|
||||
NSHTTPCookieStorage *_HTTPCookieStorage;
|
||||
NSURLCredentialStorage *_URLCredentialStorage;
|
||||
BOOL _HTTPShouldSetCookies;
|
||||
NSDictionary *_HTTPAdditionalHeaders;
|
||||
NSTimeInterval _timeoutIntervalForRequest;
|
||||
NSTimeInterval _timeoutIntervalForResource;
|
||||
NSDictionary *_HTTPAdditionalHeaders;
|
||||
}
|
||||
|
||||
- (NSURLRequest *)configureRequest:(NSURLRequest *)request;
|
||||
- (NSURLRequest*) configureRequest: (NSURLRequest*)request;
|
||||
|
||||
+ (NSURLSessionConfiguration *)defaultSessionConfiguration;
|
||||
+ (NSURLSessionConfiguration *)ephemeralSessionConfiguration;
|
||||
+ (NSURLSessionConfiguration *)backgroundSessionConfigurationWithIdentifier:
|
||||
(NSString *)identifier;
|
||||
+ (NSURLSessionConfiguration*) defaultSessionConfiguration;
|
||||
+ (NSURLSessionConfiguration*) ephemeralSessionConfiguration;
|
||||
+ (NSURLSessionConfiguration*) backgroundSessionConfigurationWithIdentifier:(NSString*)identifier;
|
||||
|
||||
- (nullable NSDictionary *)HTTPAdditionalHeaders;
|
||||
- (void)setHTTPAdditionalHeaders:(NSDictionary *)headers;
|
||||
- (NSDictionary*) HTTPAdditionalHeaders;
|
||||
- (void) setHTTPAdditionalHeaders: (NSDictionary*)headers;
|
||||
|
||||
/**
|
||||
* Gets the timeout interval to use when waiting for additional data to arrive.
|
||||
* The request timeout interval controls how long (in seconds) a task should
|
||||
* wait for additional data to arrive before giving up. The timer is reset
|
||||
* whenever new data arrives. When the request timer reaches the specified
|
||||
* interval without receiving any new data, it triggers a timeout.
|
||||
*
|
||||
* Currently not used by NSURLSession.
|
||||
*/
|
||||
- (NSTimeInterval)timeoutIntervalForRequest;
|
||||
/**
|
||||
* Sets the timeout interval to use when waiting for additional data to arrive.
|
||||
*/
|
||||
- (void)setTimeoutIntervalForRequest:(NSTimeInterval)interval;
|
||||
- (NSHTTPCookieAcceptPolicy) HTTPCookieAcceptPolicy;
|
||||
- (void) setHTTPCookieAcceptPolicy: (NSHTTPCookieAcceptPolicy)policy;
|
||||
|
||||
/**
|
||||
* Gets the maximum amount of time that a resource request should be allowed to
|
||||
* take. The resource timeout interval controls how long (in seconds) to wait
|
||||
* for an entire resource to transfer before giving up. The resource timer
|
||||
* starts when the request is initiated and counts until either the request
|
||||
* completes or this timeout interval is reached, whichever comes first.
|
||||
*/
|
||||
- (NSTimeInterval)timeoutIntervalForResource;
|
||||
/**
|
||||
* Sets the maximum amount of time that a resource request should be allowed to
|
||||
* take.
|
||||
*/
|
||||
- (void)setTimeoutIntervalForResource:(NSTimeInterval)interval;
|
||||
- (NSHTTPCookieStorage*) HTTPCookieStorage;
|
||||
- (void) setHTTPCookieStorage: (NSHTTPCookieStorage*)storage;
|
||||
|
||||
- (NSHTTPCookieAcceptPolicy)HTTPCookieAcceptPolicy;
|
||||
- (void)setHTTPCookieAcceptPolicy:(NSHTTPCookieAcceptPolicy)policy;
|
||||
- (NSInteger) HTTPMaximumConnectionsPerHost;
|
||||
- (void) setHTTPMaximumConnectionsPerHost: (NSInteger)n;
|
||||
|
||||
- (nullable NSHTTPCookieStorage *)HTTPCookieStorage;
|
||||
- (void)setHTTPCookieStorage:(NSHTTPCookieStorage *)storage;
|
||||
- (BOOL) HTTPShouldSetCookies;
|
||||
- (void) setHTTPShouldSetCookies: (BOOL)flag;
|
||||
|
||||
- (NSInteger)HTTPMaximumConnectionsPerHost;
|
||||
- (void)setHTTPMaximumConnectionsPerHost:(NSInteger)n;
|
||||
- (BOOL) HTTPShouldUsePipelining;
|
||||
- (void) setHTTPShouldUsePipelining: (BOOL)flag;
|
||||
|
||||
/**
|
||||
* Indicates whether the session should set cookies.
|
||||
*
|
||||
* This property controls whether tasks within sessions based on this
|
||||
* configuration should automatically include cookies from the shared cookie
|
||||
* store when making requests.
|
||||
*
|
||||
* If set to NO, you must manually provide cookies by adding a Cookie header
|
||||
* through the session's HTTPAdditionalHeaders property or on a per-request
|
||||
* basis using a custom NSURLRequest object.
|
||||
*
|
||||
* The default value is YES.
|
||||
*
|
||||
* See Also:
|
||||
* - HTTPCookieAcceptPolicy
|
||||
* - HTTPCookieStorage
|
||||
* - NSHTTPCookieStorage
|
||||
* - NSHTTPCookie
|
||||
*/
|
||||
- (BOOL)HTTPShouldSetCookies;
|
||||
- (NSString*) identifier;
|
||||
|
||||
/**
|
||||
* Sets whether the session should set cookies.
|
||||
*
|
||||
* This method controls whether tasks within sessions based on this
|
||||
* configuration should automatically include cookies from the shared cookie
|
||||
* store when making requests.
|
||||
*
|
||||
* If set to NO, you must manually provide cookies by adding a Cookie header
|
||||
* through the session's HTTPAdditionalHeaders property or on a per-request
|
||||
* basis using a custom NSURLRequest object.
|
||||
*
|
||||
* The default value is YES.
|
||||
*
|
||||
* See Also:
|
||||
* - HTTPCookieAcceptPolicy
|
||||
* - HTTPCookieStorage
|
||||
* - NSHTTPCookieStorage
|
||||
* - NSHTTPCookie
|
||||
*/
|
||||
- (void)setHTTPShouldSetCookies:(BOOL)flag;
|
||||
- (NSArray*) protocolClasses;
|
||||
|
||||
/**
|
||||
* HTTP/1.1 pipelining is not implemented. This flag is ignored.
|
||||
*/
|
||||
- (BOOL)HTTPShouldUsePipelining;
|
||||
- (void)setHTTPShouldUsePipelining:(BOOL)flag;
|
||||
- (NSURLRequestCachePolicy) requestCachePolicy;
|
||||
- (void) setRequestCachePolicy: (NSURLRequestCachePolicy)policy;
|
||||
|
||||
- (nullable NSString *)identifier;
|
||||
- (NSURLCache*) URLCache;
|
||||
- (void) setURLCache: (NSURLCache*)cache;
|
||||
|
||||
- (nullable NSArray *)protocolClasses;
|
||||
- (NSURLCredentialStorage*) URLCredentialStorage;
|
||||
- (void) setURLCredentialStorage: (NSURLCredentialStorage*)storage;
|
||||
|
||||
- (NSURLRequestCachePolicy)requestCachePolicy;
|
||||
- (void)setRequestCachePolicy:(NSURLRequestCachePolicy)policy;
|
||||
|
||||
- (nullable NSURLCache *)URLCache;
|
||||
- (void)setURLCache:(NSURLCache *)cache;
|
||||
|
||||
- (nullable NSURLCredentialStorage *)URLCredentialStorage;
|
||||
- (void)setURLCredentialStorage:(NSURLCredentialStorage *)storage;
|
||||
|
||||
#if !NO_GNUSTEP
|
||||
#if !NO_GNUSTEP
|
||||
/** Permits a session to be configured so that older connections are reused.
|
||||
* A value of zero or less uses the default behavior where connections are
|
||||
* reused as long as they are not older than 118 seconds, which is reasonable
|
||||
* for the vast majority if situations.
|
||||
*/
|
||||
- (NSInteger)HTTPMaximumConnectionLifetime;
|
||||
- (void)setHTTPMaximumConnectionLifetime:(NSInteger)n;
|
||||
- (NSInteger) HTTPMaximumConnectionLifetime;
|
||||
- (void) setHTTPMaximumConnectionLifetime: (NSInteger)n;
|
||||
#endif
|
||||
|
||||
@end
|
||||
|
@ -546,7 +464,7 @@ typedef NS_ENUM(NSInteger, NSURLSessionResponseDisposition) {
|
|||
NSURLSessionResponseCancel = 0,
|
||||
NSURLSessionResponseAllow = 1,
|
||||
NSURLSessionResponseBecomeDownload = 2,
|
||||
NSURLSessionResponseBecomeStream = 3
|
||||
NSURLSessionResponseBecomeStream = 3
|
||||
};
|
||||
|
||||
@protocol NSURLSessionDelegate <NSObject>
|
||||
|
@ -555,72 +473,58 @@ typedef NS_ENUM(NSInteger, NSURLSessionResponseDisposition) {
|
|||
* invalid because of a systemic error or when it has been
|
||||
* explicitly invalidated, in which case the error parameter will be nil.
|
||||
*/
|
||||
- (void)URLSession:(NSURLSession *)session
|
||||
didBecomeInvalidWithError:(nullable NSError *)error;
|
||||
- (void) URLSession: (NSURLSession*)session
|
||||
didBecomeInvalidWithError: (NSError*)error;
|
||||
|
||||
/* Implementing this method permits a delegate to provide authentication
|
||||
* credentials in response to a challenge from the remote server.
|
||||
*/
|
||||
- (void)URLSession:(NSURLSession *)session
|
||||
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
|
||||
completionHandler:
|
||||
(void (^)(NSURLSessionAuthChallengeDisposition disposition,
|
||||
NSURLCredential *credential))handler;
|
||||
- (void) URLSession: (NSURLSession*)session
|
||||
didReceiveChallenge: (NSURLAuthenticationChallenge*)challenge
|
||||
completionHandler: (void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))handler;
|
||||
|
||||
@end
|
||||
|
||||
@protocol NSURLSessionTaskDelegate <NSURLSessionDelegate>
|
||||
@optional
|
||||
|
||||
#if OS_API_VERSION(MAC_OS_VERSION_13_0, GS_API_LATEST)
|
||||
- (void)URLSession:(NSURLSession *)session
|
||||
didCreateTask:(NSURLSessionTask *)task;
|
||||
#endif
|
||||
|
||||
/* Sent as the last message related to a specific task. Error may be
|
||||
* nil, which implies that no error occurred and this task is complete.
|
||||
* nil, which implies that no error occurred and this task is complete.
|
||||
*/
|
||||
- (void)URLSession:(NSURLSession *)session
|
||||
task:(NSURLSessionTask *)task
|
||||
didCompleteWithError:(nullable NSError *)error;
|
||||
|
||||
- (void ) URLSession: (NSURLSession*)session
|
||||
task: (NSURLSessionTask*)task
|
||||
didCompleteWithError: (NSError*)error;
|
||||
|
||||
/* Called to request authentication credentials from the delegate when
|
||||
* an authentication request is received from the server which is specific
|
||||
* to this task.
|
||||
*/
|
||||
- (void)URLSession:(NSURLSession *)session
|
||||
task:(NSURLSessionTask *)task
|
||||
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
|
||||
completionHandler:
|
||||
(void (^)(NSURLSessionAuthChallengeDisposition disposition,
|
||||
NSURLCredential *credential))handler;
|
||||
- (void) URLSession: (NSURLSession*)session
|
||||
task: (NSURLSessionTask*)task
|
||||
didReceiveChallenge: (NSURLAuthenticationChallenge*)challenge
|
||||
completionHandler: (void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))handler;
|
||||
|
||||
/* Periodically informs the delegate of the progress of sending body content
|
||||
/* Periodically informs the delegate of the progress of sending body content
|
||||
* to the server.
|
||||
*/
|
||||
- (void)URLSession:(NSURLSession *)session
|
||||
task:(NSURLSessionTask *)task
|
||||
didSendBodyData:(int64_t)bytesSent
|
||||
totalBytesSent:(int64_t)totalBytesSent
|
||||
totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend;
|
||||
- (void) URLSession: (NSURLSession*)session
|
||||
task: (NSURLSessionTask*)task
|
||||
didSendBodyData: (int64_t)bytesSent
|
||||
totalBytesSent: (int64_t)totalBytesSent
|
||||
totalBytesExpectedToSend: (int64_t)totalBytesExpectedToSend;
|
||||
|
||||
/* An HTTP request is attempting to perform a redirection to a different
|
||||
* URL. You must invoke the completion routine to allow the
|
||||
* redirection, allow the redirection with a modified request, or
|
||||
* pass nil to the completionHandler to cause the body of the redirection
|
||||
* pass nil to the completionHandler to cause the body of the redirection
|
||||
* response to be delivered as the payload of this request. The default
|
||||
* is to follow redirections.
|
||||
* is to follow redirections.
|
||||
*
|
||||
*/
|
||||
- (void)URLSession:(NSURLSession *)session
|
||||
task:(NSURLSessionTask *)task
|
||||
willPerformHTTPRedirection:(NSHTTPURLResponse *)response
|
||||
newRequest:(NSURLRequest *)request
|
||||
completionHandler:(void (^)(NSURLRequest *))completionHandler;
|
||||
|
||||
- (void)URLSession:(NSURLSession *)session
|
||||
task:(NSURLSessionTask *)task
|
||||
needNewBodyStream:(void (^)(NSInputStream *bodyStream))completionHandler;
|
||||
- (void) URLSession: (NSURLSession*)session
|
||||
task: (NSURLSessionTask*)task
|
||||
willPerformHTTPRedirection: (NSHTTPURLResponse*)response
|
||||
newRequest: (NSURLRequest*)request
|
||||
completionHandler: (void (^)(NSURLRequest*))completionHandler;
|
||||
|
||||
@end
|
||||
|
||||
|
@ -628,54 +532,51 @@ typedef NS_ENUM(NSInteger, NSURLSessionResponseDisposition) {
|
|||
@optional
|
||||
/* Sent when data is available for the delegate to consume.
|
||||
*/
|
||||
- (void)URLSession:(NSURLSession *)session
|
||||
dataTask:(NSURLSessionDataTask *)dataTask
|
||||
didReceiveData:(NSData *)data;
|
||||
- (void) URLSession: (NSURLSession*)session
|
||||
dataTask: (NSURLSessionDataTask*)dataTask
|
||||
didReceiveData: (NSData*)data;
|
||||
|
||||
/** Informs the delegate of a response. This message is sent when all the
|
||||
* response headers have arrived, before the body of the response arrives.
|
||||
*/
|
||||
- (void)URLSession:(NSURLSession *)session
|
||||
dataTask:(NSURLSessionDataTask *)dataTask
|
||||
didReceiveResponse:(NSURLResponse *)response
|
||||
completionHandler:
|
||||
(void (^)(NSURLSessionResponseDisposition disposition))completionHandler;
|
||||
- (void) URLSession: (NSURLSession*)session
|
||||
dataTask: (NSURLSessionDataTask*)dataTask
|
||||
didReceiveResponse: (NSURLResponse*)response
|
||||
completionHandler: (void (^)(NSURLSessionResponseDisposition disposition))completionHandler;
|
||||
|
||||
@end
|
||||
|
||||
@protocol NSURLSessionDownloadDelegate <NSURLSessionTaskDelegate>
|
||||
|
||||
/* Sent when a download task that has completed a download. The delegate should
|
||||
* copy or move the file at the given location to a new location as it will be
|
||||
* removed when the delegate message returns.
|
||||
* URLSession:task:didCompleteWithError: will still be called.
|
||||
/* Sent when a download task that has completed a download. The delegate should
|
||||
* copy or move the file at the given location to a new location as it will be
|
||||
* removed when the delegate message returns. URLSession:task:didCompleteWithError: will
|
||||
* still be called.
|
||||
*/
|
||||
- (void)URLSession:(NSURLSession *)session
|
||||
downloadTask:(NSURLSessionDownloadTask *)downloadTask
|
||||
didFinishDownloadingToURL:(NSURL *)location;
|
||||
- (void) URLSession: (NSURLSession *)session
|
||||
downloadTask: (NSURLSessionDownloadTask *)downloadTask
|
||||
didFinishDownloadingToURL: (NSURL *)location;
|
||||
|
||||
@optional
|
||||
/* Sent periodically to notify the delegate of download progress. */
|
||||
- (void)URLSession:(NSURLSession *)session
|
||||
downloadTask:(NSURLSessionDownloadTask *)downloadTask
|
||||
didWriteData:(int64_t)bytesWritten
|
||||
totalBytesWritten:(int64_t)totalBytesWritten
|
||||
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite;
|
||||
- (void) URLSession: (NSURLSession *)session
|
||||
downloadTask: (NSURLSessionDownloadTask *)downloadTask
|
||||
didWriteData: (int64_t)bytesWritten
|
||||
totalBytesWritten: (int64_t)totalBytesWritten
|
||||
totalBytesExpectedToWrite: (int64_t)totalBytesExpectedToWrite;
|
||||
|
||||
/* Sent when a download has been resumed. If a download failed with an
|
||||
* error, the -userInfo dictionary of the error will contain an
|
||||
* NSURLSessionDownloadTaskResumeData key, whose value is the resume
|
||||
* data.
|
||||
* data.
|
||||
*/
|
||||
- (void)URLSession:(NSURLSession *)session
|
||||
downloadTask:(NSURLSessionDownloadTask *)downloadTask
|
||||
didResumeAtOffset:(int64_t)fileOffset
|
||||
expectedTotalBytes:(int64_t)expectedTotalBytes;
|
||||
- (void) URLSession: (NSURLSession *)session
|
||||
downloadTask: (NSURLSessionDownloadTask *)downloadTask
|
||||
didResumeAtOffset: (int64_t)fileOffset
|
||||
expectedTotalBytes: (int64_t)expectedTotalBytes;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
#endif /* MAC_OS_X_VERSION_10_9 */
|
||||
#endif /* GS_HAVE_NSURLSESSION */
|
||||
#endif /* __NSURLSession_h_GNUSTEP_BASE_INCLUDE */
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
|
|
@ -192,11 +192,6 @@ GS_EXPORT NSString* const GSLocale;
|
|||
#endif
|
||||
#endif
|
||||
|
||||
#if !NO_GNUSTEP
|
||||
/* Path to PEM Encoded Certificate File (NSString). Used by NSURLSession if set. */
|
||||
GS_EXPORT NSString *const GSCACertificateFilePath;
|
||||
#endif
|
||||
|
||||
/* General implementation notes:
|
||||
|
||||
OpenStep spec currently is neither complete nor consistent. Therefore
|
||||
|
|
|
@ -374,9 +374,16 @@ ifeq ($(HAVE_BLOCKS), 1)
|
|||
ifeq ($(GNUSTEP_BASE_HAVE_LIBDISPATCH), 1)
|
||||
ifeq ($(GNUSTEP_BASE_HAVE_LIBCURL), 1)
|
||||
BASE_MFILES += \
|
||||
NSURLSession.m \
|
||||
NSURLSessionTask.m \
|
||||
NSURLSessionConfiguration.m
|
||||
GSEasyHandle.m \
|
||||
GSHTTPURLProtocol.m \
|
||||
GSMultiHandle.m \
|
||||
GSNativeProtocol.m \
|
||||
GSTaskRegistry.m \
|
||||
GSTimeoutSource.m \
|
||||
GSTransferState.m \
|
||||
GSURLSessionTaskBody.m \
|
||||
GSURLSessionTaskBodySource.m \
|
||||
NSURLSession.m
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
|
|
236
Source/GSEasyHandle.h
Normal file
236
Source/GSEasyHandle.h
Normal file
|
@ -0,0 +1,236 @@
|
|||
#ifndef INCLUDED_GSEASYHANDLE_H
|
||||
#define INCLUDED_GSEASYHANDLE_H
|
||||
|
||||
#import "common.h"
|
||||
#import <curl/curl.h>
|
||||
|
||||
@class NSData;
|
||||
@class NSError;
|
||||
@class NSURL;
|
||||
@class NSURLSessionConfiguration;
|
||||
@class NSURLSessionTask;
|
||||
@class GSTimeoutSource;
|
||||
|
||||
typedef NS_ENUM(NSUInteger, GSEasyHandleAction) {
|
||||
GSEasyHandleActionAbort,
|
||||
GSEasyHandleActionProceed,
|
||||
GSEasyHandleActionPause,
|
||||
};
|
||||
|
||||
typedef NS_ENUM(NSUInteger, GSEasyHandleWriteBufferResult) {
|
||||
GSEasyHandleWriteBufferResultAbort,
|
||||
GSEasyHandleWriteBufferResultPause,
|
||||
GSEasyHandleWriteBufferResultBytes,
|
||||
};
|
||||
|
||||
@protocol GSEasyHandleDelegate <NSObject>
|
||||
|
||||
/*
|
||||
* Handle data read from the network.
|
||||
* - returns: the action to be taken: abort, proceed, or pause.
|
||||
*/
|
||||
- (GSEasyHandleAction) didReceiveData: (NSData*)data;
|
||||
|
||||
/*
|
||||
* Handle header data read from the network.
|
||||
* - returns: the action to be taken: abort, proceed, or pause.
|
||||
*/
|
||||
- (GSEasyHandleAction) didReceiveHeaderData: (NSData*)data
|
||||
contentLength: (int64_t)contentLength;
|
||||
|
||||
/*
|
||||
* Fill a buffer with data to be sent.
|
||||
* - parameter data: The buffer to fill
|
||||
* - returns: the number of bytes written to the `data` buffer, or `nil`
|
||||
* to stop the current transfer immediately.
|
||||
*/
|
||||
- (void) fillWriteBufferLength: (NSInteger)length
|
||||
result: (void (^)(GSEasyHandleWriteBufferResult result, NSInteger length, NSData *data))result;
|
||||
|
||||
/*
|
||||
* The transfer for this handle completed.
|
||||
* - parameter errorCode: An NSURLError code, or `nil` if no error occurred.
|
||||
*/
|
||||
- (void) transferCompletedWithError: (NSError*)error;
|
||||
|
||||
/*
|
||||
* Seek the input stream to the given position
|
||||
*/
|
||||
- (BOOL) seekInputStreamToPosition: (uint64_t)position;
|
||||
|
||||
/*
|
||||
* Gets called during the transfer to update progress.
|
||||
*/
|
||||
- (void) updateProgressMeterWithTotalBytesSent: (int64_t)totalBytesSent
|
||||
totalBytesExpectedToSend: (int64_t)totalBytesExpectedToSend
|
||||
totalBytesReceived: (int64_t)totalBytesReceived
|
||||
totalBytesExpectedToReceive: (int64_t)totalBytesExpectedToReceive;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
/*
|
||||
* Minimal wrapper around the curl easy interface
|
||||
* (https://curl.haxx.se/libcurl/c/)
|
||||
*
|
||||
* An *easy handle* manages the state of a transfer inside libcurl.
|
||||
*
|
||||
* As such the easy handle's responsibility is implementing the HTTP
|
||||
* protocol while the *multi handle* is in charge of managing sockets and
|
||||
* reading from / writing to these sockets.
|
||||
*
|
||||
* An easy handle is added to a multi handle in order to associate it with
|
||||
* an actual socket. The multi handle will then feed bytes into the easy
|
||||
* handle and read bytes from the easy handle. But this process is opaque
|
||||
* to use. It is further worth noting, that with HTTP/1.1 persistent
|
||||
* connections and with HTTP/2 there's a 1-to-many relationship between
|
||||
* TCP streams and HTTP transfers / easy handles. A single TCP stream and
|
||||
* its socket may be shared by multiple easy handles.
|
||||
*
|
||||
* A single HTTP request-response exchange (refered to here as a
|
||||
* *transfer*) corresponds directly to an easy handle. Hence anything that
|
||||
* needs to be configured for a specific transfer (e.g. the URL) will be
|
||||
* configured on an easy handle.
|
||||
*
|
||||
* A single `NSURLSessionTask` may do multiple, consecutive transfers, and
|
||||
* as a result it will have to reconfigure its easy handle between
|
||||
* transfers. An easy handle can be re-used once its transfer has
|
||||
* completed.
|
||||
*
|
||||
* Note: All code assumes that it is being called on a single thread,
|
||||
* it is intentionally **not** thread safe.
|
||||
*/
|
||||
@interface GSEasyHandle : NSObject
|
||||
{
|
||||
CURL *_rawHandle;
|
||||
char *_errorBuffer;
|
||||
id<GSEasyHandleDelegate> _delegate;
|
||||
GSTimeoutSource *_timeoutTimer;
|
||||
NSURL *_URL;
|
||||
}
|
||||
|
||||
- (CURL*) rawHandle;
|
||||
|
||||
- (char*) errorBuffer;
|
||||
|
||||
/*
|
||||
* Set error buffer for error messages
|
||||
* - SeeAlso: https://curl.haxx.se/libcurl/c/CURLOPT_ERRORBUFFER.html
|
||||
*/
|
||||
- (void) setErrorBuffer: (char*)buffer;
|
||||
|
||||
- (GSTimeoutSource*) timeoutTimer;
|
||||
|
||||
- (void) setTimeoutTimer: (GSTimeoutSource*)timer;
|
||||
|
||||
- (NSURL*) URL;
|
||||
|
||||
/*
|
||||
* URL to use in the request
|
||||
* - SeeAlso: https://curl.haxx.se/libcurl/c/CURLOPT_URL.html
|
||||
*/
|
||||
- (void) setURL: (NSURL*)URL;
|
||||
|
||||
- (void) setPipeWait: (BOOL)flag;
|
||||
|
||||
- (instancetype) initWithDelegate: (id<GSEasyHandleDelegate>)delegate;
|
||||
|
||||
- (void) transferCompletedWithError: (NSError*)error;
|
||||
|
||||
- (int) urlErrorCodeWithEasyCode: (int)easyCode;
|
||||
|
||||
- (void) setVerboseMode: (BOOL)flag;
|
||||
|
||||
- (void) setDebugOutput: (BOOL)flag
|
||||
task: (NSURLSessionTask*)task;
|
||||
|
||||
- (void) setPassHeadersToDataStream: (BOOL)flag;
|
||||
|
||||
/*
|
||||
* Follow any Location: header that the server sends as part of a HTTP header
|
||||
* in a 3xx response
|
||||
*/
|
||||
- (void) setFollowLocation: (BOOL)flag;
|
||||
|
||||
/*
|
||||
* Switch off the progress meter.
|
||||
*/
|
||||
- (void) setProgressMeterOff: (BOOL)flag;
|
||||
|
||||
/*
|
||||
* Skip all signal handling
|
||||
* - SeeAlso: https://curl.haxx.se/libcurl/c/CURLOPT_NOSIGNAL.html
|
||||
*/
|
||||
- (void) setSkipAllSignalHandling: (BOOL)flag;
|
||||
|
||||
/*
|
||||
* Request failure on HTTP response >= 400
|
||||
*/
|
||||
- (void) setFailOnHTTPErrorCode: (BOOL)flag;
|
||||
|
||||
- (void) setConnectToHost: (NSString*)host
|
||||
port: (NSInteger)port;
|
||||
|
||||
- (void) setSessionConfig: (NSURLSessionConfiguration*)config;
|
||||
|
||||
- (void) setAllowedProtocolsToHTTPAndHTTPS;
|
||||
|
||||
/*
|
||||
* set preferred receive buffer size
|
||||
* - SeeAlso: https://curl.haxx.se/libcurl/c/CURLOPT_BUFFERSIZE.html
|
||||
*/
|
||||
- (void) setPreferredReceiveBufferSize: (NSInteger)size;
|
||||
|
||||
/*
|
||||
* Set custom HTTP headers
|
||||
* - SeeAlso: https://curl.haxx.se/libcurl/c/CURLOPT_HTTPHEADER.html
|
||||
*/
|
||||
- (void) setCustomHeaders: (NSArray*)headers;
|
||||
|
||||
/*
|
||||
* Enable automatic decompression of HTTP downloads
|
||||
* - SeeAlso: https://curl.haxx.se/libcurl/c/CURLOPT_ACCEPT_ENCODING.html
|
||||
* - SeeAlso: https://curl.haxx.se/libcurl/c/CURLOPT_HTTP_CONTENT_DECODING.html
|
||||
*/
|
||||
- (void) setAutomaticBodyDecompression: (BOOL)flag;
|
||||
|
||||
/*
|
||||
* Set request method
|
||||
* - SeeAlso: https://curl.haxx.se/libcurl/c/CURLOPT_CUSTOMREQUEST.html
|
||||
*/
|
||||
- (void) setRequestMethod:(NSString*)method;
|
||||
|
||||
/*
|
||||
* Download request without body
|
||||
* - SeeAlso: https://curl.haxx.se/libcurl/c/CURLOPT_NOBODY.html
|
||||
*/
|
||||
- (void) setNoBody: (BOOL)flag;
|
||||
|
||||
/*
|
||||
* Enable data upload
|
||||
* - SeeAlso: https://curl.haxx.se/libcurl/c/CURLOPT_UPLOAD.html
|
||||
*/
|
||||
- (void) setUpload: (BOOL)flag;
|
||||
|
||||
/*
|
||||
* Set size of the request body to send
|
||||
* - SeeAlso: https://curl.haxx.se/libcurl/c/CURLOPT_INFILESIZE_LARGE.html
|
||||
*/
|
||||
- (void) setRequestBodyLength: (int64_t)length;
|
||||
|
||||
- (void) setTimeout: (NSInteger)timeout;
|
||||
|
||||
- (void) setProxy;
|
||||
|
||||
- (double) getTimeoutIntervalSpent;
|
||||
|
||||
|
||||
- (void) pauseReceive;
|
||||
- (void) unpauseReceive;
|
||||
|
||||
- (void) pauseSend;
|
||||
- (void) unpauseSend;
|
||||
|
||||
@end
|
||||
|
||||
#endif
|
773
Source/GSEasyHandle.m
Normal file
773
Source/GSEasyHandle.m
Normal file
|
@ -0,0 +1,773 @@
|
|||
#import "GSURLPrivate.h"
|
||||
#import "GSEasyHandle.h"
|
||||
#import "GSTimeoutSource.h"
|
||||
#import "Foundation/NSCharacterSet.h"
|
||||
#import "Foundation/NSURLSession.h"
|
||||
|
||||
typedef NS_OPTIONS(NSUInteger, GSEasyHandlePauseState) {
|
||||
GSEasyHandlePauseStateReceive = 1 << 0,
|
||||
GSEasyHandlePauseStateSend = 1 << 1
|
||||
};
|
||||
|
||||
@interface GSEasyHandle ()
|
||||
|
||||
- (void) resetTimer;
|
||||
|
||||
- (NSInteger) didReceiveData: (char*)data
|
||||
size: (NSInteger)size
|
||||
nmemb:(NSInteger)nmemb;
|
||||
|
||||
- (NSInteger) fillWriteBuffer: (char *)buffer
|
||||
size: (NSInteger)size
|
||||
nmemb: (NSInteger)nmemb;
|
||||
|
||||
- (NSInteger) didReceiveHeaderData: (char*)headerData
|
||||
size: (NSInteger)size
|
||||
nmemb: (NSInteger)nmemb
|
||||
contentLength: (double)contentLength;
|
||||
|
||||
- (int) seekInputStreamWithOffset: (int64_t)offset
|
||||
origin: (NSInteger)origin;
|
||||
|
||||
@end
|
||||
|
||||
static void
|
||||
handleEasyCode(int code)
|
||||
{
|
||||
if (CURLE_OK != code)
|
||||
{
|
||||
NSString *reason;
|
||||
NSException *e;
|
||||
|
||||
reason = [NSString stringWithFormat: @"An error occurred, CURLcode is %d",
|
||||
code];
|
||||
e = [NSException exceptionWithName: @"libcurl.easy"
|
||||
reason: reason
|
||||
userInfo: nil];
|
||||
[e raise];
|
||||
}
|
||||
}
|
||||
|
||||
static size_t
|
||||
curl_write_function(char *data, size_t size, size_t nmemb, void *userdata)
|
||||
{
|
||||
GSEasyHandle *handle;
|
||||
|
||||
if (!userdata)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
handle = (GSEasyHandle*)userdata;
|
||||
|
||||
[handle resetTimer]; //FIXME should be deffered after the function returns?
|
||||
|
||||
return [handle didReceiveData:data size:size nmemb:nmemb];
|
||||
}
|
||||
|
||||
static size_t
|
||||
curl_read_function(char *data, size_t size, size_t nmemb, void *userdata)
|
||||
{
|
||||
GSEasyHandle *handle;
|
||||
|
||||
if (!userdata)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
handle = (GSEasyHandle*)userdata;
|
||||
|
||||
[handle resetTimer]; //FIXME should be deffered after the function returns?
|
||||
|
||||
return [handle fillWriteBuffer: data size: size nmemb: nmemb];
|
||||
}
|
||||
|
||||
size_t
|
||||
curl_header_function(char *data, size_t size, size_t nmemb, void *userdata)
|
||||
{
|
||||
GSEasyHandle *handle;
|
||||
double length;
|
||||
|
||||
if (!userdata)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
handle = (GSEasyHandle*)userdata;
|
||||
|
||||
[handle resetTimer]; //FIXME should be deffered after the function returns?
|
||||
|
||||
handleEasyCode(curl_easy_getinfo(handle.rawHandle,
|
||||
CURLINFO_CONTENT_LENGTH_DOWNLOAD, &length));
|
||||
|
||||
return [handle didReceiveHeaderData: data
|
||||
size: size
|
||||
nmemb: nmemb
|
||||
contentLength: length];
|
||||
}
|
||||
|
||||
static int
|
||||
curl_seek_function(void *userdata, curl_off_t offset, int origin)
|
||||
{
|
||||
GSEasyHandle *handle;
|
||||
|
||||
if (!userdata)
|
||||
{
|
||||
return CURL_SEEKFUNC_FAIL;
|
||||
}
|
||||
|
||||
handle = (GSEasyHandle*)userdata;
|
||||
|
||||
return [handle seekInputStreamWithOffset: offset origin: origin];
|
||||
}
|
||||
|
||||
static int
|
||||
curl_debug_function(CURL *handle, curl_infotype type, char *data,
|
||||
size_t size, void *userptr)
|
||||
{
|
||||
NSURLSessionTask *task;
|
||||
NSString *text;
|
||||
NSURLRequest *o;
|
||||
NSURLRequest *r;
|
||||
id<GSLogDelegate> d;
|
||||
|
||||
if (!userptr)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (CURLINFO_SSL_DATA_OUT == type || CURLINFO_SSL_DATA_IN == type)
|
||||
{
|
||||
return 0; // Don't log encrypted data here
|
||||
}
|
||||
|
||||
task = (NSURLSessionTask*)userptr;
|
||||
text = @"";
|
||||
o = [task originalRequest];
|
||||
r = [task currentRequest];
|
||||
d = [(nil == r ? o : r) _debugLogDelegate];
|
||||
|
||||
if (d != nil)
|
||||
{
|
||||
if (CURLINFO_DATA_IN == type || CURLINFO_HEADER_IN == type)
|
||||
{
|
||||
if ([d getBytes: (const uint8_t *)data ofLength: size byHandle: o])
|
||||
{
|
||||
return 0; // Handled
|
||||
}
|
||||
}
|
||||
if (CURLINFO_DATA_OUT == type || CURLINFO_HEADER_OUT == type)
|
||||
{
|
||||
if ([d putBytes: (const uint8_t *)data ofLength: size byHandle: o])
|
||||
{
|
||||
return 0; // Handled
|
||||
}
|
||||
}
|
||||
}
|
||||
if (data)
|
||||
{
|
||||
text = [NSString stringWithUTF8String: data];
|
||||
}
|
||||
|
||||
NSLog(@"%p %lu %d %@", o, (unsigned long)[task taskIdentifier], type, text);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
curl_socket_function(void *userdata, curl_socket_t fd, curlsocktype type)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
@implementation GSEasyHandle
|
||||
{
|
||||
NSURLSessionConfiguration *_config;
|
||||
GSEasyHandlePauseState _pauseState;
|
||||
struct curl_slist *_headerList;
|
||||
}
|
||||
|
||||
- (instancetype) initWithDelegate: (id<GSEasyHandleDelegate>)delegate
|
||||
{
|
||||
if (nil != (self = [super init]))
|
||||
{
|
||||
char *eb;
|
||||
|
||||
_rawHandle = curl_easy_init();
|
||||
_delegate = delegate;
|
||||
|
||||
eb = (char *)malloc(sizeof(char) * (CURL_ERROR_SIZE + 1));
|
||||
_errorBuffer = memset(eb, 0, sizeof(char) * (CURL_ERROR_SIZE + 1));
|
||||
|
||||
[self setupCallbacks];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void) dealloc
|
||||
{
|
||||
curl_easy_cleanup(_rawHandle);
|
||||
curl_slist_free_all(_headerList);
|
||||
free(_errorBuffer);
|
||||
DESTROY(_config);
|
||||
[_timeoutTimer cancel];
|
||||
DESTROY(_timeoutTimer);
|
||||
DESTROY(_URL);
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (CURL*) rawHandle
|
||||
{
|
||||
return _rawHandle;
|
||||
}
|
||||
|
||||
- (char*) errorBuffer
|
||||
{
|
||||
return _errorBuffer;
|
||||
}
|
||||
|
||||
- (GSTimeoutSource*) timeoutTimer
|
||||
{
|
||||
return _timeoutTimer;
|
||||
}
|
||||
|
||||
- (void) setTimeoutTimer: (GSTimeoutSource*)timer
|
||||
{
|
||||
[_timeoutTimer cancel];
|
||||
ASSIGN(_timeoutTimer, timer);
|
||||
}
|
||||
|
||||
- (NSURL*) URL
|
||||
{
|
||||
return _URL;
|
||||
}
|
||||
|
||||
- (void) transferCompletedWithError: (NSError*)error
|
||||
{
|
||||
[_delegate transferCompletedWithError: error];
|
||||
}
|
||||
|
||||
- (void) resetTimer
|
||||
{
|
||||
[_timeoutTimer setTimeout: [_timeoutTimer timeout]];
|
||||
}
|
||||
|
||||
- (void) setupCallbacks
|
||||
{
|
||||
// write
|
||||
handleEasyCode(curl_easy_setopt(_rawHandle, CURLOPT_WRITEDATA, self));
|
||||
handleEasyCode(curl_easy_setopt(_rawHandle, CURLOPT_WRITEFUNCTION,
|
||||
curl_write_function));
|
||||
|
||||
// read
|
||||
handleEasyCode(curl_easy_setopt(_rawHandle, CURLOPT_READDATA, self));
|
||||
handleEasyCode(curl_easy_setopt(_rawHandle, CURLOPT_READFUNCTION,
|
||||
curl_read_function));
|
||||
|
||||
// header
|
||||
handleEasyCode(curl_easy_setopt(_rawHandle, CURLOPT_HEADERDATA, self));
|
||||
handleEasyCode(curl_easy_setopt(_rawHandle, CURLOPT_HEADERFUNCTION,
|
||||
curl_header_function));
|
||||
|
||||
// socket options
|
||||
handleEasyCode(curl_easy_setopt(_rawHandle, CURLOPT_SOCKOPTDATA, self));
|
||||
handleEasyCode(curl_easy_setopt(_rawHandle, CURLOPT_SOCKOPTFUNCTION,
|
||||
curl_socket_function));
|
||||
|
||||
// seeking in input stream
|
||||
handleEasyCode(curl_easy_setopt(_rawHandle, CURLOPT_SEEKDATA, self));
|
||||
handleEasyCode(curl_easy_setopt(_rawHandle, CURLOPT_SEEKFUNCTION,
|
||||
curl_seek_function));
|
||||
}
|
||||
|
||||
- (int) urlErrorCodeWithEasyCode: (int)easyCode
|
||||
{
|
||||
int failureErrno = (int)[self connectFailureErrno];
|
||||
|
||||
if (easyCode == CURLE_OK)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
else if (failureErrno == ECONNREFUSED)
|
||||
{
|
||||
return NSURLErrorCannotConnectToHost;
|
||||
}
|
||||
else if (easyCode == CURLE_UNSUPPORTED_PROTOCOL)
|
||||
{
|
||||
return NSURLErrorUnsupportedURL;
|
||||
}
|
||||
else if (easyCode == CURLE_URL_MALFORMAT)
|
||||
{
|
||||
return NSURLErrorBadURL;
|
||||
}
|
||||
else if (easyCode == CURLE_COULDNT_RESOLVE_HOST)
|
||||
{
|
||||
return NSURLErrorCannotFindHost;
|
||||
}
|
||||
else if (easyCode == CURLE_RECV_ERROR && failureErrno == ECONNRESET)
|
||||
{
|
||||
return NSURLErrorNetworkConnectionLost;
|
||||
}
|
||||
else if (easyCode == CURLE_SEND_ERROR && failureErrno == ECONNRESET)
|
||||
{
|
||||
return NSURLErrorNetworkConnectionLost;
|
||||
}
|
||||
else if (easyCode == CURLE_GOT_NOTHING)
|
||||
{
|
||||
return NSURLErrorBadServerResponse;
|
||||
}
|
||||
else if (easyCode == CURLE_ABORTED_BY_CALLBACK)
|
||||
{
|
||||
return NSURLErrorUnknown;
|
||||
}
|
||||
else if (easyCode == CURLE_COULDNT_CONNECT && failureErrno == ETIMEDOUT)
|
||||
{
|
||||
return NSURLErrorTimedOut;
|
||||
}
|
||||
else if (easyCode == CURLE_COULDNT_CONNECT)
|
||||
{
|
||||
return NSURLErrorCannotConnectToHost;
|
||||
}
|
||||
else if (easyCode == CURLE_OPERATION_TIMEDOUT)
|
||||
{
|
||||
return NSURLErrorTimedOut;
|
||||
}
|
||||
else
|
||||
{
|
||||
return NSURLErrorUnknown;
|
||||
}
|
||||
}
|
||||
|
||||
- (void) setVerboseMode: (BOOL)flag
|
||||
{
|
||||
handleEasyCode(curl_easy_setopt(_rawHandle, CURLOPT_VERBOSE, flag ? 1 : 0));
|
||||
}
|
||||
|
||||
- (void) setDebugOutput: (BOOL)flag
|
||||
task: (NSURLSessionTask*)task
|
||||
{
|
||||
if (flag)
|
||||
{
|
||||
handleEasyCode(curl_easy_setopt(_rawHandle, CURLOPT_DEBUGDATA, task));
|
||||
handleEasyCode(curl_easy_setopt(_rawHandle, CURLOPT_DEBUGFUNCTION,
|
||||
curl_debug_function));
|
||||
}
|
||||
else
|
||||
{
|
||||
handleEasyCode(curl_easy_setopt(_rawHandle, CURLOPT_DEBUGDATA, NULL));
|
||||
handleEasyCode(curl_easy_setopt(_rawHandle, CURLOPT_DEBUGFUNCTION, NULL));
|
||||
}
|
||||
}
|
||||
|
||||
- (void) setPassHeadersToDataStream: (BOOL)flag
|
||||
{
|
||||
handleEasyCode(curl_easy_setopt(_rawHandle, CURLOPT_HEADER,
|
||||
flag ? 1 : 0));
|
||||
}
|
||||
|
||||
- (void) setFollowLocation: (BOOL)flag
|
||||
{
|
||||
handleEasyCode(curl_easy_setopt(_rawHandle, CURLOPT_FOLLOWLOCATION,
|
||||
flag ? 1 : 0));
|
||||
}
|
||||
|
||||
- (void) setProgressMeterOff: (BOOL)flag
|
||||
{
|
||||
handleEasyCode(curl_easy_setopt(_rawHandle, CURLOPT_NOPROGRESS,
|
||||
flag ? 1 : 0));
|
||||
}
|
||||
|
||||
- (void) setSkipAllSignalHandling: (BOOL)flag
|
||||
{
|
||||
handleEasyCode(curl_easy_setopt(_rawHandle, CURLOPT_NOSIGNAL,
|
||||
flag ? 1 : 0));
|
||||
}
|
||||
|
||||
- (void) setErrorBuffer: (char*)buffer
|
||||
{
|
||||
char *b = buffer ? buffer : _errorBuffer;
|
||||
handleEasyCode(curl_easy_setopt(_rawHandle, CURLOPT_ERRORBUFFER, b));
|
||||
}
|
||||
|
||||
- (void) setFailOnHTTPErrorCode: (BOOL)flag
|
||||
{
|
||||
handleEasyCode(curl_easy_setopt(_rawHandle, CURLOPT_FAILONERROR,
|
||||
flag ? 1 : 0));
|
||||
}
|
||||
|
||||
- (void) setURL: (NSURL *)URL
|
||||
{
|
||||
ASSIGN(_URL, URL);
|
||||
if (nil != [URL absoluteString])
|
||||
{
|
||||
handleEasyCode(curl_easy_setopt(_rawHandle, CURLOPT_URL,
|
||||
[[URL absoluteString] UTF8String]));
|
||||
}
|
||||
}
|
||||
|
||||
- (void) setPipeWait: (BOOL)flag
|
||||
{
|
||||
handleEasyCode(curl_easy_setopt(_rawHandle, CURLOPT_PIPEWAIT, flag ? 1 : 0));
|
||||
}
|
||||
|
||||
- (void) setConnectToHost: (NSString*)host port: (NSInteger)port
|
||||
{
|
||||
if (nil != host)
|
||||
{
|
||||
NSString *originHost = [_URL host];
|
||||
NSString *value;
|
||||
struct curl_slist *connect_to;
|
||||
|
||||
if (0 == port)
|
||||
{
|
||||
value = [NSString stringWithFormat: @"%@::%@", originHost, host];
|
||||
}
|
||||
else
|
||||
{
|
||||
value = [NSString stringWithFormat: @"%@:%lu:%@",
|
||||
originHost, (unsigned long)port, host];
|
||||
}
|
||||
|
||||
connect_to = curl_slist_append(NULL, [value UTF8String]);
|
||||
handleEasyCode(
|
||||
curl_easy_setopt(_rawHandle, CURLOPT_CONNECT_TO, connect_to));
|
||||
}
|
||||
}
|
||||
|
||||
- (void) setSessionConfig: (NSURLSessionConfiguration*)config
|
||||
{
|
||||
ASSIGN(_config, config);
|
||||
#if defined(CURLOPT_MAXAGE_CONN)
|
||||
/* This specifies the maximum age of a connection if it is to be considered
|
||||
* a candidate for re-use. By default curl currently uses 118 seconds, so
|
||||
* this is what we will get if the configuration does not contain a positive
|
||||
* number of seconds.
|
||||
*/
|
||||
if ([config HTTPMaximumConnectionLifetime] > 0)
|
||||
{
|
||||
handleEasyCode(curl_easy_setopt(_rawHandle, CURLOPT_MAXAGE_CONN,
|
||||
(long)[config HTTPMaximumConnectionLifetime]));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
- (void) setAllowedProtocolsToHTTPAndHTTPS
|
||||
{
|
||||
handleEasyCode(curl_easy_setopt(_rawHandle, CURLOPT_PROTOCOLS,
|
||||
CURLPROTO_HTTP | CURLPROTO_HTTPS));
|
||||
handleEasyCode(curl_easy_setopt(_rawHandle, CURLOPT_REDIR_PROTOCOLS,
|
||||
CURLPROTO_HTTP | CURLPROTO_HTTPS));
|
||||
}
|
||||
|
||||
- (void) setPreferredReceiveBufferSize: (NSInteger)size
|
||||
{
|
||||
handleEasyCode(curl_easy_setopt(_rawHandle, CURLOPT_BUFFERSIZE,
|
||||
MIN(size, CURL_MAX_WRITE_SIZE)));
|
||||
}
|
||||
|
||||
- (void) setCustomHeaders: (NSArray*)headers
|
||||
{
|
||||
NSEnumerator *e;
|
||||
NSString *h;
|
||||
|
||||
e = [headers objectEnumerator];
|
||||
while (nil != (h = [e nextObject]))
|
||||
{
|
||||
_headerList = curl_slist_append(_headerList, [h UTF8String]);
|
||||
}
|
||||
handleEasyCode(curl_easy_setopt(_rawHandle, CURLOPT_HTTPHEADER, _headerList));
|
||||
}
|
||||
|
||||
- (void) setAutomaticBodyDecompression: (BOOL)flag
|
||||
{
|
||||
if (flag)
|
||||
{
|
||||
handleEasyCode(curl_easy_setopt(_rawHandle,
|
||||
CURLOPT_ACCEPT_ENCODING, ""));
|
||||
handleEasyCode(curl_easy_setopt(_rawHandle,
|
||||
CURLOPT_HTTP_CONTENT_DECODING, 1));
|
||||
}
|
||||
else
|
||||
{
|
||||
handleEasyCode(curl_easy_setopt(_rawHandle,
|
||||
CURLOPT_ACCEPT_ENCODING, NULL));
|
||||
handleEasyCode(curl_easy_setopt(_rawHandle,
|
||||
CURLOPT_HTTP_CONTENT_DECODING, 0));
|
||||
}
|
||||
}
|
||||
|
||||
- (void) setRequestMethod: (NSString*)method
|
||||
{
|
||||
if (nil == method)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
handleEasyCode(curl_easy_setopt(_rawHandle, CURLOPT_CUSTOMREQUEST,
|
||||
[method UTF8String]));
|
||||
}
|
||||
|
||||
- (void) setNoBody: (BOOL)flag
|
||||
{
|
||||
handleEasyCode(curl_easy_setopt(_rawHandle, CURLOPT_NOBODY,
|
||||
flag ? 1 : 0));
|
||||
}
|
||||
|
||||
- (void) setUpload: (BOOL)flag
|
||||
{
|
||||
handleEasyCode(curl_easy_setopt(_rawHandle, CURLOPT_UPLOAD, flag ? 1 : 0));
|
||||
}
|
||||
|
||||
- (void) setRequestBodyLength: (int64_t)length
|
||||
{
|
||||
handleEasyCode(curl_easy_setopt(_rawHandle, CURLOPT_INFILESIZE_LARGE,
|
||||
length));
|
||||
}
|
||||
|
||||
- (void) setTimeout: (NSInteger)timeout
|
||||
{
|
||||
handleEasyCode(curl_easy_setopt(_rawHandle, CURLOPT_TIMEOUT,
|
||||
(long)timeout));
|
||||
}
|
||||
|
||||
- (void) setProxy
|
||||
{
|
||||
//TODO setup proxy
|
||||
}
|
||||
|
||||
- (void) updatePauseState: (GSEasyHandlePauseState)pauseState
|
||||
{
|
||||
NSUInteger send = pauseState & GSEasyHandlePauseStateSend;
|
||||
NSUInteger receive = pauseState & GSEasyHandlePauseStateReceive;
|
||||
int bitmask;
|
||||
|
||||
bitmask = 0
|
||||
| (send ? CURLPAUSE_SEND : CURLPAUSE_SEND_CONT)
|
||||
| (receive ? CURLPAUSE_RECV : CURLPAUSE_RECV_CONT);
|
||||
handleEasyCode(curl_easy_pause(_rawHandle, bitmask));
|
||||
}
|
||||
|
||||
- (double) getTimeoutIntervalSpent
|
||||
{
|
||||
double timeSpent;
|
||||
curl_easy_getinfo(_rawHandle, CURLINFO_TOTAL_TIME, &timeSpent);
|
||||
return timeSpent / 1000;
|
||||
}
|
||||
|
||||
- (long) connectFailureErrno
|
||||
{
|
||||
long _errno;
|
||||
handleEasyCode(curl_easy_getinfo(_rawHandle, CURLINFO_OS_ERRNO, &_errno));
|
||||
return _errno;
|
||||
}
|
||||
|
||||
- (void) pauseSend
|
||||
{
|
||||
if (_pauseState & GSEasyHandlePauseStateSend)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_pauseState = _pauseState | GSEasyHandlePauseStateSend;
|
||||
[self updatePauseState: _pauseState];
|
||||
}
|
||||
|
||||
- (void) unpauseSend
|
||||
{
|
||||
if (!(_pauseState & GSEasyHandlePauseStateSend))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_pauseState = _pauseState ^ GSEasyHandlePauseStateSend;
|
||||
[self updatePauseState: _pauseState];
|
||||
}
|
||||
|
||||
- (void) pauseReceive
|
||||
{
|
||||
if (_pauseState & GSEasyHandlePauseStateReceive)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_pauseState = _pauseState | GSEasyHandlePauseStateReceive;
|
||||
[self updatePauseState: _pauseState];
|
||||
}
|
||||
|
||||
- (void) unpauseReceive
|
||||
{
|
||||
if (!(_pauseState & GSEasyHandlePauseStateReceive))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_pauseState = _pauseState ^ GSEasyHandlePauseStateReceive;
|
||||
[self updatePauseState: _pauseState];
|
||||
}
|
||||
|
||||
- (NSInteger) didReceiveData: (char*)data
|
||||
size: (NSInteger)size
|
||||
nmemb: (NSInteger)nmemb
|
||||
{
|
||||
NSData *buffer;
|
||||
GSEasyHandleAction action;
|
||||
NSUInteger bytes;
|
||||
|
||||
if (![_delegate respondsToSelector: @selector(didReceiveData:)])
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
bytes = size * nmemb;
|
||||
buffer = AUTORELEASE([[NSData alloc] initWithBytes: data length: bytes]);
|
||||
action = [_delegate didReceiveData: buffer];
|
||||
switch (action)
|
||||
{
|
||||
case GSEasyHandleActionProceed:
|
||||
return bytes;
|
||||
case GSEasyHandleActionAbort:
|
||||
return 0;
|
||||
case GSEasyHandleActionPause:
|
||||
_pauseState = _pauseState | GSEasyHandlePauseStateReceive;
|
||||
return CURL_WRITEFUNC_PAUSE;
|
||||
}
|
||||
}
|
||||
|
||||
- (NSInteger) didReceiveHeaderData: (char*)headerData
|
||||
size: (NSInteger)size
|
||||
nmemb: (NSInteger)nmemb
|
||||
contentLength: (double)contentLength
|
||||
{
|
||||
NSData *buffer;
|
||||
GSEasyHandleAction action;
|
||||
NSInteger bytes = size * nmemb;
|
||||
|
||||
buffer = [NSData dataWithBytes: headerData length: bytes];
|
||||
|
||||
[self setCookiesWithHeaderData: buffer];
|
||||
|
||||
if (![_delegate respondsToSelector:
|
||||
@selector(didReceiveHeaderData:contentLength:)])
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
action = [_delegate didReceiveHeaderData: buffer
|
||||
contentLength: (int64_t)contentLength];
|
||||
switch (action)
|
||||
{
|
||||
case GSEasyHandleActionProceed:
|
||||
return bytes;
|
||||
case GSEasyHandleActionAbort:
|
||||
return 0;
|
||||
case GSEasyHandleActionPause:
|
||||
_pauseState = _pauseState | GSEasyHandlePauseStateReceive;
|
||||
return CURL_WRITEFUNC_PAUSE;
|
||||
}
|
||||
}
|
||||
|
||||
- (NSInteger) fillWriteBuffer: (char*)buffer
|
||||
size: (NSInteger)size
|
||||
nmemb: (NSInteger)nmemb
|
||||
{
|
||||
__block NSInteger d;
|
||||
|
||||
if (![_delegate respondsToSelector: @selector(fillWriteBufferLength:result:)])
|
||||
{
|
||||
return CURL_READFUNC_ABORT;
|
||||
}
|
||||
|
||||
[_delegate fillWriteBufferLength: size * nmemb
|
||||
result: ^(GSEasyHandleWriteBufferResult result, NSInteger length, NSData *data)
|
||||
{
|
||||
switch (result)
|
||||
{
|
||||
case GSEasyHandleWriteBufferResultPause:
|
||||
_pauseState = _pauseState | GSEasyHandlePauseStateSend;
|
||||
d = CURL_READFUNC_PAUSE;
|
||||
break;
|
||||
case GSEasyHandleWriteBufferResultAbort:
|
||||
d = CURL_READFUNC_ABORT;
|
||||
break;
|
||||
case GSEasyHandleWriteBufferResultBytes:
|
||||
memcpy(buffer, [data bytes], length);
|
||||
d = length;
|
||||
break;
|
||||
}
|
||||
}];
|
||||
|
||||
return d;
|
||||
}
|
||||
|
||||
- (int) seekInputStreamWithOffset: (int64_t)offset
|
||||
origin: (NSInteger)origin
|
||||
{
|
||||
NSAssert(SEEK_SET == origin, @"Unexpected 'origin' in seek.");
|
||||
|
||||
if (![_delegate respondsToSelector: @selector(seekInputStreamToPosition:)])
|
||||
{
|
||||
return CURL_SEEKFUNC_CANTSEEK;
|
||||
}
|
||||
|
||||
if ([_delegate seekInputStreamToPosition: offset])
|
||||
{
|
||||
return CURL_SEEKFUNC_OK;
|
||||
}
|
||||
else
|
||||
{
|
||||
return CURL_SEEKFUNC_CANTSEEK;
|
||||
}
|
||||
}
|
||||
|
||||
- (void) setCookiesWithHeaderData: (NSData*)data
|
||||
{
|
||||
NSString *headerLine;
|
||||
NSRange r;
|
||||
NSString *head;
|
||||
NSString *tail;
|
||||
NSCharacterSet *set;
|
||||
NSString *key;
|
||||
NSString *value;
|
||||
NSArray *cookies;
|
||||
NSDictionary *d;
|
||||
|
||||
if (nil != _config
|
||||
&& NSHTTPCookieAcceptPolicyNever != [_config HTTPCookieAcceptPolicy]
|
||||
&& nil != [_config HTTPCookieStorage])
|
||||
{
|
||||
headerLine = [[NSString alloc] initWithData: data
|
||||
encoding: NSUTF8StringEncoding];
|
||||
if (0 == [headerLine length])
|
||||
{
|
||||
RELEASE(headerLine);
|
||||
return;
|
||||
}
|
||||
|
||||
r = [headerLine rangeOfString: @":"];
|
||||
if (NSNotFound != r.location)
|
||||
{
|
||||
head = [headerLine substringToIndex:r.location];
|
||||
tail = [headerLine substringFromIndex:r.location + 1];
|
||||
set = [NSCharacterSet whitespaceAndNewlineCharacterSet];
|
||||
key = [head stringByTrimmingCharactersInSet:set];
|
||||
value = [tail stringByTrimmingCharactersInSet:set];
|
||||
|
||||
if (nil != key && nil != value)
|
||||
{
|
||||
d = [NSDictionary dictionaryWithObject: value forKey: key];
|
||||
cookies = [NSHTTPCookie cookiesWithResponseHeaderFields: d
|
||||
forURL: _URL];
|
||||
if ([cookies count] > 0)
|
||||
{
|
||||
[[_config HTTPCookieStorage] setCookies: cookies
|
||||
forURL: _URL
|
||||
mainDocumentURL: nil];
|
||||
}
|
||||
}
|
||||
}
|
||||
RELEASE(headerLine);
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
9
Source/GSHTTPURLProtocol.h
Normal file
9
Source/GSHTTPURLProtocol.h
Normal file
|
@ -0,0 +1,9 @@
|
|||
#ifndef INCLUDED_GSHTTPURLPROTOCOL_H
|
||||
#define INCLUDED_GSHTTPURLPROTOCOL_H
|
||||
|
||||
#import "GSNativeProtocol.h"
|
||||
|
||||
@interface GSHTTPURLProtocol : GSNativeProtocol
|
||||
@end
|
||||
|
||||
#endif
|
1028
Source/GSHTTPURLProtocol.m
Normal file
1028
Source/GSHTTPURLProtocol.m
Normal file
File diff suppressed because it is too large
Load diff
89
Source/GSMultiHandle.h
Normal file
89
Source/GSMultiHandle.h
Normal file
|
@ -0,0 +1,89 @@
|
|||
#ifndef INCLUDED_GSMULTIHANDLE_H
|
||||
#define INCLUDED_GSMULTIHANDLE_H
|
||||
|
||||
#import "common.h"
|
||||
#import <curl/curl.h>
|
||||
#import "GSDispatch.h"
|
||||
|
||||
@class NSURLSessionConfiguration;
|
||||
@class GSEasyHandle;
|
||||
|
||||
/*
|
||||
* Minimal wrapper around curl multi interface
|
||||
* (https://curl.haxx.se/libcurl/c/libcurl-multi.html).
|
||||
*
|
||||
* The the *multi handle* manages the sockets for easy handles
|
||||
* (`GSEasyHandle`), and this implementation uses
|
||||
* libdispatch to listen for sockets being read / write ready.
|
||||
*
|
||||
* Using `dispatch_source_t` allows this implementation to be
|
||||
* non-blocking and all code to run on the same thread
|
||||
* thus keeping is simple.
|
||||
*
|
||||
* - SeeAlso: GSEasyHandle
|
||||
*/
|
||||
@interface GSMultiHandle : NSObject
|
||||
{
|
||||
CURLM *_rawHandle;
|
||||
}
|
||||
|
||||
- (CURLM*) rawHandle;
|
||||
- (instancetype) initWithConfiguration: (NSURLSessionConfiguration*)configuration
|
||||
workQueue: (dispatch_queue_t)workQueque;
|
||||
- (void) addHandle: (GSEasyHandle*)easyHandle;
|
||||
- (void) removeHandle: (GSEasyHandle*)easyHandle;
|
||||
- (void) updateTimeoutTimerToValue: (NSInteger)value;
|
||||
|
||||
@end
|
||||
|
||||
// What read / write ready event to register / unregister.
|
||||
typedef NS_ENUM(NSUInteger, GSSocketRegisterActionType) {
|
||||
GSSocketRegisterActionTypeNone = 0,
|
||||
GSSocketRegisterActionTypeRegisterRead,
|
||||
GSSocketRegisterActionTypeRegisterWrite,
|
||||
GSSocketRegisterActionTypeRegisterReadAndWrite,
|
||||
GSSocketRegisterActionTypeUnregister,
|
||||
};
|
||||
|
||||
@interface GSSocketRegisterAction : NSObject
|
||||
{
|
||||
GSSocketRegisterActionType _type;
|
||||
}
|
||||
|
||||
- (instancetype) initWithRawValue: (int)rawValue;
|
||||
- (GSSocketRegisterActionType) type;
|
||||
- (BOOL) needsReadSource;
|
||||
- (BOOL) needsWriteSource;
|
||||
- (BOOL) needsSource;
|
||||
|
||||
@end
|
||||
|
||||
/*
|
||||
* Read and write libdispatch sources for a specific socket.
|
||||
*
|
||||
* A simple helper that combines two sources -- both being optional.
|
||||
*
|
||||
* This info is stored into the socket using `curl_multi_assign()`.
|
||||
*
|
||||
* - SeeAlso: GSSocketRegisterAction
|
||||
*/
|
||||
@interface GSSocketSources : NSObject
|
||||
{
|
||||
dispatch_source_t _readSource;
|
||||
dispatch_source_t _writeSource;
|
||||
}
|
||||
|
||||
- (void) createSourcesWithAction: (GSSocketRegisterAction *)action
|
||||
socket: (curl_socket_t)socket
|
||||
queue: (dispatch_queue_t)queue
|
||||
handler: (dispatch_block_t)handler;
|
||||
- (dispatch_source_t) createSourceWithType: (dispatch_source_type_t)type
|
||||
socket: (curl_socket_t)socket
|
||||
queue: (dispatch_queue_t)queue
|
||||
handler: (dispatch_block_t)handler;
|
||||
|
||||
+ (instancetype) from: (void*)socketSourcePtr;
|
||||
|
||||
@end
|
||||
|
||||
#endif
|
485
Source/GSMultiHandle.m
Normal file
485
Source/GSMultiHandle.m
Normal file
|
@ -0,0 +1,485 @@
|
|||
#import "GSMultiHandle.h"
|
||||
#import "GSTimeoutSource.h"
|
||||
#import "GSEasyHandle.h"
|
||||
|
||||
#import "Foundation/NSArray.h"
|
||||
#import "Foundation/NSDictionary.h"
|
||||
#import "Foundation/NSError.h"
|
||||
#import "Foundation/NSException.h"
|
||||
#import "Foundation/NSURLError.h"
|
||||
#import "Foundation/NSURLSession.h"
|
||||
#import "Foundation/NSValue.h"
|
||||
|
||||
@interface GSMultiHandle ()
|
||||
- (void) readAndWriteAvailableDataOnSocket: (curl_socket_t)socket;
|
||||
- (void) readMessages;
|
||||
- (void) completedTransferForEasyHandle: (CURL*)rawEasyHandle
|
||||
easyCode: (int)easyCode;
|
||||
- (int32_t) registerWithSocket: (curl_socket_t)socket
|
||||
what: (int)what
|
||||
socketSourcePtr: (void *)socketSourcePtr;
|
||||
@end
|
||||
|
||||
static void handleEasyCode(int code)
|
||||
{
|
||||
if (CURLE_OK != code)
|
||||
{
|
||||
NSString *reason;
|
||||
NSException *e;
|
||||
|
||||
reason = [NSString stringWithFormat: @"An error occurred, CURLcode is %d",
|
||||
code];
|
||||
e = [NSException exceptionWithName: @"libcurl.easy"
|
||||
reason: reason
|
||||
userInfo: nil];
|
||||
[e raise];
|
||||
}
|
||||
}
|
||||
|
||||
static void handleMultiCode(int code)
|
||||
{
|
||||
if (CURLM_OK != code)
|
||||
{
|
||||
NSString *reason;
|
||||
NSException *e;
|
||||
|
||||
reason = [NSString stringWithFormat: @"An error occurred, CURLcode is %d",
|
||||
code];
|
||||
e = [NSException exceptionWithName: @"libcurl.multi"
|
||||
reason: reason
|
||||
userInfo: nil];
|
||||
[e raise];
|
||||
}
|
||||
}
|
||||
|
||||
static int curl_socket_function(CURL *easyHandle, curl_socket_t socket, int what, void *userdata, void *socketptr)
|
||||
{
|
||||
GSMultiHandle *handle = (GSMultiHandle*)userdata;
|
||||
|
||||
return [handle registerWithSocket: socket
|
||||
what: what
|
||||
socketSourcePtr: socketptr];
|
||||
}
|
||||
|
||||
static int curl_timer_function(CURL *easyHandle, int timeout, void *userdata) {
|
||||
GSMultiHandle *handle = (GSMultiHandle*)userdata;
|
||||
|
||||
[handle updateTimeoutTimerToValue: timeout];
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@implementation GSMultiHandle
|
||||
{
|
||||
NSMutableArray *_easyHandles;
|
||||
dispatch_queue_t _queue;
|
||||
GSTimeoutSource *_timeoutSource;
|
||||
}
|
||||
|
||||
- (CURLM*) rawHandle
|
||||
{
|
||||
return _rawHandle;
|
||||
}
|
||||
|
||||
- (instancetype) initWithConfiguration: (NSURLSessionConfiguration*)conf
|
||||
workQueue: (dispatch_queue_t)aQueue
|
||||
{
|
||||
if (nil != (self = [super init]))
|
||||
{
|
||||
_rawHandle = curl_multi_init();
|
||||
_easyHandles = [[NSMutableArray alloc] init];
|
||||
#if HAVE_DISPATCH_QUEUE_CREATE_WITH_TARGET
|
||||
_queue = dispatch_queue_create_with_target("GSMultiHandle.isolation",
|
||||
DISPATCH_QUEUE_SERIAL, aQueue);
|
||||
#else
|
||||
_queue = dispatch_queue_create("GSMultiHandle.isolation",
|
||||
DISPATCH_QUEUE_SERIAL);
|
||||
dispatch_set_target_queue(_queue, aQueue);
|
||||
#endif
|
||||
[self setupCallbacks];
|
||||
[self configureWithConfiguration: conf];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void) dealloc
|
||||
{
|
||||
NSEnumerator *e;
|
||||
GSEasyHandle *handle;
|
||||
|
||||
[_timeoutSource cancel];
|
||||
DESTROY(_timeoutSource);
|
||||
|
||||
dispatch_release(_queue);
|
||||
|
||||
e = [_easyHandles objectEnumerator];
|
||||
while (nil != (handle = [e nextObject]))
|
||||
{
|
||||
curl_multi_remove_handle([handle rawHandle], _rawHandle);
|
||||
}
|
||||
DESTROY(_easyHandles);
|
||||
|
||||
curl_multi_cleanup(_rawHandle);
|
||||
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (void) configureWithConfiguration: (NSURLSessionConfiguration*)configuration
|
||||
{
|
||||
handleEasyCode(curl_multi_setopt(_rawHandle, CURLMOPT_MAX_HOST_CONNECTIONS, [configuration HTTPMaximumConnectionsPerHost]));
|
||||
handleEasyCode(curl_multi_setopt(_rawHandle, CURLMOPT_PIPELINING, [configuration HTTPShouldUsePipelining] ? CURLPIPE_MULTIPLEX : CURLPIPE_NOTHING));
|
||||
}
|
||||
|
||||
- (void)setupCallbacks
|
||||
{
|
||||
handleEasyCode(curl_multi_setopt(_rawHandle, CURLMOPT_SOCKETDATA, (void*)self));
|
||||
handleEasyCode(curl_multi_setopt(_rawHandle, CURLMOPT_SOCKETFUNCTION, curl_socket_function));
|
||||
|
||||
handleEasyCode(curl_multi_setopt(_rawHandle, CURLMOPT_TIMERDATA, (__bridge void *)self));
|
||||
handleEasyCode(curl_multi_setopt(_rawHandle, CURLMOPT_TIMERFUNCTION, curl_timer_function));
|
||||
}
|
||||
|
||||
- (void) addHandle: (GSEasyHandle*)easyHandle
|
||||
{
|
||||
// If this is the first handle being added, we need to `kick` the
|
||||
// underlying multi handle by calling `timeoutTimerFired` as
|
||||
// described in
|
||||
// <https://curl.haxx.se/libcurl/c/curl_multi_socket_action.html>.
|
||||
// That will initiate the registration for timeout timer and socket
|
||||
// readiness.
|
||||
BOOL needsTimeout = false;
|
||||
|
||||
if ([_easyHandles count] == 0)
|
||||
{
|
||||
needsTimeout = YES;
|
||||
}
|
||||
|
||||
[_easyHandles addObject: easyHandle];
|
||||
|
||||
handleMultiCode(curl_multi_add_handle(_rawHandle, [easyHandle rawHandle]));
|
||||
|
||||
if (needsTimeout)
|
||||
{
|
||||
[self timeoutTimerFired];
|
||||
}
|
||||
}
|
||||
|
||||
- (void) removeHandle: (GSEasyHandle*)easyHandle
|
||||
{
|
||||
NSEnumerator *e;
|
||||
int idx = 0;
|
||||
BOOL found = NO;
|
||||
GSEasyHandle *h;
|
||||
|
||||
e = [_easyHandles objectEnumerator];
|
||||
while (nil != (h = [e nextObject]))
|
||||
{
|
||||
if ([h rawHandle] == [easyHandle rawHandle])
|
||||
{
|
||||
found = YES;
|
||||
break;
|
||||
}
|
||||
idx++;
|
||||
}
|
||||
|
||||
NSAssert(found, @"Handle not in list.");
|
||||
|
||||
handleMultiCode(curl_multi_remove_handle(_rawHandle, [easyHandle rawHandle]));
|
||||
[_easyHandles removeObjectAtIndex: idx];
|
||||
}
|
||||
|
||||
- (void) updateTimeoutTimerToValue: (NSInteger)value
|
||||
{
|
||||
// A timeout_ms value of -1 passed to this callback means you should delete
|
||||
// the timer. All other values are valid expire times in number
|
||||
// of milliseconds.
|
||||
if (-1 == value)
|
||||
{
|
||||
[_timeoutSource suspend];
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!_timeoutSource)
|
||||
{
|
||||
_timeoutSource = [[GSTimeoutSource alloc] initWithQueue: _queue
|
||||
handler: ^{
|
||||
[self timeoutTimerFired];
|
||||
}];
|
||||
}
|
||||
[_timeoutSource setTimeout: value];
|
||||
}
|
||||
}
|
||||
|
||||
- (void) timeoutTimerFired
|
||||
{
|
||||
[self readAndWriteAvailableDataOnSocket: CURL_SOCKET_TIMEOUT];
|
||||
}
|
||||
|
||||
- (void) readAndWriteAvailableDataOnSocket: (curl_socket_t)socket
|
||||
{
|
||||
int runningHandlesCount = 0;
|
||||
|
||||
handleMultiCode(curl_multi_socket_action(_rawHandle, socket, 0, &runningHandlesCount));
|
||||
|
||||
[self readMessages];
|
||||
}
|
||||
|
||||
/// Check the status of all individual transfers.
|
||||
///
|
||||
/// libcurl refers to this as “read multi stack informationals”.
|
||||
/// Check for transfers that completed.
|
||||
- (void) readMessages
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
int count = 0;
|
||||
CURLMsg *msg;
|
||||
CURL *easyHandle;
|
||||
int code;
|
||||
|
||||
msg = curl_multi_info_read(_rawHandle, &count);
|
||||
|
||||
if (NULL == msg || CURLMSG_DONE != msg->msg || !msg->easy_handle) break;
|
||||
|
||||
easyHandle = msg->easy_handle;
|
||||
code = msg->data.result;
|
||||
[self completedTransferForEasyHandle: easyHandle easyCode: code];
|
||||
}
|
||||
}
|
||||
|
||||
- (void) completedTransferForEasyHandle: (CURL*)rawEasyHandle
|
||||
easyCode: (int)easyCode
|
||||
{
|
||||
NSEnumerator *e;
|
||||
GSEasyHandle *h;
|
||||
GSEasyHandle *handle = nil;
|
||||
NSError *err = nil;
|
||||
int errCode;
|
||||
|
||||
e = [_easyHandles objectEnumerator];
|
||||
while (nil != (h = [e nextObject]))
|
||||
{
|
||||
if ([h rawHandle] == rawEasyHandle)
|
||||
{
|
||||
handle = h;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
NSAssert(nil != handle, @"Transfer completed for easy handle"
|
||||
@", but it is not in the list of added handles.");
|
||||
|
||||
errCode = [handle urlErrorCodeWithEasyCode: easyCode];
|
||||
if (0 != errCode)
|
||||
{
|
||||
NSString *d = nil;
|
||||
|
||||
if ([handle errorBuffer][0] == 0)
|
||||
{
|
||||
const char *description = curl_easy_strerror(errCode);
|
||||
d = [[NSString alloc] initWithCString: description
|
||||
encoding: NSUTF8StringEncoding];
|
||||
}
|
||||
else
|
||||
{
|
||||
d = [[NSString alloc] initWithCString: [handle errorBuffer]
|
||||
encoding: NSUTF8StringEncoding];
|
||||
}
|
||||
err = [NSError errorWithDomain: NSURLErrorDomain
|
||||
code: errCode
|
||||
userInfo: @{NSLocalizedDescriptionKey : d, NSUnderlyingErrorKey: [NSNumber numberWithInt:easyCode]}];
|
||||
RELEASE(d);
|
||||
}
|
||||
|
||||
[handle transferCompletedWithError: err];
|
||||
}
|
||||
|
||||
- (int32_t) registerWithSocket: (curl_socket_t)socket
|
||||
what: (int)what
|
||||
socketSourcePtr: (void *)socketSourcePtr
|
||||
{
|
||||
// We get this callback whenever we need to register or unregister a
|
||||
// given socket with libdispatch.
|
||||
// The `action` / `what` defines if we should register or unregister
|
||||
// that we're interested in read and/or write readiness. We will do so
|
||||
// through libdispatch (DispatchSource) and store the source(s) inside
|
||||
// a `SocketSources` which we in turn store inside libcurl's multi handle
|
||||
// by means of curl_multi_assign() -- we retain the object first.
|
||||
|
||||
GSSocketRegisterAction *action;
|
||||
GSSocketSources *socketSources;
|
||||
|
||||
action = [[GSSocketRegisterAction alloc] initWithRawValue: what];
|
||||
socketSources = [GSSocketSources from: socketSourcePtr];
|
||||
|
||||
if (nil == socketSources && [action needsSource])
|
||||
{
|
||||
GSSocketSources *s;
|
||||
|
||||
s = [[GSSocketSources alloc] init];
|
||||
curl_multi_assign(_rawHandle, socket, (void*)s);
|
||||
socketSources = s;
|
||||
}
|
||||
else if (nil != socketSources
|
||||
&& GSSocketRegisterActionTypeUnregister == [action type])
|
||||
{
|
||||
DESTROY(socketSources);
|
||||
curl_multi_assign(_rawHandle, socket, NULL);
|
||||
}
|
||||
|
||||
if (nil != socketSources)
|
||||
{
|
||||
[socketSources createSourcesWithAction: action
|
||||
socket: socket
|
||||
queue: _queue
|
||||
handler: ^{
|
||||
[self readAndWriteAvailableDataOnSocket: socket];
|
||||
}];
|
||||
}
|
||||
|
||||
RELEASE(action);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation GSSocketRegisterAction
|
||||
|
||||
- (instancetype) initWithRawValue: (int)rawValue
|
||||
{
|
||||
if (nil != (self = [super init]))
|
||||
{
|
||||
switch (rawValue) {
|
||||
case CURL_POLL_NONE:
|
||||
_type = GSSocketRegisterActionTypeNone;
|
||||
break;
|
||||
case CURL_POLL_IN:
|
||||
_type = GSSocketRegisterActionTypeRegisterRead;
|
||||
break;
|
||||
case CURL_POLL_OUT:
|
||||
_type = GSSocketRegisterActionTypeRegisterWrite;
|
||||
break;
|
||||
case CURL_POLL_INOUT:
|
||||
_type = GSSocketRegisterActionTypeRegisterReadAndWrite;
|
||||
break;
|
||||
case CURL_POLL_REMOVE:
|
||||
_type = GSSocketRegisterActionTypeUnregister;
|
||||
break;
|
||||
default:
|
||||
NSAssert(NO, @"Invalid CURL_POLL value");
|
||||
}
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (GSSocketRegisterActionType) type
|
||||
{
|
||||
return _type;
|
||||
}
|
||||
|
||||
- (BOOL) needsReadSource
|
||||
{
|
||||
switch (self.type)
|
||||
{
|
||||
case GSSocketRegisterActionTypeRegisterRead:
|
||||
case GSSocketRegisterActionTypeRegisterReadAndWrite:
|
||||
return YES;
|
||||
default:
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL) needsWriteSource
|
||||
{
|
||||
switch (self.type)
|
||||
{
|
||||
case GSSocketRegisterActionTypeRegisterWrite:
|
||||
case GSSocketRegisterActionTypeRegisterReadAndWrite:
|
||||
return YES;
|
||||
default:
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)needsSource
|
||||
{
|
||||
return [self needsReadSource] || [self needsWriteSource];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation GSSocketSources
|
||||
|
||||
- (void) dealloc
|
||||
{
|
||||
if (_readSource)
|
||||
{
|
||||
dispatch_source_cancel(_readSource);
|
||||
}
|
||||
_readSource = NULL;
|
||||
|
||||
if (_writeSource)
|
||||
{
|
||||
dispatch_source_cancel(_writeSource);
|
||||
}
|
||||
_writeSource = NULL;
|
||||
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (void) createSourcesWithAction: (GSSocketRegisterAction*)action
|
||||
socket: (curl_socket_t)socket
|
||||
queue: (dispatch_queue_t)queue
|
||||
handler: (dispatch_block_t)handler
|
||||
{
|
||||
if (!_readSource && [action needsReadSource])
|
||||
{
|
||||
_readSource = [self createSourceWithType: DISPATCH_SOURCE_TYPE_READ
|
||||
socket: socket
|
||||
queue: queue
|
||||
handler: handler];
|
||||
}
|
||||
|
||||
if (!_writeSource && [action needsWriteSource])
|
||||
{
|
||||
_writeSource = [self createSourceWithType: DISPATCH_SOURCE_TYPE_WRITE
|
||||
socket: socket
|
||||
queue: queue
|
||||
handler: handler];
|
||||
}
|
||||
}
|
||||
|
||||
- (dispatch_source_t) createSourceWithType: (dispatch_source_type_t)type
|
||||
socket: (curl_socket_t)socket
|
||||
queue: (dispatch_queue_t)queue
|
||||
handler: (dispatch_block_t)handler
|
||||
{
|
||||
dispatch_source_t source;
|
||||
|
||||
source = dispatch_source_create(type, socket, 0, queue);
|
||||
dispatch_source_set_event_handler(source, handler);
|
||||
dispatch_source_set_cancel_handler(source, ^{
|
||||
dispatch_release(source);
|
||||
});
|
||||
dispatch_resume(source);
|
||||
|
||||
return source;
|
||||
}
|
||||
|
||||
|
||||
+ (instancetype) from: (void*)socketSourcePtr
|
||||
{
|
||||
if (!socketSourcePtr)
|
||||
{
|
||||
return nil;
|
||||
}
|
||||
else
|
||||
{
|
||||
return (GSSocketSources*)socketSourcePtr;
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
120
Source/GSNativeProtocol.h
Normal file
120
Source/GSNativeProtocol.h
Normal file
|
@ -0,0 +1,120 @@
|
|||
#ifndef INCLUDED_GSNATIVEPROTOCOL_H
|
||||
#define INCLUDED_GSNATIVEPROTOCOL_H
|
||||
|
||||
#import "GSDispatch.h"
|
||||
#import "GSEasyHandle.h"
|
||||
#import "Foundation/NSURLProtocol.h"
|
||||
#import "Foundation/NSURLSession.h"
|
||||
|
||||
@class GSTransferState;
|
||||
|
||||
@interface NSURLSessionTask (GSNativeProtocolInternal)
|
||||
|
||||
- (void) setCurrentRequest: (NSURLRequest*)request;
|
||||
|
||||
- (dispatch_queue_t) workQueue;
|
||||
|
||||
- (NSUInteger) suspendCount;
|
||||
|
||||
- (void) getBodyWithCompletion: (void (^)(GSURLSessionTaskBody *body))completion;
|
||||
|
||||
- (GSURLSessionTaskBody*) knownBody;
|
||||
- (void) setKnownBody: (GSURLSessionTaskBody*)body;
|
||||
|
||||
- (void) setError: (NSError*)error;
|
||||
|
||||
- (void) setCountOfBytesReceived: (int64_t)count;
|
||||
|
||||
- (void) setCountOfBytesExpectedToReceive: (int64_t)count;
|
||||
|
||||
- (void) setCountOfBytesExpectedToSend: (int64_t)count;
|
||||
|
||||
- (void (^)(NSData *data, NSURLResponse *response, NSError *error)) dataCompletionHandler;
|
||||
|
||||
- (void (^)(NSURL *location, NSURLResponse *response, NSError *error)) downloadCompletionHandler;
|
||||
|
||||
@end
|
||||
|
||||
typedef NS_ENUM(NSUInteger, GSCompletionActionType) {
|
||||
GSCompletionActionTypeCompleteTask,
|
||||
GSCompletionActionTypeFailWithError,
|
||||
GSCompletionActionTypeRedirectWithRequest,
|
||||
};
|
||||
|
||||
// Action to be taken after a transfer completes
|
||||
@interface GSCompletionAction : NSObject
|
||||
{
|
||||
GSCompletionActionType _type;
|
||||
int _errorCode;
|
||||
NSURLRequest *_redirectRequest;
|
||||
}
|
||||
|
||||
- (GSCompletionActionType) type;
|
||||
- (void) setType: (GSCompletionActionType) type;
|
||||
|
||||
- (int) errorCode;
|
||||
- (void) setErrorCode: (int)code;
|
||||
|
||||
- (NSURLRequest*) redirectRequest;
|
||||
- (void) setRedirectRequest: (NSURLRequest*)request;
|
||||
|
||||
@end
|
||||
|
||||
typedef NS_ENUM(NSUInteger, GSNativeProtocolInternalState) {
|
||||
// Task has been created, but nothing has been done, yet
|
||||
GSNativeProtocolInternalStateInitial,
|
||||
// The task is being fulfilled from the cache rather than the network.
|
||||
GSNativeProtocolInternalStateFulfillingFromCache,
|
||||
// The easy handle has been fully configured. But it is not added to
|
||||
// the multi handle.
|
||||
GSNativeProtocolInternalStateTransferReady,
|
||||
// The easy handle is currently added to the multi handle
|
||||
GSNativeProtocolInternalStateTransferInProgress,
|
||||
// The transfer completed.
|
||||
// The easy handle has been removed from the multi handle. This does
|
||||
// not necessarily mean the task completed. A task that gets
|
||||
// redirected will do multiple transfers.
|
||||
GSNativeProtocolInternalStateTransferCompleted,
|
||||
// The transfer failed.
|
||||
// Same as `GSNativeProtocolInternalStateTransferCompleted`,
|
||||
// but without response / body data
|
||||
GSNativeProtocolInternalStateTransferFailed,
|
||||
// Waiting for the completion handler of the HTTP redirect callback.
|
||||
// When we tell the delegate that we're about to perform an HTTP
|
||||
// redirect, we need to wait for the delegate to let us know what
|
||||
// action to take.
|
||||
GSNativeProtocolInternalStateWaitingForRedirectCompletionHandler,
|
||||
// Waiting for the completion handler of the 'did receive response' callback.
|
||||
// When we tell the delegate that we received a response (i.e. when
|
||||
// we received a complete header), we need to wait for the delegate to
|
||||
// let us know what action to take. In this state the easy handle is
|
||||
// paused in order to suspend delegate callbacks.
|
||||
GSNativeProtocolInternalStateWaitingForResponseCompletionHandler,
|
||||
// The task is completed
|
||||
// Contrast this with `GSNativeProtocolInternalStateTransferCompleted`.
|
||||
GSNativeProtocolInternalStateTaskCompleted,
|
||||
};
|
||||
|
||||
// This abstract class has the common implementation of Native protocols like
|
||||
// HTTP, FTP, etc.
|
||||
// These are libcurl helpers for the URLSession API code.
|
||||
@interface GSNativeProtocol : NSURLProtocol <GSEasyHandleDelegate>
|
||||
{
|
||||
GSEasyHandle *_easyHandle;
|
||||
GSNativeProtocolInternalState _internalState;
|
||||
GSTransferState *_transferState;
|
||||
}
|
||||
|
||||
- (void) setInternalState: (GSNativeProtocolInternalState)newState;
|
||||
|
||||
- (void) failWithError: (NSError*)error request: (NSURLRequest*)request;
|
||||
|
||||
- (void) completeTaskWithError: (NSError*)error;
|
||||
|
||||
- (void) completeTask;
|
||||
|
||||
- (void) startNewTransferWithRequest: (NSURLRequest*)request;
|
||||
|
||||
@end
|
||||
|
||||
#endif
|
818
Source/GSNativeProtocol.m
Normal file
818
Source/GSNativeProtocol.m
Normal file
|
@ -0,0 +1,818 @@
|
|||
#import "GSURLPrivate.h"
|
||||
#import "GSNativeProtocol.h"
|
||||
#import "GSTransferState.h"
|
||||
#import "GSURLSessionTaskBody.h"
|
||||
#import "Foundation/NSData.h"
|
||||
#import "Foundation/NSDictionary.h"
|
||||
#import "Foundation/NSError.h"
|
||||
#import "Foundation/NSException.h"
|
||||
#import "Foundation/NSOperation.h"
|
||||
#import "Foundation/NSURL.h"
|
||||
#import "Foundation/NSURLError.h"
|
||||
#import "Foundation/NSURLSession.h"
|
||||
#import "Foundation/NSFileHandle.h"
|
||||
|
||||
|
||||
static BOOL isEasyHandlePaused(GSNativeProtocolInternalState state)
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case GSNativeProtocolInternalStateInitial:
|
||||
return NO;
|
||||
case GSNativeProtocolInternalStateFulfillingFromCache:
|
||||
return NO;
|
||||
case GSNativeProtocolInternalStateTransferReady:
|
||||
return NO;
|
||||
case GSNativeProtocolInternalStateTransferInProgress:
|
||||
return NO;
|
||||
case GSNativeProtocolInternalStateTransferCompleted:
|
||||
return NO;
|
||||
case GSNativeProtocolInternalStateTransferFailed:
|
||||
return NO;
|
||||
case GSNativeProtocolInternalStateWaitingForRedirectCompletionHandler:
|
||||
return NO;
|
||||
case GSNativeProtocolInternalStateWaitingForResponseCompletionHandler:
|
||||
return YES;
|
||||
case GSNativeProtocolInternalStateTaskCompleted:
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
|
||||
static BOOL isEasyHandleAddedToMultiHandle(GSNativeProtocolInternalState state)
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case GSNativeProtocolInternalStateInitial:
|
||||
return NO;
|
||||
case GSNativeProtocolInternalStateFulfillingFromCache:
|
||||
return NO;
|
||||
case GSNativeProtocolInternalStateTransferReady:
|
||||
return NO;
|
||||
case GSNativeProtocolInternalStateTransferInProgress:
|
||||
return YES;
|
||||
case GSNativeProtocolInternalStateTransferCompleted:
|
||||
return NO;
|
||||
case GSNativeProtocolInternalStateTransferFailed:
|
||||
return NO;
|
||||
case GSNativeProtocolInternalStateWaitingForRedirectCompletionHandler:
|
||||
return NO;
|
||||
case GSNativeProtocolInternalStateWaitingForResponseCompletionHandler:
|
||||
return YES;
|
||||
case GSNativeProtocolInternalStateTaskCompleted:
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
|
||||
@interface NSURLSession (GSNativeProtocolInternal)
|
||||
|
||||
- (void) removeHandle: (GSEasyHandle*)handle;
|
||||
|
||||
- (void) addHandle: (GSEasyHandle*)handle;
|
||||
|
||||
@end
|
||||
|
||||
@implementation NSURLSession (GSNativeProtocolInternal)
|
||||
|
||||
- (void) removeHandle: (GSEasyHandle*)handle
|
||||
{
|
||||
[_multiHandle removeHandle: handle];
|
||||
}
|
||||
|
||||
- (void) addHandle: (GSEasyHandle*)handle
|
||||
{
|
||||
[_multiHandle addHandle: handle];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation NSURLSessionTask (GSNativeProtocolInternal)
|
||||
|
||||
- (void) setCurrentRequest: (NSURLRequest*)request
|
||||
{
|
||||
ASSIGN(_currentRequest, request);
|
||||
}
|
||||
|
||||
- (dispatch_queue_t) workQueue
|
||||
{
|
||||
return _workQueue;
|
||||
}
|
||||
|
||||
- (NSUInteger) suspendCount
|
||||
{
|
||||
return _suspendCount;
|
||||
}
|
||||
|
||||
- (void) getBodyWithCompletion: (void (^)(GSURLSessionTaskBody *body))completion
|
||||
{
|
||||
GSURLSessionTaskBody *body;
|
||||
|
||||
if (nil != _knownBody)
|
||||
{
|
||||
completion(_knownBody);
|
||||
return;
|
||||
};
|
||||
|
||||
body = AUTORELEASE([[GSURLSessionTaskBody alloc] init]);
|
||||
completion(body);
|
||||
}
|
||||
|
||||
- (GSURLSessionTaskBody*) knownBody
|
||||
{
|
||||
return _knownBody;
|
||||
}
|
||||
|
||||
- (void) setKnownBody: (GSURLSessionTaskBody*)body
|
||||
{
|
||||
ASSIGN(_knownBody, body);
|
||||
}
|
||||
|
||||
- (void) setError: (NSError*)error
|
||||
{
|
||||
ASSIGN(_error, error);
|
||||
}
|
||||
|
||||
- (void) setCountOfBytesReceived: (int64_t)count
|
||||
{
|
||||
_countOfBytesReceived = count;
|
||||
}
|
||||
|
||||
- (void) setCountOfBytesExpectedToReceive: (int64_t)count
|
||||
{
|
||||
_countOfBytesExpectedToReceive = count;
|
||||
}
|
||||
|
||||
- (void) setCountOfBytesExpectedToSend: (int64_t)count
|
||||
{
|
||||
_countOfBytesExpectedToSend = count;
|
||||
}
|
||||
|
||||
- (void (^)(NSData *data, NSURLResponse *response, NSError *error)) dataCompletionHandler
|
||||
{
|
||||
return _dataCompletionHandler;
|
||||
}
|
||||
|
||||
- (void (^)(NSURL *location, NSURLResponse *response, NSError *error)) downloadCompletionHandler
|
||||
{
|
||||
return _downloadCompletionHandler;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation GSCompletionAction
|
||||
|
||||
- (void) dealloc
|
||||
{
|
||||
DESTROY(_redirectRequest);
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (GSCompletionActionType) type
|
||||
{
|
||||
return _type;
|
||||
}
|
||||
|
||||
- (void) setType: (GSCompletionActionType) type
|
||||
{
|
||||
_type = type;
|
||||
}
|
||||
|
||||
- (int) errorCode
|
||||
{
|
||||
return _errorCode;
|
||||
}
|
||||
|
||||
- (void) setErrorCode: (int)code
|
||||
{
|
||||
_errorCode = code;
|
||||
}
|
||||
|
||||
- (NSURLRequest*) redirectRequest
|
||||
{
|
||||
return _redirectRequest;
|
||||
}
|
||||
|
||||
- (void) setRedirectRequest: (NSURLRequest*)request
|
||||
{
|
||||
ASSIGN(_redirectRequest, request);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation GSNativeProtocol
|
||||
|
||||
+ (BOOL) canInitWithRequest: (NSURLRequest*)request
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (instancetype) initWithTask: (NSURLSessionTask*)_task
|
||||
cachedResponse: (NSCachedURLResponse*)_cachedResponse
|
||||
client: (id<NSURLProtocolClient>)_client
|
||||
{
|
||||
if (nil != (self = [super initWithTask: _task
|
||||
cachedResponse: _cachedResponse
|
||||
client: _client]))
|
||||
{
|
||||
_internalState = GSNativeProtocolInternalStateInitial;
|
||||
_easyHandle = [[GSEasyHandle alloc] initWithDelegate: self];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void) dealloc
|
||||
{
|
||||
DESTROY(_easyHandle);
|
||||
DESTROY(_transferState);
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
+ (NSURLRequest*) canonicalRequestForRequest: (NSURLRequest*)request
|
||||
{
|
||||
return request;
|
||||
}
|
||||
|
||||
- (void) startLoading
|
||||
{
|
||||
[self resume];
|
||||
}
|
||||
|
||||
- (void) stopLoading
|
||||
{
|
||||
NSURLSessionTask *task;
|
||||
|
||||
if (nil != (task = [self task])
|
||||
&& NSURLSessionTaskStateSuspended == [task state])
|
||||
{
|
||||
[self suspend];
|
||||
}
|
||||
else
|
||||
{
|
||||
[self setInternalState: GSNativeProtocolInternalStateTransferFailed];
|
||||
NSAssert(nil != [task error], @"Missing error for failed task");
|
||||
[self completeTaskWithError: [task error]];
|
||||
}
|
||||
}
|
||||
|
||||
- (void) setInternalState: (GSNativeProtocolInternalState)newState
|
||||
{
|
||||
NSURLSessionTask *task;
|
||||
GSNativeProtocolInternalState oldState;
|
||||
|
||||
if (!isEasyHandlePaused(_internalState) && isEasyHandlePaused(newState))
|
||||
{
|
||||
NSAssert(NO, @"Need to solve pausing receive.");
|
||||
}
|
||||
|
||||
if (isEasyHandleAddedToMultiHandle(_internalState)
|
||||
&& !isEasyHandleAddedToMultiHandle(newState))
|
||||
{
|
||||
if (nil != (task = [self task]))
|
||||
{
|
||||
[[task session] removeHandle: _easyHandle];
|
||||
}
|
||||
}
|
||||
|
||||
oldState = _internalState;
|
||||
_internalState = newState;
|
||||
|
||||
if (!isEasyHandleAddedToMultiHandle(oldState)
|
||||
&& isEasyHandleAddedToMultiHandle(_internalState))
|
||||
{
|
||||
if (nil != (task = [self task]))
|
||||
{
|
||||
[[task session] addHandle: _easyHandle];
|
||||
}
|
||||
}
|
||||
if (isEasyHandlePaused(oldState) && !isEasyHandlePaused(_internalState))
|
||||
{
|
||||
NSAssert(NO, @"Need to solve pausing receive.");
|
||||
}
|
||||
}
|
||||
|
||||
- (void) startNewTransferWithRequest: (NSURLRequest*)request
|
||||
{
|
||||
NSURLSessionTask *task = [self task];
|
||||
|
||||
[task setCurrentRequest: request];
|
||||
|
||||
NSAssert(nil != [request URL], @"No URL in request.");
|
||||
|
||||
[task getBodyWithCompletion: ^(GSURLSessionTaskBody *body)
|
||||
{
|
||||
[task setKnownBody: body];
|
||||
[self setInternalState: GSNativeProtocolInternalStateTransferReady];
|
||||
ASSIGN(_transferState,
|
||||
[self createTransferStateWithURL: [request URL]
|
||||
body: body
|
||||
workQueue: [task workQueue]]);
|
||||
[self configureEasyHandleForRequest: request body: body];
|
||||
if ([task suspendCount] < 1)
|
||||
{
|
||||
[self resume];
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (void) configureEasyHandleForRequest: (NSURLRequest*)request
|
||||
body: (GSURLSessionTaskBody*)body
|
||||
{
|
||||
NSAssert(NO, @"Requires concrete implementation");
|
||||
}
|
||||
|
||||
- (GSTransferState*) createTransferStateWithURL: (NSURL*)url
|
||||
body: (GSURLSessionTaskBody*)body
|
||||
workQueue: (dispatch_queue_t)workQueue
|
||||
{
|
||||
GSDataDrain *drain = [self createTransferBodyDataDrain];
|
||||
|
||||
switch ([body type])
|
||||
{
|
||||
case GSURLSessionTaskBodyTypeNone:
|
||||
return AUTORELEASE([[GSTransferState alloc] initWithURL: url
|
||||
bodyDataDrain: drain]);
|
||||
case GSURLSessionTaskBodyTypeData:
|
||||
{
|
||||
GSBodyDataSource *source;
|
||||
|
||||
source = AUTORELEASE([[GSBodyDataSource alloc] initWithData: [body data]]);
|
||||
return AUTORELEASE([[GSTransferState alloc] initWithURL: url
|
||||
bodyDataDrain: drain
|
||||
bodySource: source]);
|
||||
}
|
||||
case GSURLSessionTaskBodyTypeFile:
|
||||
{
|
||||
GSBodyFileSource *source;
|
||||
|
||||
source = AUTORELEASE([[GSBodyFileSource alloc] initWithFileURL: [body fileURL]
|
||||
workQueue: workQueue
|
||||
dataAvailableHandler: ^{
|
||||
[_easyHandle unpauseSend];
|
||||
}]);
|
||||
return AUTORELEASE([[GSTransferState alloc] initWithURL: url
|
||||
bodyDataDrain: drain
|
||||
bodySource: source]);
|
||||
}
|
||||
case GSURLSessionTaskBodyTypeStream:
|
||||
{
|
||||
GSBodyStreamSource *source;
|
||||
|
||||
source = AUTORELEASE([[GSBodyStreamSource alloc] initWithInputStream: [body inputStream]]);
|
||||
return AUTORELEASE([[GSTransferState alloc] initWithURL: url
|
||||
bodyDataDrain: drain
|
||||
bodySource: source]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The data drain.
|
||||
// This depends on what the task needs.
|
||||
- (GSDataDrain*) createTransferBodyDataDrain
|
||||
{
|
||||
NSURLSessionTask *task = [self task];
|
||||
GSDataDrain *dd = AUTORELEASE([[GSDataDrain alloc] init]);
|
||||
|
||||
if ([task isKindOfClass: [NSURLSessionDownloadTask class]])
|
||||
{
|
||||
// drain to file for download tasks
|
||||
[dd setType: GSDataDrainTypeToFile];
|
||||
}
|
||||
else if ([task dataCompletionHandler])
|
||||
{
|
||||
// drain to memory if task has a completion handler, which requires the
|
||||
// full body to be passed on completion
|
||||
[dd setType: GSDataDrainInMemory];
|
||||
}
|
||||
else
|
||||
{
|
||||
// otherwise the data is probably sent to the delegate as it arrives
|
||||
[dd setType: GSDataDrainTypeIgnore];
|
||||
}
|
||||
return dd;
|
||||
}
|
||||
|
||||
- (void) resume
|
||||
{
|
||||
NSURLSessionTask *task;
|
||||
|
||||
task = [self task];
|
||||
|
||||
if (_internalState == GSNativeProtocolInternalStateInitial)
|
||||
{
|
||||
NSAssert(nil != [task originalRequest], @"Task has no original request.");
|
||||
|
||||
// Check if the cached response is good to use:
|
||||
if (nil != [self cachedResponse]
|
||||
&& [self canRespondFromCacheUsing: [self cachedResponse]])
|
||||
{
|
||||
[self setInternalState:
|
||||
GSNativeProtocolInternalStateFulfillingFromCache];
|
||||
dispatch_async([task workQueue],
|
||||
^{
|
||||
id<NSURLProtocolClient> client;
|
||||
|
||||
client = [self client];
|
||||
[client URLProtocol: self
|
||||
cachedResponseIsValid: [self cachedResponse]];
|
||||
[client URLProtocol: self
|
||||
didReceiveResponse: [[self cachedResponse] response]
|
||||
cacheStoragePolicy: NSURLCacheStorageNotAllowed];
|
||||
if ([[[self cachedResponse] data] length] > 0)
|
||||
{
|
||||
if ([client respondsToSelector:
|
||||
@selector(URLProtocol:didLoad:)])
|
||||
{
|
||||
[client URLProtocol: self
|
||||
didLoadData: [[self cachedResponse] data]];
|
||||
}
|
||||
}
|
||||
if ([client respondsToSelector:
|
||||
@selector(URLProtocolDidFinishLoading:)])
|
||||
{
|
||||
[client URLProtocolDidFinishLoading: self];
|
||||
}
|
||||
[self setInternalState:
|
||||
GSNativeProtocolInternalStateTaskCompleted];
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
[self startNewTransferWithRequest: [task originalRequest]];
|
||||
}
|
||||
}
|
||||
|
||||
if (_internalState == GSNativeProtocolInternalStateTransferReady
|
||||
&& nil != _transferState)
|
||||
{
|
||||
[self setInternalState: GSNativeProtocolInternalStateTransferInProgress];
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL) canRespondFromCacheUsing: (NSCachedURLResponse*)response
|
||||
{
|
||||
// Allows a native protocol to process a cached response.
|
||||
// If `YES` is returned, the protocol will replay the cached response
|
||||
// instead of starting a new transfer. The default implementation invalidates
|
||||
// the response in the cache and returns `NO`.
|
||||
NSURLCache *cache;
|
||||
NSURLSessionTask *task;
|
||||
|
||||
task = [self task];
|
||||
cache = [[[task session] configuration] URLCache];
|
||||
if (nil != cache && [task isKindOfClass: [NSURLSessionDataTask class]])
|
||||
{
|
||||
[cache removeCachedResponseForDataTask: (NSURLSessionDataTask*)task];
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (void) suspend
|
||||
{
|
||||
if (_internalState == GSNativeProtocolInternalStateTransferInProgress)
|
||||
{
|
||||
[self setInternalState: GSNativeProtocolInternalStateTransferReady];
|
||||
}
|
||||
}
|
||||
|
||||
- (void) completeTaskWithError: (NSError*)error
|
||||
{
|
||||
[[self task] setError: error];
|
||||
NSAssert(_internalState == GSNativeProtocolInternalStateTransferFailed,
|
||||
@"Trying to complete the task, but its transfer isn't complete / failed.");
|
||||
|
||||
// We don't want a timeout to be triggered after this.
|
||||
// The timeout timer needs to be cancelled.
|
||||
[_easyHandle setTimeoutTimer: nil];
|
||||
|
||||
[self setInternalState: GSNativeProtocolInternalStateTaskCompleted];
|
||||
}
|
||||
|
||||
- (GSEasyHandleAction) didReceiveData: (NSData*)data
|
||||
{
|
||||
NSURLResponse *response;
|
||||
|
||||
NSAssert(GSNativeProtocolInternalStateTransferInProgress == _internalState,
|
||||
@"Received body data, but no transfer in progress.");
|
||||
|
||||
response = [self validateHeaderCompleteTransferState: _transferState];
|
||||
|
||||
if (nil != response)
|
||||
{
|
||||
[_transferState setResponse: response];
|
||||
}
|
||||
|
||||
[self notifyDelegateAboutReceivedData: data];
|
||||
|
||||
_internalState = GSNativeProtocolInternalStateTransferInProgress;
|
||||
ASSIGN(_transferState, [_transferState byAppendingBodyData: data]);
|
||||
|
||||
return GSEasyHandleActionProceed;
|
||||
}
|
||||
|
||||
- (NSURLResponse*) validateHeaderCompleteTransferState: (GSTransferState*)ts
|
||||
{
|
||||
if (![ts isHeaderComplete])
|
||||
{
|
||||
NSAssert(NO, @"Received body data, but the header is not complete, yet.");
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (void) notifyDelegateAboutReceivedData: (NSData*)data
|
||||
{
|
||||
NSURLSession *session;
|
||||
NSURLSessionTask *task;
|
||||
id<NSURLSessionDelegate> delegate;
|
||||
|
||||
task = [self task];
|
||||
NSAssert(nil != task, @"Cannot notify");
|
||||
|
||||
session = [task session];
|
||||
NSAssert(nil != session, @"Missing session");
|
||||
|
||||
/* Calculate received data length */
|
||||
[task setCountOfBytesReceived: (int64_t)[data length] + [task countOfBytesReceived]];
|
||||
|
||||
delegate = [session delegate];
|
||||
if (nil != delegate
|
||||
&& [task isKindOfClass: [NSURLSessionDataTask class]]
|
||||
&& [delegate respondsToSelector: @selector(URLSession:dataTask:didReceiveData:)])
|
||||
{
|
||||
id<NSURLSessionDataDelegate> dataDelegate;
|
||||
NSURLSessionDataTask *dataTask;
|
||||
|
||||
dataDelegate = (id<NSURLSessionDataDelegate>)delegate;
|
||||
dataTask = (NSURLSessionDataTask*)task;
|
||||
|
||||
[[session delegateQueue] addOperationWithBlock:
|
||||
^{
|
||||
[dataDelegate URLSession: session
|
||||
dataTask: dataTask
|
||||
didReceiveData: data];
|
||||
}];
|
||||
}
|
||||
|
||||
if (nil != delegate
|
||||
&& [task isKindOfClass: [NSURLSessionDownloadTask class]]
|
||||
&& [delegate respondsToSelector: @selector
|
||||
(URLSession:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite:)])
|
||||
{
|
||||
id<NSURLSessionDownloadDelegate> downloadDelegate;
|
||||
NSURLSessionDownloadTask *downloadTask;
|
||||
|
||||
downloadDelegate = (id<NSURLSessionDownloadDelegate>)delegate;
|
||||
downloadTask = (NSURLSessionDownloadTask*)task;
|
||||
|
||||
[[session delegateQueue] addOperationWithBlock:
|
||||
^{
|
||||
[downloadDelegate URLSession: session
|
||||
downloadTask: downloadTask
|
||||
didWriteData: (int64_t)[data length]
|
||||
totalBytesWritten: [task countOfBytesReceived]
|
||||
totalBytesExpectedToWrite: [task countOfBytesExpectedToReceive]];
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
- (void) notifyDelegateAboutUploadedDataCount: (int64_t)count
|
||||
{
|
||||
NSURLSessionTask *task;
|
||||
id<NSURLSessionDelegate> delegate;
|
||||
|
||||
task = [self task];
|
||||
|
||||
NSAssert(nil != task, @"Cannot notify");
|
||||
|
||||
delegate = [[task session] delegate];
|
||||
if (nil != delegate
|
||||
&& [task isKindOfClass: [NSURLSessionUploadTask class]]
|
||||
&& [delegate respondsToSelector: @selector(URLSession:task:didSendBodyData:totalBytesSent:totalBytesExpectedToSend:)])
|
||||
{
|
||||
id<NSURLSessionTaskDelegate> taskDelegate;
|
||||
NSURLSession *session;
|
||||
|
||||
session = [task session];
|
||||
NSAssert(nil != session, @"Missing session");
|
||||
taskDelegate = (id<NSURLSessionTaskDelegate>)delegate;
|
||||
[[session delegateQueue] addOperationWithBlock:
|
||||
^{
|
||||
[taskDelegate URLSession: session
|
||||
task: task
|
||||
didSendBodyData: count
|
||||
totalBytesSent: [task countOfBytesSent]
|
||||
totalBytesExpectedToSend: [task countOfBytesExpectedToSend]];
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
- (GSEasyHandleAction) didReceiveHeaderData: (NSData*)data
|
||||
contentLength: (int64_t)contentLength
|
||||
{
|
||||
NSAssert(NO, @"Require concrete implementation");
|
||||
return GSEasyHandleActionAbort;
|
||||
}
|
||||
|
||||
- (void) fillWriteBufferLength: (NSInteger)length
|
||||
result: (void (^)(GSEasyHandleWriteBufferResult result, NSInteger length, NSData *data))result
|
||||
{
|
||||
id<GSURLSessionTaskBodySource> source;
|
||||
|
||||
NSAssert(GSNativeProtocolInternalStateTransferInProgress == _internalState,
|
||||
@"Requested to fill write buffer, but transfer isn't in progress.");
|
||||
|
||||
source = [_transferState requestBodySource];
|
||||
|
||||
NSAssert(nil != source,
|
||||
@"Requested to fill write buffer, but transfer state has no body source.");
|
||||
|
||||
if (nil == result)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
[source getNextChunkWithLength: length
|
||||
completionHandler: ^(GSBodySourceDataChunk chunk, NSData *_Nullable data)
|
||||
{
|
||||
switch (chunk)
|
||||
{
|
||||
case GSBodySourceDataChunkData:
|
||||
{
|
||||
NSUInteger count = [data length];
|
||||
[self notifyDelegateAboutUploadedDataCount: (int64_t)count];
|
||||
result(GSEasyHandleWriteBufferResultBytes, count, data);
|
||||
break;
|
||||
}
|
||||
case GSBodySourceDataChunkDone:
|
||||
result(GSEasyHandleWriteBufferResultBytes, 0, nil);
|
||||
break;
|
||||
case GSBodySourceDataChunkRetryLater:
|
||||
// At this point we'll try to pause the easy handle. The body
|
||||
// source is responsible for un-pausing the handle once data
|
||||
// becomes available.
|
||||
result(GSEasyHandleWriteBufferResultPause, -1, nil);
|
||||
break;
|
||||
case GSBodySourceDataChunkError:
|
||||
result(GSEasyHandleWriteBufferResultAbort, -1, nil);
|
||||
break;
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (void) transferCompletedWithError: (NSError*)error
|
||||
{
|
||||
/* At this point the transfer is complete and we can decide what to do.
|
||||
* If everything went well, we will simply forward the resulting data
|
||||
* to the delegate. But in case of redirects etc. we might send another
|
||||
* request.
|
||||
*/
|
||||
NSURLRequest *request;
|
||||
NSURLResponse *response;
|
||||
GSCompletionAction *action;
|
||||
|
||||
if (nil != error)
|
||||
{
|
||||
[self setInternalState: GSNativeProtocolInternalStateTransferFailed];
|
||||
[self failWithError: error request: [self request]];
|
||||
return;
|
||||
}
|
||||
|
||||
NSAssert(_internalState == GSNativeProtocolInternalStateTransferInProgress,
|
||||
@"Transfer completed, but it wasn't in progress.");
|
||||
|
||||
request = [[self task] currentRequest];
|
||||
NSAssert(nil != request,
|
||||
@"Transfer completed, but there's no current request.");
|
||||
|
||||
if (nil != [[self task] response])
|
||||
{
|
||||
[_transferState setResponse: [[self task] response]];
|
||||
}
|
||||
|
||||
response = [_transferState response];
|
||||
NSAssert(nil != response, @"Transfer completed, but there's no response.");
|
||||
|
||||
[self setInternalState: GSNativeProtocolInternalStateTransferCompleted];
|
||||
|
||||
action = [self completeActionForCompletedRequest: request response: response];
|
||||
switch ([action type])
|
||||
{
|
||||
case GSCompletionActionTypeCompleteTask:
|
||||
[self completeTask];
|
||||
break;
|
||||
case GSCompletionActionTypeFailWithError:
|
||||
[self setInternalState: GSNativeProtocolInternalStateTransferFailed];
|
||||
error = [NSError errorWithDomain: NSURLErrorDomain
|
||||
code: [action errorCode]
|
||||
userInfo: nil];
|
||||
[self failWithError: error request: request];
|
||||
break;
|
||||
case GSCompletionActionTypeRedirectWithRequest:
|
||||
[self redirectForRequest: [action redirectRequest]];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
- (GSCompletionAction*) completeActionForCompletedRequest: (NSURLRequest*)request
|
||||
response: (NSURLResponse*)response
|
||||
{
|
||||
GSCompletionAction *action;
|
||||
|
||||
action = AUTORELEASE([[GSCompletionAction alloc] init]);
|
||||
[action setType: GSCompletionActionTypeCompleteTask];
|
||||
|
||||
return action;
|
||||
}
|
||||
|
||||
- (void) completeTask
|
||||
{
|
||||
NSURLSessionTask *task;
|
||||
GSDataDrain *bodyDataDrain;
|
||||
id<NSURLProtocolClient> client;
|
||||
|
||||
NSAssert(_internalState == GSNativeProtocolInternalStateTransferCompleted,
|
||||
@"Trying to complete the task, but its transfer isn't complete.");
|
||||
|
||||
task = [self task];
|
||||
[task setResponse: [_transferState response]];
|
||||
client = [self client];
|
||||
|
||||
// We don't want a timeout to be triggered after this. The timeout timer
|
||||
// needs to be cancelled.
|
||||
[_easyHandle setTimeoutTimer: nil];
|
||||
|
||||
[self setInternalState: GSNativeProtocolInternalStateTaskCompleted];
|
||||
|
||||
// Add complete data to NSURLRequestProperties if the task has a data
|
||||
// completion handler
|
||||
bodyDataDrain = [_transferState bodyDataDrain];
|
||||
if (GSDataDrainInMemory == [bodyDataDrain type])
|
||||
{
|
||||
NSData *data = AUTORELEASE([[bodyDataDrain data] copy]);
|
||||
[[self request] _setProperty: data
|
||||
forKey: @"tempData"];
|
||||
}
|
||||
|
||||
// Add temporary file URL to NSURLRequest properties
|
||||
// and close the fileHandle
|
||||
if ([task isKindOfClass: [NSURLSessionDownloadTask class]])
|
||||
{
|
||||
[[bodyDataDrain fileHandle] closeFile];
|
||||
[[self request] _setProperty: [bodyDataDrain fileURL]
|
||||
forKey: @"tempFileURL"];
|
||||
}
|
||||
|
||||
if ([client respondsToSelector: @selector(URLProtocolDidFinishLoading:)])
|
||||
{
|
||||
[client URLProtocolDidFinishLoading: self];
|
||||
}
|
||||
}
|
||||
|
||||
- (void) redirectForRequest: (NSURLRequest*)request
|
||||
{
|
||||
NSAssert(NO, @"Require concrete implementation");
|
||||
}
|
||||
|
||||
- (void) failWithError: (NSError*)error request: (NSURLRequest*)request
|
||||
{
|
||||
NSDictionary *info;
|
||||
NSError *urlError;
|
||||
id<NSURLProtocolClient> client;
|
||||
|
||||
info = [NSDictionary dictionaryWithObjectsAndKeys:
|
||||
error, NSUnderlyingErrorKey,
|
||||
[request URL], NSURLErrorFailingURLErrorKey,
|
||||
[[request URL] absoluteString], NSURLErrorFailingURLStringErrorKey,
|
||||
[error localizedDescription], NSLocalizedDescriptionKey, nil];
|
||||
|
||||
urlError = [NSError errorWithDomain: NSURLErrorDomain
|
||||
code: [error code]
|
||||
userInfo: info];
|
||||
[self completeTaskWithError: urlError];
|
||||
|
||||
client = [self client];
|
||||
if ([client respondsToSelector: @selector(URLProtocol:didFailWithError:)])
|
||||
{
|
||||
[client URLProtocol: self didFailWithError: urlError];
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL) seekInputStreamToPosition: (uint64_t)position
|
||||
{
|
||||
//TODO implement seek for NSURLSessionUploadTask
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (void) updateProgressMeterWithTotalBytesSent: (int64_t)totalBytesSent
|
||||
totalBytesExpectedToSend: (int64_t)totalBytesExpectedToSend
|
||||
totalBytesReceived: (int64_t)totalBytesReceived
|
||||
totalBytesExpectedToReceive: (int64_t)totalBytesExpectedToReceive
|
||||
{
|
||||
// TODO: Update progress. Note that a single NSURLSessionTask might
|
||||
// perform multiple transfers. The values in `progress` are only for
|
||||
// the current transfer.
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
30
Source/GSTaskRegistry.h
Normal file
30
Source/GSTaskRegistry.h
Normal file
|
@ -0,0 +1,30 @@
|
|||
#ifndef INCLUDED_GSTASKREGISTRY_H
|
||||
#define INCLUDED_GSTASKREGISTRY_H
|
||||
|
||||
#import "common.h"
|
||||
|
||||
@class NSArray;
|
||||
@class NSURLSessionTask;
|
||||
|
||||
/*
|
||||
* This helper class keeps track of all tasks.
|
||||
*
|
||||
* Each `NSURLSession` has a `GSTaskRegistry` for its running tasks.
|
||||
*
|
||||
* - Note: This must **only** be accessed on the owning session's work queue.
|
||||
*/
|
||||
@interface GSTaskRegistry : NSObject
|
||||
|
||||
- (void ) addTask: (NSURLSessionTask*)task;
|
||||
|
||||
- (void) removeTask: (NSURLSessionTask*)task;
|
||||
|
||||
- (void) notifyOnTasksCompletion: (void (^)(void))tasksCompletion;
|
||||
|
||||
- (NSArray*) allTasks;
|
||||
|
||||
- (BOOL) isEmpty;
|
||||
|
||||
@end
|
||||
|
||||
#endif
|
98
Source/GSTaskRegistry.m
Normal file
98
Source/GSTaskRegistry.m
Normal file
|
@ -0,0 +1,98 @@
|
|||
#import "GSTaskRegistry.h"
|
||||
#import "Foundation/NSDictionary.h"
|
||||
#import "Foundation/NSException.h"
|
||||
#import "Foundation/NSURLSession.h"
|
||||
|
||||
|
||||
@implementation GSTaskRegistry
|
||||
{
|
||||
NSMutableDictionary *_tasks;
|
||||
void (^_tasksCompletion)(void);
|
||||
}
|
||||
|
||||
- (instancetype) init
|
||||
{
|
||||
if (nil != (self = [super init]))
|
||||
{
|
||||
_tasks = [[NSMutableDictionary alloc] init];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void) dealloc
|
||||
{
|
||||
DESTROY(_tasks);
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (NSArray*) allTasks
|
||||
{
|
||||
return [_tasks allValues];
|
||||
}
|
||||
|
||||
- (BOOL) isEmpty
|
||||
{
|
||||
return [_tasks count] == 0;
|
||||
}
|
||||
|
||||
- (void) notifyOnTasksCompletion: (void (^)(void))tasksCompletion
|
||||
{
|
||||
_tasksCompletion = tasksCompletion;
|
||||
}
|
||||
|
||||
- (void) addTask: (NSURLSessionTask*)task
|
||||
{
|
||||
NSString *identifier;
|
||||
NSUInteger taskIdentifier;
|
||||
NSURLSessionTask *t;
|
||||
|
||||
taskIdentifier = [task taskIdentifier];
|
||||
|
||||
NSAssert(taskIdentifier != 0, @"Invalid task identifier");
|
||||
|
||||
identifier = [NSString stringWithFormat: @"%lu", taskIdentifier];
|
||||
|
||||
if (nil != (t = [_tasks objectForKey: identifier]))
|
||||
{
|
||||
if ([t isEqual: task])
|
||||
{
|
||||
NSAssert(NO,
|
||||
@"Trying to re-insert a task that's already in the registry.");
|
||||
}
|
||||
else
|
||||
{
|
||||
NSAssert(NO,
|
||||
@"Trying to insert a task, but a different task with the same"
|
||||
@" identifier is already in the registry.");
|
||||
}
|
||||
}
|
||||
|
||||
[_tasks setObject: task forKey: identifier];
|
||||
}
|
||||
|
||||
- (void) removeTask: (NSURLSessionTask*)task
|
||||
{
|
||||
NSString *identifier;
|
||||
NSUInteger taskIdentifier;
|
||||
|
||||
taskIdentifier = [task taskIdentifier];
|
||||
|
||||
NSAssert(taskIdentifier != 0, @"Invalid task identifier");
|
||||
|
||||
identifier = [NSString stringWithFormat: @"%lu", taskIdentifier];
|
||||
|
||||
if (nil == [_tasks objectForKey: identifier])
|
||||
{
|
||||
NSAssert(NO, @"Trying to remove task, but it's not in the registry.");
|
||||
}
|
||||
|
||||
[_tasks removeObjectForKey: identifier];
|
||||
|
||||
if (nil != _tasksCompletion && [self isEmpty])
|
||||
{
|
||||
_tasksCompletion();
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
32
Source/GSTimeoutSource.h
Normal file
32
Source/GSTimeoutSource.h
Normal file
|
@ -0,0 +1,32 @@
|
|||
#ifndef INCLUDED_GSTIMEOUTSOURCE_H
|
||||
#define INCLUDED_GSTIMEOUTSOURCE_H
|
||||
|
||||
#import "common.h"
|
||||
#import "GSDispatch.h"
|
||||
|
||||
/*
|
||||
* A helper class that wraps a libdispatch timer.
|
||||
*
|
||||
* Used to implement the timeout of `GSMultiHandle` and `GSEasyHandle`
|
||||
*/
|
||||
@interface GSTimeoutSource : NSObject
|
||||
{
|
||||
dispatch_source_t _timer;
|
||||
NSInteger _timeoutMs;
|
||||
bool _isSuspended;
|
||||
}
|
||||
|
||||
|
||||
- (instancetype) initWithQueue: (dispatch_queue_t)queue
|
||||
handler: (dispatch_block_t)handler;
|
||||
|
||||
- (NSInteger) timeout;
|
||||
- (void) setTimeout: (NSInteger)timeoutMs;
|
||||
|
||||
- (void) suspend;
|
||||
|
||||
- (void) cancel;
|
||||
|
||||
@end
|
||||
|
||||
#endif
|
76
Source/GSTimeoutSource.m
Normal file
76
Source/GSTimeoutSource.m
Normal file
|
@ -0,0 +1,76 @@
|
|||
#import "GSTimeoutSource.h"
|
||||
|
||||
@implementation GSTimeoutSource
|
||||
|
||||
- (instancetype) initWithQueue: (dispatch_queue_t)queue
|
||||
handler: (dispatch_block_t)handler
|
||||
{
|
||||
if (nil != (self = [super init]))
|
||||
{
|
||||
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
|
||||
dispatch_source_set_event_handler(timer, handler);
|
||||
dispatch_source_set_cancel_handler(timer, ^{
|
||||
dispatch_release(timer);
|
||||
});
|
||||
|
||||
_timer = timer;
|
||||
_timeoutMs = -1;
|
||||
_isSuspended = YES;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void) dealloc
|
||||
{
|
||||
[self cancel];
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (NSInteger) timeout
|
||||
{
|
||||
return _timeoutMs;
|
||||
}
|
||||
|
||||
- (void) setTimeout: (NSInteger)timeoutMs
|
||||
{
|
||||
if (timeoutMs >= 0)
|
||||
{
|
||||
_timeoutMs = 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 (_isSuspended)
|
||||
{
|
||||
_isSuspended = NO;
|
||||
dispatch_resume(_timer);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
[self suspend];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)suspend
|
||||
{
|
||||
if (!_isSuspended)
|
||||
{
|
||||
_isSuspended = YES;
|
||||
_timeoutMs = -1;
|
||||
dispatch_suspend(_timer);
|
||||
}
|
||||
}
|
||||
|
||||
- (void) cancel
|
||||
{
|
||||
if (_timer)
|
||||
{
|
||||
dispatch_source_cancel(_timer);
|
||||
_timer = NULL; // released in cancel handler
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
140
Source/GSTransferState.h
Normal file
140
Source/GSTransferState.h
Normal file
|
@ -0,0 +1,140 @@
|
|||
#ifndef INCLUDED_GSTRANSFERTSTATE_H
|
||||
#define INCLUDED_GSTRANSFERTSTATE_H
|
||||
|
||||
#import "GSURLSessionTaskBodySource.h"
|
||||
|
||||
|
||||
@class GSURLSessionTaskBodySource;
|
||||
@class NSArray;
|
||||
@class NSData;
|
||||
@class NSMutableData;
|
||||
@class NSFileHandle;
|
||||
@class NSHTTPURLResponse;
|
||||
@class NSURL;
|
||||
@class NSURLResponse;
|
||||
|
||||
typedef NS_ENUM(NSUInteger, GSParsedResponseHeaderType) {
|
||||
GSParsedResponseHeaderTypePartial,
|
||||
GSParsedResponseHeaderTypeComplete
|
||||
};
|
||||
|
||||
/*
|
||||
* A native protocol like HTTP header being parsed.
|
||||
*
|
||||
* It can either be complete (i.e. the final CR LF CR LF has been
|
||||
* received), or partial.
|
||||
*/
|
||||
@interface GSParsedResponseHeader: NSObject
|
||||
{
|
||||
NSArray *_lines;
|
||||
GSParsedResponseHeaderType _type;
|
||||
}
|
||||
|
||||
- (GSParsedResponseHeaderType) type;
|
||||
|
||||
/*
|
||||
* Parse a header line passed by libcurl.
|
||||
*
|
||||
* These contain the <CRLF> ending and the final line contains nothing but
|
||||
* that ending.
|
||||
* - Returns: Returning nil indicates failure. Otherwise returns a new
|
||||
* `GSParsedResponseHeader` with the given line added.
|
||||
*/
|
||||
- (instancetype) byAppendingHeaderLine: (NSData*)data;
|
||||
|
||||
- (NSHTTPURLResponse*) createHTTPURLResponseForURL: (NSURL*)URL;
|
||||
|
||||
@end
|
||||
|
||||
typedef NS_ENUM(NSUInteger, GSDataDrainType) {
|
||||
// Concatenate in-memory
|
||||
GSDataDrainInMemory,
|
||||
// Write to file
|
||||
GSDataDrainTypeToFile,
|
||||
// Do nothing. Might be forwarded to delegate
|
||||
GSDataDrainTypeIgnore,
|
||||
};
|
||||
|
||||
@interface GSDataDrain: NSObject
|
||||
{
|
||||
GSDataDrainType _type;
|
||||
NSMutableData *_data;
|
||||
NSURL *_fileURL;
|
||||
NSFileHandle *_fileHandle;
|
||||
}
|
||||
|
||||
- (GSDataDrainType) type;
|
||||
- (void) setType: (GSDataDrainType)type;
|
||||
|
||||
- (NSMutableData*) data;
|
||||
|
||||
- (NSURL*) fileURL;
|
||||
|
||||
- (NSFileHandle*) fileHandle;
|
||||
|
||||
@end
|
||||
|
||||
/*
|
||||
* State related to an ongoing transfer.
|
||||
*
|
||||
* This contains headers received so far, body data received so far, etc.
|
||||
*
|
||||
* There's a strict 1-to-1 relationship between a `GSEasyHandle` and a
|
||||
* `GSTransferState`.
|
||||
*/
|
||||
@interface GSTransferState: NSObject
|
||||
{
|
||||
NSURL *_url; // The URL that's being requested
|
||||
GSParsedResponseHeader *_parsedResponseHeader; // Raw headers received.
|
||||
NSURLResponse *_response; // Once the headers is complete, this will contain the response
|
||||
id<GSURLSessionTaskBodySource> _requestBodySource; // The body data to be sent in the request
|
||||
GSDataDrain *_bodyDataDrain; // Body data received
|
||||
BOOL _isHeaderComplete;
|
||||
}
|
||||
|
||||
// Transfer state that can receive body data, but will not send body data.
|
||||
- (instancetype) initWithURL: (NSURL*)url
|
||||
bodyDataDrain: (GSDataDrain*)bodyDataDrain;
|
||||
|
||||
// Transfer state that sends body data and can receive body data.
|
||||
- (instancetype) initWithURL: (NSURL*)url
|
||||
bodyDataDrain: (GSDataDrain*)bodyDataDrain
|
||||
bodySource: (id<GSURLSessionTaskBodySource>)bodySource;
|
||||
|
||||
- (instancetype) initWithURL: (NSURL*)url
|
||||
parsedResponseHeader: (GSParsedResponseHeader*)parsedResponseHeader
|
||||
response: (NSURLResponse*)response
|
||||
bodySource: (id<GSURLSessionTaskBodySource>)bodySource
|
||||
bodyDataDrain: (GSDataDrain*)bodyDataDrain;
|
||||
|
||||
/*
|
||||
* Append body data
|
||||
*/
|
||||
- (instancetype) byAppendingBodyData: (NSData*)bodyData;
|
||||
|
||||
/*
|
||||
* Appends a header line
|
||||
*
|
||||
* Will set the complete response once the header is complete, i.e. the
|
||||
* return value's `isHeaderComplete` will then by `YES`.
|
||||
*
|
||||
* When a parsing error occurs `error` will be set.
|
||||
*/
|
||||
- (instancetype) byAppendingHTTPHeaderLineData: (NSData*)data
|
||||
error: (NSError**)error;
|
||||
|
||||
- (NSURLResponse*) response;
|
||||
|
||||
- (void) setResponse: (NSURLResponse*)response;
|
||||
|
||||
- (BOOL) isHeaderComplete;
|
||||
|
||||
- (id<GSURLSessionTaskBodySource>) requestBodySource;
|
||||
|
||||
- (GSDataDrain*) bodyDataDrain;
|
||||
|
||||
- (NSURL*) URL;
|
||||
|
||||
@end
|
||||
|
||||
#endif
|
487
Source/GSTransferState.m
Normal file
487
Source/GSTransferState.m
Normal file
|
@ -0,0 +1,487 @@
|
|||
#import "GSTransferState.h"
|
||||
#import "GSURLSessionTaskBodySource.h"
|
||||
#import "Foundation/NSArray.h"
|
||||
#import "Foundation/NSCharacterSet.h"
|
||||
#import "Foundation/NSData.h"
|
||||
#import "Foundation/NSDictionary.h"
|
||||
#import "Foundation/NSFileHandle.h"
|
||||
#import "Foundation/NSError.h"
|
||||
#import "Foundation/NSURL.h"
|
||||
#import "Foundation/NSURLError.h"
|
||||
#import "Foundation/NSURLResponse.h"
|
||||
#import "Foundation/NSURLSession.h"
|
||||
#import "Foundation/NSUUID.h"
|
||||
#import "Foundation/NSFileManager.h"
|
||||
#import "Foundation/NSPathUtilities.h"
|
||||
|
||||
#define GS_DELIMITERS_CR 0x0d
|
||||
#define GS_DELIMITERS_LR 0x0a
|
||||
|
||||
@implementation GSParsedResponseHeader
|
||||
|
||||
- (instancetype) init
|
||||
{
|
||||
if (nil != (self = [super init]))
|
||||
{
|
||||
_lines = [[NSMutableArray alloc] init];
|
||||
_type = GSParsedResponseHeaderTypePartial;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void) dealloc
|
||||
{
|
||||
DESTROY(_lines);
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (GSParsedResponseHeaderType) type
|
||||
{
|
||||
return _type;
|
||||
}
|
||||
|
||||
- (void) setType: (GSParsedResponseHeaderType)type
|
||||
{
|
||||
_type = type;
|
||||
}
|
||||
|
||||
- (void) setLines: (NSArray*)lines
|
||||
{
|
||||
ASSIGN(_lines, lines);
|
||||
}
|
||||
|
||||
- (instancetype) byAppendingHeaderLine: (NSData*)data
|
||||
{
|
||||
NSUInteger length = [data length];
|
||||
|
||||
if (length >= 2)
|
||||
{
|
||||
uint8_t last2;
|
||||
uint8_t last1;
|
||||
|
||||
[data getBytes: &last2 range: NSMakeRange(length - 2, 1)];
|
||||
[data getBytes: &last1 range: NSMakeRange(length - 1, 1)];
|
||||
|
||||
if (GS_DELIMITERS_CR == last2 && GS_DELIMITERS_LR == last1)
|
||||
{
|
||||
NSData *lineBuffer;
|
||||
NSString *line;
|
||||
|
||||
lineBuffer = [data subdataWithRange: NSMakeRange(0, length - 2)];
|
||||
line = AUTORELEASE([[NSString alloc] initWithData: lineBuffer
|
||||
encoding: NSUTF8StringEncoding]);
|
||||
|
||||
if (nil == line)
|
||||
{
|
||||
return nil;
|
||||
}
|
||||
|
||||
return [self _byAppendingHeaderLine: line];
|
||||
}
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (NSHTTPURLResponse*) createHTTPURLResponseForURL: (NSURL*)URL
|
||||
{
|
||||
NSArray *tail;
|
||||
NSArray *startLine;
|
||||
NSDictionary *headerFields;
|
||||
NSString *head;
|
||||
NSString *s, *v;
|
||||
|
||||
head = [_lines firstObject];
|
||||
if (nil == head)
|
||||
{
|
||||
return nil;
|
||||
}
|
||||
if ([_lines count] == 0)
|
||||
{
|
||||
return nil;
|
||||
}
|
||||
|
||||
tail = [_lines subarrayWithRange: NSMakeRange(1, [_lines count] - 1)];
|
||||
|
||||
startLine = [self statusLineFromLine: head];
|
||||
if (nil == startLine)
|
||||
{
|
||||
return nil;
|
||||
}
|
||||
|
||||
headerFields = [self createHeaderFieldsFromLines: tail];
|
||||
|
||||
v = [startLine objectAtIndex: 0];
|
||||
s = [startLine objectAtIndex: 1];
|
||||
|
||||
return AUTORELEASE([[NSHTTPURLResponse alloc] initWithURL: URL
|
||||
statusCode: [s integerValue]
|
||||
HTTPVersion: v
|
||||
headerFields: headerFields]);
|
||||
}
|
||||
|
||||
- (NSArray*) statusLineFromLine: (NSString*)line
|
||||
{
|
||||
NSArray *a;
|
||||
NSString *s;
|
||||
NSInteger status;
|
||||
|
||||
a = [line componentsSeparatedByString: @" "];
|
||||
if ([a count] < 3)
|
||||
{
|
||||
return nil;
|
||||
}
|
||||
|
||||
s = [a objectAtIndex: 1];
|
||||
|
||||
status = [s integerValue];
|
||||
if (status >= 100 && status <= 999)
|
||||
{
|
||||
return a;
|
||||
}
|
||||
else
|
||||
{
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
|
||||
- (NSDictionary *) createHeaderFieldsFromLines: (NSArray *)lines
|
||||
{
|
||||
NSMutableDictionary *headerFields = nil;
|
||||
NSEnumerator *e;
|
||||
NSString *line;
|
||||
|
||||
e = [_lines objectEnumerator];
|
||||
while (nil != (line = [e nextObject]))
|
||||
{
|
||||
NSRange r;
|
||||
NSString *head;
|
||||
NSString *tail;
|
||||
NSCharacterSet *set;
|
||||
NSString *key;
|
||||
NSString *value;
|
||||
NSString *v;
|
||||
|
||||
r = [line rangeOfString: @":"];
|
||||
if (r.location != NSNotFound)
|
||||
{
|
||||
head = [line substringToIndex: r.location];
|
||||
tail = [line substringFromIndex: r.location + 1];
|
||||
set = [NSCharacterSet whitespaceAndNewlineCharacterSet];
|
||||
key = [head stringByTrimmingCharactersInSet: set];
|
||||
value = [tail stringByTrimmingCharactersInSet: set];
|
||||
if (nil != key && nil != value)
|
||||
{
|
||||
if (nil == headerFields)
|
||||
{
|
||||
headerFields = [NSMutableDictionary dictionary];
|
||||
}
|
||||
if (nil != [headerFields objectForKey: key])
|
||||
{
|
||||
v = [NSString stringWithFormat:@"%@, %@",
|
||||
[headerFields objectForKey: key], value];
|
||||
[headerFields setObject: v forKey: key];
|
||||
}
|
||||
else
|
||||
{
|
||||
[headerFields setObject: value forKey: key];
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return AUTORELEASE([headerFields copy]);
|
||||
}
|
||||
|
||||
- (instancetype) _byAppendingHeaderLine: (NSString*)line
|
||||
{
|
||||
GSParsedResponseHeader *header;
|
||||
|
||||
header = AUTORELEASE([[GSParsedResponseHeader alloc] init]);
|
||||
|
||||
if ([line length] == 0)
|
||||
{
|
||||
switch (_type)
|
||||
{
|
||||
case GSParsedResponseHeaderTypePartial:
|
||||
{
|
||||
[header setType: GSParsedResponseHeaderTypeComplete];
|
||||
[header setLines: _lines];
|
||||
|
||||
return header;
|
||||
}
|
||||
case GSParsedResponseHeaderTypeComplete:
|
||||
return header;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
NSMutableArray *lines = [[self partialResponseHeader] mutableCopy];
|
||||
|
||||
[lines addObject:line];
|
||||
|
||||
[header setType: GSParsedResponseHeaderTypePartial];
|
||||
[header setLines: lines];
|
||||
|
||||
RELEASE(lines);
|
||||
|
||||
return header;
|
||||
}
|
||||
}
|
||||
|
||||
- (NSArray*) partialResponseHeader
|
||||
{
|
||||
switch (_type)
|
||||
{
|
||||
case GSParsedResponseHeaderTypeComplete:
|
||||
return [NSArray array];
|
||||
|
||||
case GSParsedResponseHeaderTypePartial:
|
||||
return _lines;
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation GSDataDrain
|
||||
|
||||
- (void) dealloc
|
||||
{
|
||||
DESTROY(_data);
|
||||
DESTROY(_fileURL);
|
||||
DESTROY(_fileHandle);
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (GSDataDrainType) type
|
||||
{
|
||||
return _type;
|
||||
}
|
||||
|
||||
- (void) setType: (GSDataDrainType)type
|
||||
{
|
||||
_type = type;
|
||||
}
|
||||
|
||||
- (NSMutableData*) data
|
||||
{
|
||||
if (!_data)
|
||||
{
|
||||
_data = [[NSMutableData alloc] init];
|
||||
}
|
||||
|
||||
return _data;
|
||||
}
|
||||
|
||||
- (NSURL*) fileURL
|
||||
{
|
||||
/* Generate a random fileURL if fileURL is not initialized.
|
||||
* It would be nice to have this implemented in an initializer. */
|
||||
if (!_fileURL)
|
||||
{
|
||||
NSUUID *randomUUID;
|
||||
NSString *fileName;
|
||||
NSURL *tempURL;
|
||||
|
||||
randomUUID = [NSUUID UUID];
|
||||
fileName = [[randomUUID UUIDString] stringByAppendingPathExtension: @"tmp"];
|
||||
tempURL = [NSURL fileURLWithPath: NSTemporaryDirectory()];
|
||||
|
||||
_fileURL = RETAIN([NSURL fileURLWithPath: fileName relativeToURL: tempURL]);
|
||||
}
|
||||
|
||||
return _fileURL;
|
||||
}
|
||||
|
||||
- (NSFileHandle*) fileHandle
|
||||
{
|
||||
/* Create temporary file and open a fileHandle for writing. */
|
||||
if (!_fileHandle)
|
||||
{
|
||||
NSFileManager *fileManager = [NSFileManager defaultManager];
|
||||
[fileManager createFileAtPath: [[self fileURL] relativePath]
|
||||
contents: nil
|
||||
attributes: nil];
|
||||
|
||||
_fileHandle = RETAIN([NSFileHandle fileHandleForWritingToURL: [self fileURL] error: NULL]);
|
||||
}
|
||||
|
||||
return _fileHandle;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation GSTransferState
|
||||
|
||||
- (instancetype) initWithURL: (NSURL*)url
|
||||
bodyDataDrain: (GSDataDrain*)bodyDataDrain
|
||||
{
|
||||
return [self initWithURL: url
|
||||
parsedResponseHeader: nil
|
||||
response: nil
|
||||
bodySource: nil
|
||||
bodyDataDrain: bodyDataDrain];
|
||||
}
|
||||
|
||||
- (instancetype) initWithURL: (NSURL*)url
|
||||
bodyDataDrain: (GSDataDrain*)bodyDataDrain
|
||||
bodySource: (id<GSURLSessionTaskBodySource>)bodySource
|
||||
{
|
||||
return [self initWithURL: url
|
||||
parsedResponseHeader: nil
|
||||
response: nil
|
||||
bodySource: bodySource
|
||||
bodyDataDrain: bodyDataDrain];
|
||||
}
|
||||
|
||||
- (instancetype) initWithURL: (NSURL*)url
|
||||
parsedResponseHeader: (GSParsedResponseHeader*)parsedResponseHeader
|
||||
response: (NSURLResponse*)response
|
||||
bodySource: (id<GSURLSessionTaskBodySource>)bodySource
|
||||
bodyDataDrain: (GSDataDrain*)bodyDataDrain
|
||||
{
|
||||
if (nil != (self = [super init]))
|
||||
{
|
||||
ASSIGN(_url, url);
|
||||
if (nil != parsedResponseHeader)
|
||||
{
|
||||
ASSIGN(_parsedResponseHeader, parsedResponseHeader);
|
||||
}
|
||||
else
|
||||
{
|
||||
_parsedResponseHeader = [[GSParsedResponseHeader alloc] init];
|
||||
}
|
||||
ASSIGN(_response, response);
|
||||
ASSIGN(_requestBodySource, bodySource);
|
||||
ASSIGN(_bodyDataDrain, bodyDataDrain);
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void) dealloc
|
||||
{
|
||||
DESTROY(_url);
|
||||
DESTROY(_parsedResponseHeader);
|
||||
DESTROY(_response);
|
||||
DESTROY(_requestBodySource);
|
||||
DESTROY(_bodyDataDrain);
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (instancetype) byAppendingBodyData: (NSData*)bodyData
|
||||
{
|
||||
switch ([_bodyDataDrain type])
|
||||
{
|
||||
case GSDataDrainInMemory:
|
||||
[[_bodyDataDrain data] appendData: bodyData];
|
||||
return self;
|
||||
case GSDataDrainTypeToFile:
|
||||
{
|
||||
NSFileHandle *fileHandle;
|
||||
|
||||
fileHandle = [_bodyDataDrain fileHandle];
|
||||
[fileHandle seekToEndOfFile];
|
||||
[fileHandle writeData: bodyData];
|
||||
|
||||
return self;
|
||||
}
|
||||
case GSDataDrainTypeIgnore:
|
||||
return self;
|
||||
}
|
||||
}
|
||||
|
||||
- (instancetype) byAppendingHTTPHeaderLineData: (NSData*)data
|
||||
error: (NSError**)error
|
||||
{
|
||||
GSParsedResponseHeader *h;
|
||||
|
||||
h = [_parsedResponseHeader byAppendingHeaderLine: data];
|
||||
if (nil == h)
|
||||
{
|
||||
if (error != NULL)
|
||||
{
|
||||
*error = [NSError errorWithDomain: NSURLErrorDomain
|
||||
code: -1
|
||||
userInfo: nil];
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
if ([h type] == GSParsedResponseHeaderTypeComplete)
|
||||
{
|
||||
NSHTTPURLResponse *response;
|
||||
GSParsedResponseHeader *ph;
|
||||
GSTransferState *ts;
|
||||
|
||||
response = [h createHTTPURLResponseForURL: _url];
|
||||
if (nil == response)
|
||||
{
|
||||
if (error != NULL)
|
||||
{
|
||||
*error = [NSError errorWithDomain: NSURLErrorDomain
|
||||
code: -1
|
||||
userInfo: nil];
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
ph = AUTORELEASE([[GSParsedResponseHeader alloc] init]);
|
||||
ts = [[GSTransferState alloc] initWithURL: _url
|
||||
parsedResponseHeader: ph
|
||||
response: response
|
||||
bodySource: _requestBodySource
|
||||
bodyDataDrain: _bodyDataDrain];
|
||||
return AUTORELEASE(ts);
|
||||
}
|
||||
else
|
||||
{
|
||||
GSTransferState *ts;
|
||||
|
||||
ts = [[GSTransferState alloc] initWithURL: _url
|
||||
parsedResponseHeader: h
|
||||
response: nil
|
||||
bodySource: _requestBodySource
|
||||
bodyDataDrain: _bodyDataDrain];
|
||||
return AUTORELEASE(ts);
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL) isHeaderComplete
|
||||
{
|
||||
return _response != nil;
|
||||
}
|
||||
|
||||
- (NSURLResponse*) response
|
||||
{
|
||||
return _response;
|
||||
}
|
||||
|
||||
- (void) setResponse: (NSURLResponse*)response
|
||||
{
|
||||
ASSIGN(_response, response);
|
||||
}
|
||||
|
||||
- (id<GSURLSessionTaskBodySource>) requestBodySource
|
||||
{
|
||||
return _requestBodySource;
|
||||
}
|
||||
|
||||
- (GSDataDrain*) bodyDataDrain
|
||||
{
|
||||
return _bodyDataDrain;
|
||||
}
|
||||
|
||||
- (NSURL*) URL
|
||||
{
|
||||
return _url;
|
||||
}
|
||||
|
||||
@end
|
|
@ -61,7 +61,6 @@
|
|||
- (id<GSLogDelegate>) _debugLogDelegate;
|
||||
- (id) _propertyForKey: (NSString*)key;
|
||||
- (void) _setProperty: (id)value forKey: (NSString*)key;
|
||||
- (NSDictionary *) _insensitiveHeaders;
|
||||
@end
|
||||
|
||||
|
||||
|
|
46
Source/GSURLSessionTaskBody.h
Normal file
46
Source/GSURLSessionTaskBody.h
Normal file
|
@ -0,0 +1,46 @@
|
|||
#ifndef INCLUDED_GSURLSESSIONTASKBODY_H
|
||||
#define INCLUDED_GSURLSESSIONTASKBODY_H
|
||||
|
||||
#import "common.h"
|
||||
|
||||
@class NSData;
|
||||
@class NSError;
|
||||
@class NSInputStream;
|
||||
@class NSNumber;
|
||||
@class NSURL;
|
||||
|
||||
typedef NS_ENUM(NSUInteger, GSURLSessionTaskBodyType) {
|
||||
GSURLSessionTaskBodyTypeNone,
|
||||
GSURLSessionTaskBodyTypeData,
|
||||
// Body data is read from the given file URL
|
||||
GSURLSessionTaskBodyTypeFile,
|
||||
// Body data is read from the given input stream
|
||||
GSURLSessionTaskBodyTypeStream,
|
||||
};
|
||||
|
||||
@interface GSURLSessionTaskBody : NSObject
|
||||
{
|
||||
GSURLSessionTaskBodyType _type;
|
||||
NSData *_data;
|
||||
NSURL *_fileURL;
|
||||
NSInputStream *_inputStream;
|
||||
}
|
||||
|
||||
- (instancetype) init;
|
||||
- (instancetype) initWithData: (NSData*)data;
|
||||
- (instancetype) initWithFileURL: (NSURL*)fileURL;
|
||||
- (instancetype) initWithInputStream: (NSInputStream*)inputStream;
|
||||
|
||||
- (GSURLSessionTaskBodyType) type;
|
||||
|
||||
- (NSData*) data;
|
||||
|
||||
- (NSURL*) fileURL;
|
||||
|
||||
- (NSInputStream*) inputStream;
|
||||
|
||||
- (NSNumber*) getBodyLengthWithError: (NSError**)error;
|
||||
|
||||
@end
|
||||
|
||||
#endif
|
111
Source/GSURLSessionTaskBody.m
Normal file
111
Source/GSURLSessionTaskBody.m
Normal file
|
@ -0,0 +1,111 @@
|
|||
#import "GSURLSessionTaskBody.h"
|
||||
#import "Foundation/NSData.h"
|
||||
#import "Foundation/NSFileManager.h"
|
||||
#import "Foundation/NSStream.h"
|
||||
#import "Foundation/NSURL.h"
|
||||
#import "Foundation/NSValue.h"
|
||||
|
||||
@implementation GSURLSessionTaskBody
|
||||
|
||||
- (instancetype) init
|
||||
{
|
||||
if (nil != (self = [super init]))
|
||||
{
|
||||
_type = GSURLSessionTaskBodyTypeNone;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype) initWithData: (NSData*)data
|
||||
{
|
||||
if (nil != (self = [super init]))
|
||||
{
|
||||
_type = GSURLSessionTaskBodyTypeData;
|
||||
ASSIGN(_data, data);
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype) initWithFileURL: (NSURL*)fileURL
|
||||
{
|
||||
if (nil != (self = [super init]))
|
||||
{
|
||||
_type = GSURLSessionTaskBodyTypeFile;
|
||||
ASSIGN(_fileURL, fileURL);
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype) initWithInputStream: (NSInputStream*)inputStream
|
||||
{
|
||||
if (nil != (self = [super init]))
|
||||
{
|
||||
_type = GSURLSessionTaskBodyTypeStream;
|
||||
ASSIGN(_inputStream, inputStream);
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void) dealloc
|
||||
{
|
||||
DESTROY(_data);
|
||||
DESTROY(_fileURL);
|
||||
DESTROY(_inputStream);
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (GSURLSessionTaskBodyType) type
|
||||
{
|
||||
return _type;
|
||||
}
|
||||
|
||||
- (NSData*) data
|
||||
{
|
||||
return _data;
|
||||
}
|
||||
|
||||
- (NSURL*) fileURL
|
||||
{
|
||||
return _fileURL;
|
||||
}
|
||||
|
||||
- (NSInputStream*) inputStream
|
||||
{
|
||||
return _inputStream;
|
||||
}
|
||||
|
||||
- (NSNumber*) getBodyLengthWithError: (NSError**)error
|
||||
{
|
||||
switch (_type)
|
||||
{
|
||||
case GSURLSessionTaskBodyTypeNone:
|
||||
return [NSNumber numberWithInt: 0];
|
||||
case GSURLSessionTaskBodyTypeData:
|
||||
return [NSNumber numberWithUnsignedInteger: [_data length]];
|
||||
case GSURLSessionTaskBodyTypeFile:
|
||||
{
|
||||
NSDictionary *attributes;
|
||||
|
||||
attributes = [[NSFileManager defaultManager]
|
||||
attributesOfItemAtPath: [_fileURL path]
|
||||
error: error];
|
||||
if (!error)
|
||||
{
|
||||
NSNumber *size = [attributes objectForKey: NSFileSize];
|
||||
return size;
|
||||
}
|
||||
else
|
||||
{
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
case GSURLSessionTaskBodyTypeStream:
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
52
Source/GSURLSessionTaskBodySource.h
Normal file
52
Source/GSURLSessionTaskBodySource.h
Normal file
|
@ -0,0 +1,52 @@
|
|||
#ifndef INCLUDED_GSURLSESSIONTASKBODYSOURCE_H
|
||||
#define INCLUDED_GSURLSESSIONTASKBODYSOURCE_H
|
||||
|
||||
#import "common.h"
|
||||
#import "GSDispatch.h"
|
||||
|
||||
@class NSFileHandle;
|
||||
@class NSInputStream;
|
||||
|
||||
typedef NS_ENUM(NSUInteger, GSBodySourceDataChunk) {
|
||||
GSBodySourceDataChunkData,
|
||||
// The source is depleted.
|
||||
GSBodySourceDataChunkDone,
|
||||
// Retry later to get more data.
|
||||
GSBodySourceDataChunkRetryLater,
|
||||
GSBodySourceDataChunkError
|
||||
};
|
||||
|
||||
/*
|
||||
* A (non-blocking) source for body data.
|
||||
*/
|
||||
@protocol GSURLSessionTaskBodySource <NSObject>
|
||||
|
||||
/*
|
||||
* Get the next chunck of data.
|
||||
*/
|
||||
- (void) getNextChunkWithLength: (NSInteger)length
|
||||
completionHandler: (void (^)(GSBodySourceDataChunk chunk, NSData *data))completionHandler;
|
||||
|
||||
@end
|
||||
|
||||
@interface GSBodyStreamSource : NSObject <GSURLSessionTaskBodySource>
|
||||
|
||||
- (instancetype) initWithInputStream: (NSInputStream*)inputStream;
|
||||
|
||||
@end
|
||||
|
||||
@interface GSBodyDataSource : NSObject <GSURLSessionTaskBodySource>
|
||||
|
||||
- (instancetype)initWithData:(NSData *)data;
|
||||
|
||||
@end
|
||||
|
||||
@interface GSBodyFileSource : NSObject <GSURLSessionTaskBodySource>
|
||||
|
||||
- (instancetype) initWithFileURL: (NSURL*)fileURL
|
||||
workQueue: (dispatch_queue_t)workQueue
|
||||
dataAvailableHandler: (void (^)(void))dataAvailableHandler;
|
||||
|
||||
@end
|
||||
|
||||
#endif
|
152
Source/GSURLSessionTaskBodySource.m
Normal file
152
Source/GSURLSessionTaskBodySource.m
Normal file
|
@ -0,0 +1,152 @@
|
|||
#import "GSURLSessionTaskBodySource.h"
|
||||
#import "Foundation/NSData.h"
|
||||
#import "Foundation/NSStream.h"
|
||||
|
||||
|
||||
@implementation GSBodyStreamSource
|
||||
{
|
||||
NSInputStream *_inputStream;
|
||||
}
|
||||
|
||||
- (instancetype) initWithInputStream: (NSInputStream*)inputStream
|
||||
{
|
||||
if (nil != (self = [super init]))
|
||||
{
|
||||
ASSIGN(_inputStream, inputStream);
|
||||
if ([_inputStream streamStatus] == NSStreamStatusNotOpen)
|
||||
{
|
||||
[_inputStream open];
|
||||
}
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void) dealloc
|
||||
{
|
||||
DESTROY(_inputStream);
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (void) getNextChunkWithLength: (NSInteger)length
|
||||
completionHandler: (void (^)(GSBodySourceDataChunk, NSData*))completionHandler
|
||||
{
|
||||
if (nil == completionHandler)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (![_inputStream hasBytesAvailable])
|
||||
{
|
||||
completionHandler(GSBodySourceDataChunkDone, nil);
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t buffer[length];
|
||||
NSInteger readBytes;
|
||||
NSData *data;
|
||||
|
||||
readBytes = [_inputStream read: buffer maxLength: length];
|
||||
if (readBytes > 0)
|
||||
{
|
||||
data = AUTORELEASE([[NSData alloc] initWithBytes: buffer
|
||||
length: readBytes]);
|
||||
completionHandler(GSBodySourceDataChunkData, data);
|
||||
}
|
||||
else if (readBytes == 0)
|
||||
{
|
||||
completionHandler(GSBodySourceDataChunkDone, nil);
|
||||
}
|
||||
else
|
||||
{
|
||||
completionHandler(GSBodySourceDataChunkError, nil);
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation GSBodyDataSource
|
||||
{
|
||||
NSData *_data;
|
||||
}
|
||||
|
||||
- (instancetype) initWithData: (NSData*)data
|
||||
{
|
||||
if (nil != (self = [super init]))
|
||||
{
|
||||
ASSIGN(_data, data);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void) dealloc
|
||||
{
|
||||
DESTROY(_data);
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (void) getNextChunkWithLength: (NSInteger)length
|
||||
completionHandler: (void (^)(GSBodySourceDataChunk, NSData*))completionHandler
|
||||
{
|
||||
if (nil == completionHandler)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
NSUInteger remaining = [_data length];
|
||||
if (remaining == 0)
|
||||
{
|
||||
completionHandler(GSBodySourceDataChunkDone, nil);
|
||||
}
|
||||
else if (remaining <= length)
|
||||
{
|
||||
NSData *r = AUTORELEASE([[NSData alloc] initWithData: _data]);
|
||||
DESTROY(_data);
|
||||
completionHandler(GSBodySourceDataChunkData, r);
|
||||
}
|
||||
else
|
||||
{
|
||||
NSData *chunk = [_data subdataWithRange: NSMakeRange(0, length)];
|
||||
NSData *remainder = [_data subdataWithRange:
|
||||
NSMakeRange(length - 1, [_data length] - length)];
|
||||
ASSIGN(_data, remainder);
|
||||
completionHandler(GSBodySourceDataChunkData, chunk);
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation GSBodyFileSource
|
||||
{
|
||||
NSURL *_fileURL;
|
||||
dispatch_queue_t _workQueue;
|
||||
void (^_dataAvailableHandler)(void);
|
||||
}
|
||||
|
||||
- (instancetype) initWithFileURL: (NSURL*)fileURL
|
||||
workQueue: (dispatch_queue_t)workQueue
|
||||
dataAvailableHandler: (void (^)(void))dataAvailableHandler
|
||||
{
|
||||
if (nil != (self = [super init]))
|
||||
{
|
||||
ASSIGN(_fileURL, fileURL);
|
||||
_workQueue = workQueue;
|
||||
_dataAvailableHandler = dataAvailableHandler;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void) dealloc
|
||||
{
|
||||
DESTROY(_fileURL);
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (void) getNextChunkWithLength: (NSInteger)length
|
||||
completionHandler: (void (^)(GSBodySourceDataChunk chunk, NSData *data))completionHandler
|
||||
{
|
||||
//FIXME
|
||||
}
|
||||
|
||||
@end
|
|
@ -43,8 +43,7 @@
|
|||
#import "Foundation/NSSet.h"
|
||||
#import "Foundation/NSValue.h"
|
||||
#import "Foundation/NSString.h"
|
||||
#import "Foundation/NSDateFormatter.h"
|
||||
#import "Foundation/NSLocale.h"
|
||||
#import "Foundation/NSCalendarDate.h"
|
||||
#import "GNUstepBase/Unicode.h"
|
||||
|
||||
static NSString * const HTTPCookieHTTPOnly = @"HTTPOnly";
|
||||
|
@ -682,22 +681,10 @@ _setCookieKey(NSMutableDictionary *dict, NSString *key, NSString *value)
|
|||
else if ([[key lowercaseString] isEqual: @"expires"])
|
||||
{
|
||||
NSDate *expireDate;
|
||||
NSDateFormatter *formatter;
|
||||
NSLocale *locale;
|
||||
NSTimeZone *gmtTimeZone;
|
||||
|
||||
locale = [NSLocale localeWithLocaleIdentifier:@"en_US"];
|
||||
gmtTimeZone = [NSTimeZone timeZoneWithAbbreviation:@"GMT"];
|
||||
|
||||
formatter = [[NSDateFormatter alloc] init];
|
||||
[formatter setDateFormat: @"EEE, dd-MMM-yyyy HH:mm:ss zzz"];
|
||||
[formatter setLocale: locale];
|
||||
[formatter setTimeZone: gmtTimeZone];
|
||||
|
||||
expireDate = [formatter dateFromString:value];
|
||||
expireDate = [NSCalendarDate dateWithString: value
|
||||
calendarFormat: @"%a, %d-%b-%Y %I:%M:%S %Z"];
|
||||
if (expireDate)
|
||||
[dict setObject: expireDate forKey: NSHTTPCookieExpires];
|
||||
RELEASE(formatter);
|
||||
}
|
||||
else if ([[key lowercaseString] isEqual: @"max-age"])
|
||||
[dict setObject: value forKey: NSHTTPCookieMaximumAge];
|
||||
|
|
|
@ -593,15 +593,13 @@ static NSArray *empty = nil;
|
|||
|
||||
- (void) main
|
||||
{
|
||||
NSEnumerator *en = [_executionBlocks objectEnumerator];
|
||||
NSEnumerator *en = [[self executionBlocks] objectEnumerator];
|
||||
GSBlockOperationBlock theBlock;
|
||||
|
||||
while ((theBlock = (GSBlockOperationBlock)[en nextObject]) != NULL)
|
||||
{
|
||||
CALL_NON_NULL_BLOCK_NO_ARGS(theBlock);
|
||||
}
|
||||
|
||||
[_executionBlocks removeAllObjects];
|
||||
}
|
||||
@end
|
||||
|
||||
|
|
|
@ -39,7 +39,6 @@ typedef struct {
|
|||
NSMutableDictionary *headers;
|
||||
BOOL shouldHandleCookies;
|
||||
BOOL debug;
|
||||
BOOL assumesHTTP3Capable;
|
||||
id<GSLogDelegate> ioDelegate;
|
||||
NSURL *URL;
|
||||
NSURL *mainDocumentURL;
|
||||
|
@ -57,9 +56,6 @@ typedef struct {
|
|||
@interface _GSMutableInsensitiveDictionary : NSMutableDictionary
|
||||
@end
|
||||
|
||||
@interface _GSInsensitiveDictionary : NSMutableDictionary
|
||||
@end
|
||||
|
||||
@implementation NSURLRequest
|
||||
|
||||
+ (id) allocWithZone: (NSZone*)z
|
||||
|
@ -120,7 +116,6 @@ typedef struct {
|
|||
ASSIGN(inst->bodyStream, this->bodyStream);
|
||||
ASSIGN(inst->method, this->method);
|
||||
inst->shouldHandleCookies = this->shouldHandleCookies;
|
||||
inst->assumesHTTP3Capable = this->assumesHTTP3Capable;
|
||||
inst->debug = this->debug;
|
||||
inst->ioDelegate = this->ioDelegate;
|
||||
inst->headers = [this->headers mutableCopy];
|
||||
|
@ -380,11 +375,6 @@ typedef struct {
|
|||
return [this->headers objectForKey: field];
|
||||
}
|
||||
|
||||
- (BOOL) assumesHTTP3Capable
|
||||
{
|
||||
return this->assumesHTTP3Capable;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
|
@ -464,11 +454,6 @@ typedef struct {
|
|||
}
|
||||
}
|
||||
|
||||
- (void)setAssumesHTTP3Capable:(BOOL)capable
|
||||
{
|
||||
this->assumesHTTP3Capable = capable;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation NSURLRequest (Private)
|
||||
|
@ -483,11 +468,6 @@ typedef struct {
|
|||
return this->ioDelegate;
|
||||
}
|
||||
|
||||
- (_GSInsensitiveDictionary *) _insensitiveHeaders
|
||||
{
|
||||
return [this->headers copy];
|
||||
}
|
||||
|
||||
- (id) _propertyForKey: (NSString*)key
|
||||
{
|
||||
return [this->properties objectForKey: key];
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,283 +0,0 @@
|
|||
/**
|
||||
NSURLSessionConfiguration.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 "Foundation/NSURLSession.h"
|
||||
#import "Foundation/NSHTTPCookie.h"
|
||||
|
||||
// TODO: This is the old implementation. It requires a rewrite!
|
||||
|
||||
@implementation NSURLSessionConfiguration
|
||||
|
||||
static NSURLSessionConfiguration *def = nil;
|
||||
|
||||
+ (void)initialize
|
||||
{
|
||||
if (nil == def)
|
||||
{
|
||||
def = [NSURLSessionConfiguration new];
|
||||
}
|
||||
}
|
||||
|
||||
+ (NSURLSessionConfiguration *)defaultSessionConfiguration
|
||||
{
|
||||
return AUTORELEASE([def copy]);
|
||||
}
|
||||
|
||||
+ (NSURLSessionConfiguration *)ephemeralSessionConfiguration
|
||||
{
|
||||
// return default session since we don't store any data on disk anyway
|
||||
return AUTORELEASE([def copy]);
|
||||
}
|
||||
|
||||
+ (NSURLSessionConfiguration *)backgroundSessionConfigurationWithIdentifier:
|
||||
(NSString *)identifier
|
||||
{
|
||||
NSURLSessionConfiguration *configuration = [def copy];
|
||||
configuration->_identifier = [identifier copy];
|
||||
return AUTORELEASE(configuration);
|
||||
}
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
if (nil != (self = [super init]))
|
||||
{
|
||||
_protocolClasses = nil;
|
||||
_HTTPMaximumConnectionsPerHost = 6;
|
||||
_HTTPShouldUsePipelining = YES;
|
||||
_HTTPCookieAcceptPolicy = NSHTTPCookieAcceptPolicyNever;
|
||||
_HTTPCookieStorage = nil;
|
||||
_HTTPShouldSetCookies = NO;
|
||||
_HTTPAdditionalHeaders = nil;
|
||||
_HTTPMaximumConnectionLifetime = 0; // Zero or less means default
|
||||
_timeoutIntervalForResource = 604800; // 7 days in seconds
|
||||
_timeoutIntervalForRequest = 60; // 60 seconds
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
DESTROY(_identifier);
|
||||
DESTROY(_HTTPAdditionalHeaders);
|
||||
DESTROY(_HTTPCookieStorage);
|
||||
DESTROY(_protocolClasses);
|
||||
DESTROY(_URLCache);
|
||||
DESTROY(_URLCredentialStorage);
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (NSString *)identifier
|
||||
{
|
||||
return _identifier;
|
||||
}
|
||||
|
||||
- (NSURLCache *)URLCache
|
||||
{
|
||||
return _URLCache;
|
||||
}
|
||||
|
||||
- (void)setURLCache:(NSURLCache *)cache
|
||||
{
|
||||
ASSIGN(_URLCache, cache);
|
||||
}
|
||||
|
||||
- (void)setURLCredentialStorage:(NSURLCredentialStorage *)storage
|
||||
{
|
||||
ASSIGN(_URLCredentialStorage, storage);
|
||||
}
|
||||
|
||||
- (NSURLRequestCachePolicy)requestCachePolicy
|
||||
{
|
||||
return _requestCachePolicy;
|
||||
}
|
||||
|
||||
- (void)setRequestCachePolicy:(NSURLRequestCachePolicy)policy
|
||||
{
|
||||
_requestCachePolicy = policy;
|
||||
}
|
||||
|
||||
- (NSArray *)protocolClasses
|
||||
{
|
||||
return _protocolClasses;
|
||||
}
|
||||
|
||||
- (NSTimeInterval)timeoutIntervalForRequest
|
||||
{
|
||||
return _timeoutIntervalForRequest;
|
||||
}
|
||||
|
||||
- (void)setTimeoutIntervalForRequest:(NSTimeInterval)interval
|
||||
{
|
||||
_timeoutIntervalForRequest = interval;
|
||||
}
|
||||
|
||||
- (NSTimeInterval)timeoutIntervalForResource
|
||||
{
|
||||
return _timeoutIntervalForResource;
|
||||
}
|
||||
|
||||
- (void)setTimeoutIntervalForResource:(NSTimeInterval)interval
|
||||
{
|
||||
_timeoutIntervalForResource = interval;
|
||||
}
|
||||
|
||||
- (NSInteger)HTTPMaximumConnectionsPerHost
|
||||
{
|
||||
return _HTTPMaximumConnectionsPerHost;
|
||||
}
|
||||
|
||||
- (void)setHTTPMaximumConnectionsPerHost:(NSInteger)n
|
||||
{
|
||||
_HTTPMaximumConnectionsPerHost = n;
|
||||
}
|
||||
|
||||
- (NSInteger)HTTPMaximumConnectionLifetime
|
||||
{
|
||||
return _HTTPMaximumConnectionLifetime;
|
||||
}
|
||||
|
||||
- (void)setHTTPMaximumConnectionLifetime:(NSInteger)n
|
||||
{
|
||||
_HTTPMaximumConnectionLifetime = n;
|
||||
}
|
||||
|
||||
- (BOOL)HTTPShouldUsePipelining
|
||||
{
|
||||
return _HTTPShouldUsePipelining;
|
||||
}
|
||||
|
||||
- (void)setHTTPShouldUsePipelining:(BOOL)flag
|
||||
{
|
||||
_HTTPShouldUsePipelining = flag;
|
||||
}
|
||||
|
||||
- (NSHTTPCookieAcceptPolicy)HTTPCookieAcceptPolicy
|
||||
{
|
||||
return _HTTPCookieAcceptPolicy;
|
||||
}
|
||||
|
||||
- (void)setHTTPCookieAcceptPolicy:(NSHTTPCookieAcceptPolicy)policy
|
||||
{
|
||||
_HTTPCookieAcceptPolicy = policy;
|
||||
}
|
||||
|
||||
- (NSHTTPCookieStorage *)HTTPCookieStorage
|
||||
{
|
||||
return _HTTPCookieStorage;
|
||||
}
|
||||
|
||||
- (void)setHTTPCookieStorage:(NSHTTPCookieStorage *)storage
|
||||
{
|
||||
ASSIGN(_HTTPCookieStorage, storage);
|
||||
}
|
||||
|
||||
- (BOOL)HTTPShouldSetCookies
|
||||
{
|
||||
return _HTTPShouldSetCookies;
|
||||
}
|
||||
|
||||
- (void)setHTTPShouldSetCookies:(BOOL)flag
|
||||
{
|
||||
_HTTPShouldSetCookies = flag;
|
||||
}
|
||||
|
||||
- (NSDictionary *)HTTPAdditionalHeaders
|
||||
{
|
||||
return _HTTPAdditionalHeaders;
|
||||
}
|
||||
|
||||
- (void)setHTTPAdditionalHeaders:(NSDictionary *)headers
|
||||
{
|
||||
ASSIGN(_HTTPAdditionalHeaders, headers);
|
||||
}
|
||||
|
||||
- (NSURLRequest *)configureRequest:(NSURLRequest *)request
|
||||
{
|
||||
return [self setCookiesOnRequest:request];
|
||||
}
|
||||
|
||||
- (NSURLRequest *)setCookiesOnRequest:(NSURLRequest *)request
|
||||
{
|
||||
NSMutableURLRequest *r = AUTORELEASE([request mutableCopy]);
|
||||
|
||||
if (_HTTPShouldSetCookies)
|
||||
{
|
||||
if (nil != _HTTPCookieStorage && nil != [request URL])
|
||||
{
|
||||
NSArray *cookies = [_HTTPCookieStorage cookiesForURL:[request URL]];
|
||||
if (nil != cookies && [cookies count] > 0)
|
||||
{
|
||||
NSDictionary *cookiesHeaderFields;
|
||||
NSString *cookieValue;
|
||||
|
||||
cookiesHeaderFields =
|
||||
[NSHTTPCookie requestHeaderFieldsWithCookies:cookies];
|
||||
cookieValue = [cookiesHeaderFields objectForKey:@"Cookie"];
|
||||
if (nil != cookieValue && [cookieValue length] > 0)
|
||||
{
|
||||
[r setValue:cookieValue forHTTPHeaderField:@"Cookie"];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return AUTORELEASE([r copy]);
|
||||
}
|
||||
|
||||
- (NSURLCredentialStorage *)URLCredentialStorage
|
||||
{
|
||||
return _URLCredentialStorage;
|
||||
}
|
||||
|
||||
- (id)copyWithZone:(NSZone *)zone
|
||||
{
|
||||
NSURLSessionConfiguration *copy = [[[self class] alloc] init];
|
||||
|
||||
if (copy)
|
||||
{
|
||||
copy->_identifier = [_identifier copy];
|
||||
copy->_URLCache = [_URLCache copy];
|
||||
copy->_URLCredentialStorage = [_URLCredentialStorage copy];
|
||||
copy->_protocolClasses = [_protocolClasses copyWithZone:zone];
|
||||
copy->_HTTPMaximumConnectionsPerHost = _HTTPMaximumConnectionsPerHost;
|
||||
copy->_HTTPShouldUsePipelining = _HTTPShouldUsePipelining;
|
||||
copy->_HTTPCookieAcceptPolicy = _HTTPCookieAcceptPolicy;
|
||||
copy->_HTTPCookieStorage = [_HTTPCookieStorage retain];
|
||||
copy->_HTTPShouldSetCookies = _HTTPShouldSetCookies;
|
||||
copy->_HTTPAdditionalHeaders = [_HTTPAdditionalHeaders copyWithZone:zone];
|
||||
copy->_timeoutIntervalForRequest = _timeoutIntervalForRequest;
|
||||
copy->_timeoutIntervalForResource = _timeoutIntervalForResource;
|
||||
}
|
||||
|
||||
return copy;
|
||||
}
|
||||
|
||||
@end
|
|
@ -1,88 +0,0 @@
|
|||
/**
|
||||
NSURLSessionPrivate.h
|
||||
|
||||
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 "Foundation/NSURLSession.h"
|
||||
#import "Foundation/NSDictionary.h"
|
||||
#import <curl/curl.h>
|
||||
#import <dispatch/dispatch.h>
|
||||
|
||||
extern NSString *GS_NSURLSESSION_DEBUG_KEY;
|
||||
|
||||
/* libcurl may request a full-duplex socket configuration with
|
||||
* CURL_POLL_INOUT, but libdispatch distinguishes between a read and write
|
||||
* socket source.
|
||||
*
|
||||
* We thus need to keep track of two dispatch sources. One may be set to NULL
|
||||
* if not used.
|
||||
*/
|
||||
struct SourceInfo
|
||||
{
|
||||
dispatch_source_t readSocket;
|
||||
dispatch_source_t writeSocket;
|
||||
};
|
||||
|
||||
typedef NS_ENUM(NSInteger, GSURLSessionProperties) {
|
||||
GSURLSessionStoresDataInMemory = (1 << 0),
|
||||
GSURLSessionWritesDataToFile = (1 << 1),
|
||||
GSURLSessionUpdatesDelegate = (1 << 2),
|
||||
GSURLSessionHasCompletionHandler = (1 << 3),
|
||||
GSURLSessionHasInputStream = (1 << 4)
|
||||
};
|
||||
|
||||
@interface
|
||||
NSURLSession (Private)
|
||||
|
||||
- (dispatch_queue_t)_workQueue;
|
||||
|
||||
- (NSData *)_certificateBlob;
|
||||
- (NSString *)_certificatePath;
|
||||
|
||||
/* Adds the internal easy handle to the multi handle.
|
||||
* Modifications are performed on the workQueue.
|
||||
*/
|
||||
- (void)_resumeTask:(NSURLSessionTask *)task;
|
||||
|
||||
/* The following methods must only be called from within callbacks dispatched on
|
||||
* the workQueue.*/
|
||||
- (void)_setTimer:(NSInteger)timeout;
|
||||
- (void)_suspendTimer;
|
||||
|
||||
/* Required for manual redirects.
|
||||
*/
|
||||
- (void)_addHandle:(CURL *)easy;
|
||||
- (void)_removeHandle:(CURL *)easy;
|
||||
|
||||
- (void)_removeSocket:(struct SourceInfo *)sources;
|
||||
- (int)_addSocket:(curl_socket_t)socket easyHandle:(CURL *)easy what:(int)what;
|
||||
- (int)_setSocket:(curl_socket_t)socket
|
||||
sources:(struct SourceInfo *)sources
|
||||
what:(int)what;
|
||||
|
||||
@end
|
File diff suppressed because it is too large
Load diff
|
@ -1,123 +0,0 @@
|
|||
/**
|
||||
NSURLSessionTaskPrivate.h
|
||||
|
||||
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 "Foundation/NSDictionary.h"
|
||||
#import "Foundation/NSFileHandle.h"
|
||||
#import "Foundation/NSURLSession.h"
|
||||
#import <curl/curl.h>
|
||||
|
||||
@interface
|
||||
NSURLSessionTask (Private)
|
||||
|
||||
- (instancetype)initWithSession:(NSURLSession *)session
|
||||
request:(NSURLRequest *)request
|
||||
taskIdentifier:(NSUInteger)identifier;
|
||||
|
||||
- (CURL *)_easyHandle;
|
||||
|
||||
/* Enable or disable libcurl verbose output. Disabled by default. */
|
||||
- (void)_setVerbose:(BOOL)flag;
|
||||
|
||||
/* This method is called by -[NSURLSession _checkForCompletion]
|
||||
*
|
||||
* We release the session (previously retained in -[NSURLSessionTask resume])
|
||||
* here and inform the delegate about the transfer state.
|
||||
*/
|
||||
- (void)_transferFinishedWithCode:(CURLcode)code;
|
||||
|
||||
/* Explicitly enable data upload with an optional estimated size. Set to 0 if
|
||||
* not available.
|
||||
*
|
||||
* This may be used when a body stream is passed at a later stage
|
||||
* (see URLSession:task:needNewBodyStream:).
|
||||
*/
|
||||
- (void)_enableUploadWithSize:(NSInteger)size;
|
||||
- (void)_setBodyStream:(NSInputStream *)stream;
|
||||
|
||||
- (void)_enableUploadWithData:(NSData *)data;
|
||||
- (void)_enableAutomaticRedirects:(BOOL)flag;
|
||||
|
||||
/* Assign with copying */
|
||||
- (void)_setOriginalRequest:(NSURLRequest *)request;
|
||||
- (void)_setCurrentRequest:(NSURLRequest *)request;
|
||||
|
||||
- (void)_setResponse:(NSURLResponse *)response;
|
||||
- (void)_setCookiesFromHeaders:(NSDictionary *)headers;
|
||||
|
||||
- (void)_setCountOfBytesSent:(int64_t)count;
|
||||
- (void)_setCountOfBytesReceived:(int64_t)count;
|
||||
- (void)_setCountOfBytesExpectedToSend:(int64_t)count;
|
||||
- (void)_setCountOfBytesExpectedToReceive:(int64_t)count;
|
||||
|
||||
- (NSMutableDictionary *)_taskData;
|
||||
|
||||
- (NSURLSession *)_session;
|
||||
|
||||
/* Task specific properties.
|
||||
*
|
||||
* See GSURLSessionProperties in NSURLSessionPrivate.h.
|
||||
*/
|
||||
- (NSInteger)_properties;
|
||||
- (void)_setProperties:(NSInteger)properties;
|
||||
|
||||
/* This value is periodically checked in progress_callback.
|
||||
* We then abort the transfer in the progress_callback if this flag is set.
|
||||
*/
|
||||
- (BOOL)_shouldStopTransfer;
|
||||
- (void)_setShouldStopTransfer:(BOOL)flag;
|
||||
|
||||
- (NSInteger)_numberOfRedirects;
|
||||
- (void)_setNumberOfRedirects:(NSInteger)redirects;
|
||||
|
||||
- (NSInteger)_headerCallbackCount;
|
||||
- (void)_setHeaderCallbackCount:(NSInteger)count;
|
||||
|
||||
- (NSFileHandle *)_createTemporaryFileHandleWithError:(NSError **)error;
|
||||
|
||||
@end
|
||||
|
||||
@interface
|
||||
NSURLSessionDataTask (Private)
|
||||
|
||||
- (GSNSURLSessionDataCompletionHandler)_completionHandler;
|
||||
- (void)_setCompletionHandler:(GSNSURLSessionDataCompletionHandler)handler;
|
||||
|
||||
@end
|
||||
|
||||
@interface
|
||||
NSURLSessionDownloadTask (Private)
|
||||
|
||||
- (GSNSURLSessionDownloadCompletionHandler)_completionHandler;
|
||||
|
||||
- (int64_t)_countOfBytesWritten;
|
||||
- (void)_updateCountOfBytesWritten:(int64_t)count;
|
||||
- (void)_setCompletionHandler:(GSNSURLSessionDownloadCompletionHandler)handler;
|
||||
|
||||
@end
|
|
@ -298,7 +298,6 @@ GS_DECLARE NSString* const NSFormalName = @"NSFormalName";
|
|||
|
||||
/* For GNUstep */
|
||||
GS_DECLARE NSString* const GSLocale = @"GSLocale";
|
||||
GS_DECLARE NSString *const GSCACertificateFilePath = @"GSCACertificateFilePath";
|
||||
|
||||
|
||||
/*
|
||||
|
|
1
Tests/base/NSURLSession/.gitignore
vendored
1
Tests/base/NSURLSession/.gitignore
vendored
|
@ -1 +0,0 @@
|
|||
Helpers/HTTPServer.bundle
|
|
@ -1,8 +1,5 @@
|
|||
|
||||
include $(GNUSTEP_MAKEFILES)/common.make
|
||||
|
||||
ifeq ($(the_library_combo), ng-gnu-gnu)
|
||||
SUBPROJECTS = Helpers
|
||||
SUBPROJECTS = ../NSURLConnection/Helpers
|
||||
|
||||
include $(GNUSTEP_MAKEFILES)/aggregate.make
|
||||
endif
|
||||
|
||||
|
|
|
@ -1,20 +0,0 @@
|
|||
include $(GNUSTEP_MAKEFILES)/common.make
|
||||
|
||||
BUNDLE_NAME = HTTPServer
|
||||
|
||||
NEEDS_GUI=NO
|
||||
|
||||
HTTPServer_OBJC_FILES = HTTPServer.m
|
||||
HTTPServer_OBJC_LIBS += -ldispatch
|
||||
|
||||
ifeq ($(GNUSTEP_TARGET_OS), windows)
|
||||
HTTPServer_OBJC_LIBS += -lws2_32
|
||||
endif
|
||||
|
||||
HTTPServer_OBJCFLAGS += -fobjc-arc
|
||||
#HTTPServer_RESOURCE_FILES += key.pem certificate.pem
|
||||
HTTPServer_PRINCIPAL_CLASS = HTTPServer
|
||||
|
||||
-include GNUmakefile.preamble
|
||||
include $(GNUSTEP_MAKEFILES)/bundle.make
|
||||
-include GNUmakefile.postamble
|
|
@ -1,30 +0,0 @@
|
|||
#import <Foundation/NSArray.h>
|
||||
#import <Foundation/NSObject.h>
|
||||
#import <Foundation/NSURLRequest.h>
|
||||
#import <Foundation/NSURLResponse.h>
|
||||
|
||||
typedef NSData * (^RequestHandlerBlock)(NSURLRequest *);
|
||||
|
||||
@interface Route : NSObject
|
||||
|
||||
+ (instancetype)routeWithURL:(NSURL *)url
|
||||
method:(NSString *)method
|
||||
handler:(RequestHandlerBlock)block;
|
||||
|
||||
- (NSString *)method;
|
||||
- (NSURL *)url;
|
||||
- (RequestHandlerBlock)block;
|
||||
|
||||
- (BOOL)acceptsURL:(NSURL *)url method:(NSString *)method;
|
||||
|
||||
@end
|
||||
|
||||
@interface HTTPServer : NSObject
|
||||
- initWithPort:(NSInteger)port routes:(NSArray<Route *> *)routes;
|
||||
|
||||
- (NSInteger)port;
|
||||
- (void)resume;
|
||||
- (void)suspend;
|
||||
|
||||
- (void)setRoutes:(NSArray<Route *> *)routes;
|
||||
@end
|
|
@ -1,402 +0,0 @@
|
|||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import <dispatch/dispatch.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#import <winsock2.h>
|
||||
|
||||
#define close(x) closesocket(x)
|
||||
#else
|
||||
|
||||
#import <netinet/in.h>
|
||||
#import <sys/socket.h>
|
||||
|
||||
#endif
|
||||
|
||||
#import "HTTPServer.h"
|
||||
|
||||
@interface
|
||||
NSString (ServerAdditions)
|
||||
- (void)enumerateLinesUsingBlock2:
|
||||
(void (^)(NSString *line, NSUInteger lineEndIndex, BOOL *stop))block;
|
||||
@end
|
||||
|
||||
@implementation
|
||||
NSString (ServerAdditions)
|
||||
|
||||
- (void)enumerateLinesUsingBlock2:
|
||||
(void (^)(NSString *line, NSUInteger lineEndIndex, BOOL *stop))block
|
||||
{
|
||||
NSUInteger length;
|
||||
NSUInteger lineStart, lineEnd, contentsEnd;
|
||||
NSRange currentLocationRange;
|
||||
BOOL stop;
|
||||
|
||||
length = [self length];
|
||||
lineStart = lineEnd = contentsEnd = 0;
|
||||
stop = NO;
|
||||
|
||||
// Enumerate through the string line by line
|
||||
while (lineStart < length && !stop)
|
||||
{
|
||||
NSString *line;
|
||||
NSRange lineRange;
|
||||
|
||||
currentLocationRange = NSMakeRange(lineStart, 0);
|
||||
[self getLineStart:&lineStart
|
||||
end:&lineEnd
|
||||
contentsEnd:&contentsEnd
|
||||
forRange:currentLocationRange];
|
||||
|
||||
lineRange = NSMakeRange(lineStart, contentsEnd - lineStart);
|
||||
line = [self substringWithRange:lineRange];
|
||||
|
||||
// Execute the block
|
||||
block(line, lineEnd, &stop);
|
||||
|
||||
// Move to the next line
|
||||
lineStart = lineEnd;
|
||||
}
|
||||
}
|
||||
@end
|
||||
|
||||
/* We don't need this once toll-free bridging works */
|
||||
NSData *
|
||||
copyDispatchDataToNSData(dispatch_data_t dispatchData)
|
||||
{
|
||||
NSMutableData *mutableData =
|
||||
[NSMutableData dataWithCapacity:dispatch_data_get_size(dispatchData)];
|
||||
|
||||
dispatch_data_apply(dispatchData, ^bool(dispatch_data_t region, size_t offset,
|
||||
const void *buffer, size_t size) {
|
||||
[mutableData appendBytes:buffer length:size];
|
||||
return true; // Continue iterating
|
||||
});
|
||||
|
||||
return [mutableData copy];
|
||||
}
|
||||
|
||||
@implementation Route
|
||||
{
|
||||
NSString *_method;
|
||||
NSURL *_url;
|
||||
RequestHandlerBlock _block;
|
||||
}
|
||||
+ (instancetype)routeWithURL:(NSURL *)url
|
||||
method:(NSString *)method
|
||||
handler:(RequestHandlerBlock)block
|
||||
{
|
||||
return [[Route alloc] initWithURL:url method:method handler:block];
|
||||
}
|
||||
|
||||
- (instancetype)initWithURL:(NSURL *)url
|
||||
method:(NSString *)method
|
||||
handler:(RequestHandlerBlock)block
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_url = url;
|
||||
_method = method;
|
||||
_block = block;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSString *)method
|
||||
{
|
||||
return _method;
|
||||
}
|
||||
- (NSURL *)url
|
||||
{
|
||||
return _url;
|
||||
}
|
||||
- (RequestHandlerBlock)block
|
||||
{
|
||||
return _block;
|
||||
}
|
||||
|
||||
- (BOOL)acceptsURL:(NSURL *)url method:(NSString *)method
|
||||
{
|
||||
return [[_url path] isEqualTo:[url path]];
|
||||
}
|
||||
|
||||
@end /* Route */
|
||||
|
||||
@implementation HTTPServer
|
||||
{
|
||||
_Atomic(BOOL) _stop;
|
||||
int _socket;
|
||||
NSInteger _port;
|
||||
NSArray<Route *> *_routes;
|
||||
dispatch_queue_t _queue;
|
||||
dispatch_queue_t _acceptQueue;
|
||||
}
|
||||
|
||||
- initWithPort:(NSInteger)port routes:(NSArray<Route *> *)routes
|
||||
{
|
||||
self = [super init];
|
||||
if (!self)
|
||||
{
|
||||
return nil;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
WSADATA wsaData;
|
||||
|
||||
// Initialise WinSock2 API
|
||||
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
|
||||
{
|
||||
NSLog(@"Error Creating Socket: %d", WSAGetLastError());
|
||||
return nil;
|
||||
}
|
||||
#endif
|
||||
|
||||
_stop = YES;
|
||||
_socket = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (_socket == -1)
|
||||
{
|
||||
NSLog(@"Error creating socket %s", strerror(errno));
|
||||
return nil;
|
||||
}
|
||||
|
||||
_routes = [routes copy];
|
||||
|
||||
struct sockaddr_in serverAddr;
|
||||
NSUInteger addrLen = sizeof(struct sockaddr_in);
|
||||
serverAddr.sin_family = AF_INET;
|
||||
serverAddr.sin_port = NSSwapHostShortToBig(port);
|
||||
serverAddr.sin_addr.s_addr = INADDR_ANY;
|
||||
|
||||
int rc;
|
||||
int yes = 1;
|
||||
rc = setsockopt(_socket, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int));
|
||||
if (rc == -1)
|
||||
{
|
||||
NSLog(@"Error setting socket options %s", strerror(errno));
|
||||
return nil;
|
||||
}
|
||||
|
||||
rc = bind(_socket, (struct sockaddr *) &serverAddr, sizeof(struct sockaddr));
|
||||
if (rc < 0)
|
||||
{
|
||||
NSLog(@"Error binding to socket %s", strerror(errno));
|
||||
return nil;
|
||||
}
|
||||
|
||||
// Get Port Number
|
||||
if (getsockname(_socket, (struct sockaddr *) &serverAddr, &addrLen) == -1)
|
||||
{
|
||||
NSLog(@"Error getting socket name %s", strerror(errno));
|
||||
return nil;
|
||||
}
|
||||
_port = NSSwapBigShortToHost(serverAddr.sin_port);
|
||||
|
||||
rc = listen(_socket, 20);
|
||||
if (rc < 0)
|
||||
{
|
||||
NSLog(@"Error listening on socket %s", strerror(errno));
|
||||
return nil;
|
||||
}
|
||||
|
||||
_queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
|
||||
_acceptQueue = dispatch_queue_create("org.gnustep.HTTPServer.AcceptQueue",
|
||||
DISPATCH_QUEUE_CONCURRENT);
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)acceptConnection
|
||||
{
|
||||
struct sockaddr_in clientAddr;
|
||||
dispatch_io_t ioChannel;
|
||||
NSUInteger sin_size;
|
||||
int clientSocket;
|
||||
|
||||
sin_size = sizeof(struct sockaddr_in);
|
||||
clientSocket = accept(_socket, (struct sockaddr *) &clientAddr, &sin_size);
|
||||
if (clientSocket < 0)
|
||||
{
|
||||
NSLog(@"Error accepting connection %s", strerror(errno));
|
||||
return;
|
||||
}
|
||||
|
||||
ioChannel
|
||||
= dispatch_io_create(DISPATCH_IO_STREAM, clientSocket, _queue,
|
||||
^(int error) {
|
||||
close(clientSocket);
|
||||
|
||||
if (error)
|
||||
{
|
||||
NSLog(@"Error creating dispatch I/O channel %s",
|
||||
strerror(error));
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
dispatch_io_set_low_water(ioChannel, 1);
|
||||
|
||||
dispatch_io_read(ioChannel, 0, SIZE_MAX, _queue,
|
||||
^(bool done, dispatch_data_t data, int error) {
|
||||
if (error)
|
||||
{
|
||||
NSLog(@"Error reading data %s", strerror(error));
|
||||
dispatch_io_close(ioChannel, DISPATCH_IO_STOP);
|
||||
return;
|
||||
}
|
||||
if (data && dispatch_data_get_size(data) != 0)
|
||||
{
|
||||
[self handleConnectionData:data
|
||||
forSocket:clientSocket];
|
||||
}
|
||||
if (done)
|
||||
{
|
||||
dispatch_io_close(ioChannel, DISPATCH_IO_STOP);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
- (void)handleConnectionData:(dispatch_data_t)data forSocket:(int)sock
|
||||
{
|
||||
NSData *reqData;
|
||||
NSString *reqString;
|
||||
NSRange bodyRange;
|
||||
NSString *method, *url, *version;
|
||||
NSURL *requestURL;
|
||||
NSScanner *scanner;
|
||||
Route *selectedRoute = nil;
|
||||
|
||||
__block NSString *firstLine = nil;
|
||||
__block NSMutableURLRequest *request = [NSMutableURLRequest new];
|
||||
__block NSUInteger headerEndIndex = 1;
|
||||
|
||||
reqData = copyDispatchDataToNSData(data);
|
||||
reqString = [[NSString alloc] initWithData:reqData
|
||||
encoding:NSUTF8StringEncoding];
|
||||
|
||||
/*
|
||||
* generic-message = Request-Line
|
||||
* *(message-header CRLF)
|
||||
* CRLF
|
||||
* [ message-body ]
|
||||
* Request-Line = Method SP Request-URI SP HTTP-Version CRLF
|
||||
*/
|
||||
[reqString enumerateLinesUsingBlock2:^(NSString *line,
|
||||
NSUInteger lineEndIndex, BOOL *stop) {
|
||||
NSRange range;
|
||||
NSString *key, *value;
|
||||
NSCharacterSet *set;
|
||||
|
||||
set = [NSCharacterSet whitespaceCharacterSet];
|
||||
|
||||
/* Parse Request Line */
|
||||
if (nil == firstLine)
|
||||
{
|
||||
firstLine = [line stringByTrimmingCharactersInSet:set];
|
||||
return;
|
||||
}
|
||||
|
||||
/* Reached end of message header. Stop. */
|
||||
if ([line length] == 0)
|
||||
{
|
||||
*stop = YES;
|
||||
headerEndIndex = lineEndIndex;
|
||||
}
|
||||
|
||||
range = [line rangeOfString:@":"];
|
||||
/* Ignore this line */
|
||||
if (NSNotFound == range.location)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
key = [[line substringToIndex:range.location]
|
||||
stringByTrimmingCharactersInSet:set];
|
||||
value = [[line substringFromIndex:range.location + 1]
|
||||
stringByTrimmingCharactersInSet:set];
|
||||
|
||||
[request addValue:value forHTTPHeaderField:key];
|
||||
}];
|
||||
|
||||
/* Calculate remaining body range */
|
||||
bodyRange = NSMakeRange(headerEndIndex, [reqData length] - headerEndIndex);
|
||||
reqData = [reqData subdataWithRange:bodyRange];
|
||||
|
||||
/* Parse Request Line */
|
||||
scanner = [NSScanner scannerWithString:firstLine];
|
||||
[scanner scanUpToString:@" " intoString:&method];
|
||||
[scanner scanUpToString:@" " intoString:&url];
|
||||
[scanner scanUpToString:@" " intoString:&version];
|
||||
|
||||
requestURL = [NSURL URLWithString:url];
|
||||
|
||||
[request setURL:requestURL];
|
||||
[request setHTTPMethod:method];
|
||||
[request setHTTPBody:reqData];
|
||||
|
||||
for (Route *r in _routes)
|
||||
{
|
||||
if ([r acceptsURL:requestURL method:method])
|
||||
{
|
||||
selectedRoute = r;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
NSData *responseData;
|
||||
if (selectedRoute)
|
||||
{
|
||||
responseData = [selectedRoute block]([request copy]);
|
||||
}
|
||||
else
|
||||
{
|
||||
responseData = [@"HTTP/1.1 404 Not Found\r\nContent-Length: 0\r\n\r\n"
|
||||
dataUsingEncoding:NSASCIIStringEncoding];
|
||||
}
|
||||
|
||||
send(sock, [responseData bytes], [responseData length], 0);
|
||||
}
|
||||
|
||||
- (void)setRoutes:(NSArray<Route *> *)routes
|
||||
{
|
||||
_routes = [routes copy];
|
||||
}
|
||||
|
||||
- (NSInteger)port
|
||||
{
|
||||
return _port;
|
||||
}
|
||||
|
||||
- (void)resume
|
||||
{
|
||||
if (_stop)
|
||||
{
|
||||
_stop = NO;
|
||||
dispatch_async(_acceptQueue, ^{
|
||||
while (!_stop)
|
||||
{
|
||||
[self acceptConnection];
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
- (void)suspend
|
||||
{
|
||||
_stop = YES;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
#ifndef __APPLE__
|
||||
dispatch_release(_acceptQueue);
|
||||
#endif
|
||||
|
||||
close(_socket);
|
||||
#ifdef _WIN32
|
||||
WSACleanup();
|
||||
#endif
|
||||
}
|
||||
|
||||
@end
|
|
@ -1,26 +0,0 @@
|
|||
#import <Foundation/NSDate.h>
|
||||
#import <Foundation/NSRunLoop.h>
|
||||
|
||||
@interface
|
||||
NSRunLoop (TimeOutAdditions)
|
||||
- (void)runForSeconds:(NSTimeInterval)seconds conditionBlock:(BOOL (^)())block;
|
||||
@end
|
||||
|
||||
@implementation
|
||||
NSRunLoop (TimeOutAdditions)
|
||||
- (void)runForSeconds:(NSTimeInterval)seconds conditionBlock:(BOOL (^)())block
|
||||
{
|
||||
NSDate *startDate = [NSDate date];
|
||||
NSTimeInterval endTime = [startDate timeIntervalSince1970] + seconds;
|
||||
NSTimeInterval interval = 0.1; // Interval to check the condition
|
||||
|
||||
while (block() && [[NSDate date] timeIntervalSince1970] < endTime)
|
||||
{
|
||||
@autoreleasepool
|
||||
{
|
||||
[[NSRunLoop currentRunLoop]
|
||||
runUntilDate:[NSDate dateWithTimeIntervalSinceNow:interval]];
|
||||
}
|
||||
}
|
||||
}
|
||||
@end
|
|
@ -1,54 +0,0 @@
|
|||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas volutpat justo
|
||||
in aliquet venenatis. Aliquam eu odio volutpat, euismod turpis a, accumsan leo.
|
||||
Aliquam mauris urna, dictum a ex vel, fermentum faucibus enim. Donec eu massa
|
||||
ipsum. Vestibulum diam metus, dictum mattis urna vel, dignissim pretium magna.
|
||||
Phasellus quis mattis felis. Morbi non dapibus nunc. Fusce id ligula fringilla,
|
||||
varius tortor et, vulputate arcu. Quisque justo dolor, bibendum et imperdiet
|
||||
ultricies, posuere vel elit. Aliquam vel risus et sapien malesuada tempor.
|
||||
Aenean lorem metus, rutrum ut massa non, ultrices tincidunt neque. Nullam
|
||||
viverra rhoncus sem et convallis.
|
||||
|
||||
Sed id ligula non mauris lacinia mattis. Donec commodo tortor sed aliquet
|
||||
commodo. Curabitur auctor, nibh eu dignissim gravida, lacus eros tincidunt
|
||||
metus, ac fermentum mauris ex vitae magna. Vestibulum sed egestas elit, eu
|
||||
placerat enim. Vivamus ullamcorper sollicitudin ullamcorper. Mauris consequat
|
||||
quam et purus malesuada, quis euismod nisl scelerisque. Sed vestibulum dictum
|
||||
nisi a viverra. Ut rutrum aliquam porta. Pellentesque est augue, ullamcorper vel
|
||||
ultricies quis, posuere non turpis. Aenean suscipit nulla massa, vel venenatis
|
||||
felis molestie ut. Morbi in lorem quam. Quisque sed facilisis neque, et mattis
|
||||
ligula. Nam non placerat odio, ac sollicitudin dolor. Donec elit neque, rhoncus
|
||||
at risus fermentum, ultrices hendrerit massa. Nulla volutpat, urna id molestie
|
||||
scelerisque, ligula est laoreet mauris, eget maximus leo purus id mi.
|
||||
|
||||
Aenean arcu metus, ornare eget est sit amet, vehicula accumsan enim. Morbi ante
|
||||
diam, faucibus nec tempus molestie, vulputate auctor massa. Curabitur posuere
|
||||
iaculis pulvinar. Fusce id ligula at justo iaculis accumsan eu sed velit.
|
||||
Quisque condimentum augue non lobortis finibus. In quis interdum ligula, a
|
||||
varius ex. Vestibulum tristique orci eget velit lacinia, at lacinia lorem
|
||||
pretium. Sed ut varius mi. Vivamus elementum luctus arcu, vitae porta felis
|
||||
cursus nec.
|
||||
|
||||
Etiam sed nisl eu nulla scelerisque fermentum. Praesent blandit eros at lacus
|
||||
blandit auctor. Sed in eros sapien. Quisque ac laoreet nisi, a fringilla nunc.
|
||||
Sed auctor eros non mauris elementum vestibulum. Aenean vel urna orci.
|
||||
Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia
|
||||
curae; In sed blandit nibh. Aenean condimentum, lorem a tincidunt consequat,
|
||||
nulla nulla ornare lorem, eu cursus sapien mi aliquam arcu. Duis porttitor mi et
|
||||
ultricies porta. Donec tempus posuere lorem sit amet tincidunt. Nam nec purus
|
||||
sit amet sem accumsan consectetur. Ut a nisl volutpat, egestas diam non, feugiat
|
||||
mi.
|
||||
|
||||
Cras sed risus eget ligula tincidunt luctus. Vestibulum ut sapien dictum,
|
||||
sagittis elit vel, semper diam. Curabitur tristique ullamcorper lacus, quis
|
||||
finibus ex rutrum vel. Phasellus non blandit nisl, id tincidunt augue. Integer
|
||||
commodo metus at augue fringilla, sit amet posuere lectus iaculis. In nunc
|
||||
lacus, pulvinar scelerisque faucibus ut, fermentum ac ante. Duis in ante
|
||||
faucibus, ullamcorper est vel, convallis lacus. Nulla fermentum, nisi eget
|
||||
sollicitudin sodales, augue felis gravida est, id luctus sem augue non turpis.
|
||||
Sed a lorem vel diam maximus malesuada ut ut leo. Fusce venenatis, tellus a
|
||||
scelerisque dictum, metus elit vehicula purus, id gravida libero libero vitae
|
||||
arcu. Curabitur a cursus metus. Aenean efficitur rhoncus ante vitae imperdiet.
|
||||
Etiam ante lacus, aliquam id sem non, ullamcorper interdum libero. Sed fringilla
|
||||
dui sed est vehicula, vitae sollicitudin mauris tristique. Praesent ut arcu ut
|
||||
neque sollicitudin finibus. Vestibulum lacus risus, egestas vel lorem eget,
|
||||
pellentesque scelerisque lectus.
|
|
@ -1,224 +0,0 @@
|
|||
#import <Foundation/NSURLSession.h>
|
||||
#import <Foundation/NSData.h>
|
||||
|
||||
@class URLManager;
|
||||
|
||||
typedef void (^URLManagerCheckBlock)(URLManager *);
|
||||
|
||||
@interface URLManager
|
||||
: NSObject <NSURLSessionDataDelegate, NSURLSessionTaskDelegate,
|
||||
NSURLSessionDelegate, NSURLSessionDownloadDelegate>
|
||||
{
|
||||
URLManagerCheckBlock _checkBlock;
|
||||
|
||||
@public
|
||||
NSURLSession *currentSession;
|
||||
NSURLSessionResponseDisposition responseAnswer;
|
||||
|
||||
NSInteger numberOfExpectedTasksBeforeCheck;
|
||||
|
||||
NSInteger didCreateTaskCount;
|
||||
NSURLSessionTask *didCreateTask;
|
||||
|
||||
NSInteger didBecomeInvalidCount;
|
||||
NSError *didBecomeInvalidError;
|
||||
|
||||
NSInteger httpRedirectionCount;
|
||||
NSURLSessionTask *httpRedirectionTask;
|
||||
NSHTTPURLResponse *httpRedirectionResponse;
|
||||
NSURLRequest *httpRedirectionRequest;
|
||||
|
||||
NSInteger didCompleteCount;
|
||||
NSURLSessionTask *didCompleteTask;
|
||||
NSError *didCompleteError;
|
||||
|
||||
NSInteger didWriteDataCount;
|
||||
NSURLSessionDownloadTask *didWriteDataTask;
|
||||
int64_t downloadBytesWritten;
|
||||
int64_t downloadTotalBytesWritten;
|
||||
int64_t downloadTotalBytesExpectedToWrite;
|
||||
|
||||
NSInteger didFinishDownloadingCount;
|
||||
NSURLSessionDownloadTask *didFinishDownloadingTask;
|
||||
NSURL *didFinishDownloadingURL;
|
||||
|
||||
NSInteger didReceiveResponseCount;
|
||||
NSURLSessionDataTask *didReceiveResponseTask;
|
||||
NSURLResponse *didReceiveResponse;
|
||||
|
||||
NSInteger didReceiveDataCount;
|
||||
NSURLSessionDataTask *didReceiveDataTask;
|
||||
NSMutableData *accumulatedData;
|
||||
|
||||
BOOL cancelRedirect;
|
||||
}
|
||||
|
||||
- (void)setCheckBlock:(URLManagerCheckBlock)block;
|
||||
|
||||
@end
|
||||
|
||||
@implementation URLManager
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
self = [super init];
|
||||
if (self)
|
||||
{
|
||||
responseAnswer = NSURLSessionResponseAllow;
|
||||
accumulatedData = [[NSMutableData alloc] init];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setCheckBlock:(URLManagerCheckBlock)block
|
||||
{
|
||||
_checkBlock = _Block_copy(block);
|
||||
}
|
||||
|
||||
#pragma mark - Session Lifecycle
|
||||
|
||||
- (void)URLSession:(NSURLSession *)session
|
||||
didCreateTask:(NSURLSessionTask *)task
|
||||
{
|
||||
ASSIGN(currentSession, session);
|
||||
|
||||
didCreateTaskCount += 1;
|
||||
ASSIGN(didCreateTask, task);
|
||||
}
|
||||
- (void)URLSession:(NSURLSession *)session
|
||||
didBecomeInvalidWithError:(NSError *)error
|
||||
{
|
||||
ASSIGN(currentSession, session);
|
||||
ASSIGN(didBecomeInvalidError, error);
|
||||
|
||||
didBecomeInvalidCount += 1;
|
||||
}
|
||||
|
||||
#pragma mark - Task Updates
|
||||
|
||||
- (void)URLSession:(NSURLSession *)session
|
||||
task:(NSURLSessionTask *)task
|
||||
willPerformHTTPRedirection:(NSHTTPURLResponse *)response
|
||||
newRequest:(NSURLRequest *)request
|
||||
completionHandler:(void (^)(NSURLRequest *))completionHandler
|
||||
{
|
||||
ASSIGN(currentSession, session);
|
||||
ASSIGN(httpRedirectionTask, task);
|
||||
ASSIGN(httpRedirectionResponse, response);
|
||||
ASSIGN(httpRedirectionRequest, request);
|
||||
|
||||
if (cancelRedirect)
|
||||
{
|
||||
completionHandler(NULL);
|
||||
}
|
||||
else
|
||||
{
|
||||
completionHandler(request);
|
||||
}
|
||||
|
||||
httpRedirectionCount += 1;
|
||||
}
|
||||
|
||||
- (void)URLSession:(NSURLSession *)session
|
||||
task:(NSURLSessionTask *)task
|
||||
didCompleteWithError:(nullable NSError *)error
|
||||
{
|
||||
ASSIGN(currentSession, session);
|
||||
ASSIGN(didCompleteTask, task);
|
||||
ASSIGN(didCompleteError, error);
|
||||
|
||||
didCompleteCount += 1;
|
||||
if (didCompleteCount == numberOfExpectedTasksBeforeCheck
|
||||
&& _checkBlock != NULL)
|
||||
{
|
||||
_checkBlock(self);
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Data Updates
|
||||
|
||||
- (void)URLSession:(NSURLSession *)session
|
||||
dataTask:(NSURLSessionDataTask *)dataTask
|
||||
didReceiveResponse:(NSURLResponse *)response
|
||||
completionHandler:
|
||||
(void (^)(NSURLSessionResponseDisposition))completionHandler
|
||||
{
|
||||
ASSIGN(currentSession, session);
|
||||
ASSIGN(didReceiveResponseTask, dataTask);
|
||||
ASSIGN(didReceiveResponse, response);
|
||||
|
||||
didReceiveResponseCount += 1;
|
||||
|
||||
completionHandler(responseAnswer);
|
||||
}
|
||||
|
||||
- (void)URLSession:(NSURLSession *)session
|
||||
dataTask:(NSURLSessionDataTask *)dataTask
|
||||
didReceiveData:(NSData *)data
|
||||
{
|
||||
ASSIGN(currentSession, session);
|
||||
ASSIGN(didReceiveResponseTask, dataTask);
|
||||
|
||||
didReceiveDataCount += 1;
|
||||
|
||||
[accumulatedData appendData:data];
|
||||
}
|
||||
|
||||
#pragma mark - Download Updates
|
||||
|
||||
- (void)URLSession:(NSURLSession *)session
|
||||
downloadTask:(NSURLSessionDownloadTask *)downloadTask
|
||||
didWriteData:(int64_t)bytesWritten
|
||||
totalBytesWritten:(int64_t)totalBytesWritten
|
||||
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
|
||||
{
|
||||
ASSIGN(currentSession, session);
|
||||
|
||||
didWriteDataCount += 1;
|
||||
downloadBytesWritten = bytesWritten;
|
||||
downloadTotalBytesExpectedToWrite = totalBytesExpectedToWrite;
|
||||
downloadTotalBytesExpectedToWrite = totalBytesExpectedToWrite;
|
||||
}
|
||||
|
||||
- (void)URLSession:(NSURLSession *)session
|
||||
downloadTask:(NSURLSessionDownloadTask *)downloadTask
|
||||
didFinishDownloadingToURL:(NSURL *)location
|
||||
{
|
||||
ASSIGN(currentSession, session);
|
||||
ASSIGN(didFinishDownloadingTask, downloadTask);
|
||||
ASSIGN(didFinishDownloadingURL, location);
|
||||
|
||||
didFinishDownloadingCount += 1;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
RELEASE(currentSession);
|
||||
|
||||
RELEASE(didCreateTask);
|
||||
RELEASE(didBecomeInvalidError);
|
||||
|
||||
RELEASE(httpRedirectionTask);
|
||||
RELEASE(httpRedirectionResponse);
|
||||
RELEASE(httpRedirectionRequest);
|
||||
|
||||
RELEASE(didCompleteTask);
|
||||
RELEASE(didCompleteError);
|
||||
|
||||
RELEASE(didWriteDataTask);
|
||||
|
||||
RELEASE(didFinishDownloadingTask);
|
||||
RELEASE(didFinishDownloadingURL);
|
||||
|
||||
RELEASE(didReceiveResponseTask);
|
||||
RELEASE(didReceiveResponse);
|
||||
|
||||
RELEASE(didReceiveDataTask);
|
||||
RELEASE(accumulatedData);
|
||||
|
||||
_Block_release(_checkBlock);
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
@end
|
131
Tests/base/NSURLSession/delegate.g
Normal file
131
Tests/base/NSURLSession/delegate.g
Normal file
|
@ -0,0 +1,131 @@
|
|||
#import <Foundation/Foundation.h>
|
||||
#import "Testing.h"
|
||||
#import "ObjectTesting.h"
|
||||
|
||||
@interface MyDelegate : NSObject <NSURLSessionDelegate>
|
||||
{
|
||||
BOOL _finished;
|
||||
NSMutableArray *_order;
|
||||
NSURLResponse *_response;
|
||||
NSData *_taskData;
|
||||
NSString *_taskText;
|
||||
NSError *_taskError;
|
||||
NSURL *_taskLocation;
|
||||
}
|
||||
- (BOOL) finished;
|
||||
- (void) reset;
|
||||
@end
|
||||
|
||||
@implementation MyDelegate
|
||||
- (void) dealloc
|
||||
{
|
||||
[self reset];
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (BOOL) finished
|
||||
{
|
||||
return _finished;
|
||||
}
|
||||
|
||||
- (id) init
|
||||
{
|
||||
if (nil != (self = [super init]))
|
||||
{
|
||||
_order = [NSMutableArray new];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSMutableArray*) order
|
||||
{
|
||||
return _order;
|
||||
}
|
||||
|
||||
- (void) reset
|
||||
{
|
||||
DESTROY(_order);
|
||||
DESTROY(_response);
|
||||
DESTROY(_taskData);
|
||||
DESTROY(_taskError);
|
||||
DESTROY(_taskText);
|
||||
DESTROY(_taskLocation);
|
||||
_finished = NO;
|
||||
}
|
||||
|
||||
- (NSURLResponse*) response
|
||||
{
|
||||
return _response;
|
||||
}
|
||||
- (NSData*) taskData
|
||||
{
|
||||
return _taskData;
|
||||
}
|
||||
- (NSError*) taskError
|
||||
{
|
||||
return _taskError;
|
||||
}
|
||||
- (NSString*) taskText
|
||||
{
|
||||
return _taskText;
|
||||
}
|
||||
|
||||
- (NSURL*) taskLocation
|
||||
{
|
||||
return _taskLocation;
|
||||
}
|
||||
|
||||
- (void) URLSession: (NSURLSession*)session
|
||||
dataTask: (NSURLSessionDataTask*)dataTask
|
||||
didReceiveResponse: (NSURLResponse*)response
|
||||
completionHandler: (void (^)(NSURLSessionResponseDisposition disposition))completionHandler
|
||||
{
|
||||
[_order addObject: NSStringFromSelector(_cmd)];
|
||||
ASSIGN(_response, response);
|
||||
completionHandler(NSURLSessionResponseAllow);
|
||||
}
|
||||
|
||||
- (void) URLSession: (NSURLSession*)session
|
||||
dataTask: (NSURLSessionDataTask*)dataTask
|
||||
didReceiveData: (NSData*)data
|
||||
{
|
||||
[_order addObject: NSStringFromSelector(_cmd)];
|
||||
|
||||
NSString *text;
|
||||
|
||||
ASSIGN(_taskData, data);
|
||||
text = [[NSString alloc] initWithData: data encoding: NSUTF8StringEncoding];
|
||||
if (nil != text)
|
||||
{
|
||||
ASSIGN(_taskText, text);
|
||||
}
|
||||
RELEASE(text);
|
||||
}
|
||||
|
||||
/* NSURLSessionDownloadDelegate */
|
||||
- (void) URLSession: (NSURLSession *)session
|
||||
downloadTask: (NSURLSessionDownloadTask *)downloadTask
|
||||
didFinishDownloadingToURL: (NSURL *)location
|
||||
{
|
||||
ASSIGN(_taskLocation, location);
|
||||
}
|
||||
|
||||
- (void) URLSession: (NSURLSession*)session
|
||||
task: (NSURLSessionTask*)task
|
||||
didCompleteWithError: (NSError*)error
|
||||
{
|
||||
[_order addObject: NSStringFromSelector(_cmd)];
|
||||
_finished = YES;
|
||||
|
||||
if (error == nil)
|
||||
{
|
||||
NSLog(@"Download is Successful");
|
||||
}
|
||||
else
|
||||
{
|
||||
NSLog(@"Error %@", [error userInfo]);
|
||||
}
|
||||
ASSIGN(_taskError, error);
|
||||
}
|
||||
@end
|
||||
|
79
Tests/base/NSURLSession/gdbinit
Normal file
79
Tests/base/NSURLSession/gdbinit
Normal file
|
@ -0,0 +1,79 @@
|
|||
def print_ivar
|
||||
set $adr = malloc(1000)
|
||||
call strcpy(($adr),($arg1))
|
||||
call GSObjCPrint(($arg0),($adr))
|
||||
call free($adr)
|
||||
end
|
||||
|
||||
def print_ivars
|
||||
if ((($arg0)->isa->info & 0x01) || (($arg0)->isa->info == 2816))
|
||||
# arg0 is a pointer to an object
|
||||
set $cls = ($arg0)->isa
|
||||
else
|
||||
if (($arg0)->isa->info & 0x02)
|
||||
# arg0 is a pointer to a class
|
||||
set $cls = ($arg0)
|
||||
else
|
||||
# arg0 is something else
|
||||
set $cls = 0
|
||||
end
|
||||
end
|
||||
while (($cls) != 0)
|
||||
set $ivars = ($cls)->ivars
|
||||
if (($ivars) != 0)
|
||||
set $i = 0
|
||||
while ($i < ($ivars)->count)
|
||||
output ($ivars)->ivar_list[$i]
|
||||
echo \n
|
||||
set $i = $i + 1
|
||||
end
|
||||
end
|
||||
set $cls = ($cls)->super_class
|
||||
end
|
||||
end
|
||||
|
||||
def pivi
|
||||
print *(int *)((char *)($arg0) + ($arg1)))
|
||||
end
|
||||
|
||||
def pivl
|
||||
print *(long *)((char *)($arg0) + ($arg1)))
|
||||
end
|
||||
|
||||
def pivp
|
||||
print *(void *)((char *)($arg0) + ($arg1)))
|
||||
end
|
||||
|
||||
def pivo
|
||||
po *((id *)((char *)($arg0) + ($arg1)))
|
||||
end
|
||||
|
||||
|
||||
document print_ivars
|
||||
Recursively print the instance varibles of the object or a class given
|
||||
as first (and only) argument.
|
||||
end
|
||||
|
||||
document pivi
|
||||
Print the value of the an instance variable as an int.
|
||||
The first argument is the pointer to the object and the second the
|
||||
offset of the instance variable.
|
||||
end
|
||||
|
||||
document pivl
|
||||
Print the value of the an instance variable as a long.
|
||||
The first argument is the pointer to the object and the second the
|
||||
offset of the instance variable.
|
||||
end
|
||||
|
||||
document pivp
|
||||
Print the value of the an instance variable as a pointer (to void).
|
||||
The first argument is the pointer to the object and the second the
|
||||
offset of the instance variable.
|
||||
end
|
||||
|
||||
document pivo
|
||||
Ask an instance variable to print itself (using po).
|
||||
The first argument is the pointer to the object and the second the
|
||||
offset of the instance variable.
|
||||
end
|
|
@ -1,886 +0,0 @@
|
|||
#import <Foundation/Foundation.h>
|
||||
|
||||
#if GS_HAVE_NSURLSESSION
|
||||
|
||||
#import "Helpers/HTTPServer.h"
|
||||
#import "NSRunLoop+TimeOutAdditions.h"
|
||||
#import "URLManager.h"
|
||||
#import "Testing.h"
|
||||
|
||||
/* Timeout in Seconds */
|
||||
static NSInteger testTimeOut = 60;
|
||||
static NSTimeInterval expectedCountOfTasksToComplete = 0;
|
||||
|
||||
/* Accessed in delegate on different thread.
|
||||
*/
|
||||
static _Atomic(NSInteger) currentCountOfCompletedTasks = 0;
|
||||
static NSLock *countLock;
|
||||
|
||||
static NSDictionary *requestCookieProperties;
|
||||
|
||||
static NSArray<Route *> *
|
||||
createRoutes(Class routeClass, NSURL *baseURL)
|
||||
{
|
||||
Route *routeOKWithContent;
|
||||
Route *routeTmpRedirectToOK;
|
||||
Route *routeTmpRelativeRedirectToOK;
|
||||
Route *routeSetCookiesOK;
|
||||
Route *routeFoldedHeaders;
|
||||
Route *routeIncorrectlyFoldedHeaders;
|
||||
NSURL *routeOKWithContentURL;
|
||||
NSURL *routeTmpRedirectToOKURL;
|
||||
NSURL *routeTmpRelativeRedirectToOKURL;
|
||||
NSURL *routeSetCookiesOKURL;
|
||||
NSURL *routeFoldedHeadersURL;
|
||||
NSURL *routeIncorrectlyFoldedHeadersURL;
|
||||
|
||||
routeOKWithContentURL = [NSURL URLWithString:@"/contentOK"];
|
||||
routeTmpRedirectToOKURL = [NSURL URLWithString:@"/tmpRedirectToOK"];
|
||||
routeTmpRelativeRedirectToOKURL =
|
||||
[NSURL URLWithString:@"/tmpRelativeRedirectToOK"];
|
||||
routeSetCookiesOKURL = [NSURL URLWithString:@"/setCookiesOK"];
|
||||
routeFoldedHeadersURL = [NSURL URLWithString:@"/foldedHeaders"];
|
||||
routeIncorrectlyFoldedHeadersURL =
|
||||
[NSURL URLWithString:@"/incorrectFoldedHeaders"];
|
||||
|
||||
routeOKWithContent = [routeClass
|
||||
routeWithURL:routeOKWithContentURL
|
||||
method:@"GET"
|
||||
handler:^NSData *(NSURLRequest *req) {
|
||||
NSData *response;
|
||||
|
||||
response =
|
||||
[@"HTTP/1.1 200 OK\r\nContent-Length: 12\r\n\r\nHello World!"
|
||||
dataUsingEncoding:NSASCIIStringEncoding];
|
||||
return response;
|
||||
}];
|
||||
|
||||
routeTmpRedirectToOK = [routeClass
|
||||
routeWithURL:routeTmpRedirectToOKURL
|
||||
method:@"GET"
|
||||
handler:^NSData *(NSURLRequest *req) {
|
||||
NSData *response;
|
||||
NSString *responseString;
|
||||
|
||||
responseString = [NSString
|
||||
stringWithFormat:@"HTTP/1.1 307 Temporary Redirect\r\nLocation: "
|
||||
@"%@\r\nContent-Length: 0\r\n\r\n",
|
||||
[baseURL
|
||||
URLByAppendingPathComponent:@"contentOK"]];
|
||||
|
||||
response = [responseString dataUsingEncoding:NSASCIIStringEncoding];
|
||||
return response;
|
||||
}];
|
||||
|
||||
routeTmpRelativeRedirectToOK = [routeClass
|
||||
routeWithURL:routeTmpRelativeRedirectToOKURL
|
||||
method:@"GET"
|
||||
handler:^NSData *(NSURLRequest *req) {
|
||||
NSData *response;
|
||||
NSString *responseString;
|
||||
|
||||
responseString = [NSString
|
||||
stringWithFormat:@"HTTP/1.1 307 Temporary Redirect\r\nLocation: "
|
||||
@"contentOK\r\nContent-Length: 0\r\n\r\n"];
|
||||
|
||||
response = [responseString dataUsingEncoding:NSASCIIStringEncoding];
|
||||
return response;
|
||||
}];
|
||||
|
||||
routeSetCookiesOK = [routeClass
|
||||
routeWithURL:routeSetCookiesOKURL
|
||||
method:@"GET"
|
||||
handler:^NSData *(NSURLRequest *req) {
|
||||
NSData *response;
|
||||
NSString *httpResponse;
|
||||
|
||||
httpResponse = @"HTTP/1.1 200 OK\r\n"
|
||||
"Content-Type: text/html; charset=UTF-8\r\n"
|
||||
"Set-Cookie: sessionId=abc123; Expires=Wed, 09 Jun "
|
||||
"2100 10:18:14 GMT; Path=/\r\n"
|
||||
"Content-Length: 13\r\n"
|
||||
"\r\n"
|
||||
"Hello, world!";
|
||||
|
||||
NSString *cookie = [req allHTTPHeaderFields][@"Cookie"];
|
||||
PASS(cookie != nil, "Cookie field is not nil");
|
||||
PASS([cookie containsString:@"RequestCookie=1234"],
|
||||
"cookie contains request cookie");
|
||||
|
||||
response = [httpResponse dataUsingEncoding:NSASCIIStringEncoding];
|
||||
return response;
|
||||
}];
|
||||
|
||||
routeFoldedHeaders = [routeClass
|
||||
routeWithURL:routeFoldedHeadersURL
|
||||
method:@"GET"
|
||||
handler:^NSData *(NSURLRequest *req) {
|
||||
NSData *response;
|
||||
|
||||
response =
|
||||
[@"HTTP/1.1 200 OK\r\nContent-Length: 12\r\nFolded-Header-SP: "
|
||||
@"Test\r\n ing\r\nFolded-Header-TAB: Test\r\n\ting\r\n\r\nHello "
|
||||
@"World!" dataUsingEncoding:NSASCIIStringEncoding];
|
||||
return response;
|
||||
}];
|
||||
|
||||
routeIncorrectlyFoldedHeaders = [routeClass
|
||||
routeWithURL:routeIncorrectlyFoldedHeadersURL
|
||||
method:@"GET"
|
||||
handler:^NSData *(NSURLRequest *req) {
|
||||
NSData *response;
|
||||
|
||||
response = [@"HTTP/1.1 200 OK\r\n"
|
||||
@" ing\r\nFolded-Header-TAB: Test\r\n\ting\r\n\r\nHello "
|
||||
@"World!" dataUsingEncoding:NSASCIIStringEncoding];
|
||||
return response;
|
||||
}];
|
||||
|
||||
return @[
|
||||
routeOKWithContent, routeTmpRedirectToOK, routeTmpRelativeRedirectToOK,
|
||||
routeSetCookiesOK, routeFoldedHeaders, routeIncorrectlyFoldedHeaders
|
||||
];
|
||||
}
|
||||
|
||||
/* Block needs to be released */
|
||||
static URLManagerCheckBlock
|
||||
downloadCheckBlock(const char *prefix, NSURLSession *session,
|
||||
NSURLSessionTask *task)
|
||||
{
|
||||
return _Block_copy(^(URLManager *mgr) {
|
||||
NSURL *location;
|
||||
NSData *data;
|
||||
NSString *string;
|
||||
NSFileManager *fm;
|
||||
|
||||
location = mgr->didFinishDownloadingURL;
|
||||
fm = [NSFileManager defaultManager];
|
||||
|
||||
PASS_EQUAL(mgr->currentSession, session,
|
||||
"%s URLManager Session is equal to session", prefix);
|
||||
|
||||
/* Check URLSession:didCreateTask: callback */
|
||||
PASS(mgr->didCreateTaskCount == 1, "%s didCreateTask: Count is correct",
|
||||
prefix);
|
||||
PASS_EQUAL(mgr->didCreateTask, task,
|
||||
"%s didCreateTask: task is equal to returned task", prefix);
|
||||
|
||||
/* Check URLSession:task:didCompleteWithError: */
|
||||
PASS(nil == mgr->didCompleteError,
|
||||
"%s didCompleteWithError: No error occurred", prefix)
|
||||
PASS(mgr->didCompleteCount == 1,
|
||||
"%s didCompleteWithError: Count is correct", prefix);
|
||||
PASS_EQUAL(mgr->didCompleteTask, task,
|
||||
"%s didCompleteWithError: task is equal to returned task",
|
||||
prefix);
|
||||
|
||||
/* Check Progress Reporting */
|
||||
PASS(mgr->didWriteDataCount == 1, "%s didWriteData: count is correct",
|
||||
prefix);
|
||||
PASS(mgr->downloadTotalBytesWritten
|
||||
== mgr->downloadTotalBytesExpectedToWrite,
|
||||
"%s didWriteData: Downloaded all expected data", prefix);
|
||||
PASS(nil != mgr->didFinishDownloadingURL,
|
||||
"%s didWriteData: Download location is not nil", prefix);
|
||||
PASS([location isFileURL], "%s location is a fileURL", prefix);
|
||||
|
||||
data = [NSData dataWithContentsOfURL:location];
|
||||
PASS(nil != data, "%s dataWithContentsOfURL is not nil", prefix)
|
||||
|
||||
string = [[NSString alloc] initWithData:data
|
||||
encoding:NSASCIIStringEncoding];
|
||||
PASS(nil != string, "%s string from data is not nil", prefix);
|
||||
PASS_EQUAL(string, @"Hello World!", "%s data is correct", prefix);
|
||||
|
||||
[string release];
|
||||
|
||||
/* Remove Downloaded Item */
|
||||
if (location)
|
||||
{
|
||||
[fm removeItemAtURL:location error:NULL];
|
||||
}
|
||||
|
||||
[countLock lock];
|
||||
currentCountOfCompletedTasks += 1;
|
||||
[countLock unlock];
|
||||
});
|
||||
}
|
||||
|
||||
static URLManagerCheckBlock
|
||||
dataCheckBlock(const char *prefix, NSURLSession *session,
|
||||
NSURLSessionTask *task)
|
||||
{
|
||||
return _Block_copy(^(URLManager *mgr) {
|
||||
PASS_EQUAL(mgr->currentSession, session,
|
||||
"%s URLManager Session is equal to session", prefix);
|
||||
|
||||
/* Check URLSession:didCreateTask: callback */
|
||||
PASS(mgr->didCreateTaskCount == 1, "%s didCreateTask: Count is correct",
|
||||
prefix);
|
||||
PASS_EQUAL(mgr->didCreateTask, task,
|
||||
"%s didCreateTask: task is equal to returned task", prefix);
|
||||
|
||||
/* Check URLSession:task:didCompleteWithError: */
|
||||
PASS(nil == mgr->didCompleteError,
|
||||
"%s didCompleteWithError: No error occurred", prefix)
|
||||
PASS(mgr->didCompleteCount == 1,
|
||||
"%s didCompleteWithError: Count is correct", prefix);
|
||||
PASS_EQUAL(mgr->didCompleteTask, task,
|
||||
"%s didCompleteWithError: task is equal to returned task",
|
||||
prefix);
|
||||
|
||||
NSData *data = mgr->accumulatedData;
|
||||
PASS(mgr->didReceiveDataCount == 1, "%s didReceiveData: Count is correct",
|
||||
prefix);
|
||||
PASS(nil != data, "%s data in didReceiveData is not nil", prefix);
|
||||
|
||||
NSString *string = [[NSString alloc] initWithData:data
|
||||
encoding:NSASCIIStringEncoding];
|
||||
PASS(nil != string, "%s string from data is not nil", prefix);
|
||||
PASS_EQUAL(string, @"Hello World!", "%s data is correct", prefix);
|
||||
|
||||
[string release];
|
||||
|
||||
[countLock lock];
|
||||
currentCountOfCompletedTasks += 1;
|
||||
[countLock unlock];
|
||||
});
|
||||
}
|
||||
|
||||
/* Block needs to be released */
|
||||
static URLManagerCheckBlock
|
||||
dataCheckBlockFailedRequest(const char *prefix, NSURLSession *session,
|
||||
NSURLSessionTask *task)
|
||||
{
|
||||
return _Block_copy(^(URLManager *mgr) {
|
||||
PASS_EQUAL(mgr->currentSession, session,
|
||||
"%s URLManager Session is equal to session", prefix);
|
||||
|
||||
/* Check URLSession:didCreateTask: callback */
|
||||
PASS(mgr->didCreateTaskCount == 1, "%s didCreateTask: Count is correct",
|
||||
prefix);
|
||||
PASS_EQUAL(mgr->didCreateTask, task,
|
||||
"%s didCreateTask: task is equal to returned task", prefix);
|
||||
|
||||
/* Check URLSession:task:didCompleteWithError: */
|
||||
PASS(nil != mgr->didCompleteError,
|
||||
"%s didCompleteWithError: An error occurred", prefix)
|
||||
PASS(mgr->didCompleteCount == 1,
|
||||
"%s didCompleteWithError: Count is correct", prefix);
|
||||
PASS_EQUAL(mgr->didCompleteTask, task,
|
||||
"%s didCompleteWithError: task is equal to returned task",
|
||||
prefix);
|
||||
|
||||
/* Check didReceiveResponse if not a canceled redirect */
|
||||
if (!mgr->cancelRedirect)
|
||||
{
|
||||
PASS(mgr->didReceiveResponseCount == 1,
|
||||
"%s didReceiveResponse: Count is correct", prefix);
|
||||
PASS(nil != mgr->didReceiveResponse, "%s didReceiveResponse is not nil",
|
||||
prefix);
|
||||
PASS_EQUAL(mgr->didReceiveResponseTask, task,
|
||||
"%s didReceiveResponse: task is equal to returned task",
|
||||
prefix);
|
||||
}
|
||||
else
|
||||
{
|
||||
PASS_EQUAL([mgr->didCompleteError code], NSURLErrorCancelled,
|
||||
"%s didCompleteError is NSURLErrorCancelled", prefix);
|
||||
}
|
||||
|
||||
[countLock lock];
|
||||
currentCountOfCompletedTasks += 1;
|
||||
[countLock unlock];
|
||||
});
|
||||
}
|
||||
|
||||
/* Creates a downloadTaskWithURL: with the /contentOK route.
|
||||
*
|
||||
* Delegate callbacks are checked via the URLManager checkBlock.
|
||||
*/
|
||||
static URLManager *
|
||||
testSimpleDownloadTransfer(NSURL *baseURL)
|
||||
{
|
||||
NSURLSession *session;
|
||||
NSURLSessionConfiguration *configuration;
|
||||
NSURLSessionDownloadTask *task;
|
||||
URLManager *mgr;
|
||||
URLManagerCheckBlock block;
|
||||
const char *prefix = "<DownloadTransfer>";
|
||||
|
||||
NSURL *contentOKURL;
|
||||
|
||||
/* URL Delegate Setup */
|
||||
mgr = [URLManager new];
|
||||
mgr->numberOfExpectedTasksBeforeCheck = 1;
|
||||
expectedCountOfTasksToComplete += 1;
|
||||
|
||||
/* URL Setup */
|
||||
contentOKURL = [baseURL URLByAppendingPathComponent:@"contentOK"];
|
||||
|
||||
configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
|
||||
session = [NSURLSession sessionWithConfiguration:configuration
|
||||
delegate:mgr
|
||||
delegateQueue:nil];
|
||||
|
||||
task = [session downloadTaskWithURL:contentOKURL];
|
||||
PASS(nil != task, "%s Session created a valid download task", prefix);
|
||||
|
||||
/* Setup Check Block */
|
||||
block = downloadCheckBlock(prefix, session, task);
|
||||
[mgr setCheckBlock:block];
|
||||
_Block_release(block);
|
||||
|
||||
[task resume];
|
||||
|
||||
return mgr;
|
||||
}
|
||||
|
||||
static URLManager *
|
||||
testDownloadTransferWithRedirect(NSURL *baseURL)
|
||||
{
|
||||
NSURLSession *session;
|
||||
NSURLSessionConfiguration *configuration;
|
||||
NSURLSessionDownloadTask *task;
|
||||
URLManager *mgr;
|
||||
URLManagerCheckBlock block;
|
||||
const char *prefix = "<DownloadTransferWithRedirect>";
|
||||
|
||||
NSURL *contentOKURL;
|
||||
|
||||
/* URL Delegate Setup */
|
||||
mgr = [URLManager new];
|
||||
mgr->numberOfExpectedTasksBeforeCheck = 1;
|
||||
expectedCountOfTasksToComplete += 1;
|
||||
|
||||
/* URL Setup */
|
||||
contentOKURL = [baseURL URLByAppendingPathComponent:@"tmpRedirectToOK"];
|
||||
|
||||
configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
|
||||
session = [NSURLSession sessionWithConfiguration:configuration
|
||||
delegate:mgr
|
||||
delegateQueue:nil];
|
||||
|
||||
task = [session downloadTaskWithURL:contentOKURL];
|
||||
PASS(nil != task, "%s Session created a valid download task", prefix);
|
||||
|
||||
/* Setup Check Block */
|
||||
block = downloadCheckBlock(prefix, session, task);
|
||||
[mgr setCheckBlock:block];
|
||||
_Block_release(block);
|
||||
|
||||
[task resume];
|
||||
|
||||
return mgr;
|
||||
}
|
||||
|
||||
/* This should use the build in redirection system from libcurl */
|
||||
static void
|
||||
testDataTransferWithRedirectAndBlock(NSURL *baseURL)
|
||||
{
|
||||
NSURLSession *session;
|
||||
NSURLSessionDataTask *task;
|
||||
NSURL *url;
|
||||
|
||||
expectedCountOfTasksToComplete += 1;
|
||||
|
||||
session = [NSURLSession sharedSession];
|
||||
url = [baseURL URLByAppendingPathComponent:@"tmpRedirectToOK"];
|
||||
task = [session
|
||||
dataTaskWithURL:url
|
||||
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
|
||||
NSString *string;
|
||||
const char *prefix = "<DataTransferWithRedirectAndBlock>";
|
||||
|
||||
PASS(nil != data, "%s data in completion handler is not nil", prefix);
|
||||
PASS(nil != response, "%s response is not nil", prefix);
|
||||
PASS([response isKindOfClass:[NSHTTPURLResponse class]],
|
||||
"%s response is an NSHTTPURLResponse", prefix);
|
||||
PASS(nil == error, "%s error is nil", prefix);
|
||||
|
||||
string = [[NSString alloc] initWithData:data
|
||||
encoding:NSASCIIStringEncoding];
|
||||
PASS_EQUAL(string, @"Hello World!", "%s received data is correct",
|
||||
prefix);
|
||||
|
||||
[string release];
|
||||
|
||||
[countLock lock];
|
||||
currentCountOfCompletedTasks += 1;
|
||||
[countLock unlock];
|
||||
}];
|
||||
|
||||
[task resume];
|
||||
}
|
||||
|
||||
static void
|
||||
testDataTransferWithCanceledRedirect(NSURL *baseURL)
|
||||
{
|
||||
NSURLSession *session;
|
||||
NSURLSessionConfiguration *configuration;
|
||||
NSURLSessionDataTask *task;
|
||||
URLManager *mgr;
|
||||
URLManagerCheckBlock block;
|
||||
const char *prefix = "<DataTransferWithCanceledRedirect>";
|
||||
|
||||
NSURL *contentOKURL;
|
||||
|
||||
/* URL Delegate Setup */
|
||||
mgr = [URLManager new];
|
||||
mgr->numberOfExpectedTasksBeforeCheck = 1;
|
||||
mgr->cancelRedirect = YES;
|
||||
expectedCountOfTasksToComplete += 1;
|
||||
|
||||
/* URL Setup */
|
||||
contentOKURL = [baseURL URLByAppendingPathComponent:@"tmpRedirectToOK"];
|
||||
configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
|
||||
session = [NSURLSession sessionWithConfiguration:configuration
|
||||
delegate:mgr
|
||||
delegateQueue:nil];
|
||||
|
||||
task = [session dataTaskWithURL:contentOKURL];
|
||||
PASS(nil != task, "%s Session created a valid download task", prefix);
|
||||
|
||||
/* Setup Check Block */
|
||||
block = dataCheckBlockFailedRequest(prefix, session, task);
|
||||
[mgr setCheckBlock:block];
|
||||
_Block_release(block);
|
||||
|
||||
[task resume];
|
||||
}
|
||||
|
||||
static void
|
||||
testDataTransferWithRelativeRedirect(NSURL *baseURL)
|
||||
{
|
||||
NSURLSession *session;
|
||||
NSURLSessionConfiguration *configuration;
|
||||
NSURLSessionDataTask *task;
|
||||
NSURL *url;
|
||||
URLManager *mgr;
|
||||
URLManagerCheckBlock block;
|
||||
const char *prefix = "<DataTransferWithRelativeRedirect>";
|
||||
|
||||
/* URL Delegate Setup */
|
||||
mgr = [URLManager new];
|
||||
mgr->numberOfExpectedTasksBeforeCheck = 1;
|
||||
expectedCountOfTasksToComplete += 1;
|
||||
|
||||
session = [NSURLSession sharedSession];
|
||||
url = [baseURL URLByAppendingPathComponent:@"tmpRelativeRedirectToOK"];
|
||||
configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
|
||||
session = [NSURLSession sessionWithConfiguration:configuration
|
||||
delegate:mgr
|
||||
delegateQueue:nil];
|
||||
|
||||
task = [session dataTaskWithURL:url];
|
||||
PASS(nil != task, "%s Session created a valid download task", prefix);
|
||||
|
||||
/* Setup Check Block */
|
||||
block = dataCheckBlock(prefix, session, task);
|
||||
[mgr setCheckBlock:block];
|
||||
_Block_release(block);
|
||||
|
||||
[task resume];
|
||||
}
|
||||
|
||||
static void
|
||||
testDownloadTransferWithBlock(NSURL *baseURL)
|
||||
{
|
||||
NSURLSession *session;
|
||||
NSURLSessionDownloadTask *task;
|
||||
NSURL *url;
|
||||
|
||||
expectedCountOfTasksToComplete += 1;
|
||||
|
||||
session = [NSURLSession sharedSession];
|
||||
url = [baseURL URLByAppendingPathComponent:@"contentOK"];
|
||||
task = [session
|
||||
downloadTaskWithURL:url
|
||||
completionHandler:^(NSURL *location, NSURLResponse *response,
|
||||
NSError *error) {
|
||||
NSFileManager *fm;
|
||||
NSData *data;
|
||||
NSString *string;
|
||||
|
||||
const char *prefix;
|
||||
|
||||
prefix = "<DownloadTransferWithBlock>";
|
||||
fm = [NSFileManager defaultManager];
|
||||
|
||||
PASS(nil != location, "%s location is not nil", prefix);
|
||||
PASS(nil != response, "%s response is not nil", prefix);
|
||||
PASS(nil == error, "%s error is nil", prefix);
|
||||
|
||||
data = [NSData dataWithContentsOfURL:location];
|
||||
PASS(nil != data, "%s data is not nil", prefix);
|
||||
|
||||
string = [[NSString alloc] initWithData:data
|
||||
encoding:NSASCIIStringEncoding];
|
||||
PASS_EQUAL(string, @"Hello World!", "%s content is correct", prefix);
|
||||
|
||||
[fm removeItemAtURL:location error:NULL];
|
||||
|
||||
[countLock lock];
|
||||
currentCountOfCompletedTasks += 1;
|
||||
[countLock unlock];
|
||||
|
||||
[string release];
|
||||
}];
|
||||
|
||||
[task resume];
|
||||
}
|
||||
|
||||
static URLManager *
|
||||
testParallelDataTransfer(NSURL *baseURL)
|
||||
{
|
||||
NSURLSession *session;
|
||||
NSURLSessionConfiguration *configuration;
|
||||
URLManager *mgr;
|
||||
NSURL *url;
|
||||
const char *prefix = "<DataTransfer>";
|
||||
|
||||
NSInteger numberOfParallelTasks = 10;
|
||||
|
||||
/* URL Delegate Setup */
|
||||
mgr = [URLManager new];
|
||||
mgr->numberOfExpectedTasksBeforeCheck = numberOfParallelTasks;
|
||||
expectedCountOfTasksToComplete += numberOfParallelTasks;
|
||||
|
||||
url = [baseURL URLByAppendingPathComponent:@"tmpRedirectToOK"];
|
||||
configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
|
||||
[configuration setHTTPMaximumConnectionsPerHost:0]; // Unlimited
|
||||
session = [NSURLSession sessionWithConfiguration:configuration
|
||||
delegate:mgr
|
||||
delegateQueue:nil];
|
||||
|
||||
/* Setup Check Block */
|
||||
[mgr setCheckBlock:^(URLManager *mgr) {
|
||||
PASS_EQUAL(mgr->currentSession, session,
|
||||
"%s URLManager Session is equal to session", prefix);
|
||||
|
||||
/* Check URLSession:didCreateTask: callback */
|
||||
PASS(mgr->didCreateTaskCount == numberOfParallelTasks,
|
||||
"%s didCreateTask: Count is correct", prefix);
|
||||
|
||||
/* Check URLSession:task:didCompleteWithError: */
|
||||
PASS(nil == mgr->didCompleteError,
|
||||
"%s didCompleteWithError: No error occurred", prefix)
|
||||
PASS(mgr->didCompleteCount == numberOfParallelTasks,
|
||||
"%s didCompleteWithError: Count is correct", prefix);
|
||||
|
||||
[countLock lock];
|
||||
currentCountOfCompletedTasks += numberOfParallelTasks;
|
||||
[countLock unlock];
|
||||
}];
|
||||
|
||||
for (NSInteger i = 0; i < numberOfParallelTasks; i++)
|
||||
{
|
||||
NSURLSessionDataTask *task;
|
||||
|
||||
task = [session dataTaskWithURL:url];
|
||||
[task resume];
|
||||
}
|
||||
|
||||
return mgr;
|
||||
}
|
||||
|
||||
static void
|
||||
testDataTaskWithBlock(NSURL *baseURL)
|
||||
{
|
||||
NSURLSession *session;
|
||||
NSURLSessionDataTask *task;
|
||||
NSURL *url;
|
||||
|
||||
expectedCountOfTasksToComplete += 1;
|
||||
|
||||
url = [baseURL URLByAppendingPathComponent:@"contentOK"];
|
||||
session = [NSURLSession sharedSession];
|
||||
task = [session
|
||||
dataTaskWithURL:url
|
||||
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
|
||||
NSString *string;
|
||||
const char *prefix = "<DataTaskWithBlock>";
|
||||
|
||||
PASS(nil != data, "%s data in completion handler is not nil", prefix);
|
||||
PASS(nil != response, "%s response is not nil", prefix);
|
||||
PASS([response isKindOfClass:[NSHTTPURLResponse class]],
|
||||
"%s response is an NSHTTPURLResponse", prefix);
|
||||
PASS(nil == error, "%s error is nil", prefix);
|
||||
|
||||
string = [[NSString alloc] initWithData:data
|
||||
encoding:NSASCIIStringEncoding];
|
||||
PASS_EQUAL(string, @"Hello World!", "%s received data is correct",
|
||||
prefix);
|
||||
|
||||
[string release];
|
||||
|
||||
[countLock lock];
|
||||
currentCountOfCompletedTasks += 1;
|
||||
[countLock unlock];
|
||||
}];
|
||||
|
||||
[task resume];
|
||||
}
|
||||
|
||||
static void
|
||||
testDataTaskWithCookies(NSURL *baseURL)
|
||||
{
|
||||
NSURLSession *session;
|
||||
NSURLSessionConfiguration *config;
|
||||
NSURLSessionDataTask *task;
|
||||
NSURL *url;
|
||||
NSHTTPCookieStorage *cookies;
|
||||
NSHTTPCookie *requestCookie;
|
||||
|
||||
expectedCountOfTasksToComplete += 1;
|
||||
|
||||
url = [baseURL URLByAppendingPathComponent:@"setCookiesOK"];
|
||||
requestCookie = [NSHTTPCookie cookieWithProperties:requestCookieProperties];
|
||||
|
||||
cookies = [NSHTTPCookieStorage new];
|
||||
[cookies setCookie:requestCookie];
|
||||
|
||||
config = [NSURLSessionConfiguration new];
|
||||
[config setHTTPCookieAcceptPolicy:NSHTTPCookieAcceptPolicyAlways];
|
||||
[config setHTTPCookieStorage:cookies];
|
||||
[config setHTTPShouldSetCookies:YES];
|
||||
|
||||
session = [NSURLSession sessionWithConfiguration:config];
|
||||
task = [session
|
||||
dataTaskWithURL:url
|
||||
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
|
||||
NSString *string;
|
||||
NSArray<NSHTTPCookie *> *cookieArray;
|
||||
NSDate *date;
|
||||
const char *prefix = "<DataTaskWithCookies>";
|
||||
|
||||
PASS(nil != data, "%s data in completion handler is not nil", prefix);
|
||||
PASS(nil != response, "%s response is not nil", prefix);
|
||||
PASS([response isKindOfClass:[NSHTTPURLResponse class]],
|
||||
"%s response is an NSHTTPURLResponse", prefix);
|
||||
PASS(nil == error, "%s error is nil", prefix);
|
||||
|
||||
string = [[NSString alloc] initWithData:data
|
||||
encoding:NSASCIIStringEncoding];
|
||||
PASS_EQUAL(string, @"Hello, world!", "%s received data is correct",
|
||||
prefix);
|
||||
|
||||
cookieArray = [cookies cookiesForURL:url];
|
||||
|
||||
NSInteger count = 0;
|
||||
for (NSHTTPCookie *ck in cookieArray)
|
||||
{
|
||||
if ([[ck name] isEqualToString:@"RequestCookie"])
|
||||
{
|
||||
PASS_EQUAL(ck, requestCookie, "RequestCookie is correct");
|
||||
count += 1;
|
||||
}
|
||||
else if ([[ck name] isEqualToString:@"sessionId"])
|
||||
{
|
||||
date = [NSDate dateWithString:@"2100-06-09 10:18:14 +0000"];
|
||||
PASS_EQUAL([ck name], @"sessionId", "Cookie name is correct");
|
||||
PASS_EQUAL([ck value], @"abc123", "Cookie value is correct");
|
||||
PASS([ck version] == 0, "Correct cookie version");
|
||||
PASS([date isEqual:[ck expiresDate]],
|
||||
"Cookie expiresDate is correct");
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
PASS(count == 2, "Found both cookies");
|
||||
|
||||
[string release];
|
||||
|
||||
[countLock lock];
|
||||
currentCountOfCompletedTasks += 1;
|
||||
[countLock unlock];
|
||||
}];
|
||||
|
||||
[task resume];
|
||||
}
|
||||
|
||||
/* Check if NSURLSessionTask correctly unfolds folded header lines */
|
||||
static void
|
||||
foldedHeaderDataTaskTest(NSURL *baseURL)
|
||||
{
|
||||
NSURLSession *session;
|
||||
NSURLSessionDataTask *task;
|
||||
NSURL *url;
|
||||
|
||||
expectedCountOfTasksToComplete += 1;
|
||||
|
||||
url = [baseURL URLByAppendingPathComponent:@"foldedHeaders"];
|
||||
session = [NSURLSession sharedSession];
|
||||
task = [session
|
||||
dataTaskWithURL:url
|
||||
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
|
||||
NSHTTPURLResponse *urlResponse = (NSHTTPURLResponse *) response;
|
||||
NSString *string;
|
||||
NSDictionary *headerDict;
|
||||
const char *prefix = "<DataTaskWithFoldedHeaders>";
|
||||
|
||||
headerDict = [urlResponse allHeaderFields];
|
||||
|
||||
PASS(nil != data, "%s data in completion handler is not nil", prefix);
|
||||
PASS(nil != response, "%s response is not nil", prefix);
|
||||
PASS([response isKindOfClass:[NSHTTPURLResponse class]],
|
||||
"%s response is an NSHTTPURLResponse", prefix);
|
||||
PASS(nil == error, "%s error is nil", prefix);
|
||||
|
||||
string = [[NSString alloc] initWithData:data
|
||||
encoding:NSASCIIStringEncoding];
|
||||
PASS_EQUAL(string, @"Hello World!", "%s received data is correct",
|
||||
prefix);
|
||||
|
||||
PASS_EQUAL([headerDict objectForKey:@"Folded-Header-SP"], @"Testing",
|
||||
"Folded header with continuation space is parsed correctly");
|
||||
PASS_EQUAL([headerDict objectForKey:@"Folded-Header-TAB"], @"Testing",
|
||||
"Folded header with continuation tab is parsed correctly");
|
||||
|
||||
[string release];
|
||||
|
||||
[countLock lock];
|
||||
currentCountOfCompletedTasks += 1;
|
||||
[countLock unlock];
|
||||
}];
|
||||
|
||||
[task resume];
|
||||
}
|
||||
|
||||
/* The disposition handler triggers transfer cancelation */
|
||||
static void
|
||||
testAbortAfterDidReceiveResponse(NSURL *baseURL)
|
||||
{
|
||||
NSURLSession *session;
|
||||
NSURLSessionConfiguration *configuration;
|
||||
NSURLSessionDataTask *task;
|
||||
URLManager *mgr;
|
||||
URLManagerCheckBlock block;
|
||||
const char *prefix = "<AbortAfterDidReceiveResponseTest>";
|
||||
|
||||
NSURL *contentOKURL;
|
||||
|
||||
/* URL Delegate Setup */
|
||||
mgr = [URLManager new];
|
||||
mgr->numberOfExpectedTasksBeforeCheck = 1;
|
||||
mgr->responseAnswer = NSURLSessionResponseCancel;
|
||||
expectedCountOfTasksToComplete += 1;
|
||||
|
||||
/* URL Setup */
|
||||
contentOKURL = [baseURL URLByAppendingPathComponent:@"contentOK"];
|
||||
|
||||
configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
|
||||
session = [NSURLSession sessionWithConfiguration:configuration
|
||||
delegate:mgr
|
||||
delegateQueue:nil];
|
||||
|
||||
task = [session dataTaskWithURL:contentOKURL];
|
||||
PASS(nil != task, "%s Session created a valid download task", prefix);
|
||||
|
||||
/* Setup Check Block */
|
||||
block = dataCheckBlockFailedRequest(prefix, session, task);
|
||||
[mgr setCheckBlock:block];
|
||||
_Block_release(block);
|
||||
|
||||
[task resume];
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, char *argv[])
|
||||
{
|
||||
@autoreleasepool
|
||||
{
|
||||
NSBundle *bundle;
|
||||
NSString *helperPath;
|
||||
NSURL *baseURL;
|
||||
NSFileManager *fm;
|
||||
HTTPServer *server;
|
||||
|
||||
Class httpServerClass;
|
||||
Class routeClass;
|
||||
|
||||
requestCookieProperties = @{
|
||||
NSHTTPCookieName : @"RequestCookie",
|
||||
NSHTTPCookieValue : @"1234",
|
||||
NSHTTPCookieDomain : @"127.0.0.1",
|
||||
NSHTTPCookiePath : @"/",
|
||||
NSHTTPCookieExpires :
|
||||
[NSDate dateWithString:@"2100-06-09 12:18:14 +0000"],
|
||||
NSHTTPCookieSecure : @NO,
|
||||
};
|
||||
|
||||
fm = [NSFileManager defaultManager];
|
||||
helperPath = [[fm currentDirectoryPath]
|
||||
stringByAppendingString:@"/Helpers/HTTPServer.bundle"];
|
||||
countLock = [[NSLock alloc] init];
|
||||
|
||||
bundle = [NSBundle bundleWithPath:helperPath];
|
||||
if (![bundle load])
|
||||
{
|
||||
[NSException raise:NSInternalInconsistencyException
|
||||
format:@"failed to load HTTPServer.bundle"];
|
||||
}
|
||||
|
||||
httpServerClass = [bundle principalClass];
|
||||
routeClass = [bundle classNamed:@"Route"];
|
||||
|
||||
/* Bind to dynamic port. Set routes after initialisation. */
|
||||
server = [[httpServerClass alloc] initWithPort:0 routes:nil];
|
||||
if (!server)
|
||||
{
|
||||
[NSException raise:NSInternalInconsistencyException
|
||||
format:@"failed to initialise HTTPServer"];
|
||||
}
|
||||
|
||||
baseURL =
|
||||
[NSURL URLWithString:[NSString stringWithFormat:@"http://127.0.0.1:%ld",
|
||||
[server port]]];
|
||||
|
||||
NSLog(@"Test Server: baseURL=%@", baseURL);
|
||||
|
||||
[server setRoutes:createRoutes(routeClass, baseURL)];
|
||||
[server resume];
|
||||
|
||||
// Call Test Functions here
|
||||
testSimpleDownloadTransfer(baseURL);
|
||||
testDownloadTransferWithBlock(baseURL);
|
||||
|
||||
testParallelDataTransfer(baseURL);
|
||||
testDataTaskWithBlock(baseURL);
|
||||
testDataTaskWithCookies(baseURL);
|
||||
|
||||
// Testing Header Line Unfolding
|
||||
foldedHeaderDataTaskTest(baseURL);
|
||||
|
||||
// Redirects
|
||||
testDownloadTransferWithRedirect(baseURL);
|
||||
testDataTransferWithRedirectAndBlock(baseURL);
|
||||
testDataTransferWithCanceledRedirect(baseURL);
|
||||
testDataTransferWithRelativeRedirect(baseURL);
|
||||
|
||||
/* Abort in Delegate */
|
||||
testAbortAfterDidReceiveResponse(baseURL);
|
||||
|
||||
[[NSRunLoop currentRunLoop]
|
||||
runForSeconds:testTimeOut
|
||||
conditionBlock:^BOOL(void) {
|
||||
return expectedCountOfTasksToComplete != currentCountOfCompletedTasks;
|
||||
}];
|
||||
|
||||
[server suspend];
|
||||
PASS(expectedCountOfTasksToComplete == currentCountOfCompletedTasks,
|
||||
"All transfers were completed before a timeout occurred");
|
||||
|
||||
[server release];
|
||||
[countLock release];
|
||||
}
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
int
|
||||
main(int argc, char *argv[])
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif /* GS_HAVE_NSURLSESSION */
|
141
Tests/base/NSURLSession/test01.m
Normal file
141
Tests/base/NSURLSession/test01.m
Normal file
|
@ -0,0 +1,141 @@
|
|||
#import <Foundation/Foundation.h>
|
||||
#import "Testing.h"
|
||||
#import "ObjectTesting.h"
|
||||
|
||||
#if GS_HAVE_NSURLSESSION
|
||||
@interface MyDelegate : NSObject <NSURLSessionDelegate>
|
||||
{
|
||||
@public
|
||||
BOOL responseCompletion;
|
||||
BOOL didComplete;
|
||||
NSString *taskText;
|
||||
NSError *taskError;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation MyDelegate
|
||||
- (void) dealloc
|
||||
{
|
||||
RELEASE(taskText);
|
||||
RELEASE(taskError);
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (void) URLSession: (NSURLSession*)session
|
||||
dataTask: (NSURLSessionDataTask*)dataTask
|
||||
didReceiveResponse: (NSURLResponse*)response
|
||||
completionHandler: (void (^)(NSURLSessionResponseDisposition disposition))completionHandler
|
||||
{
|
||||
responseCompletion = YES;
|
||||
if (NO == didComplete)
|
||||
{
|
||||
NSLog(@"### handler 1 before didComplete...");
|
||||
}
|
||||
else
|
||||
{
|
||||
NSLog(@"### handler 1 after didComplete...");
|
||||
}
|
||||
completionHandler(NSURLSessionResponseAllow);
|
||||
}
|
||||
|
||||
- (void) URLSession: (NSURLSession*)session
|
||||
dataTask: (NSURLSessionDataTask*)dataTask
|
||||
didReceiveData: (NSData*)data
|
||||
{
|
||||
NSString *text;
|
||||
|
||||
text = [[NSString alloc] initWithData: data encoding: NSUTF8StringEncoding];
|
||||
if (nil == text)
|
||||
{
|
||||
NSLog(@"Received non-utf8 %@", data);
|
||||
}
|
||||
else
|
||||
{
|
||||
ASSIGN(taskText, text);
|
||||
NSLog(@"Received String %@", text);
|
||||
}
|
||||
RELEASE(text);
|
||||
}
|
||||
- (void) URLSession: (NSURLSession*)session
|
||||
task: (NSURLSessionTask*)task
|
||||
didCompleteWithError: (NSError*)error
|
||||
{
|
||||
if (error == nil)
|
||||
{
|
||||
NSLog(@"Download is Succesfull");
|
||||
}
|
||||
else
|
||||
{
|
||||
NSLog(@"Error %@", [error userInfo]);
|
||||
}
|
||||
didComplete = YES;
|
||||
ASSIGN(taskError, error);
|
||||
}
|
||||
@end
|
||||
#endif
|
||||
|
||||
int main()
|
||||
{
|
||||
START_SET("NSURLSession test01")
|
||||
|
||||
#if !GS_HAVE_NSURLSESSION
|
||||
SKIP("library built without NSURLSession support")
|
||||
#else
|
||||
NSURLSessionConfiguration *defaultConfigObject;
|
||||
NSURLSession *defaultSession;
|
||||
NSURLSessionDataTask *dataTask;
|
||||
NSMutableURLRequest *urlRequest;
|
||||
NSURL *url;
|
||||
NSOperationQueue *mainQueue;
|
||||
NSString *params;
|
||||
MyDelegate *object;
|
||||
|
||||
#if defined(_WIN32)
|
||||
NSLog(@"Marking nonexistant host test as hopeful on Windows as it seems to be broken");
|
||||
testHopeful = YES;
|
||||
#endif
|
||||
|
||||
object = AUTORELEASE([MyDelegate new]);
|
||||
mainQueue = [NSOperationQueue mainQueue];
|
||||
defaultConfigObject = [NSURLSessionConfiguration defaultSessionConfiguration];
|
||||
defaultSession = [NSURLSession sessionWithConfiguration: defaultConfigObject
|
||||
delegate: object
|
||||
delegateQueue: mainQueue];
|
||||
url = [NSURL URLWithString:
|
||||
@"http://localhost:12345/not-here"];
|
||||
urlRequest = [NSMutableURLRequest requestWithURL: url];
|
||||
[urlRequest setHTTPMethod: @"POST"];
|
||||
params = @"name=Ravi&loc=India&age=31&submit=true";
|
||||
[urlRequest setHTTPBody: [params dataUsingEncoding: NSUTF8StringEncoding]];
|
||||
if ([urlRequest respondsToSelector: @selector(setDebug:)])
|
||||
{
|
||||
[urlRequest setDebug: YES];
|
||||
}
|
||||
|
||||
dataTask = [defaultSession dataTaskWithRequest: urlRequest];
|
||||
[dataTask resume];
|
||||
|
||||
NSDate *limit = [NSDate dateWithTimeIntervalSinceNow: 60.0];
|
||||
while (object->didComplete == NO
|
||||
&& [limit timeIntervalSinceNow] > 0.0)
|
||||
{
|
||||
ENTER_POOL
|
||||
NSDate *when = [NSDate dateWithTimeIntervalSinceNow: 0.1];
|
||||
|
||||
[[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode
|
||||
beforeDate: when];
|
||||
LEAVE_POOL
|
||||
}
|
||||
|
||||
PASS(YES == object->didComplete, "request completed")
|
||||
PASS([object->taskError code] == NSURLErrorCannotConnectToHost,
|
||||
"unable to connect to host")
|
||||
|
||||
#if defined(_WIN32)
|
||||
testHopeful = NO;
|
||||
#endif
|
||||
|
||||
#endif
|
||||
END_SET("NSURLSession test01")
|
||||
return 0;
|
||||
}
|
100
Tests/base/NSURLSession/test02.m
Normal file
100
Tests/base/NSURLSession/test02.m
Normal file
|
@ -0,0 +1,100 @@
|
|||
#import <Foundation/Foundation.h>
|
||||
#import "Testing.h"
|
||||
#import "ObjectTesting.h"
|
||||
#import "../NSURLConnection/Helpers/TestWebServer.h"
|
||||
|
||||
#if GS_HAVE_NSURLSESSION
|
||||
#import "delegate.g"
|
||||
#endif
|
||||
|
||||
int main()
|
||||
{
|
||||
START_SET("NSURLSession http")
|
||||
|
||||
#if !GS_HAVE_NSURLSESSION
|
||||
SKIP("library built without NSURLSession support")
|
||||
#else
|
||||
NSFileManager *fm;
|
||||
NSBundle *bundle;
|
||||
NSString *helperPath;
|
||||
|
||||
// load the test suite's classes
|
||||
fm = [NSFileManager defaultManager];
|
||||
helperPath = [[fm currentDirectoryPath] stringByAppendingPathComponent:
|
||||
@"../NSURLConnection/Helpers/TestConnection.bundle"];
|
||||
bundle = [NSBundle bundleWithPath: helperPath];
|
||||
NSCAssert([bundle load], NSInternalInconsistencyException);
|
||||
|
||||
TestWebServer *server;
|
||||
Class c;
|
||||
BOOL debug = YES;
|
||||
|
||||
// create a shared TestWebServer instance for performance
|
||||
c = [bundle principalClass];
|
||||
server = [[c testWebServerClass] new];
|
||||
NSCAssert(server != nil, NSInternalInconsistencyException);
|
||||
[server setDebug: debug];
|
||||
[server start: nil]; // localhost:1234 HTTP
|
||||
|
||||
|
||||
NSURLSessionConfiguration *configuration;
|
||||
NSURLSession *defaultSession;
|
||||
NSURLSessionDataTask *dataTask;
|
||||
NSMutableURLRequest *urlRequest;
|
||||
NSURL *url;
|
||||
NSOperationQueue *mainQueue;
|
||||
NSString *params;
|
||||
MyDelegate *object;
|
||||
|
||||
configuration = [[NSURLSessionConfiguration alloc] init];
|
||||
[configuration setHTTPShouldUsePipelining: YES];
|
||||
|
||||
testHopeful=YES;
|
||||
PASS_RUNS([configuration setHTTPMaximumConnectionLifetime: 42];,
|
||||
"-setHTTPMaximumConnectionLifetime: support available in CURL")
|
||||
testHopeful=NO;
|
||||
|
||||
[configuration setHTTPMaximumConnectionsPerHost: 1];
|
||||
[configuration setRequestCachePolicy: NSURLCacheStorageNotAllowed];
|
||||
|
||||
object = AUTORELEASE([MyDelegate new]);
|
||||
mainQueue = [NSOperationQueue mainQueue];
|
||||
defaultSession = [NSURLSession sessionWithConfiguration: configuration
|
||||
delegate: object
|
||||
delegateQueue: mainQueue];
|
||||
RELEASE(configuration);
|
||||
url = [NSURL URLWithString: @"http://localhost:1234/xxx"];
|
||||
params = @"dummy=true";
|
||||
urlRequest = [NSMutableURLRequest requestWithURL: url];
|
||||
[urlRequest setHTTPMethod: @"POST"];
|
||||
[urlRequest setHTTPBody: [params dataUsingEncoding: NSUTF8StringEncoding]];
|
||||
if ([urlRequest respondsToSelector: @selector(setDebug:)])
|
||||
{
|
||||
[urlRequest setDebug: YES];
|
||||
}
|
||||
|
||||
dataTask = [defaultSession dataTaskWithRequest: urlRequest];
|
||||
[dataTask resume];
|
||||
|
||||
NSDate *limit = [NSDate dateWithTimeIntervalSinceNow: 60.0];
|
||||
while ([object finished] == NO
|
||||
&& [limit timeIntervalSinceNow] > 0.0)
|
||||
{
|
||||
ENTER_POOL
|
||||
NSDate *when = [NSDate dateWithTimeIntervalSinceNow: 0.1];
|
||||
|
||||
[[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode
|
||||
beforeDate: when];
|
||||
LEAVE_POOL
|
||||
}
|
||||
|
||||
PASS(YES == [object finished], "request completed")
|
||||
PASS_EQUAL([object taskError], nil, "request did not error")
|
||||
|
||||
NSString *expect = @"Please give login and password";
|
||||
PASS_EQUAL([object taskText], expect, "request returned text")
|
||||
|
||||
#endif
|
||||
END_SET("NSURLSession http")
|
||||
return 0;
|
||||
}
|
94
Tests/base/NSURLSession/test03.m
Normal file
94
Tests/base/NSURLSession/test03.m
Normal file
|
@ -0,0 +1,94 @@
|
|||
#import <Foundation/Foundation.h>
|
||||
#import "Testing.h"
|
||||
#import "ObjectTesting.h"
|
||||
#import "../NSURLConnection/Helpers/TestWebServer.h"
|
||||
|
||||
#if GS_HAVE_NSURLSESSION
|
||||
#import "delegate.g"
|
||||
#endif
|
||||
|
||||
int main()
|
||||
{
|
||||
START_SET("NSURLSession test03")
|
||||
|
||||
#if !GS_HAVE_NSURLSESSION
|
||||
SKIP("library built without NSURLSession support")
|
||||
#else
|
||||
NSFileManager *fm;
|
||||
NSBundle *bundle;
|
||||
NSString *helperPath;
|
||||
|
||||
// load the test suite's classes
|
||||
fm = [NSFileManager defaultManager];
|
||||
helperPath = [[fm currentDirectoryPath] stringByAppendingPathComponent:
|
||||
@"../NSURLConnection/Helpers/TestConnection.bundle"];
|
||||
bundle = [NSBundle bundleWithPath: helperPath];
|
||||
NSCAssert([bundle load], NSInternalInconsistencyException);
|
||||
|
||||
TestWebServer *server;
|
||||
Class c;
|
||||
BOOL debug = YES;
|
||||
|
||||
// create a shared TestWebServer instance for performance
|
||||
c = [bundle principalClass];
|
||||
server = [[c testWebServerClass] new];
|
||||
NSCAssert(server != nil, NSInternalInconsistencyException);
|
||||
[server setDebug: debug];
|
||||
[server start: nil]; // localhost:1234 HTTP
|
||||
|
||||
|
||||
NSURLSessionConfiguration *configuration;
|
||||
NSURLSession *defaultSession;
|
||||
NSURLSessionDownloadTask *downloadTask;
|
||||
NSMutableURLRequest *urlRequest;
|
||||
NSURL *url;
|
||||
NSOperationQueue *mainQueue;
|
||||
MyDelegate *object;
|
||||
NSString *content;
|
||||
|
||||
configuration = [[NSURLSessionConfiguration alloc] init];
|
||||
|
||||
object = AUTORELEASE([MyDelegate new]);
|
||||
mainQueue = [NSOperationQueue mainQueue];
|
||||
defaultSession = [NSURLSession sessionWithConfiguration: configuration
|
||||
delegate: object
|
||||
delegateQueue: mainQueue];
|
||||
RELEASE(configuration);
|
||||
url = [NSURL URLWithString: @"http://localhost:1234/index"];
|
||||
urlRequest = [NSMutableURLRequest requestWithURL: url];
|
||||
if ([urlRequest respondsToSelector: @selector(setDebug:)])
|
||||
{
|
||||
[urlRequest setDebug: YES];
|
||||
}
|
||||
|
||||
downloadTask = [defaultSession downloadTaskWithRequest: urlRequest];
|
||||
[downloadTask resume];
|
||||
|
||||
NSDate *limit = [NSDate dateWithTimeIntervalSinceNow: 60.0];
|
||||
while ([object finished] == NO
|
||||
&& [limit timeIntervalSinceNow] > 0.0)
|
||||
{
|
||||
ENTER_POOL
|
||||
NSDate *when = [NSDate dateWithTimeIntervalSinceNow: 0.1];
|
||||
|
||||
[[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode
|
||||
beforeDate: when];
|
||||
LEAVE_POOL
|
||||
}
|
||||
|
||||
PASS(YES == [object finished], "request completed")
|
||||
PASS_EQUAL([object taskError], nil, "request did not error")
|
||||
|
||||
/* Get content from file */
|
||||
content = [NSString stringWithContentsOfFile: [[object taskLocation] path]
|
||||
encoding: NSUTF8StringEncoding
|
||||
error:nil];
|
||||
|
||||
|
||||
NSString *expect = @"Please give login and password";
|
||||
PASS_EQUAL(content, expect, "request returned text")
|
||||
|
||||
#endif
|
||||
END_SET("NSURLSession test03")
|
||||
return 0;
|
||||
}
|
|
@ -1,204 +0,0 @@
|
|||
#include "Foundation/NSDate.h"
|
||||
#import <Foundation/Foundation.h>
|
||||
#include <Foundation/NSProgress.h>
|
||||
#include <Foundation/NSString.h>
|
||||
|
||||
#if GS_HAVE_NSURLSESSION
|
||||
|
||||
#import "Helpers/HTTPServer.h"
|
||||
#import "NSRunLoop+TimeOutAdditions.h"
|
||||
#import "URLManager.h"
|
||||
#import "Testing.h"
|
||||
|
||||
typedef void (^dataCompletionHandler)(NSData *data, NSURLResponse *response,
|
||||
NSError *error);
|
||||
|
||||
/* Timeout in Seconds */
|
||||
static NSInteger testTimeOut = 60;
|
||||
static NSTimeInterval expectedCountOfTasksToComplete = 0;
|
||||
|
||||
/* Accessed in delegate on different thread.
|
||||
*/
|
||||
static _Atomic(NSInteger) currentCountOfCompletedTasks = 0;
|
||||
static NSLock *countLock;
|
||||
|
||||
/* Expected Content */
|
||||
static NSString *largeBodyPath;
|
||||
static NSData *largeBodyContent;
|
||||
|
||||
static NSArray<Route *> *
|
||||
createRoutes(Class routeClass)
|
||||
{
|
||||
Route *routeOKWithContent;
|
||||
Route *routeLargeUpload;
|
||||
NSURL *routeOKWithContentURL;
|
||||
NSURL *routeLargeUploadURL;
|
||||
|
||||
routeOKWithContentURL = [NSURL URLWithString:@"/smallUploadOK"];
|
||||
routeLargeUploadURL = [NSURL URLWithString:@"/largeUploadOK"];
|
||||
|
||||
routeOKWithContent = [routeClass
|
||||
routeWithURL:routeOKWithContentURL
|
||||
method:@"POST"
|
||||
handler:^NSData *(NSURLRequest *req) {
|
||||
NSData *response;
|
||||
|
||||
response =
|
||||
[@"HTTP/1.1 200 OK\r\nContent-Length: 0\r\nHeader-Key: "
|
||||
@"Header-Value\r\n\r\n" dataUsingEncoding:NSASCIIStringEncoding];
|
||||
return response;
|
||||
}];
|
||||
|
||||
routeLargeUpload = [routeClass
|
||||
routeWithURL:routeLargeUploadURL
|
||||
method:@"POST"
|
||||
handler:^NSData *(NSURLRequest *req) {
|
||||
NSData *response;
|
||||
|
||||
PASS_EQUAL([req valueForHTTPHeaderField:@"Request-Key"],
|
||||
@"Request-Value",
|
||||
"Request contains user-specific header line");
|
||||
PASS_EQUAL([req valueForHTTPHeaderField:@"Content-Type"],
|
||||
@"text/plain",
|
||||
"Request contains the correct Content-Type");
|
||||
PASS_EQUAL([req HTTPBody], largeBodyContent, "HTTPBody is correct");
|
||||
|
||||
response =
|
||||
[@"HTTP/1.1 200 OK\r\nContent-Length: 0\r\nHeader-Key: "
|
||||
@"Header-Value\r\n\r\n" dataUsingEncoding:NSASCIIStringEncoding];
|
||||
|
||||
return response;
|
||||
}];
|
||||
|
||||
return @[ routeOKWithContent, routeLargeUpload ];
|
||||
}
|
||||
|
||||
static void
|
||||
testLargeUploadWithBlock(NSURL *baseURL)
|
||||
{
|
||||
NSURLSession *session;
|
||||
NSURLSessionDataTask *dataTask;
|
||||
NSURLSessionUploadTask *uploadTask;
|
||||
NSMutableURLRequest *request;
|
||||
NSURL *url;
|
||||
dataCompletionHandler handler;
|
||||
|
||||
expectedCountOfTasksToComplete += 2;
|
||||
|
||||
url = [baseURL URLByAppendingPathComponent:@"largeUploadOK"];
|
||||
session = [NSURLSession sharedSession];
|
||||
request = [NSMutableURLRequest requestWithURL:url];
|
||||
|
||||
[request setHTTPBody:largeBodyContent];
|
||||
[request setHTTPMethod:@"POST"];
|
||||
[request setValue:@"Request-Value" forHTTPHeaderField:@"Request-Key"];
|
||||
[request setValue:@"text/plain" forHTTPHeaderField:@"Content-Type"];
|
||||
|
||||
/* The completion handler for the two requests */
|
||||
handler = ^(NSData *data, NSURLResponse *response, NSError *error) {
|
||||
NSHTTPURLResponse *httpResponse = response;
|
||||
|
||||
PASS([data length] == 0, "Received empty data object");
|
||||
PASS(nil != response, "Response is not nil");
|
||||
PASS([response isKindOfClass:[NSHTTPURLResponse class]],
|
||||
"Response is a NSHTTPURLResponse");
|
||||
PASS(nil == error, "Error is nil");
|
||||
|
||||
PASS_EQUAL([[httpResponse allHeaderFields] objectForKey:@"Header-Key"],
|
||||
@"Header-Value", "Response contains custom header line");
|
||||
|
||||
[countLock lock];
|
||||
currentCountOfCompletedTasks += 1;
|
||||
[countLock unlock];
|
||||
};
|
||||
|
||||
dataTask = [session dataTaskWithRequest:request completionHandler:handler];
|
||||
uploadTask = [session uploadTaskWithRequest:request
|
||||
fromData:largeBodyContent
|
||||
completionHandler:handler];
|
||||
|
||||
[dataTask resume];
|
||||
[uploadTask resume];
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, char *argv[])
|
||||
{
|
||||
@autoreleasepool
|
||||
{
|
||||
NSBundle *bundle;
|
||||
NSString *helperPath;
|
||||
NSString *currentDirectory;
|
||||
NSURL *baseURL;
|
||||
NSFileManager *fm;
|
||||
NSArray<Route *> *routes;
|
||||
HTTPServer *server;
|
||||
|
||||
Class httpServerClass;
|
||||
Class routeClass;
|
||||
|
||||
fm = [NSFileManager defaultManager];
|
||||
currentDirectory = [fm currentDirectoryPath];
|
||||
helperPath =
|
||||
[currentDirectory stringByAppendingString:@"/Helpers/HTTPServer.bundle"];
|
||||
countLock = [[NSLock alloc] init];
|
||||
|
||||
bundle = [NSBundle bundleWithPath:helperPath];
|
||||
if (![bundle load])
|
||||
{
|
||||
[NSException raise:NSInternalInconsistencyException
|
||||
format:@"failed to load HTTPServer.bundle"];
|
||||
}
|
||||
|
||||
/* Load Test Data */
|
||||
largeBodyPath =
|
||||
[currentDirectory stringByAppendingString:@"/Resources/largeBody.txt"];
|
||||
largeBodyContent = [NSData dataWithContentsOfFile:largeBodyPath];
|
||||
PASS(nil != largeBodyContent, "can load %s", [largeBodyPath UTF8String]);
|
||||
|
||||
httpServerClass = [bundle principalClass];
|
||||
routeClass = [bundle classNamed:@"Route"];
|
||||
routes = createRoutes(routeClass);
|
||||
server = [[httpServerClass alloc] initWithPort:0 routes:routes];
|
||||
if (!server)
|
||||
{
|
||||
[NSException raise:NSInternalInconsistencyException
|
||||
format:@"failed to create HTTPServer"];
|
||||
}
|
||||
|
||||
baseURL =
|
||||
[NSURL URLWithString:[NSString stringWithFormat:@"http://127.0.0.1:%ld",
|
||||
[server port]]];
|
||||
|
||||
NSLog(@"Server started with baseURL: %@", baseURL);
|
||||
|
||||
[server resume];
|
||||
|
||||
/* Call Test Functions here! */
|
||||
testLargeUploadWithBlock(baseURL);
|
||||
|
||||
[[NSRunLoop currentRunLoop]
|
||||
runForSeconds:testTimeOut
|
||||
conditionBlock:^BOOL(void) {
|
||||
return expectedCountOfTasksToComplete != currentCountOfCompletedTasks;
|
||||
}];
|
||||
|
||||
[server suspend];
|
||||
PASS(expectedCountOfTasksToComplete == currentCountOfCompletedTasks,
|
||||
"All transfers were completed before a timeout occurred");
|
||||
|
||||
[server release];
|
||||
[countLock release];
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
int
|
||||
main(int argc, char *argv[])
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
Loading…
Reference in a new issue