如何在PHP中使用HTTP缓存头

71

我有一个PHP 5.1.0网站(实际上是5.2.9,但它也必须在5.1.0+上运行)。

页面是动态生成的,但其中许多页面大部分静态。所谓静态内容是指内容不变,但围绕内容的“模板”随时间可能会发生改变。

我知道已经有几个缓存系统和PHP框架可供使用,但我的主机未安装APC或Memcached,并且我没有为这个特定项目使用任何框架。

我希望页面被缓存(我认为默认情况下PHP“禁用”缓存)。到目前为止,我正在使用:

session_cache_limiter('private'); //Aim at 'public'
session_cache_expire(180);
header("Content-type: $documentMimeType; charset=$documentCharset");
header('Vary: Accept');
header("Content-language: $currentLanguage");

我阅读了许多教程,但我找不到一些简单易懂的内容(我知道缓存是一个复杂的东西,但我只需要一些基础知识)。

发送帮助缓存的“必须”头文件有哪些?


8
欢迎来到 StackOverflow。很棒的第一个问题! - Sampson
如何为.js和.css文件指定缓存?或者它们是否包含在PHP文件生成的标头中? - David Spector
@David Spector 您可以使用 Web 服务器缓存它们。在 Apache 下,您可以通过 .htaccess 查找 "Header set Cache-Control" 来实现此操作。您可能希望尝试提出问题,因为评论不是一个好的地方 :) - AlexV
7个回答

54

你可能想使用private_no_expire替代private,但对于你知道不会改变的内容设置一个长时间的过期时间,并确保处理与Emil帖子类似的if-modified-sinceif-none-match请求。

$tsstring = gmdate('D, d M Y H:i:s ', $timestamp) . 'GMT';
$etag = $language . $timestamp;

$if_modified_since = isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) ? $_SERVER['HTTP_IF_MODIFIED_SINCE'] : false;
$if_none_match = isset($_SERVER['HTTP_IF_NONE_MATCH']) ? $_SERVER['HTTP_IF_NONE_MATCH'] : false;
if ((($if_none_match && $if_none_match == $etag) || (!$if_none_match)) &&
    ($if_modified_since && $if_modified_since == $tsstring))
{
    header('HTTP/1.1 304 Not Modified');
    exit();
}
else
{
    header("Last-Modified: $tsstring");
    header("ETag: \"{$etag}\"");
}

$etag 可以是基于内容、用户 ID、语言和时间戳的校验和,例如:

$etag = md5($language . $timestamp);

2
您所描述的是弱 E-Tag,应该带有“W/”前缀。 - Nicholas Shanks
1
我会添加“过期”头部,因为一些托管提供商(例如服务器)会发送它,并且它们的日期已经过去了,而我认为它应该在未来。 - Sasho
5
如果你以上所述的方法没有得到304状态码,检查一下HTTP_IF_NONE_MATCH头部中是否有任何多余的引号。为了确保,请将$if_none_match == $etag替换为rtrim(ltrim($if_none_match, "'\""), "'\"") == $etag - ReactiveRaven
1
不要忘记,理想的E-Tag是基于您可以读取页面内容来计算的。这是要发送到浏览器之前的HTML。通常也意味着使用缓冲区。现代php框架可能已经处理了这个问题。如果您想发送“请勿缓存我”头,则最好使用Cache-Control而不是Pragma。为什么是Cache-Control而不是Pragma?因为Pragma已被弃用。因此,最好发送Cache-Control头。点下去会给人误导。尽管如此,还是很不错的尝试 :) - renoirb
2
$timestamp 是从哪里来的?这应该在代码的其余部分 $timestamp = time(); 之前定义吗?我有什么遗漏吗? - igasparetto
显示剩余5条评论

16

你必须有一个过期头信息。从技术上讲,还有其他解决方案,但Expires头信息确实是目前最好的解决方案,因为它告诉浏览器在到期日期和时间之前不要重新检查页面,直接从缓存中提供内容。它非常有效!

另外,检查请求中是否包含If-Modified-Since头信息也很有用。当浏览器“不确定”缓存中的内容是否仍然是正确的版本时,会发送此头信息。如果自那时以来您的页面没有被修改,只需返回HTTP 304代码(未修改)。下面是一个例子,它会发送10分钟的304代码:

<?php
if(isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) {
  if(strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) < time() - 600) {
    header('HTTP/1.1 304 Not Modified');
    exit;
  }
}
?>

您可以在代码的早期放置此检查,以节省服务器资源。


12

选择其中一个或全部使用! :-)

