PageRenderTime 34ms CodeModel.GetById 0ms RepoModel.GetById 0ms app.codeStats 0ms

/src/Three20Network/Sources/TTRequestLoader.m

https://github.com/GetMoPix/three20
Objective C | 448 lines | 269 code | 97 blank | 82 comment | 58 complexity | 8a8678e10166a81678cb9b89597a3ebb MD5 | raw file
  1. //
  2. // Copyright 2009-2011 Facebook
  3. //
  4. // Licensed under the Apache License, Version 2.0 (the "License");
  5. // you may not use this file except in compliance with the License.
  6. // You may obtain a copy of the License at
  7. //
  8. // http://www.apache.org/licenses/LICENSE-2.0
  9. //
  10. // Unless required by applicable law or agreed to in writing, software
  11. // distributed under the License is distributed on an "AS IS" BASIS,
  12. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. // See the License for the specific language governing permissions and
  14. // limitations under the License.
  15. //
  16. #import "Three20Network/private/TTRequestLoader.h"
  17. //Global
  18. #import "Three20Network/TTErrorCodes.h"
  19. // Network
  20. #import "Three20Network/TTGlobalNetwork.h"
  21. #import "Three20Network/TTURLRequest.h"
  22. #import "Three20Network/TTURLRequestDelegate.h"
  23. #import "Three20Network/TTURLRequestQueue.h"
  24. #import "Three20Network/TTURLResponse.h"
  25. // Network (private)
  26. #import "Three20Network/private/TTURLRequestQueueInternal.h"
  27. // Core
  28. #import "Three20Core/NSDataAdditions.h"
  29. #import "Three20Core/NSObjectAdditions.h"
  30. #import "Three20Core/TTDebug.h"
  31. #import "Three20Core/TTDebugFlags.h"
  32. static const NSInteger kLoadMaxRetries = 2;
  33. ///////////////////////////////////////////////////////////////////////////////////////////////////
  34. ///////////////////////////////////////////////////////////////////////////////////////////////////
  35. ///////////////////////////////////////////////////////////////////////////////////////////////////
  36. @implementation TTRequestLoader
  37. @synthesize urlPath = _urlPath;
  38. @synthesize requests = _requests;
  39. @synthesize cacheKey = _cacheKey;
  40. @synthesize cachePolicy = _cachePolicy;
  41. @synthesize cacheExpirationAge = _cacheExpirationAge;
  42. ///////////////////////////////////////////////////////////////////////////////////////////////////
  43. - (id)initForRequest:(TTURLRequest*)request queue:(TTURLRequestQueue*)queue {
  44. self = [super init];
  45. if (self) {
  46. _urlPath = [request.urlPath copy];
  47. _queue = queue;
  48. _cacheKey = [request.cacheKey retain];
  49. _cachePolicy = request.cachePolicy;
  50. _cacheExpirationAge = request.cacheExpirationAge;
  51. _requests = [[NSMutableArray alloc] init];
  52. _retriesLeft = kLoadMaxRetries;
  53. [self addRequest:request];
  54. }
  55. return self;
  56. }
  57. ///////////////////////////////////////////////////////////////////////////////////////////////////
  58. - (void)dealloc {
  59. [_connection cancel];
  60. TT_RELEASE_SAFELY(_connection);
  61. TT_RELEASE_SAFELY(_response);
  62. TT_RELEASE_SAFELY(_responseData);
  63. TT_RELEASE_SAFELY(_urlPath);
  64. TT_RELEASE_SAFELY(_cacheKey);
  65. TT_RELEASE_SAFELY(_requests);
  66. [super dealloc];
  67. }
  68. ///////////////////////////////////////////////////////////////////////////////////////////////////
  69. ///////////////////////////////////////////////////////////////////////////////////////////////////
  70. #pragma mark -
  71. #pragma mark Private
  72. //////////////////////////////////////////////////////////////////////////////////////////////////
  73. // This method not called from outside,
  74. // used as a separate entry point for performSelector outside connectToURL below
  75. - (void)deliverDataResponse:(NSURL*)URL {
  76. // http://tools.ietf.org/html/rfc2397
  77. NSArray * dataSplit = [[URL resourceSpecifier] componentsSeparatedByString:@","];
  78. if([dataSplit count]!=2) {
  79. TTDCONDITIONLOG(TTDFLAG_URLREQUEST, @"UNRECOGNIZED data: URL %@", self.urlPath);
  80. return;
  81. }
  82. if([[dataSplit objectAtIndex:0] rangeOfString:@"base64"].location == NSNotFound) {
  83. // Strictly speaking, to be really conformant need to interpret %xx hex encoded entities.
  84. // The [NSString dataUsingEncoding] doesn't do that correctly, but most documents don't use that.
  85. // Skip for now.
  86. _responseData = [[[dataSplit objectAtIndex:1] dataUsingEncoding:NSASCIIStringEncoding] retain];
  87. } else {
  88. _responseData = [[NSData dataWithBase64EncodedString:[dataSplit objectAtIndex:1]] retain];
  89. }
  90. [_queue performSelector:@selector(loader:didLoadResponse:data:) withObject:self
  91. withObject:_response withObject:_responseData];
  92. }
  93. ///////////////////////////////////////////////////////////////////////////////////////////////////
  94. - (void)connectToURL:(NSURL*)URL {
  95. TTDCONDITIONLOG(TTDFLAG_URLREQUEST, @"Connecting to %@", _urlPath);
  96. // If this is a data: url, we can decode right here ... after a delay to get out of calling thread
  97. if([[URL scheme] isEqualToString:@"data"]) {
  98. [self performSelector:@selector(deliverDataResponse:) withObject:URL afterDelay:0.1];
  99. return;
  100. }
  101. TTNetworkRequestStarted();
  102. TTURLRequest* request = _requests.count == 1 ? [_requests objectAtIndex:0] : nil;
  103. NSURLRequest* URLRequest = [_queue createNSURLRequest:request URL:URL];
  104. _connection = [[NSURLConnection alloc] initWithRequest:URLRequest delegate:self];
  105. }
  106. ///////////////////////////////////////////////////////////////////////////////////////////////////
  107. - (void)dispatchLoadedBytes:(NSInteger)bytesLoaded expected:(NSInteger)bytesExpected {
  108. for (TTURLRequest* request in [[_requests copy] autorelease]) {
  109. request.totalBytesLoaded = bytesLoaded;
  110. request.totalBytesExpected = bytesExpected;
  111. for (id<TTURLRequestDelegate> delegate in request.delegates) {
  112. if ([delegate respondsToSelector:@selector(requestDidUploadData:)]) {
  113. [delegate requestDidUploadData:request];
  114. }
  115. }
  116. }
  117. }
  118. ///////////////////////////////////////////////////////////////////////////////////////////////////
  119. ///////////////////////////////////////////////////////////////////////////////////////////////////
  120. #pragma mark -
  121. #pragma mark Public
  122. ///////////////////////////////////////////////////////////////////////////////////////////////////
  123. - (void)addRequest:(TTURLRequest*)request {
  124. // TODO (jverkoey April 27, 2010): Look into the repercussions of adding a request with
  125. // different properties.
  126. //TTDASSERT([_urlPath isEqualToString:request.urlPath]);
  127. //TTDASSERT(_cacheKey == request.cacheKey);
  128. //TTDASSERT(_cachePolicy == request.cachePolicy);
  129. //TTDASSERT(_cacheExpirationAge == request.cacheExpirationAge);
  130. [_requests addObject:request];
  131. }
  132. ///////////////////////////////////////////////////////////////////////////////////////////////////
  133. - (void)removeRequest:(TTURLRequest*)request {
  134. [_requests removeObject:request];
  135. }
  136. ///////////////////////////////////////////////////////////////////////////////////////////////////
  137. - (void)load:(NSURL*)URL {
  138. if (nil == _connection) {
  139. [self connectToURL:URL];
  140. }
  141. }
  142. ///////////////////////////////////////////////////////////////////////////////////////////////////
  143. - (void)loadSynchronously:(NSURL*)URL {
  144. // This method simulates an asynchronous network connection. If your delegate isn't being called
  145. // correctly, this would be the place to start tracing for errors.
  146. TTNetworkRequestStarted();
  147. TTURLRequest* request = _requests.count == 1 ? [_requests objectAtIndex:0] : nil;
  148. NSURLRequest* URLRequest = [_queue createNSURLRequest:request URL:URL];
  149. NSHTTPURLResponse* response = nil;
  150. NSError* error = nil;
  151. NSData* data = [NSURLConnection
  152. sendSynchronousRequest: URLRequest
  153. returningResponse: &response
  154. error: &error];
  155. if (nil != error) {
  156. TTNetworkRequestStopped();
  157. TT_RELEASE_SAFELY(_responseData);
  158. TT_RELEASE_SAFELY(_connection);
  159. [_queue loader:self didFailLoadWithError:error];
  160. } else {
  161. [self connection:nil didReceiveResponse:(NSHTTPURLResponse*)response];
  162. [self connection:nil didReceiveData:data];
  163. [self connectionDidFinishLoading:nil];
  164. }
  165. }
  166. ///////////////////////////////////////////////////////////////////////////////////////////////////
  167. - (BOOL)cancel:(TTURLRequest*)request {
  168. NSUInteger requestIndex = [_requests indexOfObject:request];
  169. if (requestIndex != NSNotFound) {
  170. request.isLoading = NO;
  171. for (id<TTURLRequestDelegate> delegate in request.delegates) {
  172. if ([delegate respondsToSelector:@selector(requestDidCancelLoad:)]) {
  173. [delegate requestDidCancelLoad:request];
  174. }
  175. }
  176. [_requests removeObjectAtIndex:requestIndex];
  177. }
  178. if (![_requests count]) {
  179. [_queue loaderDidCancel:self wasLoading:!!_connection];
  180. if (nil != _connection) {
  181. TTNetworkRequestStopped();
  182. [_connection cancel];
  183. TT_RELEASE_SAFELY(_connection);
  184. }
  185. return NO;
  186. } else {
  187. return YES;
  188. }
  189. }
  190. ///////////////////////////////////////////////////////////////////////////////////////////////////
  191. - (NSError*)processResponse:(NSHTTPURLResponse*)response data:(id)data {
  192. for (TTURLRequest* request in _requests) {
  193. NSError* error = nil;
  194. // We need to accept valid HTTP status codes, not only 200.
  195. if (!response
  196. || (response.statusCode >= 200 && response.statusCode < 300)
  197. || response.statusCode == 304) {
  198. error = [request.response request:request processResponse:response data:data];
  199. } else {
  200. if ([request.response respondsToSelector:@selector(request:processErrorResponse:data:)]) {
  201. error = [request.response request:request processErrorResponse:response data:data];
  202. }
  203. // Supply an NSError object if request.response's
  204. // request:processErrorResponse:data: does not return one.
  205. if (!error) {
  206. TTDCONDITIONLOG(TTDFLAG_URLREQUEST, @" FAILED LOADING (%d) %@", _response.statusCode, _urlPath);
  207. NSDictionary* userInfo = [NSDictionary dictionaryWithObject:data forKey:kTTErrorResponseDataKey];
  208. error = [NSError errorWithDomain:NSURLErrorDomain code:_response.statusCode userInfo:userInfo];
  209. }
  210. }
  211. if (error) {
  212. return error;
  213. }
  214. }
  215. return nil;
  216. }
  217. ///////////////////////////////////////////////////////////////////////////////////////////////////
  218. - (void)dispatchError:(NSError*)error {
  219. for (TTURLRequest* request in [[_requests copy] autorelease]) {
  220. request.isLoading = NO;
  221. for (id<TTURLRequestDelegate> delegate in request.delegates) {
  222. if ([delegate respondsToSelector:@selector(request:didFailLoadWithError:)]) {
  223. [delegate request:request didFailLoadWithError:error];
  224. }
  225. }
  226. }
  227. }
  228. ///////////////////////////////////////////////////////////////////////////////////////////////////
  229. - (void)dispatchLoaded:(NSDate*)timestamp {
  230. for (TTURLRequest* request in [[_requests copy] autorelease]) {
  231. request.timestamp = timestamp;
  232. request.isLoading = NO;
  233. for (id<TTURLRequestDelegate> delegate in request.delegates) {
  234. if ([delegate respondsToSelector:@selector(requestDidFinishLoad:)]) {
  235. [delegate requestDidFinishLoad:request];
  236. }
  237. }
  238. }
  239. }
  240. ///////////////////////////////////////////////////////////////////////////////////////////////////
  241. - (void)dispatchAuthenticationChallenge:(NSURLAuthenticationChallenge*)challenge {
  242. for (TTURLRequest* request in [[_requests copy] autorelease]) {
  243. for (id<TTURLRequestDelegate> delegate in request.delegates) {
  244. if ([delegate respondsToSelector:@selector(request:didReceiveAuthenticationChallenge:)]) {
  245. [delegate request:request didReceiveAuthenticationChallenge:challenge];
  246. }
  247. }
  248. }
  249. }
  250. ///////////////////////////////////////////////////////////////////////////////////////////////////
  251. - (void)cancel {
  252. NSArray* requestsToCancel = [_requests copy];
  253. for (id request in requestsToCancel) {
  254. [self cancel:request];
  255. }
  256. [requestsToCancel release];
  257. }
  258. ///////////////////////////////////////////////////////////////////////////////////////////////////
  259. ///////////////////////////////////////////////////////////////////////////////////////////////////
  260. #pragma mark -
  261. #pragma mark NSURLConnectionDelegate
  262. ///////////////////////////////////////////////////////////////////////////////////////////////////
  263. - (void)connection:(NSURLConnection*)connection didReceiveResponse:(NSHTTPURLResponse*)response {
  264. _response = [response retain];
  265. NSDictionary* headers = [response allHeaderFields];
  266. int contentLength = [[headers objectForKey:@"Content-Length"] intValue];
  267. // If you hit this assertion it's because a massive file is about to be downloaded.
  268. // If you're sure you want to do this, add the following line to your app delegate startup
  269. // method. Setting the max content length to zero allows anything to go through. If you just
  270. // want to raise the limit, set it to any positive byte size.
  271. // [[TTURLRequestQueue mainQueue] setMaxContentLength:0]
  272. TTDASSERT(0 == _queue.maxContentLength || contentLength <=_queue.maxContentLength);
  273. if (contentLength > _queue.maxContentLength && _queue.maxContentLength) {
  274. TTDCONDITIONLOG(TTDFLAG_URLREQUEST, @"MAX CONTENT LENGTH EXCEEDED (%d) %@",
  275. contentLength, _urlPath);
  276. [self cancel];
  277. }
  278. _responseData = [[NSMutableData alloc] initWithCapacity:contentLength];
  279. for (TTURLRequest* request in [[_requests copy] autorelease]) {
  280. request.totalContentLength = contentLength;
  281. }
  282. }
  283. ///////////////////////////////////////////////////////////////////////////////////////////////////
  284. - (void)connection:(NSURLConnection*)connection didReceiveData:(NSData*)data {
  285. [_responseData appendData:data];
  286. for (TTURLRequest* request in [[_requests copy] autorelease]) {
  287. request.totalBytesDownloaded += [data length];
  288. }
  289. }
  290. ///////////////////////////////////////////////////////////////////////////////////////////////////
  291. - (NSCachedURLResponse *)connection: (NSURLConnection *)connection
  292. willCacheResponse: (NSCachedURLResponse *)cachedResponse {
  293. return nil;
  294. }
  295. ///////////////////////////////////////////////////////////////////////////////////////////////////
  296. - (void) connection: (NSURLConnection *)connection
  297. didSendBodyData: (NSInteger)bytesWritten
  298. totalBytesWritten: (NSInteger)totalBytesWritten
  299. totalBytesExpectedToWrite: (NSInteger)totalBytesExpectedToWrite {
  300. [self dispatchLoadedBytes:totalBytesWritten expected:totalBytesExpectedToWrite];
  301. }
  302. ///////////////////////////////////////////////////////////////////////////////////////////////////
  303. - (void)connectionDidFinishLoading:(NSURLConnection *)connection {
  304. TTNetworkRequestStopped();
  305. TTDCONDITIONLOG(TTDFLAG_ETAGS, @"Response status code: %d", _response.statusCode);
  306. if (_response.statusCode == 304) {
  307. [_queue loader:self didLoadUnmodifiedResponse:_response];
  308. } else {
  309. [_queue loader:self didLoadResponse:_response data:_responseData];
  310. }
  311. TT_RELEASE_SAFELY(_responseData);
  312. TT_RELEASE_SAFELY(_connection);
  313. }
  314. ///////////////////////////////////////////////////////////////////////////////////////////////////
  315. - (void)connection:(NSURLConnection *)connection
  316. didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge{
  317. TTDCONDITIONLOG(TTDFLAG_URLREQUEST, @" RECEIVED AUTH CHALLENGE LOADING %@ ", _urlPath);
  318. [_queue loader:self didReceiveAuthenticationChallenge:challenge];
  319. }
  320. ///////////////////////////////////////////////////////////////////////////////////////////////////
  321. - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
  322. TTDCONDITIONLOG(TTDFLAG_URLREQUEST, @" FAILED LOADING %@ FOR %@", _urlPath, error);
  323. TTNetworkRequestStopped();
  324. TT_RELEASE_SAFELY(_responseData);
  325. TT_RELEASE_SAFELY(_connection);
  326. if ([error.domain isEqualToString:NSURLErrorDomain] && error.code == NSURLErrorCannotFindHost
  327. && _retriesLeft) {
  328. // If there is a network error then we will wait and retry a few times in case
  329. // it was just a temporary blip in connectivity.
  330. --_retriesLeft;
  331. [self load:[NSURL URLWithString:_urlPath]];
  332. } else {
  333. [_queue loader:self didFailLoadWithError:error];
  334. }
  335. }
  336. ///////////////////////////////////////////////////////////////////////////////////////////////////
  337. ///////////////////////////////////////////////////////////////////////////////////////////////////
  338. #pragma mark -
  339. #pragma mark Accessors
  340. ///////////////////////////////////////////////////////////////////////////////////////////////////
  341. - (BOOL)isLoading {
  342. return !!_connection;
  343. }
  344. ///////////////////////////////////////////////////////////////////////////////////////////////////
  345. /**
  346. * Deprecated
  347. */
  348. - (NSString*)URL {
  349. return _urlPath;
  350. }
  351. @end