Working with Server Side Includes

Working with Server Side Includes

In a similar way as 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 via HTML comments:

  1. <!DOCTYPE html>
  2. <html>
  3. <body>
  4. <!-- ... some content -->
  5. <!-- Embed the content of another page here -->
  6. <!--#include virtual="http://..." -->
  7. <!-- ... more content -->
  8. </body>
  9. </html>

There are 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_Injection) first!

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

First, to use SSI, be sure to enable it in your application configuration:

  • YAML

    1. # config/packages/framework.yaml
    2. framework:
    3. ssi: { enabled: true }
  • XML

    1. <!-- config/packages/framework.xml -->
    2. <?xml version="1.0" encoding="UTF-8" ?>
    3. <container xmlns="http://symfony.com/schema/dic/symfony"
    4. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    5. xmlns:framework="http://symfony.com/schema/dic/symfony"
    6. xsi:schemaLocation="http://symfony.com/schema/dic/services
    7. https://symfony.com/schema/dic/services/services-1.0.xsd
    8. http://symfony.com/schema/dic/symfony
    9. https://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
    10. <framework:config>
    11. <framework:ssi enabled="true"/>
    12. </framework:config>
    13. </container>
  • PHP

    1. // config/packages/framework.php
    2. $container->loadFromExtension('framework', [
    3. 'ssi' => ['enabled' => true],
    4. ]);

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:

  1. // src/Controller/ProfileController.php
  2. namespace App\Controller;
  3. // ...
  4. class ProfileController extends AbstractController
  5. {
  6. public function index(): Response
  7. {
  8. // by default, responses are private
  9. return $this->render('profile/index.html.twig');
  10. }
  11. public function gdpr(): Response
  12. {
  13. $response = $this->render('profile/gdpr.html.twig');
  14. // sets to public and adds some expiration
  15. $response->setSharedMaxAge(600);
  16. return $response;
  17. }
  18. }

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:

  1. {# templates/profile/index.html.twig #}
  2. {# you can use a controller reference #}
  3. {{ render_ssi(controller('App\\Controller\\ProfileController::gdpr')) }}
  4. {# ... or a URL #}
  5. {{ render_ssi(url('profile_gdpr')) }}

The render_ssi twig helper will generate something like:

  1. <!--#include virtual="/_fragment?_hash=abcdef1234&_path=_controller=App\Controller\ProfileController::gdpr" -->

render_ssi 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 ESI documentation.

This work, including the code samples, is licensed under a Creative Commons BY-SA 3.0 license.