Non-blocking HTTP server for Duyler Framework worker mode with full PSR-7 support and integrated Worker Pool.
- Non-blocking I/O - Works seamlessly with Duyler Event Bus MainCyclic state
- PSR-7 Compatible - Full support for PSR-7 HTTP messages
- HTTP & HTTPS - Support for both HTTP and HTTPS protocols
- WebSocket Support - RFC 6455 compliant WebSocket implementation with zero-cost abstraction
- File Upload/Download - Complete multipart form-data and file streaming support
- Static Files - Built-in static file serving with LRU caching
- Keep-Alive - HTTP persistent connections support
- Range Requests - Partial content support for large file downloads
- Rate Limiting - Sliding window rate limiter with configurable limits
- Graceful Shutdown - Clean server termination with timeout
- Server Metrics - Built-in performance and health monitoring
- High Performance - Optimized for long-running worker processes
- Process Management - Fork-based worker processes with auto-restart
- Load Balancing - Least Connections and Round Robin algorithms
- IPC System - Unix domain sockets with FD passing support
- Dual Architecture - FD Passing (Linux) and Shared Socket (Docker/fallback)
- Auto CPU Detection - Automatic worker count based on CPU cores
- Signal Handling - Graceful shutdown via SIGTERM/SIGINT
- PHP 8.4 or higher
- ext-sockets (usually pre-installed)
- ext-pcntl (for Worker Pool)
- ext-posix (for Worker Pool)
composer require duyler/http-serverEvent-Driven Worker Mode
For long running applications with Event Bus (like Duyler Framework):
use Duyler\HttpServer\Config\ServerConfig;
use Duyler\HttpServer\Server;
use Duyler\HttpServer\WorkerPool\Config\WorkerPoolConfig;
use Duyler\HttpServer\WorkerPool\Master\SharedSocketMaster;
use Duyler\HttpServer\WorkerPool\Worker\EventDrivenWorkerInterface;
use Nyholm\Psr7\Response;
class MyApp implements EventDrivenWorkerInterface
{
public function run(int $workerId, Server $server): void
{
// IMPORTANT: DO NOT call $server->start()!
// Master manages the socket and passes connections to Server.
// Server is automatically running in Worker Pool mode.
// Initialize your application ONCE
$application = new Application($workerId, $server);
$application->run();
}
}
$serverConfig = new ServerConfig(host: '0.0.0.0', port: 8080);
$workerPoolConfig = WorkerPoolConfig::auto($serverConfig);
$app = new MyApp();
$master = new SharedSocketMaster(
config: $workerPoolConfig,
serverConfig: $serverConfig,
eventDrivenWorker: $app,
);
$master->start();See examples/event-driven-worker.php for complete example.
use Duyler\HttpServer\Server;
use Duyler\HttpServer\Config\ServerConfig;
use Nyholm\Psr7\Response;
$config = new ServerConfig(
host: '0.0.0.0',
port: 8080,
);
$server = new Server($config);
// Check if server started successfully
if (!$server->start()) {
die('Failed to start HTTP server');
}
// In your event loop
while (true) {
if ($server->hasRequest()) {
$request = $server->getRequest();
// Check for null (race condition or error)
if ($request === null) {
continue;
}
// Process request
$response = new Response(200, [], 'Hello World!');
$server->respond($response);
}
// Do other work...
}use Duyler\HttpServer\Config\ServerConfig;
$config = new ServerConfig(
// Network
host: '0.0.0.0', // Bind address
port: 8080, // Bind port
// SSL/TLS
ssl: false, // Enable HTTPS
sslCert: null, // Path to SSL certificate
sslKey: null, // Path to SSL private key
// Static Files
publicPath: null, // Path to public directory
// Timeouts
requestTimeout: 30, // Request timeout in seconds
connectionTimeout: 60, // Connection timeout in seconds
// Limits
maxConnections: 1000, // Maximum concurrent connections
maxRequestSize: 10485760, // Max request size (10MB)
bufferSize: 8192, // Read buffer size
// Keep-Alive
enableKeepAlive: true, // Enable persistent connections
keepAliveTimeout: 30, // Keep-alive timeout in seconds
keepAliveMaxRequests: 100, // Max requests per connection
// Static Cache
enableStaticCache: true, // Enable in-memory static file cache
staticCacheSize: 52428800, // Max cache size (50MB)
// Rate Limiting
enableRateLimit: false, // Enable rate limiting
rateLimitRequests: 100, // Max requests per window
rateLimitWindow: 60, // Rate limit window in seconds
// Performance
maxAcceptsPerCycle: 10, // Max new connections per cycle
// Debug
debugMode: false, // Enable debug logging mode
);$config = new ServerConfig(
host: '0.0.0.0',
port: 443,
ssl: true,
sslCert: '/path/to/certificate.pem',
sslKey: '/path/to/private-key.pem',
);
$server = new Server($config);
$server->start();use Duyler\HttpServer\WebSocket\WebSocketServer;
use Duyler\HttpServer\WebSocket\WebSocketConfig;
use Duyler\HttpServer\WebSocket\Connection;
use Duyler\HttpServer\WebSocket\Message;
// Secure by default - origin validation is enabled
$wsConfig = new WebSocketConfig(
maxMessageSize: 1048576,
pingInterval: 30,
// validateOrigin defaults to true for security
// allowedOrigins defaults to ['*'] - customize for your domains
);
$ws = new WebSocketServer($wsConfig);
$ws->on('connect', function (Connection $conn) {
echo "New connection: {$conn->getId()}\n";
});
$ws->on('message', function (Connection $conn, Message $message) {
$data = $message->getJson();
$conn->send([
'type' => 'echo',
'data' => $data,
]);
});
$ws->on('close', function (Connection $conn, int $code, string $reason) {
echo "Connection closed: $code\n";
});
$server->attachWebSocket('/ws', $ws);
$server->start();
while (true) {
if ($server->hasRequest()) {
$request = $server->getRequest();
if ($request !== null) {
$response = new Response(200, [], 'Hello');
$server->respond($response);
}
}
usleep(1000);
}For public WebSocket endpoints that accept connections from any origin:
// WARNING: This configuration is insecure and exposes your WebSocket
// to CSRF attacks. Only use for truly public endpoints.
$wsConfig = new WebSocketConfig(
validateOrigin: false, // Explicit opt-out of origin validation
allowedOrigins: ['*'], // Explicit wildcard
);See examples/websocket-chat.php for a complete chat application example.
use Duyler\HttpServer\Handler\StaticFileHandler;
$staticHandler = new StaticFileHandler(
publicPath: '/path/to/public',
enableCache: true,
maxCacheSize: 52428800, // 50MB
);
while (true) {
if ($server->hasRequest()) {
$request = $server->getRequest();
// Try to serve static file first
$response = $staticHandler->handle($request);
if ($response === null) {
// Not a static file, handle dynamically
$response = handleDynamicRequest($request);
}
$server->respond($response);
}
}use Duyler\HttpServer\Handler\FileDownloadHandler;
$fileHandler = new FileDownloadHandler();
$response = $fileHandler->download(
filePath: '/path/to/file.pdf',
filename: 'document.pdf',
mimeType: 'application/pdf'
);
$server->respond($response);// Uploads are automatically parsed from multipart/form-data
$request = $server->getRequest();
$uploadedFiles = $request->getUploadedFiles();
foreach ($uploadedFiles as $field => $file) {
/** @var \Psr\Http\Message\UploadedFileInterface $file */
if ($file->getError() === UPLOAD_ERR_OK) {
$file->moveTo('/path/to/uploads/' . $file->getClientFilename());
}
}$config = new ServerConfig(
enableRateLimit: true,
rateLimitRequests: 100, // Max 100 requests
rateLimitWindow: 60, // Per 60 seconds (per IP)
);
$server = new Server($config);
$server->start();
// Rate limiting is applied automatically
// Clients exceeding limits receive 429 Too Many Requests$server = new Server(new ServerConfig());
$server->start();
// Register shutdown handler
pcntl_signal(SIGTERM, function() use ($server) {
$success = $server->shutdown(30); // 30 second timeout
exit($success ? 0 : 1);
});
while (true) {
if ($server->hasRequest()) {
$request = $server->getRequest();
$response = new Response(200, [], 'OK');
$server->respond($response);
}
}$server = new Server(new ServerConfig());
$server->start();
// Get metrics periodically
$metrics = $server->getMetrics();
// [
// 'uptime_seconds' => 3600,
// 'total_requests' => 10000,
// 'successful_requests' => 9850,
// 'failed_requests' => 150,
// 'active_connections' => 5,
// 'total_connections' => 10050,
// 'closed_connections' => 10045,
// 'timed_out_connections' => 10,
// 'cache_hits' => 8500,
// 'cache_misses' => 1500,
// 'cache_hit_rate' => 85.0,
// 'avg_request_duration_ms' => 12.3,
// 'min_request_duration_ms' => 1.2,
// 'max_request_duration_ms' => 450.5,
// 'requests_per_second' => 2.78,
// ]start(): bool- Start the server (returns false on failure)stop(): void- Stop the servershutdown(int $timeout): bool- Graceful shutdown with timeoutreset(): void- Reset the server staterestart(): void- Restart the serverhasRequest(): bool- Check if there's a pending request (non-blocking)getRequest(): ?ServerRequestInterface- Get the next request (null if unavailable)hasPendingResponse(): bool- Check needs respondrespond(ResponseInterface): void- Send response for the current requestgetMetrics(): array- Get server performance metricssetLogger(LoggerInterface)- Set external LoggerattachWebSocket(string $path, WebSocketServer $ws): void- Attach WebSocketServeraddExternalConnection(Socket $clientSocket, array $metadata): void- Add external connection from Worker Pool
handle(ServerRequestInterface): ?ResponseInterface- Handle static file requestgetCacheStats(): array- Get cache statisticsclearCache(): void- Clear the cache
download(string $filePath, ?string $filename, ?string $mimeType): ResponseInterfacedownloadRange(string $filePath, int $start, int $end, ...): ResponseInterfaceparseRangeHeader(string $rangeHeader, int $fileSize): ?array
# Run all tests
composer test
# Run with coverage (requires Xdebug or pcov)
composer test:coverage
# Run PHPStan
composer phpstan- Enable Keep-Alive - Reduces connection overhead for multiple requests
- Use Static Cache - Cache frequently accessed static files in memory
- Adjust Buffer Size - Increase for high-throughput scenarios
- Set Appropriate Timeouts - Balance between responsiveness and resource usage
- Limit Max Connections - Prevent resource exhaustion
- Worker Pool - Dual architecture (FD Passing + Shared Socket)
- WebSocket - RFC 6455 compliant implementation
- MasterFactory with auto-detection
- PSR-3 Logger integration
- Worker Pool metrics and monitoring
- Enhanced documentation
- HTTP/2 support
- gRPC support
- Advanced Worker Pool features
Contributions are welcome! Please feel free to submit a Pull Request.
The MIT License (MIT). Please see License File for more information.