0
Fork 0
mirror of https://github.com/withastro/astro.git synced 2024-12-16 21:46:22 -05:00

fix: support HTTP/2 in astro dev (#11284)

* wip

* chore: add tests

* Add changeset

* Add comments
This commit is contained in:
Matt Kane 2024-06-19 12:42:50 +01:00 committed by GitHub
parent cb4d07819f
commit f4b029b082
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 194 additions and 12 deletions

View file

@ -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.

View file

@ -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": {

View file

@ -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,

View file

@ -50,7 +50,13 @@ export function createRequest({
? undefined
: headers instanceof Headers
? headers
: new Headers(Object.entries(headers as Record<string, any>));
: 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<string, any>).filter(([name]) => !name.startsWith(':'))
);
if (typeof url === 'string') url = new URL(url);

View file

@ -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;

View file

@ -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');
});
});
});

View file

@ -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-----

View file

@ -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-----

View file

@ -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();
});
}
},
],
},
});

View file

@ -0,0 +1,8 @@
{
"name": "@test/astro-dev-http2",
"version": "0.0.1",
"private": true,
"dependencies": {
"astro": "workspace:*"
}
}

View file

@ -0,0 +1,17 @@
---
const url = Astro.request.url
const httpVersion = Astro.request.headers.get('x-http-version')
export const prerender = false
---
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
</head>
<body>
<main>{url}</main>
<p>{httpVersion}</p>
</body>
</html>

View file

@ -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);

View file

@ -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: {}