/** * @param Application $app */ protected function bootstrapDreamFactory($app) { // Get an instance of the cluster service $app->register(new ClusterServiceProvider($app)); /** @type ClusterService $_cluster */ $_cluster = ClusterServiceProvider::service($app); $_vars = ['DF_CACHE_PREFIX' => $_cluster->getCachePrefix(), 'DF_CACHE_PATH' => $_cluster->getCachePath(), 'DF_LIMITS_CACHE_STORE' => ManagedDefaults::DEFAULT_LIMITS_STORE, 'DF_LIMITS_CACHE_PATH' => Disk::path([$_cluster->getCacheRoot(), '.limits'], true), 'DF_MANAGED_SESSION_PATH' => Disk::path([$_cluster->getCacheRoot(), '.sessions'], true), 'DF_MANAGED_LOG_FILE' => $_cluster->getHostName() . '.log', 'DF_MANAGED' => true, 'DB_DRIVER' => 'mysql']; // Get the cluster database information foreach ($_cluster->getDatabaseConfig() as $_key => $_value) { $_vars['DB_' . strtr(strtoupper($_key), '-', '_')] = $_value; } // Throw in some paths if (!empty($_paths = $_cluster->getConfig('paths', []))) { foreach ($_paths as $_key => $_value) { $_vars['DF_MANAGED_' . strtr(strtoupper($_key), '-', '_')] = $_value; } } // If this is a console request, denote it as such $_vars['DF_CONSOLE_KEY'] = $_cluster->getConsoleKey(); // Is it a console request? Validate /** @type Request $_request */ $_request = $app->make('request'); $_vars['DF_IS_VALID_CONSOLE_REQUEST'] = $_vars['DF_CONSOLE_KEY'] == $_request->header(ManagedDefaults::CONSOLE_X_HEADER, $_request->query('console_key')); // Now jam everything into the environment foreach ($_vars as $_key => $_value) { putenv($_key . '=' . $_value); $_ENV[$_key] = $_value; $_SERVER[$_key] = $_value; } // Finally, let the cluster service push some middleware onto the stack if ($_cluster instanceof HasMiddleware) { $_cluster->pushMiddleware($app->make('Illuminate\\Contracts\\Http\\Kernel')); } }
/** * Handle an incoming request. * * @param \Illuminate\Http\Request $request * @param \Closure $next * * @return mixed */ public function handle(Request $request, Closure $next) { /** * It is assumed, if you get this far, that ClusterServiceProvider was registered via * the ManagedInstance bootstrapper. If not, you're in a world of shit. * * We use provider's service() method because Facades are not loaded yet */ $_cluster = ClusterServiceProvider::service(); // Get limits or bail if (!$_cluster instanceof ProvidesManagedLimits || empty($limits = $_cluster->getLimits())) { return $next($request); } $this->testing = config('api_limits_test', 'testing' == env('APP_ENV')); if (!empty($limits)) { $userName = $this->getUser(Session::getCurrentUserId()); $userRole = $this->getRole(Session::getRoleId()); $apiName = $this->getApiKey(Session::getApiKey()); $clusterName = $_cluster->getClusterId(); $instanceName = $_cluster->getInstanceName(); $serviceName = $this->getServiceName($request); $limits = json_encode($limits); //TODO: Update dfe-console to properly set this, but right now, we want to touch as few files as possible if (!$this->testing) { $limits = str_replace(['cluster.default', 'instance.default'], [$clusterName, $clusterName . '.' . $instanceName], $limits); } // Convert to an array $limits = json_decode($limits, true); // Build the list of API Hits to check $apiKeysToCheck = [$clusterName . '.' . $instanceName => 0]; $serviceKeys = []; if ($serviceName) { $serviceKeys[$serviceName] = 0; $userRole && ($serviceKeys[$serviceName . '.' . $userRole] = 0); $userName && ($serviceKeys[$serviceName . '.' . $userName] = 0); } if ($apiName) { $apiKeysToCheck[$clusterName . '.' . $instanceName . '.' . $apiName] = 0; $userRole && ($apiKeysToCheck[$clusterName . '.' . $instanceName . '.' . $apiName . '.' . $userRole] = 0); $userName && ($apiKeysToCheck[$clusterName . '.' . $instanceName . '.' . $apiName . '.' . $userName] = 0); foreach ($serviceKeys as $key => $value) { $apiKeysToCheck[$apiName . '.' . $key] = $value; } } if ($clusterName) { $apiKeysToCheck[$clusterName] = 0; $userRole && ($apiKeysToCheck[$clusterName . '.' . $instanceName . '.' . $userRole] = 0); $userName && ($apiKeysToCheck[$clusterName . '.' . $instanceName . '.' . $userName] = 0); foreach ($serviceKeys as $key => $value) { $apiKeysToCheck[$clusterName . '.' . $instanceName . '.' . $key] = $value; } } /* Per Ben, we want to increment every limit they hit, not stop after the first one */ $overLimit = []; try { foreach (array_keys($apiKeysToCheck) as $key) { foreach ($this->periods as $period) { $_checkKey = $key . '.' . $period; /** @noinspection PhpUndefinedMethodInspection */ if (array_key_exists($_checkKey, $limits['api'])) { $_limit = $limits['api'][$_checkKey]; // For any cache drivers that make use of the cache prefix, we need to make sure we use // a prefix that every instance can see. But first, grab the current value $dfCachePrefix = env('DF_CACHE_PREFIX'); putenv('DF_CACHE_PREFIX' . '=' . 'df_limits'); $_ENV['DF_CACHE_PREFIX'] = $_SERVER['DF_CACHE_PREFIX'] = 'df_limits'; // Increment counter $cacheValue = $this->cache()->get($_checkKey, 0); $cacheValue++; if ($cacheValue > $_limit['limit']) { // Push the name of the rule onto the over-limit array so we can give the name in the 429 error message $overLimit[] = array_get($_limit, 'name', $_checkKey); } else { // Only increment the counter if we are not over the limit. Fixes DFE-205 $this->cache()->put($_checkKey, $cacheValue, $_limit['period']); } // And now set it back putenv('DF_CACHE_PREFIX' . '=' . $dfCachePrefix); $_ENV['DF_CACHE_PREFIX'] = $_SERVER['DF_CACHE_PREFIX'] = $dfCachePrefix; } } } } catch (\Exception $_ex) { return ResponseFactory::getException(new InternalServerErrorException('Unable to update cache: ' . $_ex->getMessage()), $request); } if ($overLimit) { /* Per Ben, we want to increment every limit they hit, not stop after the first one */ return ResponseFactory::getException(new TooManyRequestsException('API limit(s) exceeded: ' . implode(', ', $overLimit)), $request); } } return $next($request); }