Skip to content

Commit 7bcc34f

Browse files
authored
Add Fastly support (#597)
1 parent 521e8ef commit 7bcc34f

File tree

12 files changed

+240
-7
lines changed

12 files changed

+240
-7
lines changed

Resources/doc/features/invalidation.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ Invalidation
66
* :ref:`Varnish <foshttpcache:varnish configuration>`
77
* :ref:`Nginx <foshttpcache:nginx configuration>` (except regular expressions)
88
* :doc:`symfony-http-cache` (except regular expressions)
9+
* Fastly (except regular expressions)
910

1011
**Preparation**:
1112

Resources/doc/features/tagging.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ Tagging
55

66
* :ref:`Varnish <foshttpcache:varnish_tagging>`
77
* :ref:`Symfony <foshttpcache:symfony_httpcache_tagging>`
8+
* Fastly
89

910
If your application has many intricate relationships between cached items,
1011
which makes it complex to invalidate them by route, cache tagging will be

Resources/doc/reference/configuration/proxy-client.rst

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ can be autowired with the ``FOS\HttpCache\ProxyClient\ProxyClient`` type
1313
declaration or the service ``fos_http_cache.default_proxy_client``. Specific
1414
clients, if configured, are available as ``fos_http_cache.proxy_client.varnish``
1515
, ``fos_http_cache.proxy_client.nginx``, ``fos_http_cache.proxy_client.symfony``
16-
or ``fos_http_cache.proxy_client.cloudflare``).
16+
, ``fos_http_cache.proxy_client.cloudflare`` or ``fos_http_cache.proxy_client.fastly``).
1717

1818
If you need to adjust the proxy client, you can also configure the ``CacheManager``
1919
with a :ref:`custom proxy client <custom_proxy_client>` that you defined as a
@@ -325,6 +325,41 @@ the `AWS Async documentation_`. It can not be used with the ``client`` option.
325325
Service identifier of a `AsyncAws\CloudFront\CloudFrontClient` client. More information is available on the
326326
`AWS Async documentation_`. It can not be used with the ``configuration`` option.
327327

