redirects-in-workers
Version:
Cloudflare Pages' _redirects file support in Cloudflare Workers
119 lines (108 loc) • 3.46 kB
text/typescript
import {
generateRulesMatcher,
replacer,
} from "@cloudflare/workers-shared/asset-worker/src/utils/rules-engine.js";
import { constructRedirects } from "@cloudflare/workers-shared/utils/configuration/constructConfiguration.js";
import { parseRedirects } from "@cloudflare/workers-shared/utils/configuration/parseRedirects.js";
import {
FoundResponse,
MovedPermanentlyResponse,
PermanentRedirectResponse,
SeeOtherResponse,
TemporaryRedirectResponse,
} from "@cloudflare/workers-shared/utils/responses.js";
const REDIRECTS_VERSION = 1;
export const generateRedirectsEvaluator = (
redirectsFileContents: string,
{
maxLineLength,
maxStaticRules,
maxDynamicRules,
}: {
maxLineLength?: number;
maxStaticRules?: number;
maxDynamicRules?: number;
} = {},
): ((request: Request, assetsBinding: Fetcher) => Promise<Response | null>) => {
const redirects = parseRedirects(redirectsFileContents, {
maxLineLength, // Usually 2_000
maxStaticRules, // Usually 2_000
maxDynamicRules, // Usually 100
});
const metadata = constructRedirects({
redirects,
logger: {
debug: console.debug,
log: console.log,
info: console.info,
warn: console.warn,
error: console.error,
},
});
const staticRules =
metadata.redirects?.version === REDIRECTS_VERSION
? metadata.redirects.staticRules || {}
: {};
return async (request: Request, assetsBinding: Fetcher) => {
const url = new URL(request.url);
const search = url.search;
let { pathname } = new URL(request.url);
const staticRedirectsMatcher = () => {
return staticRules[pathname];
};
const generateRedirectsMatcher = () =>
generateRulesMatcher(
metadata.redirects?.version === REDIRECTS_VERSION
? metadata.redirects.rules
: {},
({ status, to }, replacements) => ({
status,
to: replacer(to, replacements),
}),
);
const match =
staticRedirectsMatcher() || generateRedirectsMatcher()({ request })[0];
if (match) {
if (match.status === 200) {
// A 200 redirect means that we are proxying to a different asset, for example,
// a request with url /users/12345 could be pointed to /users/id.html. In order to
// do this, we overwrite the pathname, and instead match for assets with that url,
// and importantly, do not use the regular redirect handler - as the url visible to
// the user does not change
pathname = new URL(match.to, request.url).pathname;
return assetsBinding.fetch(
new Request(new URL(pathname + search, request.url), { ...request }),
);
} else {
const { status, to } = match;
const destination = new URL(to, request.url);
const location =
destination.origin === new URL(request.url).origin
? `${destination.pathname}${destination.search || search}${
destination.hash
}`
: `${destination.href.slice(
0,
destination.href.length -
(destination.search.length + destination.hash.length),
)}${destination.search ? destination.search : search}${
destination.hash
}`;
switch (status) {
case 301:
return new MovedPermanentlyResponse(location);
case 303:
return new SeeOtherResponse(location);
case 307:
return new TemporaryRedirectResponse(location);
case 308:
return new PermanentRedirectResponse(location);
case 302:
default:
return new FoundResponse(location);
}
}
}
return null;
};
};