omegaalfa/http-promise
最新稳定版本:v1.0.1
Composer 安装命令:
composer require omegaalfa/http-promise
包简介
Modern async HTTP client with Promise/A+, connection pooling, HTTP/2 multiplexing, and middleware support for PHP.
关键字:
README 文档
README
A modern, production-ready async HTTP client for PHP with Promise/A+ support, connection pooling, and HTTP/2 multiplexing
Features • Installation • Quick Start • API Reference • Security • Performance
🎯 Why HttpPromise?
- ⚡ 246% faster than Guzzle on concurrent requests
- 🔒 Security-first: SSRF protection, header sanitization, credential isolation
- 🚀 Zero dependencies (only ext-curl + PSR-7)
- 🎭 Production-tested with comprehensive refactored architecture
- 📦 Modern PHP 8.4+ with strict types and generics
- 🔄 True async with Promise/A+ implementation
✨ Features
Core Capabilities
- 🚀 Async HTTP requests with cURL multi-handle event loop
- ⚡ Promise/A+ implementation (
then,catch,finally) - 🔗 Fluent API for elegant configuration chaining
- 🔄 Concurrent requests with
concurrent()andrace() - 🔁 Smart retry - idempotent methods only, exponential backoff
- 🏊 Connection pooling - isolated by host with secure handle reuse
- 🎭 Middleware pipeline for request/response interception
- ⚡ HTTP/2 multiplexing with libcurl 7.65.0+ safety checks
- 🎚️ Concurrency control with automatic request queueing
- 📊 Performance metrics built-in
Security Features
- 🔒 SSRF Protection - URL validation, private IP blocking
- 🛡️ Header injection prevention - CRLF sanitization (RFC 7230)
- 🔐 Credential isolation - connection pool per host
- ⏱️ Queue timeouts - prevents starvation attacks
- 🚫 Safe retry - only idempotent methods (GET, PUT, DELETE, HEAD, OPTIONS)
- ✅ Type safety - strict types, PHPStan level 6
New Architecture (Post-Refactoring)
- 🏗️ Separated concerns - 6 focused classes instead of monolithic structure
- 🔧 ConnectionPool - Dedicated connection management
- 🔄 EventLoop - Isolated event processing
- 🔁 RetryHandler - Configurable retry logic with exponential backoff
- 📊 Metrics - Dedicated performance tracking
- 🛡️ UrlValidator - Standalone SSRF protection
📦 Installation
composer require omegaalfa/http-promise
Requirements
| Dependency | Version | Purpose |
|---|---|---|
| PHP | 8.4+ | Modern language features |
| ext-curl | Any | HTTP client engine |
| PSR-7 | ^1.0|^2.0 | Response interface |
For HTTP/2 Support:
- libcurl 7.65.0+ (older versions have multiplexing bugs)
- Compiled with nghttp2 support
Check your environment:
# Check HTTP/2 support php -r "echo (curl_version()['features'] & CURL_VERSION_HTTP2) ? '✅ HTTP/2 supported' : '❌ HTTP/2 not available';" # Check libcurl version php -r "echo curl_version()['version'];"
🚀 Quick Start
use Omegaalfa\HttpPromise\HttpPromise; use Laminas\Diactoros\Response; // Create client with fluent configuration $http = HttpPromise::create(new Response()) ->withBaseUrl('https://api.example.com') ->withBearerToken('your-api-token') ->withTimeout(30.0) ->withRetry(3, 1.0) ->asJson(); // Async request with promise chaining $promise = $http->get('/users') ->then(fn($response) => json_decode($response->getBody()->getContents(), true)) ->then(fn($data) => array_filter($data, fn($user) => $user['active'])) ->catch(fn($e) => ['error' => $e->getMessage()]) ->finally(fn() => echo "Request completed\n"); // Wait for result $activeUsers = $promise->wait();
📚 Documentation
Table of Contents
- Basic Requests
- Configuration
- Authentication
- Promise Handling
- Concurrent Requests
- Middleware System
- Advanced Features
- Security Best Practices
Basic Requests
$http = HttpPromise::create(new Response()) ->withBaseUrl('https://api.example.com'); // GET with query parameters $http->get('/users', [], ['status' => 'active', 'limit' => 10])->wait(); // POST with JSON body $http->post('/users', [ 'name' => 'John Doe', 'email' => 'john@example.com' ])->wait(); // PUT (idempotent update) $http->put('/users/123', ['name' => 'Jane Doe'])->wait(); // PATCH (partial update) $http->patch('/users/123', ['email' => 'jane@example.com'])->wait(); // DELETE $http->delete('/users/123')->wait(); // HEAD (metadata only) $http->head('/users/123')->wait(); // OPTIONS (CORS preflight) $http->options('/users')->wait();
Configuration
Fluent API
$http = HttpPromise::create(new Response()) // Base configuration ->withBaseUrl('https://api.example.com') ->withTimeout(30.0) ->withUserAgent('MyApp/1.0') // Content type ->asJson() // or ->asForm() // Connection settings ->withHttp2(true) ->withTcpKeepAlive(true) ->withMaxPoolSize(50) ->withMaxConcurrent(20) // Retry configuration ->withRetry( attempts: 3, delay: 1.0, statusCodes: [429, 502, 503, 504] ) // Custom headers ->withHeaders([ 'X-API-Version' => '2.0', 'X-Request-ID' => uniqid(), ]);
Using RequestOptions
use Omegaalfa\HttpPromise\Http\RequestOptions; $options = RequestOptions::create() ->withBaseUrl('https://api.example.com') ->withTimeout(60.0) ->withRetry(3, 2.0) ->withHeaders(['Accept-Language' => 'en-US']) ->asJson(); $http = HttpPromise::create(new Response(), $options);
Authentication
// Bearer Token (OAuth 2.0, JWT) $http = HttpPromise::create(new Response()) ->withBearerToken('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...'); // Basic Authentication $http = HttpPromise::create(new Response()) ->withBasicAuth('username', 'password'); // API Key Header $http = HttpPromise::create(new Response()) ->withHeaders(['X-API-Key' => 'your-secret-key']); // Custom Authorization $http = HttpPromise::create(new Response()) ->withHeaders(['Authorization' => 'Digest username="user", realm="API"']);
Promise Handling
Chaining
$http->get('/users/1') ->then(function ($response) { // Parse JSON return json_decode($response->getBody()->getContents(), true); }) ->then(function ($user) { // Transform data return [ 'id' => $user['id'], 'name' => strtoupper($user['name']), 'email' => strtolower($user['email']), ]; }) ->then(function ($transformed) { // Make another request return $this->http->post('/audit', $transformed); }) ->catch(function ($error) { // Handle any error in the chain error_log("Request failed: " . $error->getMessage()); return null; }) ->finally(function () { // Always executes (cleanup, logging, etc.) echo "Request pipeline completed\n"; }) ->wait();
Error Handling
use Omegaalfa\HttpPromise\Exception\HttpException; use Omegaalfa\HttpPromise\Exception\TimeoutException; $promise = $http->get('/users') ->catch(function ($error) { if ($error instanceof TimeoutException) { // Handle timeout return ['error' => 'Request timed out']; } if ($error instanceof HttpException) { // Handle HTTP errors $statusCode = $error->getStatusCode(); if ($statusCode === 404) { return ['error' => 'Not found']; } } // Re-throw other errors throw $error; }); $result = $promise->wait();
Concurrent Requests
Multiple Requests in Parallel
$http = HttpPromise::create(new Response()) ->withBaseUrl('https://api.example.com') ->withMaxConcurrent(10); // Limit concurrency // Define requests $results = $http->concurrent([ 'users' => [ 'method' => 'GET', 'url' => '/users', ], 'posts' => [ 'method' => 'GET', 'url' => '/posts', 'headers' => ['X-Include' => 'comments'], ], 'stats' => [ 'method' => 'POST', 'url' => '/analytics', 'body' => ['metric' => 'pageviews'], ], ])->wait(); // Access results by key $users = json_decode($results['users']->getBody()->getContents(), true); $posts = json_decode($results['posts']->getBody()->getContents(), true);
Race Condition (Fastest Wins)
// Query multiple redundant servers, use fastest response $response = $http->race([ 'primary' => ['method' => 'GET', 'url' => 'https://api1.example.com/data'], 'secondary' => ['method' => 'GET', 'url' => 'https://api2.example.com/data'], 'tertiary' => ['method' => 'GET', 'url' => 'https://api3.example.com/data'], ])->wait(); echo "Fastest server responded!\n";
Promise Static Methods
use Omegaalfa\HttpPromise\Promise\Promise; // Wait for ALL to complete (fails fast on error) $promise1 = $http->get('/users'); $promise2 = $http->get('/posts'); $promise3 = $http->get('/comments'); $results = Promise::all([$promise1, $promise2, $promise3])->wait(); // [$usersResponse, $postsResponse, $commentsResponse] // ANY - first to succeed (ignores failures) $first = Promise::any([$promise1, $promise2, $promise3])->wait(); // ALL SETTLED - wait for all, including failures $outcomes = Promise::allSettled([$promise1, $promise2, $promise3])->wait(); /* [ ['status' => 'fulfilled', 'value' => $response1], ['status' => 'fulfilled', 'value' => $response2], ['status' => 'rejected', 'reason' => $error3], ] */ // Utility methods $resolved = Promise::resolve('immediate value'); $rejected = Promise::reject(new Exception('error')); $delayed = Promise::delay(2.0, 'value after 2 seconds');
Middleware System
Logging Middleware
$loggingMiddleware = function (array $request, callable $next) { $start = microtime(true); echo "[{$request['method']}] {$request['url']}\n"; return $next($request)->then(function ($response) use ($start) { $duration = microtime(true) - $start; echo "✅ {$response->getStatusCode()} ({$duration}s)\n"; return $response; }); }; $http = HttpPromise::create(new Response()) ->withMiddleware($loggingMiddleware);
Authentication Refresh Middleware
$authRefreshMiddleware = function (array $request, callable $next) use ($tokenManager) { // Check if token is expired if ($tokenManager->isExpired()) { $tokenManager->refresh(); // Update request with new token $request['headers']['Authorization'] = 'Bearer ' . $tokenManager->getToken(); } return $next($request); };
Rate Limiting Middleware
$rateLimitMiddleware = function (array $request, callable $next) use ($limiter) { // Wait if rate limit reached $limiter->waitIfNeeded(); return $next($request)->then(function ($response) use ($limiter) { // Update rate limit from response headers if ($response->hasHeader('X-RateLimit-Remaining')) { $remaining = (int) $response->getHeaderLine('X-RateLimit-Remaining'); $limiter->setRemaining($remaining); } return $response; }); };
Multiple Middlewares
$http = HttpPromise::create(new Response()) ->withMiddlewares([ $loggingMiddleware, $authRefreshMiddleware, $rateLimitMiddleware, $retryMiddleware, ]);
Advanced Features
Connection Pooling
// Default: 50 connections per host $http = HttpPromise::create(new Response()); // Increase pool size for high-throughput scenarios $http = $http->withMaxPoolSize(100); // Disable pooling (closes connections immediately) // Useful for one-time requests or security-sensitive contexts $http = $http->withMaxPoolSize(0);
How it works:
- Connections are pooled per host (prevents credential leakage)
- Handles are reset securely (headers, auth, cookies cleared)
- Automatic cleanup when instance is destroyed
HTTP/2 Multiplexing
$http = HttpPromise::create(new Response()) ->withHttp2(true) ->withBaseUrl('https://api.example.com'); // All requests to same host use SINGLE TCP connection for ($i = 0; $i < 100; $i++) { $http->get("/items/{$i}"); } $http->wait();
Benefits:
- Reduced latency (no connection overhead)
- Header compression (HPACK)
- Server push support
- Better bandwidth utilization
Requirements:
- libcurl 7.65.0+ (safety-checked automatically)
- Server must support HTTP/2
Concurrency Control
// Limit concurrent requests to prevent overwhelming servers $http = HttpPromise::create(new Response()) ->withMaxConcurrent(10); // Max 10 simultaneous requests // Make 1000 requests - only 10 active at a time for ($i = 0; $i < 1000; $i++) { $http->get("/item/{$i}"); } // Requests 11-1000 are queued automatically // Queue has timeout protection (prevents starvation) $http->wait();
Performance Metrics
$http = HttpPromise::create(new Response()); // Make requests for ($i = 0; $i < 100; $i++) { $http->get("https://api.example.com/item/{$i}"); } $http->wait(); // Get metrics $metrics = $http->getMetrics(); print_r($metrics); /* [ 'total_requests' => 100, 'successful_requests' => 98, 'failed_requests' => 2, 'pending_requests' => 0, 'queued_requests' => 0, 'uptime_seconds' => 5.234, 'requests_per_second' => 19.11, 'success_rate' => 98.0, ] */
Proxy Configuration
// HTTP proxy $http = HttpPromise::create(new Response()) ->withProxy('http://proxy.example.com:8080'); // SOCKS5 proxy $http = $http->withProxy('socks5://proxy.example.com:1080'); // Authenticated proxy $http = $http->withProxy('http://user:pass@proxy.example.com:8080');
⚠️ Security Note: Only use whitelisted proxy URLs in production.
🔒 Security Best Practices
HttpPromise includes production-grade security features based on comprehensive security audit:
✅ Built-in Protections
| Protection | Description | Status |
|---|---|---|
| SSRF Prevention | Blocks private IPs, validates protocols | ✅ Automatic |
| Header Injection | CRLF sanitization (RFC 7230) | ✅ Automatic |
| Credential Isolation | Connection pool per host | ✅ Automatic |
| Safe Retry | Only idempotent methods (GET, PUT, DELETE) | ✅ Automatic |
| Queue Timeout | Prevents starvation attacks | ✅ Automatic |
| Type Safety | Strict types, PHPStan level 6 | ✅ Automatic |
🛡️ Recommendations
1. URL Validation (SSRF Protection)
// ✅ GOOD - URLs are validated automatically $http->get('https://api.example.com/data'); // OK $http->get('http://10.0.0.1/admin'); // ❌ Throws InvalidArgumentException // ⚠️ If accepting user input, whitelist domains: $allowedDomains = ['api.example.com', 'cdn.example.com']; $userUrl = $_GET['url']; $parsed = parse_url($userUrl); if (!in_array($parsed['host'], $allowedDomains, true)) { throw new Exception('Domain not allowed'); } $http->get($userUrl);
2. SSL Verification
// ✅ ALWAYS verify SSL in production (default behavior) $http = HttpPromise::create(new Response()); // ⚠️ ONLY disable for local development/testing if (getenv('APP_ENV') === 'development') { $http = $http->withoutSSLVerification(); } // ❌ NEVER in production: // $http->withoutSSLVerification(); // Vulnerable to MITM attacks!
3. Credential Management
// ✅ GOOD - Use environment variables $http = HttpPromise::create(new Response()) ->withBearerToken(getenv('API_TOKEN')); // ❌ BAD - Hardcoded credentials // $http->withBearerToken('sk-1234567890abcdef'); // ✅ GOOD - Rotate tokens regularly $token = $tokenManager->getValidToken(); // Implements refresh logic $http = $http->withBearerToken($token);
4. Connection Pooling in Multi-Tenant
// ⚠️ In multi-tenant apps, consider disabling pooling // or ensure strict tenant isolation // Option 1: Disable pooling $http = HttpPromise::create(new Response()) ->withMaxPoolSize(0); // Option 2: Separate instance per tenant $httpPerTenant = [ 'tenant_a' => HttpPromise::create(new Response()) ->withBearerToken($tenantA->getToken()), 'tenant_b' => HttpPromise::create(new Response()) ->withBearerToken($tenantB->getToken()), ];
5. Timeout Configuration
// ✅ Always set aggressive timeouts $http = HttpPromise::create(new Response()) ->withTimeout(10.0); // Connection + read timeout // ✅ Use wait() with timeout for critical paths try { $response = $http->get('/data')->wait(5.0); // Max 5 seconds total } catch (TimeoutException $e) { // Handle timeout }
6. Retry Safety
// ✅ GOOD - Only idempotent methods retry automatically $http = $http->withRetry(3, 1.0); $http->get('/users'); // ✅ Safe to retry $http->put('/user/1'); // ✅ Safe to retry (idempotent) $http->delete('/user/1'); // ✅ Safe to retry (idempotent) $http->post('/payment'); // ❌ NEVER retries (non-idempotent) $http->patch('/user/1'); // ❌ NEVER retries (non-idempotent) // For POST with idempotency keys: $http->post('/payments', [ 'amount' => 100, 'idempotency_key' => uniqid('payment_', true), ]);
🔍 Security Audit
This library has undergone a comprehensive security audit covering:
- SSRF attacks
- Header injection
- Credential leakage
- Race conditions
- HTTP/2 vulnerabilities
- PHP-specific risks
See full audit report in SECURITY.md (if available).
⚡ Performance
HttpPromise is optimized for production workloads:
Benchmarks
Environment:
- PHP 8.4.15
- libcurl 7.81.0 with HTTP/2
- 2 iterations per benchmark
- httpbin.org as test endpoint
| Benchmark | HttpPromise | Guzzle | Winner | Improvement |
|---|---|---|---|---|
| Simple GET | 135.79 ms | 137.22 ms | 🏆 HttpPromise | +1.1% |
| POST JSON | 137.05 ms | 209.24 ms | 🏆 HttpPromise | +52.7% |
| Concurrent x5 | 136.98 ms | 474.07 ms | 🏆 HttpPromise | +246.1% |
| Sequential x5 | 702.45 ms | 823.99 ms | 🏆 HttpPromise | +17.3% |
| JSON Decode | 135.82 ms | 136.59 ms | 🏆 HttpPromise | +0.6% |
| Concurrent x10 | 241.07 ms | 629.95 ms | 🏆 HttpPromise | +161.3% |
| Delayed (1s) | 1541.38 ms | 1154.07 ms | 🏆 Guzzle | -25.1% |
Overall: HttpPromise wins 6/7 benchmarks and is 15% faster overall!
Key Performance Features
🚀 Connection Pooling
- Reuses cURL handles (eliminates connection overhead)
- Pooled per host (secure + fast)
- Configurable pool size
⚡ HTTP/2 Multiplexing
- Single TCP connection for multiple requests
- Header compression (HPACK)
- Parallel streams
🎯 Event Loop Optimization
- Adaptive select timeouts
- Zero-copy operations
- Non-blocking retry delays
📦 Zero Dependencies
- No Guzzle, Symfony, or other heavy frameworks
- Only ext-curl (native C extension)
- Smaller memory footprint
Run Benchmarks
# Full benchmark suite php benchmark.php # Simple benchmark (fewer iterations) php benchmark_simple.php
Performance Tips
✅ DO:
- Reuse HttpPromise instances
- Enable HTTP/2 for same-host requests
- Use concurrent() for parallel requests
- Set appropriate concurrency limits
- Monitor metrics in production
❌ DON'T:
- Create new instances per request
- Set maxConcurrent too high (overwhelms servers)
- Disable connection pooling without reason
- Use blocking operations in event loop
🏗️ Architecture
src/
├── HttpPromise.php # Main client (event loop, pooling, middleware)
│ ├── Event loop (tick/wait)
│ ├── Connection pool (per host)
│ ├── Middleware pipeline
│ └── Request queue (with timeouts)
│
├── Http/
│ ├── RequestOptions.php # Immutable configuration
│ └── HttpProcessorTrait.php # Header/body processing + sanitization
│
├── Promise/
│ ├── PromiseInterface.php # Promise/A+ contract
│ ├── Promise.php # Implementation (then/catch/finally)
│ └── Deferred.php # Deferred pattern (resolve/reject)
│
└── Exception/
├── PromiseException.php # Base exception
├── HttpException.php # HTTP errors (with status code)
├── TimeoutException.php # Timeout errors
└── RejectionException.php # Promise rejections
Design Principles
- Immutability: All
with*()methods return new instances - Type Safety: Strict types, generics, PHPStan level 6
- Security First: SSRF protection, sanitization, isolation
- Performance: Connection pooling, HTTP/2, event loop
- Developer Experience: Fluent API, clear errors, comprehensive docs
📖 Complete API Reference
All 38 public methods documented with explanations and examples.
🏭 Factory & Initialization
create(?ResponseInterface $response = null, ?RequestOptions $options = null, int $maxRetryAttempts = 3): static
Creates a new HttpPromise instance with optional pre-configuration.
Parameters:
$response(optional): PSR-7 response prototype (e.g.,new Response())$options(optional): Pre-configured RequestOptions object$maxRetryAttempts(default: 3): Maximum retry attempts for failed requests
Returns: New HttpPromise instance
Example:
use Omegaalfa\HttpPromise\HttpPromise; use Laminas\Diactoros\Response; // Basic creation $http = HttpPromise::create(); // With response prototype $http = HttpPromise::create(new Response()); // With pre-configured options $options = RequestOptions::create() ->withBaseUrl('https://api.example.com') ->withTimeout(30.0); $http = HttpPromise::create(new Response(), $options); // With custom retry limit $http = HttpPromise::create(null, null, 5); // Max 5 retries
__construct(?ResponseInterface $response = null, ?RequestOptions $options = null, int $maxRetryAttempts = 3)
Constructor (use create() factory method instead for cleaner syntax).
Example:
$http = new HttpPromise(new Response(), RequestOptions::create(), 3);
⚙️ Configuration Methods
All configuration methods return a new immutable instance.
withBaseUrl(string $baseUrl): self
Sets the base URL prepended to all relative URLs.
Parameters:
$baseUrl: Base URL (e.g.,https://api.example.com)
Returns: New instance with updated base URL
Example:
$http = HttpPromise::create() ->withBaseUrl('https://api.github.com'); // Now all requests use the base URL $http->get('/users/octocat')->wait(); // https://api.github.com/users/octocat $http->get('/repos/php/php-src')->wait(); // https://api.github.com/repos/php/php-src
withOptions(RequestOptions $options): self
Replaces all options with a pre-configured RequestOptions object.
Parameters:
$options: Complete RequestOptions configuration
Returns: New instance with replaced options
Example:
$options = RequestOptions::create() ->withBaseUrl('https://api.example.com') ->withTimeout(60.0) ->withRetry(5, 2.0) ->asJson(); $http = HttpPromise::create()->withOptions($options); // All options are now active $http->get('/data')->wait();
withTimeout(float $timeout): self
Sets connection and read timeout in seconds.
Parameters:
$timeout: Timeout in seconds (e.g.,30.0)
Returns: New instance with updated timeout
Example:
// Aggressive timeout for fast APIs $http = HttpPromise::create() ->withTimeout(5.0); // Longer timeout for slow operations $http = $http->withTimeout(120.0); // Catch timeouts $http->get('/slow-endpoint') ->catch(fn(TimeoutException $e) => ['error' => 'Timeout!']) ->wait();
withRetry(int $attempts, float $delay, array $statusCodes = [429, 502, 503, 504]): self
Configures automatic retry with exponential backoff for idempotent methods only (GET, PUT, DELETE, HEAD, OPTIONS).
Parameters:
$attempts: Maximum retry attempts$delay: Initial delay in seconds (doubles each retry)$statusCodes: HTTP status codes that trigger retries
Returns: New instance with retry configuration
Example:
// Retry up to 3 times with 1s initial delay $http = HttpPromise::create() ->withRetry(3, 1.0); // Custom status codes $http = $http->withRetry(5, 2.0, [408, 429, 500, 502, 503, 504]); // GET request retries on failure $http->get('/unreliable-api')->wait(); // Retries automatically // POST never retries (non-idempotent) $http->post('/payment', ['amount' => 100])->wait(); // No retry
Retry Schedule:
- Attempt 1: Immediate
- Attempt 2: After 1s delay
- Attempt 3: After 2s delay (exponential backoff)
- Attempt 4: After 4s delay
withHeaders(array $headers): self
Adds custom headers to all requests. Merges with existing headers.
Parameters:
$headers: Associative array of headers
Returns: New instance with merged headers
Example:
$http = HttpPromise::create() ->withHeaders([ 'Accept' => 'application/json', 'X-API-Version' => '2.0', 'X-Request-ID' => uniqid(), ]); // Add more headers later $http = $http->withHeaders([ 'X-Tenant-ID' => 'tenant-123', ]); // All headers are sent with every request $http->get('/data')->wait();
withProxy(string $proxy): self
Configures HTTP/SOCKS proxy for all requests.
Parameters:
$proxy: Proxy URL (e.g.,http://proxy.example.com:8080)
Returns: New instance with proxy configuration
Example:
// HTTP proxy $http = HttpPromise::create() ->withProxy('http://proxy.company.com:8080'); // SOCKS5 proxy $http = $http->withProxy('socks5://127.0.0.1:1080'); // Authenticated proxy $http = $http->withProxy('http://user:pass@proxy.company.com:3128');
⚠️ Security: Only use whitelisted proxy URLs in production.
withoutSSLVerification(): self
⚠️ WARNING: Disables SSL certificate verification (vulnerable to MITM attacks).
Returns: New instance with SSL verification disabled
Example:
// ❌ NEVER in production // ✅ ONLY for local development/testing if (getenv('APP_ENV') === 'development') { $http = HttpPromise::create()->withoutSSLVerification(); } // Use for self-signed certificates in staging $http->get('https://staging.local')->wait();
withUserAgent(string $userAgent): self
Sets custom User-Agent header.
Parameters:
$userAgent: User-Agent string
Returns: New instance with custom User-Agent
Example:
$http = HttpPromise::create() ->withUserAgent('MyApp/1.0 (https://example.com)'); // Different user agents for different contexts $mobileHttp = $http->withUserAgent('MyApp-Mobile/2.0'); $botHttp = $http->withUserAgent('MyBot/1.0 (+https://example.com/bot)');
🔐 Authentication Methods
withBearerToken(string $token): self
Adds Bearer token authentication (OAuth 2.0, JWT).
Parameters:
$token: Bearer token string
Returns: New instance with Authorization header
Example:
// OAuth 2.0 / JWT $http = HttpPromise::create() ->withBearerToken('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...'); $http->get('/protected-resource')->wait(); // Token rotation $newToken = $authService->refreshToken(); $http = $http->withBearerToken($newToken);
withBasicAuth(string $username, string $password): self
Adds HTTP Basic Authentication.
Parameters:
$username: Username$password: Password
Returns: New instance with Authorization header
Example:
$http = HttpPromise::create() ->withBasicAuth('admin', 'secret123'); $http->get('/admin/dashboard')->wait(); // For APIs using Basic Auth $http = HttpPromise::create() ->withBasicAuth('api-key-id', 'api-secret-key');
🎨 Content Type Methods
asJson(): self
Sets content type to application/json and auto-encodes request bodies.
Returns: New instance with JSON content type
Example:
$http = HttpPromise::create()->asJson(); // Body is automatically JSON-encoded $http->post('/users', [ 'name' => 'John Doe', 'email' => 'john@example.com', 'roles' => ['admin', 'editor'], ])->wait(); // Equivalent to: // Content-Type: application/json // Body: {"name":"John Doe","email":"john@example.com","roles":["admin","editor"]}
asForm(): self
Sets content type to application/x-www-form-urlencoded.
Returns: New instance with form content type
Example:
$http = HttpPromise::create()->asForm(); // Body is URL-encoded $http->post('/login', [ 'username' => 'john@example.com', 'password' => 'secret123', ])->wait(); // Equivalent to: // Content-Type: application/x-www-form-urlencoded // Body: username=john%40example.com&password=secret123
⚡ Performance & Concurrency
withHttp2(bool $enable = true): self
Enables HTTP/2 multiplexing (requires libcurl 7.65.0+).
Parameters:
$enable(default: true): Enable or disable HTTP/2
Returns: New instance with HTTP/2 configuration
Example:
$http = HttpPromise::create() ->withHttp2(true) ->withBaseUrl('https://api.example.com'); // All requests to same host use SINGLE TCP connection for ($i = 0; $i < 100; $i++) { $http->get("/items/$i"); } $http->wait(); // 100 requests over 1 connection! // Benefits: // - Lower latency (no connection overhead) // - Header compression (HPACK) // - Server push support
⚠️ Requirements:
- libcurl 7.65.0+ (older versions have multiplexing bugs)
- Server must support HTTP/2
withTcpKeepAlive(bool $enable = true): self
Enables TCP keep-alive to detect dead connections.
Parameters:
$enable(default: true): Enable or disable TCP keep-alive
Returns: New instance with TCP keep-alive configuration
Example:
$http = HttpPromise::create() ->withTcpKeepAlive(true); // Prevents idle connections from being killed by firewalls $http->get('/long-polling')->wait();
withMaxPoolSize(int $size): self
Sets maximum connection pool size per host.
Parameters:
$size: Max connections per host (0 = disable pooling)
Returns: New instance with updated pool size
Example:
// Optimize for high throughput $http = HttpPromise::create() ->withMaxPoolSize(100); // Disable pooling (multi-tenant isolation) $http = $http->withMaxPoolSize(0); // Conservative pooling $http = $http->withMaxPoolSize(10);
withMaxConcurrent(int $limit): self
Limits maximum concurrent requests (excess requests are queued).
Parameters:
$limit: Max simultaneous requests
Returns: New instance with concurrency limit
Example:
$http = HttpPromise::create() ->withMaxConcurrent(10); // Max 10 at once // Make 1000 requests for ($i = 0; $i < 1000; $i++) { $http->get("https://api.example.com/item/$i"); } // Only 10 execute simultaneously, rest are queued $http->wait();
withMiddleware(callable $middleware): self
Adds middleware to request/response pipeline.
Parameters:
$middleware: Callablefunction(array $request, callable $next): PromiseInterface
Returns: New instance with added middleware
Example:
// Logging middleware $loggingMiddleware = function (array $request, callable $next) { echo "[{$request['method']}] {$request['url']}\n"; return $next($request)->then(function ($response) { echo "✅ {$response->getStatusCode()}\n"; return $response; }); }; $http = HttpPromise::create() ->withMiddleware($loggingMiddleware); // Retry middleware $retryMiddleware = function (array $request, callable $next) { return $next($request)->catch(function ($e) use ($request, $next) { if ($e instanceof TimeoutException) { echo "Retrying after timeout...\n"; return $next($request); // Retry once } throw $e; }); }; $http = $http->withMiddleware($retryMiddleware);
withMiddlewares(array $middlewares): self
Adds multiple middlewares at once.
Parameters:
$middlewares: Array of middleware callables
Returns: New instance with all middlewares added
Example:
$http = HttpPromise::create()->withMiddlewares([ $authMiddleware, $loggingMiddleware, $rateLimitMiddleware, $cachingMiddleware, ]);
🌐 HTTP Request Methods
All request methods return a PromiseInterface that must be resolved with wait() or chained with then().
get(string $url, array $headers = [], array $queryParams = []): PromiseInterface
Sends an HTTP GET request.
Parameters:
$url: Request URL (absolute or relative to baseUrl)$headers: Additional headers for this request$queryParams: Query string parameters
Returns: Promise that resolves to PSR-7 ResponseInterface
Example:
$http = HttpPromise::create() ->withBaseUrl('https://api.github.com'); // Simple GET $response = $http->get('/users/octocat')->wait(); // With query parameters $response = $http->get('/search/users', [], [ 'q' => 'location:San Francisco', 'per_page' => 50, ])->wait(); // URL: /search/users?q=location%3ASan+Francisco&per_page=50 // With custom headers $response = $http->get('/repos/php/php-src', [ 'Accept' => 'application/vnd.github.v3+json', ])->wait(); // Promise chaining $users = $http->get('/users') ->then(fn($response) => json_decode($response->getBody()->getContents(), true)) ->then(fn($data) => array_column($data, 'login')) ->wait();
post(string $url, array $body = [], array $headers = []): PromiseInterface
Sends an HTTP POST request.
Parameters:
$url: Request URL$body: Request body (array auto-encoded based on content type)$headers: Additional headers
Returns: Promise that resolves to ResponseInterface
Example:
$http = HttpPromise::create()->asJson(); // Create resource $response = $http->post('/users', [ 'name' => 'John Doe', 'email' => 'john@example.com', ])->wait(); // Form submission $http = HttpPromise::create()->asForm(); $response = $http->post('/login', [ 'username' => 'john', 'password' => 'secret', ])->wait(); // With custom headers $response = $http->post('/webhooks', ['event' => 'user.created'], [ 'X-Webhook-Signature' => hash_hmac('sha256', $body, $secret), ])->wait();
⚠️ Note: POST requests are never retried (non-idempotent).
put(string $url, array $body = [], array $headers = []): PromiseInterface
Sends an HTTP PUT request (idempotent update).
Parameters:
$url: Request URL$body: Request body$headers: Additional headers
Returns: Promise that resolves to ResponseInterface
Example:
$http = HttpPromise::create()->asJson(); // Full resource update (idempotent) $response = $http->put('/users/123', [ 'name' => 'Jane Doe', 'email' => 'jane@example.com', 'status' => 'active', ])->wait(); // Safe to retry on failure $http = $http->withRetry(3, 1.0); $http->put('/users/123', $userData)->wait(); // Retries automatically
✅ Safe to retry: PUT is idempotent.
patch(string $url, array $body = [], array $headers = []): PromiseInterface
Sends an HTTP PATCH request (partial update).
Parameters:
$url: Request URL$body: Partial update data$headers: Additional headers
Returns: Promise that resolves to ResponseInterface
Example:
$http = HttpPromise::create()->asJson(); // Partial update $response = $http->patch('/users/123', [ 'email' => 'newemail@example.com', // Only update email ])->wait();
⚠️ Note: PATCH requests are never retried (non-idempotent).
delete(string $url, array $headers = []): PromiseInterface
Sends an HTTP DELETE request.
Parameters:
$url: Request URL$headers: Additional headers
Returns: Promise that resolves to ResponseInterface
Example:
$http = HttpPromise::create(); // Delete resource $response = $http->delete('/users/123')->wait(); // With authorization $response = $http->delete('/admin/users/123', [ 'X-Admin-Token' => $adminToken, ])->wait();
✅ Safe to retry: DELETE is idempotent.
head(string $url, array $headers = []): PromiseInterface
Sends an HTTP HEAD request (metadata only, no body).
Parameters:
$url: Request URL$headers: Additional headers
Returns: Promise that resolves to ResponseInterface (empty body)
Example:
$http = HttpPromise::create(); // Check if resource exists $response = $http->head('/users/123')->wait(); if ($response->getStatusCode() === 200) { echo "User exists\n"; } // Get file metadata $response = $http->head('https://example.com/large-file.zip')->wait(); $fileSize = $response->getHeaderLine('Content-Length'); $lastModified = $response->getHeaderLine('Last-Modified');
options(string $url, array $headers = []): PromiseInterface
Sends an HTTP OPTIONS request (CORS preflight, capability check).
Parameters:
$url: Request URL$headers: Additional headers
Returns: Promise that resolves to ResponseInterface
Example:
$http = HttpPromise::create(); // Check CORS $response = $http->options('/api/users')->wait(); $allowedMethods = $response->getHeaderLine('Allow'); echo "Allowed methods: $allowedMethods\n"; // GET, POST, PUT, DELETE // CORS preflight $response = $http->options('/api/users', [ 'Access-Control-Request-Method' => 'POST', 'Access-Control-Request-Headers' => 'X-Custom-Header', ])->wait();
request(string $method, string $url, array $options = []): PromiseInterface
Generic HTTP request method for custom verbs or advanced options.
Parameters:
$method: HTTP method (GET, POST, CUSTOM, etc.)$url: Request URL$options: Array with keys:headers,body,query
Returns: Promise that resolves to ResponseInterface
Example:
$http = HttpPromise::create(); // Custom HTTP method $response = $http->request('PROPFIND', '/webdav/files', [ 'headers' => ['Depth' => '1'], ])->wait(); // Complex request $response = $http->request('POST', '/api/users', [ 'headers' => [ 'Content-Type' => 'application/json', 'X-Request-ID' => uniqid(), ], 'body' => json_encode(['name' => 'John']), 'query' => ['notify' => 'true'], ])->wait();
json(string $method, string $url, array $data = [], array $headers = []): PromiseInterface
Convenience method for JSON requests.
Parameters:
$method: HTTP method$url: Request URL$data: Data to JSON-encode$headers: Additional headers
Returns: Promise that resolves to ResponseInterface
Example:
$http = HttpPromise::create(); // Automatic JSON encoding $response = $http->json('POST', '/api/users', [ 'name' => 'John Doe', 'metadata' => ['role' => 'admin'], ])->wait(); // Equivalent to: $http->asJson()->post('/api/users', ['name' => 'John Doe'])->wait();
🔄 Concurrency Methods
concurrent(array $requests): PromiseInterface
Executes multiple requests in parallel and returns results as associative array.
Parameters:
$requests: Associative array of request configurations
Returns: Promise that resolves to array<string, ResponseInterface>
Example:
$http = HttpPromise::create()->withMaxConcurrent(10); $results = $http->concurrent([ 'users' => [ 'method' => 'GET', 'url' => 'https://api.example.com/users', ], 'posts' => [ 'method' => 'GET', 'url' => 'https://api.example.com/posts', 'headers' => ['X-Include' => 'comments'], ], 'stats' => [ 'method' => 'POST', 'url' => 'https://api.example.com/stats', 'body' => ['metric' => 'pageviews'], ], ])->wait(); // Access by key $users = json_decode($results['users']->getBody()->getContents(), true); $posts = json_decode($results['posts']->getBody()->getContents(), true); $stats = json_decode($results['stats']->getBody()->getContents(), true);
Error Handling:
$http->concurrent($requests) ->catch(function ($error) { // If ANY request fails, promise rejects echo "One request failed: " . $error->getMessage(); }) ->wait();
race(array $requests): PromiseInterface
Executes requests in parallel, returns the first successful response.
Parameters:
$requests: Associative array of request configurations
Returns: Promise that resolves to first successful ResponseInterface
Example:
$http = HttpPromise::create(); // Query redundant servers, use fastest $response = $http->race([ 'primary' => [ 'method' => 'GET', 'url' => 'https://api1.example.com/data', ], 'secondary' => [ 'method' => 'GET', 'url' => 'https://api2.example.com/data', ], 'tertiary' => [ 'method' => 'GET', 'url' => 'https://api3.example.com/data', ], ])->wait(); echo "Fastest server responded!\n"; // Fallback pattern $response = $http->race([ 'cache' => ['method' => 'GET', 'url' => 'http://cache.local/data'], 'origin' => ['method' => 'GET', 'url' => 'https://api.example.com/data'], ])->wait();
⏱️ Event Loop Control
tick(): void
Processes pending requests in the event loop (non-blocking).
Returns: void
Example:
$http = HttpPromise::create(); // Start async requests $promise1 = $http->get('https://api1.example.com/data'); $promise2 = $http->get('https://api2.example.com/data'); // Manual event loop while ($http->hasPending()) { $http->tick(); // Process events // Do other work echo "Working...\n"; usleep(10000); } // Wait for results $result1 = $promise1->wait(); $result2 = $promise2->wait();
wait(?float $timeout = null): void
Blocks until all pending requests complete or timeout.
Parameters:
$timeout(optional): Maximum wait time in seconds
Returns: void
Throws: TimeoutException if timeout is exceeded
Example:
$http = HttpPromise::create(); // Start requests $http->get('https://api.example.com/users'); $http->get('https://api.example.com/posts'); // Wait for ALL to complete $http->wait(); // With timeout try { $http->wait(5.0); // Max 5 seconds } catch (TimeoutException $e) { echo "Requests took too long!\n"; }
hasPending(): bool
Checks if there are pending requests in the event loop.
Returns: true if requests are pending, false otherwise
Example:
$http = HttpPromise::create(); $http->get('https://api.example.com/data'); if ($http->hasPending()) { echo "Waiting for requests...\n"; $http->wait(); }
📊 Monitoring & Metrics
getMetrics(): array
Returns performance metrics for completed requests.
Returns: Array with metrics
Example:
$http = HttpPromise::create(); // Make requests for ($i = 0; $i < 100; $i++) { $http->get("https://api.example.com/item/$i"); } $http->wait(); // Get metrics $metrics = $http->getMetrics(); print_r($metrics); /* [ 'total_requests' => 100, 'successful_requests' => 98, 'failed_requests' => 2, 'pending_requests' => 0, 'queued_requests' => 0, 'uptime_seconds' => 5.234, 'requests_per_second' => 19.11, 'success_rate' => 98.0, ] */ // Monitoring if ($metrics['success_rate'] < 95.0) { alert("API success rate dropped below 95%!"); }
pendingCount(): int
Returns number of currently executing requests.
Returns: Count of pending requests
Example:
$http = HttpPromise::create()->withMaxConcurrent(10); for ($i = 0; $i < 100; $i++) { $http->get("https://api.example.com/item/$i"); echo "Pending: " . $http->pendingCount() . "\n"; // Max 10 }
queuedCount(): int
Returns number of requests waiting in queue.
Returns: Count of queued requests
Example:
$http = HttpPromise::create()->withMaxConcurrent(5); for ($i = 0; $i < 20; $i++) { $http->get("https://api.example.com/item/$i"); } echo "Pending: " . $http->pendingCount() . "\n"; // 5 echo "Queued: " . $http->queuedCount() . "\n"; // 15 $http->wait(); echo "Queued: " . $http->queuedCount() . "\n"; // 0
🔧 Utility Methods
getOptions(): RequestOptions
Returns current RequestOptions configuration.
Returns: RequestOptions object
Example:
$http = HttpPromise::create() ->withBaseUrl('https://api.example.com') ->withTimeout(30.0); $options = $http->getOptions(); // Inspect configuration $baseUrl = $options->baseUrl; $timeout = $options->timeout; echo "Base URL: $baseUrl\n"; echo "Timeout: $timeout seconds\n";
__destruct(): void
Destructor - automatically closes all cURL handles and cleans up resources.
Example:
$http = HttpPromise::create(); // Make requests... $http->get('/data')->wait(); // Automatically cleaned up when object is destroyed unset($http); // Explicitly destroy (optional)
🏗️ Promise API
The Promise class implements Promise/A+ specification with additional utilities.
Promise Methods
then(callable $onFulfilled = null, callable $onRejected = null): PromiseInterface
Attaches fulfillment and rejection handlers.
Example:
$promise->then( fn($value) => "Success: $value", fn($error) => "Error: {$error->getMessage()}" );
catch(callable $onRejected): PromiseInterface
Attaches rejection handler (shortcut for then(null, $onRejected)).
Example:
$promise->catch(fn($error) => handleError($error));
finally(callable $onFinally): PromiseInterface
Executes callback regardless of fulfillment or rejection.
Example:
$promise->finally(fn() => cleanup());
wait(?float $timeout = null): mixed
Blocks until promise resolves and returns value.
Example:
$result = $promise->wait(10.0); // Wait max 10 seconds
Promise Static Methods
Promise::resolve(mixed $value): PromiseInterface
Creates immediately fulfilled promise.
Example:
$promise = Promise::resolve(42); $value = $promise->wait(); // 42
Promise::reject(Throwable $reason): PromiseInterface
Creates immediately rejected promise.
Example:
$promise = Promise::reject(new Exception('Error')); $promise->catch(fn($e) => echo $e->getMessage());
Promise::all(array $promises): PromiseInterface
Waits for ALL promises to fulfill (fails fast on first rejection).
Example:
$promises = [ $http->get('/users'), $http->get('/posts'), $http->get('/comments'), ]; $results = Promise::all($promises)->wait(); // [$usersResponse, $postsResponse, $commentsResponse]
Promise::any(array $promises): PromiseInterface
Returns first fulfilled promise (ignores rejections until all fail).
Example:
$first = Promise::any([ $http->get('https://slow-api.com/data'), $http->get('https://fast-api.com/data'), ])->wait();
Promise::race(array $promises): PromiseInterface
Returns first settled promise (fulfilled OR rejected).
Example:
$fastest = Promise::race([ $http->get('https://api1.com/data'), $http->get('https://api2.com/data'), ])->wait();
Promise::delay(float $seconds, mixed $value = null): PromiseInterface
Creates promise that resolves after delay.
Example:
Promise::delay(2.0, 'Ready!') ->then(fn($msg) => echo $msg) ->wait(); // Waits 2 seconds, then echoes "Ready!"
🔒 Security Best Practices
See Security section above for comprehensive security guide.
| withBasicAuth(string, string) | Set Basic auth | self |
Content Type
| Method | Description | Returns |
|---|---|---|
asJson() |
Set JSON content type | self |
asForm() |
Set form content type | self |
Performance
| Method | Description | Returns |
|---|---|---|
withHttp2(bool) |
Enable HTTP/2 | self |
withTcpKeepAlive(bool) |
Enable TCP keep-alive | self |
withMaxPoolSize(int) |
Set connection pool size | self |
withMaxConcurrent(int) |
Set max concurrent requests | self |
withRetry(int, float, array) |
Configure retry (attempts, delay, status codes) | self |
Middleware
| Method | Description | Returns |
|---|---|---|
withMiddleware(callable) |
Add single middleware | self |
withMiddlewares(array) |
Add multiple middlewares | self |
HTTP Methods
| Method | Description | Returns |
|---|---|---|
get(string, array, ?array) |
GET request (url, headers, query) | PromiseInterface |
post(string, mixed, array) |
POST request (url, body, headers) | PromiseInterface |
put(string, mixed, array) |
PUT request | PromiseInterface |
patch(string, mixed, array) |
PATCH request | PromiseInterface |
delete(string, mixed, array) |
DELETE request | PromiseInterface |
head(string, array) |
HEAD request | PromiseInterface |
options(string, array) |
OPTIONS request | PromiseInterface |
json(string, string, mixed, array) |
JSON request (method, url, data, headers) | PromiseInterface |
Concurrent Operations
| Method | Description | Returns |
|---|---|---|
concurrent(array) |
Execute requests in parallel | PromiseInterface |
race(array) |
Race requests (first wins) | PromiseInterface |
Event Loop
| Method | Description | Returns |
|---|---|---|
wait(?float) |
Wait for all pending (timeout optional) | void |
tick() |
Process one event loop iteration | void |
hasPending() |
Check if has pending requests | bool |
pendingCount() |
Get pending request count | int |
queuedCount() |
Get queued request count | int |
Metrics
| Method | Description | Returns |
|---|---|---|
getMetrics() |
Get performance metrics | array |
getOptions() |
Get current options | RequestOptions |
PromiseInterface Methods
| Method | Description | Returns |
|---|---|---|
then(?callable, ?callable) |
Chain success/error handlers | PromiseInterface |
catch(callable) |
Handle rejection | PromiseInterface |
finally(callable) |
Always execute (cleanup) | PromiseInterface |
wait(?float) |
Wait for resolution (timeout optional) | mixed |
getState() |
Get state (pending/fulfilled/rejected) | string |
isPending() |
Check if pending | bool |
isFulfilled() |
Check if fulfilled | bool |
isRejected() |
Check if rejected | bool |
Promise Static Methods
| Method | Description | Returns |
|---|---|---|
Promise::all(iterable) |
Wait for all (fails fast) | PromiseInterface |
Promise::race(iterable) |
First to settle wins | PromiseInterface |
Promise::any(iterable) |
First to succeed wins | PromiseInterface |
Promise::allSettled(iterable) |
Wait for all (never fails) | PromiseInterface |
Promise::resolve(mixed) |
Create resolved promise | PromiseInterface |
Promise::reject(Throwable) |
Create rejected promise | PromiseInterface |
Promise::delay(float, mixed) |
Create delayed promise | PromiseInterface |
Promise::try(callable) |
Wrap callable in promise | PromiseInterface |
🐛 Troubleshooting
Common Issues
1. TimeoutException: Promise could not be resolved
Cause: Promise not linked to event loop.
Solution:
// ✅ Use HttpPromise methods (auto-wired) $promise = $http->get('/users'); // ❌ Manual promise creation (no event loop) $promise = new Promise(fn($resolve) => $resolve('value'));
2. HTTP/2 Not Working
Check support:
php -r "var_dump(curl_version()['features'] & CURL_VERSION_HTTP2);"
Enable:
$http = $http->withHttp2(true);
Note: Requires libcurl 7.65.0+ (older versions have bugs).
3. Connection Pool Not Reusing
Cause: Creating new instances per request.
Solution:
// ✅ Correct - reuses connections $http = HttpPromise::create(new Response()); for ($i = 0; $i < 100; $i++) { $http->get("/item/{$i}"); } // ❌ Wrong - new instance each time for ($i = 0; $i < 100; $i++) { HttpPromise::create(new Response())->get("/item/{$i}")->wait(); }
4. SSRF Error: "Access to private IP addresses is forbidden"
Cause: Trying to access private/reserved IP ranges (security protection).
Solution:
// ❌ Blocked $http->get('http://127.0.0.1/admin'); $http->get('http://192.168.1.1/api'); $http->get('http://169.254.169.254/metadata'); // AWS metadata // ✅ Use public domains $http->get('https://api.example.com/data'); --- ## ⚡ Performance HttpPromise is optimized for production workloads: ### Benchmarks Environment: - PHP 8.4.15 - libcurl 7.81.0 with HTTP/2 - 2 iterations per benchmark - httpbin.org as test endpoint | Benchmark | HttpPromise | Guzzle | Winner | Improvement | |-----------|-------------|--------|---------|-------------| | Simple GET | 135.79 ms | 137.22 ms | **🏆 HttpPromise** | +1.1% | | POST JSON | 137.05 ms | 209.24 ms | **🏆 HttpPromise** | +52.7% | | **Concurrent x5** | **136.98 ms** | **474.07 ms** | **🏆 HttpPromise** | **+246.1%** | | Sequential x5 | 702.45 ms | 823.99 ms | **🏆 HttpPromise** | +17.3% | | JSON Decode | 135.82 ms | 136.59 ms | **🏆 HttpPromise** | +0.6% | | **Concurrent x10** | **241.07 ms** | **629.95 ms** | **🏆 HttpPromise** | **+161.3%** | | Delayed (1s) | 1541.38 ms | 1154.07 ms | 🏆 Guzzle | -25.1% | **Overall: HttpPromise wins 6/7 benchmarks and is 15% faster overall!** ### Key Performance Features 🚀 **Connection Pooling** - Reuses cURL handles (eliminates connection overhead) - Pooled per host (secure + fast) - Configurable pool size ⚡ **HTTP/2 Multiplexing** - Single TCP connection for multiple requests - Header compression (HPACK) - Parallel streams 🎯 **Event Loop Optimization** - Adaptive select timeouts - Zero-copy operations - Non-blocking retry delays 📦 **Zero Dependencies** - No Guzzle, Symfony, or other heavy frameworks - Only ext-curl (native C extension) - Smaller memory footprint ### Run Benchmarks ```bash # Full benchmark suite php benchmark.php # Simple benchmark (fewer iterations) php benchmark_simple.php
Performance Tips
✅ DO:
- Reuse HttpPromise instances
- Enable HTTP/2 for same-host requests
- Use concurrent() for parallel requests
- Set appropriate concurrency limits
- Monitor metrics in production
❌ DON'T:
- Create new instances per request
- Set maxConcurrent too high (overwhelms servers)
- Disable connection pooling without reason
- Use blocking operations in event loop
🏗️ Architecture
After refactoring, HttpPromise now follows a clean separation of concerns:
src/
├── HttpPromise.php # Main client - orchestrates components
│ ├── Request lifecycle management
│ ├── Middleware pipeline
│ ├── Public API (get, post, concurrent, etc.)
│ └── Event loop integration
│
├── Http/
│ ├── RequestOptions.php # Immutable configuration
│ ├── HttpProcessorTrait.php # Header/body processing + sanitization
│ ├── ConnectionPool.php # Connection pooling per host
│ ├── EventLoop.php # Request queue + event loop
│ ├── RetryHandler.php # Retry logic with exponential backoff
│ ├── Metrics.php # Performance tracking
│ └── UrlValidator.php # SSRF protection + URL validation
│
├── Promise/
│ ├── PromiseInterface.php # Promise/A+ contract
│ ├── Promise.php # Implementation (then/catch/finally)
│ └── Deferred.php # Deferred pattern (resolve/reject)
│
└── Exception/
├── PromiseException.php # Base exception
├── HttpException.php # HTTP errors (with status code)
├── TimeoutException.php # Timeout errors
└── RejectionException.php # Promise rejections
Design Principles
- Immutability: All
with*()methods return new instances - Type Safety: Strict types, generics, PHPStan level 6
- Security First: SSRF protection, sanitization, isolation
- Separation of Concerns: Each class has a single responsibility
- Performance: Connection pooling, HTTP/2, event loop optimization
- Developer Experience: Fluent API, clear errors, comprehensive docs
Component Responsibilities
HttpPromise - Main orchestrator
- Public API surface
- Middleware pipeline execution
- Delegates to specialized components
ConnectionPool - Connection management
- Per-host connection pooling
- Secure handle reset between uses
- Configurable pool size
EventLoop - Request execution
- Manages pending/queued requests
- cURL multi-handle operations
- Concurrency limit enforcement
RetryHandler - Retry logic
- Idempotent method detection
- Exponential backoff
- Configurable status codes
Metrics - Performance tracking
- Request counters
- Success rate calculation
- Throughput metrics
UrlValidator - Security
- SSRF prevention
- Private IP blocking
- Protocol whitelist
🔧 Troubleshooting
"Promise could not be resolved"
Cause: Promise timeout or missing event loop integration.
Solution:
// ✅ Increase timeout $http = HttpPromise::create() ->withTimeout(60.0); // ✅ Use explicit wait with timeout $promise->wait(120.0); // ✅ Check if pending if ($http->hasPending()) { $http->wait(); }
HTTP/2 Not Working
Check support:
php -r "echo (curl_version()['features'] & CURL_VERSION_HTTP2) ? '✅ Supported' : '❌ Not available';" php -r "echo 'libcurl: ' . curl_version()['version'];"
Requirements:
- libcurl 7.65.0+ (older versions have multiplexing bugs)
- Compiled with nghttp2 support
Enable:
$http = HttpPromise::create()->withHttp2(true);
Connection Pool Not Reusing
Cause: Creating new HttpPromise instances per request.
Solution:
// ✅ CORRECT - reuses connections $http = HttpPromise::create()->withBaseUrl('https://api.example.com'); for ($i = 0; $i < 100; $i++) { $http->get("/item/$i"); } $http->wait(); // ❌ WRONG - no connection reuse for ($i = 0; $i < 100; $i++) { HttpPromise::create()->get("https://api.example.com/item/$i")->wait(); }
"URL must start with http:// or https://"
Cause: Invalid URL or SSRF protection triggered.
Solution:
// ✅ Use full URLs $http->get('https://api.example.com/data')->wait(); // ✅ Or set baseUrl $http = HttpPromise::create() ->withBaseUrl('https://api.example.com'); $http->get('/data')->wait(); // Combines to https://api.example.com/data // ❌ Private IPs are blocked (SSRF protection) $http->get('http://192.168.1.1/admin')->wait(); // Throws InvalidArgumentException
"Maximum retry attempts exceeded"
Cause: Server consistently returning retryable status codes.
Solution:
// ✅ Increase retry attempts $http = $http->withRetry(5, 2.0); // ✅ Customize retryable status codes $http = $http->withRetry(3, 1.0, [429, 503]); // Only retry these // ✅ Add circuit breaker middleware $circuitBreaker = function ($request, $next) { if (tooManyFailures()) { throw new Exception('Circuit breaker open'); } return $next($request); }; $http = $http->withMiddleware($circuitBreaker);
Headers Rejected: "Invalid header value"
Cause: CRLF injection attempt detected.
Solution:
// ❌ Rejected (CRLF injection) $http->withHeaders(['X-Custom' => "value\r\nX-Injected: attack"]); // ✅ Clean headers $http->withHeaders(['X-Custom' => 'clean-value']);
🤝 Contributing
Contributions are welcome! Please follow these guidelines:
Development Setup
# Clone repository git clone https://github.com/omegaalfa/http-promise.git cd http-promise # Install dependencies composer install # Run tests vendor/bin/phpunit # Run static analysis vendor/bin/phpstan analyse src --level=6 # Run benchmarks php benchmark.php
Guidelines
- Code Style: Follow PSR-12
- Tests: Add tests for new features
- Types: Use strict types, generics, PHPDoc
- Security: Consider security implications
- Performance: Benchmark critical paths
- Documentation: Update README for new features
Pull Request Process
- Fork the repository
- Create feature branch (
git checkout -b feature/amazing-feature) - Commit changes (
git commit -m 'Add amazing feature') - Push to branch (
git push origin feature/amazing-feature) - Open Pull Request
Reporting Issues
Include:
- PHP version
- libcurl version (
php -r "echo curl_version()['version'];") - Minimal reproduction example
- Expected vs actual behavior
📄 License
MIT License - see LICENSE file for details.
🙏 Acknowledgments
- cURL multi-handle documentation
- Promise/A+ specification
- PSR-7 HTTP message interfaces
- PHP community for feedback and contributions
Made with ❤️ by Omegaalfa
统计信息
- 总下载量: 23
- 月度下载量: 0
- 日度下载量: 0
- 收藏数: 1
- 点击次数: 0
- 依赖项目数: 0
- 推荐数: 0
其他信息
- 授权协议: MIT
- 更新时间: 2024-01-07