PageRenderTime 39ms CodeModel.GetById 1ms RepoModel.GetById 1ms app.codeStats 0ms

/WebServiceDemo/WebServiceTest/ASIHTTPRequest.m

https://gitlab.com/praveenvelanati/ios-demo
Objective C | 1398 lines | 1023 code | 245 blank | 130 comment | 199 complexity | b8e6d88211066784f56fe7bdb39b1532 MD5 | raw file
  1. //
  2. // ASIHTTPRequest.m
  3. //
  4. // Created by Ben Copsey on 04/10/2007.
  5. // Copyright 2007-2011 All-Seeing Interactive. All rights reserved.
  6. //
  7. // A guide to the main features is available at:
  8. // http://allseeing-i.com/ASIHTTPRequest
  9. //
  10. // Portions are based on the ImageClient example from Apple:
  11. // See: http://developer.apple.com/samplecode/ImageClient/listing37.html
  12. #import "ASIHTTPRequest.h"
  13. #if TARGET_OS_IPHONE
  14. #import "Reachability.h"
  15. #import "ASIAuthenticationDialog.h"
  16. #import <MobileCoreServices/MobileCoreServices.h>
  17. #else
  18. #import <SystemConfiguration/SystemConfiguration.h>
  19. #endif
  20. #import "ASIInputStream.h"
  21. #import "ASIDataDecompressor.h"
  22. #import "ASIDataCompressor.h"
  23. // Automatically set on build
  24. NSString *ASIHTTPRequestVersion = @"v1.8.1-61 2011-09-19";
  25. static NSString *defaultUserAgent = nil;
  26. NSString* const NetworkRequestErrorDomain = @"ASIHTTPRequestErrorDomain";
  27. static NSString *ASIHTTPRequestRunLoopMode = @"ASIHTTPRequestRunLoopMode";
  28. static const CFOptionFlags kNetworkEvents = kCFStreamEventHasBytesAvailable | kCFStreamEventEndEncountered | kCFStreamEventErrorOccurred;
  29. // In memory caches of credentials, used on when useSessionPersistence is YES
  30. static NSMutableArray *sessionCredentialsStore = nil;
  31. static NSMutableArray *sessionProxyCredentialsStore = nil;
  32. // This lock mediates access to session credentials
  33. static NSRecursiveLock *sessionCredentialsLock = nil;
  34. // We keep track of cookies we have received here so we can remove them from the sharedHTTPCookieStorage later
  35. static NSMutableArray *sessionCookies = nil;
  36. // The number of times we will allow requests to redirect before we fail with a redirection error
  37. const int RedirectionLimit = 5;
  38. // The default number of seconds to use for a timeout
  39. static NSTimeInterval defaultTimeOutSeconds = 10;
  40. static void ReadStreamClientCallBack(CFReadStreamRef readStream, CFStreamEventType type, void *clientCallBackInfo) {
  41. [((ASIHTTPRequest*)clientCallBackInfo) handleNetworkEvent: type];
  42. }
  43. // This lock prevents the operation from being cancelled while it is trying to update the progress, and vice versa
  44. static NSRecursiveLock *progressLock;
  45. static NSError *ASIRequestCancelledError;
  46. static NSError *ASIRequestTimedOutError;
  47. static NSError *ASIAuthenticationError;
  48. static NSError *ASIUnableToCreateRequestError;
  49. static NSError *ASITooMuchRedirectionError;
  50. static NSMutableArray *bandwidthUsageTracker = nil;
  51. static unsigned long averageBandwidthUsedPerSecond = 0;
  52. // These are used for queuing persistent connections on the same connection
  53. // Incremented every time we specify we want a new connection
  54. static unsigned int nextConnectionNumberToCreate = 0;
  55. // An array of connectionInfo dictionaries.
  56. // 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
  57. static NSMutableArray *persistentConnectionsPool = nil;
  58. // Mediates access to the persistent connections pool
  59. static NSRecursiveLock *connectionsLock = nil;
  60. // Each request gets a new id, we store this rather than a ref to the request itself in the connectionInfo dictionary.
  61. // We do this so we don't have to keep the request around while we wait for the connection to expire
  62. static unsigned int nextRequestID = 0;
  63. // Records how much bandwidth all requests combined have used in the last second
  64. static unsigned long bandwidthUsedInLastSecond = 0;
  65. // A date one second in the future from the time it was created
  66. static NSDate *bandwidthMeasurementDate = nil;
  67. // Since throttling variables are shared among all requests, we'll use a lock to mediate access
  68. static NSLock *bandwidthThrottlingLock = nil;
  69. // the maximum number of bytes that can be transmitted in one second
  70. static unsigned long maxBandwidthPerSecond = 0;
  71. // A default figure for throttling bandwidth on mobile devices
  72. unsigned long const ASIWWANBandwidthThrottleAmount = 14800;
  73. #if TARGET_OS_IPHONE
  74. // YES when bandwidth throttling is active
  75. // This flag does not denote whether throttling is turned on - rather whether it is currently in use
  76. // It will be set to NO when throttling was turned on with setShouldThrottleBandwidthForWWAN, but a WI-FI connection is active
  77. static BOOL isBandwidthThrottled = NO;
  78. // When YES, bandwidth will be automatically throttled when using WWAN (3G/Edge/GPRS)
  79. // Wifi will not be throttled
  80. static BOOL shouldThrottleBandwidthForWWANOnly = NO;
  81. #endif
  82. // Mediates access to the session cookies so requests
  83. static NSRecursiveLock *sessionCookiesLock = nil;
  84. // This lock ensures delegates only receive one notification that authentication is required at once
  85. // When using ASIAuthenticationDialogs, it also ensures only one dialog is shown at once
  86. // If a request can't acquire the lock immediately, it means a dialog is being shown or a delegate is handling the authentication challenge
  87. // Once it gets the lock, it will try to look for existing credentials again rather than showing the dialog / notifying the delegate
  88. // This is so it can make use of any credentials supplied for the other request, if they are appropriate
  89. static NSRecursiveLock *delegateAuthenticationLock = nil;
  90. // When throttling bandwidth, Set to a date in future that we will allow all requests to wake up and reschedule their streams
  91. static NSDate *throttleWakeUpTime = nil;
  92. static id <ASICacheDelegate> defaultCache = nil;
  93. // Used for tracking when requests are using the network
  94. static unsigned int runningRequestCount = 0;
  95. // You can use [ASIHTTPRequest setShouldUpdateNetworkActivityIndicator:NO] if you want to manage it yourself
  96. // Alternatively, override showNetworkActivityIndicator / hideNetworkActivityIndicator
  97. // By default this does nothing on Mac OS X, but again override the above methods for a different behaviour
  98. static BOOL shouldUpdateNetworkActivityIndicator = YES;
  99. // The thread all requests will run on
  100. // Hangs around forever, but will be blocked unless there are requests underway
  101. static NSThread *networkThread = nil;
  102. static NSOperationQueue *sharedQueue = nil;
  103. // Private stuff
  104. @interface ASIHTTPRequest ()
  105. - (void)cancelLoad;
  106. - (void)destroyReadStream;
  107. - (void)scheduleReadStream;
  108. - (void)unscheduleReadStream;
  109. - (BOOL)willAskDelegateForCredentials;
  110. - (BOOL)willAskDelegateForProxyCredentials;
  111. - (void)askDelegateForProxyCredentials;
  112. - (void)askDelegateForCredentials;
  113. - (void)failAuthentication;
  114. + (void)measureBandwidthUsage;
  115. + (void)recordBandwidthUsage;
  116. - (void)startRequest;
  117. - (void)updateStatus:(NSTimer *)timer;
  118. - (void)checkRequestStatus;
  119. - (void)reportFailure;
  120. - (void)reportFinished;
  121. - (void)markAsFinished;
  122. - (void)performRedirect;
  123. - (BOOL)shouldTimeOut;
  124. - (BOOL)willRedirect;
  125. - (BOOL)willAskDelegateToConfirmRedirect;
  126. + (void)performInvocation:(NSInvocation *)invocation onTarget:(id *)target releasingObject:(id)objectToRelease;
  127. + (void)hideNetworkActivityIndicatorAfterDelay;
  128. + (void)hideNetworkActivityIndicatorIfNeeeded;
  129. + (void)runRequests;
  130. // Handling Proxy autodetection and PAC file downloads
  131. - (BOOL)configureProxies;
  132. - (void)fetchPACFile;
  133. - (void)finishedDownloadingPACFile:(ASIHTTPRequest *)theRequest;
  134. - (void)runPACScript:(NSString *)script;
  135. - (void)timeOutPACRead;
  136. - (void)useDataFromCache;
  137. // Called to update the size of a partial download when starting a request, or retrying after a timeout
  138. - (void)updatePartialDownloadSize;
  139. #if TARGET_OS_IPHONE
  140. + (void)registerForNetworkReachabilityNotifications;
  141. + (void)unsubscribeFromNetworkReachabilityNotifications;
  142. // Called when the status of the network changes
  143. + (void)reachabilityChanged:(NSNotification *)note;
  144. #endif
  145. #if NS_BLOCKS_AVAILABLE
  146. - (void)performBlockOnMainThread:(ASIBasicBlock)block;
  147. - (void)releaseBlocksOnMainThread;
  148. + (void)releaseBlocks:(NSArray *)blocks;
  149. - (void)callBlock:(ASIBasicBlock)block;
  150. #endif
  151. @property (assign) BOOL complete;
  152. @property (retain) NSArray *responseCookies;
  153. @property (assign) int responseStatusCode;
  154. @property (retain, nonatomic) NSDate *lastActivityTime;
  155. @property (assign) unsigned long long partialDownloadSize;
  156. @property (assign, nonatomic) unsigned long long uploadBufferSize;
  157. @property (retain, nonatomic) NSOutputStream *postBodyWriteStream;
  158. @property (retain, nonatomic) NSInputStream *postBodyReadStream;
  159. @property (assign, nonatomic) unsigned long long lastBytesRead;
  160. @property (assign, nonatomic) unsigned long long lastBytesSent;
  161. @property (retain) NSRecursiveLock *cancelledLock;
  162. @property (retain, nonatomic) NSOutputStream *fileDownloadOutputStream;
  163. @property (retain, nonatomic) NSOutputStream *inflatedFileDownloadOutputStream;
  164. @property (assign) int authenticationRetryCount;
  165. @property (assign) int proxyAuthenticationRetryCount;
  166. @property (assign, nonatomic) BOOL updatedProgress;
  167. @property (assign, nonatomic) BOOL needsRedirect;
  168. @property (assign, nonatomic) int redirectCount;
  169. @property (retain, nonatomic) NSData *compressedPostBody;
  170. @property (retain, nonatomic) NSString *compressedPostBodyFilePath;
  171. @property (retain) NSString *authenticationRealm;
  172. @property (retain) NSString *proxyAuthenticationRealm;
  173. @property (retain) NSString *responseStatusMessage;
  174. @property (assign) BOOL inProgress;
  175. @property (assign) int retryCount;
  176. @property (assign) BOOL willRetryRequest;
  177. @property (assign) BOOL connectionCanBeReused;
  178. @property (retain, nonatomic) NSMutableDictionary *connectionInfo;
  179. @property (retain, nonatomic) NSInputStream *readStream;
  180. @property (assign) ASIAuthenticationState authenticationNeeded;
  181. @property (assign, nonatomic) BOOL readStreamIsScheduled;
  182. @property (assign, nonatomic) BOOL downloadComplete;
  183. @property (retain) NSNumber *requestID;
  184. @property (assign, nonatomic) NSString *runLoopMode;
  185. @property (retain, nonatomic) NSTimer *statusTimer;
  186. @property (assign) BOOL didUseCachedResponse;
  187. @property (retain, nonatomic) NSURL *redirectURL;
  188. @property (assign, nonatomic) BOOL isPACFileRequest;
  189. @property (retain, nonatomic) ASIHTTPRequest *PACFileRequest;
  190. @property (retain, nonatomic) NSInputStream *PACFileReadStream;
  191. @property (retain, nonatomic) NSMutableData *PACFileData;
  192. @property (assign, nonatomic, setter=setSynchronous:) BOOL isSynchronous;
  193. @end
  194. @implementation ASIHTTPRequest
  195. #pragma mark init / dealloc
  196. + (void)initialize
  197. {
  198. if (self == [ASIHTTPRequest class]) {
  199. persistentConnectionsPool = [[NSMutableArray alloc] init];
  200. connectionsLock = [[NSRecursiveLock alloc] init];
  201. progressLock = [[NSRecursiveLock alloc] init];
  202. bandwidthThrottlingLock = [[NSLock alloc] init];
  203. sessionCookiesLock = [[NSRecursiveLock alloc] init];
  204. sessionCredentialsLock = [[NSRecursiveLock alloc] init];
  205. delegateAuthenticationLock = [[NSRecursiveLock alloc] init];
  206. bandwidthUsageTracker = [[NSMutableArray alloc] initWithCapacity:5];
  207. ASIRequestTimedOutError = [[NSError alloc] initWithDomain:NetworkRequestErrorDomain code:ASIRequestTimedOutErrorType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"The request timed out",NSLocalizedDescriptionKey,nil]];
  208. ASIAuthenticationError = [[NSError alloc] initWithDomain:NetworkRequestErrorDomain code:ASIAuthenticationErrorType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Authentication needed",NSLocalizedDescriptionKey,nil]];
  209. ASIRequestCancelledError = [[NSError alloc] initWithDomain:NetworkRequestErrorDomain code:ASIRequestCancelledErrorType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"The request was cancelled",NSLocalizedDescriptionKey,nil]];
  210. ASIUnableToCreateRequestError = [[NSError alloc] initWithDomain:NetworkRequestErrorDomain code:ASIUnableToCreateRequestErrorType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Unable to create request (bad url?)",NSLocalizedDescriptionKey,nil]];
  211. ASITooMuchRedirectionError = [[NSError alloc] initWithDomain:NetworkRequestErrorDomain code:ASITooMuchRedirectionErrorType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"The request failed because it redirected too many times",NSLocalizedDescriptionKey,nil]];
  212. sharedQueue = [[NSOperationQueue alloc] init];
  213. [sharedQueue setMaxConcurrentOperationCount:4];
  214. }
  215. }
  216. - (id)initWithURL:(NSURL *)newURL
  217. {
  218. self = [self init];
  219. [self setRequestMethod:@"GET"];
  220. [self setRunLoopMode:NSDefaultRunLoopMode];
  221. [self setShouldAttemptPersistentConnection:YES];
  222. [self setPersistentConnectionTimeoutSeconds:60.0];
  223. [self setShouldPresentCredentialsBeforeChallenge:YES];
  224. [self setShouldRedirect:YES];
  225. [self setShowAccurateProgress:YES];
  226. [self setShouldResetDownloadProgress:YES];
  227. [self setShouldResetUploadProgress:YES];
  228. [self setAllowCompressedResponse:YES];
  229. [self setShouldWaitToInflateCompressedResponses:YES];
  230. [self setDefaultResponseEncoding:NSISOLatin1StringEncoding];
  231. [self setShouldPresentProxyAuthenticationDialog:YES];
  232. [self setTimeOutSeconds:[ASIHTTPRequest defaultTimeOutSeconds]];
  233. [self setUseSessionPersistence:YES];
  234. [self setUseCookiePersistence:YES];
  235. [self setValidatesSecureCertificate:YES];
  236. [self setRequestCookies:[[[NSMutableArray alloc] init] autorelease]];
  237. [self setDidStartSelector:@selector(requestStarted:)];
  238. [self setDidReceiveResponseHeadersSelector:@selector(request:didReceiveResponseHeaders:)];
  239. [self setWillRedirectSelector:@selector(request:willRedirectToURL:)];
  240. [self setDidFinishSelector:@selector(requestFinished:)];
  241. [self setDidFailSelector:@selector(requestFailed:)];
  242. [self setDidReceiveDataSelector:@selector(request:didReceiveData:)];
  243. [self setURL:newURL];
  244. [self setCancelledLock:[[[NSRecursiveLock alloc] init] autorelease]];
  245. [self setDownloadCache:[[self class] defaultCache]];
  246. return self;
  247. }
  248. + (id)requestWithURL:(NSURL *)newURL
  249. {
  250. return [[[self alloc] initWithURL:newURL] autorelease];
  251. }
  252. + (id)requestWithURL:(NSURL *)newURL usingCache:(id <ASICacheDelegate>)cache
  253. {
  254. return [self requestWithURL:newURL usingCache:cache andCachePolicy:ASIUseDefaultCachePolicy];
  255. }
  256. + (id)requestWithURL:(NSURL *)newURL usingCache:(id <ASICacheDelegate>)cache andCachePolicy:(ASICachePolicy)policy
  257. {
  258. ASIHTTPRequest *request = [[[self alloc] initWithURL:newURL] autorelease];
  259. [request setDownloadCache:cache];
  260. [request setCachePolicy:policy];
  261. return request;
  262. }
  263. - (void)dealloc
  264. {
  265. [self setAuthenticationNeeded:ASINoAuthenticationNeededYet];
  266. if (requestAuthentication) {
  267. CFRelease(requestAuthentication);
  268. }
  269. if (proxyAuthentication) {
  270. CFRelease(proxyAuthentication);
  271. }
  272. if (request) {
  273. CFRelease(request);
  274. }
  275. if (clientCertificateIdentity) {
  276. CFRelease(clientCertificateIdentity);
  277. }
  278. [self cancelLoad];
  279. [redirectURL release];
  280. [statusTimer invalidate];
  281. [statusTimer release];
  282. [queue release];
  283. [userInfo release];
  284. [postBody release];
  285. [compressedPostBody release];
  286. [error release];
  287. [requestHeaders release];
  288. [requestCookies release];
  289. [downloadDestinationPath release];
  290. [temporaryFileDownloadPath release];
  291. [temporaryUncompressedDataDownloadPath release];
  292. [fileDownloadOutputStream release];
  293. [inflatedFileDownloadOutputStream release];
  294. [username release];
  295. [password release];
  296. [domain release];
  297. [authenticationRealm release];
  298. [authenticationScheme release];
  299. [requestCredentials release];
  300. [proxyHost release];
  301. [proxyType release];
  302. [proxyUsername release];
  303. [proxyPassword release];
  304. [proxyDomain release];
  305. [proxyAuthenticationRealm release];
  306. [proxyAuthenticationScheme release];
  307. [proxyCredentials release];
  308. [url release];
  309. [originalURL release];
  310. [lastActivityTime release];
  311. [responseCookies release];
  312. [rawResponseData release];
  313. [responseHeaders release];
  314. [requestMethod release];
  315. [cancelledLock release];
  316. [postBodyFilePath release];
  317. [compressedPostBodyFilePath release];
  318. [postBodyWriteStream release];
  319. [postBodyReadStream release];
  320. [PACurl release];
  321. [clientCertificates release];
  322. [responseStatusMessage release];
  323. [connectionInfo release];
  324. [requestID release];
  325. [dataDecompressor release];
  326. [userAgentString release];
  327. #if NS_BLOCKS_AVAILABLE
  328. [self releaseBlocksOnMainThread];
  329. #endif
  330. [super dealloc];
  331. }
  332. #if NS_BLOCKS_AVAILABLE
  333. - (void)releaseBlocksOnMainThread
  334. {
  335. NSMutableArray *blocks = [NSMutableArray array];
  336. if (completionBlock) {
  337. [blocks addObject:completionBlock];
  338. [completionBlock release];
  339. completionBlock = nil;
  340. }
  341. if (failureBlock) {
  342. [blocks addObject:failureBlock];
  343. [failureBlock release];
  344. failureBlock = nil;
  345. }
  346. if (startedBlock) {
  347. [blocks addObject:startedBlock];
  348. [startedBlock release];
  349. startedBlock = nil;
  350. }
  351. if (headersReceivedBlock) {
  352. [blocks addObject:headersReceivedBlock];
  353. [headersReceivedBlock release];
  354. headersReceivedBlock = nil;
  355. }
  356. if (bytesReceivedBlock) {
  357. [blocks addObject:bytesReceivedBlock];
  358. [bytesReceivedBlock release];
  359. bytesReceivedBlock = nil;
  360. }
  361. if (bytesSentBlock) {
  362. [blocks addObject:bytesSentBlock];
  363. [bytesSentBlock release];
  364. bytesSentBlock = nil;
  365. }
  366. if (downloadSizeIncrementedBlock) {
  367. [blocks addObject:downloadSizeIncrementedBlock];
  368. [downloadSizeIncrementedBlock release];
  369. downloadSizeIncrementedBlock = nil;
  370. }
  371. if (uploadSizeIncrementedBlock) {
  372. [blocks addObject:uploadSizeIncrementedBlock];
  373. [uploadSizeIncrementedBlock release];
  374. uploadSizeIncrementedBlock = nil;
  375. }
  376. if (dataReceivedBlock) {
  377. [blocks addObject:dataReceivedBlock];
  378. [dataReceivedBlock release];
  379. dataReceivedBlock = nil;
  380. }
  381. if (proxyAuthenticationNeededBlock) {
  382. [blocks addObject:proxyAuthenticationNeededBlock];
  383. [proxyAuthenticationNeededBlock release];
  384. proxyAuthenticationNeededBlock = nil;
  385. }
  386. if (authenticationNeededBlock) {
  387. [blocks addObject:authenticationNeededBlock];
  388. [authenticationNeededBlock release];
  389. authenticationNeededBlock = nil;
  390. }
  391. if (requestRedirectedBlock) {
  392. [blocks addObject:requestRedirectedBlock];
  393. [requestRedirectedBlock release];
  394. requestRedirectedBlock = nil;
  395. }
  396. [[self class] performSelectorOnMainThread:@selector(releaseBlocks:) withObject:blocks waitUntilDone:[NSThread isMainThread]];
  397. }
  398. // Always called on main thread
  399. + (void)releaseBlocks:(NSArray *)blocks
  400. {
  401. // Blocks will be released when this method exits
  402. }
  403. #endif
  404. #pragma mark setup request
  405. - (void)addRequestHeader:(NSString *)header value:(NSString *)value
  406. {
  407. if (!requestHeaders) {
  408. [self setRequestHeaders:[NSMutableDictionary dictionaryWithCapacity:1]];
  409. }
  410. [requestHeaders setObject:value forKey:header];
  411. }
  412. // This function will be called either just before a request starts, or when postLength is needed, whichever comes first
  413. // postLength must be set by the time this function is complete
  414. - (void)buildPostBody
  415. {
  416. if ([self haveBuiltPostBody]) {
  417. return;
  418. }
  419. // Are we submitting the request body from a file on disk
  420. if ([self postBodyFilePath]) {
  421. // If we were writing to the post body via appendPostData or appendPostDataFromFile, close the write stream
  422. if ([self postBodyWriteStream]) {
  423. [[self postBodyWriteStream] close];
  424. [self setPostBodyWriteStream:nil];
  425. }
  426. NSString *path;
  427. if ([self shouldCompressRequestBody]) {
  428. if (![self compressedPostBodyFilePath]) {
  429. [self setCompressedPostBodyFilePath:[NSTemporaryDirectory() stringByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]]];
  430. NSError *err = nil;
  431. if (![ASIDataCompressor compressDataFromFile:[self postBodyFilePath] toFile:[self compressedPostBodyFilePath] error:&err]) {
  432. [self failWithError:err];
  433. return;
  434. }
  435. }
  436. path = [self compressedPostBodyFilePath];
  437. } else {
  438. path = [self postBodyFilePath];
  439. }
  440. NSError *err = nil;
  441. [self setPostLength:[[[[[NSFileManager alloc] init] autorelease] attributesOfItemAtPath:path error:&err] fileSize]];
  442. if (err) {
  443. [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]]];
  444. return;
  445. }
  446. // Otherwise, we have an in-memory request body
  447. } else {
  448. if ([self shouldCompressRequestBody]) {
  449. NSError *err = nil;
  450. NSData *compressedBody = [ASIDataCompressor compressData:[self postBody] error:&err];
  451. if (err) {
  452. [self failWithError:err];
  453. return;
  454. }
  455. [self setCompressedPostBody:compressedBody];
  456. [self setPostLength:[[self compressedPostBody] length]];
  457. } else {
  458. [self setPostLength:[[self postBody] length]];
  459. }
  460. }
  461. if ([self postLength] > 0) {
  462. if ([requestMethod isEqualToString:@"GET"] || [requestMethod isEqualToString:@"DELETE"] || [requestMethod isEqualToString:@"HEAD"]) {
  463. [self setRequestMethod:@"POST"];
  464. }
  465. [self addRequestHeader:@"Content-Length" value:[NSString stringWithFormat:@"%llu",[self postLength]]];
  466. }
  467. [self setHaveBuiltPostBody:YES];
  468. }
  469. // Sets up storage for the post body
  470. - (void)setupPostBody
  471. {
  472. if ([self shouldStreamPostDataFromDisk]) {
  473. if (![self postBodyFilePath]) {
  474. [self setPostBodyFilePath:[NSTemporaryDirectory() stringByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]]];
  475. [self setDidCreateTemporaryPostDataFile:YES];
  476. }
  477. if (![self postBodyWriteStream]) {
  478. [self setPostBodyWriteStream:[[[NSOutputStream alloc] initToFileAtPath:[self postBodyFilePath] append:NO] autorelease]];
  479. [[self postBodyWriteStream] open];
  480. }
  481. } else {
  482. if (![self postBody]) {
  483. [self setPostBody:[[[NSMutableData alloc] init] autorelease]];
  484. }
  485. }
  486. }
  487. - (void)appendPostData:(NSData *)data
  488. {
  489. [self setupPostBody];
  490. if ([data length] == 0) {
  491. return;
  492. }
  493. if ([self shouldStreamPostDataFromDisk]) {
  494. [[self postBodyWriteStream] write:[data bytes] maxLength:[data length]];
  495. } else {
  496. [[self postBody] appendData:data];
  497. }
  498. }
  499. - (void)appendPostDataFromFile:(NSString *)file
  500. {
  501. [self setupPostBody];
  502. NSInputStream *stream = [[[NSInputStream alloc] initWithFileAtPath:file] autorelease];
  503. [stream open];
  504. NSUInteger bytesRead;
  505. while ([stream hasBytesAvailable]) {
  506. unsigned char buffer[1024*256];
  507. bytesRead = [stream read:buffer maxLength:sizeof(buffer)];
  508. if (bytesRead == 0) {
  509. break;
  510. }
  511. if ([self shouldStreamPostDataFromDisk]) {
  512. [[self postBodyWriteStream] write:buffer maxLength:bytesRead];
  513. } else {
  514. [[self postBody] appendData:[NSData dataWithBytes:buffer length:bytesRead]];
  515. }
  516. }
  517. [stream close];
  518. }
  519. - (NSString *)requestMethod
  520. {
  521. [[self cancelledLock] lock];
  522. NSString *m = requestMethod;
  523. [[self cancelledLock] unlock];
  524. return m;
  525. }
  526. - (void)setRequestMethod:(NSString *)newRequestMethod
  527. {
  528. [[self cancelledLock] lock];
  529. if (requestMethod != newRequestMethod) {
  530. [requestMethod release];
  531. requestMethod = [newRequestMethod retain];
  532. if ([requestMethod isEqualToString:@"POST"] || [requestMethod isEqualToString:@"PUT"] || [postBody length] || postBodyFilePath) {
  533. [self setShouldAttemptPersistentConnection:NO];
  534. }
  535. }
  536. [[self cancelledLock] unlock];
  537. }
  538. - (NSURL *)url
  539. {
  540. [[self cancelledLock] lock];
  541. NSURL *u = url;
  542. [[self cancelledLock] unlock];
  543. return u;
  544. }
  545. - (void)setURL:(NSURL *)newURL
  546. {
  547. [[self cancelledLock] lock];
  548. if ([newURL isEqual:[self url]]) {
  549. [[self cancelledLock] unlock];
  550. return;
  551. }
  552. [url release];
  553. url = [newURL retain];
  554. if (requestAuthentication) {
  555. CFRelease(requestAuthentication);
  556. requestAuthentication = NULL;
  557. }
  558. if (proxyAuthentication) {
  559. CFRelease(proxyAuthentication);
  560. proxyAuthentication = NULL;
  561. }
  562. if (request) {
  563. CFRelease(request);
  564. request = NULL;
  565. }
  566. [self setRedirectURL:nil];
  567. [[self cancelledLock] unlock];
  568. }
  569. - (id)delegate
  570. {
  571. [[self cancelledLock] lock];
  572. id d = delegate;
  573. [[self cancelledLock] unlock];
  574. return d;
  575. }
  576. - (void)setDelegate:(id)newDelegate
  577. {
  578. [[self cancelledLock] lock];
  579. delegate = newDelegate;
  580. [[self cancelledLock] unlock];
  581. }
  582. - (id)queue
  583. {
  584. [[self cancelledLock] lock];
  585. id q = queue;
  586. [[self cancelledLock] unlock];
  587. return q;
  588. }
  589. - (void)setQueue:(id)newQueue
  590. {
  591. [[self cancelledLock] lock];
  592. if (newQueue != queue) {
  593. [queue release];
  594. queue = [newQueue retain];
  595. }
  596. [[self cancelledLock] unlock];
  597. }
  598. #pragma mark get information about this request
  599. // cancel the request - this must be run on the same thread as the request is running on
  600. - (void)cancelOnRequestThread
  601. {
  602. #if DEBUG_REQUEST_STATUS
  603. ASI_DEBUG_LOG(@"[STATUS] Request cancelled: %@",self);
  604. #endif
  605. [[self cancelledLock] lock];
  606. if ([self isCancelled] || [self complete]) {
  607. [[self cancelledLock] unlock];
  608. return;
  609. }
  610. [self failWithError:ASIRequestCancelledError];
  611. [self setComplete:YES];
  612. [self cancelLoad];
  613. CFRetain(self);
  614. [self willChangeValueForKey:@"isCancelled"];
  615. cancelled = YES;
  616. [self didChangeValueForKey:@"isCancelled"];
  617. [[self cancelledLock] unlock];
  618. CFRelease(self);
  619. }
  620. - (void)cancel
  621. {
  622. [self performSelector:@selector(cancelOnRequestThread) onThread:[[self class] threadForRequest:self] withObject:nil waitUntilDone:NO];
  623. }
  624. - (void)clearDelegatesAndCancel
  625. {
  626. [[self cancelledLock] lock];
  627. // Clear delegates
  628. [self setDelegate:nil];
  629. [self setQueue:nil];
  630. [self setDownloadProgressDelegate:nil];
  631. [self setUploadProgressDelegate:nil];
  632. #if NS_BLOCKS_AVAILABLE
  633. // Clear blocks
  634. [self releaseBlocksOnMainThread];
  635. #endif
  636. [[self cancelledLock] unlock];
  637. [self cancel];
  638. }
  639. - (BOOL)isCancelled
  640. {
  641. BOOL result;
  642. [[self cancelledLock] lock];
  643. result = cancelled;
  644. [[self cancelledLock] unlock];
  645. return result;
  646. }
  647. // Call this method to get the received data as an NSString. Don't use for binary data!
  648. - (NSString *)responseString
  649. {
  650. NSData *data = [self responseData];
  651. if (!data) {
  652. return nil;
  653. }
  654. return [[[NSString alloc] initWithBytes:[data bytes] length:[data length] encoding:[self responseEncoding]] autorelease];
  655. }
  656. - (BOOL)isResponseCompressed
  657. {
  658. NSString *encoding = [[self responseHeaders] objectForKey:@"Content-Encoding"];
  659. return encoding && [encoding rangeOfString:@"gzip"].location != NSNotFound;
  660. }
  661. - (NSData *)responseData
  662. {
  663. if ([self isResponseCompressed] && [self shouldWaitToInflateCompressedResponses]) {
  664. return [ASIDataDecompressor uncompressData:[self rawResponseData] error:NULL];
  665. } else {
  666. return [self rawResponseData];
  667. }
  668. return nil;
  669. }
  670. #pragma mark running a request
  671. - (void)startSynchronous
  672. {
  673. #if DEBUG_REQUEST_STATUS || DEBUG_THROTTLING
  674. ASI_DEBUG_LOG(@"[STATUS] Starting synchronous request %@",self);
  675. #endif
  676. [self setSynchronous:YES];
  677. [self setRunLoopMode:ASIHTTPRequestRunLoopMode];
  678. [self setInProgress:YES];
  679. if (![self isCancelled] && ![self complete]) {
  680. [self main];
  681. while (!complete) {
  682. [[NSRunLoop currentRunLoop] runMode:[self runLoopMode] beforeDate:[NSDate distantFuture]];
  683. }
  684. }
  685. [self setInProgress:NO];
  686. }
  687. - (void)start
  688. {
  689. [self setInProgress:YES];
  690. [self performSelector:@selector(main) onThread:[[self class] threadForRequest:self] withObject:nil waitUntilDone:NO];
  691. }
  692. - (void)startAsynchronous
  693. {
  694. #if DEBUG_REQUEST_STATUS || DEBUG_THROTTLING
  695. ASI_DEBUG_LOG(@"[STATUS] Starting asynchronous request %@",self);
  696. #endif
  697. [sharedQueue addOperation:self];
  698. }
  699. #pragma mark concurrency
  700. - (BOOL)isConcurrent
  701. {
  702. return YES;
  703. }
  704. - (BOOL)isFinished
  705. {
  706. return finished;
  707. }
  708. - (BOOL)isExecuting {
  709. return [self inProgress];
  710. }
  711. #pragma mark request logic
  712. // Create the request
  713. - (void)main
  714. {
  715. @try {
  716. [[self cancelledLock] lock];
  717. #if TARGET_OS_IPHONE && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_4_0
  718. if ([ASIHTTPRequest isMultitaskingSupported] && [self shouldContinueWhenAppEntersBackground]) {
  719. if (!backgroundTask || backgroundTask == UIBackgroundTaskInvalid) {
  720. backgroundTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
  721. // Synchronize the cleanup call on the main thread in case
  722. // the task actually finishes at around the same time.
  723. dispatch_async(dispatch_get_main_queue(), ^{
  724. if (backgroundTask != UIBackgroundTaskInvalid)
  725. {
  726. [[UIApplication sharedApplication] endBackgroundTask:backgroundTask];
  727. backgroundTask = UIBackgroundTaskInvalid;
  728. [self cancel];
  729. }
  730. });
  731. }];
  732. }
  733. }
  734. #endif
  735. // A HEAD request generated by an ASINetworkQueue may have set the error already. If so, we should not proceed.
  736. if ([self error]) {
  737. [self setComplete:YES];
  738. [self markAsFinished];
  739. return;
  740. }
  741. [self setComplete:NO];
  742. [self setDidUseCachedResponse:NO];
  743. if (![self url]) {
  744. [self failWithError:ASIUnableToCreateRequestError];
  745. return;
  746. }
  747. // Must call before we create the request so that the request method can be set if needs be
  748. if (![self mainRequest]) {
  749. [self buildPostBody];
  750. }
  751. if (![[self requestMethod] isEqualToString:@"GET"]) {
  752. [self setDownloadCache:nil];
  753. }
  754. // If we're redirecting, we'll already have a CFHTTPMessageRef
  755. if (request) {
  756. CFRelease(request);
  757. }
  758. // Create a new HTTP request.
  759. request = CFHTTPMessageCreateRequest(kCFAllocatorDefault, (CFStringRef)[self requestMethod], (CFURLRef)[self url], [self useHTTPVersionOne] ? kCFHTTPVersion1_0 : kCFHTTPVersion1_1);
  760. if (!request) {
  761. [self failWithError:ASIUnableToCreateRequestError];
  762. return;
  763. }
  764. //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
  765. if ([self mainRequest]) {
  766. [[self mainRequest] buildRequestHeaders];
  767. }
  768. // 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)
  769. [self buildRequestHeaders];
  770. if ([self downloadCache]) {
  771. // If this request should use the default policy, set its policy to the download cache's default policy
  772. if (![self cachePolicy]) {
  773. [self setCachePolicy:[[self downloadCache] defaultCachePolicy]];
  774. }
  775. // If have have cached data that is valid for this request, use that and stop
  776. if ([[self downloadCache] canUseCachedDataForRequest:self]) {
  777. [self useDataFromCache];
  778. return;
  779. }
  780. // 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
  781. if ([self cachePolicy] & (ASIAskServerIfModifiedWhenStaleCachePolicy|ASIAskServerIfModifiedCachePolicy)) {
  782. NSDictionary *cachedHeaders = [[self downloadCache] cachedResponseHeadersForURL:[self url]];
  783. if (cachedHeaders) {
  784. NSString *etag = [cachedHeaders objectForKey:@"Etag"];
  785. if (etag) {
  786. [[self requestHeaders] setObject:etag forKey:@"If-None-Match"];
  787. }
  788. NSString *lastModified = [cachedHeaders objectForKey:@"Last-Modified"];
  789. if (lastModified) {
  790. [[self requestHeaders] setObject:lastModified forKey:@"If-Modified-Since"];
  791. }
  792. }
  793. }
  794. }
  795. [self applyAuthorizationHeader];
  796. NSString *header;
  797. for (header in [self requestHeaders]) {
  798. CFHTTPMessageSetHeaderFieldValue(request, (CFStringRef)header, (CFStringRef)[[self requestHeaders] objectForKey:header]);
  799. }
  800. // If we immediately have access to proxy settings, start the request
  801. // Otherwise, we'll start downloading the proxy PAC file, and call startRequest once that process is complete
  802. if ([self configureProxies]) {
  803. [self startRequest];
  804. }
  805. } @catch (NSException *exception) {
  806. NSError *underlyingError = [NSError errorWithDomain:NetworkRequestErrorDomain code:ASIUnhandledExceptionError userInfo:[exception userInfo]];
  807. [self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIUnhandledExceptionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[exception name],NSLocalizedDescriptionKey,[exception reason],NSLocalizedFailureReasonErrorKey,underlyingError,NSUnderlyingErrorKey,nil]]];
  808. } @finally {
  809. [[self cancelledLock] unlock];
  810. }
  811. }
  812. - (void)applyAuthorizationHeader
  813. {
  814. // Do we want to send credentials before we are asked for them?
  815. if (![self shouldPresentCredentialsBeforeChallenge]) {
  816. #if DEBUG_HTTP_AUTHENTICATION
  817. ASI_DEBUG_LOG(@"[AUTH] Request %@ will not send credentials to the server until it asks for them",self);
  818. #endif
  819. return;
  820. }
  821. NSDictionary *credentials = nil;
  822. // Do we already have an auth header?
  823. if (![[self requestHeaders] objectForKey:@"Authorization"]) {
  824. // If we have basic authentication explicitly set and a username and password set on the request, add a basic auth header
  825. if ([self username] && [self password] && [[self authenticationScheme] isEqualToString:(NSString *)kCFHTTPAuthenticationSchemeBasic]) {
  826. [self addBasicAuthenticationHeaderWithUsername:[self username] andPassword:[self password]];
  827. #if DEBUG_HTTP_AUTHENTICATION
  828. ASI_DEBUG_LOG(@"[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);
  829. #endif
  830. } else {
  831. // See if we have any cached credentials we can use in the session store
  832. if ([self useSessionPersistence]) {
  833. credentials = [self findSessionAuthenticationCredentials];
  834. if (credentials) {
  835. // When the Authentication key is set, the credentials were stored after an authentication challenge, so we can let CFNetwork apply them
  836. // (credentials for Digest and NTLM will always be stored like this)
  837. if ([credentials objectForKey:@"Authentication"]) {
  838. // If we've already talked to this server and have valid credentials, let's apply them to the request
  839. if (CFHTTPMessageApplyCredentialDictionary(request, (CFHTTPAuthenticationRef)[credentials objectForKey:@"Authentication"], (CFDictionaryRef)[credentials objectForKey:@"Credentials"], NULL)) {
  840. [self setAuthenticationScheme:[credentials objectForKey:@"AuthenticationScheme"]];
  841. #if DEBUG_HTTP_AUTHENTICATION
  842. ASI_DEBUG_LOG(@"[AUTH] Request %@ found cached credentials (%@), will reuse without waiting for an authentication challenge",self,[credentials objectForKey:@"AuthenticationScheme"]);
  843. #endif
  844. } else {
  845. [[self class] removeAuthenticationCredentialsFromSessionStore:[credentials objectForKey:@"Credentials"]];
  846. #if DEBUG_HTTP_AUTHENTICATION
  847. ASI_DEBUG_LOG(@"[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);
  848. #endif
  849. }
  850. // If the Authentication key is not set, these credentials were stored after a username and password set on a previous request passed basic authentication
  851. // When this happens, we'll need to create the Authorization header ourselves
  852. } else {
  853. NSDictionary *usernameAndPassword = [credentials objectForKey:@"Credentials"];
  854. [self addBasicAuthenticationHeaderWithUsername:[usernameAndPassword objectForKey:(NSString *)kCFHTTPAuthenticationUsername] andPassword:[usernameAndPassword objectForKey:(NSString *)kCFHTTPAuthenticationPassword]];
  855. #if DEBUG_HTTP_AUTHENTICATION
  856. ASI_DEBUG_LOG(@"[AUTH] Request %@ found cached BASIC credentials from a previous request. Will send credentials without waiting for an authentication challenge",self);
  857. #endif
  858. }
  859. }
  860. }
  861. }
  862. }
  863. // Apply proxy authentication credentials
  864. if ([self useSessionPersistence]) {
  865. credentials = [self findSessionProxyAuthenticationCredentials];
  866. if (credentials) {
  867. if (!CFHTTPMessageApplyCredentialDictionary(request, (CFHTTPAuthenticationRef)[credentials objectForKey:@"Authentication"], (CFDictionaryRef)[credentials objectForKey:@"Credentials"], NULL)) {
  868. [[self class] removeProxyAuthenticationCredentialsFromSessionStore:[credentials objectForKey:@"Credentials"]];
  869. }
  870. }
  871. }
  872. }
  873. - (void)applyCookieHeader
  874. {
  875. // Add cookies from the persistent (mac os global) store
  876. if ([self useCookiePersistence]) {
  877. NSArray *cookies = [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookiesForURL:[[self url] absoluteURL]];
  878. if (cookies) {
  879. [[self requestCookies] addObjectsFromArray:cookies];
  880. }
  881. }
  882. // Apply request cookies
  883. NSArray *cookies;
  884. if ([self mainRequest]) {
  885. cookies = [[self mainRequest] requestCookies];
  886. } else {
  887. cookies = [self requestCookies];
  888. }
  889. if ([cookies count] > 0) {
  890. NSHTTPCookie *cookie;
  891. NSString *cookieHeader = nil;
  892. for (cookie in cookies) {
  893. if (!cookieHeader) {
  894. cookieHeader = [NSString stringWithFormat: @"%@=%@",[cookie name],[cookie value]];
  895. } else {
  896. cookieHeader = [NSString stringWithFormat: @"%@; %@=%@",cookieHeader,[cookie name],[cookie value]];
  897. }
  898. }
  899. if (cookieHeader) {
  900. [self addRequestHeader:@"Cookie" value:cookieHeader];
  901. }
  902. }
  903. }
  904. - (void)buildRequestHeaders
  905. {
  906. if ([self haveBuiltRequestHeaders]) {
  907. return;
  908. }
  909. [self setHaveBuiltRequestHeaders:YES];
  910. if ([self mainRequest]) {
  911. for (NSString *header in [[self mainRequest] requestHeaders]) {
  912. [self addRequestHeader:header value:[[[self mainRequest] requestHeaders] valueForKey:header]];
  913. }
  914. return;
  915. }
  916. [self applyCookieHeader];
  917. // Build and set the user agent string if the request does not already have a custom user agent specified
  918. if (![[self requestHeaders] objectForKey:@"User-Agent"]) {
  919. NSString *tempUserAgentString = [self userAgentString];
  920. if (!tempUserAgentString) {
  921. tempUserAgentString = [ASIHTTPRequest defaultUserAgentString];
  922. }
  923. if (tempUserAgentString) {
  924. [self addRequestHeader:@"User-Agent" value:tempUserAgentString];
  925. }
  926. }
  927. // Accept a compressed response
  928. if ([self allowCompressedResponse]) {
  929. [self addRequestHeader:@"Accept-Encoding" value:@"gzip"];
  930. }
  931. // Configure a compressed request body
  932. if ([self shouldCompressRequestBody]) {
  933. [self addRequestHeader:@"Content-Encoding" value:@"gzip"];
  934. }
  935. // Should this request resume an existing download?
  936. [self updatePartialDownloadSize];
  937. if ([self partialDownloadSize]) {
  938. [self addRequestHeader:@"Range" value:[NSString stringWithFormat:@"bytes=%llu-",[self partialDownloadSize]]];
  939. }
  940. }
  941. - (void)updatePartialDownloadSize
  942. {
  943. NSFileManager *fileManager = [[[NSFileManager alloc] init] autorelease];
  944. if ([self allowResumeForFileDownloads] && [self downloadDestinationPath] && [self temporaryFileDownloadPath] && [fileManager fileExistsAtPath:[self temporaryFileDownloadPath]]) {
  945. NSError *err = nil;
  946. [self setPartialDownloadSize:[[fileManager attributesOfItemAtPath:[self temporaryFileDownloadPath] error:&err] fileSize]];
  947. if (err) {
  948. [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]]];
  949. return;
  950. }
  951. }
  952. }
  953. - (void)startRequest
  954. {
  955. if ([self isCancelled]) {
  956. return;
  957. }
  958. [self performSelectorOnMainThread:@selector(requestStarted) withObject:nil waitUntilDone:[NSThread isMainThread]];
  959. [self setDownloadComplete:NO];
  960. [self setComplete:NO];
  961. [self setTotalBytesRead:0];
  962. [self setLastBytesRead:0];
  963. if ([self redirectCount] == 0) {
  964. [self setOriginalURL:[self url]];
  965. }
  966. // If we're retrying a request, let's remove any progress we made
  967. if ([self lastBytesSent] > 0) {
  968. [self removeUploadProgressSoFar];
  969. }
  970. [self setLastBytesSent:0];
  971. [self setContentLength:0];
  972. [self setResponseHeaders:nil];
  973. if (![self downloadDestinationPath]) {
  974. [self setRawResponseData:[[[NSMutableData alloc] init] autorelease]];
  975. }
  976. //
  977. // Create the stream for the request
  978. //
  979. NSFileManager *fileManager = [[[NSFileManager alloc] init] autorelease];
  980. [self setReadStreamIsScheduled:NO];
  981. // Do we need to stream the request body from disk
  982. if ([self shouldStreamPostDataFromDisk] && [self postBodyFilePath] && [fileManager fileExistsAtPath:[self postBodyFilePath]]) {
  983. // Are we gzipping the request body?
  984. if ([self compressedPostBodyFilePath] && [fileManager fileExistsAtPath:[self compressedPostBodyFilePath]]) {
  985. [self setPostBodyReadStream:[ASIInputStream inputStreamWithFileAtPath:[self compressedPostBodyFilePath] request:self]];
  986. } else {
  987. [self setPostBodyReadStream:[ASIInputStream inputStreamWithFileAtPath:[self postBodyFilePath] request:self]];
  988. }
  989. [self setReadStream:[NSMakeCollectable(CFReadStreamCreateForStreamedHTTPRequest(kCFAllocatorDefault, request,(CFReadStreamRef)[self postBodyReadStream])) autorelease]];
  990. } else {
  991. // 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
  992. if ([self postBody] && [[self postBody] length] > 0) {
  993. if ([self shouldCompressRequestBody] && [self compressedPostBody]) {
  994. [self setPostBodyReadStream:[ASIInputStream inputStreamWithData:[self compressedPostBody] request:self]];
  995. } else if ([self postBody]) {
  996. [self setPostBodyReadStream:[ASIInputStream inputStreamWithData:[self postBody] request:self]];
  997. }
  998. [self setReadStream:[NSMakeCollectable(CFReadStreamCreateForStreamedHTTPRequest(kCFAllocatorDefault, request,(CFReadStreamRef)[self postBodyReadStream])) autorelease]];
  999. } else {
  1000. [self setReadStream:[NSMakeCollectable(CFReadStreamCreateForHTTPRequest(kCFAllocatorDefault, request)) autorelease]];
  1001. }
  1002. }
  1003. if (![self readStream]) {
  1004. [self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIInternalErrorWhileBuildingRequestType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Unable to create read stream",NSLocalizedDescriptionKey,nil]]];
  1005. return;
  1006. }
  1007. //
  1008. // Handle SSL certificate settings
  1009. //
  1010. if([[[[self url] scheme] lowercaseString] isEqualToString:@"https"]) {
  1011. // Tell CFNetwork not to validate SSL certificates
  1012. if (![self validatesSecureCertificate]) {
  1013. // see: http://iphonedevelopment.blogspot.com/2010/05/nsstream-tcp-and-ssl.html
  1014. NSDictionary *sslProperties = [[NSDictionary alloc] initWithObjectsAndKeys:
  1015. [NSNumber numberWithBool:YES], kCFStreamSSLAllowsExpiredCertificates,
  1016. [NSNumber numberWithBool:YES], kCFStreamSSLAllowsAnyRoot,
  1017. [NSNumber numberWithBool:NO], kCFStreamSSLValidatesCertificateChain,
  1018. kCFNull,kCFStreamSSLPeerName,
  1019. nil];
  1020. CFReadStreamSetProperty((CFReadStreamRef)[self readStream],
  1021. kCFStreamPropertySSLSettings,
  1022. (CFTypeRef)sslProperties);
  1023. [sslProperties release];
  1024. }
  1025. // Tell CFNetwork to use a client certificate
  1026. if (clientCertificateIdentity) {
  1027. NSMutableDictionary *sslProperties = [NSMutableDictionary dictionaryWithCapacity:1];
  1028. NSMutableArray *certificates = [NSMutableArray arrayWithCapacity:[clientCertificates count]+1];
  1029. // The first object in the array is our SecIdentityRef
  1030. [certificates addObject:(id)clientCertificateIdentity];
  1031. // If we've added any additional certificates, add them too
  1032. for (id cert in clientCertificates) {
  1033. [certificates addObject:cert];
  1034. }
  1035. [sslProperties setObject:certificates forKey:(NSString *)kCFStreamSSLCertificates];
  1036. CFReadStreamSetProperty((CFReadStreamRef)[self readStream], kCFStreamPropertySSLSettings, sslProperties);
  1037. }
  1038. }
  1039. //
  1040. // Handle proxy settings
  1041. //
  1042. if ([self proxyHost] && [self proxyPort]) {
  1043. NSString *hostKey;
  1044. NSString *portKey;
  1045. if (![self proxyType]) {
  1046. [self setProxyType:(NSString *)kCFProxyTypeHTTP];
  1047. }
  1048. if ([[self proxyType] isEqualToString:(NSString *)kCFProxyTypeSOCKS]) {
  1049. hostKey = (NSString *)kCFStreamPropertySOCKSProxyHost;
  1050. portKey = (NSString *)kCFStreamPropertySOCKSProxyPort;
  1051. } else {
  1052. hostKey = (NSString *)kCFStreamPropertyHTTPProxyHost;
  1053. portKey = (NSString *)kCFStreamPropertyHTTPProxyPort;
  1054. if ([[[[self url] scheme] lowercaseString] isEqualToString:@"https"]) {
  1055. hostKey = (NSString *)kCFStreamPropertyHTTPSProxyHost;
  1056. portKey = (NSString *)kCFStreamPropertyHTTPSProxyPort;
  1057. }
  1058. }
  1059. NSMutableDictionary *proxyToUse = [NSMutableDictionary dictionaryWithObjectsAndKeys:[self proxyHost],hostKey,[NSNumber numberWithInt:[self proxyPort]],portKey,nil];
  1060. if ([[self proxyType] isEqualToString:(NSString *)kCFProxyTypeSOCKS]) {
  1061. CFReadStreamSetProperty((CFReadStreamRef)[self readStream], kCFStreamPropertySOCKSProxy, proxyToUse);
  1062. } else {
  1063. CFReadStreamSetProperty((CFReadStreamRef)[self readStream], kCFStreamPropertyHTTPProxy, proxyToUse);
  1064. }
  1065. }
  1066. //
  1067. // Handle persistent connections
  1068. //
  1069. [ASIHTTPRequest expirePersistentConnections];
  1070. [connectionsLock lock];
  1071. if (![[self url] host] || ![[self url] scheme]) {
  1072. [self setConnectionInfo:nil];
  1073. [self setShouldAttemptPersistentConnection:NO];
  1074. }
  1075. // 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
  1076. NSInputStream *oldStream = nil;
  1077. // Use a persistent connection if possible
  1078. if ([self shouldAttemptPersistentConnection]) {
  1079. // If we are redirecting, we will re-use the current connection only if we are connecting to the same server
  1080. if ([self connectionInfo]) {
  1081. 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]) {
  1082. [self setConnectionInfo:nil];
  1083. // Check if we should have expired this connection
  1084. } else if ([[[self connectionInfo] objectForKey:@"expires"] timeIntervalSinceNow] < 0) {
  1085. #if DEBUG_PERSISTENT_CONNECTIONS
  1086. ASI_DEBUG_LOG(@"[CONNECTION] Not re-using connection #%i because it has expired",[[[self connectionInfo] objectForKey:@"id"] intValue]);
  1087. #endif
  1088. [persistentConnectionsPool removeObject:[self connectionInfo]];
  1089. [self setConnectionInfo:nil];
  1090. } else if ([[self connectionInfo] objectForKey:@"request"] != nil) {
  1091. //Some other request reused this connection already - we'll have to create a new one
  1092. #if DEBUG_PERSISTENT_CONNECTIONS
  1093. ASI_DEBUG_LOG(@"%@ - 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]);
  1094. #endif
  1095. [self setConnectionInfo:nil];
  1096. }
  1097. }
  1098. if (![self connectionInfo] && [[self url] host] && [[self url] scheme]) { // We must have a proper url with a host and scheme, or this will explode
  1099. // Look for a connection to the same server in the pool
  1100. for (NSMutableDictionary *existingConnection in persistentConnectionsPool) {
  1101. 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]) {
  1102. [self setConnectionInfo:existingConnection];
  1103. }
  1104. }
  1105. }
  1106. if ([[self connectionInfo] objectForKey:@"stream"]) {
  1107. oldStream = [[[self connectionInfo] objectForKey:@"stream"] retain];
  1108. }
  1109. // 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
  1110. if (![self connectionInfo]) {
  1111. [self setConnectionInfo:[NSMutableDictionary dictionary]];
  1112. nextConnectionNumberToCreate++;
  1113. [[self connectionInfo] setObject:[NSNumber numberWithInt:nextConnectionNumberToCreate] forKey:@"id"];
  1114. [[self connectionInfo] setObject:[[self url] host] forKey:@"host"];
  1115. [[self connectionInfo] setObject:[NSNumber numberWithInt:[[[self url] port] intValue]] forKey:@"port"];
  1116. [[self connectionInfo] setObject:[[self url] scheme] forKey:@"scheme"];
  1117. [persistentConnectionsPool addObject:[self connectionInfo]];
  1118. }
  1119. // If we are retrying this request, it will already have a requestID
  1120. if (![self requestID]) {
  1121. nextRequestID++;
  1122. [self setRequestID:[NSNumber numberWithUnsignedInt:nextRequestID]];
  1123. }
  1124. [[self connectionInfo] setObject:[self requestID] forKey:@"request"];
  1125. [[self connectionInfo] setObject:[self readStream] forKey:@"stream"];
  1126. CFReadStreamSetProperty((CFReadStreamRef)[self readStream], kCFStreamPropertyHTTPAttemptPersistentConnection, kCFBooleanTrue);
  1127. #if DEBUG_PERSISTENT_CONNECTIONS
  1128. ASI_DEBUG_LOG(@"[CONNECTION] Request #%@ will use connection #%i",[self requestID],[[[self connectionInfo] objectForKey:@"id"] intValue]);
  1129. #endif
  1130. // Tag the stream with an id that tells it which connection to use behind the scenes
  1131. // See http://lists.apple.com/archives/macnetworkprog/2008/Dec/msg00001.html for details on this approach
  1132. CFReadStreamSetProperty((CFReadStreamRef)[self readStream], CFSTR("ASIStreamID"), [[self connectionInfo] objectForKey:@"id"]);
  1133. } else {
  1134. #if DEBUG_PERSISTENT_CONNECTIONS
  1135. ASI_DEBUG_LOG(@"[CONNECTION] Request %@ will not use a persistent connection",self);
  1136. #endif
  1137. }
  1138. [connectionsLock unlock];
  1139. // Schedule the stream
  1140. if (![self readStreamIsScheduled] && (!throttleWakeUpTime || [throttleWakeUpTime timeIntervalSinceDate:[NSDate date]] < 0)) {
  1141. [self scheduleReadStream];
  1142. }
  1143. BOOL streamSuccessfullyOpened = NO;
  1144. // Start the HTTP connection
  1145. CFStreamClientContext ctxt = {0, self, NULL, NULL, NULL};
  1146. if (CFReadStreamSetClient((CFReadStreamRef)[self readStream], kNetworkEvents, ReadStreamClientCallBack, &ctxt)) {
  1147. if (CFReadStreamOpen((CFReadStreamRef)[self readStream])) {
  1148. streamSuccessfullyOpened = YES;
  1149. }
  1150. }
  1151. // Here, we'll close the stream that was previously using this connection, if there was one
  1152. // 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
  1153. // http://lists.apple.com/