Add server.headers
option (#5564)
With this new `server.headers` option, the users can specify custom headers for `astro dev` and `astro preview` servers. This is useful when they want to build a website requiring specific response headers such as `Cross-Origin-Opener-Policy`.
This commit is contained in:
parent
8913c51e1a
commit
dced4a8a26
13 changed files with 157 additions and 6 deletions
5
.changeset/light-moose-attend.md
Normal file
5
.changeset/light-moose-attend.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
'astro': minor
|
||||
---
|
||||
|
||||
Add `server.headers` option
|
|
@ -9,6 +9,7 @@ import { fileURLToPath } from 'url';
|
|||
import { z } from 'zod';
|
||||
import { appendForwardSlash, prependForwardSlash, trimSlashes } from '../path.js';
|
||||
import { isObject } from '../util.js';
|
||||
import { OutgoingHttpHeaders } from 'http';
|
||||
|
||||
const ASTRO_CONFIG_DEFAULTS: AstroUserConfig & any = {
|
||||
root: '.',
|
||||
|
@ -125,6 +126,7 @@ export const AstroConfigSchema = z.object({
|
|||
.optional()
|
||||
.default(ASTRO_CONFIG_DEFAULTS.server.host),
|
||||
port: z.number().optional().default(ASTRO_CONFIG_DEFAULTS.server.port),
|
||||
headers: z.custom<OutgoingHttpHeaders>().optional(),
|
||||
})
|
||||
.optional()
|
||||
.default({})
|
||||
|
@ -287,6 +289,7 @@ export function createRelativeSchema(cmd: string, fileProtocolRoot: URL) {
|
|||
.optional()
|
||||
.default(ASTRO_CONFIG_DEFAULTS.server.host),
|
||||
port: z.number().optional().default(ASTRO_CONFIG_DEFAULTS.server.port),
|
||||
headers: z.custom<OutgoingHttpHeaders>().optional(),
|
||||
streaming: z.boolean().optional().default(true),
|
||||
})
|
||||
.optional()
|
||||
|
|
|
@ -65,7 +65,7 @@ export async function createContainer(params: CreateContainerParams = {}): Promi
|
|||
logging,
|
||||
isRestart,
|
||||
});
|
||||
const { host } = settings.config.server;
|
||||
const { host, headers } = settings.config.server;
|
||||
|
||||
// The client entrypoint for renderers. Since these are imported dynamically
|
||||
// we need to tell Vite to preoptimize them.
|
||||
|
@ -76,7 +76,7 @@ export async function createContainer(params: CreateContainerParams = {}): Promi
|
|||
const viteConfig = await createVite(
|
||||
{
|
||||
mode: 'development',
|
||||
server: { host },
|
||||
server: { host, headers },
|
||||
optimizeDeps: {
|
||||
include: rendererClientEntries,
|
||||
},
|
||||
|
|
|
@ -24,10 +24,10 @@ export default async function preview(
|
|||
});
|
||||
await runHookConfigDone({ settings: settings, logging: logging });
|
||||
const host = getResolvedHostForHttpServer(settings.config.server.host);
|
||||
const { port } = settings.config.server;
|
||||
const { port, headers } = settings.config.server;
|
||||
|
||||
if (settings.config.output === 'static') {
|
||||
const server = await createStaticPreviewServer(settings, { logging, host, port });
|
||||
const server = await createStaticPreviewServer(settings, { logging, host, port, headers });
|
||||
return server;
|
||||
}
|
||||
if (!settings.adapter) {
|
||||
|
|
|
@ -3,7 +3,7 @@ import type { AstroSettings } from '../../@types/astro';
|
|||
import type { LogOptions } from '../logger/core';
|
||||
|
||||
import fs from 'fs';
|
||||
import http from 'http';
|
||||
import http, { OutgoingHttpHeaders } from 'http';
|
||||
import { performance } from 'perf_hooks';
|
||||
import sirv from 'sirv';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
@ -24,7 +24,17 @@ const HAS_FILE_EXTENSION_REGEXP = /^.*\.[^\\]+$/;
|
|||
/** The primary dev action */
|
||||
export default async function createStaticPreviewServer(
|
||||
settings: AstroSettings,
|
||||
{ logging, host, port }: { logging: LogOptions; host: string | undefined; port: number }
|
||||
{
|
||||
logging,
|
||||
host,
|
||||
port,
|
||||
headers,
|
||||
}: {
|
||||
logging: LogOptions;
|
||||
host: string | undefined;
|
||||
port: number;
|
||||
headers: OutgoingHttpHeaders | undefined;
|
||||
}
|
||||
): Promise<PreviewServer> {
|
||||
const startServerTime = performance.now();
|
||||
const defaultOrigin = 'http://localhost';
|
||||
|
@ -35,6 +45,11 @@ export default async function createStaticPreviewServer(
|
|||
dev: true,
|
||||
etag: true,
|
||||
maxAge: 0,
|
||||
setHeaders: (res, pathname, stats) => {
|
||||
for (const [name, value] of Object.entries(headers ?? {})) {
|
||||
if (value) res.setHeader(name, value);
|
||||
}
|
||||
},
|
||||
});
|
||||
// Create the preview server, send static files out of the `dist/` directory.
|
||||
const server = http.createServer((req, res) => {
|
||||
|
|
|
@ -144,6 +144,11 @@ export async function handleRoute(
|
|||
clientAddress: buildingToSSR ? req.socket.remoteAddress : undefined,
|
||||
});
|
||||
|
||||
// Set user specified headers to response object.
|
||||
for (const [name, value] of Object.entries(config.server.headers ?? {})) {
|
||||
if (value) res.setHeader(name, value);
|
||||
}
|
||||
|
||||
// attempt to get static paths
|
||||
// if this fails, we have a bad URL match!
|
||||
const paramsAndPropsRes = await getParamsAndProps({
|
||||
|
|
39
packages/astro/test/astro-dev-headers.test.js
Normal file
39
packages/astro/test/astro-dev-headers.test.js
Normal file
|
@ -0,0 +1,39 @@
|
|||
import { expect } from 'chai';
|
||||
import { loadFixture } from './test-utils.js';
|
||||
|
||||
describe('Astro dev headers', () => {
|
||||
let fixture;
|
||||
let devServer;
|
||||
const headers = {
|
||||
'x-astro': 'test',
|
||||
};
|
||||
|
||||
before(async () => {
|
||||
fixture = await loadFixture({
|
||||
root: './fixtures/astro-dev-headers/',
|
||||
server: {
|
||||
headers,
|
||||
},
|
||||
});
|
||||
await fixture.build();
|
||||
devServer = await fixture.startDevServer();
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await devServer.stop();
|
||||
});
|
||||
|
||||
describe('dev', () => {
|
||||
it('returns custom headers for valid URLs', async () => {
|
||||
const result = await fixture.fetch('/');
|
||||
expect(result.status).to.equal(200);
|
||||
expect(Object.fromEntries(result.headers)).to.include(headers);
|
||||
});
|
||||
|
||||
it('does not return custom headers for invalid URLs', async () => {
|
||||
const result = await fixture.fetch('/bad-url');
|
||||
expect(result.status).to.equal(404);
|
||||
expect(Object.fromEntries(result.headers)).not.to.include(headers);
|
||||
});
|
||||
});
|
||||
});
|
40
packages/astro/test/astro-preview-headers.test.js
Normal file
40
packages/astro/test/astro-preview-headers.test.js
Normal file
|
@ -0,0 +1,40 @@
|
|||
import { expect } from 'chai';
|
||||
import { loadFixture } from './test-utils.js';
|
||||
|
||||
describe('Astro preview headers', () => {
|
||||
let fixture;
|
||||
let previewServer;
|
||||
const headers = {
|
||||
astro: 'test',
|
||||
};
|
||||
|
||||
before(async () => {
|
||||
fixture = await loadFixture({
|
||||
root: './fixtures/astro-preview-headers/',
|
||||
server: {
|
||||
headers,
|
||||
},
|
||||
});
|
||||
await fixture.build();
|
||||
previewServer = await fixture.preview();
|
||||
});
|
||||
|
||||
// important: close preview server (free up port and connection)
|
||||
after(async () => {
|
||||
await previewServer.stop();
|
||||
});
|
||||
|
||||
describe('preview', () => {
|
||||
it('returns custom headers for valid URLs', async () => {
|
||||
const result = await fixture.fetch('/');
|
||||
expect(result.status).to.equal(200);
|
||||
expect(Object.fromEntries(result.headers)).to.include(headers);
|
||||
});
|
||||
|
||||
it('does not return custom headers for invalid URLs', async () => {
|
||||
const result = await fixture.fetch('/bad-url');
|
||||
expect(result.status).to.equal(404);
|
||||
expect(Object.fromEntries(result.headers)).not.to.include(headers);
|
||||
});
|
||||
});
|
||||
});
|
8
packages/astro/test/fixtures/astro-dev-headers/package.json
vendored
Normal file
8
packages/astro/test/fixtures/astro-dev-headers/package.json
vendored
Normal file
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"name": "@test/astro-dev-headers",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"astro": "workspace:*"
|
||||
}
|
||||
}
|
8
packages/astro/test/fixtures/astro-dev-headers/src/pages/index.astro
vendored
Normal file
8
packages/astro/test/fixtures/astro-dev-headers/src/pages/index.astro
vendored
Normal file
|
@ -0,0 +1,8 @@
|
|||
<html>
|
||||
<head>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Hello world!</h1>
|
||||
</body>
|
||||
</html>
|
||||
|
8
packages/astro/test/fixtures/astro-preview-headers/package.json
vendored
Normal file
8
packages/astro/test/fixtures/astro-preview-headers/package.json
vendored
Normal file
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"name": "@test/astro-preview-headers",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"astro": "workspace:*"
|
||||
}
|
||||
}
|
8
packages/astro/test/fixtures/astro-preview-headers/src/pages/index.astro
vendored
Normal file
8
packages/astro/test/fixtures/astro-preview-headers/src/pages/index.astro
vendored
Normal file
|
@ -0,0 +1,8 @@
|
|||
<html>
|
||||
<head>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Hello world!</h1>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1288,6 +1288,12 @@ importers:
|
|||
dependencies:
|
||||
astro: link:../../..
|
||||
|
||||
packages/astro/test/fixtures/astro-dev-headers:
|
||||
specifiers:
|
||||
astro: workspace:*
|
||||
dependencies:
|
||||
astro: link:../../..
|
||||
|
||||
packages/astro/test/fixtures/astro-directives:
|
||||
specifiers:
|
||||
astro: workspace:*
|
||||
|
@ -1514,6 +1520,12 @@ importers:
|
|||
react: 18.2.0
|
||||
react-dom: 18.2.0_react@18.2.0
|
||||
|
||||
packages/astro/test/fixtures/astro-preview-headers:
|
||||
specifiers:
|
||||
astro: workspace:*
|
||||
dependencies:
|
||||
astro: link:../../..
|
||||
|
||||
packages/astro/test/fixtures/astro-public:
|
||||
specifiers:
|
||||
astro: workspace:*
|
||||
|
|
Loading…
Reference in a new issue