Skip to content

Commit add12ba

Browse files
committed
Add CloudFront support
1 parent 715d06d commit add12ba

File tree

10 files changed

+264
-3
lines changed

10 files changed

+264
-3
lines changed

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

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,54 @@ endpoint for testing purposes.
276276

277277
.. _configuration_noop_proxy_client:
278278

279+
cloudfront
280+
----------
281+
282+
.. code-block:: yaml
283+
284+
# config/packages/fos_http_cache.yaml
285+
fos_http_cache:
286+
proxy_client:
287+
cloudfront:
288+
distribution_id: '<my-distribution-id>'
289+
configuration:
290+
accessKeyId: '<my-access-key-id>'
291+
accessKeySecret: '<my-access-key-secret>'
292+
293+
.. code-block:: yaml
294+
295+
# config/packages/fos_http_cache.yaml
296+
fos_http_cache:
297+
proxy_client:
298+
cloudfront:
299+
distribution_id: '<my-distribution-id>'
300+
client: '<my.custom.client>'
301+
302+
``distribution_id``
303+
"""""""""""""""""""
304+
305+
**type**: ``string``
306+
307+
Identifier for the CloudFront distribution you want to purge the cache for.
308+
309+
``configuration``
310+
"""""""""""""""""
311+
312+
**type**: ``array`` **default**: ``[]``
313+
314+
Configuration used to instantiate the `AsyncAws\CloudFront\CloudFrontClient` client. More information is available on
315+
the `AWS Async documentation_`. It can not be used with the ``client`` option.
316+
317+
``client``
318+
"""""""""""""""""
319+
320+
**type**: ``string`` **default**: ``null``
321+
322+
Service identifier of a `AsyncAws\CloudFront\CloudFrontClient` client. More information is available on the
323+
`AWS Async documentation_`. It can not be used with the ``configuration`` option.
324+
325+
.. _configuration_noop_proxy_client:
326+
279327
noop
280328
----
281329

