diff --git a/CHANGELOG.md b/CHANGELOG.md index 557a511a..8b179f6b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.17.0] - 2026-10-20 + ### Added - **A request rate limit inside the server (#414).** `HttpServer::addRateLimit(new RateLimit($requests, $perSeconds))` keeps a token bucket per client address, and a request over the limit of any rule it matches is answered `429 Too Many Requests` with `Retry-After` before any handler runs; before, an application had to count requests in its handler or in front of the server. Rules filter by path prefix (`paths()`, matched decoded and with dot segments resolved) and method (`methods()`); a request takes a token from every rule it matches only when all of them admit it. `HttpServerConfig::setTrustedProxies()` names proxies whose `X-Forwarded-For` gives the client; the header of any other peer is ignored. On HTTP/1 a refused POST or PUT is refused when its headers end, so its body is dropped as it arrives instead of buffered or written to disk: a refused 20,000-byte multipart upload left 0 temporary files mid-upload where an admitted one had 1 (`core/090`); HTTP/2 and HTTP/3 refuse after their body handling has started. A refusal is counted only when its 429 is sent: a body past `setMaxBodySize()` gets 413 instead and is not counted, and a declared `Content-Length` past it is answered 413 before any token is taken. In a WebSocket build a request carrying `Upgrade` is left to the upgrade path, takes no token unless it is a valid upgrade, and has its body dropped unread. gRPC calls over HTTP/2 and HTTP/3 get `grpc-status: 8`, a refused WebSocket upgrade or extended CONNECT gets 429, and static files a mount serves are not counted. `getTelemetry()` and `getStats()` gain `requests_rate_limited_total` and `requests_rate_limit_evictions_total`. Each rule keeps 65,536 buckets of 24 bytes per server instance by default, so a client spread over `setWorkers(N)` workers may get N times the limit. Evidence: `core/089` to `core/111` (`core/091` needs IPv6, `core/109` a WebSocket build) and the cmocka suites `RateLimit` and `RateLimitAsan` (SipHash against the reference vectors, the X-Forwarded-For walk, path matching, length bounds, check-then-commit). @@ -1530,7 +1532,8 @@ on the [TrueAsync](https://github.com/true-async) event loop. and Windows, quick start), `docs/` (coding standards, contributor recommendations, llhttp upstream notes), Apache 2.0 `LICENSE`. -[Unreleased]: https://github.com/true-async/server/compare/v0.16.0...HEAD +[Unreleased]: https://github.com/true-async/server/compare/v0.17.0...HEAD +[0.17.0]: https://github.com/true-async/server/compare/v0.16.0...v0.17.0 [0.16.0]: https://github.com/true-async/server/compare/v0.15.0...v0.16.0 [0.15.0]: https://github.com/true-async/server/compare/v0.14.0...v0.15.0 [0.14.0]: https://github.com/true-async/server/compare/v0.13.0...v0.14.0 diff --git a/dev/PLAN.md b/dev/PLAN.md index cbe82412..20d0063f 100644 --- a/dev/PLAN.md +++ b/dev/PLAN.md @@ -657,7 +657,12 @@ Added by the health check of 2026-09-30 (`dev/HEALTH.md`), in this order: queue, so the worker registered last is woken first and wins `accept4`. 3 workers, 32 sequential connections, 5 runs: shared fd [32], [30,2], [29,3], [31,1], [32]; SO_REUSEPORT [13,11,8] to [16,9,7]. Linux uses - SO_REUSEPORT by default. Waiting on Edmond: close as a kernel trait. + SO_REUSEPORT by default. Edmond, 2026-10-20: closed as not planned, a + kernel trait; reopen on a measurement from macOS or Windows. +50. Release 0.17.0 (asked for by Edmond, 2026-10-20): every open defect closed + (#412, #413, #424, #426, #428 merged; #329 closed). Version bump, CHANGELOG + section, IDE stubs checked against the stubs (31 docblocks changed since + 0.16.0, all match), tag `v0.17.0` with notes. `~/releases` not touched. 43. #339 — a client filename with path components: done. Edmond, 2026-10-19: behave as PHP. The name is cut after the last `/` or `\` (`php_ap_basename`, static in `main/rfc1867.c`, so copied) and the file is diff --git a/ide-stubs/true-async-server.php b/ide-stubs/true-async-server.php index b9707ae6..d7f34ddc 100644 --- a/ide-stubs/true-async-server.php +++ b/ide-stubs/true-async-server.php @@ -12,7 +12,7 @@ * and functions the extension registers. * * @since 8.6 - * @version 0.16.0 + * @version 0.17.0 * @link https://github.com/true-async/server */ diff --git a/include/php_http_server.h b/include/php_http_server.h index 3a56bba5..30d64db3 100644 --- a/include/php_http_server.h +++ b/include/php_http_server.h @@ -70,7 +70,7 @@ static zend_always_inline bool http_path_is_cwd_independent(const char *path, si #include "php_true_async_server.h" #include "core/rate_limit.h" /* http_rl_trusted_t */ -#define PHP_HTTP_SERVER_VERSION "0.16.0" +#define PHP_HTTP_SERVER_VERSION "0.17.0" /* * ==========================================================================