Why doesn't the browser send an "If-None-Match" header?

I am trying to load (and hopefully cache) a dynamically loaded image in PHP. Here are the sent and received headers:

Request:

GET /url:resource/Pomegranate/resources/images/logo.png HTTP/1.1 Host: pome.local Connection: keep-alive Cache-Control: max-age=0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.22 (KHTML, like Gecko) Ubuntu Chromium/25.0.1364.160 Chrome/25.0.1364.160 Safari/537.22 Accept-Encoding: gzip,deflate,sdch Accept-Language: en-US,en;q=0.8 Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3 Cookie: PHPSESSID=fb8ghv9ti6v5s3ekkmvtacr9u5 

Answer:

 HTTP/1.1 200 OK Date: Tue, 09 Apr 2013 11:00:36 GMT Server: Apache/2.2.22 (Ubuntu) X-Powered-By: PHP/5.3.14 ZendServer/5.0 Expires: Thu, 19 Nov 1981 08:52:00 GMT Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0 Pragma: no-cache Content-Disposition: inline; filename="logo" ETag: "1355829295" Last-Modified: Tue, 18 Dec 2012 14:44:55 Asia/Tehran Keep-Alive: timeout=5, max=98 Connection: Keep-Alive Transfer-Encoding: chunked Content-Type: image/png 

When I reload the URL, exactly the same headers are sent and received. My question is what should I send in my answer to see the If-None-Match header in a subsequent request?

NOTE. I believe that these headers were made recently, although I can’t be sure, but I think the browsers have changed to no longer send the If-None-Match header (I saw this header). I am testing Chrome and Firefox and both cannot send the title.

+11
source share
4 answers

You say Cache-Control: no-store, no-cache - but expect caching to happen?

Delete these values ​​(I think that must-revalidate, post-check=0, pre-check=0 can / should be saved - they tell the browser to check with the server if there have been any changes).

And I would stick with Last-Modified (if changes in your resources can be detected only using this criterion) - ETag is a more difficult thing to process (especially if you want to deal with it in your PHP script), and Google PageSpeed / YSlow is also advised against this.

+18
source

Same problem, similar solution

I was trying to determine why Google Chrome will not send If-None-Match headers when visiting the site I'm developing. (Chrome 46.0.2490.71 m, although I'm not sure how relevant the version is.)

This is a different, albeit very similar, answer than the ultimately quoted FP (in the commentary on the accepted answer), but it solves the same problem:

The browser does not send the If-None-Match header in subsequent requests "when it should" (i.e. ETag Server-side logic, through PHP or similar, was used to send ETag or Last-Modified in the first response),

Background

Using a self-signed TLS certificate, which in Chrome renders the lock red, changes the behavior of Chrome's caching. Before trying to fix this kind of problem, install the self-signed certificate in the effective trusted root store and completely restart the browser, as described at https://stackoverflow.com/a/4125/ .

1st Epiphany: if-None-Match requires an ETag from the server, first

I quickly realized that Chrome (and possibly most or all other browsers) would not send an If-None-Match header until the server sent an ETag header in response to a previous request. Logically, that makes sense; after all, how can Chrome send an If-None-Match when it is never assigned a value?

This made me look at my server-side logic - in particular, how headers are sent when I want the user agent to cache the response - to determine why the ETag header ETag not sent in response to Chrome. The first request for a resource. I did my best to incorporate the ETag header into the logic of my application.

I use PHP, so the @Mehran (OP) comment popped up on me (it says calling header_remove() before sending the necessary headers related to the cache solves the problem).

Frankly, I was skeptical of this decision because a) I was pretty sure that PHP would not send any native headers by default (and this is not so, given my configuration); and b) when I called var_dump(headers_list()); shortly before setting my custom caching headers in PHP, the only set of headers was the one I specifically set just above:

 header('Content-type: application/javascript; charset=utf-8'); 

So, I have nothing to lose, I tried calling header_remove(); before posting my custom headers. And, to my great surprise, PHP suddenly started sending the ETag header!

2nd Epiphany: compressed response changes its hash

Then it hit me like a bag of bricks: specifying the Content-type header in PHP, I told NGINX (the web server I use) a GZIP response as soon as PHP returned it back to NGINX! To make it clear, the Content-type that I pointed out was on the list of NGINX types for gzip.

