页面缓存是什么意思?有些页面浏览量非常大,而且与状态无关,这类页面就可以使用页面缓存技术。在页面第一次请求完毕以后,将响应结果保存起来。下一次再请求同一页面时,就不需要从头到尾再执行一遍,只需要将第一次执行的响应结果获取出来,直接返回给使用者就行了。

什么样的页面请求可以缓存?Drupal使用函数drupal_page_is_cacheable()区分哪些请求可以缓存:

function drupal_page_is_cacheable($allow_caching = NULL) {
$allow_caching_static = &drupal_static(__FUNCTION__, TRUE);
if (isset($allow_caching)) {
$allow_caching_static = $allow_caching;
} return $allow_caching_static && ($_SERVER['REQUEST_METHOD'] == 'GET' || $_SERVER['REQUEST_METHOD'] == 'HEAD')
&& !drupal_is_cli();
}

默认,只有GET和HEAD请求可以缓存。

如何保存第一次的请求响应结果?Drupal使用函数drupal_page_set_cache()保存响应结果。在请求执行完成后,Drupal执行该函数,使用cache_set()将响应结果保存到cache_page对象:

function drupal_page_set_cache() {
global $base_root; if (drupal_page_is_cacheable()) {
$cache = (object) array(
'cid' => $base_root . request_uri(),
'data' => array(
'path' => $_GET['q'],
'body' => ob_get_clean(),
'title' => drupal_get_title(),
'headers' => array(),
),
'expire' => CACHE_TEMPORARY,
'created' => REQUEST_TIME,
); // Restore preferred header names based on the lower-case names returned
// by drupal_get_http_header().
$header_names = _drupal_set_preferred_header_name();
foreach (drupal_get_http_header() as $name_lower => $value) {
$cache->data['headers'][$header_names[$name_lower]] = $value;
if ($name_lower == 'expires') {
// Use the actual timestamp from an Expires header if available.
$cache->expire = strtotime($value);
}
} if ($cache->data['body']) {
if (variable_get('page_compression', TRUE) && extension_loaded('zlib')) {
$cache->data['body'] = gzencode($cache->data['body'], 9, FORCE_GZIP);
}
cache_set($cache->cid, $cache->data, 'cache_page', $cache->expire);
}
return $cache;
}
}

这里应该仔细看看Drupal是如何缓存的:以URL为key,缓存内容是一个数组,包含path、body、title、headers。/drupal/index.php?q=node/1和/drupal/index.php?q=node/2这会是两个缓存项。

同一页面的后续请求如何从缓存读取呢?这就是Drupal的启动函数_drupal_bootstrap_page_cache()完成的内容:

function _drupal_bootstrap_page_cache() {
global $user; // Allow specifying special cache handlers in settings.php, like
// using memcached or files for storing cache information.
require_once DRUPAL_ROOT . '/includes/cache.inc'; // 这是什么意思?
// 是不是说cache.inc只是实现了默认的数据库缓存DrupalDatabaseCache,
// 如果有Memcached缓存(DrupalMemcachedCache)或者File缓存(DrupalFileCache)时,
// 可以将这些额外的类文件在settings.php中用cache_backends声明:
// $conf['cache_backends'][] = 'includes/cache.memcached.inc';
// $conf['cache_backends'][] = 'includes/cache.file.inc';
foreach (variable_get('cache_backends', array()) as $include) {
require_once DRUPAL_ROOT . '/' . $include;
} // Check for a cache mode force from settings.php.
if (variable_get('page_cache_without_database')) {
$cache_enabled = TRUE;
}
else {
drupal_bootstrap(DRUPAL_BOOTSTRAP_VARIABLES, FALSE);
$cache_enabled = variable_get('cache');
} drupal_block_denied(ip_address()); // If there is no session cookie and cache is enabled (or forced), try
// to serve a cached page.
if (!isset($_COOKIE[session_name()]) && $cache_enabled) {
// Make sure there is a user object because its timestamp will be
// checked, hook_boot might check for anonymous user etc.
$user = drupal_anonymous_user(); //匿名用户
// Get the page from the cache.
$cache = drupal_page_get_cache();
// If there is a cached page, display it.
if (is_object($cache)) {
header('X-Drupal-Cache: HIT');
// Restore the metadata cached with the page.
$_GET['q'] = $cache->data['path'];
drupal_set_title($cache->data['title'], PASS_THROUGH);
date_default_timezone_set(drupal_get_user_timezone());
// If the skipping of the bootstrap hooks is not enforced, call
// hook_boot.
if (variable_get('page_cache_invoke_hooks', TRUE)) {
bootstrap_invoke_all('boot');
}
drupal_serve_page_from_cache($cache);
// If the skipping of the bootstrap hooks is not enforced, call
// hook_exit.
if (variable_get('page_cache_invoke_hooks', TRUE)) {
bootstrap_invoke_all('exit');
}
// We are done.
exit;
}
else {
header('X-Drupal-Cache: MISS');
}
}
}

