Moves HTTP Requests out from PHP to a forked process on the server executing the request through cURL and then piping the results back into a Laravel Artisan command that fires events about the status of the response of the request. This enables PHP to execute without having to wait for responses from its HTTP requests, enabling one server instance to handle lots of outgoing HTTP requests without using many resources or blocking the execution of PHP. In my tests, 10 concurrent requests had a peak memory usage of only 40mb.
I wrote a blog about this package where I go into more detail about the motivation behind it and alternative methods that I tried before going down the route of forking HTTP requests into separate processes. It's available [here].
composer require oliverlundquist/laravel-http-background
// use it directly
HttpBg::get('https://httpbin.org/get');
HttpBg::head('https://httpbin.org/head');
HttpBg::post('https://httpbin.org/post', json_encode(['json' => 'payload']));
HttpBg::patch('https://httpbin.org/patch', json_encode(['json' => 'payload']));
HttpBg::put('https://httpbin.org/put', json_encode(['json' => 'payload']));
HttpBg::delete('https://httpbin.org/delete');
// or though a macro registered on the Laravel HTTP Client
Http::background()->get('https://httpbin.org/get');
Http::background()->head('https://httpbin.org/head');
Http::background()->post('https://httpbin.org/post', json_encode(['json' => 'payload']));
Http::background()->patch('https://httpbin.org/patch', json_encode(['json' => 'payload']));
Http::background()->put('https://httpbin.org/put', json_encode(['json' => 'payload']));
Http::background()->delete('https://httpbin.org/delete');
Timeout to establish a connection (in seconds).
HttpBg::connectTimeout(10);
Http::background()->connectTimeout(10);
Maximum time allowed for the whole request (in seconds).
HttpBg::timeout(30);
Http::background()->timeout(30);
Max attempts that this request can be retried, see the implementation example below.
HttpBg::retry(3);
Http::background()->retry(3);
Arbitrary data used to identify the request.
HttpBg::setRequestTag('webhook callback for id: 31');
Http::background()->setRequestTag('webhook callback for id: 31');
Set the Content-Type header.
HttpBg::contentType('application/json');
Http::background()->contentType('application/json');
Set the Accept header.
HttpBg::accept('application/json');
Http::background()->accept('application/json');
Set the body of the request, this can also be set implicitly by calling any of the HTTP verb short-hand methods ->post()
, ->patch()
, ->put()
, ->delete()
. The withBody() method can also set the Content-Type and Accept headers by providing values for the second and third arguments.
$contentType = 'application/json';
$accept = 'application/json';
HttpBg::withBody(json_encode(['json' => 'payload']), $contentType, $accept);
Http::background()->withBody(json_encode(['json' => 'payload']), $contentType, $accept);
Push the request to the Laravel Queue and fork a process on your queue worker server instance instead of your main PHP application server. This is useful if you don't want forked processes on your application server but rather have them processed on your server instance running the queue worker.
HttpBg::queue()->get('https://httpbin.org/get');
Http::background()->queue()->get('https://httpbin.org/get');
Check whether the background process (pid) is still running.
$request = HttpBg::get('https://httpbin.org/get');
$request = Http::background()->get('https://httpbin.org/get');
$request->processIsRunning();
Events are fired to track the progress and result of the request.
Event::listen(function (HttpBgRequestSending $event) { Log::info($event->request); });
Event::listen(function (HttpBgRequestSent $event) { Log::info($event->request); });
Event::listen(function (HttpBgRequestSuccess $event) { Log::info($event->requestId); });
Event::listen(function (HttpBgRequestFailed $event) { Log::info($event->requestId); });
Event::listen(function (HttpBgRequestTimeout $event) { Log::info($event->requestId); });
Event::listen(function (HttpBgRequestComplete $event) { Log::info($event->requestId); });
This is an example of a basic implementation that handles request retries and sends notifications of failed and timed-out requests by mail.
class AppServiceProvider extends ServiceProvider
{
public function boot(): void
{
Event::listen(function (HttpBgRequestSent $event) {
$request = $event->request;
Cache::put($request->id, $request->toArray());
});
Event::listen(function (HttpBgRequestFailed|HttpBgRequestTimeout $event) {
$requestId = $event->requestId;
$request = HttpBgRequest::newFromArray(Cache::get($requestId, []));
if (! $request->validateRequest()) {
return;
}
if ($request->maxAttempts > $request->attempts) {
HttpBg::send($request);
return;
}
Cache::forget($requestId);
Mail::to('integration@partner.com')
->send(new FailedWebHookMail($request->tag));
});
}
}
PowerShell is currently not supported. However, adding support shouldn't be too difficult, since the cURL arguments are mostly the same - just replace -o /dev/null with -o NUL.
I'm not a PowerShell expert, but I believe something like (Start-Job { & command }).Id could be used to retrieve the job ID. You could then check the job status later using Get-Job -Id $id.
If you're interested in contributing PowerShell support, feel free to open a pull request!