ReactPHP HTTP

Asynchronous HTTP client in ReactPHP ecosystem. Achieves high-concurrency HTTP communication through event-driven architecture. Optimized for non-blocking I/O, streaming, and real-time communication. Supports high-performance application development beyond traditional PHP constraints.

HTTP ClientPHPAsynchronousEvent-DrivenStreamingReactPHP

GitHub Overview

reactphp/http

Event-driven, streaming HTTP client and server implementation for ReactPHP.

Stars775
Watchers38
Forks167
Created:May 10, 2012
Language:PHP
License:MIT License

Topics

httphttp-clienthttp-serverhttpsphpreactphpresponse-streamstreaming-requeststreaming-response

Star History

reactphp/http Star History
Data as of: 10/22/2025, 09:54 AM

Library

ReactPHP HTTP

Overview

ReactPHP HTTP is "an asynchronous HTTP client and server library for PHP" developed as a core component of the ReactPHP ecosystem. It enables high-performance HTTP communication through event-driven, non-blocking I/O, efficiently handling concurrent connections and long-lived connections that were difficult with traditional PHP. With modern API design based on PSR-7 message abstraction and Promise-based asynchronous patterns, it provides innovative solutions for application development requiring massive HTTP request processing and real-time communication.

Details

ReactPHP HTTP 2025 edition is a mature library with over 10 years of development experience as a pioneer in asynchronous HTTP communication for PHP. Its design based on ReactPHP's event loop enables efficient handling of thousands of simultaneous HTTP connections in a single process. It provides complete support for PSR-7 HTTP message interfaces and standard HTTP request/response processing. Advanced features include streaming processing for efficient large file transmission, WebSocket support, and custom middleware support. Promise/A+ compliant asynchronous patterns enable building high-performance web applications that were difficult to achieve with traditional blocking I/O.

Key Features

  • Event-Driven Architecture: Non-blocking I/O through ReactPHP event loop
  • PSR-7 Compliance: Complete support for standard HTTP message interfaces
  • Promise-Based: Intuitive API design for asynchronous processing
  • Streaming Support: Efficient transmission and reception of large data
  • High Concurrency: Processing thousands of connections in a single process
  • Extensibility: Custom middleware and plugin support

Pros and Cons

Pros

  • Enables true asynchronous HTTP processing impossible with traditional PHP
  • Event-driven design provides extremely high memory efficiency and processing performance
  • High compatibility with other PHP libraries through PSR-7 compliance
  • Intuitive and readable asynchronous code through Promise patterns
  • Complete integration with ReactPHP ecosystem for extensibility
  • Excellent scalability suited for massive concurrent request processing

Cons

  • Relatively high learning curve for asynchronous programming
  • Paradigm significantly different from traditional PHP development patterns
  • Debugging and troubleshooting can become complex
  • Not all PHP libraries support asynchronous operations
  • Potential overhead for small-scale synchronous processing
  • Error handling can become complex with Promise chains

Reference Pages

Code Examples

Installation and Basic Setup

# Installation via Composer
composer require react/http react/socket

# Development dependencies
composer require --dev react/http react/promise-timer

# Full ReactPHP ecosystem
composer require react/socket react/stream react/promise react/http

# Project initialization example
mkdir reactphp-http-project
cd reactphp-http-project
composer init
composer require react/http psr/http-message

Basic HTTP Client Functionality

<?php
require 'vendor/autoload.php';

use React\EventLoop\Loop;
use React\Http\Browser;
use React\Promise\Promise;
use Psr\Http\Message\ResponseInterface;

// Initialize event loop
$loop = Loop::get();

// Create HTTP browser (client)
$browser = new Browser($loop);