For simplicity, my NGINX GZIP settings are as follows, and PHP is connected to NGINX via php-fpm:

 gzip on; gzip_min_length 1; gzip_proxied expired no-cache no-store private auth; gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript application/javascript image/svg+xml; gzip_vary on; 

I wondered why NGINX could remove the ETag that I sent to PHP when the "gzippable content type" was specified, and came up with the obvious answer at the moment: because NGINX modifies the response body that PHP passes back when NGINX compresses it! It makes sense; it makes no sense to send an ETag if it does not match the answer used to generate it. It's pretty nice that NGINX is so smart at handling this scenario.

I don’t know if NGINX was always smart enough not to compress the bodies of the responses, which were uncompressed but contained ETag headers, but this seems to be what happens here.

UPDATE : I found a comment that explains the behavior of NGINX in this regard , which in turn leads to two valuable discussions on this subject:

  1. NGINX forum thread discussing behavior .
  2. Tangentially related discussion in the project repository ; see comment Posted on Jun 15, 2013 by Massive Bird .

In the interest of preserving this valuable explanation, if it disappears, I quote Massive Bird contribution to the discussion:

Nginx removes Etag when it receives a response on the fly. This is in accordance with the specification, since a response without gzipped is not a byte-sized response, comparable to a gzipped response.

Nevertheless, the behavior of NGINX in this regard can be considered slightly erroneous in that

... also says that there is a thing called weak Etags (Etag value with the prefix W /), and tells us that it can be used to check if the answer is semantically equivalent. In this case, Nginx should not bind to this. Unfortunately, this check did not get into the source tree (unfortunately, the quote is now filled with spam). "

I'm not sure about NGINX's current position in this regard, and in particular whether it added support for the "weak" Etags.

So what is the solution?

So what is the solution to return ETag back in response? Run gzipping in PHP so that NGINX sees that the response is already compressed, and simply passes it, leaving the ETag header unchanged:

 ob_start('ob_gzhandler'); 

As soon as I added this call before sending the headers and body of the response, PHP started sending the ETag value with each response. Yes!

Other lessons learned

Here are some interesting points gleaned from my research. This information is very useful when trying to test the implementation of caching on the server side, whether in PHP or another language.

Chrome and its "Net" developer toolbar behave differently depending on how the request is triggered .

If the request is "re-made", for example, by pressing Ctrl+F5 , Chrome sends the following headers:

 Cache-Control: no-cache Pragma: no-cache 

and the server responds 200 OK .

If the request is made only with F5 , Chrome sends these headers:

 Pragma: no-cache 

and the server responds with 304 Not Modified .

And finally, if the request is made by clicking on the link to the page that you are already viewing, or by placing focus in the Chrome address bar and pressing Enter, Chrome sends the following headers:

 Cache-Control: no-cache Pragma: no-cache 

and the server responds with 200 OK (from cache) .

Although this behavior is a little confusing at first, but if you do not know how it works, this is an ideal behavior because it allows you to very carefully test every possible request / response scenario.

Perhaps the most confusing thing is that Chrome automatically inserts the Cache-Control: no-cache and Pragma: no-cache headers into the outgoing request when Chrome actually receives responses from its cache (as evidenced by the 200 OK (from cache) response).

This experience was quite informative for me, and I hope that others will find this value analysis in the future.

+28
source

Similar problem

I tried to get a conditional GET request with an If-None-Match header by setting the appropriate Etag header, but to no avail in any browser I tried.

After a lot of samples, I understand that browsers process both GET and POST in the same way as the same cache candidate. Thus, having GET with the correct Etag was effectively canceled with an immediate "POST" to the same path with Cache-Control:"no-cache, private" , although it was supplied by X-Requested-With:"XMLHttpRequest" .

Hope this can be helpful to someone.

0
source

Posting this for the future of me ...

I had a similar problem, I sent ETag in the response, but the HTTP client did not send the If-None-Match header in subsequent requests (which was strange because it was the day before).

Turns out I used http://localhost:9000 for development (which didn't use If-None-Match ) - when I switched to http://127.0.0.1:9000 Chrome 1 automatically started sending If-None-Match Header in requests again.

Optional - make sure Devtools > Network > Disable Cache [ ] not checked.

Chrome: version 71.0.3578.98 (official build) (64-bit)

1 I can't find this documented anywhere - I assume Chrome was responsible for this logic.

0
source

All Articles