View source
<?php
define('VARNISH_NO_CLEAR', 0);
define('VARNISH_DEFAULT_CLEAR', 1);
define('VARNISH_SELECTIVE_CLEAR', 2);
define('VARNISH_DEFAULT_TIMEOUT', 100);
define('VARNISH_SERVER_STATUS_DOWN', 0);
define('VARNISH_SERVER_STATUS_UP', 1);
define('VARNISH_BANTYPE_NORMAL', 0);
define('VARNISH_BANTYPE_BANLURKER', 1);
define('VARNISH_DEFAULT_BANTYPE', VARNISH_BANTYPE_NORMAL);
function varnish_menu() {
$items = array();
$items['admin/config/development/varnish'] = array(
'title' => 'Varnish',
'description' => 'Configure your varnish integration.',
'page callback' => 'drupal_get_form',
'page arguments' => array(
'varnish_admin_settings_form',
),
'access arguments' => array(
'administer varnish',
),
'file' => 'varnish.admin.inc',
);
$items['admin/config/development/varnish/general'] = array(
'title' => 'General',
'description' => 'Configure Varnish servers and cache invalidation settings',
'type' => MENU_DEFAULT_LOCAL_TASK,
'weight' => -5,
);
if (floatval(variable_get('varnish_version', 2.1)) < 3) {
$items['admin/reports/varnish'] = array(
'title' => 'Varnish status',
'description' => 'Get the output of varnishstat.',
'page callback' => 'varnish_admin_reports_page',
'access arguments' => array(
'administer varnish',
),
'file' => 'varnish.admin.inc',
);
}
return $items;
}
function varnish_theme() {
return array(
'varnish_status' => array(
'variables' => array(
'status' => array(),
'version' => 2.1,
),
),
);
}
function varnish_permission() {
return array(
'administer varnish' => array(
'title' => t('Administer Varnish'),
'description' => t('Perform administration tasks for varnish.'),
'restrict access' => TRUE,
),
);
}
function varnish_flush_caches() {
if (variable_get('cache_class_external_varnish_page', FALSE)) {
return array(
'external_varnish_page',
);
}
}
function varnish_expire_cache($paths, $wildcards = array()) {
if (count($wildcards)) {
foreach ($wildcards as $path => $wildcard) {
if ($wildcard) {
$paths[$path] .= '([/?].*)?';
}
}
}
if (module_exists('expire') && variable_get('expire_include_base_url', constant('EXPIRE_INCLUDE_BASE_URL'))) {
$host_buckets = array();
foreach ($paths as $url) {
$parts = parse_url($url);
$path = substr($parts['path'], strlen(base_path()));
$host_buckets[$parts['host']][] = $path . (!empty($parts['query']) ? '?' . $parts['query'] : '');
}
foreach ($host_buckets as $host => $purges) {
varnish_purge_paths($host, $purges);
}
}
else {
$host = _varnish_get_host();
varnish_purge_paths($host, $paths);
}
}
function varnish_purge_all_pages() {
$path = base_path();
$host = _varnish_get_host();
varnish_purge($host, $path);
}
function varnish_purge($host, $pattern) {
global $base_path, $base_root;
$version = floatval(variable_get('varnish_version', 2.1));
$command = $version >= 3 ? "ban" : "purge";
$bantype = variable_get('varnish_bantype', VARNISH_DEFAULT_BANTYPE);
$patterns = explode('|', $pattern);
foreach ($patterns as $num => $single_pattern) {
if (substr($single_pattern, 1, strlen($base_root)) == $base_root) {
$single_pattern = substr_replace($single_pattern, '', 1, strlen($base_root));
}
$patterns[$num] = $single_pattern;
}
$pattern = implode('|', $patterns);
$front_domains = variable_get('varnish_front_domains', '');
drupal_alter('varnish_front_domains', $front_domains);
$fronts = explode(' ', $front_domains);
switch ($bantype) {
case VARNISH_BANTYPE_NORMAL:
_varnish_terminal_run(array(
"{$command} req.http.host ~ {$host} && req.url ~ \"{$pattern}\"",
));
if ($front_domains) {
foreach ($fronts as $value) {
_varnish_terminal_run(array(
"{$command} req.http.host ~ {$value} && req.url ~ \"{$pattern}\"",
));
}
}
break;
case VARNISH_BANTYPE_BANLURKER:
_varnish_terminal_run(array(
"{$command} obj.http.x-host ~ {$host} && obj.http.x-url ~ \"{$pattern}\"",
));
if ($front_domains) {
foreach ($fronts as $value) {
_varnish_terminal_run(array(
"{$command} obj.http.x-host ~ {$value} && obj.http.x-url ~ \"{$pattern}\"",
));
}
}
break;
default:
watchdog('varnish', 'Varnish ban type is out of range.', array(), WATCHDOG_ERROR);
}
}
function varnish_purge_paths($host, array $paths) {
$length_limit = variable_get('varnish_cmdlength_limit', 7500) - strlen($host);
$base_path = base_path();
while (!empty($paths)) {
$purge_pattern = '^';
while (strlen($purge_pattern) < $length_limit && !empty($paths)) {
$purge_pattern .= $base_path . array_shift($paths) . '$|^';
}
$purge_pattern = substr($purge_pattern, 0, -2);
$purge_pattern = preg_replace('#/+#', '/', $purge_pattern);
varnish_purge($host, $purge_pattern);
}
}
function varnish_get_status() {
static $results = NULL;
if (is_null($results)) {
$results = array();
$status = _varnish_terminal_run(array(
'status',
));
$terminals = explode(' ', variable_get('varnish_control_terminal', '127.0.0.1:6082'));
foreach ($terminals as $terminal) {
$stat = array_shift($status);
$results[$terminal] = $stat['status']['code'] == 200 ? VARNISH_SERVER_STATUS_UP : VARNISH_SERVER_STATUS_DOWN;
}
}
return $results;
}
function theme_varnish_status($variables) {
$items = array();
$status = $variables['status'];
foreach ($status as $terminal => $state) {
list($server, $port) = explode(':', $terminal);
if ($state == VARNISH_SERVER_STATUS_UP) {
$icon = theme('image', array(
'path' => 'misc/watchdog-ok.png',
'alt' => t("Server OK: @server:@port", array(
'@server' => $server,
'@port' => $port,
)),
'title' => "{$server}:{$port}",
));
$version = $variables['version'];
if ($version < 3) {
$items[] = t('!status_icon Varnish running. Observe more detailed statistics !link.', array(
'!status_icon' => $icon,
'!link' => l(t('here'), 'admin/reports/varnish'),
));
}
else {
$items[] = t('!status_icon Varnish running.', array(
'!status_icon' => $icon,
));
}
}
else {
$icon = theme('image', array(
'path' => 'misc/watchdog-error.png',
'alt' => t("Server down: @server:@port", array(
'@server' => $server,
'@port' => $port,
)),
'title' => "{$server}:{$port}",
));
$items[] = t('!status_icon The Varnish control terminal is not responding at @server on port @port.', array(
'!status_icon' => $icon,
'@server' => $server,
'@port' => $port,
));
}
}
return theme('item_list', array(
'items' => $items,
));
}
function _varnish_get_host() {
global $base_url;
$parts = parse_url($base_url);
return $parts['host'];
}
function _varnish_terminal_run($commands) {
if (!extension_loaded('sockets')) {
return FALSE;
}
if (!is_array($commands)) {
$commands = array(
$commands,
);
}
$ret = array();
$terminals = explode(' ', variable_get('varnish_control_terminal', '127.0.0.1:6082'));
$timeout = variable_get('varnish_socket_timeout', VARNISH_DEFAULT_TIMEOUT);
$seconds = (int) ($timeout / 1000);
$microseconds = (int) ($timeout % 1000 * 1000);
foreach ($terminals as $terminal) {
list($server, $port) = explode(':', $terminal);
$client = socket_create(AF_INET, SOCK_STREAM, getprotobyname('tcp'));
socket_set_option($client, SOL_SOCKET, SO_SNDTIMEO, array(
'sec' => $seconds,
'usec' => $microseconds,
));
socket_set_option($client, SOL_SOCKET, SO_RCVTIMEO, array(
'sec' => $seconds,
'usec' => $microseconds,
));
if (@(!socket_connect($client, $server, $port))) {
watchdog('varnish', 'Unable to connect to server socket @server:@port: %error', array(
'@server' => $server,
'@port' => $port,
'%error' => socket_strerror(socket_last_error($client)),
), WATCHDOG_ERROR);
$ret[$terminal] = FALSE;
continue;
}
if (floatval(variable_get('varnish_version', 2.1)) > 2.0) {
$status = _varnish_read_socket($client);
if ($status['code'] == 107) {
$secret = variable_get('varnish_control_key', '');
$challenge = substr($status['msg'], 0, 32);
if (variable_get('varnish_control_key_appendnewline', TRUE)) {
$pack = $challenge . "\n" . $secret . "\n" . $challenge . "\n";
}
else {
$pack = $challenge . "\n" . $secret . $challenge . "\n";
}
$key = hash('sha256', $pack);
socket_write($client, "auth {$key}\n");
$status = _varnish_read_socket($client);
if ($status['code'] != 200) {
watchdog('varnish', 'Authentication to server failed!', array(), WATCHDOG_ERROR);
}
}
}
foreach ($commands as $command) {
if ($status = _varnish_execute_command($client, $command)) {
$ret[$terminal][$command] = $status;
}
}
}
return $ret;
}
function _varnish_execute_command($client, $command) {
$result = socket_write($client, "{$command}\n");
$status = _varnish_read_socket($client);
if ($status['code'] != 200) {
watchdog('varnish', 'Received status code @code running %command. Full response text: @error', array(
'@code' => $status['code'],
'%command' => $command,
'@error' => $status['msg'],
), WATCHDOG_ERROR);
return FALSE;
}
else {
return $status;
}
}
function _varnish_read_socket($client, $retry = 2) {
$header = socket_read($client, 13, PHP_BINARY_READ);
if ($header == FALSE) {
$error = socket_last_error();
if ($error == 35 && $retry > 0) {
return _varnish_read_socket($client, $retry - 1);
}
else {
watchdog('varnish', 'Socket error: @error', array(
'@error' => socket_strerror($error),
), WATCHDOG_ERROR);
return array(
'code' => $error,
'msg' => socket_strerror($error),
);
}
}
$msg_len = (int) substr($header, 4, 6) + 1;
$status = array(
'code' => substr($header, 0, 3),
'msg' => socket_read($client, $msg_len, PHP_BINARY_READ),
);
return $status;
}