// Basic GET request
function simpleGetRequest($browser) {
    echo "=== Basic GET Request ===\n";
    
    $promise = $browser->get('https://api.example.com/users');
    
    $promise->then(
        function (ResponseInterface $response) {
            echo "Status: " . $response->getStatusCode() . "\n";
            echo "Headers: " . json_encode($response->getHeaders(), JSON_PRETTY_PRINT) . "\n";
            echo "Body: " . $response->getBody() . "\n\n";
            return $response;
        },
        function (Exception $error) {
            echo "Error: " . $error->getMessage() . "\n\n";
        }
    );
    
    return $promise;
}

// Multiple concurrent GET requests
function concurrentGetRequests($browser) {
    echo "=== Concurrent GET Requests ===\n";
    
    $urls = [
        'https://api.example.com/users',
        'https://api.example.com/posts', 
        'https://api.example.com/comments'
    ];
    
    $promises = [];
    foreach ($urls as $url) {
        $promises[] = $browser->get($url)->then(
            function (ResponseInterface $response) use ($url) {
                echo "Completed: $url (Status: " . $response->getStatusCode() . ")\n";
                return [
                    'url' => $url,
                    'status' => $response->getStatusCode(),
                    'body' => (string) $response->getBody()
                ];
            },
            function (Exception $error) use ($url) {
                echo "Failed: $url - " . $error->getMessage() . "\n";
                return null;
            }
        );
    }
    
    // Wait for all requests to complete
    return \React\Promise\all($promises)->then(
        function ($results) {
            echo "All requests completed successfully!\n";
            echo "Results count: " . count(array_filter($results)) . "\n\n";
            return $results;
        }
    );
}

// Request with timeout
function requestWithTimeout($browser) {
    echo "=== Request with Timeout ===\n";
    
    $promise = $browser->withTimeout(5.0)->get('https://api.example.com/slow-endpoint');
    
    return $promise->then(
        function (ResponseInterface $response) {
            echo "Request completed within timeout\n";
            echo "Status: " . $response->getStatusCode() . "\n\n";
            return $response;
        },
        function (Exception $error) {
            if ($error instanceof \React\Promise\Timer\TimeoutException) {
                echo "Request timed out after 5 seconds\n\n";
            } else {
                echo "Request failed: " . $error->getMessage() . "\n\n";
            }
        }
    );
}

// Main execution
$browser = new Browser($loop);

// Sequential execution example
simpleGetRequest($browser)
    ->then(function() use ($browser) {
        return concurrentGetRequests($browser);
    })
    ->then(function() use ($browser) {
        return requestWithTimeout($browser);
    })
    ->then(function() use ($loop) {
        echo "All examples completed. Stopping event loop.\n";
        $loop->stop();
    });

// Start event loop
echo "Starting ReactPHP HTTP client examples...\n";
$loop->run();

POST/PUT/DELETE Requests and Authentication

<?php
require 'vendor/autoload.php';

use React\EventLoop\Loop;
use React\Http\Browser;
use Psr\Http\Message\ResponseInterface;

$loop = Loop::get();
$browser = new Browser($loop);

// POST request (sending JSON data)
function postJsonRequest($browser) {
    echo "=== POST JSON Request ===\n";
    
    $userData = [
        'name' => 'John Doe',
        'email' => '[email protected]',
        'age' => 30
    ];
    
    $headers = [
        'Content-Type' => 'application/json',
        'Authorization' => 'Bearer your-access-token',
        'User-Agent' => 'ReactPHP-HTTP-Client/1.0'
    ];
    
    $promise = $browser->post(
        'https://api.example.com/users',
        $headers,
        json_encode($userData)
    );
    
    return $promise->then(
        function (ResponseInterface $response) {
            echo "POST Status: " . $response->getStatusCode() . "\n";
            echo "Response: " . $response->getBody() . "\n\n";
            return $response;
        },
        function (Exception $error) {
            echo "POST Error: " . $error->getMessage() . "\n\n";
        }
    );
}

