HTTPAsyncFileResponse.m 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431
  1. #import "HTTPAsyncFileResponse.h"
  2. #import "HTTPConnection.h"
  3. #import "HTTPLogging.h"
  4. #import <unistd.h>
  5. #import <fcntl.h>
  6. #if ! __has_feature(objc_arc)
  7. #warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
  8. #endif
  9. /**
  10. * Does ARC support support GCD objects?
  11. * It does if the minimum deployment target is iOS 6+ or Mac OS X 8+
  12. **/
  13. #if TARGET_OS_IPHONE
  14. // Compiling for iOS
  15. #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 60000 // iOS 6.0 or later
  16. #define NEEDS_DISPATCH_RETAIN_RELEASE 0
  17. #else // iOS 5.X or earlier
  18. #define NEEDS_DISPATCH_RETAIN_RELEASE 1
  19. #endif
  20. #else
  21. // Compiling for Mac OS X
  22. #if MAC_OS_X_VERSION_MIN_REQUIRED >= 1080 // Mac OS X 10.8 or later
  23. #define NEEDS_DISPATCH_RETAIN_RELEASE 0
  24. #else
  25. #define NEEDS_DISPATCH_RETAIN_RELEASE 1 // Mac OS X 10.7 or earlier
  26. #endif
  27. #endif
  28. // Log levels : off, error, warn, info, verbose
  29. // Other flags: trace
  30. static const DDLogLevel httpLogLevel = DDLogLevelWarning; // | HTTP_LOG_FLAG_TRACE;
  31. #define NULL_FD -1
  32. /**
  33. * Architecure overview:
  34. *
  35. * HTTPConnection will invoke our readDataOfLength: method to fetch data.
  36. * We will return nil, and then proceed to read the data via our readSource on our readQueue.
  37. * Once the requested amount of data has been read, we then pause our readSource,
  38. * and inform the connection of the available data.
  39. *
  40. * While our read is in progress, we don't have to worry about the connection calling any other methods,
  41. * except the connectionDidClose method, which would be invoked if the remote end closed the socket connection.
  42. * To safely handle this, we do a synchronous dispatch on the readQueue,
  43. * and nilify the connection as well as cancel our readSource.
  44. *
  45. * In order to minimize resource consumption during a HEAD request,
  46. * we don't open the file until we have to (until the connection starts requesting data).
  47. **/
  48. @implementation HTTPAsyncFileResponse
  49. - (id)initWithFilePath:(NSString *)fpath forConnection:(HTTPConnection *)parent
  50. {
  51. if ((self = [super init]))
  52. {
  53. HTTPLogTrace();
  54. connection = parent; // Parents retain children, children do NOT retain parents
  55. fileFD = NULL_FD;
  56. filePath = [fpath copy];
  57. if (filePath == nil)
  58. {
  59. HTTPLogWarn(@"%@: Init failed - Nil filePath", THIS_FILE);
  60. return nil;
  61. }
  62. NSDictionary *fileAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:NULL];
  63. if (fileAttributes == nil)
  64. {
  65. HTTPLogWarn(@"%@: Init failed - Unable to get file attributes. filePath: %@", THIS_FILE, filePath);
  66. return nil;
  67. }
  68. fileLength = (UInt64)[[fileAttributes objectForKey:NSFileSize] unsignedLongLongValue];
  69. fileOffset = 0;
  70. aborted = NO;
  71. // We don't bother opening the file here.
  72. // If this is a HEAD request we only need to know the fileLength.
  73. }
  74. return self;
  75. }
  76. - (void)abort
  77. {
  78. HTTPLogTrace();
  79. [connection responseDidAbort:self];
  80. aborted = YES;
  81. }
  82. - (void)processReadBuffer
  83. {
  84. // This method is here to allow superclasses to perform post-processing of the data.
  85. // For an example, see the HTTPDynamicFileResponse class.
  86. //
  87. // At this point, the readBuffer has readBufferOffset bytes available.
  88. // This method is in charge of updating the readBufferOffset.
  89. // Failure to do so will cause the readBuffer to grow to fileLength. (Imagine a 1 GB file...)
  90. // Copy the data out of the temporary readBuffer.
  91. data = [[NSData alloc] initWithBytes:readBuffer length:readBufferOffset];
  92. // Reset the read buffer.
  93. readBufferOffset = 0;
  94. // Notify the connection that we have data available for it.
  95. [connection responseHasAvailableData:self];
  96. }
  97. - (void)pauseReadSource
  98. {
  99. if (!readSourceSuspended)
  100. {
  101. HTTPLogVerbose(@"%@[%p]: Suspending readSource", THIS_FILE, self);
  102. readSourceSuspended = YES;
  103. dispatch_suspend(readSource);
  104. }
  105. }
  106. - (void)resumeReadSource
  107. {
  108. if (readSourceSuspended)
  109. {
  110. HTTPLogVerbose(@"%@[%p]: Resuming readSource", THIS_FILE, self);
  111. readSourceSuspended = NO;
  112. dispatch_resume(readSource);
  113. }
  114. }
  115. - (void)cancelReadSource
  116. {
  117. HTTPLogVerbose(@"%@[%p]: Canceling readSource", THIS_FILE, self);
  118. dispatch_source_cancel(readSource);
  119. // Cancelling a dispatch source doesn't
  120. // invoke the cancel handler if the dispatch source is paused.
  121. if (readSourceSuspended)
  122. {
  123. readSourceSuspended = NO;
  124. dispatch_resume(readSource);
  125. }
  126. }
  127. - (BOOL)openFileAndSetupReadSource
  128. {
  129. HTTPLogTrace();
  130. fileFD = open([filePath UTF8String], (O_RDONLY | O_NONBLOCK));
  131. if (fileFD == NULL_FD)
  132. {
  133. HTTPLogError(@"%@: Unable to open file. filePath: %@", THIS_FILE, filePath);
  134. return NO;
  135. }
  136. HTTPLogVerbose(@"%@[%p]: Open fd[%i] -> %@", THIS_FILE, self, fileFD, filePath);
  137. readQueue = dispatch_queue_create("HTTPAsyncFileResponse", NULL);
  138. readSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, fileFD, 0, readQueue);
  139. dispatch_source_set_event_handler(readSource, ^{
  140. HTTPLogTrace2(@"%@: eventBlock - fd[%i]", THIS_FILE, fileFD);
  141. // Determine how much data we should read.
  142. //
  143. // It is OK if we ask to read more bytes than exist in the file.
  144. // It is NOT OK to over-allocate the buffer.
  145. unsigned long long _bytesAvailableOnFD = dispatch_source_get_data(readSource);
  146. UInt64 _bytesLeftInFile = fileLength - readOffset;
  147. NSUInteger bytesAvailableOnFD;
  148. NSUInteger bytesLeftInFile;
  149. bytesAvailableOnFD = (_bytesAvailableOnFD > NSUIntegerMax) ? NSUIntegerMax : (NSUInteger)_bytesAvailableOnFD;
  150. bytesLeftInFile = (_bytesLeftInFile > NSUIntegerMax) ? NSUIntegerMax : (NSUInteger)_bytesLeftInFile;
  151. NSUInteger bytesLeftInRequest = readRequestLength - readBufferOffset;
  152. NSUInteger bytesLeft = MIN(bytesLeftInRequest, bytesLeftInFile);
  153. NSUInteger bytesToRead = MIN(bytesAvailableOnFD, bytesLeft);
  154. // Make sure buffer is big enough for read request.
  155. // Do not over-allocate.
  156. if (readBuffer == NULL || bytesToRead > (readBufferSize - readBufferOffset))
  157. {
  158. readBufferSize = bytesToRead;
  159. readBuffer = reallocf(readBuffer, (size_t)bytesToRead);
  160. if (readBuffer == NULL)
  161. {
  162. HTTPLogError(@"%@[%p]: Unable to allocate buffer", THIS_FILE, self);
  163. [self pauseReadSource];
  164. [self abort];
  165. return;
  166. }
  167. }
  168. // Perform the read
  169. HTTPLogVerbose(@"%@[%p]: Attempting to read %lu bytes from file", THIS_FILE, self, (unsigned long)bytesToRead);
  170. ssize_t result = read(fileFD, readBuffer + readBufferOffset, (size_t)bytesToRead);
  171. // Check the results
  172. if (result < 0)
  173. {
  174. HTTPLogError(@"%@: Error(%i) reading file(%@)", THIS_FILE, errno, filePath);
  175. [self pauseReadSource];
  176. [self abort];
  177. }
  178. else if (result == 0)
  179. {
  180. HTTPLogError(@"%@: Read EOF on file(%@)", THIS_FILE, filePath);
  181. [self pauseReadSource];
  182. [self abort];
  183. }
  184. else // (result > 0)
  185. {
  186. HTTPLogVerbose(@"%@[%p]: Read %lu bytes from file", THIS_FILE, self, (unsigned long)result);
  187. readOffset += result;
  188. readBufferOffset += result;
  189. [self pauseReadSource];
  190. [self processReadBuffer];
  191. }
  192. });
  193. int theFileFD = fileFD;
  194. #if NEEDS_DISPATCH_RETAIN_RELEASE
  195. dispatch_source_t theReadSource = readSource;
  196. #endif
  197. dispatch_source_set_cancel_handler(readSource, ^{
  198. // Do not access self from within this block in any way, shape or form.
  199. //
  200. // Note: You access self if you reference an iVar.
  201. HTTPLogTrace2(@"%@: cancelBlock - Close fd[%i]", THIS_FILE, theFileFD);
  202. #if NEEDS_DISPATCH_RETAIN_RELEASE
  203. dispatch_release(theReadSource);
  204. #endif
  205. close(theFileFD);
  206. });
  207. readSourceSuspended = YES;
  208. return YES;
  209. }
  210. - (BOOL)openFileIfNeeded
  211. {
  212. if (aborted)
  213. {
  214. // The file operation has been aborted.
  215. // This could be because we failed to open the file,
  216. // or the reading process failed.
  217. return NO;
  218. }
  219. if (fileFD != NULL_FD)
  220. {
  221. // File has already been opened.
  222. return YES;
  223. }
  224. return [self openFileAndSetupReadSource];
  225. }
  226. - (UInt64)contentLength
  227. {
  228. HTTPLogTrace2(@"%@[%p]: contentLength - %llu", THIS_FILE, self, fileLength);
  229. return fileLength;
  230. }
  231. - (UInt64)offset
  232. {
  233. HTTPLogTrace();
  234. return fileOffset;
  235. }
  236. - (void)setOffset:(UInt64)offset
  237. {
  238. HTTPLogTrace2(@"%@[%p]: setOffset:%llu", THIS_FILE, self, offset);
  239. if (![self openFileIfNeeded])
  240. {
  241. // File opening failed,
  242. // or response has been aborted due to another error.
  243. return;
  244. }
  245. fileOffset = offset;
  246. readOffset = offset;
  247. off_t result = lseek(fileFD, (off_t)offset, SEEK_SET);
  248. if (result == -1)
  249. {
  250. HTTPLogError(@"%@[%p]: lseek failed - errno(%i) filePath(%@)", THIS_FILE, self, errno, filePath);
  251. [self abort];
  252. }
  253. }
  254. - (NSData *)readDataOfLength:(NSUInteger)length
  255. {
  256. HTTPLogTrace2(@"%@[%p]: readDataOfLength:%lu", THIS_FILE, self, (unsigned long)length);
  257. if (data)
  258. {
  259. NSUInteger dataLength = [data length];
  260. HTTPLogVerbose(@"%@[%p]: Returning data of length %lu", THIS_FILE, self, (unsigned long)dataLength);
  261. fileOffset += dataLength;
  262. NSData *result = data;
  263. data = nil;
  264. return result;
  265. }
  266. else
  267. {
  268. if (![self openFileIfNeeded])
  269. {
  270. // File opening failed,
  271. // or response has been aborted due to another error.
  272. return nil;
  273. }
  274. dispatch_sync(readQueue, ^{
  275. NSAssert(readSourceSuspended, @"Invalid logic - perhaps HTTPConnection has changed.");
  276. readRequestLength = length;
  277. [self resumeReadSource];
  278. });
  279. return nil;
  280. }
  281. }
  282. - (BOOL)isDone
  283. {
  284. BOOL result = (fileOffset == fileLength);
  285. HTTPLogTrace2(@"%@[%p]: isDone - %@", THIS_FILE, self, (result ? @"YES" : @"NO"));
  286. return result;
  287. }
  288. - (NSString *)filePath
  289. {
  290. return filePath;
  291. }
  292. - (BOOL)isAsynchronous
  293. {
  294. HTTPLogTrace();
  295. return YES;
  296. }
  297. - (void)connectionDidClose
  298. {
  299. HTTPLogTrace();
  300. if (fileFD != NULL_FD)
  301. {
  302. dispatch_sync(readQueue, ^{
  303. // Prevent any further calls to the connection
  304. connection = nil;
  305. // Cancel the readSource.
  306. // We do this here because the readSource's eventBlock has retained self.
  307. // In other words, if we don't cancel the readSource, we will never get deallocated.
  308. [self cancelReadSource];
  309. });
  310. }
  311. }
  312. - (void)dealloc
  313. {
  314. HTTPLogTrace();
  315. #if NEEDS_DISPATCH_RETAIN_RELEASE
  316. if (readQueue) dispatch_release(readQueue);
  317. #endif
  318. if (readBuffer)
  319. free(readBuffer);
  320. }
  321. @end