/IOSBoilerplate/ASIHTTPRequest.m
Objective C | 1416 lines | 1038 code | 247 blank | 131 comment | 203 complexity | f4539c265198479a5041191549256d98 MD5 | raw file
- //
- // ASIHTTPRequest.m
- //
- // Created by Ben Copsey on 04/10/2007.
- // Copyright 2007-2011 All-Seeing Interactive. All rights reserved.
- //
- // A guide to the main features is available at:
- // http://allseeing-i.com/ASIHTTPRequest
- //
- // Portions are based on the ImageClient example from Apple:
- // See: http://developer.apple.com/samplecode/ImageClient/listing37.html
- #import "ASIHTTPRequest.h"
- #if TARGET_OS_IPHONE
- #import "Reachability.h"
- #import "ASIAuthenticationDialog.h"
- #import <MobileCoreServices/MobileCoreServices.h>
- #else
- #import <SystemConfiguration/SystemConfiguration.h>
- #endif
- #import "ASIInputStream.h"
- #import "ASIDataDecompressor.h"
- #import "ASIDataCompressor.h"
- // Automatically set on build
- NSString *ASIHTTPRequestVersion = @"v1.8.1-33 2011-08-20";
- static NSString *defaultUserAgent = nil;
- NSString* const NetworkRequestErrorDomain = @"ASIHTTPRequestErrorDomain";
- static NSString *ASIHTTPRequestRunLoopMode = @"ASIHTTPRequestRunLoopMode";
- static const CFOptionFlags kNetworkEvents = kCFStreamEventHasBytesAvailable | kCFStreamEventEndEncountered | kCFStreamEventErrorOccurred;
- // In memory caches of credentials, used on when useSessionPersistence is YES
- static NSMutableArray *sessionCredentialsStore = nil;
- static NSMutableArray *sessionProxyCredentialsStore = nil;
- // This lock mediates access to session credentials
- static NSRecursiveLock *sessionCredentialsLock = nil;
- // We keep track of cookies we have received here so we can remove them from the sharedHTTPCookieStorage later
- static NSMutableArray *sessionCookies = nil;
- // The number of times we will allow requests to redirect before we fail with a redirection error
- const int RedirectionLimit = 5;
- // The default number of seconds to use for a timeout
- static NSTimeInterval defaultTimeOutSeconds = 10;
- static void ReadStreamClientCallBack(CFReadStreamRef readStream, CFStreamEventType type, void *clientCallBackInfo) {
- [((ASIHTTPRequest*)clientCallBackInfo) handleNetworkEvent: type];
- }
- // This lock prevents the operation from being cancelled while it is trying to update the progress, and vice versa
- static NSRecursiveLock *progressLock;
- static NSError *ASIRequestCancelledError;
- static NSError *ASIRequestTimedOutError;
- static NSError *ASIAuthenticationError;
- static NSError *ASIUnableToCreateRequestError;
- static NSError *ASITooMuchRedirectionError;
- static NSMutableArray *bandwidthUsageTracker = nil;
- static unsigned long averageBandwidthUsedPerSecond = 0;
- // These are used for queuing persistent connections on the same connection
- // Incremented every time we specify we want a new connection
- static unsigned int nextConnectionNumberToCreate = 0;
- // An array of connectionInfo dictionaries.
- // When attempting a persistent connection, we look here to try to find an existing connection to the same server that is currently not in use
- static NSMutableArray *persistentConnectionsPool = nil;
- // Mediates access to the persistent connections pool
- static NSRecursiveLock *connectionsLock = nil;
- // Each request gets a new id, we store this rather than a ref to the request itself in the connectionInfo dictionary.
- // We do this so we don't have to keep the request around while we wait for the connection to expire
- static unsigned int nextRequestID = 0;
- // Records how much bandwidth all requests combined have used in the last second
- static unsigned long bandwidthUsedInLastSecond = 0;
- // A date one second in the future from the time it was created
- static NSDate *bandwidthMeasurementDate = nil;
- // Since throttling variables are shared among all requests, we'll use a lock to mediate access
- static NSLock *bandwidthThrottlingLock = nil;
- // the maximum number of bytes that can be transmitted in one second
- static unsigned long maxBandwidthPerSecond = 0;
- // A default figure for throttling bandwidth on mobile devices
- unsigned long const ASIWWANBandwidthThrottleAmount = 14800;
- #if TARGET_OS_IPHONE
- // YES when bandwidth throttling is active
- // This flag does not denote whether throttling is turned on - rather whether it is currently in use
- // It will be set to NO when throttling was turned on with setShouldThrottleBandwidthForWWAN, but a WI-FI connection is active
- static BOOL isBandwidthThrottled = NO;
- // When YES, bandwidth will be automatically throttled when using WWAN (3G/Edge/GPRS)
- // Wifi will not be throttled
- static BOOL shouldThrottleBandwithForWWANOnly = NO;
- #endif
- // Mediates access to the session cookies so requests
- static NSRecursiveLock *sessionCookiesLock = nil;
- // This lock ensures delegates only receive one notification that authentication is required at once
- // When using ASIAuthenticationDialogs, it also ensures only one dialog is shown at once
- // If a request can't acquire the lock immediately, it means a dialog is being shown or a delegate is handling the authentication challenge
- // Once it gets the lock, it will try to look for existing credentials again rather than showing the dialog / notifying the delegate
- // This is so it can make use of any credentials supplied for the other request, if they are appropriate
- static NSRecursiveLock *delegateAuthenticationLock = nil;
- // When throttling bandwidth, Set to a date in future that we will allow all requests to wake up and reschedule their streams
- static NSDate *throttleWakeUpTime = nil;
- static id <ASICacheDelegate> defaultCache = nil;
- // Used for tracking when requests are using the network
- static unsigned int runningRequestCount = 0;
- // You can use [ASIHTTPRequest setShouldUpdateNetworkActivityIndicator:NO] if you want to manage it yourself
- // Alternatively, override showNetworkActivityIndicator / hideNetworkActivityIndicator
- // By default this does nothing on Mac OS X, but again override the above methods for a different behaviour
- static BOOL shouldUpdateNetworkActivityIndicator = YES;
- // The thread all requests will run on
- // Hangs around forever, but will be blocked unless there are requests underway
- static NSThread *networkThread = nil;
- static NSOperationQueue *sharedQueue = nil;
- // Private stuff
- @interface ASIHTTPRequest ()
- - (void)cancelLoad;
- - (void)destroyReadStream;
- - (void)scheduleReadStream;
- - (void)unscheduleReadStream;
- - (BOOL)willAskDelegateForCredentials;
- - (BOOL)willAskDelegateForProxyCredentials;
- - (void)askDelegateForProxyCredentials;
- - (void)askDelegateForCredentials;
- - (void)failAuthentication;
- + (void)measureBandwidthUsage;
- + (void)recordBandwidthUsage;
- - (void)startRequest;
- - (void)updateStatus:(NSTimer *)timer;
- - (void)checkRequestStatus;
- - (void)reportFailure;
- - (void)reportFinished;
- - (void)markAsFinished;
- - (void)performRedirect;
- - (BOOL)shouldTimeOut;
- - (BOOL)willRedirect;
- - (BOOL)willAskDelegateToConfirmRedirect;
- + (void)performInvocation:(NSInvocation *)invocation onTarget:(id *)target releasingObject:(id)objectToRelease;
- + (void)hideNetworkActivityIndicatorAfterDelay;
- + (void)hideNetworkActivityIndicatorIfNeeeded;
- + (void)runRequests;
- // Handling Proxy autodetection and PAC file downloads
- - (BOOL)configureProxies;
- - (void)fetchPACFile;
- - (void)finishedDownloadingPACFile:(ASIHTTPRequest *)theRequest;
- - (void)runPACScript:(NSString *)script;
- - (void)timeOutPACRead;
- - (void)useDataFromCache;
- // Called to update the size of a partial download when starting a request, or retrying after a timeout
- - (void)updatePartialDownloadSize;
- #if TARGET_OS_IPHONE
- + (void)registerForNetworkReachabilityNotifications;
- + (void)unsubscribeFromNetworkReachabilityNotifications;
- // Called when the status of the network changes
- + (void)reachabilityChanged:(NSNotification *)note;
- #endif
- #if NS_BLOCKS_AVAILABLE
- - (void)performBlockOnMainThread:(ASIBasicBlock)block;
- - (void)releaseBlocksOnMainThread;
- + (void)releaseBlocks:(NSArray *)blocks;
- - (void)callBlock:(ASIBasicBlock)block;
- #endif
- @property (assign) BOOL complete;
- @property (retain) NSArray *responseCookies;
- @property (assign) int responseStatusCode;
- @property (retain, nonatomic) NSDate *lastActivityTime;
- @property (assign) unsigned long long partialDownloadSize;
- @property (assign, nonatomic) unsigned long long uploadBufferSize;
- @property (retain, nonatomic) NSOutputStream *postBodyWriteStream;
- @property (retain, nonatomic) NSInputStream *postBodyReadStream;
- @property (assign, nonatomic) unsigned long long lastBytesRead;
- @property (assign, nonatomic) unsigned long long lastBytesSent;
- @property (retain) NSRecursiveLock *cancelledLock;
- @property (retain, nonatomic) NSOutputStream *fileDownloadOutputStream;
- @property (retain, nonatomic) NSOutputStream *inflatedFileDownloadOutputStream;
- @property (assign) int authenticationRetryCount;
- @property (assign) int proxyAuthenticationRetryCount;
- @property (assign, nonatomic) BOOL updatedProgress;
- @property (assign, nonatomic) BOOL needsRedirect;
- @property (assign, nonatomic) int redirectCount;
- @property (retain, nonatomic) NSData *compressedPostBody;
- @property (retain, nonatomic) NSString *compressedPostBodyFilePath;
- @property (retain) NSString *authenticationRealm;
- @property (retain) NSString *proxyAuthenticationRealm;
- @property (retain) NSString *responseStatusMessage;
- @property (assign) BOOL inProgress;
- @property (assign) int retryCount;
- @property (assign) BOOL willRetryRequest;
- @property (assign) BOOL connectionCanBeReused;
- @property (retain, nonatomic) NSMutableDictionary *connectionInfo;
- @property (retain, nonatomic) NSInputStream *readStream;
- @property (assign) ASIAuthenticationState authenticationNeeded;
- @property (assign, nonatomic) BOOL readStreamIsScheduled;
- @property (assign, nonatomic) BOOL downloadComplete;
- @property (retain) NSNumber *requestID;
- @property (assign, nonatomic) NSString *runLoopMode;
- @property (retain, nonatomic) NSTimer *statusTimer;
- @property (assign) BOOL didUseCachedResponse;
- @property (retain, nonatomic) NSURL *redirectURL;
- @property (assign, nonatomic) BOOL isPACFileRequest;
- @property (retain, nonatomic) ASIHTTPRequest *PACFileRequest;
- @property (retain, nonatomic) NSInputStream *PACFileReadStream;
- @property (retain, nonatomic) NSMutableData *PACFileData;
- @property (assign, nonatomic, setter=setSynchronous:) BOOL isSynchronous;
- @end
- @implementation ASIHTTPRequest
- #pragma mark init / dealloc
- + (void)initialize
- {
- if (self == [ASIHTTPRequest class]) {
- persistentConnectionsPool = [[NSMutableArray alloc] init];
- connectionsLock = [[NSRecursiveLock alloc] init];
- progressLock = [[NSRecursiveLock alloc] init];
- bandwidthThrottlingLock = [[NSLock alloc] init];
- sessionCookiesLock = [[NSRecursiveLock alloc] init];
- sessionCredentialsLock = [[NSRecursiveLock alloc] init];
- delegateAuthenticationLock = [[NSRecursiveLock alloc] init];
- bandwidthUsageTracker = [[NSMutableArray alloc] initWithCapacity:5];
- ASIRequestTimedOutError = [[NSError alloc] initWithDomain:NetworkRequestErrorDomain code:ASIRequestTimedOutErrorType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"The request timed out",NSLocalizedDescriptionKey,nil]];
- ASIAuthenticationError = [[NSError alloc] initWithDomain:NetworkRequestErrorDomain code:ASIAuthenticationErrorType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Authentication needed",NSLocalizedDescriptionKey,nil]];
- ASIRequestCancelledError = [[NSError alloc] initWithDomain:NetworkRequestErrorDomain code:ASIRequestCancelledErrorType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"The request was cancelled",NSLocalizedDescriptionKey,nil]];
- ASIUnableToCreateRequestError = [[NSError alloc] initWithDomain:NetworkRequestErrorDomain code:ASIUnableToCreateRequestErrorType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Unable to create request (bad url?)",NSLocalizedDescriptionKey,nil]];
- ASITooMuchRedirectionError = [[NSError alloc] initWithDomain:NetworkRequestErrorDomain code:ASITooMuchRedirectionErrorType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"The request failed because it redirected too many times",NSLocalizedDescriptionKey,nil]];
- sharedQueue = [[NSOperationQueue alloc] init];
- [sharedQueue setMaxConcurrentOperationCount:4];
- }
- }
- - (id)initWithURL:(NSURL *)newURL
- {
- self = [self init];
- [self setRequestMethod:@"GET"];
- [self setRunLoopMode:NSDefaultRunLoopMode];
- [self setShouldAttemptPersistentConnection:YES];
- [self setPersistentConnectionTimeoutSeconds:60.0];
- [self setShouldPresentCredentialsBeforeChallenge:YES];
- [self setShouldRedirect:YES];
- [self setShowAccurateProgress:YES];
- [self setShouldResetDownloadProgress:YES];
- [self setShouldResetUploadProgress:YES];
- [self setAllowCompressedResponse:YES];
- [self setShouldWaitToInflateCompressedResponses:YES];
- [self setDefaultResponseEncoding:NSISOLatin1StringEncoding];
- [self setShouldPresentProxyAuthenticationDialog:YES];
-
- [self setTimeOutSeconds:[ASIHTTPRequest defaultTimeOutSeconds]];
- [self setUseSessionPersistence:YES];
- [self setUseCookiePersistence:YES];
- [self setValidatesSecureCertificate:YES];
- [self setRequestCookies:[[[NSMutableArray alloc] init] autorelease]];
- [self setDidStartSelector:@selector(requestStarted:)];
- [self setDidReceiveResponseHeadersSelector:@selector(request:didReceiveResponseHeaders:)];
- [self setWillRedirectSelector:@selector(request:willRedirectToURL:)];
- [self setDidFinishSelector:@selector(requestFinished:)];
- [self setDidFailSelector:@selector(requestFailed:)];
- [self setDidReceiveDataSelector:@selector(request:didReceiveData:)];
- [self setURL:newURL];
- [self setCancelledLock:[[[NSRecursiveLock alloc] init] autorelease]];
- [self setDownloadCache:[[self class] defaultCache]];
- return self;
- }
- + (id)requestWithURL:(NSURL *)newURL
- {
- return [[[self alloc] initWithURL:newURL] autorelease];
- }
- + (id)requestWithURL:(NSURL *)newURL usingCache:(id <ASICacheDelegate>)cache
- {
- return [self requestWithURL:newURL usingCache:cache andCachePolicy:ASIUseDefaultCachePolicy];
- }
- + (id)requestWithURL:(NSURL *)newURL usingCache:(id <ASICacheDelegate>)cache andCachePolicy:(ASICachePolicy)policy
- {
- ASIHTTPRequest *request = [[[self alloc] initWithURL:newURL] autorelease];
- [request setDownloadCache:cache];
- [request setCachePolicy:policy];
- return request;
- }
- - (void)dealloc
- {
- [self setAuthenticationNeeded:ASINoAuthenticationNeededYet];
- if (requestAuthentication) {
- CFRelease(requestAuthentication);
- }
- if (proxyAuthentication) {
- CFRelease(proxyAuthentication);
- }
- if (request) {
- CFRelease(request);
- }
- if (clientCertificateIdentity) {
- CFRelease(clientCertificateIdentity);
- }
- [self cancelLoad];
- [redirectURL release];
- [statusTimer invalidate];
- [statusTimer release];
- [queue release];
- [userInfo release];
- [postBody release];
- [compressedPostBody release];
- [error release];
- [requestHeaders release];
- [requestCookies release];
- [downloadDestinationPath release];
- [temporaryFileDownloadPath release];
- [temporaryUncompressedDataDownloadPath release];
- [fileDownloadOutputStream release];
- [inflatedFileDownloadOutputStream release];
- [username release];
- [password release];
- [domain release];
- [authenticationRealm release];
- [authenticationScheme release];
- [requestCredentials release];
- [proxyHost release];
- [proxyType release];
- [proxyUsername release];
- [proxyPassword release];
- [proxyDomain release];
- [proxyAuthenticationRealm release];
- [proxyAuthenticationScheme release];
- [proxyCredentials release];
- [url release];
- [originalURL release];
- [lastActivityTime release];
- [responseCookies release];
- [rawResponseData release];
- [responseHeaders release];
- [requestMethod release];
- [cancelledLock release];
- [postBodyFilePath release];
- [compressedPostBodyFilePath release];
- [postBodyWriteStream release];
- [postBodyReadStream release];
- [PACurl release];
- [clientCertificates release];
- [responseStatusMessage release];
- [connectionInfo release];
- [requestID release];
- [dataDecompressor release];
- [userAgent release];
- #if NS_BLOCKS_AVAILABLE
- [self releaseBlocksOnMainThread];
- #endif
- [super dealloc];
- }
- #if NS_BLOCKS_AVAILABLE
- - (void)releaseBlocksOnMainThread
- {
- NSMutableArray *blocks = [NSMutableArray array];
- if (completionBlock) {
- [blocks addObject:completionBlock];
- [completionBlock release];
- completionBlock = nil;
- }
- if (failureBlock) {
- [blocks addObject:failureBlock];
- [failureBlock release];
- failureBlock = nil;
- }
- if (startedBlock) {
- [blocks addObject:startedBlock];
- [startedBlock release];
- startedBlock = nil;
- }
- if (headersReceivedBlock) {
- [blocks addObject:headersReceivedBlock];
- [headersReceivedBlock release];
- headersReceivedBlock = nil;
- }
- if (bytesReceivedBlock) {
- [blocks addObject:bytesReceivedBlock];
- [bytesReceivedBlock release];
- bytesReceivedBlock = nil;
- }
- if (bytesSentBlock) {
- [blocks addObject:bytesSentBlock];
- [bytesSentBlock release];
- bytesSentBlock = nil;
- }
- if (downloadSizeIncrementedBlock) {
- [blocks addObject:downloadSizeIncrementedBlock];
- [downloadSizeIncrementedBlock release];
- downloadSizeIncrementedBlock = nil;
- }
- if (uploadSizeIncrementedBlock) {
- [blocks addObject:uploadSizeIncrementedBlock];
- [uploadSizeIncrementedBlock release];
- uploadSizeIncrementedBlock = nil;
- }
- if (dataReceivedBlock) {
- [blocks addObject:dataReceivedBlock];
- [dataReceivedBlock release];
- dataReceivedBlock = nil;
- }
- if (proxyAuthenticationNeededBlock) {
- [blocks addObject:proxyAuthenticationNeededBlock];
- [proxyAuthenticationNeededBlock release];
- proxyAuthenticationNeededBlock = nil;
- }
- if (authenticationNeededBlock) {
- [blocks addObject:authenticationNeededBlock];
- [authenticationNeededBlock release];
- authenticationNeededBlock = nil;
- }
- [[self class] performSelectorOnMainThread:@selector(releaseBlocks:) withObject:blocks waitUntilDone:[NSThread isMainThread]];
- }
- // Always called on main thread
- + (void)releaseBlocks:(NSArray *)blocks
- {
- // Blocks will be released when this method exits
- }
- #endif
- #pragma mark setup request
- - (void)addRequestHeader:(NSString *)header value:(NSString *)value
- {
- if (!requestHeaders) {
- [self setRequestHeaders:[NSMutableDictionary dictionaryWithCapacity:1]];
- }
- [requestHeaders setObject:value forKey:header];
- }
- // This function will be called either just before a request starts, or when postLength is needed, whichever comes first
- // postLength must be set by the time this function is complete
- - (void)buildPostBody
- {
- if ([self haveBuiltPostBody]) {
- return;
- }
-
- // Are we submitting the request body from a file on disk
- if ([self postBodyFilePath]) {
-
- // If we were writing to the post body via appendPostData or appendPostDataFromFile, close the write stream
- if ([self postBodyWriteStream]) {
- [[self postBodyWriteStream] close];
- [self setPostBodyWriteStream:nil];
- }
-
- NSString *path;
- if ([self shouldCompressRequestBody]) {
- if (![self compressedPostBodyFilePath]) {
- [self setCompressedPostBodyFilePath:[NSTemporaryDirectory() stringByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]]];
-
- NSError *err = nil;
- if (![ASIDataCompressor compressDataFromFile:[self postBodyFilePath] toFile:[self compressedPostBodyFilePath] error:&err]) {
- [self failWithError:err];
- return;
- }
- }
- path = [self compressedPostBodyFilePath];
- } else {
- path = [self postBodyFilePath];
- }
- NSError *err = nil;
- [self setPostLength:[[[[[NSFileManager alloc] init] autorelease] attributesOfItemAtPath:path error:&err] fileSize]];
- if (err) {
- [self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIFileManagementError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Failed to get attributes for file at path '%@'",path],NSLocalizedDescriptionKey,error,NSUnderlyingErrorKey,nil]]];
- return;
- }
-
- // Otherwise, we have an in-memory request body
- } else {
- if ([self shouldCompressRequestBody]) {
- NSError *err = nil;
- NSData *compressedBody = [ASIDataCompressor compressData:[self postBody] error:&err];
- if (err) {
- [self failWithError:err];
- return;
- }
- [self setCompressedPostBody:compressedBody];
- [self setPostLength:[[self compressedPostBody] length]];
- } else {
- [self setPostLength:[[self postBody] length]];
- }
- }
-
- if ([self postLength] > 0) {
- if ([requestMethod isEqualToString:@"GET"] || [requestMethod isEqualToString:@"DELETE"] || [requestMethod isEqualToString:@"HEAD"]) {
- [self setRequestMethod:@"POST"];
- }
- [self addRequestHeader:@"Content-Length" value:[NSString stringWithFormat:@"%llu",[self postLength]]];
- }
- [self setHaveBuiltPostBody:YES];
- }
- // Sets up storage for the post body
- - (void)setupPostBody
- {
- if ([self shouldStreamPostDataFromDisk]) {
- if (![self postBodyFilePath]) {
- [self setPostBodyFilePath:[NSTemporaryDirectory() stringByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]]];
- [self setDidCreateTemporaryPostDataFile:YES];
- }
- if (![self postBodyWriteStream]) {
- [self setPostBodyWriteStream:[[[NSOutputStream alloc] initToFileAtPath:[self postBodyFilePath] append:NO] autorelease]];
- [[self postBodyWriteStream] open];
- }
- } else {
- if (![self postBody]) {
- [self setPostBody:[[[NSMutableData alloc] init] autorelease]];
- }
- }
- }
- - (void)appendPostData:(NSData *)data
- {
- [self setupPostBody];
- if ([data length] == 0) {
- return;
- }
- if ([self shouldStreamPostDataFromDisk]) {
- [[self postBodyWriteStream] write:[data bytes] maxLength:[data length]];
- } else {
- [[self postBody] appendData:data];
- }
- }
- - (void)appendPostDataFromFile:(NSString *)file
- {
- [self setupPostBody];
- NSInputStream *stream = [[[NSInputStream alloc] initWithFileAtPath:file] autorelease];
- [stream open];
- NSUInteger bytesRead;
- while ([stream hasBytesAvailable]) {
-
- unsigned char buffer[1024*256];
- bytesRead = [stream read:buffer maxLength:sizeof(buffer)];
- if (bytesRead == 0) {
- break;
- }
- if ([self shouldStreamPostDataFromDisk]) {
- [[self postBodyWriteStream] write:buffer maxLength:bytesRead];
- } else {
- [[self postBody] appendData:[NSData dataWithBytes:buffer length:bytesRead]];
- }
- }
- [stream close];
- }
- - (NSString *)requestMethod
- {
- [[self cancelledLock] lock];
- NSString *m = requestMethod;
- [[self cancelledLock] unlock];
- return m;
- }
- - (void)setRequestMethod:(NSString *)newRequestMethod
- {
- [[self cancelledLock] lock];
- if (requestMethod != newRequestMethod) {
- [requestMethod release];
- requestMethod = [newRequestMethod retain];
- if ([requestMethod isEqualToString:@"POST"] || [requestMethod isEqualToString:@"PUT"] || [postBody length] || postBodyFilePath) {
- [self setShouldAttemptPersistentConnection:NO];
- }
- }
- [[self cancelledLock] unlock];
- }
- - (NSURL *)url
- {
- [[self cancelledLock] lock];
- NSURL *u = url;
- [[self cancelledLock] unlock];
- return u;
- }
- - (void)setURL:(NSURL *)newURL
- {
- [[self cancelledLock] lock];
- if ([newURL isEqual:[self url]]) {
- [[self cancelledLock] unlock];
- return;
- }
- [url release];
- url = [newURL retain];
- if (requestAuthentication) {
- CFRelease(requestAuthentication);
- requestAuthentication = NULL;
- }
- if (proxyAuthentication) {
- CFRelease(proxyAuthentication);
- proxyAuthentication = NULL;
- }
- if (request) {
- CFRelease(request);
- request = NULL;
- }
- [self setRedirectURL:nil];
- [[self cancelledLock] unlock];
- }
- - (id)delegate
- {
- [[self cancelledLock] lock];
- id d = delegate;
- [[self cancelledLock] unlock];
- return d;
- }
- - (void)setDelegate:(id)newDelegate
- {
- [[self cancelledLock] lock];
- delegate = newDelegate;
- [[self cancelledLock] unlock];
- }
- - (id)queue
- {
- [[self cancelledLock] lock];
- id q = queue;
- [[self cancelledLock] unlock];
- return q;
- }
- - (void)setQueue:(id)newQueue
- {
- [[self cancelledLock] lock];
- if (newQueue != queue) {
- [queue release];
- queue = [newQueue retain];
- }
- [[self cancelledLock] unlock];
- }
- #pragma mark get information about this request
- // cancel the request - this must be run on the same thread as the request is running on
- - (void)cancelOnRequestThread
- {
- #if DEBUG_REQUEST_STATUS
- NSLog(@"[STATUS] Request cancelled: %@",self);
- #endif
-
- [[self cancelledLock] lock];
- if ([self isCancelled] || [self complete]) {
- [[self cancelledLock] unlock];
- return;
- }
- [self failWithError:ASIRequestCancelledError];
- [self setComplete:YES];
- [self cancelLoad];
-
- CFRetain(self);
- [self willChangeValueForKey:@"isCancelled"];
- cancelled = YES;
- [self didChangeValueForKey:@"isCancelled"];
-
- [[self cancelledLock] unlock];
- CFRelease(self);
- }
- - (void)cancel
- {
- [self performSelector:@selector(cancelOnRequestThread) onThread:[[self class] threadForRequest:self] withObject:nil waitUntilDone:NO];
- }
- - (void)clearDelegatesAndCancel
- {
- [[self cancelledLock] lock];
- // Clear delegates
- [self setDelegate:nil];
- [self setQueue:nil];
- [self setDownloadProgressDelegate:nil];
- [self setUploadProgressDelegate:nil];
- #if NS_BLOCKS_AVAILABLE
- // Clear blocks
- [self releaseBlocksOnMainThread];
- #endif
- [[self cancelledLock] unlock];
- [self cancel];
- }
- - (BOOL)isCancelled
- {
- BOOL result;
-
- [[self cancelledLock] lock];
- result = cancelled;
- [[self cancelledLock] unlock];
-
- return result;
- }
- // Call this method to get the received data as an NSString. Don't use for binary data!
- - (NSString *)responseString
- {
- NSData *data = [self responseData];
- if (!data) {
- return nil;
- }
-
- return [[[NSString alloc] initWithBytes:[data bytes] length:[data length] encoding:[self responseEncoding]] autorelease];
- }
- - (BOOL)isResponseCompressed
- {
- NSString *encoding = [[self responseHeaders] objectForKey:@"Content-Encoding"];
- return encoding && [encoding rangeOfString:@"gzip"].location != NSNotFound;
- }
- - (NSData *)responseData
- {
- if ([self isResponseCompressed] && [self shouldWaitToInflateCompressedResponses]) {
- return [ASIDataDecompressor uncompressData:[self rawResponseData] error:NULL];
- } else {
- return [self rawResponseData];
- }
- return nil;
- }
- #pragma mark running a request
- - (void)startSynchronous
- {
- #if DEBUG_REQUEST_STATUS || DEBUG_THROTTLING
- NSLog(@"[STATUS] Starting synchronous request %@",self);
- #endif
- [self setSynchronous:YES];
- [self setRunLoopMode:ASIHTTPRequestRunLoopMode];
- [self setInProgress:YES];
- if (![self isCancelled] && ![self complete]) {
- [self main];
- while (!complete) {
- [[NSRunLoop currentRunLoop] runMode:[self runLoopMode] beforeDate:[NSDate distantFuture]];
- }
- }
- [self setInProgress:NO];
- }
- - (void)start
- {
- [self setInProgress:YES];
- [self performSelector:@selector(main) onThread:[[self class] threadForRequest:self] withObject:nil waitUntilDone:NO];
- }
- - (void)startAsynchronous
- {
- #if DEBUG_REQUEST_STATUS || DEBUG_THROTTLING
- NSLog(@"[STATUS] Starting asynchronous request %@",self);
- #endif
- [sharedQueue addOperation:self];
- }
- #pragma mark concurrency
- - (BOOL)isConcurrent
- {
- return YES;
- }
- - (BOOL)isFinished
- {
- return finished;
- }
- - (BOOL)isExecuting {
- return [self inProgress];
- }
- #pragma mark request logic
- // Create the request
- - (void)main
- {
- @try {
-
- [[self cancelledLock] lock];
-
- #if TARGET_OS_IPHONE && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_4_0
- if ([ASIHTTPRequest isMultitaskingSupported] && [self shouldContinueWhenAppEntersBackground]) {
- backgroundTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
- // Synchronize the cleanup call on the main thread in case
- // the task actually finishes at around the same time.
- dispatch_async(dispatch_get_main_queue(), ^{
- if (backgroundTask != UIBackgroundTaskInvalid)
- {
- [[UIApplication sharedApplication] endBackgroundTask:backgroundTask];
- backgroundTask = UIBackgroundTaskInvalid;
- [self cancel];
- }
- });
- }];
- }
- #endif
- // A HEAD request generated by an ASINetworkQueue may have set the error already. If so, we should not proceed.
- if ([self error]) {
- [self setComplete:YES];
- [self markAsFinished];
- return;
- }
- [self setComplete:NO];
- [self setDidUseCachedResponse:NO];
-
- if (![self url]) {
- [self failWithError:ASIUnableToCreateRequestError];
- return;
- }
-
- // Must call before we create the request so that the request method can be set if needs be
- if (![self mainRequest]) {
- [self buildPostBody];
- }
-
- if (![[self requestMethod] isEqualToString:@"GET"]) {
- [self setDownloadCache:nil];
- }
-
-
- // If we're redirecting, we'll already have a CFHTTPMessageRef
- if (request) {
- CFRelease(request);
- }
- // Create a new HTTP request.
- request = CFHTTPMessageCreateRequest(kCFAllocatorDefault, (CFStringRef)[self requestMethod], (CFURLRef)[self url], [self useHTTPVersionOne] ? kCFHTTPVersion1_0 : kCFHTTPVersion1_1);
- if (!request) {
- [self failWithError:ASIUnableToCreateRequestError];
- return;
- }
- //If this is a HEAD request generated by an ASINetworkQueue, we need to let the main request generate its headers first so we can use them
- if ([self mainRequest]) {
- [[self mainRequest] buildRequestHeaders];
- }
-
- // Even if this is a HEAD request with a mainRequest, we still need to call to give subclasses a chance to add their own to HEAD requests (ASIS3Request does this)
- [self buildRequestHeaders];
-
- if ([self downloadCache]) {
- // If this request should use the default policy, set its policy to the download cache's default policy
- if (![self cachePolicy]) {
- [self setCachePolicy:[[self downloadCache] defaultCachePolicy]];
- }
- // If have have cached data that is valid for this request, use that and stop
- if ([[self downloadCache] canUseCachedDataForRequest:self]) {
- [self useDataFromCache];
- return;
- }
- // If cached data is stale, or we have been told to ask the server if it has been modified anyway, we need to add headers for a conditional GET
- if ([self cachePolicy] & (ASIAskServerIfModifiedWhenStaleCachePolicy|ASIAskServerIfModifiedCachePolicy)) {
- NSDictionary *cachedHeaders = [[self downloadCache] cachedResponseHeadersForURL:[self url]];
- if (cachedHeaders) {
- NSString *etag = [cachedHeaders objectForKey:@"Etag"];
- if (etag) {
- [[self requestHeaders] setObject:etag forKey:@"If-None-Match"];
- }
- NSString *lastModified = [cachedHeaders objectForKey:@"Last-Modified"];
- if (lastModified) {
- [[self requestHeaders] setObject:lastModified forKey:@"If-Modified-Since"];
- }
- }
- }
- }
- [self applyAuthorizationHeader];
-
-
- NSString *header;
- for (header in [self requestHeaders]) {
- CFHTTPMessageSetHeaderFieldValue(request, (CFStringRef)header, (CFStringRef)[[self requestHeaders] objectForKey:header]);
- }
- // If we immediately have access to proxy settings, start the request
- // Otherwise, we'll start downloading the proxy PAC file, and call startRequest once that process is complete
- if ([self configureProxies]) {
- [self startRequest];
- }
- } @catch (NSException *exception) {
- NSError *underlyingError = [NSError errorWithDomain:NetworkRequestErrorDomain code:ASIUnhandledExceptionError userInfo:[exception userInfo]];
- [self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIUnhandledExceptionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[exception name],NSLocalizedDescriptionKey,[exception reason],NSLocalizedFailureReasonErrorKey,underlyingError,NSUnderlyingErrorKey,nil]]];
- } @finally {
- [[self cancelledLock] unlock];
- }
- }
- - (void)applyAuthorizationHeader
- {
- // Do we want to send credentials before we are asked for them?
- if (![self shouldPresentCredentialsBeforeChallenge]) {
- #if DEBUG_HTTP_AUTHENTICATION
- NSLog(@"[AUTH] Request %@ will not send credentials to the server until it asks for them",self);
- #endif
- return;
- }
- NSDictionary *credentials = nil;
- // Do we already have an auth header?
- if (![[self requestHeaders] objectForKey:@"Authorization"]) {
- // If we have basic authentication explicitly set and a username and password set on the request, add a basic auth header
- if ([self username] && [self password] && [[self authenticationScheme] isEqualToString:(NSString *)kCFHTTPAuthenticationSchemeBasic]) {
- [self addBasicAuthenticationHeaderWithUsername:[self username] andPassword:[self password]];
- #if DEBUG_HTTP_AUTHENTICATION
- NSLog(@"[AUTH] Request %@ has a username and password set, and was manually configured to use BASIC. Will send credentials without waiting for an authentication challenge",self);
- #endif
- } else {
- // See if we have any cached credentials we can use in the session store
- if ([self useSessionPersistence]) {
- credentials = [self findSessionAuthenticationCredentials];
- if (credentials) {
- // When the Authentication key is set, the credentials were stored after an authentication challenge, so we can let CFNetwork apply them
- // (credentials for Digest and NTLM will always be stored like this)
- if ([credentials objectForKey:@"Authentication"]) {
- // If we've already talked to this server and have valid credentials, let's apply them to the request
- if (CFHTTPMessageApplyCredentialDictionary(request, (CFHTTPAuthenticationRef)[credentials objectForKey:@"Authentication"], (CFDictionaryRef)[credentials objectForKey:@"Credentials"], NULL)) {
- [self setAuthenticationScheme:[credentials objectForKey:@"AuthenticationScheme"]];
- #if DEBUG_HTTP_AUTHENTICATION
- NSLog(@"[AUTH] Request %@ found cached credentials (%@), will reuse without waiting for an authentication challenge",self,[credentials objectForKey:@"AuthenticationScheme"]);
- #endif
- } else {
- [[self class] removeAuthenticationCredentialsFromSessionStore:[credentials objectForKey:@"Credentials"]];
- #if DEBUG_HTTP_AUTHENTICATION
- NSLog(@"[AUTH] Failed to apply cached credentials to request %@. These will be removed from the session store, and this request will wait for an authentication challenge",self);
- #endif
- }
- // If the Authentication key is not set, these credentials were stored after a username and password set on a previous request passed basic authentication
- // When this happens, we'll need to create the Authorization header ourselves
- } else {
- NSDictionary *usernameAndPassword = [credentials objectForKey:@"Credentials"];
- [self addBasicAuthenticationHeaderWithUsername:[usernameAndPassword objectForKey:(NSString *)kCFHTTPAuthenticationUsername] andPassword:[usernameAndPassword objectForKey:(NSString *)kCFHTTPAuthenticationPassword]];
- #if DEBUG_HTTP_AUTHENTICATION
- NSLog(@"[AUTH] Request %@ found cached BASIC credentials from a previous request. Will send credentials without waiting for an authentication challenge",self);
- #endif
- }
- }
- }
- }
- }
- // Apply proxy authentication credentials
- if ([self useSessionPersistence]) {
- credentials = [self findSessionProxyAuthenticationCredentials];
- if (credentials) {
- if (!CFHTTPMessageApplyCredentialDictionary(request, (CFHTTPAuthenticationRef)[credentials objectForKey:@"Authentication"], (CFDictionaryRef)[credentials objectForKey:@"Credentials"], NULL)) {
- [[self class] removeProxyAuthenticationCredentialsFromSessionStore:[credentials objectForKey:@"Credentials"]];
- }
- }
- }
- }
- - (void)applyCookieHeader
- {
- // Add cookies from the persistent (mac os global) store
- if ([self useCookiePersistence]) {
- NSArray *cookies = [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookiesForURL:[[self url] absoluteURL]];
- if (cookies) {
- [[self requestCookies] addObjectsFromArray:cookies];
- }
- }
-
- // Apply request cookies
- NSArray *cookies;
- if ([self mainRequest]) {
- cookies = [[self mainRequest] requestCookies];
- } else {
- cookies = [self requestCookies];
- }
- if ([cookies count] > 0) {
- NSHTTPCookie *cookie;
- NSString *cookieHeader = nil;
- for (cookie in cookies) {
- if (!cookieHeader) {
- cookieHeader = [NSString stringWithFormat: @"%@=%@",[cookie name],[cookie value]];
- } else {
- cookieHeader = [NSString stringWithFormat: @"%@; %@=%@",cookieHeader,[cookie name],[cookie value]];
- }
- }
- if (cookieHeader) {
- [self addRequestHeader:@"Cookie" value:cookieHeader];
- }
- }
- }
- - (void)buildRequestHeaders
- {
- if ([self haveBuiltRequestHeaders]) {
- return;
- }
- [self setHaveBuiltRequestHeaders:YES];
-
- if ([self mainRequest]) {
- for (NSString *header in [[self mainRequest] requestHeaders]) {
- [self addRequestHeader:header value:[[[self mainRequest] requestHeaders] valueForKey:header]];
- }
- return;
- }
-
- [self applyCookieHeader];
-
- // Build and set the user agent string if the request does not already have a custom user agent specified
- if (![[self requestHeaders] objectForKey:@"User-Agent"]) {
- NSString *userAgentString = [self userAgent];
- if (!userAgentString) {
- userAgentString = [ASIHTTPRequest defaultUserAgentString];
- }
- if (userAgentString) {
- [self addRequestHeader:@"User-Agent" value:userAgentString];
- }
- }
-
-
- // Accept a compressed response
- if ([self allowCompressedResponse]) {
- [self addRequestHeader:@"Accept-Encoding" value:@"gzip"];
- }
-
- // Configure a compressed request body
- if ([self shouldCompressRequestBody]) {
- [self addRequestHeader:@"Content-Encoding" value:@"gzip"];
- }
-
- // Should this request resume an existing download?
- [self updatePartialDownloadSize];
- if ([self partialDownloadSize]) {
- [self addRequestHeader:@"Range" value:[NSString stringWithFormat:@"bytes=%llu-",[self partialDownloadSize]]];
- }
- }
- - (void)updatePartialDownloadSize
- {
- NSFileManager *fileManager = [[[NSFileManager alloc] init] autorelease];
- if ([self allowResumeForFileDownloads] && [self downloadDestinationPath] && [self temporaryFileDownloadPath] && [fileManager fileExistsAtPath:[self temporaryFileDownloadPath]]) {
- NSError *err = nil;
- [self setPartialDownloadSize:[[fileManager attributesOfItemAtPath:[self temporaryFileDownloadPath] error:&err] fileSize]];
- if (err) {
- [self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIFileManagementError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Failed to get attributes for file at path '%@'",[self temporaryFileDownloadPath]],NSLocalizedDescriptionKey,error,NSUnderlyingErrorKey,nil]]];
- return;
- }
- }
- }
- - (void)startRequest
- {
- if ([self isCancelled]) {
- return;
- }
-
- [self performSelectorOnMainThread:@selector(requestStarted) withObject:nil waitUntilDone:[NSThread isMainThread]];
-
- [self setDownloadComplete:NO];
- [self setComplete:NO];
- [self setTotalBytesRead:0];
- [self setLastBytesRead:0];
-
- if ([self redirectCount] == 0) {
- [self setOriginalURL:[self url]];
- }
-
- // If we're retrying a request, let's remove any progress we made
- if ([self lastBytesSent] > 0) {
- [self removeUploadProgressSoFar];
- }
-
- [self setLastBytesSent:0];
- [self setContentLength:0];
- [self setResponseHeaders:nil];
- if (![self downloadDestinationPath]) {
- [self setRawResponseData:[[[NSMutableData alloc] init] autorelease]];
- }
-
-
- //
- // Create the stream for the request
- //
- NSFileManager *fileManager = [[[NSFileManager alloc] init] autorelease];
- [self setReadStreamIsScheduled:NO];
-
- // Do we need to stream the request body from disk
- if ([self shouldStreamPostDataFromDisk] && [self postBodyFilePath] && [fileManager fileExistsAtPath:[self postBodyFilePath]]) {
-
- // Are we gzipping the request body?
- if ([self compressedPostBodyFilePath] && [fileManager fileExistsAtPath:[self compressedPostBodyFilePath]]) {
- [self setPostBodyReadStream:[ASIInputStream inputStreamWithFileAtPath:[self compressedPostBodyFilePath] request:self]];
- } else {
- [self setPostBodyReadStream:[ASIInputStream inputStreamWithFileAtPath:[self postBodyFilePath] request:self]];
- }
- [self setReadStream:[NSMakeCollectable(CFReadStreamCreateForStreamedHTTPRequest(kCFAllocatorDefault, request,(CFReadStreamRef)[self postBodyReadStream])) autorelease]];
- } else {
-
- // If we have a request body, we'll stream it from memory using our custom stream, so that we can measure bandwidth use and it can be bandwidth-throttled if necessary
- if ([self postBody] && [[self postBody] length] > 0) {
- if ([self shouldCompressRequestBody] && [self compressedPostBody]) {
- [self setPostBodyReadStream:[ASIInputStream inputStreamWithData:[self compressedPostBody] request:self]];
- } else if ([self postBody]) {
- [self setPostBodyReadStream:[ASIInputStream inputStreamWithData:[self postBody] request:self]];
- }
- [self setReadStream:[NSMakeCollectable(CFReadStreamCreateForStreamedHTTPRequest(kCFAllocatorDefault, request,(CFReadStreamRef)[self postBodyReadStream])) autorelease]];
-
- } else {
- [self setReadStream:[NSMakeCollectable(CFReadStreamCreateForHTTPRequest(kCFAllocatorDefault, request)) autorelease]];
- }
- }
- if (![self readStream]) {
- [self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIInternalErrorWhileBuildingRequestType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Unable to create read stream",NSLocalizedDescriptionKey,nil]]];
- return;
- }
-
-
- //
- // Handle SSL certificate settings
- //
- if([[[[self url] scheme] lowercaseString] isEqualToString:@"https"]) {
- NSMutableDictionary *sslProperties = [NSMutableDictionary dictionaryWithCapacity:1];
- // Tell CFNetwork not to validate SSL certificates
- if (![self validatesSecureCertificate]) {
- [sslProperties setObject:(NSString *)kCFBooleanFalse forKey:(NSString *)kCFStreamSSLValidatesCertificateChain];
- }
- // Tell CFNetwork to use a client certificate
- if (clientCertificateIdentity) {
- NSMutableArray *certificates = [NSMutableArray arrayWithCapacity:[clientCertificates count]+1];
- // The first object in the array is our SecIdentityRef
- [certificates addObject:(id)clientCertificateIdentity];
- // If we've added any additional certificates, add them too
- for (id cert in clientCertificates) {
- [certificates addObject:cert];
- }
- [sslProperties setObject:certificates forKey:(NSString *)kCFStreamSSLCertificates];
- }
- CFReadStreamSetProperty((CFReadStreamRef)[self readStream], kCFStreamPropertySSLSettings, sslProperties);
- }
- //
- // Handle proxy settings
- //
- if ([self proxyHost] && [self proxyPort]) {
- NSString *hostKey;
- NSString *portKey;
- if (![self proxyType]) {
- [self setProxyType:(NSString *)kCFProxyTypeHTTP];
- }
- if ([[self proxyType] isEqualToString:(NSString *)kCFProxyTypeSOCKS]) {
- hostKey = (NSString *)kCFStreamPropertySOCKSProxyHost;
- portKey = (NSString *)kCFStreamPropertySOCKSProxyPort;
- } else {
- hostKey = (NSString *)kCFStreamPropertyHTTPProxyHost;
- portKey = (NSString *)kCFStreamPropertyHTTPProxyPort;
- if ([[[[self url] scheme] lowercaseString] isEqualToString:@"https"]) {
- hostKey = (NSString *)kCFStreamPropertyHTTPSProxyHost;
- portKey = (NSString *)kCFStreamPropertyHTTPSProxyPort;
- }
- }
- NSMutableDictionary *proxyToUse = [NSMutableDictionary dictionaryWithObjectsAndKeys:[self proxyHost],hostKey,[NSNumber numberWithInt:[self proxyPort]],portKey,nil];
- if ([[self proxyType] isEqualToString:(NSString *)kCFProxyTypeSOCKS]) {
- CFReadStreamSetProperty((CFReadStreamRef)[self readStream], kCFStreamPropertySOCKSProxy, proxyToUse);
- } else {
- CFReadStreamSetProperty((CFReadStreamRef)[self readStream], kCFStreamPropertyHTTPProxy, proxyToUse);
- }
- }
- //
- // Handle persistent connections
- //
-
- [ASIHTTPRequest expirePersistentConnections];
- [connectionsLock lock];
-
-
- if (![[self url] host] || ![[self url] scheme]) {
- [self setConnectionInfo:nil];
- [self setShouldAttemptPersistentConnection:NO];
- }
-
- // Will store the old stream that was using this connection (if there was one) so we can clean it up once we've opened our own stream
- NSInputStream *oldStream = nil;
-
- // Use a persistent connection if possible
- if ([self shouldAttemptPersistentConnection]) {
-
- // If we are redirecting, we will re-use the current connection only if we are connecting to the same server
- if ([self connectionInfo]) {
-
- if (![[[self connectionInfo] objectForKey:@"host"] isEqualToString:[[self url] host]] || ![[[self connectionInfo] objectForKey:@"scheme"] isEqualToString:[[self url] scheme]] || [(NSNumber *)[[self connectionInfo] objectForKey:@"port"] intValue] != [[[self url] port] intValue]) {
- [self setConnectionInfo:nil];
- // Check if we should have expired this connection
- } else if ([[[self connectionInfo] objectForKey:@"expires"] timeIntervalSinceNow] < 0) {
- #if DEBUG_PERSISTENT_CONNECTIONS
- NSLog(@"[CONNECTION] Not re-using connection #%i because it has expired",[[[self connectionInfo] objectForKey:@"id"] intValue]);
- #endif
- [persistentConnectionsPool removeObject:[self connectionInfo]];
- [self setConnectionInfo:nil];
- } else if ([[self connectionInfo] objectForKey:@"request"] != nil) {
- //Some other request reused this connection already - we'll have to create a new one
- #if DEBUG_PERSISTENT_CONNECTIONS
- NSLog(@"%@ - Not re-using connection #%i for request #%i because it is already used by request #%i",self,[[[self connectionInfo] objectForKey:@"id"] intValue],[[self requestID] intValue],[[[self connectionInfo] objectForKey:@"request"] intValue]);
- #endif
- [self setConnectionInfo:nil];
- }
- }
-
-
-
- if (![self connectionInfo] && [[self url] host] && [[self url] scheme]) { // We must have a proper url with a host and scheme, or this will explode
-
- // Look for a connection to the same server in the pool
- for (NSMutableDictionary *existingConnection in persistentConnectionsPool) {
- if (![existingConnection objectForKey:@"request"] && [[existingConnection objectForKey:@"host"] isEqualToString:[[self url] host]] && [[existingConnection objectForKey:@"scheme"] isEqualToString:[[self url] scheme]] && [(NSNumber *)[existingConnection objectForKey:@"port"] intValue] == [[[self url] port] intValue]) {
- [self setConnectionInfo:existingConnection];
- }
- }
- }
-
- if ([[self connectionInfo] objectForKey:@"stream"]) {
- oldStream = [[[self connectionInfo] objectForKey:@"stream"] retain];
- }
-
- // No free connection was found in the pool matching the server/scheme/port we're connecting to, we'll need to create a new one
- if (![self connectionInfo]) {
- [self setConnectionInfo:[NSMutableDictionary dictionary]];
- nextConnectionNumberToCreate++;
- [[self connectionInfo] setObject:[NSNumber numberWithInt:nextConnectionNumberToCreate] forKey:@"id"];
- [[self connectionInfo] setObject:[[self url] host] forKey:@"host"];
- [[self connectionInfo] setObject:[NSNumber numberWithInt:[[[self url] port] intValue]] forKey:@"port"];
- [[self connectionInfo] setObject:[[self url] scheme] forKey:@"scheme"];
- [persistentConnectionsPool addObject:[self connectionInfo]];
- }
-
- // If we are retrying this request, it will already have a requestID
- if (![self requestID]) {
- nextRequestID++;
- [self setRequestID:[NSNumber numberWithUnsignedInt:nextRequestID]];
- }
- [[self connectionInfo] setObject:[self requestID] forKey:@"request"];
- [[self connectionInfo] setObject:[self readStream] forKey:@"stream"];
- CFReadStreamSetProperty((CFReadStreamRef)[self readStream], kCFStreamPropertyHTTPAttemptPersistentConnection, kCFBooleanTrue);
-
- #if DEBUG_PERSISTENT_CONNECTIONS
- NSLog(@"[CONNECTION] Request #%@ will use connection #%i",[self requestID],[[[self connectionInfo] objectForKey:@"id"] intValue]);
- #endif
-
-
- // Tag the stream with an id that tells it which connection to use behind the scenes
- // See http://lists.apple.com/archives/macnetworkprog/2008/Dec/msg00001.html for details on this approach
-
- CFReadStreamSetProperty((CFReadStreamRef)[self readStream], CFSTR("ASIStreamID"), [[self connectionInfo] objectForKey:@"id"]);
-
- } else {
- #if DEBUG_PERSISTENT_CONNECTIONS
- NSLog(@"[CONNECTION] Request %@ will not use a persistent connection",self);
- #endif
- }
-
- [connectionsLock unlock];
- // Schedule the stream
- if (![self readStreamIsScheduled] && (!throttleWakeUpTime || [throttleWakeUpTime timeIntervalSinceDate:[NSDate date]] < 0)) {
- [self scheduleReadStream];
- }
-
- BOOL streamSuccessfullyOpened = NO;
- // Start the HTTP connection
- CFStreamClientContext ctxt = {0, self, NULL, NULL, NULL};
- if (CFReadStreamSetClient((CFReadStreamRef)[self readStream], kNetworkEvents, ReadStreamClientCallBack, &ctxt)) {
- if (CFReadStreamOpen((CFReadStreamRef)[self readStream])) {
- streamSuccessfullyOpened = YES;
- }
- }
-
- // Here, we'll close the stream that was previously using this connection, if there was one
- // We've kept it open until now (when we've just opened a new stream) so that the new stream can make use of the old connection
- // http://lists.apple.com/archives/Macnetworkprog/2006/Mar/msg00119.html
- if (oldStream) {
- [oldStream close];
- [oldStream release];
- oldStream = nil;
- }
- if (!streamSuccessfullyOpened) {
- [self setConnectionCanBeReused:NO];
- [self destroyReadStream];
- [self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIInternalErrorWhileBuildingRequestType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Unable to start HTTP connection",NSLocalizedDescriptionKey,nil]]];
- return;
- }
-
- if (![self mainRequest]) {
- if ([self shouldResetUploadProgress]) {
- if ([self showAccurateProgress]) {
- [self incrementUploadSizeBy:[self postLength]];
- } else {
- [self incrementUploadSizeBy:1];
- }
- [ASIHTTPRequest updateProgressIndicator:&uploadProgressDelegate withProgress:0 ofTotal:1];
- }
- if ([self shouldResetDownloadProgress] && ![self partialDownloadSize]) {
- [ASIHTTPRequest updateProgressIndicator:&downloadProgressDelegate withProgress:0 ofTotal:1];
- }
- }
-
-
- // Record when the request started, so we can timeout if nothing happens
- [self setLastActivityTime:[NSDate date]];
- [self setStatusTimer:[NSTimer timerWithTimeInterval:0.25 target:self selector:@selector(updateStatus:) userInfo:nil repeats:YES]];
- [[NSRunLoop currentRunLoop] addTimer:[self statusTimer] forMode:[self runLoopMode]];
- }
- - (void)setStatusTimer:(NSTimer *)timer
- {
- CFRetain(self);
- // We must invalidate t