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

tourze/http-forward-bundle

Tourze HTTP Forward Bundle is a Symfony bundle for PHP that forwards incoming HTTP requests to another backend, making it easy to build lightweight proxies, request relays, and traffic redirects with simple configuration and minimal code.

View on GitHub
Deep Wiki
Context7

Getting Started

tourze/http-forward-bundle is a request-forwarding layer: it takes an incoming HTTP request, replays it against an upstream/backend URL, and returns the upstream response to the caller. In day-to-day terms it's the plumbing behind a proxy endpoint, a strangler-fig migration off a legacy service, or a way to expose a third-party API under your own domain without building a controller per endpoint.

Version context: 1.0.1 is a patch release. The notes contain only the compare link (1.0.0...1.0.1), so there are no declared breaking changes, new features, or deprecations. Treat it as a safe bump; pin ^1.0 and skim the diff at the compare URL before upgrading if you depend on internals.

Install:

composer require tourze/http-forward-bundle

Important framing: this is a Symfony bundle, not a Laravel package. There is no service provider, no config/http-forward.php, and no facade. If you're in a Laravel codebase you have two realistic options:

  1. Use the forwarding classes directly (they're plain PHP services) and bind them into Laravel's container yourself.
  2. Run it inside a Symfony kernel — e.g. a sidecar container — and have Laravel route to that.

Where to look first, in order:

  • src/ — find the class that actually performs the forward (usually named something like *Forwarder, *Client, or *Proxy). That's the seam you'll interact with.
  • DependencyInjection/Configuration.php — the authoritative list of config keys and defaults. There is no README substitute for this file.
  • Resources/config/services.php (or .yaml) — the service IDs and constructor arguments, which tells you what to bind/override.
  • composer.json — confirm the PSR-7/PSR-18/PSR-15 interfaces and the Symfony version constraints before assuming Laravel compatibility.

First use case: a catch-all route that proxies /api/legacy/{any?} to a legacy backend during migration.

// Laravel routes/api.php
Route::any('/legacy/{any?}', LegacyProxyController::class)->where('any', '.*');
final class LegacyProxyController
{
    public function __construct(private Forwarder $forwarder) {}

    public function __invoke(Request $request)
    {
        return $this->forwarder->forward(
            $request,
            config('services.legacy.base_url').'/'.$request->path()
        );
    }
}

Start with a single read-only GET endpoint before forwarding anything that mutates state.

Implementation Patterns

Method and payload fidelity. The forwarder's value is that it preserves the verb, headers, query string, and body. Build your wrapper so nothing narrows that: pass the raw body through rather than $request->all() re-encoded as JSON, since re-encoding breaks multipart uploads and changes numeric/boolean typing.

Strip hop-by-hop headers before forwarding. Host, Connection, Keep-Alive, Transfer-Encoding, Content-Length, Upgrade, and TE describe the client↔your-app connection, not the upstream one. Leaking Content-Length after re-encoding the body is the single most common cause of hangs and truncated responses. Set a fresh Host for the upstream.

Auth handling is a decision, not a default. Three patterns, pick deliberately:

  • Passthrough — forward Authorization untouched. Correct when the upstream validates the same token.
  • Replace — swap the caller's credentials for a service credential or signed JWT. Do this for third-party services; never expose your upstream key to clients.
  • HMAC — sign the forwarded request with a shared secret and a timestamp so the upstream can reject replays.

Never blindly forward Cookie. If both apps share a session cookie name you'll get confusing cross-authentication behaviour.

Stream, don't buffer. For file downloads and large JSON payloads, ensure the response body is streamed (StreamedResponse / a PSR-7 stream) rather than read into a string. A 200 MB export forwarded into memory will OOM the worker under any concurrency.

Timeouts and failure mapping. Configure a connect timeout and a total timeout on the underlying HTTP client. When the upstream is down or slow, return a clean 502 / 504 with a minimal body — never let a Guzzle/Symfony HTTP exception bubble into a rendered stack trace, which can leak the upstream hostname and internal paths.

Correlation IDs. Generate or accept an X-Request-Id, attach it to the outbound request, and log it alongside the inbound request. When a user reports a failure you need to find both sides of the call in one query.

Laravel integration shape. Keep the bundle behind a small interface so you're not importing third-party classes across your codebase:

// app/Contracts/UpstreamForwarder.php
interface UpstreamForwarder
{
    public function forward(Request $request, string $targetUrl): Response;
}

Bind the bundle's class in a service provider (or an adapter that translates Laravel's Request/Response into the PSR/Symfony equivalents). This costs ~30 lines and means a future version bump or package swap touches one file.

