chuyskywalker/rolling-curl
Efficient curl_multi wrapper for fetching many URLs in parallel without overwhelming servers. Maintains a fixed number of simultaneous connections, rolling new requests in as others finish, with optional per-request callbacks to process responses as they arrive.
App\Jobs\FetchUrlsJob) where blocking HTTP calls stall workers. The rolling queue design prevents DOS-like bursts while maintaining throughput.UrlFetched events on completion). Can integrate with Laravel Echo for real-time updates.curl_multi is poorly implemented.// app/Providers/RollingCurlServiceProvider.php
public function register()
{
$this->app->singleton(RollingCurl::class, function () {
$rollingCurl = new RollingCurl();
$rollingCurl->setCallback([$this->app['logger'], 'handleResponse']);
return $rollingCurl;
});
}
// app/Jobs/FetchUrlsJob.php
public function handle()
{
$urls = $this->urls;
$rollingCurl = app(RollingCurl::class);
foreach ($urls as $url) {
$rollingCurl->get($url);
}
$rollingCurl->setSimultaneousLimit(10)->execute();
}
// app/Console/Commands/ScrapeCommand.php
protected function handle()
{
$rollingCurl = new RollingCurl();
$rollingCurl->setCallback([$this, 'processUrl']);
// ... add URLs ...
$rollingCurl->execute();
}
// app/Http/Middleware/FetchExternalData.php
public function handle($request, Closure $next)
{
$rollingCurl = app(RollingCurl::class);
$rollingCurl->get('https://api.example.com/data')->execute();
return $next($request);
}
curl_multi_* functions or change error handling.prunePendingRequestQueue() must be called manually; unbounded queues in long-running processes (e.g., Laravel Horizon supervisors).try-catch with finally to ensure pruning:
try {
$rollingCurl->execute();
} finally {
$rollingCurl->prunePendingRequestQueue();
}
retry() helper or a custom decorator:
$rollingCurl->setCallback(function (Request $request) {
if ($request->getHttpCode() === 503) {
throw new RetryException('Service unavailable', 3);
}
});
Request::addOptions() for unsafe defaults (e.g., CURLOPT_FOLLOWLOCATION). Use Laravel’s HttpClient for trusted endpoints.simultaneousLimit based on system load (e.g., Kubernetes HPA metrics)?curl_multi_* in PHPUnit? Consider php-curl-mock.HttpClient + Pool (modern, maintained) or ReactPHP (for event-loop integration) instead?RollingCurl jobs to database or redis queues for async processing.UrlFetched events to trigger downstream actions (e.g., database updates, notifications).Log facade for structured logging:
$rollingCurl->setCallback(function (Request $request) {
Log::info('Fetched URL', ['url' => $request->getUrl(), 'status' => $request->getHttpCode()]);
});
created_at timestamps for analytics.Retry-After headers by implementing a custom RollingCurl decorator.HttpClient for OAuth2 tokens, then pass to RollingCurl via addOptions().Phase 1: Proof of Concept (PoC)
Job with RollingCurl.file_get_contents() calls to parallel RollingCurl requests.Phase 2: Incremental Rollout
Phase 3: Full Adoption
curl_multi implementations with RollingCurl.deprecated() helper.ext-curl (enabled by default in Laravel).ext-json for parsing API responses (Laravel’s json_decode() works without it).RollingCurl and Guzzle’s Pool in the same request batch (inconsistent timeouts).HttpClient for new projects; use RollingCurl only for legacy systems.file:// or localhost).$rollingCurl->setOptions([
CURLOPT_USERAGENT => 'LaravelScraper/1.0',
CURLOPT_TIMEOUT => 30,
]);
retry() helper for transient failures:
$rollingCurl->setCallback(function (Request $request) {
if ($request->getHttpCode() >= 500) {
throw new RetryException('Server error', 3);
}
});
prunePendingRequestQueue() and clearCompleted() to free memory.rolling_curl_jobsHow can I help you explore Laravel packages today?