// Form data POST
function postFormRequest($browser) {
    echo "=== POST Form Request ===\n";
    
    $formData = http_build_query([
        'username' => 'testuser',
        'password' => 'testpass123',
        'remember' => '1'
    ]);
    
    $headers = [
        'Content-Type' => 'application/x-www-form-urlencoded',
        'Content-Length' => strlen($formData)
    ];
    
    return $browser->post('https://api.example.com/login', $headers, $formData)
        ->then(
            function (ResponseInterface $response) {
                echo "Form POST Status: " . $response->getStatusCode() . "\n";
                
                // Cookie retrieval example
                if ($response->hasHeader('Set-Cookie')) {
                    $cookies = $response->getHeader('Set-Cookie');
                    echo "Received Cookies: " . implode(', ', $cookies) . "\n";
                }
                
                echo "Login Response: " . $response->getBody() . "\n\n";
                return $response;
            },
            function (Exception $error) {
                echo "Form POST Error: " . $error->getMessage() . "\n\n";
            }
        );
}

// PUT request (data update)
function putRequest($browser) {
    echo "=== PUT Request ===\n";
    
    $updateData = [
        'name' => 'John Doe (Updated)',
        'email' => '[email protected]',
        'age' => 31
    ];
    
    $headers = [
        'Content-Type' => 'application/json',
        'Authorization' => 'Bearer your-access-token'
    ];
    
    return $browser->put(
        'https://api.example.com/users/123',
        $headers,
        json_encode($updateData)
    )->then(
        function (ResponseInterface $response) {
            echo "PUT Status: " . $response->getStatusCode() . "\n";
            echo "Updated User: " . $response->getBody() . "\n\n";
            return $response;
        },
        function (Exception $error) {
            echo "PUT Error: " . $error->getMessage() . "\n\n";
        }
    );
}

// DELETE request
function deleteRequest($browser) {
    echo "=== DELETE Request ===\n";
    
    $headers = [
        'Authorization' => 'Bearer your-access-token'
    ];
    
    return $browser->delete('https://api.example.com/users/123', $headers)
        ->then(
            function (ResponseInterface $response) {
                $status = $response->getStatusCode();
                echo "DELETE Status: $status\n";
                
                if ($status === 204) {
                    echo "User successfully deleted\n\n";
                } else {
                    echo "Delete Response: " . $response->getBody() . "\n\n";
                }
                
                return $response;
            },
            function (Exception $error) {
                echo "DELETE Error: " . $error->getMessage() . "\n\n";
            }
        );
}

// Authenticated request chain
function authenticatedRequestChain($browser) {
    echo "=== Authenticated Request Chain ===\n";
    
    // 1. Login
    $loginData = ['username' => 'admin', 'password' => 'admin123'];
    
    return $browser->post(
        'https://api.example.com/auth/login',
        ['Content-Type' => 'application/json'],
        json_encode($loginData)
    )->then(
        function (ResponseInterface $response) use ($browser) {
            $loginResponse = json_decode($response->getBody(), true);
            $token = $loginResponse['access_token'] ?? null;
            
            if (!$token) {
                throw new Exception('Login failed: No access token received');
            }
            
            echo "Login successful, token received\n";
            
            // 2. Access protected resource
            return $browser->get(
                'https://api.example.com/admin/users',
                ['Authorization' => "Bearer $token"]
            );
        }
    )->then(
        function (ResponseInterface $response) {
            echo "Protected resource access successful\n";
            echo "Admin Users: " . $response->getBody() . "\n\n";
            return $response;
        }
    )->otherwise(
        function (Exception $error) {
            echo "Authentication chain failed: " . $error->getMessage() . "\n\n";
        }
    );
}

// Main execution
postJsonRequest($browser)
    ->then(function() use ($browser) {
        return postFormRequest($browser);
    })
    ->then(function() use ($browser) {
        return putRequest($browser);
    })
    ->then(function() use ($browser) {
        return deleteRequest($browser);
    })
    ->then(function() use ($browser) {
        return authenticatedRequestChain($browser);
    })
    ->then(function() use ($loop) {
        echo "All POST/PUT/DELETE examples completed.\n";
        $loop->stop();
    });

