Last active
June 3, 2016 12:43
-
-
Save huebs/31625a8b221b52c83bd8033e770d802d to your computer and use it in GitHub Desktop.
Simple implementation of a rate limiter in PHP.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/** | |
* @author Ben Huebscher <[email protected]> | |
*/ | |
class RateLimiter | |
{ | |
/** | |
* @var int | |
*/ | |
private $max; | |
/** | |
* @var float | |
*/ | |
private $interval; | |
/** | |
* @var float | |
*/ | |
private $start; | |
/** | |
* @var int | |
*/ | |
private $count; | |
/** | |
* @param int $max per interval | |
* @param float $interval in seconds | |
*/ | |
public function __construct($max, $interval) | |
{ | |
if (0 >= ($max = (int) $max)) { | |
throw new InvalidArgumentException('Max must be an integer greater than 0.'); | |
} | |
if (0.0 >= ($interval = (float) $interval)) { | |
throw new InvalidArgumentException('Interval must be greater than 0.'); | |
} | |
$this->max = $max; | |
$this->interval = $interval; | |
$this->reset(); | |
} | |
/** | |
* @return int | |
*/ | |
public function getMax() | |
{ | |
return $this->max; | |
} | |
/** | |
* @return float | |
*/ | |
public function getInterval() | |
{ | |
return $this->interval; | |
} | |
/** | |
* @param int $stride | |
*/ | |
public function increment($stride = 1) | |
{ | |
if ($this->limitReached()) { | |
$this->waitUntilNextInterval(); | |
} | |
$this->count += $stride; | |
} | |
/** | |
* @return bool | |
*/ | |
private function limitReached() | |
{ | |
if ($this->canReset()) { | |
$this->reset(); | |
} | |
return $this->count >= $this->max; | |
} | |
/** | |
* @return int | |
*/ | |
private function waitUntilNextInterval() | |
{ | |
$waitTime = $this->timeUntilNextReset(); | |
while (true !== ($waitTime = time_nanosleep($waitTime['seconds'], $waitTime['nanoseconds']))) { | |
if (false === $waitTime) { | |
throw new RuntimeException('Rate Limiter Failed'); | |
} | |
} | |
$this->reset(); | |
} | |
/** | |
* @return int[] | |
*/ | |
private function timeUntilNextReset() | |
{ | |
return self::arrayFromMicrotime($this->interval - $this->timeSinceLastReset()); | |
} | |
/** | |
* @return bool | |
*/ | |
private function canReset() | |
{ | |
return $this->timeSinceLastReset() > $this->interval; | |
} | |
/** | |
* @return int | |
*/ | |
private function timeSinceLastReset() | |
{ | |
return $this->start + microtime(true); | |
} | |
private function reset() | |
{ | |
$this->start = -microtime(true); | |
$this->count = 0; | |
} | |
/** | |
* @param float $microtime | |
* | |
* @return int[] | |
*/ | |
public static function arrayFromMicrotime($microtime) | |
{ | |
$time['seconds'] = (int) floor($microtime); | |
$time['nanoseconds'] = (int) round(($microtime - $time['seconds']) * 1000000000); | |
return $time; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment