class AcquiaCloudPurger in Acquia Purge 8
Acquia Cloud.
Plugin annotation
@PurgePurger(
id = "acquia_purge",
label = @Translation("Acquia Cloud"),
configform = "",
cooldown_time = 0.2,
description = @Translation("Invalidate content from Acquia Cloud."),
multi_instance = FALSE,
types = {"url", "wildcardurl", "tag", "everything"},
)
Hierarchy
- class \Drupal\Component\Plugin\PluginBase implements DerivativeInspectionInterface, PluginInspectionInterface
- class \Drupal\Core\Plugin\PluginBase uses DependencySerializationTrait, MessengerTrait, StringTranslationTrait
- class \Drupal\purge\Plugin\Purge\Purger\PurgerBase implements PurgerInterface uses PurgeLoggerAwareTrait
- class \Drupal\acquia_purge\Plugin\Purge\Purger\AcquiaCloudPurger implements DebuggerAwareInterface, PurgerInterface uses DebuggerAwareTrait
- class \Drupal\purge\Plugin\Purge\Purger\PurgerBase implements PurgerInterface uses PurgeLoggerAwareTrait
- class \Drupal\Core\Plugin\PluginBase uses DependencySerializationTrait, MessengerTrait, StringTranslationTrait
Expanded class hierarchy of AcquiaCloudPurger
File
- src/
Plugin/ Purge/ Purger/ AcquiaCloudPurger.php, line 28
Namespace
Drupal\acquia_purge\Plugin\Purge\PurgerView source
class AcquiaCloudPurger extends PurgerBase implements DebuggerAwareInterface, PurgerInterface {
use DebuggerAwareTrait;
/**
* Maximum number of requests to send concurrently.
*/
const CONCURRENCY = 6;
/**
* Float: the number of seconds to wait while trying to connect to a server.
*/
const CONNECT_TIMEOUT = 1.5;
/**
* Float: the timeout of the request in seconds.
*/
const TIMEOUT = 3.0;
/**
* Groups of tags per request.
*
* Batches of cache tags are split up into multiple requests to prevent HTTP
* request headers from growing too large or Varnish refusing to process them.
*/
const TAGS_GROUPED_BY = 15;
/**
* Information object interfacing with the Acquia platform.
*
* @var \Drupal\acquia_purge\AcquiaCloud\PlatformInfoInterface
*/
protected $platformInfo;
/**
* The Guzzle HTTP client.
*
* @var \GuzzleHttp\ClientInterface
*/
protected $httpClient;
/**
* Constructs a AcquiaCloudPurger object.
*
* @param \Drupal\acquia_purge\AcquiaCloud\PlatformInfoInterface $acquia_purge_platforminfo
* Information object interfacing with the Acquia platform.
* @param \GuzzleHttp\ClientInterface $http_client
* An HTTP client that can perform remote requests.
* @param array $configuration
* A configuration array containing information about the plugin instance.
* @param string $plugin_id
* The plugin_id for the plugin instance.
* @param mixed $plugin_definition
* The plugin implementation definition.
*/
public final function __construct(PlatformInfoInterface $acquia_purge_platforminfo, ClientInterface $http_client, array $configuration, $plugin_id, $plugin_definition) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->platformInfo = $acquia_purge_platforminfo;
$this->httpClient = $http_client;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static($container
->get('acquia_purge.platforminfo'), $container
->get('http_client'), $configuration, $plugin_id, $plugin_definition);
}
/**
* Retrieve request options used for all of Acquia Purge's balancer requests.
*
* @param array[] $extra
* Associative array of options to merge onto the standard ones.
*
* @return mixed[]
* Guzzle option array.
*/
protected function getGlobalOptions(array $extra = []) {
$opt = [
// Disable exceptions for 4XX HTTP responses, those aren't failures to us.
'http_errors' => FALSE,
// Prevent inactive balancers from sucking all runtime up.
'connect_timeout' => self::CONNECT_TIMEOUT,
// Prevent unresponsive balancers from making Drupal slow.
'timeout' => self::TIMEOUT,
// Deliberately disable SSL verification to prevent unsigned certificates
// from breaking down a website when purging a https:// URL!
'verify' => FALSE,
// Trigger \Drupal\acquia_purge\Http\AcquiaCloudBalancerMiddleware which
// inspects Acquia Cloud responses and throws exceptions for failures.
'acquia_purge_balancer_middleware' => TRUE,
];
// Trigger the debugging middleware when Purge's debug mode is enabled.
if ($this
->debugger()
->enabled()) {
$opt['acquia_purge_debugger'] = $this
->debugger();
}
return array_merge($opt, $extra);
}
/**
* Concurrently execute the given requests.
*
* @param \Closure $requests
* Generator yielding requests which will be passed to \GuzzleHttp\Pool.
*/
protected function getResultsConcurrently(\Closure $requests) {
$this
->debugger()
->callerAdd(__METHOD__);
$results = [];
// Create a concurrently executed Pool which collects a boolean per request.
$pool = new Pool($this->httpClient, $requests(), [
'options' => $this
->getGlobalOptions(),
'concurrency' => self::CONCURRENCY,
'fulfilled' => function ($response, $result_id) use (&$results) {
$this
->debugger()
->callerAdd(__METHOD__ . '::fulfilled');
$results[$result_id][] = TRUE;
$this
->debugger()
->callerRemove(__METHOD__ . '::fulfilled');
},
'rejected' => function ($exception, $result_id) use (&$results) {
$this
->debugger()
->callerAdd(__METHOD__ . '::rejected');
$this
->debugger()
->logFailedRequest($exception);
$results[$result_id][] = FALSE;
$this
->debugger()
->callerRemove(__METHOD__ . '::rejected');
},
]);
// Initiate the transfers and create a promise.
$promise = $pool
->promise();
// Force the pool of requests to complete.
$promise
->wait();
$this
->debugger()
->callerRemove(__METHOD__);
return $results;
}
/**
* {@inheritdoc}
*/
public function getIdealConditionsLimit() {
// The max amount of outgoing HTTP requests that can be made during script
// execution time. Although always respected as outer limit, it will be
// lower in practice as PHP resource limits (max execution time) bring it
// further down. However, the maximum amount of requests will be higher on
// the CLI.
$balancers = count($this->platformInfo
->getBalancerAddresses());
if ($balancers) {
return intval(ceil(200 / $balancers));
}
return 100;
}
/**
* {@inheritdoc}
*/
public function hasRuntimeMeasurement() {
return TRUE;
}
/**
* {@inheritdoc}
*/
public function invalidate(array $invalidations) {
// Since we implemented ::routeTypeToMethod(), this Latin preciousness
// shouldn't ever occur and when it does, will be easily recognized.
throw new \Exception("Malum consilium quod mutari non potest!");
}
/**
* Invalidate a set of tag invalidations.
*
* @see \Drupal\purge\Plugin\Purge\Purger\PurgerInterface::invalidate()
* @see \Drupal\purge\Plugin\Purge\Purger\PurgerInterface::routeTypeToMethod()
*/
public function invalidateTags(array $invalidations) {
$this
->debugger()
->callerAdd(__METHOD__);
// Set invalidation states to PROCESSING. Detect tags with spaces in them,
// as space is the only character Drupal core explicitely forbids in tags.
foreach ($invalidations as $invalidation) {
$tag = $invalidation
->getExpression();
if (strpos($tag, ' ') !== FALSE) {
$invalidation
->setState(InvalidationInterface::FAILED);
$this->logger
->error("Tag '%tag' contains a space, this is forbidden.", [
'%tag' => $tag,
]);
}
else {
$invalidation
->setState(InvalidationInterface::PROCESSING);
}
}
// Create grouped sets of 12 so that we can spread out the BAN load.
$group = 0;
$groups = [];
foreach ($invalidations as $invalidation) {
if ($invalidation
->getState() !== InvalidationInterface::PROCESSING) {
continue;
}
if (!isset($groups[$group])) {
$groups[$group] = [
'tags' => [],
[
'objects' => [],
],
];
}
if (count($groups[$group]['tags']) >= self::TAGS_GROUPED_BY) {
$group++;
}
$groups[$group]['objects'][] = $invalidation;
$groups[$group]['tags'][] = $invalidation
->getExpression();
}
// Test if we have at least one group of tag(s) to purge, if not, bail.
if (!count($groups)) {
foreach ($invalidations as $invalidation) {
$invalidation
->setState(InvalidationInterface::FAILED);
}
return;
}
// Now create requests for all groups of tags.
$site = $this->platformInfo
->getSiteIdentifier();
$ipv4_addresses = $this->platformInfo
->getBalancerAddresses();
$requests = function () use ($groups, $ipv4_addresses, $site) {
foreach ($groups as $group_id => $group) {
$tags = new TagsHeaderValue($group['tags'], Hash::cacheTags($group['tags']));
foreach ($ipv4_addresses as $ipv4) {
(yield $group_id => function ($poolopt) use ($site, $tags, $ipv4) {
$opt = [
'headers' => [
'X-Acquia-Purge' => $site,
'X-Acquia-Purge-Tags' => $tags
->__toString(),
'Accept-Encoding' => 'gzip',
'User-Agent' => 'Acquia Purge',
],
];
// Pass the TagsHeaderValue to DebuggerMiddleware (when loaded).
if ($this
->debugger()
->enabled()) {
$opt['acquia_purge_tags'] = $tags;
}
if (is_array($poolopt) && count($poolopt)) {
$opt = array_merge($poolopt, $opt);
}
return $this->httpClient
->requestAsync('BAN', "http://{$ipv4}/tags", $opt);
});
}
}
};
// Execute the requests generator and retrieve the results.
$results = $this
->getResultsConcurrently($requests);
// Triage the results and set all invalidation states correspondingly.
foreach ($groups as $group_id => $group) {
if (!isset($results[$group_id]) || !count($results[$group_id])) {
foreach ($group['objects'] as $invalidation) {
$invalidation
->setState(InvalidationInterface::FAILED);
}
}
else {
if (in_array(FALSE, $results[$group_id])) {
foreach ($group['objects'] as $invalidation) {
$invalidation
->setState(InvalidationInterface::FAILED);
}
}
else {
foreach ($group['objects'] as $invalidation) {
$invalidation
->setState(InvalidationInterface::SUCCEEDED);
}
}
}
}
$this
->debugger()
->callerRemove(__METHOD__);
}
/**
* Invalidate a set of URL invalidations.
*
* @see \Drupal\purge\Plugin\Purge\Purger\PurgerInterface::invalidate()
* @see \Drupal\purge\Plugin\Purge\Purger\PurgerInterface::routeTypeToMethod()
*/
public function invalidateUrls(array $invalidations) {
$this
->debugger()
->callerAdd(__METHOD__);
// Change all invalidation objects into the PROCESS state before kickoff.
foreach ($invalidations as $inv) {
$inv
->setState(InvalidationInterface::PROCESSING);
}
// Generate request objects for each balancer/invalidation combination.
$ipv4_addresses = $this->platformInfo
->getBalancerAddresses();
$token = $this->platformInfo
->getBalancerToken();
$requests = function () use ($invalidations, $ipv4_addresses, $token) {
foreach ($invalidations as $inv) {
foreach ($ipv4_addresses as $ipv4) {
(yield $inv
->getId() => function ($poolopt) use ($inv, $ipv4, $token) {
$uri = $inv
->getExpression();
$host = parse_url($uri, PHP_URL_HOST);
$uri = str_replace($host, $ipv4, $uri);
$opt = [
'headers' => [
'X-Acquia-Purge' => $token,
'Accept-Encoding' => 'gzip',
'User-Agent' => 'Acquia Purge',
'Host' => $host,
],
];
if (is_array($poolopt) && count($poolopt)) {
$opt = array_merge($poolopt, $opt);
}
return $this->httpClient
->requestAsync('PURGE', $uri, $opt);
});
}
}
};
// Execute the requests generator and retrieve the results.
$results = $this
->getResultsConcurrently($requests);
// Triage the results and set all invalidation states correspondingly.
foreach ($invalidations as $invalidation) {
$inv_id = $invalidation
->getId();
if (!isset($results[$inv_id]) || !count($results[$inv_id])) {
$invalidation
->setState(InvalidationInterface::FAILED);
}
else {
if (in_array(FALSE, $results[$inv_id])) {
$invalidation
->setState(InvalidationInterface::FAILED);
}
else {
$invalidation
->setState(InvalidationInterface::SUCCEEDED);
}
}
}
$this
->debugger()
->callerRemove(__METHOD__);
}
/**
* Invalidate URLs that contain the wildcard character "*".
*
* @see \Drupal\purge\Plugin\Purge\Purger\PurgerInterface::invalidate()
* @see \Drupal\purge\Plugin\Purge\Purger\PurgerInterface::routeTypeToMethod()
*/
public function invalidateWildcardUrls(array $invalidations) {
$this
->debugger()
->callerAdd(__METHOD__);
// Change all invalidation objects into the PROCESS state before kickoff.
foreach ($invalidations as $inv) {
$inv
->setState(InvalidationInterface::PROCESSING);
}
// Generate request objects for each balancer/invalidation combination.
$ipv4_addresses = $this->platformInfo
->getBalancerAddresses();
$token = $this->platformInfo
->getBalancerToken();
$requests = function () use ($invalidations, $ipv4_addresses, $token) {
foreach ($invalidations as $inv) {
foreach ($ipv4_addresses as $ipv4) {
(yield $inv
->getId() => function ($poolopt) use ($inv, $ipv4, $token) {
$uri = str_replace('https://', 'http://', $inv
->getExpression());
$host = parse_url($uri, PHP_URL_HOST);
$uri = str_replace($host, $ipv4, $uri);
$opt = [
'headers' => [
'X-Acquia-Purge' => $token,
'Accept-Encoding' => 'gzip',
'User-Agent' => 'Acquia Purge',
'Host' => $host,
],
];
if (is_array($poolopt) && count($poolopt)) {
$opt = array_merge($poolopt, $opt);
}
return $this->httpClient
->requestAsync('BAN', $uri, $opt);
});
}
}
};
// Execute the requests generator and retrieve the results.
$results = $this
->getResultsConcurrently($requests);
// Triage the results and set all invalidation states correspondingly.
foreach ($invalidations as $invalidation) {
$inv_id = $invalidation
->getId();
if (!isset($results[$inv_id]) || !count($results[$inv_id])) {
$invalidation
->setState(InvalidationInterface::FAILED);
}
else {
if (in_array(FALSE, $results[$inv_id])) {
$invalidation
->setState(InvalidationInterface::FAILED);
}
else {
$invalidation
->setState(InvalidationInterface::SUCCEEDED);
}
}
}
$this
->debugger()
->callerRemove(__METHOD__);
}
/**
* Invalidate the entire website.
*
* This supports invalidation objects of the type 'everything'. Because many
* load balancers on Acquia Cloud host multiple websites (e.g. sites in a
* multisite) this will only affect the current site instance. This works
* because all Varnish-cached resources are tagged with a unique identifier
* coming from platformInfo::getSiteIdentifier().
*
* @see \Drupal\purge\Plugin\Purge\Purger\PurgerInterface::invalidate()
* @see \Drupal\purge\Plugin\Purge\Purger\PurgerInterface::routeTypeToMethod()
*/
public function invalidateEverything(array $invalidations) {
$this
->debugger()
->callerAdd(__METHOD__);
// Set the 'everything' object(s) into processing mode.
foreach ($invalidations as $invalidation) {
$invalidation
->setState(InvalidationInterface::PROCESSING);
}
// Fetch the site identifier and start with a successive outcome.
$overall_success = TRUE;
// Synchronously request each balancer to wipe out everything for this site.
$opt = $this
->getGlobalOptions();
$opt['headers'] = [
'X-Acquia-Purge' => $this->platformInfo
->getSiteIdentifier(),
'Accept-Encoding' => 'gzip',
'User-Agent' => 'Acquia Purge',
];
foreach ($this->platformInfo
->getBalancerAddresses() as $ip_address) {
try {
$this->httpClient
->request('BAN', 'http://' . $ip_address . '/site', $opt);
} catch (\Exception $e) {
$this
->debugger()
->logFailedRequest($e);
$overall_success = FALSE;
}
}
// Set the object states according to our overall result.
foreach ($invalidations as $invalidation) {
if ($overall_success) {
$invalidation
->setState(InvalidationInterface::SUCCEEDED);
}
else {
$invalidation
->setState(InvalidationInterface::FAILED);
}
}
$this
->debugger()
->callerRemove(__METHOD__);
}
/**
* {@inheritdoc}
*/
public function routeTypeToMethod($type) {
$methods = [
'tag' => 'invalidateTags',
'url' => 'invalidateUrls',
'wildcardurl' => 'invalidateWildcardUrls',
'everything' => 'invalidateEverything',
];
return isset($methods[$type]) ? $methods[$type] : 'invalidate';
}
}
Members
Name | Modifiers | Type | Description | Overrides |
---|---|---|---|---|
AcquiaCloudPurger:: |
protected | property | The Guzzle HTTP client. | |
AcquiaCloudPurger:: |
protected | property | Information object interfacing with the Acquia platform. | |
AcquiaCloudPurger:: |
constant | Maximum number of requests to send concurrently. | ||
AcquiaCloudPurger:: |
constant | Float: the number of seconds to wait while trying to connect to a server. | ||
AcquiaCloudPurger:: |
public static | function |
Creates an instance of the plugin. Overrides PurgerBase:: |
|
AcquiaCloudPurger:: |
protected | function | Retrieve request options used for all of Acquia Purge's balancer requests. | |
AcquiaCloudPurger:: |
public | function |
Get the maximum number of invalidations that this purger can process. Overrides PurgerBase:: |
|
AcquiaCloudPurger:: |
protected | function | Concurrently execute the given requests. | |
AcquiaCloudPurger:: |
public | function |
Indicates whether your purger utilizes dynamic runtime measurement. Overrides PurgerCapacityDataInterface:: |
|
AcquiaCloudPurger:: |
public | function |
Invalidate content from external caches. Overrides PurgerInterface:: |
|
AcquiaCloudPurger:: |
public | function | Invalidate the entire website. | |
AcquiaCloudPurger:: |
public | function | Invalidate a set of tag invalidations. | |
AcquiaCloudPurger:: |
public | function | Invalidate a set of URL invalidations. | |
AcquiaCloudPurger:: |
public | function | Invalidate URLs that contain the wildcard character "*". | |
AcquiaCloudPurger:: |
public | function |
Route certain type of invalidations to other methods. Overrides PurgerBase:: |
|
AcquiaCloudPurger:: |
constant | Groups of tags per request. | ||
AcquiaCloudPurger:: |
constant | Float: the timeout of the request in seconds. | ||
AcquiaCloudPurger:: |
final public | function |
Constructs a AcquiaCloudPurger object. Overrides PurgerBase:: |
|
DebuggerAwareTrait:: |
private | property | The debugger instance. | |
DebuggerAwareTrait:: |
public | function | ||
DebuggerAwareTrait:: |
public | function | ||
DependencySerializationTrait:: |
protected | property | An array of entity type IDs keyed by the property name of their storages. | |
DependencySerializationTrait:: |
protected | property | An array of service IDs keyed by property name used for serialization. | |
DependencySerializationTrait:: |
public | function | 1 | |
DependencySerializationTrait:: |
public | function | 2 | |
MessengerTrait:: |
protected | property | The messenger. | 29 |
MessengerTrait:: |
public | function | Gets the messenger. | 29 |
MessengerTrait:: |
public | function | Sets the messenger. | |
PluginBase:: |
protected | property | Configuration information passed into the plugin. | 1 |
PluginBase:: |
protected | property | The plugin implementation definition. | 1 |
PluginBase:: |
protected | property | The plugin_id. | |
PluginBase:: |
constant | A string which is used to separate base plugin IDs from the derivative ID. | ||
PluginBase:: |
public | function |
Gets the base_plugin_id of the plugin instance. Overrides DerivativeInspectionInterface:: |
|
PluginBase:: |
public | function |
Gets the derivative_id of the plugin instance. Overrides DerivativeInspectionInterface:: |
|
PluginBase:: |
public | function |
Gets the definition of the plugin implementation. Overrides PluginInspectionInterface:: |
3 |
PluginBase:: |
public | function |
Gets the plugin_id of the plugin instance. Overrides PluginInspectionInterface:: |
|
PluginBase:: |
public | function | Determines if the plugin is configurable. | |
PurgeLoggerAwareTrait:: |
protected | property | Channel logger. | |
PurgeLoggerAwareTrait:: |
public | function | ||
PurgerBase:: |
protected | property | Unique instance ID for this purger. | |
PurgerBase:: |
protected | property | The runtime measurement counter. | |
PurgerBase:: |
public | function |
The current instance of this purger plugin is about to be deleted. Overrides PurgerInterface:: |
1 |
PurgerBase:: |
public | function |
Get the time in seconds to wait after invalidation. Overrides PurgerCapacityDataInterface:: |
|
PurgerBase:: |
public | function |
Retrieve the unique instance ID for this purger instance. Overrides PurgerInterface:: |
|
PurgerBase:: |
public | function |
Retrieve the user-readable label for this purger instance. Overrides PurgerInterface:: |
|
PurgerBase:: |
public | function |
Get the runtime measurement counter. Overrides PurgerCapacityDataInterface:: |
|
PurgerBase:: |
public | function |
Get the maximum number of seconds, processing a single invalidation takes. Overrides PurgerCapacityDataInterface:: |
|
PurgerBase:: |
public | function |
Retrieve the list of supported invalidation types. Overrides PurgerInterface:: |
|
PurgerBase:: |
public | function |
Inject the runtime measurement counter. Overrides PurgerCapacityDataInterface:: |
|
StringTranslationTrait:: |
protected | property | The string translation service. | 1 |
StringTranslationTrait:: |
protected | function | Formats a string containing a count of items. | |
StringTranslationTrait:: |
protected | function | Returns the number of plurals supported by a given language. | |
StringTranslationTrait:: |
protected | function | Gets the string translation service. | |
StringTranslationTrait:: |
public | function | Sets the string translation service to use. | 2 |
StringTranslationTrait:: |
protected | function | Translates a string to the current language or to a given language. |