API Index — Namespaces, Packages, Reports, Indices
ConcurrencyLimitMiddleware
in package
implements
MiddlewareInterface
Concurrency-Limit Middleware
Bounds the number of in-flight requests. Supports two modes:
Global mode (backward-compatible, original behaviour)
A single shared Counter (OpenSwoole\Atomic) caps total in-flight
requests across all clients. Simple, zero-allocation, but one slow
client can exhaust the global cap and 503 everyone else.
Per-key mode (nginx limit_conn parity)
A Store table (OpenSwoole\Table) tracks in-flight counts per key.
The default key is the client IP (proxy-aware via App::clientIp()),
matching nginx $binary_remote_addr. A custom key resolver can be
supplied for other partitioning schemes (e.g. API key, tenant ID).
nginx equivalent:
limit_conn_zone $binary_remote_addr zone=addr:10m;
limit_conn addr 100;
limit_conn_status 503;
limit_conn_dry_run off;
The counter/store entry is incremented on entry and decremented on exit
via try/finally, so handlers that throw still decrement correctly.
Pre-fork footgun — both the Counter and the Store table MUST be
instantiated/created BEFORE $app->run() so all forked workers share
the same shared-memory segment. Creating them after run() gives each
worker its own isolated counter — the limit will be maxConcurrent × numWorkers in practice, with no cross-worker enforcement.
Usage — global mode (existing API, no change):
$inflight = new \ZealPHP\Counter();
$app->addMiddleware(new \ZealPHP\Middleware\ConcurrencyLimitMiddleware(
maxConcurrent: 100,
counter: $inflight,
));
Usage — per-key mode (nginx limit_conn parity):
Store::make('conn_limit', 4096, [
'count' => [\OpenSwoole\Table::TYPE_INT, 4],
]);
$app->addMiddleware(new \ZealPHP\Middleware\ConcurrencyLimitMiddleware(
maxConcurrent: 20,
counter: null, // no global counter
tableName: 'conn_limit', // per-key Store table
));
Usage — per-key + global cap together:
$global = new \ZealPHP\Counter();
$app->addMiddleware(new \ZealPHP\Middleware\ConcurrencyLimitMiddleware(
maxConcurrent: 20, // per-key cap
counter: $global, // also enforce global cap (separate Counter)
tableName: 'conn_limit',
globalMax: 500, // global ceiling across all clients
));
Options:
rejectStatus int HTTP status on rejection (400–599, default 503)
dryRun bool Observe + log, never enforce (default false)
keyResolver callable|null fn(ServerRequestInterface): string — override key;
default uses App::clientIp()
globalMax int When > 0 and a $counter is provided, also enforce
this global ceiling alongside the per-key limit
Table of Contents
Interfaces
- MiddlewareInterface
Properties
- $counter : Counter|null
- $dryRun : bool
- $globalMax : int
- $keyResolver : callable(ServerRequestInterface): string
- $maxConcurrent : int
- $rejectStatus : int
- $tableName : string|null
- $warnedMissingTable : bool
Methods
- __construct() : mixed
- process() : ResponseInterface
- processGlobal() : ResponseInterface
- processPerKey() : ResponseInterface
- reject() : ResponseInterface
Properties
$counter
private
Counter|null
$counter
= null
$dryRun
private
bool
$dryRun
= false
$globalMax
private
int
$globalMax
= 0
$keyResolver
private
callable(ServerRequestInterface): string
$keyResolver
$maxConcurrent
private
int
$maxConcurrent
$rejectStatus
private
int
$rejectStatus
= 503
$tableName
private
string|null
$tableName
= null
$warnedMissingTable
private
static bool
$warnedMissingTable
= false
Methods
__construct()
public
__construct(int $maxConcurrent[, Counter|null $counter = null ][, string|null $tableName = null ][, int $globalMax = 0 ][, int $rejectStatus = 503 ][, bool $dryRun = false ][, callable(ServerRequestInterface): string|null $keyResolver = null ]) : mixed
Parameters
- $maxConcurrent : int
-
Per-key (or global) in-flight cap.
- $counter : Counter|null = null
-
Optional global
Counter(global mode when$tableNameis null; optional additional global cap in per-key mode). - $tableName : string|null = null
-
Storetable for per-key limiting. Must be created before$app->run(). - $globalMax : int = 0
-
Global cap enforced via
$counterwhen0 and
$tableNameis set. Ignored when$tableNameis null (legacy mode uses$maxConcurrentas the global cap). - $rejectStatus : int = 503
-
HTTP status on rejection (
400–599). - $dryRun : bool = false
-
Log rejections without enforcing.
- $keyResolver : callable(ServerRequestInterface): string|null = null
-
Override key; default =
clientIp().
process()
public
process(ServerRequestInterface $request, RequestHandlerInterface $handler) : ResponseInterface
Parameters
- $request : ServerRequestInterface
- $handler : RequestHandlerInterface
Return values
ResponseInterfaceprocessGlobal()
private
processGlobal(ServerRequestInterface $request, RequestHandlerInterface $handler) : ResponseInterface
Parameters
- $request : ServerRequestInterface
- $handler : RequestHandlerInterface
Return values
ResponseInterfaceprocessPerKey()
private
processPerKey(ServerRequestInterface $request, RequestHandlerInterface $handler) : ResponseInterface
Parameters
- $request : ServerRequestInterface
- $handler : RequestHandlerInterface
Return values
ResponseInterfacereject()
private
reject() : ResponseInterface