$loop->run();

Streaming and File Processing

<?php
require 'vendor/autoload.php';

use React\EventLoop\Loop;
use React\Http\Browser;
use React\Stream\WritableResourceStream;
use React\Stream\ReadableResourceStream;
use Psr\Http\Message\ResponseInterface;

$loop = Loop::get();
$browser = new Browser($loop);

// Processing streaming responses
function streamingResponse($browser) {
    echo "=== Streaming Response ===\n";
    
    return $browser->requestStreaming('GET', 'https://api.example.com/large-dataset')
        ->then(
            function (ResponseInterface $response) {
                echo "Streaming response started (Status: " . $response->getStatusCode() . ")\n";
                
                $body = $response->getBody();
                
                // Read data from stream
                $dataReceived = 0;
                $body->on('data', function ($chunk) use (&$dataReceived) {
                    $dataReceived += strlen($chunk);
                    echo "Received chunk: " . strlen($chunk) . " bytes (Total: $dataReceived bytes)\n";
                    
                    // Process chunk data here
                    // e.g., save to database, write to file, etc.
                });
                
                $body->on('end', function () use (&$dataReceived) {
                    echo "Streaming completed. Total received: $dataReceived bytes\n\n";
                });
                
                $body->on('error', function (Exception $error) {
                    echo "Stream error: " . $error->getMessage() . "\n\n";
                });
                
                return $response;
            },
            function (Exception $error) {
                echo "Streaming request failed: " . $error->getMessage() . "\n\n";
            }
        );
}

// File download
function downloadFile($browser, $url, $localPath) {
    echo "=== File Download ===\n";
    echo "Downloading: $url\n";
    
    $file = fopen($localPath, 'w');
    if (!$file) {
        echo "Cannot open file for writing: $localPath\n\n";
        return \React\Promise\resolve();
    }
    
    $fileStream = new WritableResourceStream($file, $browser->getLoop());
    
    return $browser->requestStreaming('GET', $url)
        ->then(
            function (ResponseInterface $response) use ($fileStream, $localPath) {
                echo "Download started (Status: " . $response->getStatusCode() . ")\n";
                
                if ($response->hasHeader('Content-Length')) {
                    $contentLength = $response->getHeaderLine('Content-Length');
                    echo "File size: $contentLength bytes\n";
                }
                
                $body = $response->getBody();
                $downloaded = 0;
                
                $body->on('data', function ($chunk) use ($fileStream, &$downloaded) {
                    $fileStream->write($chunk);
                    $downloaded += strlen($chunk);
                    
                    if ($downloaded % 10240 === 0) { // Progress every 10KB
                        echo "Downloaded: $downloaded bytes\n";
                    }
                });
                
                $body->on('end', function () use ($fileStream, $localPath, &$downloaded) {
                    $fileStream->end();
                    echo "Download completed: $localPath ($downloaded bytes)\n\n";
                });
                
                $body->on('error', function (Exception $error) use ($fileStream) {
                    $fileStream->close();
                    echo "Download error: " . $error->getMessage() . "\n\n";
                });
                
                return $response;
            }
        );
}

// File upload
function uploadFile($browser, $url, $filePath) {
    echo "=== File Upload ===\n";
    echo "Uploading: $filePath\n";
    
    if (!file_exists($filePath)) {
        echo "File not found: $filePath\n\n";
        return \React\Promise\resolve();
    }
    
    $fileSize = filesize($filePath);
    echo "File size: $fileSize bytes\n";
    
    $file = fopen($filePath, 'r');
    $fileStream = new ReadableResourceStream($file, $browser->getLoop());
    
    $headers = [
        'Content-Type' => 'application/octet-stream',
        'Content-Length' => $fileSize,
        'Authorization' => 'Bearer your-upload-token'
    ];
    
    return $browser->requestStreaming('POST', $url, $headers, $fileStream)
        ->then(
            function (ResponseInterface $response) use ($filePath) {
                echo "Upload response (Status: " . $response->getStatusCode() . ")\n";
                echo "Upload response body: " . $response->getBody() . "\n\n";
                return $response;
            },
            function (Exception $error) use ($filePath) {
                echo "Upload failed for $filePath: " . $error->getMessage() . "\n\n";
            }
        );
}

