Permalink
Browse files

Important changes to download cache behaviour:

ASIDownloadCache will now store 301, 302, 303 and 307 redirect responses in the cache. Requests pulling from the cache will automatically redirect (assuming shouldRedirect is true) as if they'd received a Location header from the server.
This change should allow the download cache to operate more effectively as an offline fallback when no internet connection is available.
Additionally, requests encountering a 304 will no longer read from the cache and then write back to it. Instead, they merely update the expiry date (if an updated date was supplied).
As part of this work, two new required methods were added to the ASICacheDelegate protocol, see ASICacheDelegate.h for more info
Finally, ASIDownloadCache now stores the response status code in a custom header in the cached headers dictionary, and requests will have their response status code set to this when they are pulled from the cache.
  • Loading branch information...
1 parent ad73765 commit e76a84533e9a985df5e83588b5de58336766651b @pokeb committed May 15, 2011
@@ -56,6 +56,13 @@ typedef enum _ASICacheStoragePolicy {
// Should return the cache policy that will be used when requests have their cache policy set to ASIUseDefaultCachePolicy
- (ASICachePolicy)defaultCachePolicy;
+// Returns the date a cached response should expire on. Pass a non-zero max age to specify a custom date.
+- (NSDate *)expiryDateForRequest:(ASIHTTPRequest *)request maxAge:(NSTimeInterval)maxAge;
+
+// Updates cached response headers with a new expiry date. Pass a non-zero max age to specify a custom date.
+- (void)updateExpiryForRequest:(ASIHTTPRequest *)request maxAge:(NSTimeInterval)maxAge;
+
+// Looks at the request's cache policy and any cached headers to determine if the cache data is still valid
- (BOOL)canUseCachedDataForRequest:(ASIHTTPRequest *)request;
// Removes cached data for a particular request
View
@@ -85,31 +85,24 @@ - (void)setStoragePath:(NSString *)path
[[self accessLock] unlock];
}
-- (void)storeResponseForRequest:(ASIHTTPRequest *)request maxAge:(NSTimeInterval)maxAge
+- (void)updateExpiryForRequest:(ASIHTTPRequest *)request maxAge:(NSTimeInterval)maxAge
{
- [[self accessLock] lock];
-
- if ([request error] || ![request responseHeaders] || ([request responseStatusCode] != 200) || ([request cachePolicy] & ASIDoNotWriteToCacheCachePolicy)) {
- [[self accessLock] unlock];
+ NSString *headerPath = [self pathToStoreCachedResponseHeadersForRequest:request];
+ NSMutableDictionary *cachedHeaders = [NSMutableDictionary dictionaryWithContentsOfFile:headerPath];
+ if (!cachedHeaders) {
return;
}
-
- if ([self shouldRespectCacheControlHeaders] && ![[self class] serverAllowsResponseCachingForRequest:request]) {
- [[self accessLock] unlock];
+ NSDate *expires = [self expiryDateForRequest:request maxAge:maxAge];
+ if (!expires) {
return;
}
+ [cachedHeaders setObject:[NSNumber numberWithDouble:[expires timeIntervalSince1970]] forKey:@"X-ASIHTTPRequest-Expires"];
+ [cachedHeaders writeToFile:headerPath atomically:NO];
+}
- NSString *headerPath = [self pathToStoreCachedResponseHeadersForRequest:request];
- NSString *dataPath = [self pathToStoreCachedResponseDataForRequest:request];
-
+- (NSDate *)expiryDateForRequest:(ASIHTTPRequest *)request maxAge:(NSTimeInterval)maxAge
+{
NSMutableDictionary *responseHeaders = [NSMutableDictionary dictionaryWithDictionary:[request responseHeaders]];
- if ([request isResponseCompressed]) {
- [responseHeaders removeObjectForKey:@"Content-Encoding"];
- }
-
- // Create a special 'X-ASIHTTPRequest-Expires' header
- // This is what we use for deciding if cached data is current, rather than parsing the expires / max-age headers individually each time
- // We store this as a timestamp to make reading it easier as NSDateFormatter is quite expensive
// If we weren't given a custom max-age, lets look for one in the response headers
if (!maxAge) {
@@ -126,16 +119,65 @@ - (void)storeResponseForRequest:(ASIHTTPRequest *)request maxAge:(NSTimeInterval
// RFC 2612 says max-age must override any Expires header
if (maxAge) {
- [responseHeaders setObject:[NSNumber numberWithDouble:[[[NSDate date] addTimeInterval:maxAge] timeIntervalSince1970]] forKey:@"X-ASIHTTPRequest-Expires"];
+ return [[NSDate date] addTimeInterval:maxAge];
} else {
NSString *expires = [responseHeaders objectForKey:@"Expires"];
if (expires) {
- [responseHeaders setObject:[NSNumber numberWithDouble:[[ASIHTTPRequest dateFromRFC1123String:expires] timeIntervalSince1970]] forKey:@"X-ASIHTTPRequest-Expires"];
+ return [ASIHTTPRequest dateFromRFC1123String:expires];
}
}
+ return nil;
+}
+
+- (void)storeResponseForRequest:(ASIHTTPRequest *)request maxAge:(NSTimeInterval)maxAge
+{
+ [[self accessLock] lock];
+
+ if ([request error] || ![request responseHeaders] || ([request cachePolicy] & ASIDoNotWriteToCacheCachePolicy)) {
+ [[self accessLock] unlock];
+ return;
+ }
+
+ // We only cache 200/OK or redirect reponses (redirect responses are cached so the cache works better with no internet connection)
+ int responseCode = [request responseStatusCode];
+ if (responseCode != 200 && responseCode != 301 && responseCode != 302 && responseCode != 303 && responseCode != 307) {
+ [[self accessLock] unlock];
+ return;
+ }
+
+ if ([self shouldRespectCacheControlHeaders] && ![[self class] serverAllowsResponseCachingForRequest:request]) {
+ [[self accessLock] unlock];
+ return;
+ }
+
+ NSString *headerPath = [self pathToStoreCachedResponseHeadersForRequest:request];
+ NSString *dataPath = [self pathToStoreCachedResponseDataForRequest:request];
+
+ NSMutableDictionary *responseHeaders = [NSMutableDictionary dictionaryWithDictionary:[request responseHeaders]];
+ if ([request isResponseCompressed]) {
+ [responseHeaders removeObjectForKey:@"Content-Encoding"];
+ }
+
+ // Create a special 'X-ASIHTTPRequest-Expires' header
+ // This is what we use for deciding if cached data is current, rather than parsing the expires / max-age headers individually each time
+ // We store this as a timestamp to make reading it easier as NSDateFormatter is quite expensive
+
+ NSDate *expires = [self expiryDateForRequest:request maxAge:maxAge];
+ if (expires) {
+ [responseHeaders setObject:[NSNumber numberWithDouble:[expires timeIntervalSince1970]] forKey:@"X-ASIHTTPRequest-Expires"];
+ }
+
+ // Store the response code in a custom header so we can reuse it later
+
+ // We'll change 304/Not Modified to 200/OK because this is likely to be us updating the cached headers with a conditional GET
+ int statusCode = [request responseStatusCode];
+ if (statusCode == 304) {
+ statusCode = 200;
+ }
+ [responseHeaders setObject:[NSNumber numberWithInt:[request responseStatusCode]] forKey:@"X-ASIHTTPRequest-Response-Status-Code"];
[responseHeaders writeToFile:headerPath atomically:NO];
-
+
if ([request responseData]) {
[[request responseData] writeToFile:dataPath atomically:NO];
} else if ([request downloadDestinationPath] && ![[request downloadDestinationPath] isEqualToString:dataPath]) {
@@ -282,14 +324,15 @@ - (BOOL)isCachedDataCurrentForRequest:(ASIHTTPRequest *)request
return NO;
}
- // If we already have response headers for this request, check to see if the new content is different
- if ([request responseHeaders]) {
+ // New content is not different
+ if ([request responseStatusCode] == 304) {
+ [[self accessLock] unlock];
+ return YES;
+ }
- // New content is not different
- if ([request responseStatusCode] == 304) {
- [[self accessLock] unlock];
- return YES;
- }
+ // If we already have response headers for this request, check to see if the new content is different
+ // We check [request complete] so that we don't end up comparing response headers from a redirection with these
+ if ([request responseHeaders] && [request complete]) {
@jeffery9

jeffery9 Apr 24, 2012

when we do http conditional get request, the request never set the status of complete to YES

so we add a code segment to let 200 OK to not read cache.

if ([request responseStatusCode] == 304) {

[[self accessLock] unlock];

return NO;

}

// If the Etag or Last-Modified date are different from the one we have, we'll have to fetch this resource again
NSArray *headersToCompare = [NSArray arrayWithObjects:@"Etag",@"Last-Modified",nil];
Oops, something went wrong.

0 comments on commit e76a845

Please sign in to comment.