Skip to content

Commit

Permalink
fix: support HTTP/2 in astro dev (#11284)
Browse files Browse the repository at this point in the history
* wip

* chore: add tests

* Add changeset

* Add comments
  • Loading branch information
ascorbic authored Jun 19, 2024
1 parent cb4d078 commit f4b029b
Show file tree
Hide file tree
Showing 13 changed files with 194 additions and 12 deletions.
7 changes: 7 additions & 0 deletions .changeset/six-fans-kiss.md
Original file line number Diff line number Diff line change
@@ -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.
1 change: 1 addition & 0 deletions packages/astro/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
4 changes: 0 additions & 4 deletions packages/astro/src/core/create-vite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
8 changes: 7 additions & 1 deletion packages/astro/src/core/request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
4 changes: 3 additions & 1 deletion packages/astro/src/vite-plugin-astro-server/request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
37 changes: 37 additions & 0 deletions packages/astro/test/astro-dev-http2.test.js
Original file line number Diff line number Diff line change
@@ -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');
});
});
});
24 changes: 24 additions & 0 deletions packages/astro/test/fixtures/astro-dev-http2/.cert/cert.pem
Original file line number Diff line number Diff line change
@@ -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-----
28 changes: 28 additions & 0 deletions packages/astro/test/fixtures/astro-dev-http2/.cert/key.pem
Original file line number Diff line number Diff line change
@@ -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-----
26 changes: 26 additions & 0 deletions packages/astro/test/fixtures/astro-dev-http2/astro.config.ts
Original file line number Diff line number Diff line change
@@ -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();
});
}
},
],
},
});
8 changes: 8 additions & 0 deletions packages/astro/test/fixtures/astro-dev-http2/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"name": "@test/astro-dev-http2",
"version": "0.0.1",
"private": true,
"dependencies": {
"astro": "workspace:*"
}
}
17 changes: 17 additions & 0 deletions packages/astro/test/fixtures/astro-dev-http2/src/pages/index.astro
Original file line number Diff line number Diff line change
@@ -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>
23 changes: 22 additions & 1 deletion packages/astro/test/test-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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);
Expand Down
19 changes: 14 additions & 5 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit f4b029b

Please sign in to comment.