Testing. In Laravel, Http::fake() covers the PSR-18 client only if the bundle is configured to use it — otherwise you'll need to fake at the boundary of your adapter. Assert on the outbound URL, method, and headers; Http::assertSent(fn ($req) => $req->url() === ...). Add one test per behaviour that matters: method preservation, query string encoding, header stripping, upstream 4xx passthrough, upstream timeout mapping.

Route design. A {any?} catch-all is convenient but it also swallows routes you later want to define locally. Scope it under a distinct prefix (/legacy/..., /proxy/...) and register it last so explicit routes win.

Gotchas and Tips

The empty changelog is itself the finding. A patch release with only a compare link means you cannot reason about the change from the notes. Read the diff at the linked URL before upgrading in production — "patch" is a versioning claim, not a guarantee. Watch for changed default config values or bumped dependency constraints, both of which ship happily in patch versions.

Symfony bundle ≠ Laravel package. Do not run php artisan vendor:publish and expect anything. Configuration lives in the bundle's Configuration.php and is normally loaded through a Symfony kernel extension. In Laravel you're responsible for instantiating and configuring the object graph yourself. If the constructor expects Symfony services (event dispatcher, HTTP client, logger), you'll need to supply Laravel equivalents or PSR-compatible ones.

Query string fidelity. Rebuilding the query from $request->query() and http_build_query() will re-encode arrays and change key ordering, and it drops repeated keys in some cases. Append the raw QUERY_STRING server value to the target URL instead. Symptom to look for: upstream 400s that only occur on requests with nested or repeated parameters.

Don't follow redirects. Set allow_redirects => false on the client. A 302 from the upstream is a legitimate response for the caller; following it internally silently changes the semantics and can mask an auth flow the client needed to see.

Forward-loop protection. A misconfigured base URL pointing back at your own host produces an infinite recursion that manifests as a worker timeout, not an error. Assert at boot that the upstream host differs from APP_URL's host, or add a hop-count header check.

Trusted proxies and X-Forwarded-*. Once your app forwards, there are now two proxy hops. Laravel's TrustProxies middleware and Symfony's trusted_proxies config must both agree, or $request->ip() and generated URLs will be wrong — and any rate limiting keyed on IP will misbehave.

Debugging checklist when the upstream rejects you: log the exact outbound URL, method, and full header set as a single structured entry before the call. Nine times out of ten the problem is a stale Host header, a duplicated Content-Type, or a Content-Length from the original request. A raw curl reproduction using the logged values isolates package bug from config bug in under a minute.

Extension points worth knowing:

  • Decorate the forwarder service rather than editing vendor code — one interface, wrap it, add logging or metrics.
  • Most implementations accept an injected PSR-18/PSR-7 client; swapping it is the cleanest way to add retries, circuit breaking, or a mock.
  • If the bundle dispatches pre/post-forward events, listen to them for audit logging instead of putting that logic in your controller.

Upgrade discipline. Given a 1.0.x version and a thin changelog, pin to ~1.0.1 rather than ^1.0 if the forwarding path is on a critical route, and re-check the compare link on each release. Keep your adapter thin — that's the real risk mitigation here, more than any version constraint.

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.
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
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