首先,检查一下请求者的IP地址是否允许:

drupal_block_denied(ip_address());

如果请求者的IP地址是禁止的,则Drupal会请求者发送403信息:

function drupal_block_denied($ip) {
// Deny access to blocked IP addresses - t() is not yet available.
if (drupal_is_denied($ip)) {
header($_SERVER['SERVER_PROTOCOL'] . ' 403 Forbidden');
print 'Sorry, ' . check_plain(ip_address()) . ' has been banned.';
exit();
}
}

然后,检查请求是否是匿名的。只有匿名请求才会检查缓存:

if (!isset($_COOKIE[session_name()]) /*没有会话COOKIE时才会检查缓存*/ && $cache_enabled) {
// ......
}

匿名请求时,Drupal通过函数drupal_anonymous_user()设置$user全局变量:

function drupal_anonymous_user() {
$user = new stdClass();
$user->uid = 0;
$user->hostname = ip_address();
$user->roles = array();
$user->roles[DRUPAL_ANONYMOUS_RID] = 'anonymous user';
$user->cache = 0;
return $user;
}

同时,匿名请求也会返回一个额外的HTTP头信息X-Drupal-Cache,代表当前内容时候是否是缓存返回的:

header('X-Drupal-Cache: HIT'); // 缓存命中
header('X-Drupal-Cache: MISS'); // 缓存丢失

Drupal通过函数drupal_page_set_cache()保存页面缓存,对应的,使用函数drupal_page_get_cache()获取页面缓存:

function drupal_page_get_cache($check_only = FALSE) {
global $base_root;
static $cache_hit = FALSE; if ($check_only) {
return $cache_hit;
} if (drupal_page_is_cacheable()) {
$cache = cache_get($base_root . request_uri(), 'cache_page');
if ($cache !== FALSE) {
$cache_hit = TRUE;
}
return $cache;
}
}

获取的缓存内容是drupal_page_set_cache()保存的数组:

$cache = drupal_page_get_cache();

// $cache = array(
// 'path' => '...',
// 'body' => '...',
// 'title' => '...',
// 'headers' => array(...),
// );

最后,透过函数drupal_serve_page_from_cache()返回缓存内容,结束请求。

