Rework expiry handling in ASIDownloadCache:
All requests now store a single expiry timestamp constructed from either secondsToCache, a cache-control: max-age header, or an expires header (in order of precedence) This is used for all expiry checks, and should speed up checking if cached data is stale because it elimiates the need for NSDateFormatter or NSScanner when reading from the cache (closes gh-173)
Showing
2 changed files
with
31 additions
and
52 deletions
@@ -35,11 +35,6 @@ | @@ -35,11 +35,6 @@ | ||
35 | // A helper function that determines if the server has requested data should not be cached by looking at the request's response headers | 35 | // A helper function that determines if the server has requested data should not be cached by looking at the request's response headers |
36 | + (BOOL)serverAllowsResponseCachingForRequest:(ASIHTTPRequest *)request; | 36 | + (BOOL)serverAllowsResponseCachingForRequest:(ASIHTTPRequest *)request; |
37 | 37 | ||
38 | -// A date formatter that can be used to construct an RFC 1123 date | ||
39 | -// The returned formatter is safe to use on the calling thread | ||
40 | -// Do not use this formatter for parsing dates because the format can vary slightly - use ASIHTTPRequest's dateFromRFC1123String: class method instead | ||
41 | -+ (NSDateFormatter *)rfc1123DateFormatter; | ||
42 | - | ||
43 | @property (assign, nonatomic) ASICachePolicy defaultCachePolicy; | 38 | @property (assign, nonatomic) ASICachePolicy defaultCachePolicy; |
44 | @property (retain, nonatomic) NSString *storagePath; | 39 | @property (retain, nonatomic) NSString *storagePath; |
45 | @property (retain) NSRecursiveLock *accessLock; | 40 | @property (retain) NSRecursiveLock *accessLock; |
@@ -101,17 +101,39 @@ static NSString *permanentCacheFolder = @"PermanentStore"; | @@ -101,17 +101,39 @@ static NSString *permanentCacheFolder = @"PermanentStore"; | ||
101 | 101 | ||
102 | NSString *headerPath = [self pathToStoreCachedResponseHeadersForRequest:request]; | 102 | NSString *headerPath = [self pathToStoreCachedResponseHeadersForRequest:request]; |
103 | NSString *dataPath = [self pathToStoreCachedResponseDataForRequest:request]; | 103 | NSString *dataPath = [self pathToStoreCachedResponseDataForRequest:request]; |
104 | - | 104 | + |
105 | NSMutableDictionary *responseHeaders = [NSMutableDictionary dictionaryWithDictionary:[request responseHeaders]]; | 105 | NSMutableDictionary *responseHeaders = [NSMutableDictionary dictionaryWithDictionary:[request responseHeaders]]; |
106 | if ([request isResponseCompressed]) { | 106 | if ([request isResponseCompressed]) { |
107 | [responseHeaders removeObjectForKey:@"Content-Encoding"]; | 107 | [responseHeaders removeObjectForKey:@"Content-Encoding"]; |
108 | } | 108 | } |
109 | - if (maxAge != 0) { | 109 | + |
110 | - [responseHeaders removeObjectForKey:@"Expires"]; | 110 | + // Create a special 'X-ASIHTTPRequest-Expires' header |
111 | - [responseHeaders setObject:[NSString stringWithFormat:@"max-age=%i",(int)maxAge] forKey:@"Cache-Control"]; | 111 | + // This is what we use for deciding if cached data is current, rather than parsing the expires / max-age headers individually each time |
112 | + // We store this as a timestamp to make reading it easier as NSDateFormatter is quite expensive | ||
113 | + | ||
114 | + // If we weren't given a custom max-age, lets look for one in the response headers | ||
115 | + if (!maxAge) { | ||
116 | + NSString *cacheControl = [[responseHeaders objectForKey:@"Cache-Control"] lowercaseString]; | ||
117 | + if (cacheControl) { | ||
118 | + NSScanner *scanner = [NSScanner scannerWithString:cacheControl]; | ||
119 | + [scanner scanUpToString:@"max-age" intoString:NULL]; | ||
120 | + if ([scanner scanString:@"max-age" intoString:NULL]) { | ||
121 | + [scanner scanString:@"=" intoString:NULL]; | ||
122 | + [scanner scanDouble:&maxAge]; | ||
123 | + } | ||
124 | + } | ||
125 | + } | ||
126 | + | ||
127 | + // RFC 2612 says max-age must override any Expires header | ||
128 | + if (maxAge) { | ||
129 | + [responseHeaders setObject:[NSNumber numberWithDouble:[[[NSDate date] addTimeInterval:maxAge] timeIntervalSince1970]] forKey:@"X-ASIHTTPRequest-Expires"]; | ||
130 | + } else { | ||
131 | + NSString *expires = [responseHeaders objectForKey:@"Expires"]; | ||
132 | + if (expires) { | ||
133 | + [responseHeaders setObject:[NSNumber numberWithDouble:[[ASIHTTPRequest dateFromRFC1123String:expires] timeIntervalSince1970]] forKey:@"X-ASIHTTPRequest-Expires"]; | ||
134 | + } | ||
112 | } | 135 | } |
113 | - // We use this special key to help expire the request when we get a max-age header | 136 | + |
114 | - [responseHeaders setObject:[[[self class] rfc1123DateFormatter] stringFromDate:[NSDate date]] forKey:@"X-ASIHTTPRequest-Fetch-date"]; | ||
115 | [responseHeaders writeToFile:headerPath atomically:NO]; | 137 | [responseHeaders writeToFile:headerPath atomically:NO]; |
116 | 138 | ||
117 | if ([request responseData]) { | 139 | if ([request responseData]) { |
@@ -281,33 +303,10 @@ static NSString *permanentCacheFolder = @"PermanentStore"; | @@ -281,33 +303,10 @@ static NSString *permanentCacheFolder = @"PermanentStore"; | ||
281 | 303 | ||
282 | if ([self shouldRespectCacheControlHeaders]) { | 304 | if ([self shouldRespectCacheControlHeaders]) { |
283 | 305 | ||
284 | - // Look for a max-age header | 306 | + // Look for X-ASIHTTPRequest-Expires header to see if the content is out of date |
285 | - NSString *cacheControl = [[cachedHeaders objectForKey:@"Cache-Control"] lowercaseString]; | 307 | + NSNumber *expires = [cachedHeaders objectForKey:@"X-ASIHTTPRequest-Expires"]; |
286 | - if (cacheControl) { | ||
287 | - NSScanner *scanner = [NSScanner scannerWithString:cacheControl]; | ||
288 | - [scanner scanUpToString:@"max-age" intoString:NULL]; | ||
289 | - if ([scanner scanString:@"max-age" intoString:NULL]) { | ||
290 | - [scanner scanString:@"=" intoString:NULL]; | ||
291 | - NSTimeInterval maxAge = 0; | ||
292 | - [scanner scanDouble:&maxAge]; | ||
293 | - | ||
294 | - NSDate *fetchDate = [ASIHTTPRequest dateFromRFC1123String:[cachedHeaders objectForKey:@"X-ASIHTTPRequest-Fetch-date"]]; | ||
295 | - NSDate *expiryDate = [[[NSDate alloc] initWithTimeInterval:maxAge sinceDate:fetchDate] autorelease]; | ||
296 | - | ||
297 | - if ([expiryDate timeIntervalSinceNow] >= 0) { | ||
298 | - [[self accessLock] unlock]; | ||
299 | - return YES; | ||
300 | - } | ||
301 | - // RFC 2612 says max-age must override any Expires header | ||
302 | - [[self accessLock] unlock]; | ||
303 | - return NO; | ||
304 | - } | ||
305 | - } | ||
306 | - | ||
307 | - // Look for an Expires header to see if the content is out of date | ||
308 | - NSString *expires = [cachedHeaders objectForKey:@"Expires"]; | ||
309 | if (expires) { | 308 | if (expires) { |
310 | - if ([[ASIHTTPRequest dateFromRFC1123String:expires] timeIntervalSinceNow] >= 0) { | 309 | + if ([[NSDate dateWithTimeIntervalSince1970:[expires doubleValue]] timeIntervalSinceNow] >= 0) { |
311 | [[self accessLock] unlock]; | 310 | [[self accessLock] unlock]; |
312 | return YES; | 311 | return YES; |
313 | } | 312 | } |
@@ -408,21 +407,6 @@ static NSString *permanentCacheFolder = @"PermanentStore"; | @@ -408,21 +407,6 @@ static NSString *permanentCacheFolder = @"PermanentStore"; | ||
408 | return [NSString stringWithFormat:@"%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X",result[0], result[1], result[2], result[3], result[4], result[5], result[6], result[7],result[8], result[9], result[10], result[11],result[12], result[13], result[14], result[15]]; | 407 | return [NSString stringWithFormat:@"%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X",result[0], result[1], result[2], result[3], result[4], result[5], result[6], result[7],result[8], result[9], result[10], result[11],result[12], result[13], result[14], result[15]]; |
409 | } | 408 | } |
410 | 409 | ||
411 | -+ (NSDateFormatter *)rfc1123DateFormatter | ||
412 | -{ | ||
413 | - NSMutableDictionary *threadDict = [[NSThread currentThread] threadDictionary]; | ||
414 | - NSDateFormatter *dateFormatter = [threadDict objectForKey:@"ASIDownloadCacheDateFormatter"]; | ||
415 | - if (dateFormatter == nil) { | ||
416 | - dateFormatter = [[[NSDateFormatter alloc] init] autorelease]; | ||
417 | - [dateFormatter setLocale:[[[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"] autorelease]]; | ||
418 | - [dateFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]]; | ||
419 | - [dateFormatter setDateFormat:@"EEE, dd MMM yyyy HH:mm:ss 'GMT'"]; | ||
420 | - [threadDict setObject:dateFormatter forKey:@"ASIDownloadCacheDateFormatter"]; | ||
421 | - } | ||
422 | - return dateFormatter; | ||
423 | -} | ||
424 | - | ||
425 | - | ||
426 | - (BOOL)canUseCachedDataForRequest:(ASIHTTPRequest *)request | 410 | - (BOOL)canUseCachedDataForRequest:(ASIHTTPRequest *)request |
427 | { | 411 | { |
428 | // Ensure the request is allowed to read from the cache | 412 | // Ensure the request is allowed to read from the cache |
-
Please register or login to post a comment