// Processing large data in chunks
function processLargeDataInChunks($browser) {
    echo "=== Large Data Chunk Processing ===\n";
    
    $processedChunks = 0;
    $buffer = '';
    
    return $browser->requestStreaming('GET', 'https://api.example.com/large-json-array')
        ->then(
            function (ResponseInterface $response) use (&$processedChunks, &$buffer) {
                echo "Starting large data processing\n";
                
                $body = $response->getBody();
                
                $body->on('data', function ($chunk) use (&$processedChunks, &$buffer) {
                    $buffer .= $chunk;
                    
                    // Split data by newlines and process
                    $lines = explode("\n", $buffer);
                    $buffer = array_pop($lines); // Keep last incomplete line in buffer
                    
                    foreach ($lines as $line) {
                        if (trim($line)) {
                            // Process each line as JSON
                            $data = json_decode($line, true);
                            if ($data) {
                                $processedChunks++;
                                echo "Processed item $processedChunks: " . ($data['id'] ?? 'unknown') . "\n";
                                
                                // Perform actual data processing here
                                // e.g., save to database, transform data, etc.
                            }
                        }
                    }
                });
                
                $body->on('end', function () use (&$processedChunks, &$buffer) {
                    // Process remaining data
                    if (trim($buffer)) {
                        $data = json_decode($buffer, true);
                        if ($data) {
                            $processedChunks++;
                            echo "Processed final item: " . ($data['id'] ?? 'unknown') . "\n";
                        }
                    }
                    
                    echo "Large data processing completed. Total items: $processedChunks\n\n";
                });
                
                return $response;
            }
        );
}

// Main execution
streamingResponse($browser)
    ->then(function() use ($browser) {
        return downloadFile($browser, 'https://example.com/sample-file.zip', '/tmp/downloaded-file.zip');
    })
    ->then(function() use ($browser) {
        return uploadFile($browser, 'https://api.example.com/upload', '/tmp/test-upload.txt');
    })
    ->then(function() use ($browser) {
        return processLargeDataInChunks($browser);
    })
    ->then(function() use ($loop) {
        echo "All streaming examples completed.\n";
        $loop->stop();
    });

$loop->run();

Error Handling and Retry Functionality

<?php
require 'vendor/autoload.php';

use React\EventLoop\Loop;
use React\Http\Browser;
use React\Promise\Timer\TimeoutException;
use Psr\Http\Message\ResponseInterface;

$loop = Loop::get();
$browser = new Browser($loop);

// Comprehensive error handling
function comprehensiveErrorHandling($browser) {
    echo "=== Comprehensive Error Handling ===\n";
    
    return $browser->get('https://api.example.com/unreliable-endpoint')
        ->then(
            function (ResponseInterface $response) {
                $status = $response->getStatusCode();
                echo "Response received with status: $status\n";
                
                // HTTP status code specific handling
                if ($status >= 200 && $status < 300) {
                    echo "Success: Request completed successfully\n";
                    return $response;
                } elseif ($status >= 400 && $status < 500) {
                    throw new Exception("Client error: HTTP $status - " . $response->getReasonPhrase());
                } elseif ($status >= 500) {
                    throw new Exception("Server error: HTTP $status - " . $response->getReasonPhrase());
                } else {
                    throw new Exception("Unexpected status code: $status");
                }
            }
        )->otherwise(
            function (Exception $error) {
                echo "Error occurred: " . get_class($error) . "\n";
                echo "Error message: " . $error->getMessage() . "\n";
                
                // Error type specific handling
                if ($error instanceof TimeoutException) {
                    echo "Error type: Request timeout\n";
                } elseif ($error instanceof \React\Socket\ConnectionException) {
                    echo "Error type: Connection error\n";
                } elseif ($error instanceof \InvalidArgumentException) {
                    echo "Error type: Invalid request parameters\n";
                } else {
                    echo "Error type: General error\n";
                }
                
                echo "Error handling completed\n\n";
                throw $error; // Re-throw if needed
            }
        );
}

