Edge-Side-Includes with Cloudflare Workers

At Cloudflare we’re accelerating web assets in a number of different ways. Part of this is caching, by storing the response given by the origin server directly within our 151+ global data centers. This will dramatically improve the delivery of the resources as the visitor will directly get them from the data center closest to them, instead of waiting for us to fetch the request from the origin web server.

The issue with dynamic (but not a lot) pages

The subject we’re gonna cover today is the concept of Edge-Side-Includes. And what’s better than a real use-case to introduce what it is used for? Let’s take a website where all pages are including advertisements at the head and bottom. Could we consider these pages static? We couldn’t as at least part of this page is dynamic. Could we consider caching it? That’s a no again as it would mean the first dynamic part rendered will be cached and served for the other visitors trying to get the page. It would be a catastrophe if the advertisements are user-specific.

So the issue here is that we can’t cache the page. That’s quite a shame as it means that we’ll fetch the page again, and over again for every new request just to get this 1% portion of dynamic content.

Counter-measure with delta-compression?

Back in time, we’ve released Railgun, which consist in doing delta-compression of the requests received by a web server so Railgun listener could just send the delta bytes since the last request. We’re also working on the inclusion of this delta-compression in our Argo Tunnel listener, which is a small agent opening tunnels to us so you don’t even have to have your applications public on the internet, a simple outbound HTTPS access is enough to publish, secure and accelerate applications on Internet.

In both cases, we’ll need to fetch the complete webpage in order to calculate the difference from the last request, right? This is where Edge-Side-Includes takes place.

The Edge-Side-Includes standard has been submitted to the W3C (https://www.w3.org/TR/esi-lang) in August 2001 and defines an XML markup language that can be inserted in HTML or other text based contents which defines how interstitial proxies/CDNs need to combine static and dynamic portion and were to get them. The result is that it’s possible to keep those 99% in cache and for the remaining 1%, the interstitial cache proxy will fetch directly from the destination defined in the Edge-Side-Include block for finally combining both static and dynamic parts and sending the final webpage to the visitor.

Implementing it in a Worker

We released months ago Cloudflare Workers, our serverless framework which helps to implement custom logics directly within the Edge. The Workers are triggering in the path of the requests and the responses and can manipulate almost everything and spin subrequests on-the-fly.
This could dramatically improve the time to action for the implementation of new logics on your applications since you won’t have to modify them anymore as this can directly be done in the Edge, even if your applications are hosted on a bunch of different locations (Cloud and on-premise).

This scenario sounds then quite compatible with what we can achieve with Cloudflare Workers. For memories, here are the actions the EDGE needs to do for implementing the ESIs:

Fetching the static content

Searching in the payload for any <esi:include/> blocks

Fetching separately every ESI blocks found

Merging the static and dynamic contents

Sending the whole new payload to visitors

For this purpose, I created a small page on my test web server, with the following content:

So in this page we can see that most of the content will be static and we can find just before the end of the <body> an ESI pointing to https://httpbin.org/headers that the Edge will need to combine with the static content, in place of the ESI block.

This is the main portion of the script, testing first if the response is text-based and return the content directly to the visitor if not. Then instanciating a stream pipeline to send to the visitor using a specific function called streamTransformBody() in charge of the payload chunking.

You'll notice that I'm setting a max-age=0 cache-control header in the response as I don't want browsers to cache this response in case of a bad configuration on the Origin side.

I’m also declaring this script as fail-safe so that the request will go through normally if raising an Exception. More debugging information can be found on our developer HUB. Feel free to adapt for your use-cases and for example sending the Exception to a specific header, or even fancier sending the event to a logging tool.

In this portion, we’re receiving the chunks and searching for ESI blocks and if found, getting the dynamic parts. You can see that when the regex is matching, we’re calling a subRequest() function. This function will fetch the content of the ESI destination and return + encode the received payload.

This portion is quite simple as it defines the function that does the subrequest to the ESI destination, pay attention to what the Origin server is serving as cache-control response header for those dynamic parts as we want to keep them dynamic and not cached by Cloudflare. It’s also possible to override the TTL via Cloudflare directly if you don’t want to modify your application. The documentation about how to manipulate Cloudflare features is available on the developers HUB

In the streamTransformBody() function, I’m chunking the payload received in the readable object with specific boundaries in order to avoid chunks to terminate in the middle of a line or worse, in the middle of an ESI block.