A lightweight PHP framework for building HAProxy Stream Processing Offload Agent (SPOA) services
This library implements the SPOE protocol and allows PHP applications to receive messages from HAProxy, process request or session metadata, and return variables back to HAProxy for use in ACLs and routing decisions.
Install via Composer:
composer require rocketman/spoa-php
Runtime
react/socketDevelopment
phpunit/phpunitphpstan/phpstanreact/event-loopConnection classThe central abstraction in this framework is SPOA\Server\Connection.
Each incoming SPOE connection from HAProxy is represented by a
Connection instance. Applications register message handlers on the
connection and return variables (or Promises that resolve to variables)
to HAProxy in response.
The framework handles:
Your application code only needs to define handlers.
Handlers are registered by message name:
$conn->on('get-ip-reputation', function (array $args): PromiseInterface|array {
// ...
});
Message Name: The event name (e.g., get-ip-reputation) must
match the spoe-message identifier in your HAProxy
configuration.
Arguments ($args): An associative array of
SPOA\Protocol\Arg object instances.
Named Arguments: Accessed via their name defined in HAProxy
(e.g., $args['client_ip']).
Nameless Arguments: Accessed via the key arg(n), where n
is the zero-based position (e.g., $args['arg(0)']).
Ordinal Access: As the array maintains the order defined in
HAProxy, you can retreive any argument by its position
regardless of its name (e.g., $fourthArg = $args[array_keys($args)[3]]).
Handlers return an associative array of variables to set or unset.
use SPOA\Protocol\Arg;
return [
'sess.one' => Arg::str('two'),
'txn.score' => Arg::uint32(42),
];
Alternatively, a handler may return a React\Promise\PromiseInterface
that resolves to the associative array. This allows handlers to perform
asynchronous operations before returning data to HAProxy.
Variable names may be prefixed with a scope:
| Prefix | Scope |
|---|---|
proc. |
Process |
sess. |
Session |
txn. |
Transaction |
req. |
Request |
res. |
Response |
If no prefix is provided, the variable defaults to process scope.
Specifying null as a value will unset the variable. To set a
variable to null, use Arg::null().
Below is a minimal SPOA agent implemented using this library. The example is intentionally procedural to focus on the frameworkâs programming model.
<?php
use React\EventLoop\Loop;
use React\Promise\PromiseInterface;
use React\Socket\Server;
use SPOA\Protocol\Arg;
use SPOA\Server\Connection;
$loop = Loop::get();
$server = new Server('127.0.0.1:12345', $loop);
$server->on('connection', function ($conn) {
$spoa = new Connection($conn);
$spoa->on('get-ip-reputation', function (array $args): PromiseInterface|array {
$srcIp = $args['client_ip']?->value;
// check IP address and set reputation
$rep = 42;
return [
'sess.score' => Arg::uint32($rep),
];
});
});
$loop->run();
This example:
The application runs inside a standard ReactPHP event loop.
spoe-test.cfg)spoe-agent iprep-agent
messages get-ip-reputation
option var-prefix iprep
timeout hello 200ms
timeout idle 3m
timeout processing 500ms
use-backend iprep-server
spoe-message get-ip-reputation
args client_ip=src
event on-frontend-http-request
frontend http
filter spoe config /etc/haproxy/spoe-test.cfg
http-request set-header X-Pass 1 if { var(sess.iprep.score) -m int gt 20 }
backend iprep-server
mode tcp
option spop-check
timeout connect 5s
timeout server 3m
server iprep 127.0.0.1:12345 # check (check is optional)
This configuration:
A complete, working SPOA agent built using this library can be found in the Zookeeper Online music database and charting application.
In particular, see:
This example shows how the library can be embedded into a larger application, including integration with an existing ReactPHP event loop and non-trivial decision logic.
Run the test suite:
vendor/bin/phpunit
Static analysis:
vendor/bin/phpstan analyse src tests
Both are executed automatically via GitHub Actions on push.
MIT License. See LICENSE for details.