NSURLCache与NSURLSession不兼容:高速缓存控制:最大年龄:86000,专用,必须重新验证 [英] NSURLCache, together with NSURLSession, does not respect: Cache-Control: max-age:86000, private, must-revalidate

查看:138
本文介绍了NSURLCache与NSURLSession不兼容:高速缓存控制:最大年龄:86000,专用,必须重新验证的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在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.没问题.

如果我没看错,Cache-Control: must-revalidate, max-age=86400, private会告诉NSURLCache在24小时内认为该缓存是最新的,并且在接下来的24小时之内不会进行任何网络调用.

事实并非如此,第二次发出http请求时,它实际上发出了If-None-Match标头并返回了HTTP 304.

在我看来,NSURLCache可以部分正常工作.它可以缓存响应,但不遵循Apple文档描述的 RFC 2616 语义,因此此处.

各种相关的stackoverflow帖子也没有帮助我.

解决方案

问题

问题是使用Cache-Control响应指令必须重新验证.

据我所知,通过省略必须重新验证的用例,您已经对用例有了完美的定义:

Cache-Control: max-age=86400, private

这控制所请求的资源被视为新鲜的时间.在这段时间过去之后,答案不再应该直接来自缓存,而应该联系服务器以进行后续请求的验证.在您的情况下,由于服务器提供了ETag,因此iOS将向服务器发送带有If-None-Match标头的请求.

验证

要对此进行检查,我使用了没有NSURLCache设置的testRestfulAPI方法,并在服务器端配置了最长60秒的生存期,因此我不必等待一天来检查结果.

之后,我每秒触发一次testRestfulAPI.我总是从缓存中获得期望的结果. Charles表示数据必须来自缓存,因为60秒钟没有联系服务器.

RFC 7234

此处是5.2.2.1下RFC 7234(已取代RFC 2616)的引号.它指出:

必须重新验证指令才能支持可靠 某些协议功能的操作.在任何情况下,缓存 必须遵守必须重新验证的指令;特别是如果有缓存 由于任何原因都无法到达原始服务器,它必须生成504 (网关超时)响应.

仅当且仅当服务器应使用must-revalidate指令 如果未能验证表示中的请求可能导致 错误的操作,例如默默执行的财务 交易.

在阅读了这些内容之后,如果您将自己置于缓存开发人员的视野中,则可以很好地想象,当看到必须重新验证的内容时,始终会与原始服务器联系,而忽略任何其他指令(例如max-age) .在我看来,缓存在实践中经常准确地表明这种行为.

第5.2.2.1节还有另一节.我不会隐瞒,其内容如下:

必须重新验证" response指令指示,一旦过时,缓存必须在未经原始服务器成功验证的情况下,不得使用该响应来满足后续请求.

这通常被解释为,通过将max-age与must-revalidate一起指定,您可以确定内容何时过期(在max-age秒后),然后必须在原始服务器上进行验证,然后才能提供内容./p>

但是,实际上,由于上​​述原因,似乎必须重新验证总是会导致对原始服务器上的每个请求进行验证.

In AppDelegate.m, I configured:

NSURLCache *sharedURLCache = [[NSURLCache alloc] initWithMemoryCapacity:20 * 1024 * 1024 diskCapacity:100 * 1024 * 1024 diskPath:@"FhtHttpCacheDir"];

Then the http request:

- (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];
    }
}

The first time it requests, it got HTTP 200 with Etag + Cache-Control headers. No problem.

If I am not wrong, Cache-Control: must-revalidate, max-age=86400, private will tell NSURLCache to consider the cache as being fresh within 24 hours and will not make any network calls within the next 24 hours.

But it is not the case, the second time the http request is made, it actually sends out If-None-Match headers out and got back HTTP 304.

It appears to me that NSURLCache is partially working. It can cache response, but it does not respect RFC 2616 semantics as Apple doc describes so here. FYI, I did not change the cache policy so it uses the default NSURLRequestUseProtocolCachePolicy.

I googled for more than a day for similar issues and other experienced similar ones but I have not found any solutions. Some asked about the same problem in AFNetworking's github issues but the author closes the issue as it is not directly related to AFNetworking here and here.

Also various related stackoverflow posts did not help me either.

解决方案

Problem

The problem is the usage of the Cache-Control response directive must-revalidate.

By omitting must-revalidate you already have the perfect definition of your use case as far as I've understood it:

Cache-Control: max-age=86400, private

This controls how long the requested resource is considered fresh. After this time has elapsed, the answer should no longer come directly from the cache instead the server should be contacted for validation for subsequent requests. In your case since the server supplies an ETag, iOS sends a request with an If-None-Match header to the server.

Verification

To check this, I used your testRestfulAPI method without NSURLCache settings and configured a maximum age of 60 seconds on the server side, so I don't have to wait a day to check the result.

After that, I triggered testRestfulAPI once per second. I always got the desired result from the cache. And Charles showed that the data must come from the cache because the server was not contacted for 60 seconds.

RFC 7234

Here is a quote from RFC 7234 (which obsoletes RFC 2616), under 5.2.2.1. it states:

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.

After reading that and if you put yourself in the view of a cache developer, you can well imagine that when a must-revalidate is seen, the original server is always contacted and any additional directives such as max-age are simply ignored. It seems to me that caches often show exactly this behavior in practice.

There is another section in chapter 5.2.2.1. which I will not conceal and which reads as follows:

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.

This is often interpreted that by specifying max-age together with must-revalidate you can determine when a content is stale (after max-age seconds) and then it must validate at the origin server before it can serve the content.

In practice, however, for the reasons given above, it seems that must-revalidate always leads to a validation of each request on the origin server.

这篇关于NSURLCache与NSURLSession不兼容:高速缓存控制:最大年龄:86000,专用,必须重新验证的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