Async libraries and fibers12 min read

AMPHP: async libraries, fibers and a Composer HTTP server

AMPHP is a set of Composer libraries for non-blocking PHP built on fibers and Revolt. It is not a separate interpreter. This guide covers its HTTP server model and persistent-process pitfalls.

PHP compatibility and project lifecycle

Source states amphp/http-server requires PHP 8.1+. Explicit PHP 8.4 or 8.5 support for http-server is not stated in the supplied pages (sibling packages mention 8.4/8.5 fixes). Support windows, licenses and prices are unknown.

Official sources checked 2026-10-22. Browser lessons on this site run PHP 8.5.

This guide is based only on the two official AMPHP pages supplied for it.

What AMPHP is

AMPHP describes itself as a collection of event-driven libraries for PHP designed with fibers and concurrency in mind. It is not a PHP interpreter, a fork of PHP, or a standalone runtime binary. Your code runs on the ordinary PHP engine, and AMPHP is installed as Composer packages on top of it. The homepage says PHP shipped fibers in 8.1 and that AMPHP builds higher-level non-blocking I/O libraries on that foundation.

The classification is therefore async libraries plus an application server library. It is not PHP core. It is also not a framework or a CMS such as Drupal, which is a separate product built on PHP. The supplied sources do not discuss Swoole or Open Swoole. Those are separate projects, and AMPHP is neither of them. Do not assume their extension-based behavior applies here.

Architecture

The package list on the homepage shows how the pieces fit together:

  • amphp/amp is the concurrency layer, with Futures and coroutines.
  • amphp/socket, amphp/http, amphp/http-client and amphp/http-server cover networking and HTTP.
  • Database and cache clients are listed for MySQL, Postgres and Redis.
  • amphp/parallel and amphp/cluster cover processes, threads and multi-worker setups.

The HTTP server page says the server is built on the Revolt event loop and on Amp. The server accepts HTTP/1.1 and HTTP/2 connections, turns each request into a Request object and passes it to a RequestHandler. The handler returns a Response. Middleware can wrap handlers to pre-process requests or post-process responses. An ErrorHandler answers malformed requests.

Execution model

In classic PHP-FPM or CGI-style hosting, each request normally starts with fresh PHP state, and that state is discarded afterwards. AMPHP's HTTP server works differently. You start one long-lived PHP process that listens on a socket. According to the source, each call to handleRequest() runs in its own coroutine, so requests are handled cooperatively inside that single process. While one handler waits on non-blocking I/O, other requests proceed. A handler may also start more coroutines with Amp\async().

The source gives one important warning: nearly every built-in PHP I/O function blocks. Examples it names are mysqli_query, file_get_contents and usleep. It says not to use blocking I/O in the HTTP server. Its sleep example would make a second request wait until the first one finishes. Use the AMPHP clients for sockets, files, HTTP, databases and Redis. For unavoidable blocking or long computations, the source suggests the Parallel library.

State leakage and cleanup

Because the process persists, anything stored in static properties, globals, singletons, caches or long-lived handler properties survives across requests and is visible to later and concurrent requests. That is the main risk when moving from classic request lifetimes. Typical leaks include:

  • the current user or tenant kept in a shared object
  • per-request data left in a static variable
  • unbounded in-memory caches
  • open connections or file handles that are never closed

Practical rules for any persistent worker:

  • Keep request-scoped data on the Request object. The source documents request attributes for this, and recommends namespacing attribute names like classes.
  • Treat handler properties and services as shared by all requests.
  • Bound or expire caches, and release resources explicitly.
  • Do not assume a framework's reset logic is present. Check each library you reuse.
  • Restart workers on a schedule only if your deployment tooling supports it. The supplied sources do not describe a restart policy.

The example below deliberately shows both kinds of state.

Practical use cases

The sources point to non-blocking HTTP servers, WebSocket services, concurrent outbound HTTP requests, database access through async clients, and parallel work in separate processes or threads. Whether it suits your application depends on whether your dependencies can avoid blocking I/O. That is not something the packets settle.

Installation approach

The source states the HTTP server requires PHP 8.1 or newer and installs with Composer: composer require amphp/http-server. It also mentions that the optional nghttp2 library can be used through FFI to speed up the server and reduce memory use. The sources give no measured numbers, and none are claimed here.

The homepage lists http-server in the 3.x line and amp in 3.x. It also shows recent release notes, including http-server 3.4.7 dated 2026-10-10. Support windows, end-of-life dates, licenses and prices are not in the packets, so they are unknown.

Our learning track targets PHP 8.4, with PHP 8.5 planned. The http-server page lists only PHP 8.1+. Release notes for sibling packages mention PHP 8.4 and 8.5 fixes, but the supplied pages do not state explicit 8.4 or 8.5 support for http-server itself. The example below uses an installed PHP 8.4 only as the learning-track baseline. Confirm compatibility in an isolated check.

Strengths

  • It works inside normal PHP with Composer and no interpreter replacement.
  • It supports concurrent request handling in one process.
  • It supports HTTP/1.1, HTTP/2, TLS, WebSockets, routing, sessions, middleware and static files, some through separate packages.
  • The libraries are built around fibers, so non-blocking code can be written in a synchronous style.

