amphp/http-server
Non-blocking, concurrent HTTP/1.1 and HTTP/2 application server for PHP 8.1+ built on Revolt and Amp (fibers). Includes TLS, middleware, gzip, and integrations for routing, static files, WebSockets, sessions, and more.
composer require amphp/http-server
bootstrap.php or similar):
use Amp\Http\Server\SocketHttpServer;
use Amp\Http\Server\RequestHandler;
use Amp\Http\Server\Response;
use Amp\Http\HttpStatus;
use Amp\Log\ConsoleLogger;
$logger = new ConsoleLogger();
$server = SocketHttpServer::createForDirectAccess($logger);
$requestHandler = new class implements RequestHandler {
public function handleRequest(Request $request): Response {
return new Response(
status: HttpStatus::OK,
headers: ['Content-Type' => 'text/plain'],
body: 'Hello, AMPHP!'
);
}
};
$server->expose('127.0.0.1:8080');
$server->start($requestHandler, new DefaultErrorHandler());
index.php or CLI entrypoint):
Amp\run(function () use ($server) {
Amp\trapSignal([SIGINT, SIGTERM], function () {
$server->stop();
});
});
For a quick static file server, combine with amphp/http-server-static-content:
composer require amphp/http-server-static-content
use Amp\Http\Server\StaticContent\StaticContentHandler;
$staticHandler = new StaticContentHandler(__DIR__.'/public');
$server->start($staticHandler, new DefaultErrorHandler());
$handler = Middleware\stackMiddleware(
$coreHandler,
new LoggingMiddleware(),
new AuthMiddleware(),
new RateLimitMiddleware()
);
amphp/mysql):
$db = new Amp\Mysql\Connection('mysql:host=localhost;dbname=test');
$requestHandler = new class($db) implements RequestHandler {
public function handleRequest(Request $request): Response {
$result = Amp\async(function () {
return $this->db->query('SELECT * FROM users WHERE id = ?', [$request->getUri()->getQuery()]);
});
return new Response(body: json_encode($result->await()));
}
};
amphp/http-server-routeruse Amp\Http\Server\Router;
$router = new Router();
$router->addRoute('GET', '/users', new UsersHandler());
$router->addRoute('POST', '/users', new UserCreateHandler());
$server->start($router, new DefaultErrorHandler());
use Amp\Websocket\Server\WebsocketServer;
$websocketHandler = new class implements WebsocketServer {
public function onOpen(Connection $connection): void { /* ... */ }
public function onMessage(Connection $connection, string $message): void { /* ... */ }
// ...
};
$server->addWebsocketHandler('/ws', $websocketHandler);
$uploadHandler = new class implements RequestHandler {
public function handleRequest(Request $request): Response {
$body = Amp\async(function () use ($request) {
return $request->getBody()->buffer();
});
$file = fopen('uploads/' . uniqid() . '.tmp', 'wb');
fwrite($file, $body->await());
fclose($file);
return new Response(status: HttpStatus::OK);
}
};
Blocking I/O:
file_get_contents($url) (blocks the event loop).Amp\Http\Client\request() or Amp\File\open().sleep(), file_get_contents(), or database queries.Memory Leaks:
fopen() without fclose()) or unbuffered responses can leak memory.await() async operations or use Amp\ByteStream\close().Middleware Order:
stackMiddleware(). Auth should typically run before logging to avoid logging unauthorized requests.Log Requests:
$loggerMiddleware = new class implements Middleware {
public function handleRequest(Request $request, RequestHandler $next): Response {
\Amp\Log\error('Incoming request: ' . $request->getMethod() . ' ' . $request->getUri());
return $next->handleRequest($request);
}
};
Slow Requests:
Amp\Time\delay() to simulate async delays:
Amp\delay(1000); // 1-second delay (non-blocking)
HTTP/2 Issues:
nghttp2 is installed (pecl install nghttp2).$server = SocketHttpServer::createForDirectAccess($logger, [
'http2' => false,
]);
Connection Pooling:
Client instances for repeated requests:
$client = new Amp\Http\Client\Client();
$server->setClientFactory(function () use ($client) {
return $client;
});
Gzip Compression:
$server = SocketHttpServer::createForDirectAccess($logger, [
'compression' => true,
]);
$response = new Response(..., ['Content-Encoding' => 'gzip']);
Concurrency Limits:
$server = SocketHttpServer::createForDirectAccess($logger, [
'max_connections' => 5000,
'max_requests' => 10000,
]);
Custom Error Pages:
$errorHandler = new class implements ErrorHandler {
public function handleError(int $status, ?string $reason, ?Request $request): Response {
return new Response(
status: $status,
body: "<h1>Error $status</h1><p>$reason</p>"
);
}
};
Dynamic Port Binding:
$port = getenv('PORT') ?: 8080;
$server->expose("0.0.0.0:$port");
TLS Configuration:
use Amp\Socket\BindContext;
$context = BindContext::withTls(
__DIR__.'/cert.pem',
__DIR__.'/key.pem'
);
$server->expose('0.0.0.0:443', $context);
Service Provider Integration:
use Amp\Http\Server\SocketHttpServer;
use Illuminate\Support\ServiceProvider;
class AmpHttpServerProvider extends ServiceProvider {
public function register() {
$this->app->singleton(SocketHttpServer::class, function () {
return SocketHttpServer::createForDirectAccess(
$this->app->make(\Psr\Log\LoggerInterface::class)
);
});
}
}
Middleware Adaptation:
amphp/http-server middleware:
$laravelMiddleware = new class implements Middleware {
public function handleRequest(Request $request, RequestHandler $next): Response {
$response = $next->handleRequest($request);
// Modify response (e.g., add headers)
$response->setHeader('X-Powered-By', 'AMPHP');
return $response;
}
};
Route Caching:
$router = new Router();
$router->addRoute('GET', '/', new HomeHandler());
// Cache compiled routes if using a router with this feature.
How can I help you explore Laravel packages today?