View source
<?php
namespace Drupal\acquia_purge\AcquiaPlatformCdn;
use Drupal\acquia_purge\AcquiaCloud\Hash;
use Drupal\acquia_purge\AcquiaCloud\PlatformInfoInterface;
use Drupal\acquia_purge\Plugin\Purge\Purger\DebuggerInterface;
use Drupal\acquia_purge\Plugin\Purge\TagsHeader\TagsHeaderValue;
use Drupal\purge\Logger\LoggerChannelPartInterface;
use Drupal\purge\Plugin\Purge\Invalidation\InvalidationInterface;
use GuzzleHttp\ClientInterface;
use GuzzleHttp\Exception\RequestException;
use GuzzleHttp\Psr7\Request;
use Psr\Http\Message\ResponseInterface;
class FastlyBackend extends BackendBase implements BackendInterface {
const API_ENDPOINT = 'https://api.fastly.com/';
const CONNECT_TIMEOUT = 1.5;
const TIMEOUT = 3.0;
protected $serviceId;
protected $token;
public function __construct(array $config, PlatformInfoInterface $acquia_purge_platforminfo, LoggerChannelPartInterface $logger, DebuggerInterface $debugger, ClientInterface $http_client) {
parent::__construct($config, $acquia_purge_platforminfo, $logger, $debugger, $http_client);
$this->serviceId = (string) $this->config['service_id'];
$this->token = (string) $this->config['token'];
self::platformInfo($this->platformInfo);
}
public function invalidateTags(array $invalidations) {
$tags = [];
foreach ($invalidations as $invalidation) {
$invalidation
->setState(InvalidationInterface::PROCESSING);
$tags[] = $invalidation
->getExpression();
}
$tags = new TagsHeaderValue($tags, self::getHashedTags($tags));
$success = FALSE;
try {
$request = new Request('POST', $this
->fastlyRequestUri('service/service_id/purge'));
$request_opt = $this
->fastlyRequestOpt([
'Surrogate-Key' => $tags
->__toString(),
]);
if ($this
->debugger()
->enabled()) {
$request_opt['acquia_purge_tags'] = $tags;
}
$response = $this->httpClient
->send($request, $request_opt);
$data = $this
->fastlyResponseData($response);
if (count($data)) {
$success = TRUE;
}
else {
throw new RequestException('Unexpected API response.', $request, $response);
}
} catch (\Exception $e) {
$this
->debugger()
->logFailedRequest($e);
}
if ($success) {
foreach ($invalidations as $invalidation) {
$invalidation
->setState(InvalidationInterface::SUCCEEDED);
}
}
else {
foreach ($invalidations as $invalidation) {
$invalidation
->setState(InvalidationInterface::FAILED);
}
}
}
public function invalidateUrls(array $invalidations) {
$request_opt = $this
->fastlyRequestOpt();
$request_opt['verify'] = FALSE;
unset($request_opt['headers']['Accept']);
unset($request_opt['headers']['Fastly-Key']);
foreach ($invalidations as $invalidation) {
$invalidation
->setState(InvalidationInterface::PROCESSING);
$success = FALSE;
try {
$request = new Request('PURGE', $invalidation
->getExpression());
$response = $this->httpClient
->send($request, $request_opt);
$data = $this
->fastlyResponseData($response);
if (isset($data['status']) && $data['status'] === 'ok') {
$success = TRUE;
}
else {
throw new RequestException('Unexpected API response.', $request, $response);
}
} catch (\Exception $e) {
$this
->debugger()
->logFailedRequest($e);
}
if ($success) {
$invalidation
->setState(InvalidationInterface::SUCCEEDED);
}
else {
$invalidation
->setState(InvalidationInterface::FAILED);
}
}
}
public function invalidateEverything(array $invalidations) {
foreach ($invalidations as $invalidation) {
$invalidation
->setState(InvalidationInterface::PROCESSING);
}
$key = current(Hash::cacheTags([
$this->platformInfo
->getSiteIdentifier(),
]));
$success = FALSE;
try {
$request = new Request('POST', $this
->fastlyRequestUri('service/service_id/purge/%s', $key));
$response = $this->httpClient
->send($request, $this
->fastlyRequestOpt());
$data = $this
->fastlyResponseData($response);
if (isset($data['status']) && $data['status'] === 'ok') {
$success = TRUE;
}
else {
throw new RequestException('Unexpected API response.', $request, $response);
}
} catch (\Exception $e) {
$this
->debugger()
->logFailedRequest($e);
}
if ($success) {
foreach ($invalidations as $invalidation) {
$invalidation
->setState(InvalidationInterface::SUCCEEDED);
}
}
else {
foreach ($invalidations as $invalidation) {
$invalidation
->setState(InvalidationInterface::FAILED);
}
}
}
public static function tagsHeaderName() {
return 'Surrogate-Key';
}
public static function tagsHeaderValue(array $tags) {
$tags_hashed = self::getHashedTags($tags);
$tags[] = $identifier = self::platformInfo()
->getSiteIdentifier();
$tags_hashed[] = current(Hash::cacheTags([
$identifier,
]));
return new TagsHeaderValue($tags, $tags_hashed);
}
protected function fastlyRequestOpt(array $headers = []) {
$opt = [
'headers' => $headers,
'http_errors' => FALSE,
'connect_timeout' => self::CONNECT_TIMEOUT,
'timeout' => self::TIMEOUT,
];
$opt['headers']['Accept'] = 'application/json';
$opt['headers']['Fastly-Key'] = $this->token;
$opt['headers']['User-Agent'] = 'Acquia Purge';
if ($this
->debugger()
->enabled()) {
$opt['acquia_purge_debugger'] = $this
->debugger();
}
return $opt;
}
protected function fastlyRequestUri($path) {
$args = func_get_args();
$args[0] = str_replace('service_id', $this->serviceId, $args[0]);
$args[0] = ltrim($args[0], '/');
return self::API_ENDPOINT . call_user_func_array('sprintf', $args);
}
protected function fastlyResponseData(ResponseInterface $response) {
if ($data = json_decode($response
->getBody(), TRUE)) {
if (isset($data['msg']) && strpos($data['msg'], 'credentials') !== FALSE) {
$message = "Invalid credentials - please contact Acquia Support and";
$message .= " clear the cache after the issue got resolved.";
self::setTemporaryRuntimeError($message, 86400);
throw new \RuntimeException($message);
}
if (isset($data['msg'], $data['detail']) && $data['msg'] == 'Record not found') {
if ($data['detail'] == 'Cannot find service') {
$message = "Invalid environment - please contact Acquia Support and";
$message .= " clear the cache after the issue got resolved.";
self::setTemporaryRuntimeError($message, 43200);
throw new \RuntimeException($message);
}
}
return $data;
}
return [];
}
protected static function getHashedTags(array $tags) {
$identifier = self::platformInfo()
->getSiteIdentifier();
$tags_prefixed = [];
foreach ($tags as $tag) {
$tags_prefixed[] = $identifier . $tag;
}
return Hash::cacheTags($tags_prefixed);
}
public static function validateConfiguration(array $config) {
if (!isset($config['service_id'], $config['token'])) {
return FALSE;
}
if (!(strlen($config['service_id']) && strlen($config['token']))) {
return FALSE;
}
return TRUE;
}
}