diff --git a/packages/integrations/node/src/http-server.ts b/packages/integrations/node/src/http-server.ts index 2f2339cdf..22ab849ba 100644 --- a/packages/integrations/node/src/http-server.ts +++ b/packages/integrations/node/src/http-server.ts @@ -23,13 +23,14 @@ function parsePathname(pathname: string, host: string | undefined, port: number) export function createServer( { client, port, host, removeBase }: CreateServerOptions, - handler: http.RequestListener + handler: http.RequestListener, + trailingSlash: string ) { const listener: http.RequestListener = (req, res) => { if (req.url) { let pathname: string | undefined = removeBase(req.url); pathname = pathname[0] === '/' ? pathname : '/' + pathname; - const encodedURI = parsePathname(pathname, host, port); + let encodedURI = parsePathname(pathname, host, port); if (!encodedURI) { res.writeHead(400); @@ -37,7 +38,21 @@ export function createServer( return res; } - const stream = send(req, encodedURI, { + let pathForSend = encodedURI; + + if (trailingSlash === 'never') { + if (pathname.endsWith('/')) { + encodedURI = parsePathname(pathname.substring(0, pathname.length - 1), host, port); + if (!encodedURI) { + res.writeHead(400); + res.end('Bad request.'); + return res; + } + } + pathForSend = encodedURI + '/'; + } + + const stream = send(req, pathForSend, { root: fileURLToPath(client), dotfiles: pathname.startsWith('/.well-known/') ? 'allow' : 'deny', }); @@ -64,9 +79,11 @@ export function createServer( location = req.url + '/'; } - res.statusCode = 301; - res.setHeader('Location', location); - res.end(location); + if (!pathForSend.endsWith('/')) { + res.statusCode = 301; + res.setHeader('Location', location); + res.end(location); + } }); stream.on('file', () => { forwardError = true; diff --git a/packages/integrations/node/src/index.ts b/packages/integrations/node/src/index.ts index 5978371e4..1f339ddaf 100644 --- a/packages/integrations/node/src/index.ts +++ b/packages/integrations/node/src/index.ts @@ -46,6 +46,7 @@ export default function createIntegration(userOptions: UserOptions): AstroIntegr server: config.build.server?.toString(), host: config.server.host, port: config.server.port, + trailingSlash: config.trailingSlash, }; setAdapter(getAdapter(_options)); diff --git a/packages/integrations/node/src/standalone.ts b/packages/integrations/node/src/standalone.ts index abe40ff5c..7a90594f1 100644 --- a/packages/integrations/node/src/standalone.ts +++ b/packages/integrations/node/src/standalone.ts @@ -53,7 +53,8 @@ export default function startServer(app: NodeApp, options: Options) { host, removeBase: app.removeBase.bind(app), }, - handler + handler, + options.trailingSlash ); const protocol = server.server instanceof https.Server ? 'https' : 'http'; diff --git a/packages/integrations/node/src/types.ts b/packages/integrations/node/src/types.ts index 85f4f4fbc..4d22ea5b6 100644 --- a/packages/integrations/node/src/types.ts +++ b/packages/integrations/node/src/types.ts @@ -15,6 +15,7 @@ export interface Options extends UserOptions { port: number; server: string; client: string; + trailingSlash: string; } export type RequestHandlerParams = [