NSURLCache 与 NSURLSession 一起不遵守:Cache-Control: max-age:86000, private, must-revalidate
NSURLCache, together with NSURLSession, does not respect: Cache-Control: max-age:86000, private, must-revalidate
在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 headers 的 HTTP 200。没问题。
如果我没记错的话,Cache-Control: must-revalidate, max-age=86400, private
会告诉 NSURLCache 认为缓存在 24 小时内是新鲜的,并且在接下来的 24 小时内不会进行任何网络调用。
但事实并非如此,第二次http请求,居然发出了If-None-Match
headers出去,得到了HTTP 304.
在我看来,NSURLCache 部分工作。它可以缓存响应,但它不遵守 RFC 2616 Apple 文档描述的语义 here。仅供参考,我没有更改缓存策略,因此它使用默认的 NSURLRequestUseProtocolCachePolicy
.
我在谷歌上搜索了一天多的类似问题和其他遇到的类似问题,但我没有找到任何解决方案。有些人在 AFNetworking 的 github 问题中询问了同样的问题,但作者关闭了这个问题,因为它与 AFNetworking here and here 没有直接关系。
各种相关的 Whosebug 帖子也没有帮助我。
尝试更改这些行
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];
问题
问题是 Cache-Control 响应指令 must-revalidate 的用法。
根据我的理解,通过省略 must-revalidate,您已经对您的用例有了完美的定义:
Cache-Control: max-age=86400, private
这控制请求的资源被视为新鲜的时间。这段时间过去后,答案不应再直接来自缓存,而应联系服务器以验证后续请求。在您的情况下,由于服务器提供了一个 ETag,iOS 向服务器发送一个带有 If-None-Match header 的请求。
验证
为了检查这一点,我使用了您的没有设置 NSURLCache 的 testRestfulAPI 方法,并在服务器端配置了 60 秒的最大时效,所以我不必等一天就可以检查结果。
之后,我每秒触发一次testRestfulAPI。我总是从缓存中得到想要的结果。并且Charles表明数据一定来自缓存,因为60秒没有联系服务器。
RFC 7234
这里引用了 RFC 7234(废弃了 RFC 2616)中的 5.2.2.1。它指出:
The must-revalidate directive is necessary to support reliable
operation for certain protocol features. In all circumstances a cache
MUST obey the must-revalidate directive; in particular, if a cache
cannot reach the origin server for any reason, it MUST generate a 504
(Gateway Timeout) response.
The must-revalidate directive ought to be used by servers if and only
if failure to validate a request on the representation could result in
incorrect operation, such as a silently unexecuted financial
transaction.
读完之后,如果您将自己置于缓存开发人员的视野中,您可以很好地想象当看到 must-revalidate 时,始终会联系原始服务器并且任何附加指令,例如 max-age 被简单地忽略了。在我看来,缓存在实践中经常表现出这种行为。
5.2.2.1章节还有一节。我不会隐瞒,内容如下:
The "must-revalidate" response directive indicates that once it has become stale, a cache MUST NOT use the response to satisfy subsequent requests without successful validation on the origin server.
这通常被解释为通过指定 max-age 和 must-revalidate 您可以确定内容何时过时(max-age 秒后),然后它必须在原始服务器上验证在它可以提供内容之前。
然而,实际上,由于上述原因,似乎 must-revalidate 总是导致对源服务器上的每个请求进行验证。
在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 headers 的 HTTP 200。没问题。
如果我没记错的话,Cache-Control: must-revalidate, max-age=86400, private
会告诉 NSURLCache 认为缓存在 24 小时内是新鲜的,并且在接下来的 24 小时内不会进行任何网络调用。
但事实并非如此,第二次http请求,居然发出了If-None-Match
headers出去,得到了HTTP 304.
在我看来,NSURLCache 部分工作。它可以缓存响应,但它不遵守 RFC 2616 Apple 文档描述的语义 here。仅供参考,我没有更改缓存策略,因此它使用默认的 NSURLRequestUseProtocolCachePolicy
.
我在谷歌上搜索了一天多的类似问题和其他遇到的类似问题,但我没有找到任何解决方案。有些人在 AFNetworking 的 github 问题中询问了同样的问题,但作者关闭了这个问题,因为它与 AFNetworking here and here 没有直接关系。
各种相关的 Whosebug 帖子也没有帮助我。
尝试更改这些行
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];
问题
问题是 Cache-Control 响应指令 must-revalidate 的用法。
根据我的理解,通过省略 must-revalidate,您已经对您的用例有了完美的定义:
Cache-Control: max-age=86400, private
这控制请求的资源被视为新鲜的时间。这段时间过去后,答案不应再直接来自缓存,而应联系服务器以验证后续请求。在您的情况下,由于服务器提供了一个 ETag,iOS 向服务器发送一个带有 If-None-Match header 的请求。
验证
为了检查这一点,我使用了您的没有设置 NSURLCache 的 testRestfulAPI 方法,并在服务器端配置了 60 秒的最大时效,所以我不必等一天就可以检查结果。
之后,我每秒触发一次testRestfulAPI。我总是从缓存中得到想要的结果。并且Charles表明数据一定来自缓存,因为60秒没有联系服务器。
RFC 7234
这里引用了 RFC 7234(废弃了 RFC 2616)中的 5.2.2.1。它指出:
The must-revalidate directive is necessary to support reliable operation for certain protocol features. In all circumstances a cache MUST obey the must-revalidate directive; in particular, if a cache cannot reach the origin server for any reason, it MUST generate a 504 (Gateway Timeout) response.
The must-revalidate directive ought to be used by servers if and only if failure to validate a request on the representation could result in incorrect operation, such as a silently unexecuted financial transaction.
读完之后,如果您将自己置于缓存开发人员的视野中,您可以很好地想象当看到 must-revalidate 时,始终会联系原始服务器并且任何附加指令,例如 max-age 被简单地忽略了。在我看来,缓存在实践中经常表现出这种行为。
5.2.2.1章节还有一节。我不会隐瞒,内容如下:
The "must-revalidate" response directive indicates that once it has become stale, a cache MUST NOT use the response to satisfy subsequent requests without successful validation on the origin server.
这通常被解释为通过指定 max-age 和 must-revalidate 您可以确定内容何时过时(max-age 秒后),然后它必须在原始服务器上验证在它可以提供内容之前。
然而,实际上,由于上述原因,似乎 must-revalidate 总是导致对源服务器上的每个请求进行验证。