public function testGetErrorFalse() { $obj = new \stdClass(); $obj->title = 'Rock star'; $cr = new CallbackResult(); $cr->setData($obj); $this->assertFalse($cr->getError()); }
/** * Checks if user is within rate limits. * * @param RequestBag $requestBag * @param CallbackResult $cr * * @return bool * @throws \Webiny\Component\Rest\RestException */ public static function isWithinRateLimits(RequestBag $requestBag, CallbackResult $cr) { // do we have rate control in place? if (!($rateControl = $requestBag->getApiConfig()->get('RateControl', false))) { return true; // if rate control is not set, user is within his limits } // check if we should ignore rate control for this particular method if (isset($requestBag->getMethodData()['rateControl']['ignore'])) { return true; } // verify that we have a Cache service set if (!($cache = $requestBag->getApiConfig()->get('Cache', false))) { throw new RestException('Rest Rate Control requires that you have a Cache service defined under the Rest configuration.'); } // set the limit in response header $cr->attachDebugHeader('RateControl-Limit', $rateControl->Limit, true); // get current usage $cacheKey = md5('Webiny.Rest.RateLimit.' . self::httpRequest()->getClientIp()); $cacheData = self::cache($cache)->read($cacheKey); if (!$cacheData) { $cacheData = ['usage' => 0, 'penalty' => 0, 'ttl' => time() + 60 * $rateControl->Interval]; } else { $cacheData = self::unserialize($cacheData); // validate the ttl if (time() > $cacheData['ttl']) { $cacheData = ['usage' => 0, 'penalty' => 0, 'ttl' => time() + 60 * $rateControl->Interval]; } } // check if user is already in penalty if ($cacheData['penalty'] > time()) { // when in penalty the reset value, equals the penalty value $cr->attachDebugHeader('RateControl-Reset', $cacheData['penalty'] - time(), true); // and the remaining equals 0 $cr->attachDebugHeader('RateControl-Remaining', 0, true); return false; } // check if rate is reached if ($cacheData['usage'] >= $rateControl->Limit) { // set penalty for reaching the limit $cr->attachDebugHeader('RateControl-Reset', $rateControl->Penalty * 60 + time(), true); // and the remaining 0 $cr->attachDebugHeader('RateControl-Remaining', 0, true); return false; } // if limit not reached, increment the usage and save the data $cacheData['usage']++; $cr->attachDebugHeader('RateControl-Remaining', $rateControl->Limit - $cacheData['usage'], true); $cr->attachDebugHeader('RateControl-Reset', $cacheData['ttl'], true); $cacheTtl = $rateControl->Interval > $rateControl->Penalty ? $rateControl->Interval : $rateControl->Penalty; self::cache($cache)->save($cacheKey, self::serialize($cacheData), $cacheTtl * 60); return true; }
/** * Processes the callback and returns an instance of CallbackResult. * * @return CallbackResult * @throws \Webiny\Component\Rest\RestException */ public function getCallbackResult() { $class = $this->requestBag->getClassData()['class']; $this->requestBag->setClassInstance(new $class()); // create CallbackResult instance $cr = new CallbackResult(); $env = 'production'; if ($this->requestBag->getApiConfig()->get('Environment', 'production') == 'development') { $cr->setEnvToDevelopment(); $env = 'development'; } // attach some metadata $cr->attachDebugHeader('Class', $class); $cr->attachDebugHeader('ClassVersion', $this->requestBag->getClassData()['version']); $cr->attachDebugHeader('Method', strtoupper($this->httpRequest()->getRequestMethod())); if (!$this->requestBag->getMethodData()) { // if no method matched the request $cr->setHeaderResponse(404); $cr->setErrorResponse('No service matched the request.'); return $cr; } // check rate limit try { $rateControl = RateControl::isWithinRateLimits($this->requestBag, $cr); if (!$rateControl) { $cr->setHeaderResponse(429); $cr->setErrorResponse('Rate control limit reached.'); return $cr; } } catch (\Exception $e) { throw new RestException('Rate control verification failed. ' . $e->getMessage()); } // verify access role try { $hasAccess = Security::hasAccess($this->requestBag); if (!$hasAccess) { $cr->setHeaderResponse(403); $cr->setErrorResponse('You don\'t have the required access level.'); $cr->attachDebugHeader('RequestedRole', $this->requestBag->getMethodData()['role']); return $cr; } } catch (\Exception $e) { throw new RestException('Access role verification failed. ' . $e->getMessage()); } // verify cache try { $cachedResult = Cache::getFromCache($this->requestBag); } catch (\Exception $e) { throw new RestException('Reading result from cache failed. ' . $e->getMessage()); } // finalize output if ($cachedResult) { $cr->setData($cachedResult); $cr->attachDebugHeader('Cache', 'HIT'); } else { try { $result = call_user_func_array([$this->requestBag->getClassInstance(), $this->requestBag->getMethodData()['method']], $this->requestBag->getMethodParameters()); // check if method has custom headers set $cr->setHeaderResponse($this->requestBag->getMethodData()['header']['status']['success']); $cr->attachDebugHeader('Cache', 'MISS'); // add result to output $cr->setData($result); // check if we need to attach the cache headers if ($this->requestBag->getMethodData()['header']['cache']['expires'] > 0) { $cr->setExpiresIn($this->requestBag->getMethodData()['header']['cache']['expires']); } // cache the result Cache::saveResult($this->requestBag, $result); } catch (RestErrorException $re) { // check if method has custom headers set $cr->setHeaderResponse($statusCode = $re->getResponseCode(), $this->requestBag->getMethodData()['header']['status']['errorMessage']); // check if a custom http response code is set $cr->setErrorResponse($re->getErrorMessage(), $re->getErrorDescription(), $re->getErrorCode()); $errors = $re->getErrors(); foreach ($errors as $e) { $cr->addErrorMessage($e); } if ($env == 'development') { $cr->addDebugMessage(['file' => $re->getFile(), 'line' => $re->getLine(), 'traces' => explode('#', $re->getTraceAsString())]); } } catch (\Exception $e) { // check if method has custom headers set $cr->setHeaderResponse($this->requestBag->getMethodData()['header']['status']['error'], $this->requestBag->getMethodData()['header']['status']['errorMessage']); $cr->setErrorResponse('There has been an error processing the request.'); if ($env == 'development') { $cr->addErrorMessage(['message' => $e->getMessage()]); $cr->addDebugMessage(['file' => $e->getFile(), 'line' => $e->getLine(), 'traces' => explode('#', $e->getTraceAsString())]); } } } return $cr; }