NSURLCache和NSURLSession不遵守:Cache-Control: max-age:86000, private, must-revalidate

6
在AppDelegate.m中,我进行了配置:
NSURLCache *sharedURLCache = [[NSURLCache alloc] initWithMemoryCapacity:20 * 1024 * 1024 diskCapacity:100 * 1024 * 1024 diskPath:@"FhtHttpCacheDir"];

然后是http请求:
- (void) testRestfulAPI{
    NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
    NSURLSession *session = [NSURLSession sessionWithConfiguration:config];

    NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:@"http://192.168.0.223:8000/v1/topictypes"]];

    [request setHTTPMethod:@"GET"];
    [request setValue:@"application/json" forHTTPHeaderField:@"Accept"];

    NSError *error = nil;
    if (!error) {
        NSURLSessionDataTask *downloadTask = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
            if (!error) {
                NSHTTPURLResponse *httpResp = (NSHTTPURLResponse*) response;
                if (httpResp.statusCode == 200) {
                    NSDictionary* json = [NSJSONSerialization
                                          JSONObjectWithData:data
                                          options:kNilOptions
                                          error:&error];
                    NSLog(@"JSON: %@", json);
                }
            }
        }];
        [downloadTask resume];
    }
}

第一次请求时,它获得了带有Etag和Cache-Control头的HTTP 200。没有问题。

enter image description here

如果我没错的话,Cache-Control: must-revalidate, max-age=86400, private 会告诉NSURLCache在24小时内将缓存视为新鲜,并且在接下来的24小时内不会进行任何网络调用。
但事实并非如此,第二次发出http请求时,实际上会发送If-None-Match头,并收到HTTP 304响应。

enter image description here

我觉得NSURLCache部分工作正常。它可以缓存响应,但不遵循苹果文档中描述的RFC 2616语义(这里)。请注意,我没有更改缓存策略,因此使用默认的NSURLRequestUseProtocolCachePolicy
我搜索了一天以上类似问题的谷歌结果,其他人也遇到了相似的问题,但我没有找到任何解决方案。有些人在AFNetworking的github问题中问了同样的问题,但作者关闭了该问题,因为它与AFNetworking无直接关系(这里)(这里)
此外,各种相关的stackoverflow帖子对我也没有帮助。

1
你定义了 sharedURLCache,但从未使用过它... - Mihai Fratu
2个回答

6

问题

问题是使用Cache-Control响应指令must-revalidate。

如果省略must-revalidate,根据我所理解的,您已经拥有了完美的用例定义:

Cache-Control: max-age=86400, private

这控制了请求资源被视为新鲜的时间。在此时间之后,答案不应直接从缓存中获取,而是应该联系服务器进行验证以供后续请求使用。在您的情况下,由于服务器提供了ETag,iOS向服务器发送带有If-None-Match头的请求。

验证

为了检查这一点,我使用了您的testRestfulAPI方法,没有NSURLCache设置,并在服务器端配置了最大60秒的年龄,因此我不必等待一天来检查结果。
之后,我每秒触发testRestfulAPI一次。我总是从缓存中获得所需的结果。并且Charles显示数据必须来自缓存,因为服务器在60秒内没有被联系过。

Verification using Charles

RFC 7234

以下是RFC 7234的引用(取代RFC 2616),在5.2.2.1中指出:

必须重新验证(must-revalidate)指令对于支持某些协议特性的可靠操作是必要的。在所有情况下,缓存必须遵守必须重新验证指令;特别是,如果缓存由于任何原因无法访问源服务器,则必须生成504(网关超时)响应。

只有在未验证表示请求可能导致错误操作(例如未执行的金融交易)时,服务器才应使用必须重新验证指令。

阅读了上述内容,并将自己放在缓存开发者的角度上,您可以想象到,当看到必须重新验证时,原始服务器总是会被联系,并且任何其他指令,如max-age,都将被忽略。实践中,缓存似乎经常显示此行为。

在5.2.2.1章节中还有另一部分,我不会隐瞒,其内容如下:

“必须重新验证”响应指令表示一旦响应已过期,缓存就必须在原始服务器上进行成功验证后才能使用响应来满足后续请求。
通常解释为,通过将max-age与must-revalidate一起指定,可以确定何时内容变得陈旧(在max-age秒之后),然后必须在原始服务器上验证才能提供内容。
但是,在实践中,由于上述原因,似乎必须始终在原始服务器上验证每个请求。

我尝试删除了must-revalidate,神奇地它就起作用了!所以我把你的答案标记为已接受。我一直认为通过添加'must-revalidate',它明确告诉缓存只有在缓存过期后才与服务器检查。我的观点是,在24小时内,缓存应被视为新鲜的,因此网络不应该启动。而Android客户端“Volley”的行为正好符合我的预期,所以我从未质疑Cache-Control头的正确性。 - foresightyj
1
这里有一个讨论 https://dev59.com/kHA85IYBdhLWcg3wHvzB#8729854,关于规范与通常实现有些偏差的问题。 - foresightyj

1
尝试更改这些行。
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:config];

转换为:

NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
config.urlCache = sharedURLCache; // make sure sharedURLCache is accessible from here
NSURLSession *session = [NSURLSession sessionWithConfiguration:config];

我可能忘记粘贴实际将sharedURlCache设置回[NSURLCache sharedCache]的那一行,所以我想它会自动使用。虽然我没有明确将其设置到NSURLSessionConfiguration中。在我的时区,现在已经是周末晚上了。代码在我的办公电脑上,我必须等到下周一才能验证。感谢您的答复! - foresightyj
明白了。那应该也可以行得通...根据苹果的文档:或者,您可以创建一个自定义的NSURLCache对象,并使用setSharedURLCache:将其设置为共享缓存实例。在调用此方法之前,您应该这样做。- https://developer.apple.com/documentation/foundation/nsurlcache/1413377-sharedurlcache?language=objc - Mihai Fratu
我刚刚确认了[NSURLCache setSharedURLCache:sharedURLCache];config.URLCache = [NSURLCache sharedURLCache];都没有任何区别。 - foresightyj

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接