Limitations

  • Blocking calls can stall the whole server process. Existing blocking libraries need review.
  • Persistent state needs deliberate cleanup.
  • You need to learn Futures, coroutines and the concurrency combinators.
  • Behavior of third-party PHP libraries under a persistent process is not covered by the sources.
  • Some pages in the packet were truncated. Details of the Deployment pages (scaling with extensions, production performance) are unknown here.

Deployment notes

SocketHttpServer::createForDirectAccess() is for direct network access and imposes default limits on connections per IP, total connections and concurrent requests. SocketHttpServer::createForBehindProxy() is for use behind a proxy such as nginx. It requires a list of trusted proxy IPs and the forwarded-header type. The example binds to 127.0.0.1 only, which suits local development. AMPHP's server speaks HTTP directly. It is not FastCGI, so do not mix up the two or expose either to the public internet without a deliberate design. Cloud provisioning is out of scope. For scaling and production tuning, read the official Deployment pages, since their contents were not supplied.

Next steps

Run the example in an isolated environment, request the same URL several times, and watch which values reset and which persist. Then read the HTTP server documentation on routing, sessions and middleware.

Practical examples

These examples have not been run in a live environment yet. Commands and versions follow the official documentation linked on this page; check them before relying on them, and report a problem if one does not work.

Minimal AMPHP HTTP server showing request-scoped versus process-wide state

A single server.php that starts an AMPHP SocketHttpServer on 127.0.0.1:1337 with one middleware and one handler. The middleware stores a value in a request attribute, which is request-scoped. The handler keeps a counter in a property, which persists for the life of the process and shows how state survives across requests. This is a native Composer and CLI example, not a browser exercise.

server.php

<?php

declare(strict_types=1);

use Amp\ByteStream;
use Amp\Http\HttpStatus;
use Amp\Http\Server\DefaultErrorHandler;
use Amp\Http\Server\Middleware;
use Amp\Http\Server\Request;
use Amp\Http\Server\RequestHandler;
use Amp\Http\Server\Response;
use Amp\Http\Server\SocketHttpServer;
use Amp\Log\ConsoleFormatter;
use Amp\Log\StreamHandler;
use Monolog\Logger;
use Monolog\Processor\PsrLogMessageProcessor;

use function Amp\Http\Server\Middleware\stackMiddleware;

require __DIR__ . '/vendor/autoload.php';

$logHandler = new StreamHandler(ByteStream\getStdout());
$logHandler->pushProcessor(new PsrLogMessageProcessor());
$logHandler->setFormatter(new ConsoleFormatter());

$logger = new Logger('server');
$logger->pushHandler($logHandler);

// Request-scoped state: stored on the Request object, gone after the request.
$middleware = new class implements Middleware {
    public function handleRequest(Request $request, RequestHandler $next): Response
    {
        $request->setAttribute('learnphponline.example.started-at', microtime(true));

        return $next->handleRequest($request);
    }
};

// Process-wide state: this property lives as long as the server process.
$requestHandler = new class implements RequestHandler {
    private int $served = 0;

    public function handleRequest(Request $request): Response
    {
        $this->served++;

        $hasAttribute = $request->hasAttribute('learnphponline.example.started-at') ? 'yes' : 'no';
        $path = $request->getUri()->getPath();

        $body = "Hello from AMPHP.\n"
            . "Path: {$path}\n"
            . "Requests served by this process so far: {$this->served}\n"
            . "Request-scoped attribute set by middleware: {$hasAttribute}\n";

        return new Response(
            HttpStatus::OK,
            ['content-type' => 'text/plain; charset=utf-8'],
            $body,
        );
    }
};

$stackedHandler = stackMiddleware($requestHandler, $middleware);
$errorHandler = new DefaultErrorHandler();

$server = SocketHttpServer::createForDirectAccess($logger);
$server->expose('127.0.0.1:1337');
$server->start($stackedHandler, $errorHandler);

// Serve requests until SIGINT or SIGTERM is received by the process.
Amp\trapSignal([SIGINT, SIGTERM]);

$server->stop();

Install and run

composer require amphp/http-server amphp/log monolog/monolog
php server.php
curl http://127.0.0.1:1337/first
curl http://127.0.0.1:1337/second

Expected behavior

Run the first two commands from the directory containing server.php; the server logs to the console and listens on 127.0.0.1:1337. Run the curl commands from a second terminal. Each response is plain text. The process-wide counter should increase from 1 to 2 on the second request without resetting, while the request-scoped attribute line should say yes on every request. Stop the server with Ctrl+C.

Not a browser exercise: it needs the PHP CLI, Composer and network access to install packages. The source states PHP 8.1+ for amphp/http-server and does not explicitly confirm PHP 8.4 or 8.5, so verify on 8.4 in an isolated check. Dependency versions are resolved by Composer and are not pinned here. Signal handling behavior on all platforms is not covered by the sources. The counter is deliberately never reset, so do not copy that pattern into real applications.