huang-yi/shadowfax
Shadowfax is a Laravel package for sending HTTP requests concurrently, making it easy to run multiple requests in parallel and handle responses efficiently. Built on Guzzle, it offers a simple API to reduce wait times when your app depends on several external services.
Illuminate\Foundation\Http\Kernel is compatible with the package. Verify with php -m | grep -i swoole and php --ri swoole. The last tagged release was January 2022, so it targets Laravel 8-ish — on newer Laravel versions you may need to pin laravel/framework or fork the package.composer require huang-yi/shadowfax
php artisan vendor:publish --provider="HuangYi\Shadowfax\ShadowfaxServiceProvider"
This gives you config/shadowfax.php. Read it first — it is the fastest way to understand how the package maps Swoole server options, worker counts, and host/port to your app.php artisan shadowfax:serve). Point a browser at http://127.0.0.1:<port> (the default in the config). If you are used to Nginx + PHP-FPM, this single process is now your web tier./api/ping route under ab or wrk before and after to confirm the win on your machine.Server/kernel wrapper (how it builds Illuminate\Http\Request from Swoole\Http\Request and writes Swoole\Http\Response), and the artisan command (what options it exposes). Those three files explain 90% of the package.AppServiceProvider::boot, config loading, and autoloading run once per worker, not once per request. Treat service-provider code as long-lived: do expensive setup there, but do not capture request state in it.Request::setUserResolver, etc.). This keeps long-lived workers behaving like fresh PHP-FPM requests without giving up the performance gain.Swoole\Runtime::enableCoroutine() or the equivalent config flag), your database/Redis/HTTP clients must be coroutine-safe. Use Swoole-aware clients or connection pools (e.g. Redis/PDO pools) rather than the default Laravel drivers, which hold a single blocking connection per worker.php artisan queue:work, scheduled commands, and websocket/background jobs in separate, ordinary PHP processes. Do not try to run the queue worker inside the Swoole HTTP workers — a blocking job will stall every request that worker handles.php artisan config:cache and php artisan route:cache before deploying. Because the app boots once, cached config is read a single time — a change requires a worker restart (php artisan shadowfax:reload or sending SIGTERM/SIGUSR1 to the master, depending on what the package wires up).worker_num to roughly CPU cores for CPU-bound apps (more if you lean on coroutine IO). Use systemd or Supervisor to keep the master process alive and to restart on deploy.static property on a class, $_SERVER/$_ENV mutation, or cached singleton holding a Request will leak to the next user on the same worker. This is the #1 source of "works locally, corrupts in production" bugs with Swoole-backed Laravel. Audit your own service providers and middleware for statics.app()->forgetInstance() is your friend when debugging. If a test passes alone but fails under load, suspect a stale binding. Reproduce by hitting the same worker repeatedly (keep the connection alive) and inspecting the resolved instance.file_get_contents on a remote URL, a big sleep, or a heavy report will block all requests assigned to that coroutine/worker. Move them to a queue or make them async.$request->file(), StreamedResponse, and large response()->download() behavior explicitly — the Swoole response object does not behave identically to the FPM SAPI.$_SERVER/$_FILES/php://input. Use Laravel's Request API. Shadowfax constructs the request from the Swoole request object, and superglobals may be stale or empty.config/*.php or .env has no effect until you reload workers. Same for package discovery after composer require.APP_ENV=testing hitting the real port) rather than only via phpunit with the FPM test client. Differences in header casing, cookies, and Content-Length handling show up only there.on('WorkerError') or a custom exception handler) so a crashing worker is loud, not silent.memory_get_usage() per worker over time, set max_request in the Swoole options (e.g. 500–1000) to recycle workers, and avoid accumulating entries in static arrays. A periodic worker restart masks leaks but is not a fix.vendor/. Forking is realistic on modern Laravel; a shallow fork that keeps the server and request/response adapters while dropping stale framework assumptions is usually cheaper than fighting the pinned version.How can I help you explore Laravel packages today?