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

Hpropagate Laravel Package

vira/hpropagate

Vira HPropagate is a Laravel package for propagating selected HTTP headers to outgoing requests, helping keep correlation IDs and tracing context consistent across internal service calls. Simple configuration makes request context easier to forward.

View on GitHub
Deep Wiki
Context7

Getting Started

vira/hpropagate is a Symfony bundle, not a Laravel package — there is no service provider, no config/hpropagate.php, and composer require vira/hpropagate will not auto-wire anything for a Laravel app. Treat it in one of two ways:

  1. Port the pattern (the normal path for a Laravel developer) — read the bundle source to copy its behaviour into a Guzzle middleware or a Laravel HTTP-client hook. This is usually an hour of work and avoids pulling Symfony's DI into your app.
  2. Use it literally — only worth it if your Laravel app already talks to services via symfony/http-client rather than Guzzle, in which case you still need to register the bundle manually (Laravel doesn't run Kernel::registerBundles()), so you'd be constructing the listener and the client decorator by hand in a service provider.

Where to look first, in this order:

  • DependencyInjection/Configuration.php — the exact config tree. This tells you the header allowlist key, whether there's a default set, and whether per-client scoping exists.
  • The event listener/subscriber that reads the incoming request. Confirm whether it listens on kernel.request with priority and whether it filters to the master request.
  • The HttpClientInterface decorator — this is the piece actually doing the injection, and it's the piece you'd reimplement.

First use case: correlation-ID propagation. Incoming X-Request-ID (or X-Correlation-ID) is read once per request and attached to every outbound call, so a single trace spans your gateway → your API → downstream services.

# config/packages/vira_h_propagate.yaml (Symfony side)
vira_h_propagate:
    headers:
        - X-Request-ID
        - traceparent

The Laravel equivalent you'd actually ship:

// app/Providers/HttpServiceProvider.php
Http::globalRequestMiddleware(function ($request) {
    $id = request()->header('X-Request-ID') ?? (string) Str::uuid();
    return $request
        ->withHeader('X-Request-ID', $id)
        ->withHeader('traceparent', request()->header('traceparent', ''));
});

That single middleware reproduces ~90% of the bundle's value for a typical Laravel app.

Implementation Patterns

Allowlist, never "propagate everything". The whole design of the bundle hinges on an explicit header list. Mirror that in Laravel — a hardcoded array, ideally in a config file, so you can review propagation changes in a diff rather than discovering them in production.

Capture once, at the edge. The bundle's listener grabs headers during kernel.request, before controllers run. In Laravel the analogue is a middleware that resolves the correlation ID, stores it (container binding, Context, or a request attribute), and also echoes it back on the response so clients can correlate.

final class CorrelationId
{
    public function handle($request, Closure $next)
    {
        $id = $request->header('X-Request-ID') ?: (string) Str::uuid();
        app()->instance('correlation.id', $id);
        $response = $next($request);
        return $response->header('X-Request-ID', $id);
    }
}

Then read from the container in your HTTP middleware, not from request() — that keeps the value stable across sub-requests and works in queue workers.

Scope propagation per downstream. Don't blast Authorization or Cookie at third-party APIs. The bundle supports scoping the decorated client; in Laravel use named clients with their own middleware:

Http::macro('internal', fn () => Http::withMiddleware($propagateHeaders)->baseUrl(config('services.internal.url')));
Http::macro('stripe', fn () => Http::withToken(config('services.stripe.secret'))); // no propagation

Queue jobs need explicit handling. The bundle only covers in-request outbound calls. The moment a job dispatches, the incoming request is gone. Serialize the correlation ID into the job payload ($this->correlationId = app('correlation.id')) and rebind it in handle(). This is the single most common gap when teams "add correlation IDs".

Test the middleware, not the network. Assert on the request object, not on a real endpoint:

Http::fake();
$this->withHeader('X-Request-ID', 'abc-123')->get('/endpoint');
Http::assertSent(fn ($req) => $req->header('X-Request-ID')[0] === 'abc-123');

Migrating an existing Symfony service to Laravel: the bundle's config maps cleanly onto Http::globalRequestMiddleware. Keep the header names identical across both apps during the transition, otherwise traces break at the boundary — and that boundary is exactly where you need them.

Gotchas and Tips

  • Header case is not preserved. HTTP headers are case-insensitive; Guzzle and Symfony normalize them differently (X-Request-Id vs X-Request-ID). If a downstream does a strict string comparison, you'll get silent misses. Assert with $request->hasHeader('x-request-id'), never === on the raw name.
  • The master-request trap. Symfony's sub-requests (fragments, ESI, forwarded actions) each fire kernel.request. If the listener doesn't check isMasterRequest()/isMainRequest(), you'll propagate headers from the wrong request. In Laravel, the equivalent is $request->is() on forwarded routes or Request::createFrom() in tests — verify which request object your middleware actually sees.
  • Long-running processes hold stale state. Under Octane, Swoole, or a queue worker, any app()->instance() binding set from a request survives into the next one unless reset. Use scoped() bindings (Laravel 10+) or resolve from request() at call time so a stale X-Request-ID doesn't leak between users.
  • Redirects and retries re-send headers to new hosts. A 302 to a third-party domain will carry your propagated Authorization/trace headers unless the client strips them. Guzzle's allow_redirects config and Symfony's max_redirects both allow per-host header stripping — configure it, don't assume.
  • Async requests bypass middleware timing. Deferred/pooled requests capture headers at creation time. If you set the correlation ID after dispatching, it won't be there. Set it before building the pool.
  • Don't propagate to observability backends. Sending traceparent to Sentry/Datadog ingestion endpoints is usually harmless but noisy, and sending X-Request-ID to a vendor API can confuse their rate-limit de-duplication. Keep the allowlist tight and the client list narrower.
  • Debugging tip: log the outgoing header map once per environment (Log::debug('outbound headers', $request->getHeaders()) behind a config flag). Header propagation failures are almost always "the middleware wasn't attached to this client" — the log tells you instantly.
  • Low adoption is a real risk here. With no stars and no dependents, expect no upstream fixes. The bundle is small, so vendor the relevant logic into your app rather than depending on it for anything load-bearing — a composer update six months from now may well hit an abandoned package, and a Laravel app has no obligation to carry a Symfony bundle it barely uses.
  • Extension point worth copying: the decorator approach means propagation is transparent to call sites. If you reimplement in Laravel, keep that property — don't make developers call ->withTraceHeaders() manually, because someone will forget and the trace will break silently.
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.
oxygen/auth
relicsoftware/barakah-payment-gateway
tobya/work-with-composer
equipc/laravel-email-validator
equipc/laravel-email-database-log
adedsec/nova-translated-labels
wazza/laravel-db-encryption
hokny/laravel-generator
vonsogt/backup
jengo/queues
jeremykenedy/laravel-observability
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums
rlacerda83/query-parser
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names