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

Proxy Sdk Laravel Package

redirectionio/proxy-sdk

PHP SDK for Redirection.io's proxy, helping PHP applications apply managed redirect and routing rules. Integrate redirects, status codes, and URL handling into your app via a lightweight client API that communicates with the Redirection.io proxy.

View on GitHub
Deep Wiki
Context7

Getting Started

redirectionio/proxy-sdk is not a Laravel package — it is the shared, framework-agnostic plumbing that redirection.io's framework integrations (Symfony bundle, etc.) are built on. There is no service provider, no facade, no config file, no auto-discovery. If you are on Laravel you either (a) install a higher-level integration that wraps this, or (b) write ~40 lines of glue yourself. This package is the glue.

Install it, then immediately open src/ — the surface is small (an agent client plus request/rule value objects), and the README is thin. The class and method names below reflect the SDK's shape; confirm them against the source before relying on them. The package was dormant for a long stretch, so the README has not kept pace with the code — the source is the documentation.

composer require redirectionio/proxy-sdk

What you actually need before writing code:

  1. A redirection.io project key (from the manager UI).
  2. A reachable agent — by default the SDK talks to a local agent over a socket at tcp://127.0.0.1:10301. In production you normally run the agent as a sidecar/daemon on the same host; in development you can point at the hosted endpoint or leave redirection disabled.

First use case — redirects managed outside your codebase. The classic win: marketing/SEO owns thousands of 301s, and you stop shipping Route::redirect() PRs. The middleware asks the agent "does any rule match this request?" and, if the answer is a redirect rule, short-circuits with a Location header before your router ever runs.

// config/redirectionio.php
return [
    'enabled'     => env('REDIRECTIONIO_ENABLED', false),
    'dsn'         => env('REDIRECTIONIO_DSN', 'tcp://127.0.0.1:10301'),
    'project_key' => env('REDIRECTIONIO_PROJECT_KEY'),
    'timeout'     => env('REDIRECTIONIO_TIMEOUT', 0.05), // seconds
    'persist'     => env('REDIRECTIONIO_PERSIST', true), // v0.4.0+
];
// app/Providers/AppServiceProvider.php
$this->app->singleton(\RedirectionIO\Client\Sdk\Agent::class, fn () => new \RedirectionIO\Client\Sdk\Agent(
    config('redirectionio.dsn'),
    config('redirectionio.project_key'),
    // v0.4.0: pass the persist flag through the connection/client options
    // (see src/ — the exact option name is `persist` or similar; verify).
));
// app/Http/Middleware/RedirectionIo.php
public function handle(Request $request, Closure $next)
{
    if (! config('redirectionio.enabled')) {
        return $next($request);
    }

    try {
        $rule = $this->agent->findMatchingRule(/* method, host, uri, headers, scheme, ip */);
    } catch (\Throwable $e) {
        report($e);              // never let the agent 500 your site
        return $next($request);
    }

    if ($rule && $rule->getLocation()) {
        return redirect()->away($rule->getLocation(), $rule->getStatusCode() ?: 302);
    }

    return $next($request);
}

Register it in the global stack in app/Http/Kernel.php ($middleware), not in the web group and not as route middleware — see the gotchas below.

Implementation Patterns

Bind the agent as a singleton, but make connection lazy. The agent client holds a socket. Constructing one per request is wasteful; constructing one at boot and connecting eagerly means a boot-time crash when the agent isn't up yet. Let the singleton be created cheaply and let the first query open the connection.

Use the new persist flag deliberately (v0.4.0). The client connection can now be told not to persist. Think of it as choosing a connection lifecycle, not flipping a random switch:

  • persist = true (the traditional behaviour) — the socket is kept open and reused across queries within the same process. Best for Octane/Swoole/RoadRunner workers and any long-lived process, where you amortise the handshake over thousands of requests. Also fine in PHP-FPM if the singleton is scoped per request, because it only saves a single connect.
  • persist = false — a fresh connection is opened (and closed) for each query. Use this when the agent sits behind a TCP proxy/load balancer with a short idle timeout, when the agent restarts frequently in development, or when you have been chasing "connection reset by peer" errors that only appear after a period of low traffic. It costs you a handshake per lookup — trivial on loopback, measurable across a network hop.

In practice: leave it on in production, turn it off in local/dev and anywhere the agent endpoint is not a stable local socket. Expose it as an env var so you can flip it without a deploy — that is the whole point of the release.

Place the middleware globally and early. Because Laravel's global middleware stack runs before dispatchToRoute() resolves a route, a globally-registered middleware will fire for URLs that no longer have a route — which is exactly the case you care about (old URLs 404'ing after a site restructure). Put it after TrustProxies and TrimStrings so scheme/host and the URI are already normalised, and before anything that does DB work you could skip on a redirect.

