spoa-php

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.

Latest Version CI License Source


Features


Requirements


Installation

Install via Composer:

composer require rocketman/spoa-php

Dependencies

Runtime

Development


Core Concepts

The Connection class

The 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.


Registering message handlers

Handlers are registered by message name:

$conn->on('get-ip-reputation', function (array $args): PromiseInterface|array {
    // ...
});

Returning variables to HAProxy

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 scopes

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().


Getting Started

Below is a minimal SPOA agent implemented using this library. The example is intentionally procedural to focus on the framework’s programming model.

Minimal PHP agent

<?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.


HAProxy Configuration

SPOE configuration (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

HAProxy configuration excerpt

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:


Real-world example

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.


Testing

Run the test suite:

vendor/bin/phpunit

Static analysis:

vendor/bin/phpstan analyse src tests

Both are executed automatically via GitHub Actions on push.


License

MIT License. See LICENSE for details.