Tiny zero-dependency PSR-18 HTTP client backed by ext-curl, with a retry / header-injection middleware pipeline.
A small PSR-18 HTTP client backed by ext-curl, with a composable middleware pipeline (retry on selected status codes, header injection). It speaks PSR-7 requests + responses end-to-end and consumes PSR-17 factories, so any PSR-7 implementation (nyholm/psr7, guzzlehttp/psr7, slim/psr7, …) plugs in as the message layer. The package does not ship its own PSR-7 implementation — bring whichever you already use.
ClientInterface over ext-curl — drop-in anywhere a PSR-18 client is expected.Pipeline (itself a PSR-18 client).RetryMiddleware — exponential backoff on configurable status codes and network errors.HeaderInjectionMiddleware — stamp API keys / User-Agent / telemetry headers on every request.NetworkExceptionInterface carrying the failed request.strict_types, final classes.Guzzle is excellent but heavy — it pulls in guzzlehttp/psr7, guzzlehttp/promises, and its own promise/handler machinery. When all you need is a PSR-18 client that does retries and header injection, this package is a tiny, zero-runtime-dependency alternative: just ext-curl plus the PSR interface packages. You supply the PSR-7 implementation you already have, so there is no duplicate message library in your dependency tree.
composer require amashukov/http-client-php nyholm/psr7
(Substitute nyholm/psr7 for the PSR-7 implementation of your choice.)
use Amashukov\HttpClient\CurlClient;
use Nyholm\Psr7\Factory\Psr17Factory;
$factory = new Psr17Factory();
$client = new CurlClient($factory, $factory);
$request = $factory->createRequest('GET', 'https://api.example.test/data');
$response = $client->sendRequest($request);
echo $response->getStatusCode(); // 200
echo (string) $response->getBody(); // raw body
CurlClient implements Psr\Http\Client\ClientInterface; it is a drop-in replacement anywhere a PSR-18 client is expected. The constructor takes a Psr\Http\Message\ResponseFactoryInterface and a Psr\Http\Message\StreamFactoryInterface so the caller controls which PSR-7 implementation backs the returned ResponseInterface.
Network failures (DNS, connect timeout, TLS handshake, …) surface as Amashukov\HttpClient\Exception\TransportException, which implements Psr\Http\Client\NetworkExceptionInterface (and therefore ClientExceptionInterface); getRequest() returns the request that failed.
Pipeline wraps any PSR-18 client with an ordered list of middlewares, and is itself a PSR-18 client.
use Amashukov\HttpClient\Pipeline;
use Amashukov\HttpClient\Middleware\HeaderInjectionMiddleware;
use Amashukov\HttpClient\Middleware\RetryMiddleware;
$client = new Pipeline(
new CurlClient($factory, $factory),
[
new HeaderInjectionMiddleware(['X-Api-Key' => $apiKey]),
new RetryMiddleware(
maxAttempts: 3,
retryStatusCodes: [429, 502, 503, 504],
baseDelayMs: 200,
),
],
);
$response = $client->sendRequest($request);
Implement Amashukov\HttpClient\MiddlewareInterface:
use Amashukov\HttpClient\MiddlewareInterface;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
final class LoggingMiddleware implements MiddlewareInterface
{
public function handle(RequestInterface $request, callable $next): ResponseInterface
{
$start = microtime(true);
$response = $next($request);
printf("%s %s -> %d (%.0fms)\n",
$request->getMethod(),
$request->getUri(),
$response->getStatusCode(),
(microtime(true) - $start) * 1000,
);
return $response;
}
}
Call $next($request) exactly once for normal flow, or zero times to short-circuit with a synthetic response.
HeaderInjectionMiddleware — sets the configured headers on every outbound request, replacing any caller-supplied value for the same name. Useful for API keys, User-Agent, telemetry headers.
RetryMiddleware — retries on configured status codes (default [429, 502, 503, 504]) and on any thrown Psr\Http\Client\NetworkExceptionInterface. Delay is exponential (baseDelayMs * 2^(attempt-1)); baseDelayMs=0 means immediate retry. Returns the last response when all attempts are exhausted; rethrows the last network exception when every attempt failed at the transport layer. A $sleeper callable can be injected to control timing in tests.
ext-curlpsr/http-client ^1.0psr/http-message ^2.0psr/http-factory ^1.1CurlClient and Pipeline implement Psr\Http\Client\ClientInterface; composer.json declares provide: { psr/http-client-implementation: "1.0" }.Psr\Http\Message\RequestInterface / ResponseInterface.CurlClient consumes ResponseFactoryInterface + StreamFactoryInterface; it does not implement those factories itself, leaving the choice of PSR-7 implementation to the caller.| Package | Tier | Purpose |
|---|---|---|
| amashukov/toncenter-client-php | RPC | toncenter v2/v3 API client (uses this client) |
| amashukov/eth-rpc-client-php | RPC | Ethereum JSON-RPC client (uses this client) |
| amashukov/ton-php | meta | TON umbrella package |
| amashukov/eth-php | meta | EVM umbrella package |
@PER-CS ruleset.MIT.
How can I help you explore Laravel packages today?