Skip to content

Commit

Permalink
Add cloudflare support (#587)
Browse files Browse the repository at this point in the history
  • Loading branch information
jean-gui committed Feb 21, 2023
1 parent be22ba1 commit 715d06d
Show file tree
Hide file tree
Showing 11 changed files with 212 additions and 5 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
@@ -1,6 +1,11 @@
Changelog
=========

2.15.0
------

* Added Cloudflare proxy client.

2.14.0
------

Expand Down
40 changes: 39 additions & 1 deletion Resources/doc/reference/configuration/proxy-client.rst
Expand Up @@ -12,7 +12,8 @@ The proxy client is also directly available as a service. The default client
can be autowired with the ``FOS\HttpCache\ProxyClient\ProxyClient`` type
declaration or the service ``fos_http_cache.default_proxy_client``. Specific
clients, if configured, are available as ``fos_http_cache.proxy_client.varnish``
, ``fos_http_cache.proxy_client.nginx`` or ``fos_http_cache.proxy_client.symfony``).
, ``fos_http_cache.proxy_client.nginx``, ``fos_http_cache.proxy_client.symfony``
or ``fos_http_cache.proxy_client.cloudflare``).

If you need to adjust the proxy client, you can also configure the ``CacheManager``
with a :ref:`custom proxy client <custom_proxy_client>` that you defined as a
Expand Down Expand Up @@ -236,6 +237,43 @@ HTTP method for sending purge requests to the Symfony HttpCache. Make sure to
configure the purge plugin for your HttpCache with the matching header if you
change this.

cloudflare
-------

.. code-block:: yaml
# config/packages/fos_http_cache.yaml
fos_http_cache:
proxy_client:
cloudflare:
zone_identifier: '<my-zone-identifier>'
authentication_token: '<user-authentication-token>'
http:
servers:
- 'https://api.cloudflare.com'
``authentication_token``
"""""""""""""""""""""""

**type**: ``string``

User API token for authentication against Cloudflare APIs, requires ``Zone.Cache`` Purge permissions.

``zone_identifier``
"""""""""""""""""

**type**: ``string``

Identifier for the Cloudflare zone you want to purge the cache for.

