aboutsummaryrefslogtreecommitdiff
path: root/packages/integrations/node/src
diff options
context:
space:
mode:
authorGravatar Marvin <32611659+msxdan@users.noreply.github.com> 2024-01-25 11:23:27 +0100
committerGravatar GitHub <noreply@github.com> 2024-01-25 10:23:27 +0000
commitf7f19bfa5b6b8d41a7ba3884d455961e817ec676 (patch)
treec3c13fbb65cb4a290857780af582f558293ccadd /packages/integrations/node/src
parent1901ed3ef5e798dd3b69e1b6c05db94b6a471ad2 (diff)
downloadastro-f7f19bfa5b6b8d41a7ba3884d455961e817ec676.tar.gz
astro-f7f19bfa5b6b8d41a7ba3884d455961e817ec676.tar.zst
astro-f7f19bfa5b6b8d41a7ba3884d455961e817ec676.zip
feat(node): add trailingSlash support (#9080)
* feat(node): add trailing slash support * add changeset * test(node): add base route test in trailing-slash.js detected an infinite loop in base path when trailingSlash: never * fix(node): avoid infinite redirect when trailingSlash: never * address test failures after rebase pt.1 * address test failures after rebase pt.2 --------- Co-authored-by: lilnasy <69170106+lilnasy@users.noreply.github.com>
Diffstat (limited to 'packages/integrations/node/src')
-rw-r--r--packages/integrations/node/src/serve-static.ts62
-rw-r--r--packages/integrations/node/src/server.ts1
-rw-r--r--packages/integrations/node/src/types.ts2
3 files changed, 49 insertions, 16 deletions
diff --git a/packages/integrations/node/src/serve-static.ts b/packages/integrations/node/src/serve-static.ts
index 77de9b358..a88b1332f 100644
--- a/packages/integrations/node/src/serve-static.ts
+++ b/packages/integrations/node/src/serve-static.ts
@@ -1,5 +1,6 @@
import path from 'node:path';
import url from 'node:url';
+import fs from 'node:fs';
import send from 'send';
import type { IncomingMessage, ServerResponse } from 'node:http';
import type { Options } from './types.js';
@@ -18,8 +19,47 @@ export function createStaticHandler(app: NodeApp, options: Options) {
*/
return (req: IncomingMessage, res: ServerResponse, ssr: () => unknown) => {
if (req.url) {
- let pathname = app.removeBase(req.url);
- pathname = decodeURI(new URL(pathname, 'http://host').pathname);
+ 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":
+ if (!hasSlash) {
+ 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,
@@ -47,20 +87,6 @@ export function createStaticHandler(app: NodeApp, options: Options) {
_res.setHeader('Cache-Control', 'public, max-age=31536000, immutable');
}
});
- stream.on('directory', () => {
- // On directory find, redirect to the trailing slash
- let location: string;
- if (req.url!.includes('?')) {
- const [url1 = '', search] = req.url!.split('?');
- location = `${url1}/?${search}`;
- } else {
- location = appendForwardSlash(req.url!);
- }
-
- res.statusCode = 301;
- res.setHeader('Location', location);
- res.end(location);
- });
stream.on('file', () => {
forwardError = true;
});
@@ -81,6 +107,10 @@ function resolveClientDir(options: Options) {
return client;
}
+function prependForwardSlash(pth: string) {
+ return pth.startsWith('/') ? pth : '/' + pth;
+}
+
function appendForwardSlash(pth: string) {
return pth.endsWith('/') ? pth : pth + '/';
}
diff --git a/packages/integrations/node/src/server.ts b/packages/integrations/node/src/server.ts
index d9f24cca5..73b59c53f 100644
--- a/packages/integrations/node/src/server.ts
+++ b/packages/integrations/node/src/server.ts
@@ -8,6 +8,7 @@ import type { Options } from './types.js';
applyPolyfills();
export function createExports(manifest: SSRManifest, options: Options) {
const app = new NodeApp(manifest);
+ options.trailingSlash = manifest.trailingSlash;
return {
options: options,
handler:
diff --git a/packages/integrations/node/src/types.ts b/packages/integrations/node/src/types.ts
index 9e4f4ce91..3c03dffac 100644
--- a/packages/integrations/node/src/types.ts
+++ b/packages/integrations/node/src/types.ts
@@ -1,5 +1,6 @@
import type { NodeApp } from 'astro/app/node';
import type { IncomingMessage, ServerResponse } from 'node:http';
+import type { SSRManifest } from 'astro';
export interface UserOptions {
/**
@@ -17,6 +18,7 @@ export interface Options extends UserOptions {
server: string;
client: string;
assets: string;
+ trailingSlash?: SSRManifest['trailingSlash'];
}
export interface CreateServerOptions {