328+
.. _configuration_fastly_proxy_client:
329+
330+
Fastly
331+
----------
332+
.. code-block:: yaml
333+
334+
# config/packages/fos_http_cache.yaml
335+
fos_http_cache:
336+
proxy_client:
337+
fastly:
338+
service_identifier: '<my-service-identifier>'
339+
authentication_token: '<my-authentication-token>'
340+
soft_purge: true
341+
342+
``service_identifier``
343+
"""""""""""""""""""""
344+
345+
**type**: ``string``
346+
347+
Identifier for the Fastly service you want to purge the cache for.
348+
349+
``authentication_token``
350+
"""""""""""""""""""""
351+
352+
**type**: ``string``
353+
354+
Authentication token (API Token) which can be created in the profile section of your account
355+
356+
``soft_purge``
357+
"""""""""""""""""""""
358+
359+
**type**: ``boolean`` **default**: ``true``
360+
361+
Boolean for doing soft purges or not on tag & URL purging. Soft purges expires the cache unlike hard purge (removal), and allow grace/stale handling within Fastly VCL.
362+
328363
.. _configuration_noop_proxy_client:
329364

330365
noop

Resources/doc/spelling_word_list.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ cacheable
88
cloudflare
99
cloudfront
1010
ETag
11+
Fastly
1112
friendsofsymfony
1213
github
1314
hardcoded

src/DependencyInjection/Configuration.php

Lines changed: 74 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,17 @@ function ($v) {
175175
}
176176
)
177177
->then(function ($v) {
178-
$v['tags']['response_header'] = $this->isVarnishXkey($v) ? 'xkey' : TagHeaderFormatter::DEFAULT_HEADER_NAME;
178+
switch (true) {
179+
case $this->isVarnishXkey($v):
180+
$v['tags']['response_header'] = 'xkey';
181+
break;
182+
case $this->isFastly($v):
183+
$v['tags']['response_header'] = 'Surrogate-Key';
184+
break;
185+
default:
186+
$v['tags']['response_header'] = TagHeaderFormatter::DEFAULT_HEADER_NAME;
187+
break;
188+
}
179189

180190
return $v;
181191
})
@@ -188,7 +198,15 @@ function ($v) {
188198
}
189199
)
190200
->then(function ($v) {
191-
$v['tags']['separator'] = $this->isVarnishXkey($v) ? ' ' : ',';
201+
switch (true) {
202+
case $this->isVarnishXkey($v):
203+
case $this->isFastly($v):
204+
$v['tags']['separator'] = ' ';
205+
break;
206+
default:
207+
$v['tags']['separator'] = ',';
208+
break;
209+
}
192210

193211
return $v;
194212
})
@@ -216,6 +234,12 @@ private function isVarnishXkey(array $v): bool
216234
;
217235
}
218236

237+
private function isFastly(array $v): bool
238+
{
239+
return array_key_exists('proxy_client', $v)
240+
&& array_key_exists('fastly', $v['proxy_client']);
241+
}
242+
219243
private function addCacheableResponseSection(ArrayNodeDefinition $rootNode)
220244
{
221245
$rootNode
@@ -413,7 +437,7 @@ private function addProxyClientSection(ArrayNodeDefinition $rootNode)
413437
->arrayNode('proxy_client')
414438
->children()
415439
->enumNode('default')
416-
->values(['varnish', 'nginx', 'symfony', 'cloudflare', 'cloudfront', 'noop'])
440+
->values(['varnish', 'nginx', 'symfony', 'cloudflare', 'cloudfront', 'fastly', 'noop'])
417441
->info('If you configure more than one proxy client, you need to specify which client is the default.')
418442
->end()
419443
->arrayNode('varnish')
@@ -518,6 +542,23 @@ private function addProxyClientSection(ArrayNodeDefinition $rootNode)
518542
->end()
519543
->end()
520544

545+
->arrayNode('fastly')
546+
->info('Configure a client to interact with Fastly.')
547+
->children()
548+
->scalarNode('service_identifier')
549+
->info('Identifier for your Fastly service account.')
550+
->end()
551+
->scalarNode('authentication_token')
552+
->info('User token for authentication against Fastly APIs.')
553+
->end()
554+
->scalarNode('soft_purge')
555+
->info('Boolean for doing soft purges or not on tag & URL purging. Soft purges expires the cache unlike hard purge (removal), and allow grace/stale handling within Fastly VCL.')
556+
->defaultValue(true)
557+
->end()
558+
->append($this->getFastlyHttpDispatcherNode())
559+
->end()
560+
->end()
561+
521562
->booleanNode('noop')->end()
522563
->end()
523564
->validate()
@@ -536,7 +577,7 @@ private function addProxyClientSection(ArrayNodeDefinition $rootNode)
536577
throw new InvalidConfigurationException(sprintf('You can only set one of "http.servers" or "http.servers_from_jsonenv" but not both to avoid ambiguity for the proxy "%s"', $proxyName));
537578
}
538579

539-
if (!\in_array($proxyName, ['noop', 'default', 'symfony', 'cloudflare', 'cloudfront'])) {
580+
if (!\in_array($proxyName, ['noop', 'default', 'symfony', 'cloudflare', 'cloudfront', 'fastly'])) {
540581
if (!$arrayServersConfigured && !$jsonServersConfigured) {
541582
throw new InvalidConfigurationException(sprintf('The "http.servers" or "http.servers_from_jsonenv" section must be defined for the proxy "%s"', $proxyName));
542583
}
@@ -636,6 +677,35 @@ private function getCloudflareHttpDispatcherNode()
636677
return $node;
637678
}
638679

680+
private function getFastlyHttpDispatcherNode()
681+
{
682+
$treeBuilder = new TreeBuilder('http');
683+
684+
$node = $treeBuilder->getRootNode();
685+
686+
$node
687+
->addDefaultsIfNotSet()
688+
->children()
689+
->arrayNode('servers')
690+
->info('Addresses of the hosts the caching proxy is running on. The values may be hostnames or ips, and with :port if not the default port. For fastly, you normally do not need to change the default value.')
691+
->useAttributeAsKey('name')
692+
->requiresAtLeastOneElement()
693+
->defaultValue(['https://api.fastly.com'])
694+
->prototype('scalar')->end()
695+
->end()
696+
->scalarNode('base_url')
697+
->defaultValue('service')
698+
->info('Default host name and optional path for path based invalidation.')
699+
->end()
700+
->scalarNode('http_client')
701+
->defaultNull()
702+
->info('Httplug async client service name to use for sending the requests.')
703+
->end()
704+
->end();
705+
706+
return $node;
707+
}
708+
639709
private function addTestSection(ArrayNodeDefinition $rootNode)
640710
{
641711
$rootNode

src/DependencyInjection/FOSHttpCacheExtension.php

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,9 @@ private function loadProxyClient(ContainerBuilder $container, XmlFileLoader $loa
346346
if (isset($config['cloudfront'])) {
347347
$this->loadCloudfront($container, $loader, $config['cloudfront']);
348348
}
349+
if (isset($config['fastly'])) {
350+
$this->loadFastly($container, $loader, $config['fastly']);
351+
}
349352
if (isset($config['noop'])) {
350353
$loader->load('noop.xml');
351354
}
@@ -498,19 +501,34 @@ private function loadCloudfront(ContainerBuilder $container, XmlFileLoader $load
498501
$loader->load('cloudfront.xml');
499502
}
500503

504+
private function loadFastly(ContainerBuilder $container, XmlFileLoader $loader, array $config)
505+
{
506+
$this->createHttpDispatcherDefinition($container, $config['http'], 'fos_http_cache.proxy_client.fastly.http_dispatcher');
507+
508+
$options = [
509+
'service_identifier' => $config['service_identifier'],
510+
'authentication_token' => $config['authentication_token'],
511+
'soft_purge' => $config['soft_purge'],
512+
];
513+
514+
$container->setParameter('fos_http_cache.proxy_client.fastly.options', $options);
515+
516+
$loader->load('fastly.xml');
517+
}
518+
501519
/**
502520
* @param array $config Configuration section for the tags node
503521
* @param string $client Name of the client used with the cache manager,
504522
* "custom" when a custom client is used
505523
*/
506524
private function loadCacheTagging(ContainerBuilder $container, XmlFileLoader $loader, array $config, $client)
507525
{
508-
if ('auto' === $config['enabled'] && !in_array($client, ['varnish', 'symfony', 'cloudflare'])) {
526+
if ('auto' === $config['enabled'] && !in_array($client, ['varnish', 'symfony', 'cloudflare', 'fastly'])) {
509527
$container->setParameter('fos_http_cache.compiler_pass.tag_annotations', false);
510528

511529
return;
512530
}
513-
if (!in_array($client, ['varnish', 'symfony', 'cloudflare', 'custom', 'noop'])) {
531+
if (!in_array($client, ['varnish', 'symfony', 'cloudflare', 'custom', 'fastly', 'noop'])) {
514532
throw new InvalidConfigurationException(sprintf('You can not enable cache tagging with the %s client', $client));
515533
}
516534

@@ -660,6 +678,10 @@ private function getDefaultProxyClient(array $config)
660678
return 'cloudfront';
661679
}
662680

681+
if (isset($config['fastly'])) {
682+
return 'fastly';
683+
}
684+
663685
if (isset($config['noop'])) {
664686
return 'noop';
665687
}

src/Resources/config/fastly.xml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?xml version="1.0" ?>
2+
3+
<container xmlns="http://symfony.com/schema/dic/services"
4+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
5+
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
6+
7+
<services>
8+
<service id="fos_http_cache.proxy_client.fastly"
9+
class="FOS\HttpCache\ProxyClient\Fastly"
10+
public="false">
11+
<argument type="service" id="fos_http_cache.proxy_client.fastly.http_dispatcher"/>
12+
<argument>%fos_http_cache.proxy_client.fastly.options%</argument>
13+
</service>
14+
</services>
15+
16+
</container>
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the FOSHttpCacheBundle package.
5+
*
6+
* (c) FriendsOfSymfony <http://friendsofsymfony.github.com/>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
$container->loadFromExtension('fos_http_cache', [
13+
'proxy_client' => [
14+
'fastly' => [
15+
'service_identifier' => 'myServiceIdentifier',
16+
'authentication_token' => 'mytoken',
17+
],
18+
],
19+
]);
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?xml version="1.0" encoding="UTF-8" ?>
2+
<container xmlns="http://symfony.com/schema/dic/services">
3+
4+
<config xmlns="http://example.org/schema/dic/fos_http_cache">
5+
<proxy-client>
6+
<fastly authentication-token="mytoken" service-identifier="myServiceIdentifier">
7+
</fastly>
8+
</proxy-client>
9+
10+
</config>
11+
</container>
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
fos_http_cache:
2+
3+
proxy_client:
4+
fastly:
5+
service_identifier: myServiceIdentifier
6+
authentication_token: mytoken

tests/Unit/DependencyInjection/ConfigurationTest.php

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -382,6 +382,37 @@ public function testCloudfrontConfigurationWithClientIsNotAllowed()
382382
(new Processor())->processConfiguration($configuration, ['fos_http_cache' => $params]);
383383
}
384384

385+
public function testSupportsFastly()
386+
{
387+
$expectedConfiguration = $this->getEmptyConfig();
388+
$expectedConfiguration['proxy_client'] = [
389+
'fastly' => [
390+
'service_identifier' => 'myServiceIdentifier',
391+
'authentication_token' => 'mytoken',
392+
'soft_purge' => true,
393+
'http' => ['servers' => ['https://api.fastly.com'], 'http_client' => null, 'base_url' => 'service'],
394+
],
395+
];
396+
$expectedConfiguration['cache_manager']['enabled'] = 'auto';
397+
$expectedConfiguration['cache_manager']['generate_url_type'] = 'auto';
398+
$expectedConfiguration['tags']['enabled'] = 'auto';
399+
$expectedConfiguration['tags']['response_header'] = 'Surrogate-Key';
400+
$expectedConfiguration['tags']['separator'] = ' ';
401+
$expectedConfiguration['invalidation']['enabled'] = 'auto';
402+
403+
$formats = array_map(function ($path) {
404+
return __DIR__.'/../../Resources/Fixtures/'.$path;
405+
}, [
406+
'config/fastly.yml',
407+
'config/fastly.xml',
408+
'config/fastly.php',
409+
]);
410+
411+
foreach ($formats as $format) {
412+
$this->assertProcessedConfigurationEquals($expectedConfiguration, [$format]);
413+
}
414+
}
415+
385416
public function testEmptyServerConfigurationIsNotAllowed()
386417
{
387418
$this->expectException(InvalidConfigurationException::class);

tests/Unit/DependencyInjection/FOSHttpCacheExtensionTest.php

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,26 @@ public function testConfigLoadCloudfrontWithClient()
220220
$this->assertTrue($container->hasDefinition('fos_http_cache.event_listener.invalidation'));
221221
}
222222

223+
public function testConfigLoadFastly()
224+
{
225+
$container = $this->createContainer();
226+
$this->extension->load([
227+
[
228+
'proxy_client' => [
229+
'fastly' => [
230+
'service_identifier' => 'test',
231+
'authentication_token' => 'test',
232+
],
233+
],
234+
],
235+
], $container);
236+
237+
$this->assertFalse($container->hasDefinition('fos_http_cache.proxy_client.varnish'));
238+
$this->assertTrue($container->hasDefinition('fos_http_cache.proxy_client.fastly'));
239+
$this->assertTrue($container->hasAlias('fos_http_cache.default_proxy_client'));
240+
$this->assertTrue($container->hasDefinition('fos_http_cache.event_listener.invalidation'));
241+
}
242+
223243
public function testConfigLoadNoop()
224244
{
225245
$container = $this->createContainer();

0 commit comments

Comments
 (0)