Search by

kinetis / revolt-http-client

aln-1

A Revolt-native, Fiber-suspending implementation of Symfony's HttpClientInterface — usable standalone by any project (AsyncAws or otherwise), not only Kinetis.

Package info

github.com/kinetis-dev/revolt-http-client

pkg:composer/kinetis/revolt-http-client

Statistics

Installs: 70

Dependents: 10

Suggesters: 0

Stars: 0

Open Issues: 0

v1.4.1 2026-09-13 11:01 UTC

This package is auto-updated.

Last update: 2026-09-13 11:15:48 UTC


README

Kinetis

kinetis/revolt-http-client
A Revolt-native, Fiber-suspending implementation of Symfony's HttpClientInterface

Packagist Version Packagist Downloads PHP Version License CI

Part of Kinetis, a non-blocking PHP framework for API-first applications, developed in the kinetis-dev/kinetis monorepo.

Built for Kinetis, but usable in any PHP project — this package depends on nothing beyond symfony/http-client (and its symfony/http-client-contracts), amphp/http-client, and revolt/event-loop. No kinetis/framework required.

use Kinetis\RevoltHttpClient\Http;

$api = new Http()->withBaseUrl('https://api.example.com')->withToken($key);

$orders = $api->get('/orders', ['status' => 'open'])->throw()->json();
$api->post('/orders', ['sku' => 'A1', 'quantity' => 2]);

A request made through this client suspends the calling Fiber and yields back to Revolt's event loop while waiting on the network, instead of blocking the whole process — so several run at once through Kinetis\Async\concurrently() with no pooling API of its own.

What it guarantees

  • A credential is pinned to one origin. A client carrying an Authorization or Cookie header requires withBaseUrl(), and then every URL it accepts is relative to that base. Another origin is another client. The validated URL alone names where a request goes: Host, Proxy-Authorization, and Accept-Encoding are refused wherever a caller writes them.
  • Redirects are never followed. A 3xx is a terminal response with a Location to read. Following one means deciding, per response, whether a new origin may see this client's Authorization header, cookies, and body — a decision belonging to the caller who knows what the credential is for.
  • One retry layer, for idempotent methods only, and one total deadline. withRetries() is the only way to configure retries, the transport underneath makes one wire attempt per request, and a per-call retry option is refused rather than merged. Retries apply only to exactly GET, HEAD, OPTIONS, TRACE, PUT, and DELETE; every other method, POST and PATCH included, is sent once, because neither a dropped connection nor a retryable status proves the server did not apply it. A stream or Closure body is refused on a request that could be retried, rather than resent. withTimeout() bounds the whole operation on a monotonic clock — every attempt, every backoff, and every read of the response — and is enforced here rather than trusted to the transport.
  • A bounded response. withMaxResponseBytes() is the ceiling a body may reach, enforced while it arrives rather than once it is already in memory. Every request asks for identity encoding, so the bytes counted are the bytes held rather than the bytes off the wire.
  • Failures carry no secrets. HttpRequestException — the one exception type this package throws — carries the request method, the origin, a status, and a category from a fixed list. No path, no query string, no header, no credential, no body, and no vendor exception chained behind it.

An error status is not one of those failures: successful(), failed(), clientError(), serverError(), and redirect() are answers to branch on, and throw() opts into raising instead. Read the body with json(), jsonPath('customer.email'), or body().

AmpHttpClientFactory::create() returns the underlying Symfony HttpClientInterface on its own, for libraries that want to be handed a client. It is a plain Symfony client and the escape hatch: none of the guarantees above apply to it, redirect following included. One thing carries across, and it is what Http itself is built on: one request is one wire attempt, so a library given this client retries on its own terms or not at all.

use Kinetis\RevoltHttpClient\AmpHttpClientFactory;

$client = AmpHttpClientFactory::create();
$response = $client->request('GET', 'https://example.com/');

Using it with AsyncAws

Every AsyncAws client — S3, SQS, SES, DynamoDB, or any of its other services — extends AsyncAws\Core\AbstractApi, whose constructor takes an optional ?HttpClientInterface $httpClient:

use AsyncAws\S3\S3Client;
use Kinetis\RevoltHttpClient\AmpHttpClientFactory;

$s3 = new S3Client(['region' => 'us-east-1'], null, AmpHttpClientFactory::create());

Nothing about this is Kinetis-specific — the same pattern works for any other library that accepts an injectable HttpClientInterface.

Installation

composer require kinetis/revolt-http-client

Requires PHP 8.4+ (the floor symfony/http-client:^8.0 itself requires). Full documentation: kinetis.dev/docs/revolt-http-client.html.

License

MIT — see LICENSE.