diff --git a/.changeset/brave-pumpkins-train.md b/.changeset/brave-pumpkins-train.md new file mode 100644 index 0000000000..61a7492406 --- /dev/null +++ b/.changeset/brave-pumpkins-train.md @@ -0,0 +1,5 @@ +--- +"astro": patch +--- + +Fixes an issue where forwarded requests did not include hostname on node-based adapters. This also makes error pages more reliable. diff --git a/packages/astro/src/core/app/node.ts b/packages/astro/src/core/app/node.ts index 0795c8dd6f..df2fa12d9a 100644 --- a/packages/astro/src/core/app/node.ts +++ b/packages/astro/src/core/app/node.ts @@ -63,8 +63,9 @@ export class NodeApp extends App { const protocol = req.headers['x-forwarded-proto'] ?? ('encrypted' in req.socket && req.socket.encrypted ? 'https' : 'http'); - const hostname = req.headers.host || req.headers[':authority']; - const url = `${protocol}://${hostname}${req.url}`; + const hostname = req.headers["x-forwarded-host"] ?? req.headers.host ?? req.headers[":authority"]; + const port = req.headers["x-forwarded-port"]; + const url = `${protocol}://${hostname}${port ? `:${port}` : ''}${req.url}`; const options: RequestInit = { method: req.method || 'GET', headers: makeRequestHeaders(req), diff --git a/packages/integrations/node/test/fixtures/url-protocol/src/pages/index.astro b/packages/integrations/node/test/fixtures/url-protocol/src/pages/index.astro deleted file mode 100644 index 61fb9867b1..0000000000 --- a/packages/integrations/node/test/fixtures/url-protocol/src/pages/index.astro +++ /dev/null @@ -1,11 +0,0 @@ ---- ---- - - - - url-protocol - - - {Astro.url.protocol} - - diff --git a/packages/integrations/node/test/fixtures/url-protocol/package.json b/packages/integrations/node/test/fixtures/url/package.json similarity index 80% rename from packages/integrations/node/test/fixtures/url-protocol/package.json rename to packages/integrations/node/test/fixtures/url/package.json index 4b0775716a..f349011fd3 100644 --- a/packages/integrations/node/test/fixtures/url-protocol/package.json +++ b/packages/integrations/node/test/fixtures/url/package.json @@ -1,5 +1,5 @@ { - "name": "@test/url-protocol", + "name": "@test/url", "version": "0.0.0", "private": true, "dependencies": { diff --git a/packages/integrations/node/test/fixtures/url/src/pages/index.astro b/packages/integrations/node/test/fixtures/url/src/pages/index.astro new file mode 100644 index 0000000000..003429f520 --- /dev/null +++ b/packages/integrations/node/test/fixtures/url/src/pages/index.astro @@ -0,0 +1,9 @@ +--- +--- + + + + URL + + {Astro.url.href} + diff --git a/packages/integrations/node/test/url-protocol.test.js b/packages/integrations/node/test/url.test.js similarity index 62% rename from packages/integrations/node/test/url-protocol.test.js rename to packages/integrations/node/test/url.test.js index 94d53104b9..39d4665272 100644 --- a/packages/integrations/node/test/url-protocol.test.js +++ b/packages/integrations/node/test/url.test.js @@ -3,14 +3,15 @@ import { before, describe, it } from 'node:test'; import { TLSSocket } from 'node:tls'; import nodejs from '../dist/index.js'; import { createRequestAndResponse, loadFixture } from './test-utils.js'; +import * as cheerio from 'cheerio'; -describe('URL protocol', () => { - /** @type {import('./test-utils').Fixture} */ +describe('URL', () => { + /** @type {import('./test-utils.js').Fixture} */ let fixture; before(async () => { fixture = await loadFixture({ - root: './fixtures/url-protocol/', + root: './fixtures/url/', output: 'server', adapter: nodejs({ mode: 'standalone' }), }); @@ -18,7 +19,7 @@ describe('URL protocol', () => { }); it('return http when non-secure', async () => { - const { handler } = await import('./fixtures/url-protocol/dist/server/entry.mjs'); + const { handler } = await import('./fixtures/url/dist/server/entry.mjs'); let { req, res, text } = createRequestAndResponse({ url: '/', }); @@ -31,7 +32,7 @@ describe('URL protocol', () => { }); it('return https when secure', async () => { - const { handler } = await import('./fixtures/url-protocol/dist/server/entry.mjs'); + const { handler } = await import('./fixtures/url/dist/server/entry.mjs'); let { req, res, text } = createRequestAndResponse({ socket: new TLSSocket(), url: '/', @@ -45,7 +46,7 @@ describe('URL protocol', () => { }); it('return http when the X-Forwarded-Proto header is set to http', async () => { - const { handler } = await import('./fixtures/url-protocol/dist/server/entry.mjs'); + const { handler } = await import('./fixtures/url/dist/server/entry.mjs'); let { req, res, text } = createRequestAndResponse({ headers: { 'X-Forwarded-Proto': 'http' }, url: '/', @@ -59,7 +60,7 @@ describe('URL protocol', () => { }); it('return https when the X-Forwarded-Proto header is set to https', async () => { - const { handler } = await import('./fixtures/url-protocol/dist/server/entry.mjs'); + const { handler } = await import('./fixtures/url/dist/server/entry.mjs'); let { req, res, text } = createRequestAndResponse({ headers: { 'X-Forwarded-Proto': 'https' }, url: '/', @@ -71,4 +72,24 @@ describe('URL protocol', () => { const html = await text(); assert.equal(html.includes('https:'), true); }); + + it('includes forwarded host and port in the url', async () => { + const { handler } = await import('./fixtures/url/dist/server/entry.mjs'); + let { req, res, text } = createRequestAndResponse({ + headers: { + 'X-Forwarded-Proto': 'https', + 'X-Forwarded-Host': 'abc.xyz', + 'X-Forwarded-Port': '444' + }, + url: '/', + }); + + handler(req, res); + req.send(); + + const html = await text(); + const $ = cheerio.load(html); + + assert.equal($('body').text(), "https://abc.xyz:444/"); + }); }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b0db54548a..51abebd49d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4703,7 +4703,7 @@ importers: specifier: workspace:* version: link:../../../../../astro - packages/integrations/node/test/fixtures/url-protocol: + packages/integrations/node/test/fixtures/url: dependencies: '@astrojs/node': specifier: workspace:*