绕过http响应头Cache-Control:如何设置缓存过期?

时间:2022-09-06 21:24:51

All http responses from a server come with the headers that inform our app not to cache the responses:

来自服务器的所有http响应都带有标题,告知我们的应用不要缓存响应:

Cache-Control: no-cache
Pragma: no-cache
Expires: 0

So, if you are making NSUrlRequests with default cache policy "NSURLRequestUseProtocolCachePolicy" then the app will always load data from the server. However, we need to cache the responses and the obvious solution would be to set these headers to some time for example (at backend side), set to 10 seconds. But I'm interested in a solution how to bypass this policy and cache every request for 10 seconds.

因此,如果您使用默认缓存策略“NSURLRequestUseProtocolCachePolicy”创建NSUrlRequests,则应用程序将始终从服务器加载数据。但是,我们需要缓存响应,显而易见的解决方案是将这些标头设置为某个时间(例如在后端),设置为10秒。但是我对如何绕过这个策略并将每个请求缓存10秒的解决方案感兴趣。

For that you need to setup shared cache. That might be done in AppDelegate didFinishLaunchingWithOptions:

为此,您需要设置共享缓存。这可能在AppDelegate didFinishLaunchingWithOptions中完成:

NSURLCache *URLCache = [[NSURLCache alloc] initWithMemoryCapacity:4 * 1024 * 1024
                                             diskCapacity:20 * 1024 * 1024
                                               diskPath:nil];
[NSURLCache setSharedURLCache:URLCache];

Then, we need to embed our code to force to cache a response. If you use an instance of AFHttpClient then it can be done by overriding the method below and manually storing the cache into the shared cache:

然后,我们需要嵌入我们的代码来强制缓存响应。如果您使用AFHttpClient的实例,则可以通过覆盖以下方法并手动将缓存存储到共享缓存中来完成:

- (NSCachedURLResponse *)connection:(NSURLConnection *)connection
              willCacheResponse:(NSCachedURLResponse *)cachedResponse {

  NSMutableDictionary *mutableUserInfo = [[cachedResponse userInfo] mutableCopy];
  NSMutableData *mutableData = [[cachedResponse data] mutableCopy];
  NSURLCacheStoragePolicy storagePolicy = NSURLCacheStorageAllowedInMemoryOnly;

  // ...

  return [[NSCachedURLResponse alloc] initWithResponse:[cachedResponse response]
                                                data:mutableData
                                            userInfo:mutableUserInfo
                                       storagePolicy:storagePolicy];
}

And the last thing is to set cachePolicy for the requests. In our case we want to set the same cache policy to all requests. So again, if you use an instance of AFHttpClient then it can be done by overriding the method below:

最后一件事是为请求设置cachePolicy。在我们的例子中,我们希望为所有请求设置相同的缓存策略。所以再次,如果您使用AFHttpClient的实例,那么可以通过覆盖以下方法来完成:

- (NSMutableURLRequest *)requestWithMethod:(NSString *)method path:(NSString *)path parameters:(NSDictionary *)parameters {

  NSMutableURLRequest *request = [super requestWithMethod:method path:path parameters:parameters];
  request.cachePolicy = NSURLRequestReturnCacheDataElseLoad;

  return request;
}

So far so good. "NSURLRequestReturnCacheDataElseLoad" makes to perform request first time and load response from cache all other times. The problem is, it's unclear how to set cache expiration time, for example 10 seconds.

到现在为止还挺好。 “NSURLRequestReturnCacheDataElseLoad”使得第一次执行请求并在其他时间从缓存加载响应。问题是,目前还不清楚如何设置缓存过期时间,例如10秒。

3 个解决方案

#1


10  

You can implement a custom NSURLCache that only returns cached responses that has not expired.

您可以实现仅返回尚未过期的缓存响应的自定义NSURLCache。

Example:

#import "CustomURLCache.h"