@@ -328,3 +376,4 @@ bundle. Please refer to the :ref:`FOSHttpCache library’s documentation <foshtt
328376
for more information.
329377

330378
.. _xkey vmod: https://github.com/varnish/varnish-modules/blob/master/docs/vmod_xkey.rst
379+
.. _AWS Async documentation_: https://async-aws.com/configuration.html

composer.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,11 @@
5050
"symfony/monolog-bundle": "^3.0",
5151
"symfony/routing": "^4.4 || ^5.0 || ^6.0",
5252
"matthiasnoback/symfony-dependency-injection-test": "^4.0",
53-
"sebastian/exporter": "^2.0"
53+
"sebastian/exporter": "^2.0",
54+
"jean-beru/fos-http-cache-cloudfront": "^1.0"
5455
},
5556
"suggest": {
57+
"jean-beru/fos-http-cache-cloudfront": "To use CloudFront proxy",
5658
"sensio/framework-extra-bundle": "For Tagged Cache Invalidation",
5759
"symfony/expression-language": "For Tagged Cache Invalidation",
5860
"symfony/console": "To send invalidation requests from the command line"

src/DependencyInjection/Configuration.php

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use FOS\HttpCache\SymfonyCache\PurgeListener;
1616
use FOS\HttpCache\SymfonyCache\PurgeTagsListener;
1717
use FOS\HttpCache\TagHeaderFormatter\TagHeaderFormatter;
18+
use JeanBeru\HttpCacheCloudFront\Proxy\CloudFront;
1819
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
1920
use Symfony\Component\Config\Definition\Builder\NodeBuilder;
2021
use Symfony\Component\Config\Definition\Builder\NodeDefinition;
@@ -412,7 +413,7 @@ private function addProxyClientSection(ArrayNodeDefinition $rootNode)
412413
->arrayNode('proxy_client')
413414
->children()
414415
->enumNode('default')
415-
->values(['varnish', 'nginx', 'symfony', 'cloudflare', 'noop'])
416+
->values(['varnish', 'nginx', 'symfony', 'cloudflare', 'cloudfront', 'noop'])
416417
->info('If you configure more than one proxy client, you need to specify which client is the default.')
417418
->end()
418419
->arrayNode('varnish')
@@ -494,6 +495,28 @@ private function addProxyClientSection(ArrayNodeDefinition $rootNode)
494495
->end()
495496
->end()
496497

498+
->arrayNode('cloudfront')
499+
->children()
500+
->scalarNode('distribution_id')
501+
->info('Identifier for your CloudFront distribution you want to purge the cache for')
502+
->end()
503+
->scalarNode('client')
504+
->info('AsyncAws\CloudFront\CloudFrontClient client to use')
505+
->defaultNull()
506+
->end()
507+
->variableNode('configuration')
508+
->defaultValue([])
509+
->info('Client configuration from https://async-aws.com/configuration.html')
510+
->end()
511+
->end()
512+
->validate()
513+
->ifTrue(function ($v) {
514+
return !empty($v['client']) && !empty($v['configuration']);
515+
})
516+
->thenInvalid('You can not set both cloudfront.client and cloudfront.configuration')
517+
->end()
518+
->end()
519+
497520
->booleanNode('noop')->end()
498521
->end()
499522
->validate()
@@ -512,7 +535,7 @@ private function addProxyClientSection(ArrayNodeDefinition $rootNode)
512535
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));
513536
}
514537

515-
if (!\in_array($proxyName, ['noop', 'default', 'symfony', 'cloudflare'])) {
538+
if (!\in_array($proxyName, ['noop', 'default', 'symfony', 'cloudflare', 'cloudfront'])) {
516539
if (!$arrayServersConfigured && !$jsonServersConfigured) {
517540
throw new InvalidConfigurationException(sprintf('The "http.servers" or "http.servers_from_jsonenv" section must be defined for the proxy "%s"', $proxyName));
518541
}
@@ -525,6 +548,12 @@ private function addProxyClientSection(ArrayNodeDefinition $rootNode)
525548
throw new InvalidConfigurationException('Either configure the "http.servers" or "http.servers_from_jsonenv" section or enable "proxy_client.symfony.use_kernel_dispatcher"');
526549
}
527550
}
551+
552+
if ('cloudfront' === $proxyName) {
553+
if (!class_exists(CloudFront::class)) {
554+
throw new InvalidConfigurationException('Did you forget to install jean-beru/fos-http-cache-cloudfront ?');
555+
}
556+
}
528557
}
529558

530559
return $config;

src/DependencyInjection/FOSHttpCacheExtension.php

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,14 @@
1111

1212
namespace FOS\HttpCacheBundle\DependencyInjection;
1313

14+
use AsyncAws\CloudFront\CloudFrontClient;
1415
use FOS\HttpCache\ProxyClient\HttpDispatcher;
1516
use FOS\HttpCache\ProxyClient\ProxyClient;
1617
use FOS\HttpCache\SymfonyCache\KernelDispatcher;
1718
use FOS\HttpCache\TagHeaderFormatter\MaxHeaderValueLengthFormatter;
1819
use FOS\HttpCacheBundle\DependencyInjection\Compiler\HashGeneratorPass;
1920
use FOS\HttpCacheBundle\Http\ResponseMatcher\ExpressionResponseMatcher;
21+
use JeanBeru\HttpCacheCloudFront\Proxy\CloudFront;
2022
use Symfony\Component\Config\Definition\ConfigurationInterface;
2123
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
2224
use Symfony\Component\Config\FileLocator;
@@ -90,6 +92,8 @@ public function load(array $configs, ContainerBuilder $container)
9092
if ('noop' !== $defaultClient
9193
&& array_key_exists('base_url', $config['proxy_client'][$defaultClient])) {
9294
$generateUrlType = UrlGeneratorInterface::ABSOLUTE_PATH;
95+
} elseif('cloudfront' === $defaultClient) {
96+
$generateUrlType = UrlGeneratorInterface::ABSOLUTE_PATH;
9397
} else {
9498
$generateUrlType = UrlGeneratorInterface::ABSOLUTE_URL;
9599
}
@@ -340,6 +344,9 @@ private function loadProxyClient(ContainerBuilder $container, XmlFileLoader $loa
340344
if (isset($config['cloudflare'])) {
341345
$this->loadCloudflare($container, $loader, $config['cloudflare']);
342346
}
347+
if (isset($config['cloudfront'])) {
348+
$this->loadCloudfront($container, $loader, $config['cloudfront']);
349+
}
343350
if (isset($config['noop'])) {
344351
$loader->load('noop.xml');
345352
}
@@ -471,6 +478,27 @@ private function loadCloudflare(ContainerBuilder $container, XmlFileLoader $load
471478
$loader->load('cloudflare.xml');
472479
}
473480

481+
private function loadCloudfront(ContainerBuilder $container, XmlFileLoader $loader, array $config)
482+
{
483+
if (null !== $config['client']) {
484+
$container->setAlias(
485+
'fos_http_cache.proxy_client.cloudfront.cloudfront_client',
486+
$config['client']
487+
);
488+
} else {
489+
$container->setDefinition(
490+
'fos_http_cache.proxy_client.cloudfront.cloudfront_client',
491+
new Definition(CloudFrontClient::class, [$config['configuration']])
492+
);
493+
}
494+
495+
$container->setParameter('fos_http_cache.proxy_client.cloudfront.options', [
496+
'distribution_id' => $config['distribution_id'],
497+
]);
498+
499+
$loader->load('cloudfront.xml');
500+
}
501+
474502
/**
475503
* @param array $config Configuration section for the tags node
476504
* @param string $client Name of the client used with the cache manager,
@@ -629,6 +657,10 @@ private function getDefaultProxyClient(array $config)
629657
return 'cloudflare';
630658
}
631659

660+
if (isset($config['cloudfront'])) {
661+
return 'cloudfront';
662+
}
663+
632664
if (isset($config['noop'])) {
633665
return 'noop';
634666
}

src/Resources/config/cloudfront.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.cloudfront"
9+
class="JeanBeru\HttpCacheCloudFront\Proxy\CloudFront"
10+
public="true">
11+
<argument type="service" id="fos_http_cache.proxy_client.cloudfront.cloudfront_client"/>
12+
<argument>%fos_http_cache.proxy_client.cloudfront.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+
'cloudfront' => [
15+
'distribution_id' => 'my_distribution',
16+
'configuration' => ['accessKeyId' => 'AwsAccessKeyId', 'accessKeySecret' => 'AwsAccessKeySecret'],
17+
],
18+
],
19+
]);
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
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+
<cloudfront distribution-id="my_distribution">
7+
<configuration>
8+
<accessKeyId>AwsAccessKeyId</accessKeyId>
9+
<accessKeySecret>AwsAccessKeySecret</accessKeySecret>
10+
</configuration>
11+
</cloudfront>
12+
</proxy-client>
13+
14+
</config>
15+
</container>
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
fos_http_cache:
2+
3+
proxy_client:
4+
cloudfront:
5+
distribution_id: 'my_distribution'
6+
configuration:
7+
accessKeyId: 'AwsAccessKeyId'
8+
accessKeySecret: 'AwsAccessKeySecret'

tests/Unit/DependencyInjection/ConfigurationTest.php

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,52 @@ public function testSupportsCloudflare()
327327
}
328328
}
329329

330+
public function testSupportsCloudfront()
331+
{
332+
$expectedConfiguration = $this->getEmptyConfig();
333+
$expectedConfiguration['proxy_client'] = [
334+
'cloudfront' => [
335+
'distribution_id' => 'my_distribution',
336+
'configuration' => ['accessKeyId' => 'AwsAccessKeyId', 'accessKeySecret' => 'AwsAccessKeySecret'],
337+
'client' => null,
338+
],
339+
];
340+
$expectedConfiguration['cache_manager']['enabled'] = 'auto';
341+
$expectedConfiguration['cache_manager']['generate_url_type'] = 'auto';
342+
$expectedConfiguration['tags']['enabled'] = 'auto';
343+
$expectedConfiguration['invalidation']['enabled'] = 'auto';
344+
345+
$formats = array_map(function ($path) {
346+
return __DIR__.'/../../Resources/Fixtures/'.$path;
347+
}, [
348+
'config/cloudfront.yml',
349+
'config/cloudfront.xml',
350+
'config/cloudfront.php',
351+
]);
352+
353+
foreach ($formats as $format) {
354+
$this->assertProcessedConfigurationEquals($expectedConfiguration, [$format]);
355+
}
356+
}
357+
358+
public function testCloudfrontConfigurationWithClientIsNotAllowed()
359+
{
360+
$this->expectException(InvalidConfigurationException::class);
361+
$this->expectExceptionMessage('You can not set both cloudfront.client and cloudfront.configuration');
362+
363+
$params = $this->getEmptyConfig();
364+
$params['proxy_client'] = [
365+
'cloudfront' => [
366+
'distribution_id' => 'my_distribution',
367+
'configuration' => ['accessKeyId' => 'AwsAccessKeyId', 'accessKeySecret' => 'AwsAccessKeySecret'],
368+
'client' => 'my.client',
369+
],
370+
];
371+
372+
$configuration = new Configuration(false);
373+
(new Processor())->processConfiguration($configuration, ['fos_http_cache' => $params]);
374+
}
375+
330376
public function testEmptyServerConfigurationIsNotAllowed()
331377
{
332378
$this->expectException(InvalidConfigurationException::class);

tests/Unit/DependencyInjection/FOSHttpCacheExtensionTest.php

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,51 @@ public function testConfigLoadCloudflare()
166166
$this->assertTrue($container->hasDefinition('fos_http_cache.event_listener.invalidation'));
167167
}
168168

169+
public function testConfigLoadCloudfront()
170+
{
171+
$container = $this->createContainer();
172+
$this->extension->load([
173+
[
174+
'proxy_client' => [
175+
'cloudfront' => [
176+
'distribution_id' => 'my_distribution',
177+
],
178+
],
179+
],
180+
], $container);
181+
182+
$this->assertFalse($container->hasDefinition('fos_http_cache.proxy_client.varnish'));
183+
$this->assertTrue($container->hasDefinition('fos_http_cache.proxy_client.cloudfront.cloudfront_client'));
184+
$this->assertTrue($container->hasParameter('fos_http_cache.proxy_client.cloudfront.options'));
185+
$this->assertSame(['distribution_id' => 'my_distribution'], $container->getParameter('fos_http_cache.proxy_client.cloudfront.options'));
186+
$this->assertTrue($container->hasDefinition('fos_http_cache.proxy_client.cloudfront'));
187+
$this->assertTrue($container->hasAlias('fos_http_cache.default_proxy_client'));
188+
$this->assertTrue($container->hasDefinition('fos_http_cache.event_listener.invalidation'));
189+
}
190+
191+
public function testConfigLoadCloudfrontWithClient()
192+
{
193+
$container = $this->createContainer();
194+
$this->extension->load([
195+
[
196+
'proxy_client' => [
197+
'cloudfront' => [
198+
'distribution_id' => 'my_distribution',
199+
'client' => 'my.client',
200+
],
201+
],
202+
],
203+
], $container);
204+
205+
$this->assertFalse($container->hasDefinition('fos_http_cache.proxy_client.varnish'));
206+
$this->assertTrue($container->hasAlias('fos_http_cache.proxy_client.cloudfront.cloudfront_client'));
207+
$this->assertTrue($container->hasParameter('fos_http_cache.proxy_client.cloudfront.options'));
208+
$this->assertSame(['distribution_id' => 'my_distribution'], $container->getParameter('fos_http_cache.proxy_client.cloudfront.options'));
209+
$this->assertTrue($container->hasDefinition('fos_http_cache.proxy_client.cloudfront'));
210+
$this->assertTrue($container->hasAlias('fos_http_cache.default_proxy_client'));
211+
$this->assertTrue($container->hasDefinition('fos_http_cache.event_listener.invalidation'));
212+
}
213+
169214
public function testConfigLoadNoop()
170215
{
171216
$container = $this->createContainer();

0 commit comments

Comments
 (0)