-
Notifications
You must be signed in to change notification settings - Fork 12
realm-server: opt-in HTTP/2 for faster local indexing #4787
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
67aea30
3032194
e013c1b
e8411db
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,83 @@ | ||
| #!/usr/bin/env bash | ||
| #MISE description="Provision a local dev cert so realm-server can serve HTTPS/HTTP-2" | ||
| # | ||
| # Generates ~/.local/share/boxel/dev-certs/{localhost.pem, localhost-key.pem} | ||
| # using mkcert. Idempotent — re-runs are a no-op if the cert already exists | ||
| # and isn't near expiry. | ||
| # | ||
| # Why: indexing fans out 80+ federated-search requests per heavy aggregator | ||
| # card render. Chrome's HTTP/1.1 per-origin connection limit (6) serializes | ||
| # them and turns a single cohort render into ~4–5 minutes. HTTP/2 multiplexes | ||
| # them over one connection, dropping the same render to seconds. Browsers | ||
| # only do HTTP/2 over TLS, so realm-server needs a cert. | ||
| # | ||
| # Note: this script does NOT run `mkcert -install`. That step adds mkcert's | ||
| # root CA to the system trust store and requires sudo. It's only needed if | ||
| # you want YOUR manual browser sessions (visiting https://localhost:4203 | ||
| # directly) to trust the cert without a warning. The prerenderer and the | ||
| # test-harness indexer don't need it — they launch Chromium with | ||
| # `--ignore-certificate-errors`. The hint at the end of this script shows | ||
| # how to opt in. | ||
|
|
||
| set -euo pipefail | ||
|
|
||
| CERT_DIR="${BOXEL_DEV_CERT_DIR:-$HOME/.local/share/boxel/dev-certs}" | ||
| CERT_FILE="$CERT_DIR/localhost.pem" | ||
| KEY_FILE="$CERT_DIR/localhost-key.pem" | ||
|
|
||
| # Idempotent skip when the cert already exists and isn't within 7 days of | ||
| # expiry. openssl's `-checkend` returns 0 if the cert is valid for at least | ||
| # the given number of seconds. | ||
| if [ -f "$CERT_FILE" ] && [ -f "$KEY_FILE" ]; then | ||
| if openssl x509 -in "$CERT_FILE" -checkend $((7 * 24 * 60 * 60)) -noout >/dev/null 2>&1; then | ||
| exit 0 | ||
| fi | ||
| echo "[ensure-dev-cert] Existing cert at $CERT_FILE is near expiry; regenerating." | ||
| fi | ||
|
|
||
| if ! command -v mkcert >/dev/null 2>&1; then | ||
| cat >&2 <<'EOF' | ||
| [ensure-dev-cert] mkcert not installed — skipping HTTP/2 cert provisioning. | ||
| The realm-server will boot HTTP/1.1-only; indexing falls back to the slow | ||
| serial path for heavy aggregator cards. This is a soft warning, not an | ||
| error — `mise run dev` continues normally. | ||
|
|
||
| To enable HTTP/2, install mkcert and re-run this task: | ||
| Linux (Debian/Ubuntu): sudo apt install -y mkcert libnss3-tools | ||
| Linux (Fedora/RHEL): sudo dnf install -y mkcert nss-tools | ||
| macOS (Homebrew): brew install mkcert nss | ||
|
|
||
| Then: `mise run infra:ensure-dev-cert` | ||
|
|
||
| See the repo-root README ("HTTP/2 dev access") for the why. | ||
| EOF | ||
| exit 0 | ||
| fi | ||
|
|
||
| mkdir -p "$CERT_DIR" | ||
|
|
||
| echo "[ensure-dev-cert] Generating cert at $CERT_FILE" | ||
| mkcert \ | ||
| -cert-file "$CERT_FILE" \ | ||
| -key-file "$KEY_FILE" \ | ||
| localhost 127.0.0.1 ::1 | ||
|
|
||
| if ! mkcert -CAROOT >/dev/null 2>&1; then | ||
| # mkcert prints CAROOT to stdout; the check above only confirms the binary | ||
| # works. If a CA root doesn't exist yet on this machine, the cert above | ||
| # will still have been generated against a freshly created root in mkcert's | ||
| # own data dir — it just isn't installed into the system trust store. | ||
| : | ||
| fi | ||
|
|
||
| cat <<EOF | ||
| [ensure-dev-cert] Cert provisioned. After your next `mise run dev`, the | ||
| realm-server will accept HTTP/2 on https://localhost:4203 in addition to | ||
| the existing HTTP/1.1 on http://localhost:4201. | ||
|
|
||
| The prerenderer and test-harness indexer always use the HTTP/2 alias | ||
| (automatic). If you also want YOUR browser to trust the cert without a | ||
| warning when visiting https://localhost:4203 directly, run: | ||
|
|
||
| mkcert -install # one-time, requires sudo | ||
| EOF |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -50,6 +50,7 @@ export default class NetworkService extends Service { | |
|
|
||
| private makeVirtualNetwork() { | ||
| let virtualNetwork = new VirtualNetwork(globalThis.fetch); | ||
| this.installH2OriginMappings(virtualNetwork); | ||
| let resolvedBaseRealmURL = new URL( | ||
| withTrailingSlash(config.resolvedBaseRealmURL), | ||
| ); | ||
|
|
@@ -85,6 +86,22 @@ export default class NetworkService extends Service { | |
| return virtualNetwork; | ||
| } | ||
|
|
||
| // HTTP/2 alias re-route: when the prerender harness injects a JSON | ||
| // list of {from, to} origin pairs as window.__realmH2OriginMappings__, | ||
| // route realm-server fetches through the HTTPS/h2 listener(s) so | ||
| // they multiplex over one connection per origin instead of | ||
| // serializing through Chrome's HTTP/1.1 6-per-origin ceiling. | ||
| // Canonical realm URLs (in card data) stay on the http origin — only | ||
| // the wire fetch is rewritten. See the repo-root README's "HTTP/2 dev | ||
| // access" section. | ||
| private installH2OriginMappings(virtualNetwork: VirtualNetwork) { | ||
| let raw = (globalThis as unknown as { __realmH2OriginMappings__?: string }) | ||
| .__realmH2OriginMappings__; | ||
| for (let { from, to } of parseH2OriginMappings(raw)) { | ||
| virtualNetwork.addURLMapping(from, to); | ||
| } | ||
|
Comment on lines
+97
to
+102
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [Claude Code] Fixed in e8411db — extracted |
||
| } | ||
|
|
||
| resetState = () => { | ||
| this.virtualNetwork = this.makeVirtualNetwork(); | ||
| }; | ||
|
|
@@ -99,3 +116,53 @@ declare module '@ember/service' { | |
| function withTrailingSlash(url: string): string { | ||
| return url.endsWith('/') ? url : `${url}/`; | ||
| } | ||
|
|
||
| // Parses the JSON-encoded h2 origin mappings injected by the prerender | ||
| // harness. Each entry must have a parseable `from` URL and a `to` URL | ||
| // whose scheme is `https:` and whose hostname is a loopback alias — | ||
| // this contains the `--ignore-certificate-errors` trust relaxation to | ||
| // local-dev only. A malformed input, an empty global, or a `to` that | ||
| // would redirect realm fetches off-loopback returns nothing. | ||
| export function parseH2OriginMappings( | ||
| raw: string | undefined, | ||
| ): Array<{ from: URL; to: URL }> { | ||
| if (!raw) return []; | ||
| let parsed: unknown; | ||
| try { | ||
| parsed = JSON.parse(raw); | ||
| } catch { | ||
| return []; | ||
| } | ||
| if (!Array.isArray(parsed)) return []; | ||
| let mappings: Array<{ from: URL; to: URL }> = []; | ||
| for (let entry of parsed) { | ||
| if (!entry || typeof entry !== 'object') continue; | ||
| let from = (entry as { from?: unknown }).from; | ||
| let to = (entry as { to?: unknown }).to; | ||
| if (typeof from !== 'string' || typeof to !== 'string') continue; | ||
| let fromUrl: URL; | ||
| let toUrl: URL; | ||
| try { | ||
| fromUrl = new URL(from); | ||
| toUrl = new URL(to); | ||
| } catch { | ||
| continue; | ||
| } | ||
| if (toUrl.protocol !== 'https:') continue; | ||
| if (!isLoopbackHostname(toUrl.hostname)) continue; | ||
| if (fromUrl.origin === toUrl.origin) continue; | ||
| mappings.push({ from: fromUrl, to: toUrl }); | ||
| } | ||
| return mappings; | ||
| } | ||
|
|
||
| function isLoopbackHostname(hostname: string): boolean { | ||
| let h = hostname.toLowerCase(); | ||
| return ( | ||
| h === 'localhost' || | ||
| h.endsWith('.localhost') || | ||
| h === '127.0.0.1' || | ||
| h === '::1' || | ||
| h === '[::1]' | ||
| ); | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.