NSString * const EXPIRES_KEY = @"cache date";
int const CACHE_EXPIRES = -10;

@implementation CustomURLCache

// static method for activating this custom cache
+(void)activate {
    CustomURLCache *urlCache = [[CustomURLCache alloc] initWithMemoryCapacity:(2*1024*1024) diskCapacity:(2*1024*1024) diskPath:nil] ;
    [NSURLCache setSharedURLCache:urlCache];
}

-(NSCachedURLResponse *)cachedResponseForRequest:(NSURLRequest *)request {
    NSCachedURLResponse * cachedResponse = [super cachedResponseForRequest:request];
    if (cachedResponse) {
        NSDate* cacheDate = [[cachedResponse userInfo] objectForKey:EXPIRES_KEY];
        if ([cacheDate timeIntervalSinceNow] < CACHE_EXPIRES) {
            [self removeCachedResponseForRequest:request];
            cachedResponse = nil;
        }
    }

    return cachedResponse;
}

- (void)storeCachedResponse:(NSCachedURLResponse *)cachedResponse forRequest:(NSURLRequest *)request {
    NSMutableDictionary *userInfo = cachedResponse.userInfo ? [cachedResponse.userInfo mutableCopy] : [NSMutableDictionary dictionary];
    [userInfo setObject:[NSDate date] forKey:EXPIRES_KEY];
    NSCachedURLResponse *newCachedResponse = [[NSCachedURLResponse alloc] initWithResponse:cachedResponse.response data:cachedResponse.data userInfo:userInfo storagePolicy:cachedResponse.storagePolicy];

    [super storeCachedResponse:newCachedResponse forRequest:request];
}

@end

If this does not give you enough control then I would implement a custom NSURLProtocol with a startLoading method as below and use it in conjunction with the custom cache.

如果这没有给你足够的控制,那么我将使用如下的startLoading方法实现自定义NSURLProtocol,并将其与自定义缓存结合使用。

- (void)startLoading
{
    NSMutableURLRequest *newRequest = [self.request mutableCopy];
    [NSURLProtocol setProperty:@YES forKey:@"CacheSet" inRequest:newRequest];

    NSCachedURLResponse *cachedResponse = [[NSURLCache sharedURLCache] cachedResponseForRequest:self.request];
    if (cachedResponse) {  
        [self connection:nil didReceiveResponse:[cachedResponse response]];
        [self connection:nil didReceiveData:[cachedResponse data]];
        [self connectionDidFinishLoading:nil];
    } else {
        _connection = [NSURLConnection connectionWithRequest:newRequest delegate:self];
    }
}

Some links:

#2


7  

If somebody would be interested, here is Stephanus answer rewritten in Swift:

如果有人会感兴趣,这里是Stephanus在Swift中重写的答案:

class CustomURLCache: NSURLCache {

    // UserInfo expires key
    let kUrlCacheExpiresKey = "CacheData";

    // How long is cache data valid in seconds
    let kCacheExpireInterval:NSTimeInterval = 60*60*24*5;

    // get cache response for a request
    override func cachedResponseForRequest(request:NSURLRequest) -> NSCachedURLResponse? {
        // create empty response
        var response:NSCachedURLResponse? = nil

        // try to get cache response
        if let cachedResponse = super.cachedResponseForRequest(request) {

            // try to get userInfo
            if let userInfo = cachedResponse.userInfo {

                // get cache date
                if let cacheDate = userInfo[kUrlCacheExpiresKey] as NSDate? {

                    // check if the cache data are expired
                    if (cacheDate.timeIntervalSinceNow < -kCacheExpireInterval) {
                        // remove old cache request
                        self.removeCachedResponseForRequest(request);
                    } else {
                        // the cache request is still valid
                        response = cachedResponse
                    }
                }
            }
        }

        return response;
    }

