summaryrefslogtreecommitdiff
path: root/packages/integrations/node/src/serve-static.ts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/integrations/node/src/serve-static.ts')
-rw-r--r--packages/integrations/node/src/serve-static.ts125
1 files changed, 0 insertions, 125 deletions
diff --git a/packages/integrations/node/src/serve-static.ts b/packages/integrations/node/src/serve-static.ts
deleted file mode 100644
index 725f7afa6..000000000
--- a/packages/integrations/node/src/serve-static.ts
+++ /dev/null
@@ -1,125 +0,0 @@
-import fs from 'node:fs';
-import type { IncomingMessage, ServerResponse } from 'node:http';
-import path from 'node:path';
-import url from 'node:url';
-import type { NodeApp } from 'astro/app/node';
-import send from 'send';
-import type { Options } from './types.js';
-
-// check for a dot followed by a extension made up of lowercase characters
-const isSubresourceRegex = /.+\.[a-z]+$/i;
-
-/**
- * Creates a Node.js http listener for static files and prerendered pages.
- * In standalone mode, the static handler is queried first for the static files.
- * If one matching the request path is not found, it relegates to the SSR handler.
- * Intended to be used only in the standalone mode.
- */
-export function createStaticHandler(app: NodeApp, options: Options) {
- const client = resolveClientDir(options);
- /**
- * @param ssr The SSR handler to be called if the static handler does not find a matching file.
- */
- return (req: IncomingMessage, res: ServerResponse, ssr: () => unknown) => {
- if (req.url) {
- const [urlPath, urlQuery] = req.url.split('?');
- const filePath = path.join(client, app.removeBase(urlPath));
-
- let pathname: string;
- let isDirectory = false;
- try {
- isDirectory = fs.lstatSync(filePath).isDirectory();
- } catch {}
-
- const { trailingSlash = 'ignore' } = options;
-
- const hasSlash = urlPath.endsWith('/');
- switch (trailingSlash) {
- case 'never':
- if (isDirectory && urlPath != '/' && hasSlash) {
- pathname = urlPath.slice(0, -1) + (urlQuery ? '?' + urlQuery : '');
- res.statusCode = 301;
- res.setHeader('Location', pathname);
- return res.end();
- } else pathname = urlPath;
- // intentionally fall through
- case 'ignore':
- {
- if (isDirectory && !hasSlash) {
- pathname = urlPath + '/index.html';
- } else pathname = urlPath;
- }
- break;
- case 'always':
- // trailing slash is not added to "subresources"
- if (!hasSlash && !isSubresourceRegex.test(urlPath)) {
- pathname = urlPath + '/' + (urlQuery ? '?' + urlQuery : '');
- res.statusCode = 301;
- res.setHeader('Location', pathname);
- return res.end();
- } else pathname = urlPath;
- break;
- }
- // app.removeBase sometimes returns a path without a leading slash
- pathname = prependForwardSlash(app.removeBase(pathname));
-
- const stream = send(req, pathname, {
- root: client,
- dotfiles: pathname.startsWith('/.well-known/') ? 'allow' : 'deny',
- });
-
- let forwardError = false;
-
- stream.on('error', (err) => {
- if (forwardError) {
- console.error(err.toString());
- res.writeHead(500);
- res.end('Internal server error');
- return;
- }
- // File not found, forward to the SSR handler
- ssr();
- });
- stream.on('headers', (_res: ServerResponse) => {
- // assets in dist/_astro are hashed and should get the immutable header
- if (pathname.startsWith(`/${options.assets}/`)) {
- // This is the "far future" cache header, used for static files whose name includes their digest hash.
- // 1 year (31,536,000 seconds) is convention.
- // Taken from https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control#immutable
- _res.setHeader('Cache-Control', 'public, max-age=31536000, immutable');
- }
- });
- stream.on('file', () => {
- forwardError = true;
- });
- stream.pipe(res);
- } else {
- ssr();
- }
- };
-}
-
-function resolveClientDir(options: Options) {
- const clientURLRaw = new URL(options.client);
- const serverURLRaw = new URL(options.server);
- const rel = path.relative(url.fileURLToPath(serverURLRaw), url.fileURLToPath(clientURLRaw));
-
- // walk up the parent folders until you find the one that is the root of the server entry folder. This is how we find the client folder relatively.
- const serverFolder = path.basename(options.server);
- let serverEntryFolderURL = path.dirname(import.meta.url);
- while (!serverEntryFolderURL.endsWith(serverFolder)) {
- serverEntryFolderURL = path.dirname(serverEntryFolderURL);
- }
- const serverEntryURL = serverEntryFolderURL + '/entry.mjs';
- const clientURL = new URL(appendForwardSlash(rel), serverEntryURL);
- const client = url.fileURLToPath(clientURL);
- return client;
-}
-
-function prependForwardSlash(pth: string) {
- return pth.startsWith('/') ? pth : '/' + pth;
-}
-
-function appendForwardSlash(pth: string) {
- return pth.endsWith('/') ? pth : pth + '/';
-}