function drupal_serve_page_from_cache(stdClass $cache) {
// Negotiate whether to use compression.
$page_compression = variable_get('page_compression', TRUE) && extension_loaded('zlib');
$return_compressed = $page_compression && isset($_SERVER['HTTP_ACCEPT_ENCODING']) && strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') !== FALSE; // Get headers set in hook_boot(). Keys are lower-case.
$hook_boot_headers = drupal_get_http_header(); // Headers generated in this function, that may be replaced or unset using
// drupal_add_http_headers(). Keys are mixed-case.
$default_headers = array(); foreach ($cache->data['headers'] as $name => $value) {
// In the case of a 304 response, certain headers must be sent, and the
// remaining may not (see RFC 2616, section 10.3.5). Do not override
// headers set in hook_boot().
$name_lower = strtolower($name);
if (in_array($name_lower, array('content-location', 'expires', 'cache-control', 'vary')) && !isset($hook_boot_headers[$name_lower])) {
drupal_add_http_header($name, $value);
unset($cache->data['headers'][$name]);
}
} // If the client sent a session cookie, a cached copy will only be served
// to that one particular client due to Vary: Cookie. Thus, do not set
// max-age > 0, allowing the page to be cached by external proxies, when a
// session cookie is present unless the Vary header has been replaced or
// unset in hook_boot().
$max_age = !isset($_COOKIE[session_name()]) || isset($hook_boot_headers['vary']) ? variable_get('page_cache_maximum_age', 0) : 0;
$default_headers['Cache-Control'] = 'public, max-age=' . $max_age; // Entity tag should change if the output changes.
$etag = '"' . $cache->created . '-' . intval($return_compressed) . '"';
header('Etag: ' . $etag); // See if the client has provided the required HTTP headers.
$if_modified_since = isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) ? strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) : FALSE;
$if_none_match = isset($_SERVER['HTTP_IF_NONE_MATCH']) ? stripslashes($_SERVER['HTTP_IF_NONE_MATCH']) : FALSE; if ($if_modified_since && $if_none_match
&& $if_none_match == $etag // etag must match
&& $if_modified_since == $cache->created) { // if-modified-since must match
header($_SERVER['SERVER_PROTOCOL'] . ' 304 Not Modified');
drupal_send_headers($default_headers);
return;
} // Send the remaining headers.
foreach ($cache->data['headers'] as $name => $value) {
drupal_add_http_header($name, $value);
} $default_headers['Last-Modified'] = gmdate(DATE_RFC1123, $cache->created); // HTTP/1.0 proxies does not support the Vary header, so prevent any caching
// by sending an Expires date in the past. HTTP/1.1 clients ignores the
// Expires header if a Cache-Control: max-age= directive is specified (see RFC
// 2616, section 14.9.3).
$default_headers['Expires'] = 'Sun, 19 Nov 1978 05:00:00 GMT'; drupal_send_headers($default_headers); // Allow HTTP proxies to cache pages for anonymous users without a session
// cookie. The Vary header is used to indicates the set of request-header
// fields that fully determines whether a cache is permitted to use the
// response to reply to a subsequent request for a given URL without
// revalidation. If a Vary header has been set in hook_boot(), it is assumed
// that the module knows how to cache the page.
if (!isset($hook_boot_headers['vary']) && !variable_get('omit_vary_cookie')) {
header('Vary: Cookie');
} if ($page_compression) {
header('Vary: Accept-Encoding', FALSE);
// If page_compression is enabled, the cache contains gzipped data.
if ($return_compressed) {
// $cache->data['body'] is already gzip'ed, so make sure
// zlib.output_compression does not compress it once more.
ini_set('zlib.output_compression', '0');
header('Content-Encoding: gzip');
}
else {
// The client does not support compression, so unzip the data in the
// cache. Strip the gzip header and run uncompress.
$cache->data['body'] = gzinflate(substr(substr($cache->data['body'], 10), 0, -8));
}
} // Print the page.
print $cache->data['body'];
}

