Headless: Rate Limiting
A document to provide guidance on how to implement API requests within a headless service
TL;DR; headless implementations mean that in order for Zesty.io platform rate limits to not have a negative impact on servicing your traffic you need to fingerprint requests based on the client IP address and user-agent to fulfill the expectations of the platform.
—
A headless CMS is a content management system that decouples the backend, where content is created and stored, from the frontend, where it is displayed. Instead of relying on pre-built themes or templates, it delivers content via APIs (such as REST or GraphQL) to any platform or device, enabling developers to build custom frontends using their preferred technologies. This architecture promotes flexibility, scalability, and omnichannel content distribution, ensuring the same content can power websites, mobile apps, voice assistants, and more. – ChatGPT
In the context of rate limiting the important takeaway from this ChatGPT provided definition of a headless CMS is; delivers content via APIs. This matters because ultimately it means many requests become one. Let us illustrate.
Traditional Requests
In a traditional request-response webpage render from a CMS it would equate to one client for each connection.
A browser(read: client) makes an HTTP request to the server and the server renders a page sending an html response. In this exchange, and in every web exchange, all parties identify themselves with IP addresses. These addresses are unique.
Note: There is nuance to the "uniqueness" of IP addresses but for our purposes in this document we will ignore them.
DoS and DDoS Attacks
Servers which service web traffic need to defend themselves from bad actors. One common attack vector bad actors will take is a Denial of Service (DoS) or Distributed Denial of Service (DDoS) attack. A common technique for defending against these attacks is blocking the IP address of the attacking clients.
Note: There is more nuance to defending against a DDoS attack but for the purpose of this document we will ignore them.
GDPR Compliance
With the advent of the European Union's General Data Protection Regulation (GDPR) IP addresses should now be treated as private data. Therefore to be compliant with GDRP any system which receives IP addresses needs to either secure that data or discard it. At Zesty.io we opt to discard it. But this creates a conundrum for how we defend our platform against DoS and DDoS attacks.
Rate Limiting & Fingerprints
Zesty.io continues to protect its platform by using fingerprinting, instead of IP addresses, to make rate limiting decisions. Before traffic reaches our origin it passes through our global CDN service. At the CDN Points of Presence(PoP) we fingerprint each request by making an MD5 hash of the requesting client's IP address and user-agent. This allows us to use a unique, consistent and anonymous ID for a particular client. Remaining GDPR compliant.
As an origin for web traffic there are many requests happening at the same time resulting in traffic like the following.
Headless Requests
An issue arises with headless as it acts as a client to the origin, similar to a CDN PoP, on behalf of requesting clients. Making all requests appear to the origin as a single client. Causing the origin rate limit policy to apply to all headless requests as a single client.
For example; if operating a headless website which has 10 users browsing. Each page load is also going to load any CSS and JavaScript for the page. Meaning at least 2 additional requests, if not more. So for every page it takes 3 requests. Meaning 10 users browsing at 3 requests each page you would see approximately 30 requests.
All of these requests are evaluated against the Zesty.io rate limit policy. Which is approximately 10 requests per second(RPS). It is approximate because the way the limiting works is if a client exceeds 600 requests over 60 seconds a ban begins, roughly 10 RPS. Meaning additional requests from that client will be banned and receive an immediate 429 response. This ban lasts for 30 minutes.
When acting on behalf of other clients you can see how easy it is for a headless implementation to quickly reach the rate limit. How do we solve this problem? By implementing headless requests to meet the platform's expectations with regards to anonymously but consistently identifying unique clients.
z-fingerprint
When the headless server makes a Zesty.io platform request on behalf of a requesting client to fetch data to fulfill the request it should do so by providing a custom header which implements the fingerprinting strategy as described above. Making a MD5 hash of the requesting clients host IP and user-agent.
z-fingerprint: hash(IP + user-agent)
const { createHash } = require("node:crypto");
const express = require("express");
const app = express();
app.get("/", async (req, res) => {
const ip = req.ip;
const userAgent = req.get("user-agent");
const hash = createHash("sha256");
hash.update(`${ip}${userAgent}`);
const fingerprint = hash.digest("hex");
const data = await fetch("https://www.zesty.io/", {
headers: {
"z-fingerprint": fingerprint,
},
});
const html = await data.text();
res.send(html);
});
app.listen(3000, () => {
console.log(`Server start`);
});
E.g. An example using Expressjs in the Nodejs runtime to request the zesty.io homepage
In this example you can see, at the server, we use the request IP and user-agent to generate a hash which we include as a custom header value in the request for a webpage.
Note: You will need to determine for your specific language, runtime and http library the appropriate way to set a custom header.
In Conclusion
By fingerprinting requests to the Zesty.io origin on behalf of your site visitors you can avoid your site traffic being aggregated into a single policy limitation. Instead, having each of your visitors have the policy applied to them individually. Allowing the Zesty.io platform to defend against bad actors while continuing to service legitimate requests. All while remaining GDPR compliant.
Note: Zesty.io media traffic uses a different architecture and is not bound by rate limiting.
Note: Rate limiting applies to all WebEngine service requests; domain URL, preview URL, custom API, instant API and toJSON API.
Updated about 2 months ago