Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down Expand Up @@ -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
Expand Down
7 changes: 6 additions & 1 deletion dev/PLAN.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion ide-stubs/true-async-server.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/

Expand Down
2 changes: 1 addition & 1 deletion include/php_http_server.h
Original file line number Diff line number Diff line change
Expand Up @@ -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"

/*
* ==========================================================================
Expand Down
Loading