app/Http/Middleware/DTInterceptor.php (113 lines of code) (raw):

<?php namespace App\Http\Middleware; use Closure; use Illuminate\Http\Request; use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Log; class DTInterceptor { private const OPBEANS_DT_PROBABILITY_ENV_VAR_NAME = 'OPBEANS_DT_PROBABILITY'; private const OPBEANS_DT_PROBABILITY_DEFAULT = 0.5; private const OPBEANS_SERVICES_ENV_VAR_NAME = 'OPBEANS_SERVICES'; private bool $isEnabled = false; private float $probability; /** @var string[] */ private array $services; public function __construct() { $buildForwardingWillDisabledMsg = function (string $reason): string { return $reason . ' - forwarding to other backend services will be disabled'; }; $serviceName = config('app.name'); $probabilityEnvVarVal = env(self::OPBEANS_DT_PROBABILITY_ENV_VAR_NAME); if ($probabilityEnvVarVal === null) { Log::info( $buildForwardingWillDisabledMsg( self::OPBEANS_DT_PROBABILITY_ENV_VAR_NAME . ' environment variable is not set' ), ['this' => $this->selfToLog()] ); return; } if (filter_var($probabilityEnvVarVal, FILTER_VALIDATE_FLOAT) === false) { $probability = self::OPBEANS_DT_PROBABILITY_DEFAULT; Log::error( self::OPBEANS_DT_PROBABILITY_ENV_VAR_NAME . ' environment variable value' . ' (' . $probabilityEnvVarVal. ')' . ' is not a valid float - using default value (' . self::OPBEANS_DT_PROBABILITY_DEFAULT . ')' ); } else { $probability = floatval($probabilityEnvVarVal); } $servicesEnvVarVal = env(self::OPBEANS_SERVICES_ENV_VAR_NAME); if ($servicesEnvVarVal === null) { Log::info( $buildForwardingWillDisabledMsg( self::OPBEANS_SERVICES_ENV_VAR_NAME . ' environment variable is not set' ), ['this' => $this->selfToLog()] ); return; } $services = self::parseServices($servicesEnvVarVal); if (empty($services)) { Log::error( $buildForwardingWillDisabledMsg( self::OPBEANS_SERVICES_ENV_VAR_NAME . ' environment variable does not contain valid URLs' ), ['this' => $this->selfToLog()] ); return; } $this->probability = $probability; $this->services = $services; $this->isEnabled = true; Log::info('Forwarding to other backend services is enabled', ['this' => $this->selfToLog()]); } /** * @return string[] */ private static function parseServices(string $envVarVal): array { $result = []; $envVarValAsList = explode(',', $envVarVal); foreach ($envVarValAsList as $urlPrefix){ $urlPrefix = trim($urlPrefix); if (empty($urlPrefix)) { continue; } if (str_ends_with($urlPrefix, '/')) { $urlPrefix = substr($urlPrefix, 0, -1); } $result[] = $urlPrefix; } return $result; } /** * @return array<string, mixed> */ private function selfToLog(): array { return get_object_vars($this); } private function randomDecideIfToForward(): bool { return (mt_rand() / mt_getrandmax()) <= $this->probability; } private function buildForwardUrl(string $service, string $requestPath): string { return str_starts_with($requestPath, '/') ? ($service . $requestPath) : ($service . '/' . $requestPath); } private function randomForwardToService(Request $request): bool { if (!$this->randomDecideIfToForward()) { Log::info('Decided not to forward', ['this' => $this->selfToLog()]); return false; } $serviceIndex = array_rand($this->services); $forwardUrl = $this->buildForwardUrl($this->services[$serviceIndex], $request->path()); Log::info('Decided to forward', ['forwardUrl' => $forwardUrl, 'this' => $this->selfToLog()]); $response = Http::get( $forwardUrl)->json(); abort(response()->json($response)); } public function handle(Request $request, Closure $next) { if ($this->isEnabled && $this->randomForwardToService($request)) { // this return is not reachable return null; } return $next($request); } }