Skip to content

Commit dd4654d

Browse files
RealOrangeOnehelenb
authored andcommitted
Update common caching worker
1 parent 48d9d0a commit dd4654d

File tree

1 file changed

+238
-41
lines changed

1 file changed

+238
-41
lines changed

cloudflare/workers.js

Lines changed: 238 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,253 @@
1-
addEventListener('fetch', (event) => {
1+
// NOTE: A 'Cache Level' page rule set to 'Cache Everything' will
2+
// prevent private cookie cache skipping from working, as it is
3+
// applied after this worker runs.
4+
5+
// When any cookie in this list is present in the request, cache will be skipped
6+
const PRIVATE_COOKIES = ["sessionid"];
7+
8+
// These querystring keys are stripped from the request as they are generally not
9+
// needed by the origin.
10+
const STRIP_QUERYSTRING_KEYS = [
11+
"utm_source",
12+
"utm_campaign",
13+
"utm_medium",
14+
"utm_term",
15+
"utm_content",
16+
"gclid",
17+
"fbclid",
18+
"dm_i", // DotDigital
19+
"msclkid",
20+
"al_applink_data", // Meta outbound app links
21+
22+
// https://docs.flying-press.com/cache/ignore-query-strings
23+
"age-verified",
24+
"ao_noptimize",
25+
"usqp",
26+
"cn-reloaded",
27+
"sscid",
28+
"ef_id",
29+
"_bta_tid",
30+
"_bta_c",
31+
"fb_action_ids",
32+
"fb_action_types",
33+
"fb_source",
34+
"_ga",
35+
"adid",
36+
"_gl",
37+
"gclsrc",
38+
"gdfms",
39+
"gdftrk",
40+
"gdffi",
41+
"_ke",
42+
"trk_contact",
43+
"trk_msg",
44+
"trk_module",
45+
"trk_sid",
46+
"mc_cid",
47+
"mc_eid",
48+
"mkwid",
49+
"pcrid",
50+
"mtm_source",
51+
"mtm_medium",
52+
"mtm_campaign",
53+
"mtm_keyword",
54+
"mtm_cid",
55+
"mtm_content",
56+
"epik",
57+
"pp",
58+
"pk_source",
59+
"pk_medium",
60+
"pk_campaign",
61+
"pk_keyword",
62+
"pk_cid",
63+
"pk_content",
64+
"redirect_log_mongo_id",
65+
"redirect_mongo_id",
66+
"sb_referer_host",
67+
];
68+
69+
// If this is true, the querystring keys stripped from the request will be
70+
// addeed to any Location header served by a redirect.
71+
const REPLACE_STRIPPED_QUERYSTRING_ON_REDIRECT_LOCATION = false
72+
73+
// If this is true, querystring key are stripped if they have no value eg. ?foo
74+
// Disabled by default, but highly recommended
75+
const STRIP_VALUELESS_QUERYSTRING_KEYS = false;
76+
77+
// Only these status codes should be considered cacheable
78+
// (from https://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.4)
79+
const CACHABLE_HTTP_STATUS_CODES = [200, 203, 206, 300, 301, 410];
80+
81+
addEventListener("fetch", (event) => {
282
event.respondWith(main(event));
383
});
484

585
async function main(event) {
6-
const newRequest = stripSessionCookie(event.request);
7-
return fetch(newRequest);
86+
const cache = caches.default;
87+
let request = event.request;
88+
let strippedParams;
89+
[request, strippedParams] = stripQuerystring(request);
90+
91+
if (!requestIsCachable(request)) {
92+
// If the request isn't cacheable, return a Response directly from the origin.
93+
return fetch(request);
94+
}
95+
96+
const cachingRequest = getCachingRequest(request);
97+
let response = await cache.match(cachingRequest);
98+
99+
if (!response) {
100+
// If we didn't get a response from the cache, fetch one from the origin
101+
// and put it in the cache.
102+
response = await fetch(request);
103+
if (responseIsCachable(response)) {
104+
event.waitUntil(cache.put(cachingRequest, response.clone()));
105+
}
106+
}
107+
108+
if (REPLACE_STRIPPED_QUERYSTRING_ON_REDIRECT_LOCATION) {
109+
response = replaceStrippedQsOnRedirectResponse(response, strippedParams);
110+
}
111+
112+
return response;
8113
}
9114

10-
/**
11-
* Strip session cookies from the front-end.
12-
*
13-
* It's important that you disable this script from:
14-
* - /admin/*
15-
* - /review/*
16-
* - /contact/*
17-
*
18-
* Otherwise CSRF won't work.
19-
*
115+
/*
116+
* Cacheability Utilities
117+
*/
118+
function requestIsCachable(request) {
119+
/*
120+
* Given a Request, determine if it should be cached.
121+
* Currently the only factor here is whether a private cookie is present.
122+
*/
123+
return !hasPrivateCookie(request)
124+
}
125+
126+
function responseIsCachable(response) {
127+
/*
128+
* Given a Response, determine if it should be cached.
129+
* Currently the only factor here is whether the status code is cachable.
130+
*/
131+
return CACHABLE_HTTP_STATUS_CODES.includes(response.status)
132+
}
133+
134+
function getCachingRequest(request) {
135+
/**
136+
* When needed, modify a request for use as the cache key.
137+
*
138+
* Note: Modifications to this request are not sent upstream.
139+
*/
140+
return new Request(new URL(request.url), request); // Do nothing.
141+
}
142+
143+
144+
/*
145+
* Request Utilities
20146
*/
21-
function stripSessionCookie(request) {
22-
const newHeaders = new Headers(request.headers);
147+
function stripQuerystring(request) {
148+
/**
149+
* Given a Request, return a new Request with the ignored or blank querystring keys stripped out,
150+
* along with an object representing the stripped values.
151+
*/
23152
const url = new URL(request.url);
24-
const cookieString = newHeaders.get('Cookie');
25-
if (
26-
cookieString !== null &&
27-
(cookieString.includes('csrftoken') ||
28-
cookieString.includes('sessionid'))
29-
) {
30-
const newValue = stripCookie(
31-
stripCookie(newHeaders.get('Cookie'), 'sessionid'),
32-
'csrftoken',
33-
);
34-
newHeaders.set('Cookie', newValue);
35-
return new Request(request.url, {
36-
headers: newHeaders,
37-
method: request.method,
38-
body: request.body,
39-
redirect: request.redirect,
40-
});
153+
154+
const stripKeys = STRIP_QUERYSTRING_KEYS.filter((v) =>
155+
url.searchParams.has(v),
156+
);
157+
158+
let strippedParams = {};
159+
160+
if (stripKeys.length) {
161+
stripKeys.reduce((acc, key) => {
162+
acc[key] = url.searchParams.getAll(key);
163+
url.searchParams.delete(key);
164+
return acc;
165+
}, strippedParams);
166+
}
167+
168+
if (STRIP_VALUELESS_QUERYSTRING_KEYS) {
169+
// Strip query params without values to avoid unnecessary cache misses
170+
for (const [key, value] of url.searchParams.entries()) {
171+
if (!value) {
172+
url.searchParams.delete(key);
173+
strippedParams[key] = '';
174+
}
175+
}
176+
}
177+
178+
return [new Request(url, request), strippedParams];
179+
}
180+
181+
function hasPrivateCookie(request) {
182+
/*
183+
* Given a Request, determine if one of the 'private' cookies are present.
184+
*/
185+
const cookieHeader = request.headers.get("Cookie");
186+
if (!cookieHeader) {
187+
return false;
188+
}
189+
190+
const requestCookieNames = cookieHeader
191+
.split(";")
192+
.map((cookie) => cookie.split("=")[0].trim());
193+
194+
return PRIVATE_COOKIES.some((privateCookieName) =>
195+
requestCookieNames.includes(privateCookieName)
196+
);
197+
}
198+
199+
/**
200+
* Response Utilities
201+
*/
202+
203+
function replaceStrippedQsOnRedirectResponse(response, strippedParams) {
204+
/**
205+
* Given an existing Response, and an object of stripped querystring keys,
206+
* determine if the response is a redirect.
207+
* If it is, add the stripped querystrings to the location header.
208+
* This allows us to persist tracking querystrings (like UTM) over redirects.
209+
*/
210+
response = new Response(response.body, response);
211+
212+
if ([301, 302].includes(response.status)) {
213+
const locationHeaderValue = response.headers.get("location");
214+
let locationUrl;
215+
216+
if (!locationHeaderValue) {
217+
return response;
218+
}
219+
220+
const isAbsolute = isUrlAbsolute(locationHeaderValue);
221+
222+
if (!isAbsolute) {
223+
// If the Location URL isn't absolute, we need to provide a Host so we can use
224+
// a URL object.
225+
locationUrl = new URL(locationHeaderValue, "http://www.example.com");
226+
} else {
227+
locationUrl = new URL(locationHeaderValue)
228+
}
229+
230+
for (const [key, value] of Object.entries(strippedParams)) {
231+
locationUrl.searchParams.append(key, value);
232+
}
233+
234+
let newLocation;
235+
236+
if (isAbsolute) {
237+
newLocation = locationUrl.toString()
238+
} else {
239+
newLocation = `${locationUrl.pathname}${locationUrl.search}`;
240+
}
241+
242+
response.headers.set("location", newLocation);
41243
}
42244

43-
return request;
245+
return response
44246
}
45247

46248
/**
47-
* Strip a cookie from the cookie string and return a new cookie string.
249+
* URL Utilities
48250
*/
49-
function stripCookie(cookiesString, cookieName) {
50-
return cookiesString
51-
.split(';')
52-
.filter((v) => {
53-
return v.split('=')[0].trim() !== cookieName;
54-
})
55-
.join(';');
251+
function isUrlAbsolute(url) {
252+
return (url.indexOf('://') > 0 || url.indexOf('//') === 0);
56253
}

0 commit comments

Comments
 (0)