akamai.module in Akamai 7.3
Same filename and directory in other branches
Integration with Akamai CDN CCU API.
Akamai is a registered trademark of Akamai Technologies, Inc.
File
akamai.moduleView source
<?php
/**
* @file
* Integration with Akamai CDN CCU API.
*
* Akamai is a registered trademark of Akamai Technologies, Inc.
*/
/**
* Default max number of seconds to spend processing the cron queue.
*/
define('AKAMAI_CRON_QUEUE_TIME_LIMIT_DEFAULT', 15);
/**
* Default number of seconds to spend checking status of pending purge requests.
*/
define('AKAMAI_PURGE_STATUS_TIME_LIMIT_DEFAULT', 15);
/**
* Default number of hours to keep purge request data.
*/
define('AKAMAI_PURGE_REQUEST_HOURS_TO_KEEP_DEFAULT', 72);
/**
* Implements hook_perm().
*/
function akamai_permission() {
return array(
'administer akamai' => array(
'description' => t('Configure the Akamai CDN settings. Username, password, etc.'),
'title' => t('Administer Akamai Settings'),
),
'purge akamai cache' => array(
'description' => t('Allowed to clear content from the Akamai cache.'),
'title' => t('Purge Akamai Cache'),
),
);
}
/**
* Implements hook_menu().
*/
function akamai_menu() {
$items = array();
$items['admin/config/system/akamai'] = array(
'title' => 'Akamai',
'description' => 'Akamai CDN settings.',
'page callback' => 'drupal_get_form',
'page arguments' => array(
'akamai_settings_form',
),
'access arguments' => array(
'administer akamai',
),
'file' => 'akamai.admin.inc',
);
$items['admin/config/system/akamai/settings'] = array(
'title' => 'Settings',
'weight' => -10,
'type' => MENU_DEFAULT_LOCAL_TASK,
);
$items['admin/config/system/akamai/purge'] = array(
'title' => 'Manual Purge (CCU)',
'description' => 'Admin interface to manually purge URLs from Akamai via the Content Control Utility (CCU) API.',
'page callback' => 'drupal_get_form',
'page arguments' => array(
'akamai_manual_purge_form',
),
'access arguments' => array(
'purge akamai cache',
),
'file' => 'akamai.admin.inc',
'type' => MENU_LOCAL_TASK,
);
$items['admin/reports/akamai'] = array(
'title' => 'Akamai purge requests',
'description' => 'Provides status of purge requests.',
'page callback' => 'akamai_purge_request_list',
'access arguments' => array(
'purge akamai cache',
),
'file' => 'akamai.admin.inc',
);
$items['admin/reports/akamai/%'] = array(
'title' => 'Akamai purge requests',
'description' => 'Provides information about an individual purge request.',
'page callback' => 'akamai_purge_request_detail',
'page arguments' => array(
3,
),
'access arguments' => array(
'purge akamai cache',
),
'file' => 'akamai.admin.inc',
);
return $items;
}
/**
* Implements hook_theme().
*/
function akamai_theme() {
return array(
'akamai_page_cache_control_block' => array(
'variables' => array(
'cache_control_form' => NULL,
),
'template' => 'akamai-page-cache-control-block',
),
);
}
/**
* Implements hook_module_implements_alter().
*/
function akamai_module_implements_alter(&$implementations, $hook) {
// Move Akamai to run after Acquia Purge since it can grab the stale cache in
// certain edge cases, i.e. Akamai purging superfast followed by a request.
if ($hook == 'expire_cache' && (isset($implementations['purge']) || isset($implementations['acquia_purge']))) {
$group = $implementations['akamai'];
unset($implementations['akamai']);
$implementations['akamai'] = $group;
}
// Disable the akamai node hooks if expire module is available, since
// akamai_expire_cache() will get invoked instead.
if (in_array($hook, array(
'node_update',
'node_delete',
)) && module_exists('expire')) {
unset($implementations['akamai']);
}
}
/**
* Implements hook_expire_cache().
*/
function akamai_expire_cache($urls, $wildcards, $object_type, $object) {
akamai_purge_paths(array_keys($urls));
}
/**
* Implements hook_node_update().
*
* When nodes are modified, clear URL from the Akamai
* cache. Clear base node/% url as well as aliases.
*/
function akamai_node_update($node) {
akamai_purge_entity('node', $node);
}
/**
* Implements hook_node_delete().
*
* When nodes are modified, clear URL from the Akamai
* cache. Clear base node/% url as well as aliases.
*/
function akamai_node_delete($node) {
akamai_purge_entity('node', $node);
}
/**
* Implements hook_block_info().
*/
function akamai_block_info() {
$blocks['akamai'] = array(
'info' => t('Akamai Cache Control'),
'cache' => DRUPAL_NO_CACHE,
);
return $blocks;
}
/**
* Implements hook_block_view().
*/
function akamai_block_view($delta) {
if ($delta == 'akamai') {
$cache_control_form = drupal_get_form('akamai_page_cache_control_form');
$block = array(
'subject' => t('Akamai Cache Control'),
'content' => $cache_control_form,
);
return $block;
}
}
/**
* Form builder for purging the current URL from the Akamai cache.
*
* @see akamai_block_view()
*/
function akamai_page_cache_control_form() {
$form = array();
$nid = arg(1);
if (arg(0) == 'node' && is_numeric($nid) && arg(2) == NULL) {
$path = arg(0) . '/' . $nid;
$form['#node'] = node_load($nid);
}
else {
$path = check_plain($_GET['q']);
$form['#node'] = NULL;
}
$path_label = $path;
if ($path == variable_get('site_frontpage', 'node')) {
$path_label = t("[frontpage]");
}
$form['path'] = array(
'#type' => 'hidden',
'#value' => $path,
);
$form['message'] = array(
'#type' => 'item',
'#title' => t('Refresh URL'),
'#markup' => $path_label,
);
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Refresh Akamai Cache'),
);
return $form;
}
/**
* Form submission handler for akamai_page_cache_control_form().
*
* Purge the 'path' variable from the Akamai cache.
*/
function akamai_page_cache_control_form_submit($form, &$form_state) {
$values = $form_state['values'];
$path = $values['path'];
$result = akamai_purge_path($path);
if ($result === TRUE) {
$message = t("A purge request for the path, %path, has been queued and will be submitted when cron runs.", array(
'%path' => $path,
));
drupal_set_message($message);
}
elseif (is_object($result)) {
$message = t("A purge request for the path, %path, has been submitted to Akamai, please allow 10 minutes for changes to take effect.", array(
'%path' => $path,
));
drupal_set_message($message);
}
else {
drupal_set_message(t("There was a problem submitting a purge request for this page. Check your log messages for more information."), 'error');
}
}
/**
* Purges a path and its related paths from Akamai's cache.
*
* If purge request queuing is enabled, the request will be queued and submitted
* to the CCU API when cron is run.
*
* If purge request queing is not enabled, the purge request will be made
* immediately.
*
* @param string $path
* The path being purged, such as "node/34".
* - If you provide an internal path (e.g. "node/34"), its alias will also be
* purged, if one exists. If an alias is provided, the corresponding system
* path will also be purged. Additional query arguments must be supplied in
* $options['query'], not included in $path.
* - The special string '<front>' generates a link to the site's base URL.
* @param array $options
* (optional) An associative array of additional options, with the following
* elements:
* - 'hostname': The hostname at which the path to be purged exists. If
* omitted, the hostname will be obtained from the akamai_get_hostname()
* function.
* - Additional elements as used by the url() function.
*
* @see akamai_get_purge_paths()
*
* @return mixed
* A response object if the purge request was submitted successfully.
* FALSE if purge request submission or queing failed.
* TRUE if the request was queued.
*/
function akamai_purge_path($path, array $options = array()) {
$language = empty($options['language']) ? $GLOBALS['language_url'] : $options['language'];
if (empty($options['hostname'])) {
$hostname = akamai_get_hostname($language);
}
else {
$hostname = $options['hostname'];
}
$paths = akamai_get_purge_paths($path, $options);
return akamai_purge_paths($paths, $hostname);
}
/**
* Purges a set of paths from Akamai's cache.
*
* If purge request queuing is enabled, the path will be queued and submitted
* to the CCU API when cron is run.
*
* If purge request queing is not enabled, the purge request will be made
* immediately.
*
* @param array $paths
* A set of paths to purge. Aliases and other paths will not be added to the
* list of paths. These paths should already be properly formatted and include
* the base path (e.g. "/node/34", "/sitemap.xml").
* @param string $hostname
* The hostname at which the paths to be purged exist. If omitted, the
* hostname will be obtained from the akamai_get_hostname() function.
*
* @return mixed
* A response object if the purge request was submitted successfully.
* FALSE if purge request submission or queing failed.
* TRUE if the request was queued.
*/
function akamai_purge_paths(array $paths, $hostname = NULL) {
if (empty($hostname)) {
$hostname = akamai_get_hostname();
}
// Either queue the purge request or submit it now.
if (variable_get('akamai_queue_purge_requests', FALSE)) {
return akamai_queue_purge_request($hostname, $paths);
}
else {
$result = akamai_submit_purge_request($hostname, $paths);
if ($result == FALSE && variable_get('akamai_queue_on_failure', TRUE)) {
return akamai_queue_purge_request($hostname, $paths);
}
}
return $result;
}
/**
* Builds a list of paths to purge in addition to a given path.
*
* The list includes the system path and path alias. The base path is included
* if the given path is set to the front page. Other modules may alter the list
* by implementing hook_akamai_paths_alter().
*
* @param string $path
* The path being purged, such as "node/34".
* @param array $options
* (optional) Additional options used by the url() function.
*
* The following options are not supported:
* - absolute
* - https
* - fragment
*
* @return array
* An array of paths to submit to the CCU API.
*/
function akamai_get_purge_paths($path, array $options) {
$language_code = empty($options['language']->language) ? NULL : $options['language']->language;
// Lookup system path.
$path = drupal_get_normal_path($path, $language_code);
// Build options array to pass to url(). We don't want absolute URLs or
// or fragments in the URLs, so we prevent that here.
$url_options = $options;
$url_options['absolute'] = FALSE;
if (isset($url_options['https'])) {
unset($url_options['https']);
}
if (isset($url_options['fragment'])) {
unset($url_options['fragment']);
}
// Build the list of paths to purge.
$paths = [];
// Add system path by instructing url() to not replace with the alias.
$paths[] = url($path, $url_options + [
'alias' => TRUE,
]);
// Add aliased path.
$paths[] = url($path, $url_options);
// Check if this path is configured as the frontpage.
if ($path == variable_get('site_frontpage', 'node')) {
$paths[] = url('<front>', $url_options);
}
// Allow other modules to add/modify paths to be cleared.
$context = $options;
$context['original_path'] = $path;
drupal_alter('akamai_paths', $paths, $context);
// Remove duplicates.
$paths = array_unique($paths);
return $paths;
}
/**
* Gets the hostname for which purge requests will be issued.
*
* @param object $language
* (optional) The language object. If the option to use the language domain is
* enabled, $language->domain will be used.
*
* @return string
* Akamai hostname.
*/
function akamai_get_hostname($language = NULL) {
$language = empty($language) ? $GLOBALS['language_url'] : $language;
if (variable_get('akamai_use_language_domain', FALSE) && !empty($language->domain)) {
$hostname = $language->domain;
}
else {
$hostname = variable_get('akamai_hostname', '');
}
$context = [
'language' => $language,
];
drupal_alter('akamai_hostname', $hostname, $context);
return $hostname;
}
/**
* Submits a purge request to the CCU API.
*
* @param string $hostname
* The name of the URL that contains the objects you want to purge.
* @param array $paths
* An array of paths to be purged.
*
* @return object|false
* If the request was accepted, a response object. Otherwise, FALSE.
*/
function akamai_submit_purge_request($hostname, array $paths) {
if (variable_get('akamai_disabled', FALSE)) {
watchdog('akamai', 'Request to purge paths ignored because clearing is disabled. Check module settings.');
return FALSE;
}
try {
$ccu = akamai_get_client();
$response = $ccu
->postPurgeRequest($hostname, $paths);
if (!empty($response)) {
akamai_log_purge_request($hostname, $paths, $response);
return $response;
}
} catch (GuzzleHttp\Exception\RequestException $e) {
watchdog_exception('akamai', $e);
$request = $e
->getRequest();
$request_variables = [
'@method' => $request
->getMethod(),
'@uri' => $request
->getUri(),
'@body' => (string) $request
->getBody(),
];
watchdog('akamai', 'Request: @method @uri @body', $request_variables, WATCHDOG_ERROR);
if ($e
->hasResponse()) {
$response_body = $e
->getResponse()
->getBody()
->getContents();
watchdog('akamai', 'Response: @response', [
'@response' => $response_body,
], WATCHDOG_ERROR);
}
} catch (Exception $e) {
watchdog_exception('akamai', $e);
}
return FALSE;
}
/**
* Logs the purge request for reporting and progress tracking.
*/
function akamai_log_purge_request($hostname, $paths, $response) {
try {
$time = time();
$is_fast_purge = empty($response->progressUri);
$record = [
'purge_id' => $response->purgeId,
'support_id' => $response->supportId,
'estimated_seconds' => $response->estimatedSeconds,
'check_after' => $is_fast_purge ? NULL : $time + $response->pingAfterSeconds,
'submission_time' => $time,
'status' => $response->detail,
'progress_uri' => $is_fast_purge ? NULL : $response->progressUri,
'completion_time' => $is_fast_purge ? $time + $response->estimatedSeconds : NULL,
'hostname' => $hostname,
'paths' => array_unique($paths),
];
return drupal_write_record('akamai_purge_requests', $record);
} catch (Exception $e) {
watchdog_exception('akamai', $e);
return FALSE;
}
}
/**
* Queues a purge request to be submitted at a later time.
*
* @param string $hostname
* The name of the URL that contains the objects you want to purge.
* @param array $paths
* An array of paths to be purged.
*/
function akamai_queue_purge_request($hostname, $paths) {
$queue = DrupalQueue::get('akamai_ccu');
return $queue
->createItem([
'hostname' => $hostname,
'paths' => $paths,
]);
}
/**
* Implements hook_cron().
*/
function akamai_cron() {
if (variable_get('akamai_disabled', FALSE)) {
watchdog('akamai', 'Akamai cron task skipped because purging is disabled. Check module settings.');
return;
}
module_load_include('inc', 'akamai', 'akamai.cron');
akamai_cron_process_queue();
akamai_cron_check_status();
}
/**
* Purges an entity's path from Akamai's cache.
*/
function akamai_purge_entity($entity_type, $entity) {
$uri = entity_uri($entity_type, $entity);
return akamai_purge_path($uri['path'], $uri['options']);
}
/**
* Deprecated. Use akamai_purge_path() instead.
*
* Clear one or more paths from Akamai. Clears node/id and any URL aliases.
*
* @param array $paths_in
* An array of paths or single path to clear.
* @param array $params
* (optional) An array of params for the API call.
* @param object $node
* (optional)
*
* @return mixed
* A response object if the purge request was accepted, FALSE otherwise.
*
* @deprecated Deprecated in 7.x-3.0.
* @see akamai_purge_path()
*/
function akamai_clear_url($paths_in, $params = [], $node = NULL) {
if (variable_get('akamai_disabled', FALSE)) {
watchdog('akamai', 'Request to clear paths ignored because clearing is disabled. Check module settings.');
return FALSE;
}
// Get the system path and all aliases to this url.
$paths = array();
$options = [];
if (!empty($node)) {
$options['entity_type'] = 'node';
$options['entity'] = $node;
}
foreach ($paths_in as $path) {
$paths = array_merge($paths, akamai_get_purge_paths($path, $options));
}
// It is possible to have no paths at this point if other modules have
// altered the paths.
if (empty($paths)) {
watchdog('akamai', 'No resultant paths to clear for %paths', array(
'%paths' => implode(', ', $paths_in),
), WATCHDOG_NOTICE);
return FALSE;
}
// This can cause the array to become non-associative and will cause
// json_encode() to use an object instead of an array when encoding.
$paths = array_unique($paths);
$default_params = array(
'basepath' => akamai_get_hostname(),
'action' => variable_get("akamai_action", "invalidate"),
'domain' => variable_get("akamai_network", ""),
);
$params = array_merge($default_params, $params);
if (!empty($params['basepath'])) {
$parsed = parse_url($params['basepath']);
$hostname = empty($parsed['host']) ? NULL : $parsed['host'];
}
$version = variable_get('akamai_ccu_version', 3);
try {
$client = akamai_get_client($version);
if (!empty($params['domain'])) {
$client
->setNetwork($params['domain']);
}
// v2 of the API uses 'remove', but v3 uses 'delete'.
if ($params['action'] == 'remove') {
$client
->setOperation($client::OPERATION_DELETE);
}
else {
$client
->setOperation($client::OPERATION_INVALIDATE);
}
$response = $client
->postPurgeRequest($hostname, $paths);
return $response;
} catch (AkamaiException $e) {
watchdog_exception('akamai', $e);
return FALSE;
}
}
/**
* Gets an instance of a CCU client class.
*
* @param int $version
* Specifies which version of the CCU API to use. Either 2 or 3.
*/
function akamai_get_client($version = NULL) {
if (variable_get('akamai_credential_storage', 'database') == 'database') {
$edgegrid_client = new \Akamai\Open\EdgeGrid\Client([
'base_uri' => variable_get('akamai_base_uri', ''),
'timeout' => variable_get('akamai_timeout', \Akamai\Open\EdgeGrid\Client::DEFAULT_REQUEST_TIMEOUT),
]);
$client_token = variable_get('akamai_client_token', '');
$client_secret = variable_get('akamai_client_secret', '');
$access_token = variable_get('akamai_access_token', '');
$edgegrid_client
->setAuth($client_token, $client_secret, $access_token);
}
else {
$section = variable_get('akamai_edgerc_section', 'default');
$path = variable_get('akamai_edgerc_path', NULL);
$edgegrid_client = \Akamai\Open\EdgeGrid\Client::createFromEdgeRcFile($section, $path);
}
$version = empty($version) ? variable_get('akamai_ccu_version', 3) : $version;
if ($version == 2) {
$ccu_client = new \Drupal\akamai\Ccu2Client($edgegrid_client);
}
else {
$ccu_client = new \Drupal\akamai\Ccu3Client($edgegrid_client);
}
$ccu_client
->setNetwork(variable_get('akamai_network', 'production'));
if (variable_get('akamai_action', 'invalidate') == 'remove') {
$ccu_client
->setOperation($ccu_client::OPERATION_DELETE);
}
return $ccu_client;
}
Functions
Name | Description |
---|---|
akamai_block_info | Implements hook_block_info(). |
akamai_block_view | Implements hook_block_view(). |
akamai_clear_url Deprecated | Deprecated. Use akamai_purge_path() instead. |
akamai_cron | Implements hook_cron(). |
akamai_expire_cache | Implements hook_expire_cache(). |
akamai_get_client | Gets an instance of a CCU client class. |
akamai_get_hostname | Gets the hostname for which purge requests will be issued. |
akamai_get_purge_paths | Builds a list of paths to purge in addition to a given path. |
akamai_log_purge_request | Logs the purge request for reporting and progress tracking. |
akamai_menu | Implements hook_menu(). |
akamai_module_implements_alter | Implements hook_module_implements_alter(). |
akamai_node_delete | Implements hook_node_delete(). |
akamai_node_update | Implements hook_node_update(). |
akamai_page_cache_control_form | Form builder for purging the current URL from the Akamai cache. |
akamai_page_cache_control_form_submit | Form submission handler for akamai_page_cache_control_form(). |
akamai_permission | Implements hook_perm(). |
akamai_purge_entity | Purges an entity's path from Akamai's cache. |
akamai_purge_path | Purges a path and its related paths from Akamai's cache. |
akamai_purge_paths | Purges a set of paths from Akamai's cache. |
akamai_queue_purge_request | Queues a purge request to be submitted at a later time. |
akamai_submit_purge_request | Submits a purge request to the CCU API. |
akamai_theme | Implements hook_theme(). |
Constants
Name | Description |
---|---|
AKAMAI_CRON_QUEUE_TIME_LIMIT_DEFAULT | Default max number of seconds to spend processing the cron queue. |
AKAMAI_PURGE_REQUEST_HOURS_TO_KEEP_DEFAULT | Default number of hours to keep purge request data. |
AKAMAI_PURGE_STATUS_TIME_LIMIT_DEFAULT | Default number of seconds to spend checking status of pending purge requests. |