NSURLCache does not work when response header value for transfer-encoding is chunked
Asked Answered
R

1

9

I found an issue with (possibly) NSURLCache today while inspecting request and response headers in Charles Proxy. The issue is a little perplexing, but I'm able to repro it consistently:

In a nutshell, the issue has to do with caching of NSURLRequests using iOS's native NSURLCache with the default policy. It turns out that the request is not cached whenever the response has the header transfer-encoding: chunked. But if the response header is content-length: xxx instead, caching works fine. Specifically, it seems that when the response is chunked, NSURLCache doesn't save the eTag and also neglects appending the if-none-match header to subsequent requests to the same url, and consequently, caching fails (as it should), i.e. a 200 is returned instead of a 304.

I'm testing on the iOS8.2 simulator. Even if you don't have a solution, I'd love to hear if you've experienced the same issue. I've found at least one similar report), and here's a related thread posted by my back-end engineer.

Realist answered 18/3, 2015 at 1:52 Comment(3)
None, other than we are able to repro this bug with high certainty. For now, we're looking into always sending the JSON back with the content-length header. What's the nature of your problem?Realist
Hi, our backend system cannot return content-length because the response is huge and it must be returned as chunks. So we need to find the way to cache chunked response or an official documentation saying that is impossible. Appreciate any advice.Nl
Hi, check out my answer here: #20249207Legwork
U
1

It should work if you manually add the response data to the cache. I've got an image loading class where I want to make sure everything is cached, so I do something like this:

- (void)getImageWithURL:(NSURL *)url onCompletion:(void (^)(UIImage *image, NSError *error))completion {
    NSURLRequest *request = [NSURLRequest requestWithURL:url];

    UIImage *cachedImage = [self cachedImageForURLRequest:request];
    if (cachedImage) {
        NSLog(@"Got image from cache.");
        completion(cachedImage, nil);
        return;
    }

    [[[NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]] dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
        // Manually cache the response.
        NSCachedURLResponse *cachedResponse = [[NSCachedURLResponse alloc] initWithResponse:response data:data userInfo:nil storagePolicy:NSURLCacheStorageAllowed];
        [[NSURLCache sharedURLCache] storeCachedResponse:cachedResponse forRequest:request];
        NSLog(@"Got a fresh image.");
        completion([UIImage imageWithData:data], error);
    }] resume];
}

- (UIImage *)cachedImageForURLRequest:(NSURLRequest *)urlRequest {
    NSCachedURLResponse *cachedResponse = [[NSURLCache sharedURLCache] cachedResponseForRequest:urlRequest];
    return [UIImage imageWithData:cachedResponse.data];
}
Uprising answered 6/7, 2015 at 10:25 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.