Add Phase 0 spike: end-to-end transport verified
Bare PHP behind FrankenPHP plus a Qt/QML host that spawns it. GET /api/ping publishes a Mercure event; the QML window receives it back over the SSE stream. Findings (Caddy directive ordering, Mercure transport scalar, PR_SET_PDEATHSIG for child cleanup, PHP 8.5 curl_close deprecation, port collision with system FrankenPHP, pure-QML SSE viability) are recorded in spike/README.md so Phase 1 starts from a known-good baseline. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
88
spike/README.md
Normal file
88
spike/README.md
Normal file
@@ -0,0 +1,88 @@
|
||||
# Phase 0 — Throwaway transport spike
|
||||
|
||||
The smallest thing that proves the php-qml architecture's transport works end-to-end on Linux.
|
||||
|
||||
A Qt/QML host spawns a bundled FrankenPHP process, hits `GET /api/ping` over local HTTP, and subscribes to Mercure SSE. Clicking "Ping" makes a request whose handler also publishes a Mercure event — that event then arrives back in the QML window via the SSE stream.
|
||||
|
||||
Lifetime of this directory is short: it gets removed when Phase 1's framework skeleton supersedes it. See [PLAN.md §13](../PLAN.md#13-roadmap-to-poc).
|
||||
|
||||
## Run it
|
||||
|
||||
Prerequisites on the dev machine:
|
||||
|
||||
- Linux (tested on openSUSE Tumbleweed)
|
||||
- `cmake`, `qt6-base-devel`, `qt6-declarative-devel`, `qt6-quickcontrols2-devel`, `qt6-tools-devel`, `gcc-c++`
|
||||
- `curl` (for the FrankenPHP download)
|
||||
|
||||
Fetch FrankenPHP if `bin/frankenphp` is missing:
|
||||
|
||||
```bash
|
||||
curl -fsSL -o spike/bin/frankenphp \
|
||||
https://github.com/php/frankenphp/releases/download/v1.12.2/frankenphp-linux-x86_64
|
||||
chmod +x spike/bin/frankenphp
|
||||
```
|
||||
|
||||
Then from the repo root:
|
||||
|
||||
```bash
|
||||
./spike/run.sh
|
||||
```
|
||||
|
||||
The script builds the Qt binary on first run (into `spike/build/`) and launches it. The Qt host spawns FrankenPHP as a child; closing the window cleans both up.
|
||||
|
||||
## What you should see
|
||||
|
||||
A window with two status dots — backend and Mercure — that flip green within a second of launch. Clicking **Ping** appends two lines to the event log: an outgoing `→ ping` line with the response payload and timing, and an incoming `← event …` line carrying the Mercure-pushed copy of the same payload. The two arrive within ~50 ms of each other on a quiet machine.
|
||||
|
||||
Killing the child manually from another terminal — `pkill -f spike/bin/frankenphp` — flips the status dots and the QML logs `· mercure → disconnected`. Re-running `run.sh` reconnects.
|
||||
|
||||
## What's hardcoded
|
||||
|
||||
| Thing | Value |
|
||||
| --- | --- |
|
||||
| Backend URL | `http://127.0.0.1:8765` |
|
||||
| Mercure topic | `app://ping` |
|
||||
| Mercure JWT (publisher + subscriber) | `!ChangeThisDevKey!` (anonymous subscribers are also enabled) |
|
||||
| FrankenPHP version | `v1.12.2` (pinned in this README; `bin/frankenphp` is gitignored) |
|
||||
| Port | `8765` (chosen to avoid the system FrankenPHP that already binds 8080 on this box) |
|
||||
|
||||
No auth on `/api/ping`. No `Last-Event-ID` resume. No optimistic updates. All of those land in Phase 1+.
|
||||
|
||||
## Project layout
|
||||
|
||||
```text
|
||||
spike/
|
||||
Caddyfile # FrankenPHP / Caddy / Mercure config
|
||||
run.sh # build + run
|
||||
bin/frankenphp # downloaded static binary (gitignored)
|
||||
php/
|
||||
index.php # GET /api/ping → JSON pong + Mercure publish
|
||||
qt/
|
||||
CMakeLists.txt
|
||||
main.cpp # Qt host: spawns frankenphp, installs SIGTERM handler, prctl(PR_SET_PDEATHSIG)
|
||||
Main.qml # window UI: status dots, Ping button, event log
|
||||
Mercure.qml # pure-QML SSE client over XMLHttpRequest
|
||||
```
|
||||
|
||||
## Findings worth carrying into Phase 1
|
||||
|
||||
- **The `mercure` Caddy directive is site-level, not global.** It goes inside the site block alongside `php_server`, not in the `{}` global block.
|
||||
- **The `transport` directive is now scalar, not URL.** `transport_url local://local` is deprecated in FrankenPHP 1.12; use `transport local`.
|
||||
- **A default Mercure config requires a transport.** Without one, FrankenPHP falls back to `bolt` and demands a database file, which fails with `invalid transport: timeout` on first boot. For dev / spikes, `transport local` (in-memory) is the right choice.
|
||||
- **Subprocess cleanup needs both belt and suspenders.** Qt's `aboutToQuit` only fires for graceful exits; for `SIGTERM` we need an explicit signal handler that calls `QCoreApplication::quit()`. As a Linux safety net, `prctl(PR_SET_PDEATHSIG, SIGTERM)` in the child via `QProcess::setChildProcessModifier()` ensures the kernel reaps the child even if the parent crashes.
|
||||
- **PHP 8.5 deprecates `curl_close()`.** It's been a no-op since 8.0; spike code now relies on the variable going out of scope. Worth scrubbing for in Phase 1's PHP code style rules.
|
||||
- **The system can have a pre-existing FrankenPHP on `:8080`.** Hardcoded port choice for the spike is `:8765`; for the framework proper, pick the port at runtime per session (§3, *Startup*) or use a unix socket on Linux/macOS.
|
||||
- **Pure-QML SSE works.** `XMLHttpRequest` in QML 6.11 does deliver partial responses during `readyState === LOADING`, so `Mercure.qml` parses the stream without a C++ helper. The C++ `MercureClient` planned in PLAN.md §7 is still the right call for Phase 1 (reconnect, `Last-Event-ID`, backpressure), but it's not blocked on a Qt limitation.
|
||||
- **JWT minting in plain PHP is ten lines.** Symfony's `mercure-bundle` is convenience, not necessity — useful to know for the Phase 1 SessionAuthenticator.
|
||||
|
||||
## Out of scope for this spike
|
||||
|
||||
(All deferred to Phase 1+, see [PLAN.md §13](../PLAN.md#13-roadmap-to-poc).)
|
||||
|
||||
- Symfony, Doctrine, any framework
|
||||
- Per-session secret, real auth
|
||||
- `Last-Event-ID` resume / reconnect with backoff
|
||||
- Optimistic updates, `pending` role, `connectionState` enum
|
||||
- Single-instance lock, deep links
|
||||
- Packaging, auto-update, code signing
|
||||
- Tests (manual run only)
|
||||
Reference in New Issue
Block a user