Search by

mammatus / http-server

WyriHaximus

🐙 Multi vhost HTTP Server

Package info

github.com/MammatusPHP/http-server

Type:composer-plugin

pkg:composer/mammatus/http-server

Statistics

Installs: 25 801

Dependents: 1

Suggesters: 0

Stars: 2

Open Issues: 2

dev-master 2026-11-06 08:50 UTC

This package is auto-updated.

Last update: 2026-11-06 14:00:11 UTC


README

Continuous Integration Latest Stable Version Total Downloads Type Coverage License

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

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, call webSocketHub()->publish('channel-name', $payload).
  • Subscribe hook: Hub::onChannelSubscribe() registers a callable invoked with a Connection and the channel name when a client successfully subscribes. Clients send {"o":"sub","c":"channel-name"} (see subscribe() in the browser client; Operation and WireProperties values are injected at serve time). Register during startup from the same kind of code that calls publish(); do not edit generated src/Server/*.php. Each call replaces the previous callback. Duplicate sub for 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.