UNPKG

redirects-in-workers

Version:

Cloudflare Pages' _redirects file support in Cloudflare Workers

119 lines (108 loc) 3.46 kB
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; }; };