Ответы HTTP_IF_MODIFIED_SINCE и HTTP_IF_NONE_MATCH в PHP

У меня есть кешируемый динамический контент, созданный в PHP 5.1.0+. Я уже отправляю правильные заголовки (включая Last-Modified и ETag) клиентам.

Теперь я хочу, чтобы мой сценарий мог ответить $_SERVER['HTTP_IF_MODIFIED_SINCE'] а также $_SERVER['HTTP_IF_NONE_MATCH'] когда присутствует. Когда условия совпадают, я хочу ответить HTTP 304 "Not Modified" клиентам.

Каковы правильные условия? Когда именно я выпускаю 304 вместо всего контента?

Принятый ответ на вопрос Как узнать, когда отправлять ответ 304 Not Modified, кажется, выдает это правильно, но у меня трудные времена, чтобы портировать этот код на PHP 5.

Спасибо!

7 ответов

Решение

Я всегда использовал:

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();
        }
    }
}

Не помню, написал я это или получил откуда-то еще...

Я обычно использую его в верхней части файла следующим образом:

caching_headers ($_SERVER['SCRIPT_FILENAME'], filemtime($_SERVER['SCRIPT_FILENAME']));

Ответ, на который вы ссылаетесь, кажется, содержит все, что вам нужно. Подвести итоги:

  • генерируйте свои собственные заголовки ETag и Last-Modified, как если бы вы отправляли все тело
  • посмотрите заголовок If-Modified-Since, который отправил клиент, если ваш последний последний измененный старше или тот же самый, отправьте 304
  • посмотрите заголовок If-None-Match клиента, если он совпадает с вашим собственным ETag, отправьте 304
  • если вы достигнете этого места, заголовки не совпадают, отправьте полное тело и новые заголовки ETag/Last-Modified

Вот фрагмент моей функции render_file().

$last_modified = filemtime($filename);
if ($last_modified === false) {
  throw new Exception('Modify date unknown');
}
if (array_key_exists('HTTP_IF_MODIFIED_SINCE', $_SERVER)) {
  $if_modified_since = strtotime(preg_replace('/;.*$/', '', $_SERVER['HTTP_IF_MODIFIED_SINCE']));
  if ($if_modified_since >= $last_modified) { // Is the Cached version the most recent?
    header($_SERVER['SERVER_PROTOCOL'].' 304 Not Modified');
    exit();
  }
}
header('Last-Modified: '.date('r', $last_modified)); // tz should be GMT according to specs but also works with other tzs

// other headers and contents go here  

Если бы я мог немного улучшить первоначальный блестящий ответ от Рича Брэдшоу Rich Bradshaw

Этот код настроен, и теперь 100% проходит проверки If-Modified-Since и If-None-Match. Он также правильно форматирует дату последнего изменения, так как оригинальный ответ отправляет +0000 в конце вместо GMT и добавляет заголовок VARY к ответу 304. Вы можете проверить это на redbot.org

<?php
function caching_headers ($file, $timestamp) {
    $lastModified=filemtime($_SERVER['SCRIPT_FILENAME']);
    $gmt_mtime = gmdate("D, d M Y H:i:s T", $lastModified);
    header('ETag: "'.md5($timestamp.$file).'"');
    header('Last-Modified: '.$gmt_mtime);
    header('Cache-Control: must-revalidate, proxy-revalidate, max-age=3600');

    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');
            header("Vary: Accept-Encoding,User-Agent");
            exit();
        }
    }
}
caching_headers ($_SERVER['SCRIPT_FILENAME'], filemtime($_SERVER['SCRIPT_FILENAME']));
?>

Если клиент выполнил условный запрос GET и доступ разрешен, но документ не был изменен, сервер ДОЛЖЕН ответить этим кодом состояния. Ответ 304 НЕ ДОЛЖЕН содержать тело сообщения и, таким образом, всегда заканчивается первой пустой строкой после полей заголовка.

От - http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html

Так что, если вы отправляете 304, не отправляйте тело.

Эта статья ответит на все ваши вопросы по кешированию

Я обнаружил, что добавление

RewriteRule .* - [E=HTTP_IF_MODIFIED_SINCE:%{HTTP:If-Modified-Since}]
RewriteRule .* - [E=HTTP_IF_NONE_MATCH:%{HTTP:If-None-Match}]

Внизу мой htaccess файл (ниже всех rewriterule) работал.

Зачем?

Проведя много исследований по этому вопросу, я обнаружил, что условные запросы на самом деле замедляют работу сайта. Существуют определенные сценарии, когда это не так, но сопоставление с общими шаблонами использования в целом приводит к снижению пропускной способности и снижению эффективности кэширования.

C.

Другие вопросы по тегам