Add multi-protocol server description to README#1
Closed
EdmondDantes wants to merge 1 commit intomainfrom
Closed
Conversation
Explains that TrueAsync Server combines multiple protocols (HTTP/1.1, HTTP/2, HTTP/3, WebSocket, SSE, gRPC) in a single server instance on one port, with ALPN negotiation and HTTP Upgrade for protocol selection.
EdmondDantes
added a commit
that referenced
this pull request
May 6, 2026
FUTURES #1. Each listener now carries its own HTTP_PROTO_MASK_*; the effective acceptance is listener_mask ∩ server_view_mask. New PHP API: addHttp1Listener / addHttp2Listener on HttpServerConfig for protocol-restricted ports (h2c-only, h1-only). Default addListener() unchanged. Also adds docs/USAGE.md and fixes the README quick-start to use the actual public API.
EdmondDantes
added a commit
that referenced
this pull request
May 7, 2026
PR #1 of the StaticHandler plan is now functional end-to-end. Files under a configured StaticHandler mount are served directly in C — no PHP coroutine, no zend_call_function, no fcall_info_cache lookup on the static path. Pipeline: http_connection_dispatch_request (src/core/http_connection.c) └─ if any mount registered → http_static_try_serve(...) ├─ method check (GET/HEAD only; others passthrough) ├─ http_static_path_resolve → percent-decode + traversal guard │ + dotfile policy ├─ http_static_path_is_hidden (hide-glob match via fnmatch) ├─ open(O_RDONLY [|O_NOFOLLOW]) + fstat (synchronous; PR #5 │ upgrades to libuv thread pool) ├─ ETag/conditional → 304 with empty body ├─ slurp into zend_string + http_response_static_set_* └─ ctx->skip_php_handler = true http_handler_coroutine_entry sees skip_php_handler set and returns immediately after the request-counter bump and CoDel sample — no PHP VM entry. Existing dispose path flushes the response on the wire, so all keep-alive / drain / Alt-Svc / counter convergence reuses the existing infrastructure (no http_request_finalize extraction needed; the flag-based fold matches the plan's acceptance criterion of zero new flush-path duplication). The accept path now allows static-only deployments — when the server has zero PHP handlers but at least one static mount, http_connection_spawn proceeds with handler=NULL and the dispatcher synthesises a 404 for any request the static path didn't claim. PHPT matrix in tests/phpt/server/static/ covers: 001 — 200, MIME (CSS / HTML index), nested file, 404, traversal (relative + percent-encoded), HEAD body suppression with Content-Length, weak ETag conditional GET → 304, non-GET method passthrough. 002 — dotfile-deny default still 404s under on_missing: Next (security guarantee), genuinely-missing files fall through to PHP, URLs outside the prefix fall through to PHP. Pre-existing 153 PHPT tests still pass (1 skip, 0 fail). Two small public response API helpers added in php_http_server.h: http_response_static_set_status / set_header / set_body_str — unguarded direct-field setters for the single-writer C path. The PHP-facing setters keep their close/streaming guards. Closes the PR-#1 scope from docs/PLAN_STATIC_HANDLER.md (§5). PRs #2-#6 (H2/H3, range, precompressed, sendfile, browse) land incrementally on top of the same dispatch hook.
EdmondDantes
added a commit
that referenced
this pull request
May 7, 2026
Code-review pass over PR #1 against docs/CODING_STANDARDS.md: Comments — drop decorative WHAT-restatements (function-name says it), keep WHY / invariant / RFC-citation comments. Net −72 lines of prose for the same information density. const — http_static_etag_format takes const struct stat *; fixed- size buffer params and read-only locals across the static TUs are now const where true. Internal helpers carry const-pointer params for inputs they only read. Hot-path branch hints — UNEXPECTED on every error/edge branch in the dispatch FSM (BAD_REQUEST, FORBIDDEN, HIDE, !opened, oversized, slurp failure, hide-glob match), EXPECTED on the read-loop success branch. The success path stays straight- line. MIME table — add precomputed content_type_len per entry so the hot path no longer does strlen() on every served file. Single MIME() macro keeps the table readable. ETag length — replace per-call strlen(etag_buf) with the new HTTP_STATIC_ETAG_LEN constant; same for HTTP_STATIC_DATE_LEN. The new ZEND_ASSERT in http_static_etag_format caught an off-by-one in the original buffer size (was 22, correct is 21 — the format produces 20 chars + NUL). Debug-build invariant — assert_builtin_table_sorted() runs once per process under !NDEBUG, replacing a stale "static_assert verifies ordering" comment that was never actually wired up. Misleading comment — addStaticHandler claimed protocol_mask was needed for the start() preflight; in fact static_handler_count covers that. Updated to reflect actual intent (symmetry with addHttpHandler convention). All 153 pre-existing PHPT tests still pass; the two static tests in tests/phpt/server/static/ continue to pass on this refactor.
EdmondDantes
added a commit
that referenced
this pull request
May 7, 2026
Security audit pass over PR #1 — five findings, all addressed. HIGH — symlink traversal via intermediate components. O_NOFOLLOW only protects the final path component; an attacker- or operator-created symlink at any earlier level inside the mount root could redirect open(2) outside. Added a realpath()- based prefix check in resolved_under_root() that runs after every successful try_open_candidate; mismatch → 404. The realpath/open TOCTOU is acceptable — exploiting it requires filesystem write access on the host. HIGH — backslash bypass on Windows. The segment validator splits on '/' only, so "..\..\etc\passwd" arrived as one segment that didn't match ".." and would have escaped the mount on a host where the kernel treats '\' as a separator. Backslash is now rejected anywhere in the percent-decoded path (literal or %5C); URLs never legitimately contain '\' under RFC 3986 anyway. MEDIUM — 403 on EACCES disclosed the existence of restricted files. Collapsed every "open failed" branch (ENOENT, ELOOP, ENOTDIR, EACCES, EPERM) onto the same 404 / on_missing fallback used elsewhere — symmetric with dotfile-deny. MEDIUM — setCacheControl accepted CRLF/NUL, allowing response- splitting if an operator concatenated user input. Added the same control-character rejection used by setHeader. MEDIUM — SYMLINKS_OWNER documented as "follow if owner-of-link matches" but the post-open uid comparison wasn't implemented; the mode behaved like FOLLOW. Aliased to REJECT (O_NOFOLLOW) until the real owner-match check lands — the policy is no weaker than its advertised security promise. LOW — http_static_format_http_date used %04d on tm_year + 1900 without bound-checking; a far-future mtime (filesystem tampering) would overflow the fixed buffer logically and produce a malformed Last-Modified. Year is now clamped to [0, 9999] with a debug-build ZEND_ASSERT on the snprintf return. New PHPT 003-static-security.phpt covers each finding: symlink escape via /static/sneak/secret.txt → 404, literal and %5C-encoded backslash → 400, %00 NUL injection → 400, chmod-000 file → not 403, setCacheControl("\r\n…") → InvalidArgumentException. All 154 PHPT tests still pass (1 pre-existing skip, 0 fail).
EdmondDantes
added a commit
that referenced
this pull request
May 7, 2026
Add §5a "Status" tracking what's actually shipped in this branch versus what remains. Highlights: Closed: full hard-zero coroutine path on plain TCP, security fixes (backslash, symlink-traversal via realpath prefix-check, EACCES→404, header-injection in setCacheControl), zend_async API v0.11 (fs_open + sendfile, in php-src + ext/async submodules). Remaining for PR #1's acceptance: PHPT for If-Modified-Since, telemetry counter for zero-coroutine, bench vs entry.php, valgrind on a million-request run, TLS hard-zero (needs non-suspending TLS write helper), headers↔sendfile ordering on EAGAIN (TCP_CORK or wait-for-completion). Future PRs untouched: H2/H3 (#2), Range (#3), precompressed (#4), browse (#6). Note that PR #5 (sendfile) effectively merged into #1 because zero-copy sendfile required the zend_async API extension anyway, and it's cleaner than gating it behind a flag. Mark acceptance checklist items in §6 with [x] / [~] / [ ] so the status is at-a-glance. Open questions §7 picks up TLS hard-zero, headers/sendfile ordering, and SYMLINKS_OWNER as deferred items the next iteration owns.
EdmondDantes
added a commit
that referenced
this pull request
May 8, 2026
Bounded per-server cache of recent static-handler path lookups. On a warm cache, http_static_try_serve skips the realpath/stat walk that runs on every request — the dominant cost on hot serving when the dentry cache is cold. Defaults: 512 entries, 60-second TTL (matches nginx open_file_cache / open_file_cache_valid). Both tunable per worker via env: TRUE_ASYNC_STATIC_CACHE_ENTRIES=0 disables; TTL=0 also disables. No locking — single-thread per event loop is the invariant; worker pool gives each worker its own snapshot. Per-entry: zend_string key (the absolute resolved path), full struct stat copy, content_type pointer (borrowed from the persistent MIME table), pemalloc'd ETag and Last-Modified buffers, LRU links. HashTable destructor frees the entry; cache destroy fires at server free. Wire-up in http_static_try_serve: lookup before resolved_under_root; on hit skip the realpath. On miss + realpath success: insert. Hit-side reuse is currently realpath-only — st, etag, content_type fields in the view are NULL/zero. Full metadata reuse (skip stat / etag / mime lookup) is a separate follow-up that needs a one-shot stat at insert time inside the async chain. Bench (debug-zts build, 16 cores WSL2, 64 KiB file, wrk -c64 -t4 -d5s, warm dentry cache, 3-run average): - cache on : ~11017 req/s - cache off : ~11748 req/s On warm dentry cache realpath is essentially free, so the HashTable lookup is pure overhead in this microbench. The cache earns its keep on cold-cache scenarios and once follow-up #1 lands (skip stat/etag/mime on hit). Tests: 19/20 PASS + 1 pre-existing SKIP across static + tls suites.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
$server->start()callTest plan