Viewed   94 times

I have cacheable dynamic content made in PHP 5.1.0+. I already send the correct headers (including Last-Modified and ETag) to clients.

I now want my script to be able to answer $_SERVER['HTTP_IF_MODIFIED_SINCE'] and $_SERVER['HTTP_IF_NONE_MATCH'] when present. When the conditions matches, I want to answer a HTTP 304 "Not Modified" to clients.

What are the correct conditions? When exactly I issue a 304 instead of the whole content?

The accepted answer in question How to know when to send a 304 Not Modified response seems to issue this correctly but I have hard times to port that code to PHP 5.

Thank you!

 Answers

2

I've always used:

function caching_headers ($file, $timestamp) {
    $gmt_mtime = gmdate('r', $timestamp);
    header('ETag: "'.md5($timestamp.$file).'"');
    header('Last-Modified: '.$gmt_mtime);
    header('Cache-Control: public');

    if(isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) || isset($_SERVER['HTTP_IF_NONE_MATCH'])) {
        if ($_SERVER['HTTP_IF_MODIFIED_SINCE'] == $gmt_mtime || str_replace('"', '', stripslashes($_SERVER['HTTP_IF_NONE_MATCH'])) == md5($timestamp.$file)) {
            header('HTTP/1.1 304 Not Modified');
            exit();
        }
    }
}

Don't remember whether I wrote it or got it from somewhere else...

I'm normally using it at the top of a file in this way:

caching_headers ($_SERVER['SCRIPT_FILENAME'], filemtime($_SERVER['SCRIPT_FILENAME']));
Sunday, August 28, 2022
 
ventsyv
 
4

In your case, && and and do the same thing, but take a look at the operator precedence. You can see that && and and are not on the same level, so mixing that could give you unexpected results in some cases - I recommend always using &&, but that's your choice.

Friday, December 16, 2022
2

Never output anything before sending the HEADER, if you do so you will not be able to send the header and it will throw an error !
It's also a good practice to set error_reporting(0) on production server to make sure that no error gets shown before header

Friday, August 5, 2022
 
satya
 
5

The solution is to ensure that you are using the mysqlnd driver for php.

How do you know that you're not using mysqlnd?

When viewing php -i, there will be no mention of "mysqlnd". The pdo_mysql section will have something like this:

pdo_mysql

PDO Driver for MySQL => enabled Client API version => 5.1.72

How do you install it?

Most installation guides for L/A/M/P suggest apt-get install php5-mysql but the native driver for MySQL is installed by a different package: php5-mysqlnd. I found that this was available with the ppa:ondrej/php5-oldstable.

To switch to the new driver (on Ubuntu):

  • Remove the old driver:
    apt-get remove php5-mysql
  • Install the new driver:
    apt-get install php5-mysqlnd
  • Restart apache2:
    service apache2 restart

How do I check that the driver is being used?

Now php -i will mention "mysqlnd" explicitly in the pdo_mysql section:

pdo_mysql

PDO Driver for MySQL => enabled
Client API version => mysqlnd 5.0.10 - 20111026 - $Id:      e707c415db32080b3752b232487a435ee0372157 $

PDO settings

Ensure that PDO::ATTR_EMULATE_PREPARES is false (check your defaults or set it):
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);

Ensure that PDO::ATTR_STRINGIFY_FETCHES is false (check your defaults or set it):
$pdo->setAttribute(PDO::ATTR_STRINGIFY_FETCHES, false);

Returned values

  • Floating-point types (FLOAT, DOUBLE) are returned as PHP floats.
  • Integer types (INTEGER, INT, SMALLINT, TINYINT, MEDIUMINT, BIGINT †) are returned as PHP integers.
  • Fixed-Point types (DECIMAL, NUMERIC) are returned as strings.

† BIGINTs with a value greater than a 64 bit signed int (9223372036854775807) will return as a string (or 32 bits on a 32 bit system)

    object(stdClass)[915]
      public 'integer_col' => int 1
      public 'double_col' => float 1.55
      public 'float_col' => float 1.5
      public 'decimal_col' => string '1.20' (length=4)
      public 'bigint_col' => string '18446744073709551615' (length=20)
Thursday, September 22, 2022
 
4
  • It's not quite correct. Please take a look at the algorithm: alt text http://img532.imageshack.us/img532/1017/cache.png
  • The solution is proxy-friendly, you may use Cache-control: proxy-revalidate to force caches to obey any freshness information you give them about a resource (only applies to shared|proxy caches)

Here is the function that might help:

function isModified($mtime, $etag) {
    return !( (
        isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])
        && 
        strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) >= $mtime
    ) || (
        isset($_SERVER['HTTP_IF_NONE_MATCH'])
        && 
        $_SERVER['HTTP_IF_NONE_MATCH'] == $etag
    ) ) ;
}

I suggest that you take a look at the following article: http://www.peej.co.uk/articles/http-caching.html

Update:

[AlexV] Is is even possible to receive if-none-match AND if-modified-since at the same time?

You can definitely have both set. However:

If none of the entity tags match, then the server MAY perform the requested method as if the If-None-Match header field did not exist, but MUST also ignore any If-Modified-Since header field(s) in the request. That is, if no entity tags match, then the server MUST NOT return a 304 (Not Modified) response.

RFC2616 #14.26

Example values (W stands for 'weak'; read more in RFC2616 #13.3.3):

If-None-Match: "xyzzy", "r2d2xxxx", "c3piozzzz"
If-None-Match: W/"xyzzy", W/"r2d2xxxx", W/"c3piozzzz"
If-Modified-Since: Sat, 29 Oct 1994 19:43:31 GMT
If-None-Match: *

As a special case, the value "*" matches any current entity of the resource.

Friday, December 23, 2022
Only authorized users can answer the search term. Please sign in first, or register a free account.
Not the answer you're looking for? Browse other questions tagged :