// Request with retry functionality
function requestWithRetry($browser, $url, $maxRetries = 3, $backoffDelay = 1.0) {
    echo "=== Request with Retry Logic ===\n";
    echo "Attempting request: $url\n";
    
    $attempt = 0;
    
    $makeRequest = function() use ($browser, $url, $maxRetries, $backoffDelay, &$attempt, &$makeRequest) {
        $attempt++;
        echo "Attempt $attempt/$maxRetries\n";
        
        return $browser->withTimeout(10.0)->get($url)
            ->then(
                function (ResponseInterface $response) use ($attempt) {
                    $status = $response->getStatusCode();
                    echo "Request successful on attempt $attempt (Status: $status)\n\n";
                    return $response;
                }
            )->otherwise(
                function (Exception $error) use ($attempt, $maxRetries, $backoffDelay, $makeRequest, $url) {
                    echo "Attempt $attempt failed: " . $error->getMessage() . "\n";
                    
                    // Check if error is retryable
                    $shouldRetry = false;
                    
                    if ($error instanceof TimeoutException) {
                        $shouldRetry = true;
                        echo "Timeout error - retryable\n";
                    } elseif ($error instanceof \React\Socket\ConnectionException) {
                        $shouldRetry = true;
                        echo "Connection error - retryable\n";
                    } elseif (method_exists($error, 'getResponse')) {
                        $response = $error->getResponse();
                        if ($response && $response->getStatusCode() >= 500) {
                            $shouldRetry = true;
                            echo "Server error - retryable\n";
                        }
                    }
                    
                    if ($shouldRetry && $attempt < $maxRetries) {
                        $delay = $backoffDelay * pow(2, $attempt - 1); // Exponential backoff
                        echo "Retrying in $delay seconds...\n";
                        
                        return \React\Promise\Timer\sleep($delay, $browser->getLoop())
                            ->then($makeRequest);
                    } else {
                        echo "Max retries reached or non-retryable error\n\n";
                        throw $error;
                    }
                }
            );
    };
    
    return $makeRequest();
}

// Circuit breaker pattern
class CircuitBreaker {
    private $failureCount = 0;
    private $lastFailureTime = null;
    private $state = 'CLOSED'; // CLOSED, OPEN, HALF_OPEN
    private $maxFailures;
    private $recoveryTimeout;
    
    public function __construct($maxFailures = 5, $recoveryTimeout = 60) {
        $this->maxFailures = $maxFailures;
        $this->recoveryTimeout = $recoveryTimeout;
    }
    
    public function call($browser, $url) {
        echo "Circuit breaker state: {$this->state}\n";
        
        if ($this->state === 'OPEN') {
            if (time() - $this->lastFailureTime > $this->recoveryTimeout) {
                $this->state = 'HALF_OPEN';
                echo "Circuit breaker transitioning to HALF_OPEN\n";
            } else {
                echo "Circuit breaker is OPEN - request blocked\n\n";
                return \React\Promise\reject(new Exception('Circuit breaker is OPEN'));
            }
        }
        
        return $browser->get($url)
            ->then(
                function (ResponseInterface $response) {
                    // Success - reset failure count
                    $this->failureCount = 0;
                    if ($this->state === 'HALF_OPEN') {
                        $this->state = 'CLOSED';
                        echo "Circuit breaker recovered - state: CLOSED\n";
                    }
                    return $response;
                }
            )->otherwise(
                function (Exception $error) {
                    // Failure - increment counter
                    $this->failureCount++;
                    $this->lastFailureTime = time();
                    
                    echo "Request failed (failure count: {$this->failureCount})\n";
                    
                    if ($this->failureCount >= $this->maxFailures) {
                        $this->state = 'OPEN';
                        echo "Circuit breaker opened due to excessive failures\n";
                    }
                    
                    throw $error;
                }
            );
    }
}

