Proper If-Modified-Since handling that works with Firefox
Posted on: March 19th, 2008Finally, I managed to write an If-Modified-Since
handling that also works with Firefox.
The scenario I had to deal with looks like the following: there is a web site whose content can be changed by a few users. Those users can log in to the page to edit it. The fact whether they are logged in or not is saved in a cookie (using PHP sessions), you cannot determine from a URL if a user is logged in or not. Without the possibility to log in, the handling of If-Modified-Since
headers is easy: you send a Last-Modified
header (in my case, the modification time is easy to find out) and if this time is greater or equal to an existing If-Modified-Since
header, you send the 304 Not Modified
status without any content. To comply with the standard, we want to handle If-Unmodified-Since
headers as well. For those, the same rules apply, if the modifiction time is greater or equal to an existing If-Unmodified-Since
header, the 412 Precondition Failed
status is sent.
Things look a bit more complicated when you want to implement the login mechanism that I have described above. One problem, of course, is that the page itself has not been modified when the user logs in, so the browser will use the version from the cache and the user will not see that he is logged in, so he cannot edit the page. This one is fixed by sending the current time as Last-Modified
as long as the user is logged in. So the browser has to reload the page every time.
The second problem you will face is that Firefox usually uses the version from the cache without checking if it has changed, even when you send Cache-Control: must-revalidate
. (By the way, I also send Pragma: must-revalidate
to overwrite any rubbish that PHP sends during session_start()
.) This is easily fixed by sending Expires: 0
. Now Firefox reloads the page every time and only uses the version from the cache if it receives a 304
.
Let’s assume that the user does not change anything and logs out. Now, it seems to him that he is still logged in because the brower sends the last Last-Modified
time it received as If-Modified-Since
. The page has not been modified since then, so the version from the cache, where the user is logged in, will be used. At first, I tried to change this behaviour by sending a Cache-control: no-cache
header. You have to pay attention with this, as this only tells the browser not to load a cached page, but it still saves it (at least Firefox, I don’t know if this is the correct behaviour). So the next time the browser requests the page, it will send a If-Modified-Since
header with this newer cached page. To avoid this, also send the no-store
Cache-Control
header. (I send Cache-Control: no-cache,no-store
as well as Pragma: no-cache,no-store
.)
So, as a summary, send Cache-Control: no-cache,no-store
as well as the current time as Last-Modified
header as long as the user is logged in. Send Cache-Control: must-revalidate
and Expires: 0
as long as he isn’t.
Firefox caching behaviour
Posted on: March 9th, 2008As I have posted before, I am trying to implement a proper If-Modified-Since
handling to some web page. A problem I had to manage was that you could log in on that page, which caused the user to have to reload the page in order to be logged in, as the browser correctly used the cached not-logged-in version. I wanted to solve this problem by always sending the current time stamp as Last-Modified
header, and thus never sending a 304 Not Modified
(the fact that logged-in users cannot use the cache then is acceptable, as the number of user accounts is quite limited). Firefox did not like that solution, it kept using the cached page without checking if it had changed (wheres in Opera everything worked fine).
What I wanted to achieve Firefox to do exactly was only to use the cache if it received a 304 Not Modified
status. As I could not find out anything detailed about this on the Internet, I tried several combinations of HTTP headers, such as Cache-Control: must-revalidate
, which did not have any effect. The working solution was simple but not easy to find: Expires: 0
.
PHP sending strange Last-Modified headers
Posted on: March 8th, 2008At the moment I am trying to implement a proper handling of Last-Modified
and If-Modified-Since
headers (RFC 2616 is really useful, by the way) and have experienced very strange behaviour of PHP during sending Last-Modified
headers.
One smaller thing that occured to me was session_start()
to send Cache-Control
, Pragma
, and Expires
headers. In PHP, you can simply remove them from the sent headers by calling header("Pragma: ", true);
, for example.
Well, I set the Last-Modified
header to some date("r")
value, and it happened that my browser always received the time of the request (in GMT instead of CET) instead of the time I sent. When I sent "Last-Modified: 0"
, this was converted to a correct HTTP GMT date string. Sending the same thing as X-Last-Modified
for example sent exactly what I set although. (By the way, HTTP trace works better and faster than Firefox’s Web Developer Toolbar.)
It seems to be that PHP tries to parse and correct the Last-Modified header before sending it, and this “feature” is a little buggy. I fixed the problem by simply using gmdate("r")
instead. My PHP version is 5.2.5-pl1-gentoo, by the way.