Skip to content

Commit

Permalink
Rate Limits by Route
Browse files Browse the repository at this point in the history
Allow the ability to update rate limits by route
  • Loading branch information
filliph committed Feb 9, 2017
1 parent 2955b43 commit 3b9de97
Show file tree
Hide file tree
Showing 4 changed files with 94 additions and 34 deletions.
5 changes: 5 additions & 0 deletions config/rate_limit.global.php.dist
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ return [
*/
'routes' => [],

/**
* Lets you define separate limit / period settings per-route. Example: 'dragon-byte-tech.rpc.version-check' => ['limit' => 10, 'period' => 30]
*/
'route_specific_limits' => [],

/**
* This should be an array compatible with Zend Cache.
*/
Expand Down
27 changes: 21 additions & 6 deletions src/Mvc/RateLimitRequestListener.php
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,7 @@ public function attach(EventManagerInterface $events, $priority = 1)
public function onRoute(MvcEvent $event)
{
$request = $event->getRequest();
$router = $event->getRouter();
$routeMatch = $router->match($request);
$routeMatch = $event->getRouter()->match($request);

if (!$request instanceof HttpRequest) {
return;
Expand All @@ -94,13 +93,13 @@ public function onRoute(MvcEvent $event)

try {
// Check if we're within the limit
$this->rateLimitService->rateLimitHandler();
$this->rateLimitService->rateLimitHandler($routeMatch->getMatchedRouteName());

// Update the response
$response = $event->getResponse();

// Add the headers to the response
$this->rateLimitService->ensureHeaders($response);
$this->ensureHeaders($response);

// Set the response back
$event->setResponse($response);
Expand All @@ -113,27 +112,43 @@ public function onRoute(MvcEvent $event)
);

// Add the headers so clients will know when they can try again
$this->rateLimitService->ensureHeaders($response);
$this->ensureHeaders($response);

// And we're done here
return $response;
}
}

/**
* @param HttpResponse $response
* @return \Zend\Http\Headers
*/
public function ensureHeaders(HttpResponse $response)
{
$headers = $response->getHeaders();

$headers->addHeaderLine('X-RateLimit-Limit', $this->rateLimitService->getLimit());
$headers->addHeaderLine('X-RateLimit-Remaining', $this->rateLimitService->getRemainingCalls());
$headers->addHeaderLine('X-RateLimit-Reset', $this->rateLimitService->getTimeToReset());

return $headers;
}

/**
* @param RouteMatch $routeMatch
* @return bool
*/
private function hasRoute(RouteMatch $routeMatch)
{
$routes = $this->rateLimitService->getRoutes();
$currentRoute = $routeMatch->getMatchedRouteName();

if (!$routes) {
return false;
}

foreach ($routes as $route) {
if (fnmatch($route, $routeMatch->getMatchedRouteName())) {
if (fnmatch($route, $currentRoute)) {
return true;
}
}
Expand Down
55 changes: 55 additions & 0 deletions src/Options/RateLimitOptions.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ class RateLimitOptions extends AbstractOptions
*/
protected $routes = [];

/**
* @var array
*/
protected $route_specific_limits = [];

/**
* @var int
*/
Expand All @@ -48,6 +53,17 @@ class RateLimitOptions extends AbstractOptions
*/
protected $period = 0;


/**
* Constructor
*
* @param array|Traversable|null $options
*/
public function __construct($options = null)
{
parent::__construct($options);
}

/**
* @return string
*/
Expand Down Expand Up @@ -80,6 +96,22 @@ public function setRoutes($routes)
$this->routes = $routes;
}

/**
* @param array $routeSpecificLimits
*/
public function setRouteSpecificLimits($routeSpecificLimits)
{
$this->route_specific_limits = $routeSpecificLimits;
}

/**
* @return array
*/
public function getRouteSpecificLimits()
{
return $this->route_specific_limits;
}

/**
* @return int
*/
Expand Down Expand Up @@ -111,4 +143,27 @@ public function setPeriod($period)
{
$this->period = $period;
}

/**
* @param string $route
*/
public function setRouteSpecificLimitsFromRoute($route)
{
$routeSpecificLimits = $this->getRouteSpecificLimits();
if (!$route OR !isset($routeSpecificLimits[$route]))
{
return;
}

$options = $routeSpecificLimits[$route];

if (!is_array($options) AND !$options instanceof Traversable)
{
return;
}

foreach ($options as $key => $value) {
$this->__set($key, $value);
}
}
}
41 changes: 13 additions & 28 deletions src/Service/RateLimitService.php
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,14 @@ public function __construct(AbstractAdapter $storage, RateLimitOptions $rateLimi
/**
* @inheritdoc
*/
public function rateLimitHandler()
public function rateLimitHandler($route = '')
{
if ($route)
{
// Override the options based on this route
$this->rateLimitOptions->setRouteSpecificLimitsFromRoute($route);
}

if ($this->getRemainingCalls() == 0) {
throw new TooManyRequestsHttpException('Too Many Requests');
}
Expand All @@ -71,48 +77,27 @@ public function rateLimitHandler()
$this->saveDataInStorage($data);
}

/**
* @param HttpResponse $response
* @return \Zend\Http\Headers
*/
public function ensureHeaders(HttpResponse $response)
{
$headers = $response->getHeaders();

$headers->addHeaderLine('X-RateLimit-Limit', $this->getLimit());
$headers->addHeaderLine('X-RateLimit-Remaining', $this->getRemainingCalls());
$headers->addHeaderLine('X-RateLimit-Reset', $this->getTimeToReset());

return $headers;
}

/**
* @return int
*/
private function getLimit()
public function getLimit()
{
$limit = $this->rateLimitOptions->getLimit();
return $limit;
return $this->rateLimitOptions->getLimit();
}

/**
* @return int
*/
private function getRemainingCalls()
public function getRemainingCalls()
{
// Get the data from the cache
$data = $this->getDataFromStorage();

$limit = $this->rateLimitOptions->getLimit();
$calls = count($data);

return $limit - $calls;
// Make sure we never go below 0 as that will trip up the headers
return max(0, $this->getLimit() - count($this->getDataFromStorage()));
}

/**
* @return int
*/
private function getTimeToReset()
public function getTimeToReset()
{
// Get the data from the cache
$data = $this->getDataFromStorage();
Expand Down

0 comments on commit 3b9de97

Please sign in to comment.