Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RCTImageLoader always caches images (incl. 404s) and ignores HTTP cache response headers #7571

Closed
rocman opened this issue May 15, 2016 · 40 comments
Labels
Platform: iOS iOS applications. Ran Commands One of our bots successfully processed a command. Resolution: Locked This issue was locked by the bot.

Comments

@rocman
Copy link
Contributor

rocman commented May 15, 2016

As a result, if a file is missing at the beginning, a 404 response is cached and act as a direct response to the later requests, even when the file exists.
Reviewed the code (https://github.com/facebook/react-native/blob/ed1ee9bc0f3ec051ce0a10e030c532953ce7710c/Libraries/Image/RCTImageLoader.m), in line #408, storeCachedResponse is called no matter what the status code is.

@rocman
Copy link
Contributor Author

rocman commented May 15, 2016

And I guess the caches are not expiring...

@ide ide changed the title 404 response is cached in RCTImageLoader RCTImageLoader always caches images (incl. 404s) and ignores HTTP cache response headers May 15, 2016
@DanielMSchmidt
Copy link
Contributor

@facebook-github-bot feature

@ghost
Copy link

ghost commented May 17, 2016

Hey @rocman! Thanks for opening the issue, however it looks like a feature request. As noted in the Issue template we'd like to use the GitHub issues to track bugs only. Can you implement the feature as a standalone npm module? If not consider sending a pull request or a create an entry on Product Pains. It has a voting system and if the feature gets upvoted enough it might get implemented. Closing this now, thanks for understanding!

@ghost ghost closed this as completed May 17, 2016
@ghost ghost added the Ran Commands One of our bots successfully processed a command. label May 17, 2016
@rocman
Copy link
Contributor Author

rocman commented May 17, 2016

nonono, it's not a feature request. It's a bug report.

@rocman
Copy link
Contributor Author

rocman commented May 17, 2016

A 404 response is definitely not suitable to cache.

@DanielMSchmidt
Copy link
Contributor

Oh sorry, I thought the bug you were reporting was that the cache never expires.
@facebook-github-bot reopen

@ide ide reopened this May 17, 2016
@ide ide added the Platform: iOS iOS applications. label May 17, 2016
@rocman
Copy link
Contributor Author

rocman commented May 22, 2016

And it seems that nowhere is doing the job to clear the expired cache.

@namse
Copy link
Contributor

namse commented May 31, 2016

I think storeCachedResponse should be deleted and we just use NSURLRequest's cache policy. Because storeCachedResponse dosen't care about expiration of cache. that function just store on cache, isn't it?
If it is right, I will make a Pull Request for this. I'm just confusing a little bit about storeCachedResponse that really just store without cache expiration.

@ide
Copy link
Contributor

ide commented May 31, 2016

Sometimes we should cache 404s if the server says to. @skatpgusskat's analysis looks right. If we want more control over the cache (and I think we do), we should make storeCachedResponse follow NSURLRequest or the HTTP spec's caching policy.

@namse
Copy link
Contributor

namse commented Jun 1, 2016

@ide I've just tested RCTNetworking.m's networkTaskWithRequest, they just perfectly work caching when the image file on server was deleted(so, 404) but before set Cache-Control with enough max-age. and after max-age seconds later, the response become 404.

Do we really need more control of cache? for what?

I think the best picture is 'use default cache without storeCachedResponse, and additionally cache decoded data'.
And the flow is below,

  1. Use networkTaskWithRequest first
  2. On response, make cache key with URL & Date(of response's header) and check the decoded cached image is in _decodedCache(NSCache)
  3. If there is Decoded and Cachced image, use that and return as UIImage, or there is not, return response's data as NSData
  4. If the NSData did return, decode it and cache on _decodedCache with key(URL & Date)

I figured out that the 'Date' fields on response's header wasn't changed if it is cached by default protocol. so it could be part of cache(NSCache)'s key.

@ide
Copy link
Contributor

ide commented Jun 1, 2016

@nicklockwood might be the best person to comment on that caching policy.

Do we really need more control of cache? for what?

Yes, being able to inspect what's in cache is useful. Not sure if we do that today but it's an API I think we will want eventually.

@namse
Copy link
Contributor

namse commented Jun 2, 2016

@ide Could you explain me more why it is useful to be able to inspect what's in cache?
Because we could benchmark the cache?

At the exact, the reason Why do we need to be able to inspect what's in cache is, to use cached data. (I think.)
If it is true, I don't think we really need to provide API because as I said basically we can know the request was cached or not.

When we requested data and get response, we can know 2 facts, '(1) that was on cache' or '(2) that wasn't so we request to server'.
Do we need any more information more than (1) and (2)? I don't think we need more information.

And then finally if it was in cache, we just provide decoded cached image data(if we have decoded one on decodedImageCache. if we don't, just let him decode and store to cache), it seem simple and best, doesn't it?

In this way we couldn't know response is cached before request data. and my opinion is 'do we really need that information before request data even we could use well-working algorithm processing general caching policy like 'Cache-Control' things'.

@rocman
Copy link
Contributor Author

rocman commented Jun 2, 2016

@skatpgusskat

Do we need any more information more than (1) and (2)?

For example, the expire date of the cache. The cache can be expired, and as a result, the info about the expiring policy is needed, and as a further result, the logic to manage the cache is needed.

@namse
Copy link
Contributor

namse commented Jun 2, 2016

@rocman

@ide I've just tested RCTNetworking.m's networkTaskWithRequest, they just perfectly work caching when the image file on server was deleted(so, 404) but before set Cache-Control with enough max-age. and after max-age seconds later, the response become 404.

Even default cache system make to expire data with caching policy, do we need to expire the cache ourselves?

@rocman
Copy link
Contributor Author

rocman commented Jun 2, 2016

@skatpgusskat
RCTNetworking is working well. However, RCTImageLoader create and manage another cache on top of it, without expiring the cache, which prevents all the later network requests on the cached image requests from sending to server.
It seems that, just let RCTNetworking do the cache jobs, and everything will be fine.

@namse
Copy link
Contributor

namse commented Jun 2, 2016

Back to the basic, the main point is 'NSURLCache's storeCachedResponse and cachedResponseForRequest doesn't work with cache policy'.
And my point is 'why don't we stop using NSURLCache and just use NSURLRequest's default caching system'

Here is NSURLRequest's cache policy logic.
http_caching_2x

@rocman
Copy link
Contributor Author

rocman commented Jun 2, 2016

@skatpgusskat I have the same idea with you.

@namse
Copy link
Contributor

namse commented Jun 2, 2016

If other guys, @ide, @nicklockwood or someone who manage this part agree this idea, I'll gladly make code and PR.

@tedzhou
Copy link

tedzhou commented Jun 2, 2016

@skatpgusskat I agree. I believe NSURLRequest works just fine.

@nicklockwood
Copy link
Contributor

In my experience, cache control headers are very rarely set correctly on the server side, leading to images being expired unnecessarily. iOS's default caching policy is also somewhat opaque and subject to change at Apple's whim - I don't think we want to just rely on the defaults.

That said, caching a 404 is obviously a mistake. We should only be caching successful image responses, so please do put up a PR to fix that.

I'm open to making the caching system more accessible - e.g. moving the image/network cache into its own module and exposing it to JS. I did have a diff along those lines long ago, but it never came to anything.

@namse
Copy link
Contributor

namse commented Jun 2, 2016

@nicklockwood I agree that iOS's default cahcing policy is opaque.

But if we use 'NSURLRequest'(we didn't set any NSURLRequestCachePolicy, right?), it mean we still use iOS's default caching policy as above picture with defeault setting : NSURLRequestUseProtocolCachePolicy.

What is the opaque one on iOS's default caching policy? I think the only one is 'when the cache control doesn't set up, how long time caching work'.
As you know, Apple said 'the stale cache will fetch again'. and the opaque one is what the exactly 'stale' means.

from the experiences, people said on iOS, 6 ~ 12 hours later the cached data(that has no cache control or expiration setting before) is deleted, so I think that is 'stale'.

What we can get when we provide image/network cache module is

  1. can set the 'stale's standard time.
  2. can provide information that url's response is cached.
  3. can provide decoded image cahcing (I think It is really good)

And as I said above, the No 2. is not useful comparing that we use default caching system. no reason why we should provide No 2., logically.

Do we really need provide No 1.? If there are people who care about 'stale', he should set the cache control up on server side. Let's think about the general case.
And the biggest problem is we should make code that our customized cache work with HTTP Spec's cache control, FOR ONLY ONE - Setting the OUR 'STALE' STANDARD.

Without making the code which provide our cache with HTTP Spec, We cannot solve 404 response cache bug. Not only 404, the Image has changed on server, the Client's Image will not change before using all cache(200MB, on disk cache, code). but if we use NSURLRequest's default cache policy, we don't have to make that codes. I think that is waste of time or overtechnologizing.

I strongly suggest just using default cache policy. and if you don't agree with me, please let me know why. I'm so glad to be helpful to solve this problem.

@ide
Copy link
Contributor

ide commented Jun 2, 2016

In my experience, cache control headers are very rarely set correctly on the server side, leading to images being expired unnecessarily.

I really think we should honor the server's cache headers by default unless we explicitly override them on the client. It's better to refetch an image and err on the side of correctness than to have a stale cache.

It could be useful to let the client override the server's cache headers if the programmer is explicit about it. For example: <Image src={...} cachePolicy={???} />, where cachePolicy is a string like "always", "never", "server" (HTTP spec), or "system" (Apple/Google arbitrary behavior), and later could be a function but let's start simple.

@namse
Copy link
Contributor

namse commented Jun 2, 2016

@ide to provide client cachePolicy like 'always', or 'never', we can use NSURLRequestReloadIgnoringLocalCacheData or NSURLRequestReturnCacheDataDontLoad.
Also If we want to provide cache's max-old on client side, we can set the request's header with Cache-Control: max-old=XXX. That's how HTTP control the cache on client side, I believe.

@namse
Copy link
Contributor

namse commented Jun 3, 2016

I have my doubts about creating the Cache Module. So, here is my road map,

  1. provide cachePolicy property on JS side, with string 'always', 'never', 'server', 'system', or number N (N second later expired), by setting NSURLRequest's NSURLRequestCachePolicy
  2. defaultly use NSURLRequestUseProtocolCachePolicy
  3. control the cache with default cache system.
  4. provide decoded image cache with stored with key - ${URL}-${server response header's Date}(because response has cached, the date will not change until fetching new one.)

Anything else? do you agree with me?

@tedzhou
Copy link

tedzhou commented Jun 3, 2016

@skatpgusskat
One more thing. Assume that we receive a image with Cache-Control:mag-age=600, then 10mins later client should make a request from server right? If the client is offline at that moment, we would get a empty imageView.

Generally, image frameworks should use the last received image instead.

@namse
Copy link
Contributor

namse commented Jun 3, 2016

@tedzhou good point. the NSURLRequestReturnCacheDataElseLoad is fit to it. then we could provide 'offline' mode for cachePolicy property.

I think it couldn't be default because generally programmer think if server or cache has no that data, then it should be fail, so they will make a logic with that thought.

reference is here.

@ide
Copy link
Contributor

ide commented Jun 3, 2016

do you agree with me?

Wait for sign-off from facebook and whether that proposal meets their current needs and provides groundwork for their future plans.

cc @vjeux -- how much of a plan for the network cache have you thought through yet, and are there explicit goals and non-goals for it?

@vjeux
Copy link
Contributor

vjeux commented Jun 3, 2016

I have no idea what's the status on this. @nicklockwood commented on the thread, any thoughts?

@namse
Copy link
Contributor

namse commented Jun 3, 2016

@vjeux may it be helpful to give you summary arranged this issue and discussion above?

It just looks like... there is a plan of network cache for react-native, so we should be careful with that plan's direction, right?

@namse
Copy link
Contributor

namse commented Jun 7, 2016

@ide could you let us when it is ready please?

@ide
Copy link
Contributor

ide commented Jun 7, 2016

Just as a heads up, it sounds like there is no explicit plan or timeline.

@namse
Copy link
Contributor

namse commented Jun 19, 2016

@ide even there are no dicision, it looks better provide Image module with default cache system. I start to make that code.
it is so ugly situation to let user use odd image url to ignore stupid current cache system.

ghost pushed a commit that referenced this issue Jul 21, 2016
…system

Summary:
Before this PR, ```RCTImageLodaer```'s Cache was too big(200MB on disk) and It doesn't work with HTTP Cache-Control header. So to provide dynamic image, the users must have to add random value on url( ex. adding current date) to avoid cache.

So I change that cache system to default ```NSURLRequest```'s cache system, which is well-working with HTTP specs. As the discussion on this issue #7571 , making custom cache policy processor is not ready yet and useless, over-tech things, I think.

Even we have no plan about image cache system(or would change plan later), before having a nice plan, I think we should let user use image module with common HTTP Specs.

So I remove custom ```NSURLCache```, and make logic like below,

1. try fetch image,

2. on response, get ```Date``` on response's header and make ```cacheKey``` with ```Date```.
> (why? because if ```NSURLRequest```'s response was cached, the response's ```Date``` header dosen't change.)

3. find decoded imag
Closes #8235

Reviewed By: bnham

Differential Revision: D3469086

Pulled By: javache

fbshipit-source-id: 35a5552cda6e6c367481020bbf3c28eb4a9d0207
@namse
Copy link
Contributor

namse commented Jul 22, 2016

Hey guys, here is good news! the PR has accepted!

@ide
Copy link
Contributor

ide commented Jul 22, 2016

^ Closing this issue since RN iOS is now using NSURLCache's behavior on master.

@ide ide closed this as completed Jul 22, 2016
@npomfret
Copy link
Contributor

npomfret commented Aug 5, 2016

What in the 404 response headers causes images to be cached? The server I'm using responds with the following (only), is it the e-tag?

Accept-Ranges:bytes
Content-Length:34
Content-Type:application/json
Date:Fri, 05 Aug 2016 13:29:55 GMT
Etag:"7-301c4533490e06afd01653ddbcafab48"
Server:CouchbaseLite 1.3.0 (build 62)

@namse
Copy link
Contributor

namse commented Aug 5, 2016

@npomfret you mean, the server responds the 404 code with that header? Nothing caches on 404 response. I actually dont understand what you say.

@rocman
Copy link
Contributor Author

rocman commented Aug 6, 2016

@npomfret A "max-age" or "expires-date" header field would be needed.

@npomfret
Copy link
Contributor

npomfret commented Aug 6, 2016

@skatpgusskat the title of this issue is 'RCTImageLoader always caches images (incl. 404s) and ignores HTTP cache response headers', and @ide says "Sometimes we should cache 404s if the server says to".

I'm experiencing cached images that are 404ing, and the headers I posted are the headers that the server responds with when there's a 404. My question is: do/should these headers result in a cached 404? Because it seems they are being cached (in RN 0.29) and I just get a blank image - even when the image becomes available and is no longer 404ing.

@rocman
Copy link
Contributor Author

rocman commented Aug 6, 2016

@npomfret I was suffering from the same issue with you. It seems that @skatpgusskat has fixed it with a pull request, and the pull request was accepted few days ago. Maybe in the next version, the problem will be gone.

@npomfret
Copy link
Contributor

npomfret commented Aug 6, 2016

Ah! Awesome, thanks.

mpretty-cyro pushed a commit to HomePass/react-native that referenced this issue Aug 25, 2016
…system

Summary:
Before this PR, ```RCTImageLodaer```'s Cache was too big(200MB on disk) and It doesn't work with HTTP Cache-Control header. So to provide dynamic image, the users must have to add random value on url( ex. adding current date) to avoid cache.

So I change that cache system to default ```NSURLRequest```'s cache system, which is well-working with HTTP specs. As the discussion on this issue facebook#7571 , making custom cache policy processor is not ready yet and useless, over-tech things, I think.

Even we have no plan about image cache system(or would change plan later), before having a nice plan, I think we should let user use image module with common HTTP Specs.

So I remove custom ```NSURLCache```, and make logic like below,

1. try fetch image,

2. on response, get ```Date``` on response's header and make ```cacheKey``` with ```Date```.
> (why? because if ```NSURLRequest```'s response was cached, the response's ```Date``` header dosen't change.)

3. find decoded imag
Closes facebook#8235

Reviewed By: bnham

Differential Revision: D3469086

Pulled By: javache

fbshipit-source-id: 35a5552cda6e6c367481020bbf3c28eb4a9d0207
@facebook facebook locked as resolved and limited conversation to collaborators Jul 19, 2018
@react-native-bot react-native-bot added the Resolution: Locked This issue was locked by the bot. label Jul 19, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Platform: iOS iOS applications. Ran Commands One of our bots successfully processed a command. Resolution: Locked This issue was locked by the bot.
Projects
None yet
Development

No branches or pull requests

9 participants