mammatus / http-server
🐙 Multi vhost HTTP Server
Package info
github.com/MammatusPHP/http-server
Type:composer-plugin
pkg:composer/mammatus/http-server
Requires
- php: ^8.4
- composer-plugin-api: ^2.0.0
- ext-json: ^8.4
- composer/composer: ^2.11.1
- evenement/evenement: ^3.0.2
- eventsauce/object-hydrator: ^1.8.0
- for/http-middleware-psr15-adapter: ^2.0.0
- guzzlehttp/psr7: ^2.7
- mammatus/app: dev-master
- mammatus/groups: ^0.4.0
- mammatus/healthz-vhost: ^0.2.1
- mammatus/http-server-attributes: ^0.3.0
- mammatus/http-server-contracts: ^0.1.0
- mammatus/http-server-webroot: ^0.1.2
- mammatus/kubernetes-attributes: ^1.3.0
- mammatus/kubernetes-events: ^5.2.0
- mammatus/life-cycle-events: ^2.3.0
- nikic/fast-route: 2.0.0-beta1
- nikic/php-parser: ^5.9.0
- psr/container: ^1 || ^2 || ^3
- psr/http-factory: ^1 || ^2 || ^3
- psr/http-message: ^1 || ^2 || ^3
- psr/http-server-handler: ^1 || ^2
- psr/http-server-middleware: ^1 || ^2 || ^3
- psr/log: ^3.0.2
- ratchet/rfc6455: ^0.4.1
- react/async: ^4.3.0
- react/cache: ^1.2.0
- react/event-loop: ^1.6.0
- react/http: ^1.12.0
- react/promise: ^3.3.0
- react/socket: ^1.17.0
- react/stream: ^1.4.0
- realodix/change-case: ^4.2.4
- roave/better-reflection: ^6.75.0
- wyrihaximus/broadcast: ^2.7.0
- wyrihaximus/broadcast-contracts: ^1.3.0
- wyrihaximus/generative-composer-plugin-tooling: ^1.8.4
- wyrihaximus/psr-3-context-logger: ^3.0.0
- wyrihaximus/react-http-middleware-webroot-preload: ^3.1.0
- wyrihaximus/simple-twig: ^2.4.0
Requires (Dev)
- colinodell/psr-testlogger: ^1.3.1
- monolog/monolog: ^3.12.1
- pimple/pimple: ^3.6.2
- shipmonk/dead-code-detector: ^0.14.1 || ^0.15.0 || ^1.0.0
- wyrihaximus/makefiles: ^0.15.5
- wyrihaximus/test-utilities: ^14.7.0
Suggests
- shipmonk/dead-code-detector: This package supports ^1 || ^0.15 || ^0.14.1 by hooking in and marking all listeners as alive
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-11-06 14:00:11 UTC
README
Async multi-vhost HTTP on ReactPHP and react/http for MammatusPHP applications. mammatus/http-server is a Composer plugin that discovers virtual hosts and route handlers across your project and installed packages, then generates ReactPHP server classes and Kubernetes-oriented Helm values. Generated servers implement LifeCycleHandler and start or stop with Mammatus groups.
Install
To install via Composer, use the command below. Composer picks the latest compatible version and applies a ^ constraint.
composer require mammatus/http-server
The plugin runs on every composer dump-autoload (pre-autoload-dump) and via the composer generate-config script. Both call Mammatus\Http\Server\Composer\Installer::findServers.
How it works
On autoload dump the plugin scans the codebase and installed packages, collects vhosts, HTTP handlers, and optional Kubernetes metadata, then writes generated PHP under the plugin install path (for example vendor/mammatus/http-server/ in an application).
flowchart LR
subgraph discover [Discovery on composer dump]
VhostClasses["Classes implementing Vhost"]
HandlerClasses["Classes with Vhost plus Route attributes"]
Packages["Packages with extra.mammatus.http.server.has-vhosts"]
end
Plugin["mammatus/http-server Plugin"]
Generated["src/Server LifeCycleHandlers"]
Helm["Kubernetes Helm ServerValues"]
discover --> Plugin
Plugin --> Generated
Plugin --> Helm
Generated --> Groups["Mammatus groups start and stop"]
Loading
- Discovery filters:
Plugin::filters()matches packages withextra.mammatus.http.server.has-vhosts, classes implementingVhost, and handler classes that carry both#[Vhost]and#[Route]. - Collection:
Collectoryields servers, HTTP handlers, WebSocket#[Rpc]methods and#[Channel]registrations, and optionalService/Ingressitems from vhost classes. - Generated output:
Server.php.twigtosrc/Server/{PascalVhostName}.php(oneLifeCycleHandlerper vhost)ServerValues.php.twigtoServerValuesfor Helm integration
- Do not edit generated files manually. They are overwritten on the next install or update (see the banner on
Frontend). - Routing: FastRoute with a on-disk route cache under
var/fast-route/{vhostName}inside generated code. - Per-vhost HTTP stack: request logging, body buffer and parser, optional PSR-15 middleware from the vhost, optional webroot preload middleware, then FastRoute dispatch.
- WebSockets: when a vhost has RPC or channel handlers (or vhost WebSocket attributes), generated servers add
Rfc6455UpgradeMiddleware, aHub(viawebSocketHub()withpublish()andonChannelSubscribe()), an object hydrator undersrc/Server/WebSocket/Hydrators/, and optionallyClientAssetMiddlewarewhen#[ServeClientAsset]is present on the vhost class.
Define a virtual host
Implement Mammatus\Http\Server\Configuration\Vhost: static name(), port(), and webroot() (NoWebroot or a path-backed webroot), plus instance middleware(). Optional #[Group], #[Service], and #[Ingress] on the class feed Helm values generation.
Example from this repository's dev app (FrontendVhost.php):
<?php declare(strict_types=1); namespace Mammatus\DevApp\Http\Server; use Mammatus\Groups\Attributes\Group; use Mammatus\Groups\Type; use Mammatus\Http\Server\Configuration\Vhost; use Mammatus\Http\Server\Configuration\Webroot; use Mammatus\Http\Server\Webroot\NoWebroot; use Mammatus\Kubernetes\Attributes\Ingress; use Mammatus\Kubernetes\Attributes\Service; use Psr\Http\Server\MiddlewareInterface; #[Group(Type::Daemon, 'frontend')] #[Service] #[Ingress('www.example.com')] final class FrontendVhost implements Vhost { private const string SERVER_NAME = 'frontend'; private const int LISTEN_PORT = 1337; public static function port(): int { return self::LISTEN_PORT; } public static function name(): string { return self::SERVER_NAME; } public static function webroot(): Webroot { return new NoWebroot(); } public static function maxConcurrentRequests(): null { return null; } /** @return iterable<MiddlewareInterface> */ public function middleware(): iterable { yield from []; } }
For a ready-made health and probe vhost, see mammatus/healthz-vhost (port 9666, Kubernetes probes, static files under public/).
HTTP route handlers
Handlers are plain PHP classes annotated with #[Vhost] and #[Route]. The collector (Collector::handler()) registers each matching public method that is not a constructor or destructor and has zero or one parameter. With one parameter, the type is a route payload DTO (FastRoute path segments map into it). Static and instance methods are both supported (Handler).
Home page handler (HomePageHandler.php):
#[Vhost('frontend')] #[Route(HttpMethod::GET, '/')] final readonly class HomePageHandler { public function handle(): ResponseInterface { return new Response(OK, ['Content-Type' => 'text/plain'], 'Hello World!'); } }
Route parameter payload (PingHandler.php and Ping.php):
#[Vhost('frontend')] #[Route(HttpMethod::GET, '/ping/{name}')] final readonly class PingHandler { public function handle(Ping $ping): ResponseInterface { return new Response(OK, ['Content-Type' => 'application/json'], '{}'); } }
Probe routes and other attributes are documented in mammatus/http-server-attributes.
WebSockets
WebSockets share the vhost listen port. After an HTTP upgrade, clients send JSON frames with an o operation field (Operation, WireMessage). Attributes live in mammatus/http-server-attributes.
Vhost class attributes (read by VhostConfiguration):
| Attribute | Purpose |
|---|---|
#[HeartbeatInterval(seconds: float)] |
Periodic RFC6455 PING and optional evt on the heartbeat channel. Omit for default 13 seconds; seconds <= 0 disables heartbeat. |
#[HeartbeatChannel('name')] |
Channel name for heartbeat events (default heartbeat). |
#[ServeClientAsset] |
Serves the bundled ES module at /.well-known/mammatus/websocket-client.mjs. |
The dev-app vhost uses #[ServeClientAsset] (FrontendVhost.php).
RPC handlers: #[Vhost('…')] on the class and #[Rpc('methodName')] on a public method. Supported signatures (Collector):
(ServerRequestInterface $upgradeRequest)only(ParamsDto $params, ServerRequestInterface $upgradeRequest)with a DTO first argument
Return a DTO, scalar, or React\Promise\PromiseInterface for async work. Example PingHandler.php:
#[Vhost('frontend')] final readonly class PingHandler { #[Rpc('ping')] public function ping(PingParams $params, ServerRequestInterface $upgradeRequest): PingResult { return new PingResult(pong: true, echo: $params->message); } }
Pub/sub channels: #[Channel('channel-name', PayloadClass::class)] on a class with the same #[Vhost]. Payload objects serialize through the generated hydrator. Example DemoEvent.php.
- Publish: from application code that can reach the vhost generated
LifeCycleHandler, callwebSocketHub()->publish('channel-name', $payload). - Subscribe hook:
Hub::onChannelSubscribe()registers acallableinvoked with aConnectionand the channel name when a client successfully subscribes. Clients send{"o":"sub","c":"channel-name"}(seesubscribe()in the browser client;OperationandWirePropertiesvalues are injected at serve time). Register during startup from the same kind of code that callspublish(); do not edit generatedsrc/Server/*.php. Each call replaces the previous callback. Duplicatesubfor the same connection and channel does not invoke the callback again.
use Mammatus\Http\Server\WebSocket\Connection; // $vhostServer: generated LifeCycleHandler for the vhost (inject via your DI container). $vhostServer->webSocketHub()->onChannelSubscribe( static function (Connection $connection, string $channel): void { // Metrics, logging, or push an initial evt to $connection, etc. }, );
Browser client: import MammatusWebSocket from the well-known URL when #[ServeClientAsset] is enabled. connect() keeps the socket open with exponential backoff reconnect; each successful open re-subscribes the heartbeat channel and every channel passed to subscribe(). Call disconnect() to close and disable reconnect. Event handlers registered with on() survive reconnects; in-flight rpc() promises reject when the connection drops. Interactive demo: HTTP route /demo/websocket.
Publishing vhosts from a Composer package
Set extra.mammatus.http.server.has-vhosts to true in composer.json so the plugin scans that package for Vhost implementations and attributed handlers (this package and healthz-vhost use the same flag):
"extra": { "mammatus": { "http": { "server": { "has-vhosts": true } } } }
Application code and every scanned package contribute handlers to the same generated servers keyed by vhost name.
Hacking this repository
See CONTRIBUTING.md. Run make install, then make contrib while iterating and make before opening a pull request. Example vhost and handlers live under etc/dev-app/.
License
The MIT License (MIT)
Copyright (c) 2026 Cees-Jan Kiewiet
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.