    // store cached response
    override func storeCachedResponse(cachedResponse: NSCachedURLResponse, forRequest: NSURLRequest) {
        // create userInfo dictionary
        var userInfo = NSMutableDictionary()
        if let cachedUserInfo = cachedResponse.userInfo {
            userInfo = NSMutableDictionary(dictionary:cachedUserInfo)
        }
        // add current date to the UserInfo
        userInfo[kUrlCacheExpiresKey] = NSDate()

        // create new cached response
        let newCachedResponse = NSCachedURLResponse(response:cachedResponse.response, data:cachedResponse.data, userInfo:userInfo,storagePolicy:cachedResponse.storagePolicy)
        super.storeCachedResponse(newCachedResponse, forRequest:forRequest)

    }

}

#3


1  

Another possible solution is to modify the response object and clobber the Cache-Control headers from the server and replace them with your own desired values.

另一种可能的解决方案是修改响应对象并从服务器中删除Cache-Control标头,并用您自己想要的值替换它们。

There are two places you could do that.

你可以做两个地方。

You could do it in a NSURLSessionDataDelegate in func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, willCacheResponse proposedResponse: NSCachedURLResponse, completionHandler: (NSCachedURLResponse?) -> Void), but if you do it there, then can no longer use the usual completion handler-based methods getting results from session tasks.

您可以在func URLSession中的NSURLSessionDataDelegate中执行此操作(会话:NSURLSession,dataTask:NSURLSessionDataTask,willCacheResponse proposedResponse:NSCachedURLResponse,completionHandler:(NSCachedURLResponse?) - > Void),但如果您在那里执行,则无法再使用通常的完成基于处理程序的方法从会话任务中获取结果。

Another place you could do it is by defining a custom NSURLProtocol, which intercepts HTTP and HTTPS responses and modifies them.

您可以做的另一个地方是定义一个自定义NSURLProtocol,它拦截HTTP和HTTPS响应并修改它们。

#1


10  

You can implement a custom NSURLCache that only returns cached responses that has not expired.

您可以实现仅返回尚未过期的缓存响应的自定义NSURLCache。

Example:

#import "CustomURLCache.h"

NSString * const EXPIRES_KEY = @"cache date";
int const CACHE_EXPIRES = -10;

@implementation CustomURLCache

// static method for activating this custom cache
+(void)activate {
    CustomURLCache *urlCache = [[CustomURLCache alloc] initWithMemoryCapacity:(2*1024*1024) diskCapacity:(2*1024*1024) diskPath:nil] ;
    [NSURLCache setSharedURLCache:urlCache];
}

-(NSCachedURLResponse *)cachedResponseForRequest:(NSURLRequest *)request {
    NSCachedURLResponse * cachedResponse = [super cachedResponseForRequest:request];
    if (cachedResponse) {
        NSDate* cacheDate = [[cachedResponse userInfo] objectForKey:EXPIRES_KEY];
        if ([cacheDate timeIntervalSinceNow] < CACHE_EXPIRES) {
            [self removeCachedResponseForRequest:request];
            cachedResponse = nil;
        }
    }

    return cachedResponse;
}

- (void)storeCachedResponse:(NSCachedURLResponse *)cachedResponse forRequest:(NSURLRequest *)request {
    NSMutableDictionary *userInfo = cachedResponse.userInfo ? [cachedResponse.userInfo mutableCopy] : [NSMutableDictionary dictionary];
    [userInfo setObject:[NSDate date] forKey:EXPIRES_KEY];
    NSCachedURLResponse *newCachedResponse = [[NSCachedURLResponse alloc] initWithResponse:cachedResponse.response data:cachedResponse.data userInfo:userInfo storagePolicy:cachedResponse.storagePolicy];

    [super storeCachedResponse:newCachedResponse forRequest:request];
}

@end

If this does not give you enough control then I would implement a custom NSURLProtocol with a startLoading method as below and use it in conjunction with the custom cache.

