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 Bundle Laravel Package

roadrunner/http-bundle

RoadRunner HTTP Bundle integrates RoadRunner’s high-performance HTTP server with Symfony, bridging PSR-7 requests through long-running workers for faster, more efficient PHP request handling.

View on GitHub
Deep Wiki
Context7

Getting Started

This bundle is the glue between RoadRunner (a Go application server that keeps PHP workers alive across requests) and Symfony's HttpKernel. You keep writing ordinary controllers, Request/Response objects, and listeners — what changes is the process model: your kernel boots once and serves hundreds of requests from the same PHP process.

Minimum viable setup:

  1. composer require roadrunner-symfony/http-bundle plus spiral/roadrunner-cli (gives you vendor/bin/rr).
  2. Register the bundle in config/bundles.php if Flex doesn't do it, and check vendor/roadrunner-symfony/http-bundle/src/DependencyInjection/Configuration.php for the exact config tree before writing YAML — this is the first file to read, because the config surface is small and everything else is documentation-by-source.
  3. Add a .rr.yaml at project root: server.command: php public/index.php, an http.address, a worker pool size, and rpc.listen if you want rr CLI control.
  4. vendor/bin/rr get-binary && vendor/bin/rr serve.

The first use case worth proving is not "my homepage renders" — it's "the worker loop is actually looping." Add a debug route that returns getmypid() and spl_object_id($someService), hit it three times, and confirm the values are stable. That single check tells you you're in worker mode (not PHP-FPM), that DI services persist between requests, and that you now own state-hygiene for the whole app. Everything else in this assessment follows from that fact.

Before committing, note the adoption signal: 47 stars, 0 dependents. That doesn't mean it's bad — it's the RoadRunner org's own Symfony integration — but it means you'll be reading source instead of Stack Overflow, and you should pin the version and keep a rollback plan (see Gotchas).

Implementation Patterns

Design for the boot/serve split. Anything expensive that is safe to compute once — container compilation, config loading, HTTP client construction, Twig warmup, feature-flag fetch — is effectively free after the first request. Anything that is stateful is now a liability. Draw a hard line: request-scoped data lives in the Request object or in services tagged kernel.reset, never in a constructor-injected singleton.

PSR-7 in, HttpFoundation through the kernel, PSR-7 out. The bundle converts the RoadRunner request into a Symfony Request, dispatches, and converts the Response back. Practical consequence: Request::createFromGlobals() is not the source of truth, so $_GET, $_POST, $_FILES, and php://input are unreliable. Always go through $request->query->get(), $request->request->get(), $request->files, $request->getContent(). Audit third-party bundles for superglobal reads — a surprising number still do it.

Use the middleware pipeline rather than kernel listeners for anything transport-adjacent: trusted proxies, static file serving, request IDs, CORS, access logging, timing headers. Middleware wraps the whole request/response cycle and runs outside the kernel, which is cheaper and keeps concerns separated. Order matters — outermost registrations see the raw PSR-7 request first. Mirror your framework.trusted_proxies setting into the proxy middleware or $request->getClientIp() will hand you the load balancer's IP.

Let the Go server serve static content. Symfony serving a 4 MB asset ties up a PHP worker. Use RoadRunner's static HTTP middleware or the bundle's static middleware, with forbid: [".php", ".htaccess"]. Same logic for large downloads: prefer streaming responses, and test them explicitly — Content-Length and Transfer-Encoding are negotiated at the Go layer, not by Symfony's send().

Sessions and auth. PHP's native file sessions technically survive, but the lock/serialize lifecycle across persistent workers is exactly the class of bug that only appears under concurrency. Use a shared store (Redis/DB) and verify Set-Cookie propagation end-to-end. Same for the security token storage — confirm it's reset between requests.

Operational wiring. One rr serve per host, http.num_workers roughly equal to CPU cores. Supervise it with systemd. Use rr workers, rr reset, rr stop via the RPC endpoint instead of kill. Add a /health route that touches the DB and Redis so the proxy can drain unhealthy workers. Consider RoadRunner's metrics config for Prometheus — it gives you per-worker queue depth and request timing that Symfony's profiler can't see.

Testing is split-brain. KernelBrowser functional tests never touch the worker, so they will happily pass while production leaks memory or bleeds Doctrine state. Add one CI job that boots rr serve and fires two sequential requests at the same endpoint through the same worker, asserting no cross-contamination. That test catches more real bugs in this setup than the rest of the suite combined.

Gotchas and Tips

State bleed is the whole game. The usual suspects: Doctrine's EntityManager (unit of work grows unbounded, and a failed flush leaves it closed), Monolog's buffered handlers, RequestStack leftovers, Twig globals mutated by extensions, and any static/self::$cache in your own code. Doctrine in particular needs clear() between requests and an isOpen() guard that resets the manager after a broken transaction. Verify the bundle actually invokes Symfony's services_resetter — if it doesn't, tag your stateful services with kernel.reset and wire the reset yourself.

die(), dd(), and exit() kill the worker mid-request. The user gets a connection reset, RoadRunner respawns the worker, and you may see nothing in the Symfony logs. dump() goes to stdout, not the response — use a file or stderr logger while developing. Disable APP_DEBUG in the worker: the debug toolbar and profiler collect data on every request and turn into a memory leak with a latency tax.

Uncaught throwables can be opaque. Set max_jobs (recycle workers every N requests) and a pool memory limit as safety nets — they convert slow degradation into periodic clean restarts. Then treat worker restarts as a signal to investigate, not a fix.

Config and cache changes need a worker restart. The container is compiled once at boot. bin/console cache:clear while workers are running deletes files the live processes still reference. Deploy order: build/warm cache, then restart workers (rr reset), then shift traffic. Also remember .env is read once at boot — per-request env mutation won't do what you expect.

Uploads behave differently. UploadedFile instances are constructed from PSR-7 data, so is_uploaded_file() and moveTo() semantics don't match FPM. Test a real multipart upload, including size limits configured on both the Go side (http limits / body size) and PHP's upload_max_filesize.

Debugging friction is real. Xdebug plus a persistent worker is slow and confusing (breakpoints hit in a loop that never exits). Prefer structured logging and rr serve -v with -o key=value config overrides for quick experiments. Keep symfony serve for day-to-day UI work; reserve rr serve for integration and perf checks.

Extension points worth knowing: implement the bundle's middleware interface and tag the service (check the bundle's Resources/config for the exact tag name) for cross-cutting transport concerns; decorate HttpKernelInterface for anything that must run outside the DI reset cycle; use RoadRunner's own .rr.yaml middleware for gzip, headers, and static. Keep business code free of any RoadRunnerBundle\* import so you can swap to baldinof/roadrunner-bundle or a hand-rolled spiral/roadrunner-http worker later.

Because adoption is thin, pin hard. Lock the bundle and spiral/roadrunner-* versions together, read the changelog on every bump, and keep .rr.yaml under version control next to composer.lock — the two drift apart silently. Budget a day for a spike before committing: boot RoadRunner, run your heaviest endpoint twice in one worker, and watch RSS. If memory climbs, you know the scope of the cleanup before it's a production incident.

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.
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core