'Varnish settings', '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/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; } /** * Implemetation of hook_perm() * * Allows admins to control access to varnish settings. */ function varnish_perm() { return array('administer varnish'); } /** * Implementation of hook_requirements() * * Insure that varnish's connection is good. */ function varnish_requirements($phase) { if ($phase == 'runtime') { $requirements = array('varnish'); $requirements['varnish']['title'] = t('Varnish status'); // try a varnish admin connect, report results $status = _varnish_terminal_run('status'); $terminals = explode(' ', variable_get('varnish_control_terminal', '127.0.0.1:6082')); foreach ($terminals as $term) { list($server, $port) = explode(':', $term); $stat = array_shift($status); if ($stat['code'] === FALSE) { $requirements['varnish']['value'] = t('Varnish connection broken'); $requirements['varnish']['severity'] = REQUIREMENT_ERROR; $requirements['varnish']['description'] = t('The Varnish control terminal is not responding at %server on port %port.', array('%server' => $server, '%port' => $port)); return $requirements; } else { $requirements['varnish']['value'] = t('Varnish running. Observe more detailed statistics !link.', array('!link' => l(t('here'), 'admin/reports/varnish'))); } } return $requirements; } } /** * Implementation of hook_nodeapi() * * Used to pick up cache_clearing events */ function varnish_nodeapi(&$node, $op) { if ($op == 'insert' || $op == 'update') { // We've probably just run through node_save, and normally this is where // Drupal calls a cache_clear_all(). switch (variable_get('varnish_cache_clear', VARNISH_DEFAULT_CLEAR)) { case VARNISH_DEFAULT_CLEAR: varnish_purge_all_pages(); break; } } } /** * Implementation of hook_comment() * * Used to pick up cache_clearing events */ function varnish_comment($comment, $op) { switch ($op) { case 'insert': case 'update': case 'publish': case 'unpublish': case 'delete': if (variable_get('varnish_cache_clear', VARNISH_DEFAULT_CLEAR) == VARNISH_DEFAULT_CLEAR) { varnish_purge_all_pages(); } break; } } /** * Implementation of hook_expire_cache * * Takes an array from expire.module and issue purges. * * You may also safely call this function directly with an array of local urls to purge. */ function varnish_expire_cache($paths) { $host = _varnish_get_host(); $base = base_path(); $purge = implode('$|^'. $base, $paths); _varnish_terminal_run('purge req.http.host ~ '. $host .' && req.url ~ "^'. $base . $purge .'$"'); } /** * Implementation of hook_form_alter() * * Add our submit callback to the "clear caches" button. */ function varnish_form_alter(&$form, $form_state, $form_id) { if ($form_id == 'system_performance_settings') { $form['clear_cache']['clear']['#submit'][] = 'varnish_purge_all_pages'; } } /** * Implementation of hook_flush_caches() * * Flush caches on events like cron. * * This borrows logic from cache_clear_all() to respect cache_lifetime. */ function varnish_flush_caches() { if (variable_get('varnish_flush_cron', 0)) { if (variable_get('cache_lifetime', 0)) { $cache_flush = variable_get('cache_flush_varnish', 0); if ($cache_flush == 0) { // This is the first request to clear the cache, start a timer. variable_set('cache_flush_varnish', time()); } else if (time() > ($cache_flush + variable_get('cache_lifetime', 0))) { varnish_purge_all_pages(); } } else { varnish_purge_all_pages(); } } } /** * Helper function to quickly flush all caches for the current site. */ function varnish_purge_all_pages() { $path = base_path(); $host = _varnish_get_host(); _varnish_terminal_run("purge req.http.host ~ $host && req.url ~ ^$path"); } /** * Help[er function to parse the host from the global $base_url */ function _varnish_get_host() { global $base_url; $parts = parse_url($base_url); return $parts['host']; } /** * Helper function that sends commands to Varnish * * Utilizes sockets to talk to varnish terminal. */ function _varnish_terminal_run($command) { if (!extension_loaded('sockets')) { // Prevent fatal errors if people don't have requirements. return FALSE; } $ret = array(); $terminals = explode(' ', variable_get('varnish_control_terminal', '127.0.0.1:6082')); foreach ($terminals as $terminal) { list($server, $port) = explode(':', $terminal); $client = socket_create(AF_INET, SOCK_STREAM, getprotobyname('tcp')); if (!socket_connect($client, $server, $port)) { watchdog('varnish', 'Unable to connect to server socket !server:!port', array('!server' => $server, '!port' => $port), WATCHDOG_ERROR); $ret[] = FALSE; break; } socket_set_option($client, SOL_SOCKET, SO_RCVTIMEO, array('sec'=>0, 'usec'=>50)); // If there is a CLI banner message (varnish >= 2.1.x), try to read it and move on. $status = _varnish_read_socket($client); // Do we need to authenticate? if ($status['code'] == 107) { // Require authentication $challenge = substr($status['msg'], 0, 32); $pack = $challenge ."\x0A". $secret ."\x0A". $challenge ."\x0A"; $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); } } // Send command and get response. $result = socket_write($client, "$command\n"); $status = _varnish_read_socket($client); if ($status['code'] != 200) { watchdog('varnish', 'Recieved status code !code running %command. Full response text: !error', array('!code' => $status['code'], '%command' => $command, '!error' => $status['msg']), WATCHDOG_ERROR); $ret[] = FALSE; } else { // successful connection $ret[] = $status; } socket_close($client); } return $ret; } function _varnish_read_socket($client) { // status and length info is always 13 characters. $header = socket_read($client, 13, PHP_BINARY_READ); if ($header == FALSE) { $error = socket_last_error(); watchdog('varnish', 'Socket error: !error', array('!error' => socket_strerror($error)), WATCHDOG_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; }