From f4b029b08264268c68fc81ea25b264e81f47e683 Mon Sep 17 00:00:00 2001 From: Matt Kane Date: Wed, 19 Jun 2024 12:42:50 +0100 Subject: [PATCH] fix: support HTTP/2 in `astro dev` (#11284) * wip * chore: add tests * Add changeset * Add comments --- .changeset/six-fans-kiss.md | 7 ++++ packages/astro/package.json | 1 + packages/astro/src/core/create-vite.ts | 4 -- packages/astro/src/core/request.ts | 8 +++- .../src/vite-plugin-astro-server/request.ts | 4 +- packages/astro/test/astro-dev-http2.test.js | 37 +++++++++++++++++++ .../fixtures/astro-dev-http2/.cert/cert.pem | 24 ++++++++++++ .../fixtures/astro-dev-http2/.cert/key.pem | 28 ++++++++++++++ .../fixtures/astro-dev-http2/astro.config.ts | 26 +++++++++++++ .../fixtures/astro-dev-http2/package.json | 8 ++++ .../astro-dev-http2/src/pages/index.astro | 17 +++++++++ packages/astro/test/test-utils.js | 23 +++++++++++- pnpm-lock.yaml | 19 +++++++--- 13 files changed, 194 insertions(+), 12 deletions(-) create mode 100644 .changeset/six-fans-kiss.md create mode 100644 packages/astro/test/astro-dev-http2.test.js create mode 100644 packages/astro/test/fixtures/astro-dev-http2/.cert/cert.pem create mode 100644 packages/astro/test/fixtures/astro-dev-http2/.cert/key.pem create mode 100644 packages/astro/test/fixtures/astro-dev-http2/astro.config.ts create mode 100644 packages/astro/test/fixtures/astro-dev-http2/package.json create mode 100644 packages/astro/test/fixtures/astro-dev-http2/src/pages/index.astro diff --git a/.changeset/six-fans-kiss.md b/.changeset/six-fans-kiss.md new file mode 100644 index 0000000000..f80b87b02d --- /dev/null +++ b/.changeset/six-fans-kiss.md @@ -0,0 +1,7 @@ +--- +'astro': patch +--- + +Fixes an issue that would break `Astro.request.url` and `Astro.request.headers` in `astro dev` if HTTP/2 was enabled. + +HTTP/2 is now enabled by default in `astro dev` if `https` is configured in the Vite config. diff --git a/packages/astro/package.json b/packages/astro/package.json index 7f043f2969..aaa9034f9e 100644 --- a/packages/astro/package.json +++ b/packages/astro/package.json @@ -229,6 +229,7 @@ "rollup": "^4.18.0", "sass": "^1.77.5", "srcset-parse": "^1.1.0", + "undici": "^6.19.2", "unified": "^11.0.4" }, "engines": { diff --git a/packages/astro/src/core/create-vite.ts b/packages/astro/src/core/create-vite.ts index 940bc391c3..1627dde2cc 100644 --- a/packages/astro/src/core/create-vite.ts +++ b/packages/astro/src/core/create-vite.ts @@ -173,10 +173,6 @@ export async function createVite( process.env.NODE_ENV === 'test' || process.env.NODE_ENV === 'production' ? false : undefined, // disable HMR for test - // handle Vite URLs - proxy: { - // add proxies here - }, watch: { // Prevent watching during the build to speed it up ignored: mode === 'build' ? ['**'] : undefined, diff --git a/packages/astro/src/core/request.ts b/packages/astro/src/core/request.ts index 423f8a965b..241dd66a6c 100644 --- a/packages/astro/src/core/request.ts +++ b/packages/astro/src/core/request.ts @@ -50,7 +50,13 @@ export function createRequest({ ? undefined : headers instanceof Headers ? headers - : new Headers(Object.entries(headers as Record)); + : new Headers( + // Filter out HTTP/2 pseudo-headers. These are internally-generated headers added to all HTTP/2 requests with trusted metadata about the request. + // Examples include `:method`, `:scheme`, `:authority`, and `:path`. + // They are always prefixed with a colon to distinguish them from other headers, and it is an error to add the to a Headers object manually. + // See https://httpwg.org/specs/rfc7540.html#HttpRequest + Object.entries(headers as Record).filter(([name]) => !name.startsWith(':')) + ); if (typeof url === 'string') url = new URL(url); diff --git a/packages/astro/src/vite-plugin-astro-server/request.ts b/packages/astro/src/vite-plugin-astro-server/request.ts index c8b3b4cb85..b231bfde35 100644 --- a/packages/astro/src/vite-plugin-astro-server/request.ts +++ b/packages/astro/src/vite-plugin-astro-server/request.ts @@ -25,7 +25,9 @@ export async function handleRequest({ incomingResponse, }: HandleRequest) { const { config, loader } = pipeline; - const origin = `${loader.isHttps() ? 'https' : 'http'}://${incomingRequest.headers.host}`; + const origin = `${loader.isHttps() ? 'https' : 'http'}://${ + incomingRequest.headers[':authority'] ?? incomingRequest.headers.host + }`; const url = new URL(origin + incomingRequest.url); let pathname: string; diff --git a/packages/astro/test/astro-dev-http2.test.js b/packages/astro/test/astro-dev-http2.test.js new file mode 100644 index 0000000000..10521b8cb0 --- /dev/null +++ b/packages/astro/test/astro-dev-http2.test.js @@ -0,0 +1,37 @@ +import assert from 'node:assert/strict'; +import { after, before, describe, it } from 'node:test'; +import * as cheerio from 'cheerio'; +import { loadFixture } from './test-utils.js'; + +describe('Astro HTTP/2 support', () => { + let fixture; + let devServer; + + before(async () => { + fixture = await loadFixture({ + root: './fixtures/astro-dev-http2/', + }); + devServer = await fixture.startDevServer(); + }); + + after(async () => { + await devServer.stop(); + }); + + describe('dev', () => { + it('returns custom headers for valid URLs', async () => { + const result = await fixture.fetch('/'); + assert.equal(result.status, 200); + const html = await result.text(); + console.log(result.headers); + const $ = cheerio.load(html); + const urlString = $('main').text(); + assert.equal(Boolean(urlString), true); + const url = new URL(urlString); + // Not asserting host because of all the ways localhost can be represented + assert.equal(url.protocol, 'https:'); + assert.equal(url.port, '4321'); + assert.equal($('p').text(), '2.0'); + }); + }); +}); diff --git a/packages/astro/test/fixtures/astro-dev-http2/.cert/cert.pem b/packages/astro/test/fixtures/astro-dev-http2/.cert/cert.pem new file mode 100644 index 0000000000..d82edd5feb --- /dev/null +++ b/packages/astro/test/fixtures/astro-dev-http2/.cert/cert.pem @@ -0,0 +1,24 @@ +-----BEGIN CERTIFICATE----- +MIIEHDCCAoSgAwIBAgIRAIWPzjvpgZGzQqe1TFcdGmAwDQYJKoZIhvcNAQELBQAw +ZTEeMBwGA1UEChMVbWtjZXJ0IGRldmVsb3BtZW50IENBMR0wGwYDVQQLDBRhbGV4 +QERFU0tUT1AtMFM5OFUzMDEkMCIGA1UEAwwbbWtjZXJ0IGFsZXhAREVTS1RPUC0w +Uzk4VTMwMB4XDTI0MDIyNjAwNDkyOVoXDTI2MDUyNTIzNDkyOVowSDEnMCUGA1UE +ChMebWtjZXJ0IGRldmVsb3BtZW50IGNlcnRpZmljYXRlMR0wGwYDVQQLDBRhbGV4 +QERFU0tUT1AtMFM5OFUzMDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB +AMRj8/YGXRRWkpIxdaWbL+v2RZoI7iGOyJliC2Iudag5/irpiBAFgZAqpjSb1i1E +OKkXRQlCx21jQWe/jEFGqEPIqjeLPrXHATKU+6prOH2FV2qF7PDd0gi1gkR0cxX7 +dUA9kUeqm1HHkogUuhRjg5uCklyCraN49yz6QU6U7uiTo4ZM9mjfig0EfG2W1DBp +G0bKkEhgkSKw3v1mvGVYN5yAv6unLjDVJGwLKqTTpDpsahG47h+ZPHj7wjSOQiDB +tXR+HNLJdSe59+GQ8D5/M7hRG6rZ+8GzaNjQWRl8BK6Ls0k1qtMgcEFeNDLEWTj1 +16yNmd4/IX4irMmSA+F7PgUCAwEAAaNkMGIwDgYDVR0PAQH/BAQDAgWgMBMGA1Ud +JQQMMAoGCCsGAQUFBwMBMB8GA1UdIwQYMBaAFE+Vk1SZFKjFDIsieTVT/860OBs6 +MBoGA1UdEQQTMBGCCWxvY2FsaG9zdIcEfwAAATANBgkqhkiG9w0BAQsFAAOCAYEA +szcc7pdwdYu3uIN8f8LHFABDhxiPDLMqmyEJXym5z8c44Mtl0mfGnKs0uIzl/XtL +F1aLH8yHubZ1LJkIczAypcryekmk+VzTsNdv1aKelhsZ9QJUxg/NsrMXe5DZ9Eeu +KxlJBJKo+oRpDsxRYo1l5FvIljcVvOUTaKR3UtY6FU2xdDMNMtoJDbaJCPAg143H +ZnSa7xEv7fGDTcn9oKFc1fc1BnCy4qCHkxF8pIeXbXEZ/q1fqNNtp87/PigPT7YO +ppcYvEsj4Y+6yuDfIrWAZNcbtiOfFUyPXy1KN+/VxZhcZ/MuAbl4EiESDFbE5j8U +whIHlRXUY6B09PL9NVNGyjNDH3NMQkSKVFA2KVeaFeDIROjPeMkrKY56lWVpEiru +HVLuFVpM27uJEKgSNeAYttfOEvsvr20Otmt29Uu59qfX+w0crQUEElcUJB4DgRH+ +NVoYZLYMm88B5aVUsmwkkrJJrAJ+M6UnzIhdN2alJxXojW38jDWzn+dWbc0a2hIB +-----END CERTIFICATE----- diff --git a/packages/astro/test/fixtures/astro-dev-http2/.cert/key.pem b/packages/astro/test/fixtures/astro-dev-http2/.cert/key.pem new file mode 100644 index 0000000000..347d13537e --- /dev/null +++ b/packages/astro/test/fixtures/astro-dev-http2/.cert/key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDEY/P2Bl0UVpKS +MXWlmy/r9kWaCO4hjsiZYgtiLnWoOf4q6YgQBYGQKqY0m9YtRDipF0UJQsdtY0Fn +v4xBRqhDyKo3iz61xwEylPuqazh9hVdqhezw3dIItYJEdHMV+3VAPZFHqptRx5KI +FLoUY4ObgpJcgq2jePcs+kFOlO7ok6OGTPZo34oNBHxtltQwaRtGypBIYJEisN79 +ZrxlWDecgL+rpy4w1SRsCyqk06Q6bGoRuO4fmTx4+8I0jkIgwbV0fhzSyXUnuffh +kPA+fzO4URuq2fvBs2jY0FkZfASui7NJNarTIHBBXjQyxFk49desjZnePyF+IqzJ +kgPhez4FAgMBAAECggEBAL+70d89ATys9LYT4YcIBnY5XmRvGYXbr47IANMfBrFx +xOpCSxtRNNf6O4AbMLPK6gJzfGv5LVhnUeCnSpgkEnzy+PP3VwcDPfETMMyFl4Y8 +W0bdb6EM/1SPWJnaks1ATY2lTiQItVDXJgEDM1Raf4+gn6H/1uRFYhQgUwgUMVcP +rb/6qk+vFJXGuyx1R1DoIoqz+8iBfcsddUplx8ulJv74/Lgn5B/5sZ6I6cnW8lva +FskuY1DL2RUywuV2J2fnFGNuGPPvc5elE0ORYLjlUGDMHQq+a9UxXsfCC4gHoks1 +P4vYZjzOVGRA3o+F8khuZLeCebMsKnoyzmkeRGejlzUCgYEA8d+TtwU3qlFUl3sa +TGJm+tD5sgLaZHDssMhkkTtTVzYIllFSyvT8UI7/9ZwYinq0JOnGN0Cb7TsH4AGQ +jQzHfiudibvODK4HL3rVkiOwj+kdjH+oTMlCTGsCj9uZhEajzKpXgpSlYkpqhDR2 +zCdMdFXCE+SpJCJaTI+jcbup6P8CgYEAz9xUBQIk8CdySkgIB0gmpIgtvS1EwYod +YvYu+coQ1lEtALetDdbx5VfasWd1A18sIFlkfZZMKr5+1QXVuKOFhx2n49XIev/6 +t+Hgx8aToIpB0tz/3CTea5HGK8FvX6t5QKDL6XqDrRGO1FVdMSWl9WLfmpTynJdj +sNHr7JFwNPsCgYAz5OE/ekoYK7z3hzz8OHyZwa5hCAWtWSEfSM9y7YSTCI/NGIOn +8eoUqqm2G5iUVYFDDjkt75nEy06EPDG0YZKHunnhbD7oL4pxIGykHy4poj1pwJXu +a5vi4264SMhmPfW02rNN2/Cj5w11cgAvCxt3NlMei4fSreAr3wGVTEtHJwKBgEBw +QIfQ81yUDgVjMUH4pyoooW1dRExvoc6VHVkIwJGAVuA7EOYSdakwxDZtKURjU82v +iMy6NGCn76/ggDIeV33cvriOBPnEs5gf6Uxljkydr+xL4PIBaAaXCYV1ES7qfMuB +TdXSylFz+QBwelSLJFjfTwygElpjQF+HpIkRSWTTAoGBAJIqeQ4edg4weut00+32 +A8rQEpiz5SByPYNUPCI5BReKd+/Dw2QdXnfJNVg7/NFfuUdvPVkmptsisYdfWhlp +y+KFAwdbgDUU+ruPuv5fHU4sA85Uuxazr/YXZIB6wmsQKt8cNezqNhjY6UovTOdZ +7qnkPUO8VGcnrRZiav8WIevg +-----END PRIVATE KEY----- diff --git a/packages/astro/test/fixtures/astro-dev-http2/astro.config.ts b/packages/astro/test/fixtures/astro-dev-http2/astro.config.ts new file mode 100644 index 0000000000..b02d07ce5d --- /dev/null +++ b/packages/astro/test/fixtures/astro-dev-http2/astro.config.ts @@ -0,0 +1,26 @@ +import { defineConfig } from "astro/config"; +import { readFileSync } from "fs"; +// https://astro.build/config +export default defineConfig({ + output: "hybrid", + vite: { + server: { + https: { + key: readFileSync(new URL(".cert/key.pem", import.meta.url)), + cert: readFileSync(new URL(".cert/cert.pem", import.meta.url)), + }, + }, + plugins: [ + { + name: 'http-version-plugin', + // This plugin allows tests to track the version of HTTP used in the request + configureServer: (server) => { + server.middlewares.use((req, res, next) => { + req.headers['x-http-version'] = req.httpVersion; + next(); + }); + } + }, + ], + }, +}); diff --git a/packages/astro/test/fixtures/astro-dev-http2/package.json b/packages/astro/test/fixtures/astro-dev-http2/package.json new file mode 100644 index 0000000000..7691514c32 --- /dev/null +++ b/packages/astro/test/fixtures/astro-dev-http2/package.json @@ -0,0 +1,8 @@ +{ + "name": "@test/astro-dev-http2", + "version": "0.0.1", + "private": true, + "dependencies": { + "astro": "workspace:*" + } +} \ No newline at end of file diff --git a/packages/astro/test/fixtures/astro-dev-http2/src/pages/index.astro b/packages/astro/test/fixtures/astro-dev-http2/src/pages/index.astro new file mode 100644 index 0000000000..f8d6059314 --- /dev/null +++ b/packages/astro/test/fixtures/astro-dev-http2/src/pages/index.astro @@ -0,0 +1,17 @@ +--- +const url = Astro.request.url +const httpVersion = Astro.request.headers.get('x-http-version') +export const prerender = false +--- + + + + + + + + +
{url}
+

{httpVersion}

+ + diff --git a/packages/astro/test/test-utils.js b/packages/astro/test/test-utils.js index a4a4e22f85..d68d64e387 100644 --- a/packages/astro/test/test-utils.js +++ b/packages/astro/test/test-utils.js @@ -5,6 +5,7 @@ import { fileURLToPath } from 'node:url'; import { execa } from 'execa'; import fastGlob from 'fast-glob'; import stripAnsi from 'strip-ansi'; +import { Agent } from 'undici'; import { check } from '../dist/cli/check/index.js'; import build from '../dist/core/build/index.js'; import { RESOLVED_SPLIT_MODULE_ID } from '../dist/core/build/plugins/plugin-ssr.js'; @@ -122,8 +123,13 @@ export async function loadFixture(inlineConfig) { // Load the config. const { astroConfig: config } = await resolveConfig(inlineConfig, 'dev'); + const protocol = config.vite?.server?.https ? 'https' : 'http'; + const resolveUrl = (url) => - `http://${config.server.host || 'localhost'}:${config.server.port}${url.replace(/^\/?/, '/')}`; + `${protocol}://${config.server.host || 'localhost'}:${config.server.port}${url.replace( + /^\/?/, + '/' + )}`; // A map of files that have been edited. let fileEdits = new Map(); @@ -171,6 +177,21 @@ export async function loadFixture(inlineConfig) { config, resolveUrl, fetch: async (url, init) => { + if (config.vite?.server?.https) { + init = { + // Use a custom fetch dispatcher. This is an undici option that allows + // us to customize the fetch behavior. We use it here to allow h2. + dispatcher: new Agent({ + connect: { + // We disable cert validation because we're using self-signed certs + rejectUnauthorized: false, + }, + // Enable HTTP/2 support + allowH2: true, + }), + ...init, + }; + } const resolvedUrl = resolveUrl(url); try { return await fetch(resolvedUrl, init); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1b6d6f1b8c..13b9916e7b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -836,6 +836,9 @@ importers: srcset-parse: specifier: ^1.1.0 version: 1.1.0 + undici: + specifier: ^6.19.2 + version: 6.19.2 unified: specifier: ^11.0.4 version: 11.0.4 @@ -2073,6 +2076,12 @@ importers: specifier: workspace:* version: link:../../.. + packages/astro/test/fixtures/astro-dev-http2: + dependencies: + astro: + specifier: workspace:* + version: link:../../.. + packages/astro/test/fixtures/astro-directives: dependencies: astro: @@ -11531,9 +11540,9 @@ packages: undici-types@5.26.5: resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} - undici@6.13.0: - resolution: {integrity: sha512-Q2rtqmZWrbP8nePMq7mOJIN98M0fYvSgV89vwl/BQRT4mDOeY2GXZngfGpcBBhtky3woM7G24wZV3Q304Bv6cw==} - engines: {node: '>=18.0'} + undici@6.19.2: + resolution: {integrity: sha512-JfjKqIauur3Q6biAtHJ564e3bWa8VvT+7cSiOJHFbX4Erv6CLGDpg8z+Fmg/1OI/47RA+GI2QZaF48SSaLvyBA==} + engines: {node: '>=18.17'} unicorn-magic@0.1.0: resolution: {integrity: sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==} @@ -13332,7 +13341,7 @@ snapshots: '@octokit/plugin-paginate-rest': 11.3.0(@octokit/core@6.1.2) '@octokit/plugin-rest-endpoint-methods': 13.2.1(@octokit/core@6.1.2) '@octokit/types': 13.5.0 - undici: 6.13.0 + undici: 6.19.2 '@octokit/auth-action@5.1.1': dependencies: @@ -18705,7 +18714,7 @@ snapshots: undici-types@5.26.5: {} - undici@6.13.0: {} + undici@6.19.2: {} unicorn-magic@0.1.0: {}