26

I've encountered next problem with Post/Redirect/Get pattern. When performing GET after redirect Chrome takes the page from cache. So user sees stale data.

302 chrome from cache

I've tried following to force/support revalidation

if (request.checkNotModified(sinceLastTweet)) return null;
response.setHeader("Cache-Control", "no-cache");
response.setHeader("Last-Modified", String.valueOf(sinceLastTweet));

But only no-store causes a server request.

Why Chrome takes the page from cache when performing redirect?


enter image description here

@RequestMapping(method = GET)
public String home(ModelMap model, @PathVariable String username, HttpServletResponse response, WebRequest request) {
    // response.setHeader("Cache-Control", "no-store");

    List<Tweet> tweets = tweetRepository.findAll();
    // long sinceLastTweet = tweets.get(0).getTimestamp().toEpochMilli();
    // if (request.checkNotModified(sinceLastTweet)) return null;
    // response.setHeader("Cache-Control", "no-cache");
    // response.setHeader("Last-Modified", String.valueOf(sinceLastTweet));

    model.addAttribute("tweets", tweets);
    model.addAttribute("tweet", new Tweet());
    model.addAttribute("username", username);

    return "home";
}
user2418306
  • 2,173
  • 1
  • 19
  • 29
  • 1
    What version of spring and are you using spring security? Specifically I'm wondering if you are using spring security what options are set for the security HTTP response headers. https://docs.spring.io/spring-security/site/docs/current/reference/html/headers.html – Sean Carroll May 06 '16 at 03:13
  • 1
    @SeanCarroll I'm not using Spring Security. Spring version is `4.2.4`. – user2418306 May 06 '16 at 06:06

5 Answers5

13

1) You have to try to force Chrome to not use the cache,

How to control web page caching, across all browsers?

Like the answer of that question suggest you have to put this header in the http response:

response.header("Cache-Control: no-cache, no-store, must-revalidate"); // HTTP 1.1.
response.header("Pragma: no-cache"); // HTTP 1.0.
response.header("Expires: 0"); // Proxies.

2) You can make a little trick

You can program to respond any request with the follow format with the same controller:

http://localhost:8080/spitter/username-{timestamp}

Then you change your links in your page to have the last timestamp before send the request.

In this case, event if chrome caches the page it doesn't matter.

Instead of timestamp you can use any other methodology, like "autoincrement" or "uuid".

Community
  • 1
  • 1
Troncador
  • 2,856
  • 2
  • 18
  • 39
  • 1
    Note that since this question is tagged "Java", the code sample would be better written as: `response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate"); /* HTTP 1.1. */ response.setHeader("Pragma", "no-cache"); /* HTTP 1.0. */ response.setHeader("Expires", "0"); /* Proxies. */` – rinogo Jan 25 '17 at 16:28
7

If one Chrome instance has cached page /username the page is not hit by that Chrome as effect of the redirect because Chrome reads the Location header and serves the cached page.

Maybe one time you visited the page while trying cache headers and now your Chrome thinks it must cache it, maybe even forever.

You need to invalidate the cache (with that Chrome) for the /username page:

  1. setup the /username page to be always served with headers that disable caching, like those suggested by rev_dihazum or how is done in Spring WebContentGenerator.
  2. Clear the cache manually or visit /username and hit CTRL+R while being in the page: page will be actually requested, cache disabling headers will be read and from that moment the page will be again requested each time to the server, including when is requested as effect of a redirect.

If the page is public and users, whose browsers you cannot control, have already cached it, then problem is worse and you may choose one of the following:

  1. wait for cache to expire
  2. change name to the page
  3. add some random number, more or less as suggested by prad121, to the Location URL to make it unique, a variation of 2)

Note that Last-Modified time format is Tue, 15 Nov 1994 08:12:31 GMT, you may use the setDateHeader method to set it as a long

You may look into WebContentGenerator to see other caching code in Spring.

Testo Testini
  • 2,120
  • 16
  • 27
  • 1
    Note: The [answer](http://stackoverflow.com/a/37153048/114558) by @Troncador includes the "no cache" headers that will be needed. (This answer only suggests they exist somewhere in the Spring code...) – rinogo Jan 25 '17 at 16:27
  • That's true, however the WebContentGenerator code of Spring shows how to do it nicely with constants and is a good learning start point – Testo Testini Jan 27 '17 at 17:08
1

Add some random numbers(current time in milisecs) at the end of the URL. like service URL is

http://localhost:7001/rest/getBook/5

So instead of making call to above URL, u append some random numbers at end while making the above service call

/rest/getBook/5?_time-in-milisec

It requires little be tweaking of service call in javascript.

URL = http://localhost:7001/rest/getBook/5 + "?_" +new Date()

Since each time URL is different, It does not refer to browser cache.

SkyWalker
  • 24,796
  • 7
  • 62
  • 118
prad121
  • 21
  • 3
  • This is an old bad practise because user will have as many cached versions as there were queries to this URL. – Jehy May 10 '16 at 09:49
  • In my case, Chrome is caching my 302 redirect, adding a random query string parameter appears to have cured the problem. – rooch84 May 07 '20 at 14:42
1

Maybe you can try using an ETag for your page.

I am not exactly sure if I correctly understand your redirect scenario though.

Let's say you have some display page that contains a username and you have some second page that provides Editing for your data and you want to be able to change the username there and press a button to save it an get back to the display page thereafter.

One option would be to include the username in the URL (like ...&username=Jack). Since the username has changed, the URL would change as well (i.e. ...&username=Jim). In this case, Chrome would not have a cached version of the display page for Jack and would get a fresh version of it from the server. (If you have any field that you don't want to mention explicitly in the URL you could calculate a checksum over all fields and add this as a parameter like ...&check=12345678).

Another option would be to have the server generate an ETag - so let's look into this in detail. What we want is that the server app is in charge to tell the browser if the data needs a refresh or can be taken from the Cache, right?

Using Etag for letting the server determine if the data is up-to-date or needs refresh

{ Please forgive me that I am not using your very example here since I am currently developing on a different Stack - however, I think your problem can be solved with your Java/Spring Stack the same way }

Have a look at the screenshot in the section Response Headers and about in the middle you see ETag. This id is generated by the server - so the server app is in control here. (Maybe this article helps you if you haven't implemented an ETag yet.)

So what happens? As soon as the content changes on the server, the ETag changes as well - the Browser sees the ETag in the response, respects that the server is in charge and asks the server if the content has changed and if so kicks out the cached page with the expired content and requests a new one from the server.

You have to make sure, of course, that as soon as the server learns that the user has changed from Jack to Jim (or any other field accordingly) the server app generates the new ETag for this user's display page.

Hope it helps!

dr. rAI
  • 1,455
  • 1
  • 9
  • 14
0

You can use POSTs instead of GETs. POSTs are not cached.