header('Expires:Thu,01-Jan-70 00:00:01 GMT');
header('Last-Modified:'.gmdate('D,d M Y H:i:s')。'GMT');
header('Cache-Control:no-store,no-cache,must-revalidate');
header('Cache-Control:post-check=0,pre-check=0',false);
header('Pragma:no-cache');

session_cache_limiter和session_cache_expire已经控制了Expires、Cache-Control、Last-Modified和Pragma... - AlexV
我明白了。是的,你说得对。而且我也看到我没有仔细阅读你的问题。抱歉。 - Mike Foster

9
这是一个帮助你进行http缓存的小型类。它有一个名为“Init”的静态函数,需要两个参数:页面(或浏览器请求的任何其他文件)上次修改的时间戳和该页面可以由浏览器缓存的最大时间(以秒为单位)。请看下面的代码示例:
```html

这里有一个小类,可为您执行http缓存。它有一个名为“Init”的静态函数,需要2个参数,即页面(或浏览器请求的任何其他文件)上次修改的时间戳和该页面可以由浏览器缓存的最长时间(以秒为单位)。

```
class HttpCache 
{
    public static function Init($lastModifiedTimestamp, $maxAge)
    {
        if (self::IsModifiedSince($lastModifiedTimestamp))
        {
            self::SetLastModifiedHeader($lastModifiedTimestamp, $maxAge);
        }
        else 
        {
            self::SetNotModifiedHeader($maxAge);
        }
    }

    private static function IsModifiedSince($lastModifiedTimestamp)
    {
        $allHeaders = getallheaders();

        if (array_key_exists("If-Modified-Since", $allHeaders))
        {
            $gmtSinceDate = $allHeaders["If-Modified-Since"];
            $sinceTimestamp = strtotime($gmtSinceDate);

            // Can the browser get it from the cache?
            if ($sinceTimestamp != false && $lastModifiedTimestamp <= $sinceTimestamp)
            {
                return false;
            }
        }

        return true;
    }

    private static function SetNotModifiedHeader($maxAge)
    {
        // Set headers
        header("HTTP/1.1 304 Not Modified", true);
        header("Cache-Control: public, max-age=$maxAge", true);
        die();
    }

    private static function SetLastModifiedHeader($lastModifiedTimestamp, $maxAge)
    {
        // Fetching the last modified time of the XML file
        $date = gmdate("D, j M Y H:i:s", $lastModifiedTimestamp)." GMT";

        // Set headers
        header("HTTP/1.1 200 OK", true);
        header("Cache-Control: public, max-age=$maxAge", true);
        header("Last-Modified: $date", true);
    }
}

8
<?php
header("Expires: Sat, 26 Jul 2020 05:00:00 GMT"); // Date in the future
?>

设置缓存页面的过期日期是在客户端上缓存页面的一种有用的方式。


好的,而且已经使用了session_cache_limiter和session_cache_expire来处理这个问题。 - AlexV

8

这是PHP缓存的最佳解决方案,只需在脚本顶部使用即可。

$seconds_to_cache = 3600;
$ts = gmdate("D, d M Y H:i:s", time() + $seconds_to_cache) . " GMT";
header("Expires: $ts");
header("Pragma: cache");
header("Cache-Control: max-age=$seconds_to_cache");

2
能否解释一下你的答案,这样其他人就知道为什么这是最佳解决方案了! - Kevin
将以下代码添加到您的PHP页面顶部,它将强制缓存您的文件到浏览器。 - Amit Ghosh Anto

4

我在处理来自Facebook Feed的JSON缓存时遇到了问题,直到我使用了flush和隐藏错误报告才解决了。我知道这不是理想的代码,但我只需要一个快速解决办法。

error_reporting(0);
    $headers = apache_request_headers();
    //print_r($headers);
    $timestamp = time();
    $tsstring = gmdate('D, d M Y H:i:s ', $timestamp) . 'GMT';
    $etag = md5($timestamp);
    header("Last-Modified: $tsstring");
    header("ETag: \"{$etag}\"");
    header('Expires: Thu, 01-Jan-70 00:00:01 GMT');

    if(isset($headers['If-Modified-Since'])) {
            //echo 'set modified header';
            if(intval(time()) - intval(strtotime($headers['IF-MODIFIED-SINCE'])) < 300) {
              header('HTTP/1.1 304 Not Modified');
              exit();
            }
    }
    flush();
//JSON OP HERE

这个非常有效。


1
如果您不将字段名称 $headers['If-Modified-Since'] 设置为大写,则可能无法正常工作 -> $headers['IF-MODIFIED-SINCE']。 - Mr Br

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接