amphp/http-client
A fully asynchronous HTTP/1 and HTTP/2 client for PHP built on Revolt, with no cURL dependency. It supports concurrent requests, connection pooling, redirects, compression, streaming bodies, cookies, and proxies through a composable interceptor API.
Architecture fit. amphp/http-client is a fiber-based, event-loop-driven HTTP client built on Revolt, not a drop-in replacement for Laravel's default Guzzle-backed Http facade. Laravel's request lifecycle (PHP-FPM or Octane workers) is fundamentally synchronous and request-scoped, while this package is designed for long-lived processes that own an event loop. The realistic fit is narrow and high-value: CLI-side workloads where fan-out concurrency dominates latency — bulk API aggregation, webhook/notification dispatch, third-party sync jobs, scraping/mirroring, and health-check sweeps. Inside a normal FPM request it is architecturally mismatched; inside Octane it competes with Swoole/RoadRunner's own loop rather than composing with it. The correct mental model is "a dedicated async worker library that happens to live in the same Composer project," not "a faster Guzzle."
Integration feasibility. Installation is trivial (composer require amphp/http-client), but integration cost is in the seams. Three friction points dominate:
Request/Response are mutable, non-PSR-7, and the client is not a PSR-18 implementation. Any Laravel code, middleware, or package typed against Guzzle/PSR-7 responses needs an adapter layer or a clean internal abstraction.ext/curl. HTTP is implemented over raw TCP sockets with TLS via OpenSSL. This is a genuine advantage in restricted environments where cURL is unavailable or unwanted, but it means proxy handling (amphp/http-tunnel), TLS edge cases, and corporate egress behavior differ from what the platform team has already validated for Guzzle.nghttp2 FFI (optional, for HTTP/2 perf) requires ffi.enable, which is often disabled by security policy — treat it as a nice-to-have, not an assumption.Technical risk. Medium. The library itself is mature, MIT-licensed, actively released, and standards-compliant with good failure-mode hygiene (documented inactivity timeouts, HTTP/2 window management, redirect policy). The risk is organizational: a small ecosystem relative to Guzzle/Symfony HttpClient, effectively a single-primary-maintainer bus factor, no first-class Laravel/Telescope/Horizon instrumentation, and a Dependents: 0 signal that — whether a data artifact or not — means few public reference implementations to crib from. Team risk is real: fibers and interceptors are a different debugging model, and stack traces through suspended fibers are harder to read than synchronous Guzzle failures.
Key questions.
ext/curl genuinely unavailable, or are we optimizing for a constraint we don't have?nghttp2?Stack fit. Treat this as a specialized internal capability, not a platform-wide HTTP layer. Keep Guzzle (via Laravel's Http facade) as the default for request-scoped, blocking, observability-integrated calls. Introduce amphp/http-client behind a single internal contract, e.g. BulkHttpClient::fetchMany(array $requests): array, with the Amp implementation scoped to CLI workers and fan-out jobs. This contains blast radius and keeps the 95% of HTTP traffic on the path the team already knows.
Migration path. Nothing needs to migrate. This is additive. The sequencing that de-risks it:
BulkHttpClient interface with the Guzzle implementation first, so the Amp version is a swap, not a rewrite.Compatibility. Confirm PHP 8.1+ (fiber requirement) against the Laravel version matrix and any Octane/Swoole version pins. Verify OpenSSL availability and CA bundle configuration matches Guzzle's — this is the most common silent-breakage vector when switching from cURL. Verify proxy support needs against amphp/http-tunnel. Confirm the HTTP/2 egress path with the network team before assuming multiplexing benefits. If FFI is disabled, benchmark HTTP/1.1 keep-alive pooling as the baseline, since that may be the production reality.
Sequencing. Do not introduce both a new HTTP client and a new execution model (CLI workers) at once unless the target workload already runs in CLI. If the first candidate lives in an FPM request path, either defer it or restructure it into a queued job first — the queue refactor is the prerequisite, not a nice-to-have.
Maintenance. Low-to-moderate code footprint, but non-trivial ownership. Semantic versioning is respected and the changelog is well-kept, so upgrade work is predictable. The real maintenance burden is the internal wrapper, adapter layer, and the async idioms the team invents — those are ours to maintain forever. Pin to a minor range and review releases on a quarterly cadence; avoid float-latest on a package with a single primary maintainer.
Support. Community support only (GitHub issues, AMPHP channels). There is no Laravel-native integration, no Telescope HTTP recorder, no Horizon-aware instrumentation. Budget for in-house instrumentation: request/response logging, timing, error classification, and correlation IDs. The LogHttpArchive HAR listener is a genuine gift here — it gives production-grade timing forensics that the Guzzle side typically lacks — but it writes files and can capture sensitive headers, so it needs a retention and redaction policy, not just a config flag.
Scaling. Strong on the dimension that matters: N concurrent calls collapse from N × latency to roughly max(latency). HTTP/2 multiplexing reduces socket and file-descriptor pressure versus N parallel curl handles, and connection pooling amortizes handshakes across a batch. Memory scales with in-flight bodies — streaming via Payload is the mitigation for large transfers, but it forces the team to be explicit about buffer-vs-stream, and the changelog shows this is a live tuning area (window sizes, slow consumers). Vertical, not horizontal: one process handles far more work, which is a cost win but also creates a fatter failure domain per worker.
Failure modes. The ones to plan for, in order of likelihood:
SetRequestTimeout should be the default in the wrapper, not opt-in.FollowRedirects being outermost means header behavior depends on where your interceptor sits. This is a subtle correctness trap for auth headers.HTTP_PROXY env handling equivalent to what Guzzle/curl provides.Ramp-up. This is the dominant cost. Expect 2–4 weeks for one engineer to become genuinely productive, longer to become the team's authority on fibers, Revolt, and interceptor semantics. Debugging requires new habits: async stack traces, suspended-fiber states, and a mental model where "it returned" and "it completed" are different events. Mitigate by (a) confining the package to one or two owners, (b) publishing an internal wrapper with sane defaults so product engineers never touch HttpClient directly, (c) writing two or three reference examples mirrored on real jobs, and (d) keeping the Guzzle path alive as a documented fallback so a stalled adoption doesn't become a stalled roadmap.
Net TPM read. Worth pursuing if — and only if — the measured workload has genuine fan-out concurrency in a CLI/worker context and the team can absorb the async learning curve. The score profile (low overall, high opportunity) matches that read: high ceiling for the right use case, poor general-purpose return, and a real cost in organizational attention. Approve as a scoped spike with a hard checkpoint at the benchmark stage; reject as a platform-wide HTTP replacement.
How can I help you explore Laravel packages today?