@universal-middleware/sirv
Version:
Universal static file serving middleware
361 lines (358 loc) • 11.5 kB
JavaScript
import { bindUniversal, contextSymbol, universalSymbol, getAdapterRuntime, nodeHeadersToWeb } from './chunk-APV2JR36.js';
import 'node:http';
import 'node:stream';
var deno = typeof Deno !== "undefined";
var bun = typeof Bun !== "undefined";
function createRequestAdapter(options = {}) {
const { origin = env.ORIGIN, trustProxy = env.TRUST_PROXY === "1" } = options;
let { protocol: protocolOverride, host: hostOverride } = origin ? new URL(origin) : {};
if (protocolOverride) {
protocolOverride = protocolOverride.slice(0, -1);
}
let warned = false;
return function requestAdapter(req) {
if (req[requestSymbol]) {
return req[requestSymbol];
}
function parseForwardedHeader(name) {
return (headers[`x-forwarded-${name}`] || "").split(",", 1)[0].trim();
}
let headers = req.headers;
if (headers[":method"]) {
headers = Object.fromEntries(Object.entries(headers).filter(([key]) => !key.startsWith(":")));
}
const protocol = protocolOverride || trustProxy && parseForwardedHeader("proto") || req.protocol || // biome-ignore lint/suspicious/noExplicitAny: encrypted can exist in some express versions
req.socket?.encrypted && "https" || "http";
let host = hostOverride || trustProxy && parseForwardedHeader("host") || headers.host;
if (!host && !warned) {
console.warn(
"Could not automatically determine the origin host, using 'localhost'. Use the 'origin' option or the 'ORIGIN' environment variable to set the origin explicitly."
);
warned = true;
host = "localhost";
}
const request = new Request(`${protocol}://${host}${req.originalUrl ?? req.url}`, {
method: req.method,
headers,
body: convertBody(req),
// @ts-expect-error
duplex: "half"
});
req[requestSymbol] = request;
return request;
};
}
function convertBody(req) {
if (req.method === "GET" || req.method === "HEAD") {
return;
}
if (req.rawBody !== void 0) {
return req.rawBody;
}
if (!bun && !deno) {
return req;
}
return new ReadableStream({
start(controller) {
req.on("data", (chunk) => controller.enqueue(chunk));
req.on("end", () => controller.close());
req.on("error", (err) => controller.error(err));
}
});
}
var deno2 = typeof Deno !== "undefined";
async function sendResponse(fetchResponse, nodeResponse) {
const fetchBody = fetchResponse.body;
let body = null;
if (!fetchBody) {
body = null;
} else if (typeof fetchBody.pipe === "function") {
body = fetchBody;
} else if (typeof fetchBody.pipeTo === "function") {
const { Readable: Readable2 } = await import('node:stream');
if (!deno2 && Readable2.fromWeb) {
body = Readable2.fromWeb(fetchBody);
} else {
const reader = fetchBody.getReader();
body = new Readable2({
async read() {
try {
const { done, value } = await reader.read();
if (done) {
this.push(null);
} else {
const canContinue = this.push(value);
if (!canContinue) {
reader.releaseLock();
}
}
} catch (e) {
this.destroy(e);
}
}
});
}
} else if (fetchBody) {
const { Readable: Readable2 } = await import('node:stream');
body = Readable2.from(fetchBody);
}
setHeaders(fetchResponse, nodeResponse);
if (body) {
body.pipe(nodeResponse);
await new Promise((resolve, reject) => {
body.on("error", (err) => {
nodeResponse.destroy(err);
reject(err);
});
nodeResponse.on("error", (err) => {
body.destroy(err);
reject(err);
});
nodeResponse.on("finish", resolve);
nodeResponse.on("drain", () => {
body.resume();
});
});
} else {
nodeResponse.setHeader("content-length", "0");
nodeResponse.end();
}
}
function createTransformStream() {
const textEncoder = new TextEncoder();
return new TransformStream({
transform(chunk, ctrl) {
if (typeof chunk === "string") {
ctrl.enqueue(textEncoder.encode(chunk));
} else if (chunk instanceof Uint8Array) {
ctrl.enqueue(chunk);
} else {
ctrl.enqueue(new Uint8Array(chunk));
}
}
});
}
function override(nodeResponse, key, forwardTo) {
const original = nodeResponse[key];
nodeResponse[key] = (...args) => {
if (!nodeResponse.headersSent) {
nodeResponse.writeHead(nodeResponse.statusCode);
}
if (args[0] && args[0].length > 0) {
forwardTo.write(args[0]).catch(console.error);
}
if (key === "end") {
forwardTo.close().catch(() => {
});
}
return true;
};
return {
original(...args) {
original.apply(nodeResponse, args);
},
restore() {
nodeResponse[key] = original;
}
};
}
function overrideWriteHead(nodeResponse, callback) {
const original = nodeResponse.writeHead;
let alreadyCalled = false;
nodeResponse.writeHead = () => {
if (!alreadyCalled) {
callback().catch(console.error);
alreadyCalled = true;
}
return nodeResponse;
};
return {
original(...args) {
original.apply(nodeResponse, args);
},
restore() {
nodeResponse.writeHead = original;
}
};
}
function getFullUrl(pathnameOrFull, req) {
try {
return new URL(pathnameOrFull).href;
} catch {
const protocol = req.socket?.encrypted || req.headers["x-forwarded-proto"] === "https" ? "https" : "http";
const host = req.headers["x-forwarded-host"] || req.headers.host || "localhost";
const baseUrl = `${protocol}://${host}`;
return new URL(pathnameOrFull, baseUrl).href;
}
}
function responseAdapter(nodeResponse, bodyInit) {
if ([301, 302, 303, 307, 308].includes(nodeResponse.statusCode) && nodeResponse.req) {
const location = nodeResponse.getHeader("location");
if (location) {
const fullUrl = getFullUrl(location, nodeResponse.req);
return Response.redirect(fullUrl, nodeResponse.statusCode);
}
}
return new Response([204, 304].includes(nodeResponse.statusCode) ? null : bodyInit, {
status: nodeResponse.statusCode,
statusText: nodeResponse.statusMessage,
headers: nodeHeadersToWeb(nodeResponse.getHeaders())
});
}
function wrapResponse(nodeResponse, next) {
if (nodeResponse[wrappedResponseSymbol]) return;
nodeResponse[wrappedResponseSymbol] = true;
const body = createTransformStream();
const writer = body.writable.getWriter();
const [reader1, reader2] = body.readable.tee();
const original = {
write: override(nodeResponse, "write", writer),
end: override(nodeResponse, "end", writer),
writeHead: overrideWriteHead(nodeResponse, triggerPendingMiddlewares)
};
async function triggerPendingMiddlewares() {
if (!nodeResponse[pendingMiddlewaresSymbol]) {
return;
}
const middlewares = nodeResponse[pendingMiddlewaresSymbol];
delete nodeResponse[pendingMiddlewaresSymbol];
let response;
try {
response = responseAdapter(nodeResponse, reader1);
for (const middleware of middlewares) {
const tmp = await middleware(response);
if (tmp) response = tmp;
}
} catch (e) {
response = void 0;
await writer.abort();
original.writeHead.restore();
original.write.restore();
original.end.restore();
if (next) {
next(e);
} else {
throw e;
}
}
if (!response) return;
const readableToOriginal = response.body ?? reader2;
setHeaders(response, nodeResponse, true);
original.writeHead.restore();
nodeResponse.flushHeaders();
const wait = readableToOriginal.pipeTo(
new WritableStream({
write(chunk) {
original.write.original(chunk);
},
close() {
original.end.original();
},
abort() {
original.end.original();
}
})
);
await wait;
original.write.restore();
original.end.restore();
}
}
function setHeaders(fetchResponse, nodeResponse, mirror = false) {
nodeResponse.statusCode = fetchResponse.status;
if (fetchResponse.statusText) {
nodeResponse.statusMessage = fetchResponse.statusText;
}
const nodeResponseHeaders = new Set(Object.keys(nodeResponse.getHeaders()));
const setCookie = fetchResponse.headers.getSetCookie();
for (const cookie of setCookie) {
nodeResponse.appendHeader("set-cookie", cookie);
}
fetchResponse.headers.forEach((value, key) => {
nodeResponseHeaders.delete(key);
if (key === "set-cookie") return;
nodeResponse.setHeader(key, value);
});
if (mirror) {
nodeResponseHeaders.forEach((key) => {
nodeResponse.removeHeader(key);
});
}
}
var requestSymbol = /* @__PURE__ */ Symbol.for("unRequest");
var pendingMiddlewaresSymbol = /* @__PURE__ */ Symbol.for("unPendingMiddlewares");
var wrappedResponseSymbol = /* @__PURE__ */ Symbol.for("unWrappedResponse");
var env = typeof globalThis.process?.env !== "undefined" ? globalThis.process.env : typeof import.meta?.env !== "undefined" ? import.meta.env : {};
function nextOr404(res, next) {
if (next) {
next();
} else {
res.statusCode = 404;
res.end();
}
}
function createMiddleware(middlewareFactory, options = {}) {
const requestAdapter = createRequestAdapter(options);
return (...args) => {
const middleware = middlewareFactory(...args);
return bindUniversal(middleware, async function universalMiddlewareExpress(req, res, next) {
try {
req[contextSymbol] ??= {};
const request = requestAdapter(req);
const response = await this[universalSymbol](request, getContext(req), getRuntime(req, res));
if (!response) {
return nextOr404(res, next);
}
if (typeof response === "function") {
if (res.headersSent) {
throw new Error(
"Universal Middleware called after headers have been sent. Please open an issue at https://github.com/magne4000/universal-middleware"
);
}
if (req.complete === void 0) req.complete = req._readableState?.ended ?? true;
wrapResponse(res, next);
res[pendingMiddlewaresSymbol] ??= [];
res[pendingMiddlewaresSymbol].push(response);
return nextOr404(res, next);
}
if (response instanceof Response) {
await sendResponse(response, res);
} else {
req[contextSymbol] = response;
return nextOr404(res, next);
}
} catch (error) {
if (next) {
next(error);
} else {
console.error(error);
if (!res.headersSent) {
res.statusCode = 500;
}
if (!res.writableEnded) {
res.end();
}
}
}
});
};
}
function getContext(req) {
return req[contextSymbol];
}
function getRuntime(request, response) {
return getAdapterRuntime("express", {
params: request.params,
// biome-ignore lint/suspicious/noExplicitAny: cast
req: request,
// biome-ignore lint/suspicious/noExplicitAny: cast
res: response,
express: Object.freeze({
// biome-ignore lint/suspicious/noExplicitAny: cast
req: request,
// biome-ignore lint/suspicious/noExplicitAny: cast
res: response
})
});
}
export { createMiddleware, createRequestAdapter };