diff --git a/http_cache/ssi.rst b/http_cache/ssi.rst new file mode 100644 index 00000000000..055ac26b213 --- /dev/null +++ b/http_cache/ssi.rst @@ -0,0 +1,142 @@ +.. index:: + single: Cache; SSI + single: SSI + +.. _server-side-includes: + +Working with Server Side Includes +================================= + +In a similar way as :doc:`ESI (Edge Side Includes) `, SSI can be used to +control HTTP caching on fragments of a response. The most important +difference that is SSI is known directly by most web servers like +`Apache `_, +`Nginx `_ etc. + +The SSI instructions are done in HTML comments: + +.. code-block:: html + + + + + + + + + + + + + +There is some other `available directives +`_ but +Symfony manages only the ``#include virtual`` one. + +.. caution:: + + Be careful with SSI, your website may be victim of injections. + Please read this OWASP article first: + https://www.owasp.org/index.php/Server-Side_Includes_(SSI)_Injection. + +When the web server reads an SSI directive, it requests the given URI or gives +directly from its cache. It repeats this process until there is no more +SSI directives to handle. Then, it merges all responses into one and sends +it to the client. + +.. _using-ssi-in-symfony: + +Using SSI in Symfony +~~~~~~~~~~~~~~~~~~~~ + +First, to use SSI, be sure to enable it in your application configuration: + +.. configuration-block:: + + .. code-block:: yaml + + # config/packages/framework.yaml + framework: + # ... + ssi: { enabled: true } + + .. code-block:: xml + + + + + + + + + + + + .. code-block:: php + + // config/packages/framework.php + $container->loadFromExtension('framework', array( + // ... + 'ssi' => array('enabled' => true), + )); + +Suppose you have a page with private content like a Profile page and you want +to cache a static GDPR content block. With SSI, you can add some expiration +on this block and keep the page private:: + + // src/Controller/ProfileController.php + + // ... + class ProfileController extends AbstractController + { + public function index(): Response + { + // by default, responses are private + return $this->render('profile/index.html.twig'); + } + + public function gdpr(): Response + { + $response = $this->render('profile/gdpr.html.twig'); + + // sets to public and adds some expiration + $response->setSharedMaxAge(600); + + return $response; + } + } + +The profile index page has not public caching, but the GDPR block has +10 minutes of expiration. Let's include this block into the main one: + +.. code-block:: twig + + {# templates/profile/index.html.twig #} + + {# you can use a controller reference #} + {{ render_ssi(controller('App\Controller\ProfileController::gdpr')) }} + + {# ... or a URL #} + {{ render_ssi(url('profile_gdpr')) }} + +The ``render_ssi`` twig helper will generate something like: + +.. code-block:: html + + + +``render_esi`` ensures that SSI directive are generated only if the request +has the header requirement like ``Surrogate-Capability: device="SSI/1.0"`` +(normally given by the web server). +Otherwise it will embed directly the sub-response. + +.. note:: + + For more information about Symfony cache fragments, take a tour on + the :ref:`ESI documentation `.