function circuitBreakerExample($browser) {
    echo "=== Circuit Breaker Pattern ===\n";
    
    $circuitBreaker = new CircuitBreaker(3, 30);
    
    // Test circuit breaker with multiple requests
    $promises = [];
    for ($i = 1; $i <= 7; $i++) {
        $promises[] = $circuitBreaker->call($browser, 'https://api.example.com/unstable-endpoint')
            ->then(
                function (ResponseInterface $response) use ($i) {
                    echo "Request $i succeeded\n";
                    return $response;
                },
                function (Exception $error) use ($i) {
                    echo "Request $i failed: " . $error->getMessage() . "\n";
                    return null; // Continue with other requests
                }
            );
    }
    
    return \React\Promise\all($promises)->then(
        function ($results) {
            echo "Circuit breaker test completed\n\n";
            return $results;
        }
    );
}

// Main execution
comprehensiveErrorHandling($browser)
    ->otherwise(function() { return null; }) // Catch errors and continue
    ->then(function() use ($browser) {
        return requestWithRetry($browser, 'https://api.example.com/sometimes-fails', 3, 1.0);
    })
    ->otherwise(function() { return null; }) // Catch errors and continue
    ->then(function() use ($browser) {
        return circuitBreakerExample($browser);
    })
    ->then(function() use ($loop) {
        echo "All error handling examples completed.\n";
        $loop->stop();
    });

$loop->run();

HTTP Server Implementation

<?php
require 'vendor/autoload.php';

use React\EventLoop\Loop;
use React\Http\HttpServer;
use React\Http\Message\Response;
use React\Socket\SocketServer;
use Psr\Http\Message\ServerRequestInterface;

$loop = Loop::get();

// Create and configure HTTP server
function createHttpServer($loop) {
    echo "=== ReactPHP HTTP Server ===\n";
    
    $server = new HttpServer($loop, function (ServerRequestInterface $request) {
        $method = $request->getMethod();
        $path = $request->getUri()->getPath();
        $query = $request->getQueryParams();
        
        echo "Request: $method $path\n";
        
        // Routing logic
        switch ($path) {
            case '/':
                return new Response(200, ['Content-Type' => 'application/json'], 
                    json_encode(['message' => 'Welcome to ReactPHP HTTP Server!', 'timestamp' => time()]));
                    
            case '/users':
                if ($method === 'GET') {
                    return handleGetUsers($query);
                } elseif ($method === 'POST') {
                    return handleCreateUser($request);
                }
                break;
                
            case '/health':
                return new Response(200, ['Content-Type' => 'application/json'], 
                    json_encode(['status' => 'healthy', 'uptime' => time()]));
                    
            case '/streaming':
                return handleStreamingResponse();
                
            default:
                if (preg_match('/^\/users\/(\d+)$/', $path, $matches)) {
                    $userId = $matches[1];
                    if ($method === 'GET') {
                        return handleGetUser($userId);
                    } elseif ($method === 'PUT') {
                        return handleUpdateUser($userId, $request);
                    } elseif ($method === 'DELETE') {
                        return handleDeleteUser($userId);
                    }
                }
                break;
        }
        
        // 404 Not Found
        return new Response(404, ['Content-Type' => 'application/json'], 
            json_encode(['error' => 'Not Found', 'path' => $path]));
    });
    
    return $server;
}

