monkeyscloud / monkeyslegion-http-client
Enterprise outbound HTTP client for MonkeysLegion: fluent API, cURL transport with connection pooling, middleware pipeline, retry/backoff, PSR-18 bridge, async pools.
Package info
github.com/MonkeysCloud/MonkeysLegion-HTTP-Client
pkg:composer/monkeyscloud/monkeyslegion-http-client
1.0.0
2026-05-17 03:35 UTC
Requires
- php: ^8.4
- ext-curl: *
- psr/http-client: ^1.0
- psr/http-factory: ^1.1
- psr/http-message: ^2.0
Requires (Dev)
- guzzlehttp/psr7: ^2.7
- http-interop/http-factory-guzzle: ^1.2
- phpstan/phpstan: ^2.0
- phpunit/phpunit: ^11.0
- psr/log: ^3.0
Suggests
- monkeyscloud/monkeyslegion-cli: ^2.0 — Required for install command
- monkeyscloud/monkeyslegion-di: ^2.0 — Required for HttpClientProvider
- monkeyscloud/monkeyslegion-events: ^2.0 — Required for event dispatching
- psr/log: ^3.0 — Required for LoggingMiddleware
This package is auto-updated.
Last update: 2026-05-17 03:36:49 UTC
README
Enterprise outbound HTTP client for MonkeysLegion: fluent API, cURL transport with connection pooling, middleware pipeline, retry/backoff, PSR-18 bridge, async pools. Zero external runtime dependencies — just ext-curl and PSR interfaces.
Features
| Feature | Description |
|---|---|
| Fluent API | $client->withToken($t)->post('/api', $data) |
| cURL Transport | Connection pooling, keep-alive, TLS session reuse, LRU eviction |
| Middleware Pipeline | Retry, Auth, Logging, RateLimit, Timeout — composable chain |
| Retry with Backoff | Constant, linear, exponential, exponential+jitter strategies |
| PSR-18 Bridge | Bidirectional: use as PSR-18 client or wrap any PSR-18 as transport |
| Async Pool | Concurrent curl_multi_* requests with configurable concurrency |
| Mock Transport | Queued responses + request history for testing |
| Event System | RequestSending, ResponseReceived, RequestFailed, RetryAttempted |
| PHPStan Level 9 | Full static analysis compliance, 0 errors |
Requirements
- PHP 8.4+
- ext-curl
psr/http-client^1.0psr/http-message^2.0psr/http-factory^1.1
Installation
composer require monkeyscloud/monkeyslegion-http-client
Quick Start
use MonkeysLegion\HttpClient\HttpClient; use MonkeysLegion\HttpClient\DTO\ClientConfig; $client = new HttpClient(new ClientConfig( baseUrl: 'https://api.example.com', timeout: 15, )); // GET with query params $response = $client->get('/users', query: ['page' => '1', 'limit' => '25']); $users = $response->json(); // POST with JSON body $response = $client->post('/users', json: [ 'name' => 'Jorge', 'email' => 'jorge@monkeys.cloud', ]); // Fluent builder $response = $client ->withToken($apiToken) ->withHeader('X-Request-Id', uniqid()) ->timeout(5) ->post('/orders', ['product_id' => 42, 'qty' => 1]); // Response helpers $response->statusCode; // 201 $response->isOk; // true $response->json(); // ['id' => 1, 'name' => 'Jorge'] $response->get('id'); // 1 $response->header('X-RateLimit-Remaining'); // "42" $response->duration; // 0.234 (seconds)
Architecture
┌──────────────────────────────────────────────────────┐
│ HttpClient (fluent) │
└──────────────────────┬───────────────────────────────┘
▼
┌──────────────────────────────────────────────────────┐
│ Middleware Pipeline │
│ Retry → RateLimit → Auth → Logging → Timeout → … │
└──────────────────────┬───────────────────────────────┘
▼
┌──────────────────────────────────────────────────────┐
│ Transport Layer │
│ ┌───────────┐ ┌──────────┐ ┌────────────────┐ │
│ │ CurlTransp│ │MockTransp│ │PsrTranspAdapter│ │
│ │ (pooled) │ │(testing) │ │(Guzzle bridge) │ │
│ └───────────┘ └──────────┘ └────────────────┘ │
└──────────────────────────────────────────────────────┘
Middleware
Retry
use MonkeysLegion\HttpClient\Middleware\RetryMiddleware; use MonkeysLegion\HttpClient\Retry\RetryPolicy; use MonkeysLegion\HttpClient\Enum\BackoffStrategy; use MonkeysLegion\HttpClient\Pipeline\MiddlewarePipeline; use MonkeysLegion\HttpClient\Transport\CurlTransport; $transport = new MiddlewarePipeline(new CurlTransport()); $transport->pipe(new RetryMiddleware(new RetryPolicy( maxAttempts: 3, backoff: BackoffStrategy::ExponentialJitter, baseDelayMs: 100, maxDelayMs: 5000, retryableStatusCodes: [429, 500, 502, 503, 504], ))); $client = new HttpClient(transport: $transport);
Authentication
use MonkeysLegion\HttpClient\Middleware\AuthMiddleware; // Bearer token $transport->pipe(AuthMiddleware::bearer($token)); // Basic auth $transport->pipe(AuthMiddleware::basic('user', 'pass')); // API key header $transport->pipe(AuthMiddleware::apiKey('sk_live_xxx', 'X-API-Key')); // Custom callback $transport->pipe(AuthMiddleware::custom(fn($req) => $req->withHeaders(['X-Signature' => hmac($req->body)]) ));
Logging (PSR-3)
use MonkeysLegion\HttpClient\Middleware\LoggingMiddleware; $transport->pipe(new LoggingMiddleware( logger: $psrLogger, level: 'debug', bodyLimit: 1024, // truncate bodies > 1KB )); // Automatically redacts Authorization, Cookie, X-API-Key headers
Rate Limiting
use MonkeysLegion\HttpClient\Middleware\RateLimitMiddleware; $transport->pipe(new RateLimitMiddleware(maxPerSecond: 10)); // Per-domain token bucket
Timeout
use MonkeysLegion\HttpClient\Middleware\TimeoutMiddleware; $transport->pipe(new TimeoutMiddleware(timeout: 30, connectTimeout: 5));
PSR-18 Bridge
Use HttpClient as PSR-18
use MonkeysLegion\HttpClient\Bridge\PsrClientAdapter; $psrClient = new PsrClientAdapter($httpClient); $psrResponse = $psrClient->sendRequest($psrRequest); // Works as drop-in for any PSR-18 consumer (Stripe, AWS SDK, etc.)
Use Guzzle as Transport
use MonkeysLegion\HttpClient\Bridge\PsrTransportAdapter; use GuzzleHttp\Client as GuzzleClient; use GuzzleHttp\Psr7\HttpFactory; $guzzle = new GuzzleClient(); $factory = new HttpFactory(); $transport = new PsrTransportAdapter($guzzle, $factory, $factory); $client = new HttpClient(transport: $transport);
Async / Concurrent Requests
use MonkeysLegion\HttpClient\Async\Pool; use MonkeysLegion\HttpClient\DTO\RequestContext; use MonkeysLegion\HttpClient\Enum\HttpMethod; $requests = [ new RequestContext(HttpMethod::GET, 'https://api.example.com/users/1'), new RequestContext(HttpMethod::GET, 'https://api.example.com/users/2'), new RequestContext(HttpMethod::GET, 'https://api.example.com/users/3'), ]; $responses = Pool::send($transport, $requests, concurrency: 5); // Returns list<HttpResponse> in same order as requests
Testing
use MonkeysLegion\HttpClient\Transport\MockTransport; use MonkeysLegion\HttpClient\HttpClient; $mock = new MockTransport(); $mock->enqueueJson(['id' => 1, 'name' => 'Test'], statusCode: 201); $client = new HttpClient(transport: $mock); $response = $client->post('/users', json: ['name' => 'Test']); assert($response->statusCode === 201); assert($response->get('name') === 'Test'); assert($mock->requestCount() === 1); assert($mock->assertSent('/users'));
Configuration
php ml http-client:install
http_client {
timeout = ${HTTP_CLIENT_TIMEOUT:-30}
connect_timeout = ${HTTP_CLIENT_CONNECT_TIMEOUT:-5}
verify_ssl = ${HTTP_CLIENT_VERIFY_SSL:-true}
max_redirects = ${HTTP_CLIENT_MAX_REDIRECTS:-5}
user_agent = ${HTTP_CLIENT_USER_AGENT:-MonkeysLegion/2.0}
pool_size = ${HTTP_CLIENT_POOL_SIZE:-10}
throw_on_error = ${HTTP_CLIENT_THROW_ON_ERROR:-false}
retry {
max_attempts = ${HTTP_CLIENT_RETRY_MAX:-3}
backoff = ${HTTP_CLIENT_RETRY_BACKOFF:-exponential_jitter}
base_delay = ${HTTP_CLIENT_RETRY_DELAY:-100}
max_delay = ${HTTP_CLIENT_RETRY_MAX_DELAY:-5000}
}
}
Events
| Event | When |
|---|---|
RequestSending |
Before request is sent |
ResponseReceived |
After response received |
RequestFailed |
On connection/timeout failure |
RetryAttempted |
When retry is triggered |
Response API
$response->statusCode; // int $response->body; // string (raw) $response->headers; // array<string, string> $response->duration; // float (seconds) $response->isOk; // 2xx $response->isRedirect; // 3xx $response->isClientError; // 4xx $response->isServerError; // 5xx $response->isFailed; // 4xx or 5xx $response->json(); // array<string, mixed> $response->get('key'); // dot-notation access $response->header('name'); // case-insensitive header lookup $response->toArray(); // full response as array
License
MIT © MonkeysCloud