Fail open, always. The only correct failure mode for a redirect proxy is "serve the request as if no rule matched". Wrap the call in try/catch, report() the exception, and fall through. Consider a short-lived cache flag (or Laravel's Cache::add) to stop hammering a dead agent for the next 30 seconds.

Log the decision, not just the outcome. A redirect that "just happens" is invisible in your app. Log rule_id, matched path, target and status at info level (or a dedicated channel) behind a sampling switch — this is the only practical way to answer "why did /pricing redirect to /plans on Tuesday?" when a rule was edited in the manager by someone else.

Model it as config-driven, environment-aware. Default enabled to false and turn it on per environment. In phpunit.xml, leave it off: tests should be hermetic, and a missing agent should not turn a green suite red. For the one or two tests that exercise the feature, bind a stub:

$this->app->instance(Agent::class, new class extends Agent {
    public function findMatchingRule(...$args) { return new FakeRule('/new-home', 301); }
});

Multi-tenant / multi-domain. Host-based rules need the correct host sent to the agent. If you serve several domains from one app, pass $request->getHost() (post-TrustProxies) rather than config('app.url'), and if each brand has its own project key, resolve the agent from a small registry keyed by host instead of a single singleton.

Pair it with a redirect cache. The agent is fast (local socket, sub-millisecond), but the redirect response itself should still carry long-lived Cache-Control headers so your CDN/Varnish absorbs repeat traffic. That is the cheapest latency win available here.

Octane / long-running workers. With Octane, RoadRunner or Swoole the singleton socket survives across requests and across worker forks. This is the scenario the persist flag exists for, but do not assume it solves staleness: even with a persistent connection you should register a reconnect/health-check on RequestReceived (or catch exceptions and re-instantiate the agent on failure). With persist = false the staleness problem disappears but you pay a connect per request — measure before choosing.

Gotchas and Tips

  • v0.4.0 is connection-level only. One bullet in the changelog: the persist flag on client connections is now configurable. No new rule APIs, no new middleware hooks, no Laravel affordances. Nothing else in this assessment changes; if you already have working glue, the release is a no-op until you choose to set the flag.
  • Confirm the option's name and default in src/. The release note does not say whether persistence defaults to on (backwards-compatible) or off. Check before assuming your existing behaviour is unchanged — especially on Octane, where a flipped default would mean a connect per request.
  • The package has been dormant. Treat it as vendored code: fork it, or pin the version and expect to patch it yourself. Verify PHP 8.x compatibility (composer why-not redirectionio/proxy-sdk) before committing — an old composer.json constraint can block the install on 8.1+.
  • The #1 bug is a mismatched URI. The agent matches on the path including the query string. Use $request->getRequestUri() (or getPathInfo() + ? + getQueryString()), not $request->path(), and do not URL-decode before sending. Rules authored against /foo?a=b will silently never match /foo.
  • Symlinked/rewritten apps and proxies break matching. Behind a load balancer, $request->getScheme() returns http unless TrustProxies is configured — HTTPS-only rules then never fire. Same for getHost().
  • Redirect loops. If a rule's destination also matches a rule, the browser ping-pongs until it gives up. Add a cheap guard: skip the redirect if the computed target equals the current absolute URL.
  • redirect()->away($url, $status) defaults to 302. Only 301/302/307/308 are meaningful here; 307/308 preserve the method and body, which matters if a POST is being redirected. Never use 301 while you are still authoring rules — browsers cache it indefinitely.
  • Regex replacements. Many rules rewrite the path with a capture group rather than providing a full literal Location. Use the SDK's own replacement helper on the rule object instead of concatenating strings, or you will lose query strings and get double-encoded paths.
  • Socket errors surface at query time, not construction time. If you only test "does the app boot", a down agent will first bite you under real traffic. Add a health check (an artisan command or /up extension) that performs one no-op query — and run it with both persist settings if you change the flag.
  • Do not run it in console. Queue workers, artisan commands and scheduled jobs have no meaningful request context; guard with app()->runningInConsole() or just keep enabled=false outside HTTP. Note that console processes are exactly where a non-persistent connection is pointless — the process exits before reuse matters.
  • getStatusCode() may be null on rules that are not redirects (e.g. deny/other actions the agent may return). Check getLocation() first, as in the snippet, rather than trusting the status.
  • No extension points to speak of. There is no event dispatcher, no plugin hook, no logger interface to swap. Everything you want to observe must be wrapped around the call — which is another reason to keep the SDK behind one small middleware class of your own, so you can replace the whole thing with a raw socket call or a fork later without touching the rest of the app.
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.
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
frankyso/ipaymu
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core