Conversation
|
Does this actually speed things up? Do we have any numbers that say 6 workers are slower than the alternative? Plenty of production WordPress sites run on 2 workers or 4 workers. If we were to go with this, we'd need to:
|
|
Hey @adamziel, appreciate the quick feedback. Let me address the concerns. On whether this actually speeds things up: That's exactly what we want to find out. @brandonpayton asked me to work on this exploration. This PR isn't a proposal to ship HTTP/2 as is, but rather the infrastructure to run benchmarks and see if there's a measurable difference. The hypothesis is that HTTP/2 multiplexing eliminates the 6-connection-per-domain bottleneck in HTTP/1.1, which should matter for WordPress pages that trigger many sub-requests. You're right that plenty of production WordPress sites run on 2-4 workers. But as a development tool, our job is to cater to the wild edge cases as much as the median. Some WP installs easily generate lots of parallel asset requests and we need to handle those well too. Running on a beefy machine, the tool should consume as much as resources available at its disposal to deliver results as fast as possible. HTTP/1.1 caps us at 6 concurrent connections per domain, which puts a hard ceiling on how many workers can actually be utilized in parallel. HTTP/2 multiplexing removes that ceiling entirely, opening the door to exploring higher worker counts where the workload demands it. On the design decisions: I want to push back a little on the framing that these are "LLM design decisions." I collaborated with AI as a tool, but every design choice was either mine or something I evaluated and approved. It was iterative, not a one-shot generation that I rubber-stamped. And its still just a draft PR :) On the specific points:
On CI/cross-platform cert generation: For TLS, the primary path I want to rely on is Bottom line: This is meant to answer the question "does HTTP/2 help?" with data. HTTP/2 is functional at this point in this PR. Next steps would be to run benchmarks and then we can discuss the implementation details further. |
Add tls.ts with: - Self-signed cert generation via OpenSSL (with SAN for localhost/127.0.0.1) - mkcert detection (checks installed + CA root trusted) - mkcert cert generation for locally-trusted HTTPS - Certificate resolver with priority chain: user-supplied > mkcert > self-signed Made-with: Cursor
- Add --http2, --ssl-cert, --ssl-key CLI flags to both `server` and `start` commands - Add --min-workers, --max-workers CLI flags (wiring comes in a later commit) - Branch start-server.ts: --http2 uses http2.createSecureServer(), default stays Express - Extract shared request handler logic to work with both Express and HTTP/2 types - Filter HTTP/2 pseudo-headers (keys starting with :) in parseHeaders() - Use https:// URL scheme when --http2 is active - Wire TLS certificate resolver into the server startup path Made-with: Cursor
Replace the hardcoded targetWorkerCount = 6 with a calculation based on available system memory (50% of os.freemem()), clamped between --min-workers (default 2) and --max-workers (default 12). This adapts worker count to the device rather than assuming 6 is always appropriate. Made-with: Cursor
The WASM SAPI hardcodes proto_num=1000 (HTTP/1.0) and never sets $_SERVER['SERVER_PROTOCOL']. Fix this by: - Adding optional protocolVersion field to PHPRequest - Populating it from req.httpVersion in the CLI server - Setting SERVER_PROTOCOL in prepare_$_SERVER_superglobal (defaults to HTTP/1.1) The server_array_entries in the C SAPI are registered last, so $_SERVER overrides from JS take precedence. No C/WASM changes needed. Made-with: Cursor
Made-with: Cursor
The http2, ssl-cert, ssl-key, min-workers, and max-workers options were defined in yargs and used at runtime but missing from the RunCLIArgs TypeScript interface, causing typecheck failures. Made-with: Cursor
Http2SecureServer doesn't have closeAllConnections() (it extends tls.Server, not http.Server). Without force-closing the long-lived HTTP/2 sessions, server.close() hangs forever waiting for them to end naturally. Polyfill closeAllConnections by tracking active sessions and destroying them on shutdown. Made-with: Cursor
Pipe stdio for openssl and mkcert subprocesses so their banners don't clutter the terminal. Condense log messages to a single "TLS: ..." line for all three cert resolution paths. A DEBUG_CERT_GENERATION flag allows surfacing the subprocess output when needed. Made-with: Cursor
|
Thank you @ashfame for elaborating! I was a bit worried and I see how I mistook this exploration for a production proposal. I'm glad that you've worked so diligently in here. Lovely work and I'm very curious what we'll find out here. |
Made-with: Cursor
Switch worker pool sizing to a single explicit --workers option and keep startup behavior deterministic. Retain free-memory checks as advisory warnings only, so the CLI continues with the user-requested worker count. Made-with: Cursor
Motivation for the change, related issues
The Playground CLI currently serves WordPress over HTTP/1.1, which limits browser connections to 6 parallel TCP connections per origin. HTTP/2 multiplexes all requests over a single TLS connection, eliminating head-of-line blocking and improving page load performance — especially for asset-heavy WordPress pages.
Implementation details
TLS certificate provisioning (
tls.ts)--ssl-cert/--ssl-key) > mkcert (if installed with trusted CA) > self-signed fallback with tip messagemkcert -CAROOTand CA root validationHTTP/2 server (
start-server.ts)--http2flag; default HTTP/1.1 Express path is unchangedhttp2.createSecureServer()withallowHTTP1: truefor backward compatibilityRequestListenerabstraction works with both Express and HTTP/2 request/response types:method,:path,:scheme,:authority) filtered out inparseHeaders()so they don't leak into PHP's$_SERVERSERVER_PROTOCOLplumbingreq.httpVersion(set by Node.js for both HTTP/1.1 and HTTP/2) is read and passed through asprotocolVersiononPHPRequestPHPRequestHandlerforwards it as$_SERVER['SERVER_PROTOCOL']to PHPHTTP/2.0for h2 requests andHTTP/1.1for h1 requests — no C SAPI changes neededWorker pool sizing (
run-cli.ts)--workers(default 6). The CLI spawns exactly that many PHP worker threads; it does not auto-scale based on memory.os.freemem()as a budget. If the chosen worker count looks high relative to free RAM, the CLI logs a warning and continues with the requested count.New CLI flags
--http2— enable HTTP/2 with TLS--ssl-cert/--ssl-key— supply custom TLS certificates--workers— number of PHP worker threads (default 6)Testing Instructions (or ideally a Blueprint)
Start the CLI with HTTP/2 enabled:
1. Protocol negotiation (Chrome DevTools)
Open the Network tab, enable the "Protocol" column. Load
https://localhost:9400/. Requests should showh2.2. Multiplexing
In the Network tab Waterfall, requests should fire concurrently over a single connection — no 6-connection staircase pattern.
3. HTTP/1.1 fallback
Should return a valid response (confirms
allowHTTP1: trueworks).4. HTTP/2 via curl
Look for
ALPN: server accepted h2and< HTTP/2 200.5.
$_SERVER['SERVER_PROTOCOL']correctnessCreate a blueprint (
test-bp.json):{ "steps": [ { "step": "writeFile", "path": "/wordpress/server-info.php", "data": "<?php header('Content-Type: application/json'); echo json_encode(['SERVER_PROTOCOL' => $_SERVER['SERVER_PROTOCOL'], 'HTTP_headers' => array_filter($_SERVER, fn($k) => str_starts_with($k, 'HTTP_'), ARRAY_FILTER_USE_KEY)], JSON_PRETTY_PRINT);" } ] }6. No pseudo-header leakage
curl -ks --http2 https://localhost:9400/server-info.php | jq .HTTP_headersShould only contain normal headers (
HTTP_HOST,HTTP_ACCEPT, etc.) — noHTTP_:METHOD,HTTP_:PATH, etc.7. TLS cert fallback chain
--http2 --ssl-cert=... --ssl-key=...→ logs "TLS: using provided certificates"--http2and mkcert installed → logs "TLS: using mkcert (locally-trusted)"--http2and no mkcert → logs "TLS: using self-signed certificate (install mkcert for warning-free HTTPS)"; browser shows cert warningResults
All 7 tests verified and passing.