azion
Version:
Azion Packages for Edge Computing.
340 lines (339 loc) • 14 kB
JavaScript
import { parse as x } from "cookie";
import { a as p, c as m, b as w, d, m as v, e as L, i as f, p as y } from "./index.mjs";
function R(a, { url: e, cookies: s, headers: t }) {
switch (a.type) {
case "host":
return e.hostname === a.value;
case "header":
return a.value !== void 0 ? !!t.get(a.key)?.match(a.value) : t.has(a.key);
case "cookie": {
const r = s[a.key];
return a.value !== void 0 ? !!r?.match(a.value) : r !== void 0;
}
case "query":
return a.value !== void 0 ? !!e.searchParams.get(a.key)?.match(a.value) : e.searchParams.has(a.key);
}
}
async function g(a, e = "none", s = !1) {
return await a.run(e) === "error" || !s && a.status && a.status >= 400 ? g(a, "error", !0) : {
path: a.path,
status: a.status,
headers: a.headers,
searchParams: a.searchParams,
body: a.body
};
}
function k(a) {
switch (a) {
// `none` applied headers/redirects/middleware/`beforeFiles` rewrites. It checked non-dynamic routes and static assets.
case "none":
return "filesystem";
// `filesystem` applied `afterFiles` rewrites. It checked those rewritten routes.
case "filesystem":
return "rewrite";
// `rewrite` applied dynamic params to requests. It checked dynamic routes.
case "rewrite":
return "resource";
// `resource` applied `fallback` rewrites. It checked the final routes.
case "resource":
return "miss";
default:
return "miss";
}
}
async function P(a, { request: e, assetsFetcher: s, ctx: t }, { path: r, searchParams: i }) {
let n;
const c = new URL(e.url);
p(c.searchParams, i);
const h = new Request(c, e);
try {
switch (a?.type) {
case "function":
case "middleware": {
n = await a.entrypoint.default(h, t);
break;
}
case "override": {
n = w(await s.fetch(m(h, a.path ?? r))), a.headers && d(n.headers, a.headers);
break;
}
case "static": {
n = await s.fetch(m(h, r));
break;
}
// run custom node server
case "node": {
n = await globalThis.runNodeCustomServer(h);
break;
}
default:
n = new Response("Not Found", { status: 404 });
}
} catch (o) {
return console.error(o), new Response("Internal Server Error", { status: 500 });
}
return w(n);
}
function q(a, e) {
const s = "^//?(?:", t = ")/(.*)";
return !a.startsWith(s) || !a.endsWith(t) ? !1 : a.slice(s.length, -t.length).split("|").every((i) => i in e);
}
class $ {
/**
* Creates a new instance of a request matcher.
*
* The matcher is used to match a request to a route and run the route's middleware.
* @param {object} routes The processed Vercel build output config routes.
* @param {object} output Vercel build output.
* @param {object} reqCtx Request context object; request object, assets fetcher, and execution context.
* @param {Array} buildMetadata Information about the build to be used in the routing.
* @returns {void} The matched set of path, status, headers, and search params.
*/
constructor(e, s, t, r) {
this.routes = e, this.output = s, this.reqCtx = t, this.url = new URL(t.request.url), this.cookies = x(t.request.headers.get("cookie") || ""), this.path = this.url.pathname || "/", this.headers = {
normal: new Headers(),
important: new Headers()
}, this.searchParams = new URLSearchParams(), p(this.searchParams, this.url.searchParams), this.checkPhaseCounter = 0, this.middlewareInvoked = [], this.locales = new Set(r.i18n.locales), this.hasIndexFunctions = r.hasIndexFunctions, this.defaultLocale = r.i18n.defaultLocale;
}
/**
* Checks if a Vercel source route from the build output config matches the request.
* @param {object} route Build output config source route.
* @param {string} checkStatus Whether to check the status code of the route.
* @returns {void} The source path match result if the route matches, otherwise `undefined`.
*/
checkRouteMatch(e, s) {
const t = v(e.src, this.path, e.caseSensitive);
if (!t.match || e.methods && !e.methods.map((i) => i.toUpperCase()).includes(this.reqCtx.request.method.toUpperCase()))
return;
const r = {
url: this.url,
cookies: this.cookies,
headers: this.reqCtx.request.headers
};
if (!e.has?.find((i) => !R(i, r)) && !e.missing?.find((i) => R(i, r)) && !(s && e.status !== this.status))
return t;
}
/**
* Processes the response from running a middleware function.
*
* Handles rewriting the URL and applying redirects, response headers, and overriden request headers.
* @param {object} resp Middleware response object.
*/
processMiddlewareResp(e) {
const s = "x-middleware-override-headers", t = e.headers.get(s);
if (t) {
const h = new Set(t.split(",").map((o) => o.trim()));
for (const o of h.keys()) {
const u = `x-middleware-request-${o}`, l = e.headers.get(u);
this.reqCtx.request.headers.get(o) !== l && (l ? this.reqCtx.request.headers.set(o, l) : this.reqCtx.request.headers.delete(o)), e.headers.delete(u);
}
e.headers.delete(s);
}
const r = "x-middleware-rewrite", i = e.headers.get(r);
if (i) {
const h = new URL(i, this.url);
this.path = h.pathname, p(this.searchParams, h.searchParams), e.headers.delete(r);
}
const n = "x-middleware-next";
e.headers.get(n) ? e.headers.delete(n) : !i && !e.headers.has("location") && (this.body = e.body, this.status = e.status), d(this.headers.normal, e.headers), this.headers.middlewareLocation = e.headers.get("location");
}
/**
* Runs the middleware function for a route if it exists.
* @param {string} path Path to the route's middleware function.
* @returns {boolean} Whether the middleware function was run successfully.
*/
async runRouteMiddleware(e) {
if (!e) return !0;
const s = e && this.output[e];
if (!s || s.type !== "middleware")
return this.status = 500, !1;
const t = await P(s, this.reqCtx, {
path: this.path,
searchParams: this.searchParams,
headers: this.headers,
status: this.status
});
return this.middlewareInvoked.push(e), t.status === 500 ? (this.status = t.status, !1) : (this.processMiddlewareResp(t), !0);
}
/**
* Resets the response status and headers if the route should override them.
* @param {object} route Build output config source route.
*/
applyRouteOverrides(e) {
e.override && (this.status = void 0, this.headers.normal = new Headers(), this.headers.important = new Headers());
}
/**
* Applies the route's headers for the response object.
* @param {object} route Build output config source route.
* @param {object[]} srcMatch Matches from the PCRE matcher.
* @param {string[]}captureGroupKeys Named capture group keys from the PCRE matcher.
*/
applyRouteHeaders(e, s, t) {
e.headers && (d(this.headers.normal, e.headers, {
match: s,
captureGroupKeys: t
}), e.important && d(this.headers.important, e.headers, {
match: s,
captureGroupKeys: t
}));
}
/**
* Applies the route's status code for the response object.
* @param {object} route Build output config source route.
*/
applyRouteStatus(e) {
e.status && (this.status = e.status);
}
/**
* Applies the route's destination for the matching the path to the Vercel build output.
* @param {object} route Build output config source route.
* @param {object} srcMatch Matches from the PCRE matcher.
* @param {string[]} captureGroupKeys Named capture group keys from the PCRE matcher.
* @returns {string }The previous path for the route before applying the destination.
*/
applyRouteDest(e, s, t) {
if (this.locales.has(this.path.replace("/", "")) && this.hasIndexFunctions.length > 0 && (this.path = `${this.path}/`), !e.dest || this.defaultLocale === e.dest?.replace("/", ""))
return this.path;
const r = this.path;
this.path = L(e.dest, s, t), /\/index\.rsc$/i.test(this.path) && !/\/(?:index)?$/i.test(r) && (this.path = r);
const i = /\.rsc$/i.test(this.path), n = this.path in this.output;
i && !n && (this.path = this.path.replace(/\.rsc/i, ""));
const c = new URL(this.path, this.url);
return p(this.searchParams, c.searchParams), f(this.path) || (this.path = c.pathname), r;
}
/**
* Applies the route's redirects for locales and internationalization.
* @param {void} route Build output config source route.
*/
applyLocaleRedirects(e) {
if (!e.locale?.redirect || (this.locales || (this.locales = {}), !/^\^(.)*$/.test(e.src) && e.src !== this.path) || this.headers.normal.has("location")) return;
const {
locale: { redirect: t, cookie: r }
} = e, i = r && this.cookies[r], n = y(i ?? ""), c = y(this.reqCtx.request.headers.get("accept-language") ?? ""), u = [...n, ...c].map((l) => t[l]).filter(Boolean)[0];
u && !this.path.startsWith(u) && (this.headers.normal.set("location", u), this.status = 307);
}
/**
* Modifies the source route's `src` regex to be friendly with previously found locale's in the
* `miss` phase.
*
* Sometimes, there is a source route with `src: '/{locale}'`, which rewrites all paths containing
* the locale to `/`. This is problematic for matching, and should only do this if the path is
* exactly the locale, i.e. `^/{locale}$`.
* @param {object} route Build output config source route.
* @param {object} phase Current phase of the routing process.
* @returns {object} The route with the locale friendly regex.
*/
getLocaleFriendlyRoute(e, s) {
return !this.locales || s !== "miss" ? e : /^\//.test(e.src) && e.src.slice(1) in this.locales ? { ...e, src: `^${e.src}$` } : q(e.src, this.locales) ? { ...e, src: e.src.replace(/\/\(\.\*\)$/, "(?:/(.*))?$") } : e;
}
/**
* Checks a route to see if it matches the current request.
* @param {object} phase Current phase of the routing process.
* @param {object} rawRoute Build output config source route.
* @returns {Promise}The status from checking the route.
*/
async checkRoute(e, s) {
const t = this.getLocaleFriendlyRoute(s, e), r = this.checkRouteMatch(t, e === "error");
if (!r?.match || t.middlewarePath && this.middlewareInvoked.includes(t.middlewarePath))
return "skip";
const { match: i, captureGroupKeys: n } = r;
if (this.applyRouteOverrides(t), this.applyLocaleRedirects(t), !await this.runRouteMiddleware(t.middlewarePath)) return "error";
if (this.body !== void 0) return "done";
this.applyRouteHeaders(t, i, n), this.applyRouteStatus(t);
const h = this.applyRouteDest(t, i, n);
if (t.check && !f(this.path))
if (h === this.path) {
if (e !== "miss")
return await this.checkPhase(k(e));
this.status = 404;
} else if (e === "miss") {
if (!(this.path in this.output))
return await this.checkPhase("filesystem");
this.status === 404 && (this.status = void 0);
} else
return await this.checkPhase("none");
return t.continue ? "next" : "done";
}
/**
* Checks a phase from the routing process to see if any route matches the current request.
* @param {object} phase Current phase for routing.
* @returns {Promise} The status from checking the phase.
*/
async checkPhase(e) {
if (this.checkPhaseCounter++ >= 50)
return console.error(`Routing encountered an infinite loop while checking ${this.url.pathname}`), this.status = 500, "error";
this.middlewareInvoked = [];
let s = !0;
for (const i of this.routes[e]) {
const n = await this.checkRoute(e, i);
if (n === "error")
return "error";
if (n === "done") {
s = !1;
break;
}
}
if (e === "hit" || f(this.path) || this.headers.normal.has("location"))
return "done";
if (e === "none")
for (const i of this.locales) {
const n = new RegExp(`/${i}(/.*)`), h = this.path.match(n)?.[1];
if (h && h in this.output) {
this.path = h;
break;
}
}
let t = this.path in this.output;
if (e === "rewrite" && !t && this.path.endsWith("/")) {
const i = this.path.replace(/\/$/, "");
t = i in this.output, t && (this.path = i);
}
if (e === "miss" && !t) {
const i = !this.status || this.status < 400;
this.status = i ? 404 : this.status;
}
let r = "miss";
return t || e === "miss" || e === "error" ? r = "hit" : s && (r = k(e)), await this.checkPhase(r);
}
/**
* Runs the matcher for a phase.
* @param {string} phase The phase to start matching routes from.
* @returns {Promise} The status from checking for matches.
*/
async run(e = "none") {
this.checkPhaseCounter = 0;
const s = await this.checkPhase(e);
return f(this.path) && this.headers.normal.set("location", this.path), this.headers.normal.has("location") && (!this.status || this.status < 300 || this.status >= 400) && (this.status = 307), s;
}
}
async function I(a, { path: e = "/404", status: s, headers: t, searchParams: r, body: i }, n) {
const c = t.normal.get("location");
if (c) {
if (c !== t.middlewareLocation) {
const u = [...r.keys()].length ? `?${r.toString()}` : "";
t.normal.set("location", `${c ?? "/"}${u}`);
}
return new Response(null, { status: s, headers: t.normal });
}
let h = i !== void 0 ? (
// If we have a response body from matching, use it instead.
new Response(i, { status: s })
) : await P(n[e], a, {
path: e,
searchParams: r
});
const o = t.normal;
return d(o, h.headers), d(o, t.important), h = new Response(h.body, {
...h,
status: s || h.status,
headers: o
}), h;
}
async function b(a, e, s, t) {
const r = new $(e.routes, s, a, t), i = await g(r);
return I(a, i, s);
}
export {
I as generateResponse,
b as handleRequest
};