Drupal启动阶段之二:页面缓存的更多相关文章

  1. Drupal启动阶段之六:页面头信息

    Drupal在本阶段为用户设置缓存头信息.Drupal不为验证用户缓存页面,每次请求时都是从新读取的. function _drupal_bootstrap_page_header() { boots ...

  2. Drupal启动阶段之三:数据库

    Drupal在数据库启动阶段仅仅是简单地包含了database.inc文件,然后再注册类加载器: function _drupal_bootstrap_database() { // Initiali ...

  3. Drupal启动阶段之一:配置

    配置是Drupal启动过程中的第一个阶段,通过函数_drupal_bootstrap_configuration()实现: function _drupal_bootstrap_configurati ...

  4. Drupal启动阶段之四:系统变量

    Drupal的系统变量是指保存在后台数据库variable表中的一些参数设置,透过variable_get()和variable_set()存取: 先看一看_drupal_bootstrap_vari ...

  5. 学习MVC之租房网站(十二)-缓存和静态页面

    在上一篇<学习MVC之租房网站(十一)-定时任务和云存储>学习了Quartz的使用.发邮件,并将通过UEditor上传的图片保存到云存储.在项目的最后,再学习优化网站性能的一些技术:缓存和 ...

  6. PHP+Redis 实例【二】页面缓存 新玩法

    今天算是认识到博客园里的审查团队多内幕了,哈哈,贴个图玩下. 气死宝宝了. 进入主题! 今天就不写什么功能性的了,换下口味说下关于页面级的缓存,应该怎么做. 相信有很多小伙伴查了百度,甚至google ...

  7. swoft| 源码解读系列二: 启动阶段, swoft 都干了些啥?

    date: 2018-8-01 14:22:17title: swoft| 源码解读系列二: 启动阶段, swoft 都干了些啥?description: 阅读 sowft 框架源码, 了解 sowf ...

  8. varnish页面缓存服务

    varnish页面缓存服务 https://www.cnblogs.com/L-dongf/p/9310144.html http://blog.51cto.com/xinzong/1782669 阅 ...

  9. Nginx 反向代理、负载均衡、页面缓存、URL重写及读写分离详解

    转载:http://freeloda.blog.51cto.com/2033581/1288553 大纲 一.前言 二.环境准备 三.安装与配置Nginx 四.Nginx之反向代理 五.Nginx之负 ...

随机推荐

  1. FZU - 1492(Problem 1492 地震预测)

    怀特先生是一名研究地震的科学家,最近他发现如果知道某一段时间内的地壳震动能量采样的最小波动值之和,可以有效地预测大地震的发生. 假设已知一段时间的n次地壳震动能量的采样值为a1,a2,-an,那么第i ...

  2. BZOJ 1150 [CTSC2007]数据备份Backup(贪心+优先队列)

    [题目链接] http://www.lydsy.com/JudgeOnline/problem.php?id=1150 [题目大意] 给出n个数,请你挑出k对(每个数不可重复选取),使得他们差的绝对值 ...

  3. [BZOJ2216]Lightning Conductor

    原来决策单调性指的是这个东西... 一些DP可以写成$f_i=\max\limits_{j\lt i}g(i,j)$,设$p_i(p_i<j)$表示使得$g(i,j)$最大的$j$,如果$p_1 ...

  4. 【优先队列】POJ1442-Black Box

    [思路] 建立一个小堆和一个大堆.大堆用来存放第1..index-1大的数,其余数存放在大堆,小堆的堆顶元素便是我们要求出的第index大的数.每次插入一个A(n),必须保证大堆中数字数目不变,故先插 ...

  5. [转]MySQL 数据类型(二)

    MySQL 的数值数据类型可以大致划分为两个类别,一个是整数,另一个是浮点数或小数.许多不同的子类型对这些类别中的每一个都是可用的,每个子类型支持不同大小的数据,并且 MySQL 允许我们指定数值字段 ...

  6. c++中const使用详解

    const在c++中是一个关键字,它限定一个变量不允许被改变.使用const在一定程度上可以提高程序的安全性和可靠性,另外,在观看别人代码的时候,清晰理解const所起的作用,对理解对方的程序也有一些 ...

  7. nor flash 和nand flash 的区别

    ROM和RAM指的都是半导体存储器,ROM是Read Only Memory的缩写,RAM是Random Access Memory的缩写.ROM在系统停止供电的时候仍然可以保持数据,而RAM通常都是 ...

  8. linux图机界面机制

    1.X WindowX Window 是由麻省理工学院(MIT)推出的窗口系统,简称X,它旨在建立不依赖于特定硬件系统的图形和文字显示窗口系统的标准.1987 年9 月,MIT 推出了X 系统的11 ...

  9. minishift的本地代码构建

    看文档说支持,但实际尝试了下,失败 发现仍然是找github中的代码 找了半天,发现是在目录下有.git的隐含目录,然后copy到其他目录下删除,然后再试,发现仍然失败. 日志看是指定的目录并没有传入 ...

  10. 利用PMKID破解PSK的实际测试与影响评估

    在2018年8月4日,一位研究员在hashcat论坛中发布了一篇帖子,表示他研究WPA3协议密码破解方法的过程中,发现了一个针对WPA/WPA2协议密码破解的新方法,使用PMKID(the Pairw ...