// Get users list
function handleGetUsers($query) {
    $page = $query['page'] ?? 1;
    $limit = $query['limit'] ?? 10;
    
    $users = [
        ['id' => 1, 'name' => 'John Doe', 'email' => '[email protected]'],
        ['id' => 2, 'name' => 'Jane Smith', 'email' => '[email protected]'],
        ['id' => 3, 'name' => 'Bob Johnson', 'email' => '[email protected]'],
    ];
    
    return new Response(200, ['Content-Type' => 'application/json'], json_encode([
        'users' => array_slice($users, ($page - 1) * $limit, $limit),
        'pagination' => [
            'page' => (int)$page,
            'limit' => (int)$limit,
            'total' => count($users)
        ]
    ]));
}

// Get specific user
function handleGetUser($userId) {
    $users = [
        1 => ['id' => 1, 'name' => 'John Doe', 'email' => '[email protected]', 'created_at' => '2025-01-01T00:00:00Z'],
        2 => ['id' => 2, 'name' => 'Jane Smith', 'email' => '[email protected]', 'created_at' => '2025-01-02T00:00:00Z'],
        3 => ['id' => 3, 'name' => 'Bob Johnson', 'email' => '[email protected]', 'created_at' => '2025-01-03T00:00:00Z'],
    ];
    
    if (isset($users[$userId])) {
        return new Response(200, ['Content-Type' => 'application/json'], 
            json_encode($users[$userId]));
    } else {
        return new Response(404, ['Content-Type' => 'application/json'], 
            json_encode(['error' => 'User not found', 'user_id' => $userId]));
    }
}

// Create user
function handleCreateUser(ServerRequestInterface $request) {
    $body = (string) $request->getBody();
    $data = json_decode($body, true);
    
    if (!$data || !isset($data['name']) || !isset($data['email'])) {
        return new Response(400, ['Content-Type' => 'application/json'], 
            json_encode(['error' => 'Invalid request data', 'required' => ['name', 'email']]));
    }
    
    $newUser = [
        'id' => rand(1000, 9999),
        'name' => $data['name'],
        'email' => $data['email'],
        'created_at' => date('c')
    ];
    
    return new Response(201, ['Content-Type' => 'application/json'], 
        json_encode($newUser));
}

// Update user
function handleUpdateUser($userId, ServerRequestInterface $request) {
    $body = (string) $request->getBody();
    $data = json_decode($body, true);
    
    if (!$data) {
        return new Response(400, ['Content-Type' => 'application/json'], 
            json_encode(['error' => 'Invalid JSON data']));
    }
    
    $updatedUser = [
        'id' => (int)$userId,
        'name' => $data['name'] ?? 'Unknown',
        'email' => $data['email'] ?? '[email protected]',
        'updated_at' => date('c')
    ];
    
    return new Response(200, ['Content-Type' => 'application/json'], 
        json_encode($updatedUser));
}

// Delete user
function handleDeleteUser($userId) {
    // Actual deletion logic would go here
    return new Response(204, [], ''); // No Content
}

// Streaming response
function handleStreamingResponse() {
    return new Response(200, ['Content-Type' => 'text/plain'], 
        \React\Stream\ReadableResourceStream::fromIterator([
            "Starting stream...\n",
            "Data chunk 1\n",
            "Data chunk 2\n", 
            "Data chunk 3\n",
            "Stream completed.\n"
        ]));
}

// Start server
function startServer($loop) {
    $server = createHttpServer($loop);
    
    $socket = new SocketServer('127.0.0.1:8080', $loop);
    $server->listen($socket);
    
    echo "HTTP Server started on http://127.0.0.1:8080\n";
    echo "Available endpoints:\n";
    echo "  GET  / - Welcome message\n";
    echo "  GET  /health - Health check\n";
    echo "  GET  /users - List users\n";
    echo "  POST /users - Create user\n";
    echo "  GET  /users/{id} - Get specific user\n";
    echo "  PUT  /users/{id} - Update user\n";
    echo "  DELETE /users/{id} - Delete user\n";
    echo "  GET  /streaming - Streaming response\n\n";
    echo "Press Ctrl+C to stop the server\n";
    
    return $server;
}

// Start the server
startServer($loop);
$loop->run();