Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Http Client Laravel Package

workerman/http-client

An asynchronous, non-blocking HTTP client for Workerman. It supports GET/POST requests, file uploads and downloads, redirects, cookies, proxies, timeouts, keep-alive, and concurrent callback-based requests for high-performance PHP applications.

View on GitHub
Deep Wiki
Context7

Getting Started

Pull in the package alongside Workerman itself — the client is a thin layer over Workerman\Connection\AsyncTcpConnection, so there is no separate event loop to install:

composer require workerman/http-client workerman/workerman

The single most important thing to internalise first: this client is not a drop-in replacement for Laravel's Http facade. It has no ->get() that returns a response. Every call is request(url, callbacks) and the callbacks only fire while a Workerman event loop is running. A plain php artisan tinker script that calls $client->get(...) will appear to do nothing at all.

Minimal working script — note the dummy Worker used purely to start the loop:

use Workerman\Worker;
use Workerman\Http\Client;

$worker = new Worker();
$worker->onWorkerStart = function () {
    $client = new Client(['timeout' => 5]);

    $client->get('https://httpbin.org/get', function ($response) {
        echo $response->getStatusCode(), "\n";
        echo $response->getBody(), "\n";
    }, function ($exception) {
        echo "ERR: ", $exception->getMessage(), "\n";
    });
};

Worker::runAll();

Where to look first in the repo: src/Client.php for the option keys and the get/post/request signatures, and src/Response.php for the small surface you actually get back (getStatusCode(), getHeaders(), getBody()).

First real use case: fan-out. Fetch 50 URLs and total the wall-clock time — it should be roughly the slowest single request, not the sum. If that demo does not impress you, the package probably is not the right tool for your problem.

Implementation Patterns

Reuse one Client per process. The client owns the connection pool and keep-alive sockets. Construct it once in onWorkerStart (or as a Laravel container singleton) and hand it around; new Client() inside a request handler defeats pooling and leaks sockets under load.

Fan-out with a completion counter. There is no Promise::all, so the idiomatic pattern is a shared counter plus a "done" branch:

$urls = [...];
$results = [];
$pending = count($urls);

foreach ($urls as $url) {
    $client->get($url, function ($response) use (&$results, &$pending, $url) {
        $results[$url] = $response->getBody();
        if (--$pending === 0) { /* all done */ }
    }, function ($e) use (&$results, &$pending, $url) {
        $results[$url] = null;
        if (--$pending === 0) { /* all done */ }
    });
}

Decrement in both callbacks or you will hang forever on the first failure. For cleaner code, wrap each request in a small deferred object that resolves/rejects, or use Workerman\Coroutine\Channel if you also pull in workerman/coroutine.

Generic requests and JSON bodies. post() with an array sends application/x-www-form-urlencoded. For a JSON API you must encode and set the header yourself:

$client->request('POST', $url, [
    'headers' => ['Content-Type' => 'application/json'],
    'data'    => json_encode($payload),
], $success, $error);

URL-encode query strings yourself ($url . '?' . http_build_query($q)) — there is no query option.

Per-call option overrides. Options passed to request() (timeout, headers, max_redirects, progress) override the client defaults for that call only. Keep the client-level defaults conservative and widen timeouts on the specific slow endpoint rather than globally.

Streaming large downloads. Use the progress callback to write chunks to disk and avoid holding the whole body in memory:

$fh = fopen($path, 'wb');
$client->get($url, function ($response) use ($fh) { fclose($fh); },
    function ($e) use ($fh) { fclose($fh); },
);
// with 'progress' => function ($buffer, $response) use ($fh) { fwrite($fh, $buffer); }

Laravel integration. The realistic shape is an Artisan command or a queue-worker daemon that boots a Workerman worker and does the fan-out inside it, then pushes results into the database or dispatches follow-up jobs. Do not call Eloquent, Redis, or sleep() inside a success callback — it blocks the loop and starves every other in-flight request. Collect into an array, then persist in the completion branch, or dispatch() a job (a fast push onto Redis is usually acceptable; a full Eloquent save of 50 rows is not).

Gotchas and Tips

  • No loop, no callbacks. The most common "the package is broken" report. In a Laravel app either run inside an existing Workerman/Webman process, or start the loop explicitly. Worker::runAll() with zero workers exits immediately.
  • There is no synchronous API. Don't try to assign $response = $client->get(...) — it returns nothing useful. Everything is continuation-passing.
  • Exceptions inside callbacks are swallowed or kill the worker depending on your error handler. Wrap callback bodies in try/catch and log; a TypeError in one callback otherwise takes down every other request on that process.
  • Error callback receives an exception, but a 500 response is a success. You get getStatusCode() in the success path; check it explicitly. Only transport-level failures (DNS, connect, TLS, read timeout) hit the error callback.
  • Timeouts are per-attempt, and redirects count. A max_redirects => 10 chain with a 3s timeout can still burn 30s overall. Lower max_redirects for internal service calls.
  • TLS on stock PHP builds. Self-signed or internal CAs fail unless you set the ssl option (verify_peer/verify_peer_name, or cafile). Don't globally disable verification to "fix" it — pin the CA file.
  • Proxy config is a client/request option string (http://user:pass@host:port), not an environment variable — HTTP_PROXY is ignored.
  • POST arrays are form-encoded. Silent breakage when an API expects JSON: you get a 400 or an HTML error page in getBody() with no exception raised.
  • The response body is always fully buffered unless you use progress. Multi-hundred-MB downloads will bloat memory per in-flight request.
  • Debugging: set Worker::$logFile / Worker::$stdoutFile and log inside both callbacks with the URL included — the async stack is otherwise invisible. If nothing prints, verify the loop is actually running before touching client options.
  • Extension points are thin. Subclass Client (or wrap it in your own HttpFanout service) to inject logging, retries with backoff, request IDs, and metrics. Retries in particular you must implement yourself: on error, re-issue the request with a usleep-free backoff driven by a Workerman timer, not sleep().
  • Testing: the callback API is awkward to unit test. Abstract the client behind a small interface with a promise-based or synchronous fake in tests, and keep the Workerman-dependent code confined to one integration test that actually boots a worker.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle