skip to Main Content

We are using Symfony (Sulu) with Varnish as reverse proxy, with tag based invalidation (xkey) which works as expected. Now we want to use ESI for dynamic blocks on our website, separate from the "main" cache lifetime.

But each time the webpage is cached in full, even after hard refresh/clear browser cache or using new browser, the page is always displaying the first cache entry.

We have enabled ESI in Symfony:

framework:
...
    esi: true
    fragments: { path: /_fragment }

And provide render_esi in our Twig templates:

navbar.html.twig

...
            {{ render_esi(controller('App\Controller\Website\LoginButtonController::renderEsiLoginButton')) }}
            <div class="navbar-main__toggler"></div>
        </div>

LoginButtonController

class LoginButtonController extends AbstractController
{
    public function renderEsiLoginButton(): Response
    {
        $response = $this->render('esi/_login_button.html.twig', ['random_nr' => rand()]);

        $response->setMaxAge(10);
        $response->setSharedMaxAge(10);
        $response->setPublic();

        return $response;
    }
}

_login_button.html.twig

<div class="navbar-main__action-account">
    <div class="account__dropdown">
        <a href="#" class="account__user">{{ random_nr }}</a>
        ...
    </div>
</div>

And our Varnish default.vcl:

sub vcl_recv {
    call fos_tags_xkey_recv;
    call fos_user_context_recv;
    call sulu_recv;

    // # Add a Surrogate-Capability header to announce ESI
    set req.http.Surrogate-Capability = "abc=ESI/1.0";
...

sub vcl_backend_response {
    set beresp.grace = 2m;

    call fos_user_context_backend_response;
    call sulu_backend_response;

    // Check for ESI acknowledgement and remove Surrogate-Control header
    if (beresp.http.Surrogate-Control ~ "ESI/1.0") {
        unset beresp.http.Surrogate-Control;
        set beresp.do_esi = true;
    }
}
...

Our fos_http_cache config:

fos_http_cache:
    tags:
        enabled: true
        response_header: xkey
        max_header_value_length: 1024
    proxy_client:
        symfony:
            use_kernel_dispatcher: true

    user_context:
        enabled: true
        role_provider: true
#        user_hash_header: 'X-User-Context-Hash'
        hash_cache_ttl: 900

and sulu http cache:

when@prod: &prod
    sulu_http_cache:
        debug:
            enabled: false
        tags:
            enabled: true
        cache:
            max_age: 240
            shared_max_age: 480
        proxy_client:
            symfony:
                enabled: false
            varnish:
                enabled: true
                servers: [ '%env(VARNISH_SERVER)%' ]
                tag_mode: purgekeys

When using curl, we also see the esi:include tags correctly:

<span class="navbar-main__action-search-button"></span>

<esi:include src="/_fragment?_hash=HAz3Ym5wXab4GXTRKnq8oUR3WwnGqUpprL0niBc%2BEwg%3D&_path=_format%3Dhtml%26_locale%3Den%26_controller%3DApp%255CController%255CWebsite%255CLoginButtonController%253A%253ArenderEsiLoginButton" onerror="continue" />

<div class="navbar-main__toggler"></div>

But the webpage is still cached in full, even after hard refresh/clear browser cache or using new browser, the page is always displaying the first random number. Until we clear the Varnish cache in full.

What are we missing?

2

Answers


  1. By the looks of it, you’re doing everything right. But some targeted varnishlog commands will give us a better understanding of how Varnish is caching the various fragments.

    I’m going to need you to send me the output of the following command:

    sudo varnishlog -g request -q "ReqUrl eq '/'"
    

    My assumption is that the issue is occurring on the homepage. If that’s not the case, alter the filter in the varnishlog command.

    It’s important that you run this command on an empty cache!

    When Varnish performs the various fetches, we can have a look at the TTL tags in the logs. This will tell us how Varnish decided on the TTL of each fragment (either through a TTL set in the VCL code, or through the Cache-Control header.

    The logs will also return the various ESI subrequests as separate log transactions, making it easier to understand what happened when.

    Please put the log output in your original question and I’ll help you figure out what’s going on.

    Login or Signup to reply.
  2. Only thing that comes directly to my eye comparing this to the symfony ESI-Docs

    is that

    each ESI tag requires a fully-qualified URL.

    you have a relative link in the src attribute of the esi tag, you could try using the absolute_uri option of render_esi (IMHO it does not really make sense though that this defaults to false)

    But yeah, i think its worth a try

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search