Skip to content

Commit

Permalink
Merge pull request #354 from FriendsOfSymfony/rework-response-tagger
Browse files Browse the repository at this point in the history
Rework response tagger
  • Loading branch information
dbu committed May 24, 2017
2 parents 42ff6dd + 2dfdbc7 commit bd0ce29
Show file tree
Hide file tree
Showing 18 changed files with 239 additions and 191 deletions.
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ See also the [GitHub releases page](https://github.com/FriendsOfSymfony/FOSHttpC
* Abstracting tags by adding new `TagCapable` for ProxyClients.
* Added `strict` option to `ResponseTagger` that throws an exception when empty
tags are added. By default, empty tags are ignored.
* Added `TagHeaderFormatter` interface that is used within the `ResponseTagger`
to provide the header name and for formatting the tags header value.

### Varnish

Expand Down Expand Up @@ -77,6 +79,14 @@ See also the [GitHub releases page](https://github.com/FriendsOfSymfony/FOSHttpC
`ProxyTestCase`; use the traits `CacheAssertions` and `HttpCaller` instead.
* Added HTTP method parameter to `HttpCaller::getResponse()`.

2.0.0-beta3
-----------

* **BC break:** The `ResponseTagger` no longer expects an instance of
`TagCapable` as first argument. To adjust the tag header name or the way the
tags are formatted, use the new `header_formatter` option with a
`TagHeaderFormatter`.

1.4.2
-----

Expand Down
11 changes: 6 additions & 5 deletions doc/proxy-clients.rst
Original file line number Diff line number Diff line change
Expand Up @@ -94,13 +94,14 @@ dispatcher as explained above and pass it to the Varnish client::

You can also pass some options to the Varnish client:

* ``tags_header`` (X-Cache-Tags): Allows you to change the HTTP header used for
tagging. If you change this, make sure to use the correct header name in your
:doc:`proxy server configuration <proxy-configuration>`;
* ``header_length`` (7500): Control the maximum header size when invalidating
* ``tags_header`` (default: ``X-Cache-Tags``): The HTTP header used to specify
which tags to invalidate when sending invalidation requests to the caching
proxy. Make sure that your :ref:`Varnish configuration <varnish_tagging>`
corresponds to the header used here;
* ``header_length`` (default: 7500): Control the maximum header size when invalidating
tags. If there are more tags to invalidate than fit into the header, the
invalidation request is split into several requests;
* ``default_ban_headers`` Map of headers that are set on each ban request,
* ``default_ban_headers`` (default: []): Map of headers that are set on each ban request,
merged with the built-in headers.

Additionally, you can specify the request factory used to build the
Expand Down
37 changes: 27 additions & 10 deletions doc/response-tagging.rst
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
Response Tagging
================

The ``ResponseTagger`` helps you keep track tags for a response, which can be
added to response headers that you can later use to invalidate all cache
The ``ResponseTagger`` helps you keep track of tags for a response. It can add
the tags as a response header that you can later use to invalidate all cache
entries with that tag.

.. _tags:
Expand All @@ -12,23 +12,40 @@ Setup

.. note::

Make sure to :doc:`configure your proxy <proxy-configuration>` for tagging first.
Make sure to :doc:`configure your proxy <proxy-configuration>` for tagging
first.

The response tagger is a decorator around a proxy client that implements
the ``TagCapable`` interface, handling adding tags to responses::
The response tagger uses an instance of ``TagHeaderFormatter`` to know the
header name used to mark tags on the content and to format the tags into the
correct header value. This library ships with a
``CommaSeparatedTagHeaderFormatter`` that formats an array of tags into a
comma-separated list. This format is expected for invalidation with the
Varnish reverse proxy. When using the default settings, everything is created
automatically and the ``X-Cache-Tags`` header will be used::

use FOS\HttpCache\ResponseTagger;

// $proxyClient already created, implementing FOS\HttpCache\ProxyClient\Invalidation\TagCapable
$responseTagger = new ResponseTagger($proxyClient);
$responseTagger = new ResponseTagger();

.. _response_tagger_optional_parameters:

If you need a different behavior, you can provide your own implementation of
the ``TagHeaderFormatter`` interface. But be aware that your
:ref:`Varnish configuration <varnish_tagging>` has to match with the tag on the response.
For example, to use a different header name::

use FOS\HttpCache\ResponseTagger;
use FOS\HttpCache\TagHeaderFormatter;

$formatter = new CommaSeparatedTagHeaderFormatter('Custom-Header-Name');
$responseTagger = new ResponseTagger(['header_formatter' => $formatter]);

The response tagger validates tags that you set. By default, it simply ignores
empty strings. You can set the response tagger to strict mode to have it throw
an ``InvalidTagException`` on empty tags::
empty strings and does not add them to the list of tags. You can set the
response tagger to strict mode to have it throw an ``InvalidTagException`` on
empty tags::

$responseTagger = new ResponseTagger($proxyClient, ['strict' => true]);
$responseTagger = new ResponseTagger(['strict' => true]);

Usage
~~~~~
Expand Down
24 changes: 16 additions & 8 deletions doc/varnish-configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -175,26 +175,34 @@ Tagging
Feature: :ref:`cache tagging <tags>`

If you have included ``fos_ban.vcl``, tagging will be automatically enabled
using an ``X-Cache-Tags`` header.
with the ``X-Cache-Tags`` header for both marking the tags on the response and
for the invalidation request to tell what tags to invalidate.

If you need to use a different tag for the headers than the default
``X-Cache-Tags`` used in ``fos_ban.vcl``, you will have to write your own VCL
code for tag invalidation and change the tagging header
:ref:`configured in the cache invalidator <varnish_custom_tags_header>`. Your custom
VCL will look like this:
If you use a different name for :doc:`response tagging <response-tagging>` than
the default ``X-Cache-Tags`` or a different name for specifying which tags to
invalidate in your :ref:`cache invalidator configuration <varnish_custom_tags_header>`
you have to write your own VCL code for tag invalidation. Your custom VCL will
look like this:

.. configuration-block::

.. literalinclude:: ../resources/config/varnish/fos_ban.vcl
:language: varnish
:emphasize-lines: 17-22,49-50
:emphasize-lines: 17-23,50-51
:linenos:

.. literalinclude:: ../resources/config/varnish-3/fos_ban.vcl
:language: varnish3
:emphasize-lines: 17-22,49-50
:emphasize-lines: 17-23,50-51
:linenos:

.. hint::

The line you need to adjust from the code above is line 21. The left side
is the header used to tag the response, the right side is the header used
when sending invalidation requests. If you change one or the other header
name, make sure to adjust the configuration accordingly.

.. _varnish user context:

User Context
Expand Down
1 change: 1 addition & 0 deletions resources/config/varnish-3/fos_ban.vcl
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ sub fos_ban_recv {
ban("obj.http.X-Host ~ " + req.http.X-Host
+ " && obj.http.X-Url ~ " + req.http.X-Url
+ " && obj.http.content-type ~ " + req.http.X-Content-Type
// the left side is the response header, the right side the invalidation header
+ " && obj.http.X-Cache-Tags ~ " + req.http.X-Cache-Tags
);
} else {
Expand Down
1 change: 1 addition & 0 deletions resources/config/varnish/fos_ban.vcl
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ sub fos_ban_recv {
ban("obj.http.X-Host ~ " + req.http.X-Host
+ " && obj.http.X-Url ~ " + req.http.X-Url
+ " && obj.http.content-type ~ " + req.http.X-Content-Type
// the left side is the response header, the right side the invalidation header
+ " && obj.http.X-Cache-Tags ~ " + req.http.X-Cache-Tags
);
} else {
Expand Down
19 changes: 0 additions & 19 deletions src/ProxyClient/Invalidation/TagCapable.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,23 +30,4 @@ interface TagCapable extends ProxyClient
* @return $this
*/
public function invalidateTags(array $tags);

/**
* Get value for the tags header as it should be included in the response.
*
* This does all necessary escaping and concatenates the tags in a way that
* individual tags can be invalidated by this client.
*
* @param array $tags
*
* @return string
*/
public function getTagsHeaderValue(array $tags);

/**
* Get the HTTP header name for the cache tags.
*
* @return string
*/
public function getTagsHeaderName();
}
16 changes: 0 additions & 16 deletions src/ProxyClient/MultiplexerClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -98,22 +98,6 @@ public function flush()
return $count;
}

/**
* {@inheritdoc}
*/
public function getTagsHeaderValue(array $tags)
{
return $this->invokeFirst(TagCapable::class, 'getTagsHeaderValue', [$tags]);
}

/**
* {@inheritdoc}
*/
public function getTagsHeaderName()
{
return $this->invokeFirst(TagCapable::class, 'getTagsHeaderName', []);
}

/**
* Forwards tag invalidation request to all clients.
*
Expand Down
16 changes: 0 additions & 16 deletions src/ProxyClient/Noop.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,22 +48,6 @@ public function invalidateTags(array $tags)
return $this;
}

/**
* {@inheritdoc}
*/
public function getTagsHeaderValue(array $tags)
{
return '';
}

/**
* {@inheritdoc}
*/
public function getTagsHeaderName()
{
return 'X-Noop-Cache-Tags';
}

/**
* {@inheritdoc}
*/
Expand Down
25 changes: 9 additions & 16 deletions src/ProxyClient/Varnish.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,15 @@ class Varnish extends HttpProxyClient implements BanCapable, PurgeCapable, Refre
const HTTP_HEADER_HOST = 'X-Host';
const HTTP_HEADER_URL = 'X-Url';
const HTTP_HEADER_CONTENT_TYPE = 'X-Content-Type';

/**
* Default name of the header used to invalidate content with specific tags.
*
* This happens to be the same as TagHeaderFormatter::DEFAULT_HEADER_NAME
* but does not technically need to be the same.
*
* @var string
*/
const DEFAULT_HTTP_HEADER_CACHE_TAGS = 'X-Cache-Tags';

/**
Expand Down Expand Up @@ -64,22 +73,6 @@ public function invalidateTags(array $tags)
return $this;
}

/**
* {@inheritdoc}
*/
public function getTagsHeaderValue(array $tags)
{
return implode(',', array_unique($this->escapeTags($tags)));
}

/**
* {@inheritdoc}
*/
public function getTagsHeaderName()
{
return $this->options['tags_header'];
}

/**
* {@inheritdoc}
*/
Expand Down
29 changes: 18 additions & 11 deletions src/ResponseTagger.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@
namespace FOS\HttpCache;

use FOS\HttpCache\Exception\InvalidTagException;
use FOS\HttpCache\ProxyClient\Invalidation\TagCapable;
use FOS\HttpCache\TagHeaderFormatter\CommaSeparatedTagHeaderFormatter;
use FOS\HttpCache\TagHeaderFormatter\TagHeaderFormatter;
use Psr\Http\Message\ResponseInterface;
use Symfony\Component\OptionsResolver\Options;
use Symfony\Component\OptionsResolver\OptionsResolver;

/**
Expand All @@ -23,6 +25,7 @@
* @author David Buchmann <mail@davidbu.ch>
* @author André Rømcke <ar@ez.no>
* @author Wicliff Wolda <wicliff.wolda@gmail.com>
* @author Yanick Witschi <yanick.witschi@terminal42.ch>
*/
class ResponseTagger
{
Expand All @@ -32,37 +35,41 @@ class ResponseTagger
private $options;

/**
* @var TagCapable
* @var TagHeaderFormatter
*/
private $proxyClient;
private $headerFormatter;

/**
* @var array
*/
private $tags = [];

/**
* Create the response tagger with a tag capable proxy client and options.
* Create the response tagger with a tag header formatter and options.
*
* Supported options are:
*
* - header_formatter (TagHeaderFormatter) Default: CommaSeparatedTagHeaderFormatter with default header name
* - strict (bool) Default: false. If set to true, throws exception when adding empty tags
*
* @param TagCapable $proxyClient
* @param array $options
* @param array $options
*/
public function __construct(TagCapable $proxyClient, array $options = [])
public function __construct(array $options = [])
{
$this->proxyClient = $proxyClient;

$resolver = new OptionsResolver();
$resolver->setDefaults([
// callback to avoid instantiating the formatter when its not needed
'header_formatter' => function (Options $options) {
return new CommaSeparatedTagHeaderFormatter();
},
'strict' => false,
]);

$resolver->setAllowedTypes('header_formatter', TagHeaderFormatter::class);
$resolver->setAllowedTypes('strict', 'bool');

$this->options = $resolver->resolve($options);
$this->headerFormatter = $this->options['header_formatter'];
}

/**
Expand All @@ -72,7 +79,7 @@ public function __construct(TagCapable $proxyClient, array $options = [])
*/
public function getTagsHeaderName()
{
return $this->proxyClient->getTagsHeaderName();
return $this->headerFormatter->getTagsHeaderName();
}

/**
Expand All @@ -84,7 +91,7 @@ public function getTagsHeaderName()
*/
public function getTagsHeaderValue()
{
return $this->proxyClient->getTagsHeaderValue($this->tags);
return $this->headerFormatter->getTagsHeaderValue($this->tags);
}

/**
Expand Down

0 comments on commit bd0ce29

Please sign in to comment.