``http.servers``
""""""""""""""""

**type**: ``array`` **default**: ``['https://api.cloudflare.com']``

List of Cloudflare API endpoints to use for purging the cache. You can use this to specify a different
endpoint for testing purposes.

.. _configuration_noop_proxy_client:

noop
Expand Down
1 change: 1 addition & 0 deletions Resources/doc/spelling_word_list.txt
Expand Up @@ -5,6 +5,7 @@ autoconfigure
autoconfigured
backend
cacheable
cloudflare
ETag
friendsofsymfony
github
Expand Down
46 changes: 44 additions & 2 deletions src/DependencyInjection/Configuration.php
Expand Up @@ -412,7 +412,7 @@ private function addProxyClientSection(ArrayNodeDefinition $rootNode)
->arrayNode('proxy_client')
->children()
->enumNode('default')
->values(['varnish', 'nginx', 'symfony', 'noop'])
->values(['varnish', 'nginx', 'symfony', 'cloudflare', 'noop'])
->info('If you configure more than one proxy client, you need to specify which client is the default.')
->end()
->arrayNode('varnish')
Expand Down Expand Up @@ -482,6 +482,18 @@ private function addProxyClientSection(ArrayNodeDefinition $rootNode)
->end()
->end()

->arrayNode('cloudflare')
->children()
->scalarNode('authentication_token')
->info('API authorization token, requires Zone.Cache Purge permissions')
->end()
->scalarNode('zone_identifier')
->info('Identifier for your Cloudflare zone you want to purge the cache for')
->end()
->append($this->getCloudflareHttpDispatcherNode())
->end()
->end()

->booleanNode('noop')->end()
->end()
->validate()
Expand All @@ -500,7 +512,7 @@ private function addProxyClientSection(ArrayNodeDefinition $rootNode)
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));
}

if (!\in_array($proxyName, ['noop', 'default', 'symfony'])) {
if (!\in_array($proxyName, ['noop', 'default', 'symfony', 'cloudflare'])) {
if (!$arrayServersConfigured && !$jsonServersConfigured) {
throw new InvalidConfigurationException(sprintf('The "http.servers" or "http.servers_from_jsonenv" section must be defined for the proxy "%s"', $proxyName));
}
Expand Down Expand Up @@ -564,6 +576,36 @@ private function getHttpDispatcherNode()
return $node;
}

private function getCloudflareHttpDispatcherNode()
{
$treeBuilder = new TreeBuilder('http');

// Keep compatibility with symfony/config < 4.2
if (!method_exists($treeBuilder, 'getRootNode')) {
$node = $treeBuilder->root('http');
} else {
$node = $treeBuilder->getRootNode();
}

$node
->addDefaultsIfNotSet()
->children()
->arrayNode('servers')
->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 80.')
->useAttributeAsKey('name')
->requiresAtLeastOneElement()
->defaultValue(['https://api.cloudflare.com'])
->prototype('scalar')->end()
->end()
->scalarNode('http_client')
->defaultNull()
->info('Httplug async client service name to use for sending the requests.')
->end()
->end();

return $node;
}

private function addTestSection(ArrayNodeDefinition $rootNode)
{
$rootNode
Expand Down
24 changes: 22 additions & 2 deletions src/DependencyInjection/FOSHttpCacheExtension.php
Expand Up @@ -337,6 +337,9 @@ private function loadProxyClient(ContainerBuilder $container, XmlFileLoader $loa
if (isset($config['symfony'])) {
$this->loadSymfony($container, $loader, $config['symfony']);
}
if (isset($config['cloudflare'])) {
$this->loadCloudflare($container, $loader, $config['cloudflare']);
}
if (isset($config['noop'])) {
$loader->load('noop.xml');
}
Expand Down Expand Up @@ -455,19 +458,32 @@ private function loadSymfony(ContainerBuilder $container, XmlFileLoader $loader,
$loader->load('symfony.xml');
}

private function loadCloudflare(ContainerBuilder $container, XmlFileLoader $loader, array $config)
{
$this->createHttpDispatcherDefinition($container, $config['http'], 'fos_http_cache.proxy_client.cloudflare.http_dispatcher');
$options = [
'authentication_token' => $config['authentication_token'],
'zone_identifier' => $config['zone_identifier'],
];

$container->setParameter('fos_http_cache.proxy_client.cloudflare.options', $options);

$loader->load('cloudflare.xml');
}

/**
* @param array $config Configuration section for the tags node
* @param string $client Name of the client used with the cache manager,
* "custom" when a custom client is used
*/
private function loadCacheTagging(ContainerBuilder $container, XmlFileLoader $loader, array $config, $client)
{
if ('auto' === $config['enabled'] && !in_array($client, ['varnish', 'symfony'])) {
if ('auto' === $config['enabled'] && !in_array($client, ['varnish', 'symfony', 'cloudflare'])) {
$container->setParameter('fos_http_cache.compiler_pass.tag_annotations', false);

return;
}
if (!in_array($client, ['varnish', 'symfony', 'custom', 'noop'])) {
if (!in_array($client, ['varnish', 'symfony', 'cloudflare', 'custom', 'noop'])) {
throw new InvalidConfigurationException(sprintf('You can not enable cache tagging with the %s client', $client));
}

Expand Down Expand Up @@ -609,6 +625,10 @@ private function getDefaultProxyClient(array $config)
return 'symfony';
}

if (isset($config['cloudflare'])) {
return 'cloudflare';
}

if (isset($config['noop'])) {
return 'noop';
}
Expand Down
16 changes: 16 additions & 0 deletions src/Resources/config/cloudflare.xml
@@ -0,0 +1,16 @@
<?xml version="1.0" ?>

<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">

<services>
<service id="fos_http_cache.proxy_client.cloudflare"
class="FOS\HttpCache\ProxyClient\Cloudflare"
public="true">
<argument type="service" id="fos_http_cache.proxy_client.cloudflare.http_dispatcher"/>
<argument>%fos_http_cache.proxy_client.cloudflare.options%</argument>
</service>
</services>

</container>
19 changes: 19 additions & 0 deletions tests/Resources/Fixtures/config/cloudflare.php
@@ -0,0 +1,19 @@
<?php

/*
* This file is part of the FOSHttpCacheBundle package.
*
* (c) FriendsOfSymfony <http://friendsofsymfony.github.com/>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

$container->loadFromExtension('fos_http_cache', [
'proxy_client' => [
'cloudflare' => [
'authentication_token' => 'mytoken',
'zone_identifier' => 'myzone',
],
],
]);
11 changes: 11 additions & 0 deletions tests/Resources/Fixtures/config/cloudflare.xml
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8" ?>
<container xmlns="http://symfony.com/schema/dic/services">

<config xmlns="http://example.org/schema/dic/fos_http_cache">
<proxy-client>
<cloudflare authentication-token="mytoken" zone-identifier="myzone">
</cloudflare>
</proxy-client>

</config>
</container>
6 changes: 6 additions & 0 deletions tests/Resources/Fixtures/config/cloudflare.yml
@@ -0,0 +1,6 @@
fos_http_cache:

proxy_client:
cloudflare:
authentication_token: mytoken
zone_identifier: myzone
29 changes: 29 additions & 0 deletions tests/Unit/DependencyInjection/ConfigurationTest.php
Expand Up @@ -298,6 +298,35 @@ public function testSupportsSymfony()
}
}

public function testSupportsCloudflare()
{
$expectedConfiguration = $this->getEmptyConfig();
$expectedConfiguration['proxy_client'] = [
'cloudflare' => [
'authentication_token' => 'mytoken',
'zone_identifier' => 'myzone',
'http' => ['servers' => ['https://api.cloudflare.com'], 'http_client' => null],
],
];
$expectedConfiguration['cache_manager']['enabled'] = 'auto';
$expectedConfiguration['cache_manager']['generate_url_type'] = 'auto';
$expectedConfiguration['tags']['enabled'] = 'auto';
$expectedConfiguration['invalidation']['enabled'] = 'auto';
$expectedConfiguration['user_context']['logout_handler']['enabled'] = false;

$formats = array_map(function ($path) {
return __DIR__.'/../../Resources/Fixtures/'.$path;
}, [
'config/cloudflare.yml',
'config/cloudflare.xml',
'config/cloudflare.php',
]);

foreach ($formats as $format) {
$this->assertProcessedConfigurationEquals($expectedConfiguration, [$format]);
}
}

public function testEmptyServerConfigurationIsNotAllowed()
{
$this->expectException(InvalidConfigurationException::class);
Expand Down
20 changes: 20 additions & 0 deletions tests/Unit/DependencyInjection/FOSHttpCacheExtensionTest.php
Expand Up @@ -146,6 +146,26 @@ public function testConfigLoadSymfonyWithKernelDispatcher()
$this->assertSame(KernelDispatcher::class, $container->getDefinition('fos_http_cache.proxy_client.symfony.http_dispatcher')->getClass());
}

public function testConfigLoadCloudflare()
{
$container = $this->createContainer();
$this->extension->load([
[
'proxy_client' => [
'cloudflare' => [
'authentication_token' => 'test',
'zone_identifier' => 'test',
],
],
],
], $container);

$this->assertFalse($container->hasDefinition('fos_http_cache.proxy_client.varnish'));
$this->assertTrue($container->hasDefinition('fos_http_cache.proxy_client.cloudflare'));
$this->assertTrue($container->hasAlias('fos_http_cache.default_proxy_client'));
$this->assertTrue($container->hasDefinition('fos_http_cache.event_listener.invalidation'));
}

public function testConfigLoadNoop()
{
$container = $this->createContainer();
Expand Down

0 comments on commit 715d06d

Please sign in to comment.