如果这没有给你足够的控制,那么我将使用如下的startLoading方法实现自定义NSURLProtocol,并将其与自定义缓存结合使用。

- (void)startLoading
{
    NSMutableURLRequest *newRequest = [self.request mutableCopy];
    [NSURLProtocol setProperty:@YES forKey:@"CacheSet" inRequest:newRequest];

    NSCachedURLResponse *cachedResponse = [[NSURLCache sharedURLCache] cachedResponseForRequest:self.request];
    if (cachedResponse) {  
        [self connection:nil didReceiveResponse:[cachedResponse response]];
        [self connection:nil didReceiveData:[cachedResponse data]];
        [self connectionDidFinishLoading:nil];
    } else {
        _connection = [NSURLConnection connectionWithRequest:newRequest delegate:self];
    }
}

Some links:

#2


7  

If somebody would be interested, here is Stephanus answer rewritten in Swift:

如果有人会感兴趣,这里是Stephanus在Swift中重写的答案:

class CustomURLCache: NSURLCache {

    // UserInfo expires key
    let kUrlCacheExpiresKey = "CacheData";

    // How long is cache data valid in seconds
    let kCacheExpireInterval:NSTimeInterval = 60*60*24*5;

    // get cache response for a request
    override func cachedResponseForRequest(request:NSURLRequest) -> NSCachedURLResponse? {
        // create empty response
        var response:NSCachedURLResponse? = nil

        // try to get cache response
        if let cachedResponse = super.cachedResponseForRequest(request) {

            // try to get userInfo
            if let userInfo = cachedResponse.userInfo {

                // get cache date
                if let cacheDate = userInfo[kUrlCacheExpiresKey] as NSDate? {

                    // check if the cache data are expired
                    if (cacheDate.timeIntervalSinceNow < -kCacheExpireInterval) {
                        // remove old cache request
                        self.removeCachedResponseForRequest(request);
                    } else {
                        // the cache request is still valid
                        response = cachedResponse
                    }
                }
            }
        }

        return response;
    }

    // store cached response
    override func storeCachedResponse(cachedResponse: NSCachedURLResponse, forRequest: NSURLRequest) {
        // create userInfo dictionary
        var userInfo = NSMutableDictionary()
        if let cachedUserInfo = cachedResponse.userInfo {
            userInfo = NSMutableDictionary(dictionary:cachedUserInfo)
        }
        // add current date to the UserInfo
        userInfo[kUrlCacheExpiresKey] = NSDate()

        // create new cached response
        let newCachedResponse = NSCachedURLResponse(response:cachedResponse.response, data:cachedResponse.data, userInfo:userInfo,storagePolicy:cachedResponse.storagePolicy)
        super.storeCachedResponse(newCachedResponse, forRequest:forRequest)

    }

}

#3


1  

Another possible solution is to modify the response object and clobber the Cache-Control headers from the server and replace them with your own desired values.

另一种可能的解决方案是修改响应对象并从服务器中删除Cache-Control标头,并用您自己想要的值替换它们。

There are two places you could do that.

你可以做两个地方。

You could do it in a NSURLSessionDataDelegate in func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, willCacheResponse proposedResponse: NSCachedURLResponse, completionHandler: (NSCachedURLResponse?) -> Void), but if you do it there, then can no longer use the usual completion handler-based methods getting results from session tasks.

您可以在func URLSession中的NSURLSessionDataDelegate中执行此操作(会话:NSURLSession,dataTask:NSURLSessionDataTask,willCacheResponse proposedResponse:NSCachedURLResponse,completionHandler:(NSCachedURLResponse?) - > Void),但如果您在那里执行,则无法再使用通常的完成基于处理程序的方法从会话任务中获取结果。

Another place you could do it is by defining a custom NSURLProtocol, which intercepts HTTP and HTTPS responses and modifies them.

您可以做的另一个地方是定义一个自定义NSURLProtocol,它拦截HTTP和HTTPS响应并修改它们。