You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Tracks FUTURES.md item #2. Unblocks the production tier for HttpArena static profiles (static, static-h2, static-h3) — the only remaining blocker before meta.json can flip from "type": "tuned" to "type": "production".
Today entry.php slurps /data/static into a userland hash table and hand-rolls .br/.gz selection. Production rules forbid user-land MIME lookup, in-memory caching, and hand-rolled compression — so the static profiles cannot be marked production until the framework provides a native primitive.
HttpServer / HttpServerConfig have no static-file API; this issue introduces one.
Builder class (not array $options) — symmetric to HttpServerConfig, gives IDE autocomplete and per-setter validation. No PHP callback per request — that re-enters the VM and kills the zero-coroutine fast path.
Architecture (key decision)
Dispatch happens in C, before ZEND_ASYNC_NEW_COROUTINE at src/core/http_connection.c:1614. The static path is a pure FSM driven by uv_fs_open → fstat → read-loop → close. No coroutine alloc, no zend_try, no PHP-VM entry on matched static requests.
if (UNEXPECTED(server->static_handler_count>0)) {
http_static_result_trc=http_static_try_serve(server, conn, ctx, req);
if (rc==HTTP_STATIC_HANDLED) return; // C owns lifecycleif (rc==HTTP_STATIC_ERROR) { emit_short_error(...); return; }
/* PASSTHROUGH falls through to coroutine + PHP handler */
}
A shared http_request_finalize(conn, ctx) helper, extracted from http_handler_coroutine_dispose, runs from both the coroutine path and the static FSM tail.
For HTTP/2 / HTTP/3 the file is plumbed as an nghttp2 / nghttp3 data provider — natural fit, still zero coroutine.
Hot-path budget
Documented in detail in §4 of the plan. Highlights:
One open(O_RDONLY|O_CLOEXEC) + one fstat per request. No probe-stat.
64 KiB read buffer, single emalloc per request, freed in close-callback.
Built-in MIME table (perfect-hash / sorted binary search), per-mount overrides only on miss.
After PR #1+#2+#3+#4 land: rewrite entry.php to drop the in-memory map and the hand-rolled encoding chooser, then flip frameworks/true-async-server/meta.json to "type": "production" for the static profiles. Closes FUTURES #2.
Acceptance
StaticHandler class compiles, stubs regenerate, arginfo hash matches.
addStaticHandler rejects calls after start().
enablePrecompressed('foo') throws at the setter, not at start().
Tracks FUTURES.md item #2. Unblocks the
productiontier for HttpArena static profiles (static,static-h2,static-h3) — the only remaining blocker beforemeta.jsoncan flip from"type": "tuned"to"type": "production".Full design + implementation plan:
docs/PLAN_STATIC_HANDLER.md.Why
Today
entry.phpslurps/data/staticinto a userland hash table and hand-rolls.br/.gzselection. Production rules forbid user-land MIME lookup, in-memory caching, and hand-rolled compression — so the static profiles cannot be markedproductionuntil the framework provides a native primitive.HttpServer/HttpServerConfighave no static-file API; this issue introduces one.Surface (PHP)
Builder class (not
array $options) — symmetric toHttpServerConfig, gives IDE autocomplete and per-setter validation. No PHP callback per request — that re-enters the VM and kills the zero-coroutine fast path.Architecture (key decision)
Dispatch happens in C, before
ZEND_ASYNC_NEW_COROUTINEatsrc/core/http_connection.c:1614. The static path is a pure FSM driven byuv_fs_open→fstat→ read-loop →close. No coroutine alloc, nozend_try, no PHP-VM entry on matched static requests.A shared
http_request_finalize(conn, ctx)helper, extracted fromhttp_handler_coroutine_dispose, runs from both the coroutine path and the static FSM tail.For HTTP/2 / HTTP/3 the file is plumbed as an
nghttp2/nghttp3data provider — natural fit, still zero coroutine.Hot-path budget
Documented in detail in §4 of the plan. Highlights:
open(O_RDONLY|O_CLOEXEC)+ onefstatper request. No probe-stat.emallocper request, freed in close-callback.mtime_ns ^ (size << 17) ^ ino, single 64-bit mix, 16 hex chars.EXPECTED/UNEXPECTEDon dispatcher and FSM branches.addStaticHandlertime, not per request.writev-coalesced headers + first read chunk for small files (H1 plain).PR breakdown
StaticHandlerclass, dispatch hook (H1 only), MIME, traversal guard, weak ETag, conditional GET,uv_fs_*chain,http_request_finalizeextraction. PHPT matrix: 200 / 304 / 404 / 403 / traversal / dotfile-deny.multipart/byteranges,If-Range, 416)..br/.gz/.zst. Reusescompression/http_compression_negotiate.c.sendfile(2)/splice(2)zero-copy fast path (H1 plain, no on-the-fly encoding).browse(dir listing). Lowest priority.After PR #1+#2+#3+#4 land: rewrite
entry.phpto drop the in-memory map and the hand-rolled encoding chooser, then flipframeworks/true-async-server/meta.jsonto"type": "production"for the static profiles. Closes FUTURES #2.Acceptance
StaticHandlerclass compiles, stubs regenerate, arginfo hash matches.addStaticHandlerrejects calls afterstart().enablePrecompressed('foo')throws at the setter, not atstart()...,%2e%2e,%00, NUL, absolute paths) → 400/404.Reject404s,Followserves,OwnerMatchserves only when owner-of-link == owner-of-target.If-None-Match/If-Modified-Since→ 304 with empty body.Deny→ 404 on/.git/config.on_missing: Nextfalls through toaddHttpHandler; defaultNotFoundreturns 404 in C.wrk -c 256 -t 4 -d 30 /static/main.cssdoes not regress vs currententry.php; expected 5–15% gain from the coroutine skip.Open questions parked
open_file_cache-style) — defer until bench shows it matters.browseHTML dir listing — PR Support running on stock PHP (without TrueAsync fork) #6, low priority.Anchors
src/core/http_connection.c:1562(http_connection_dispatch_request).src/core/http_connection.c:1614.src/core/http_connection.c:1601.src/http2/http2_strategy.c:156.src/http3/http3_dispatch.c:134.src/http_server_class.c:944-966(addHttpHandler).src/http_server_config.c.