From bfbe6d19b9ffe86c0a55952a8c844b566fdfaba9 Mon Sep 17 00:00:00 2001 From: Michel Bardelmeijer Date: Thu, 17 Sep 2015 23:07:19 +0200 Subject: [PATCH 1/3] Add composer support with PSR-4 autoloading --- .gitignore | 1 + README.md | 37 ++++----- composer.json | 16 ++++ .../RateExceededException.php | 42 ++-------- src/Ratelimiter.php | 77 +++++++++++++++++++ 5 files changed, 119 insertions(+), 54 deletions(-) create mode 100644 .gitignore create mode 100644 composer.json rename ratelimiter.php => src/RateExceededException.php (53%) create mode 100644 src/Ratelimiter.php diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..57872d0 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/vendor/ diff --git a/README.md b/README.md index 6011aa6..f241605 100644 --- a/README.md +++ b/README.md @@ -7,19 +7,21 @@ The class works around the problem that the timeframe is constantly moving, i.e. The code is released under an MIT license. +Installation +----- + composer require akirk/php-ratelimiter + Usage ----- -```php -$rateLimiter = new RateLimiter(new Memcache(), $_SERVER["REMOTE_ADDR"]); -try { - // allow a maximum of 100 requests for the IP in 5 minutes - $rateLimiter->limitRequestsInMinutes(100, 5); -} catch (RateExceededException $e) { - header("HTTP/1.0 529 Too Many Requests"); - exit; -} -``` + $rateLimiter = new \Akirk\Ratelimiter\Ratelimiter(new \Memcache(), $_SERVER["REMOTE_ADDR"]); + try { + // allow a maximum of 100 requests for the IP in 5 minutes + $rateLimiter->limitRequestsInMinutes(100, 5); + } catch (\Akirk\Ratelimiter\RateExceededException $e) { + header("HTTP/1.0 529 Too Many Requests"); + exit; + } Remarks ------- @@ -28,13 +30,12 @@ The script creates a memcached entry per IP and minute. If you want to protect multiple resources with different limits, use the third parameter of the constructor to namespace it: -```php -// script1.php -$rateLimiter = new RateLimiter(new Memcache(), $_SERVER["REMOTE_ADDR"], "script1"); -try { ... } -// script2.php -$rateLimiter = new RateLimiter(new Memcache(), $_SERVER["REMOTE_ADDR"], "script2"); -try { ... } -``` + // script1.php + $rateLimiter = new \Akirk\Ratelimiter\Ratelimiter(new \Memcache(), $_SERVER["REMOTE_ADDR"], "script1"); + try { ... } + + // script2.php + $rateLimiter = new \Akirk\Ratelimiter\Ratelimiter(new \Memcache(), $_SERVER["REMOTE_ADDR"], "script2"); + try { ... } You can also use something else as a second parameter, for example a `session_id` to limit the requests per user instead of IP address. diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..aa0e671 --- /dev/null +++ b/composer.json @@ -0,0 +1,16 @@ +{ + "name": "akirk/php-ratelimiter", + "description": "A small class that uses Memcache to allow only a certain number of requests per a certain amount of minutes.", + "type": "library", + "license": "MIT", + "minimum-stability": "stable", + "require": { + "php": ">=5.5", + "ext-memcache": "*" + }, + "autoload": { + "psr-4": { + "Akirk\\Ratelimiter\\": "src/" + } + } +} diff --git a/ratelimiter.php b/src/RateExceededException.php similarity index 53% rename from ratelimiter.php rename to src/RateExceededException.php index 24bb875..e0f685c 100644 --- a/ratelimiter.php +++ b/src/RateExceededException.php @@ -1,4 +1,5 @@ memcache = $memcache; - $this->prefix = $prefix . $ip; - } - - public function limitRequestsInMinutes($allowedRequests, $minutes) { - $requests = 0; - - foreach ($this->getKeys($minutes) as $key) { - $requestsInCurrentMinute = $this->memcache->get($key); - if (false !== $requestsInCurrentMinute) $requests += $requestsInCurrentMinute; - } - - if (false === $requestsInCurrentMinute) { - $this->memcache->set($key, 1, 0, $minutes * 60 + 1); - } else { - $this->memcache->increment($key, 1); - } - - if ($requests > $allowedRequests) throw new RateExceededException; - } - - private function getKeys($minutes) { - $keys = array(); - $now = time(); - for ($time = $now - $minutes * 60; $time <= $now; $time += 60) { - $keys[] = $this->prefix . date("dHi", $time); - } - - return $keys; - } -} +namespace Akirk\Ratelimiter; + +use Exception; + +class RateExceededException extends Exception { } diff --git a/src/Ratelimiter.php b/src/Ratelimiter.php new file mode 100644 index 0000000..bef9614 --- /dev/null +++ b/src/Ratelimiter.php @@ -0,0 +1,77 @@ +memcache = $memcache; + $this->prefix = $prefix . $ip; + } + + public function limitRequestsInMinutes($allowedRequests, $minutes) + { + $requests = 0; + + foreach ($this->getKeys($minutes) as $key) { + $requestsInCurrentMinute = $this->memcache->get($key); + + if (false !== $requestsInCurrentMinute) { + $requests += $requestsInCurrentMinute; + } + } + + if (false === $requestsInCurrentMinute) { + $this->memcache->set($key, 1, 0, $minutes * 60 + 1); + } else { + $this->memcache->increment($key, 1); + } + + if ($requests > $allowedRequests) { + throw new RateExceededException; + } + } + + private function getKeys($minutes) + { + $keys = []; + + $now = time(); + for ($time = $now - $minutes * 60; $time <= $now; $time += 60) { + $keys[] = $this->prefix . date("dHi", $time); + } + + return $keys; + } +} From bf86f1b00d56bdf5dab8e9d9c0f85b2ffbc4fbcb Mon Sep 17 00:00:00 2001 From: Michel Bardelmeijer Date: Thu, 17 Sep 2015 23:09:26 +0200 Subject: [PATCH 2/3] Fix markdown in readme.md --- README.md | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index f241605..cdd01c5 100644 --- a/README.md +++ b/README.md @@ -13,15 +13,15 @@ Installation Usage ----- - - $rateLimiter = new \Akirk\Ratelimiter\Ratelimiter(new \Memcache(), $_SERVER["REMOTE_ADDR"]); - try { - // allow a maximum of 100 requests for the IP in 5 minutes - $rateLimiter->limitRequestsInMinutes(100, 5); - } catch (\Akirk\Ratelimiter\RateExceededException $e) { - header("HTTP/1.0 529 Too Many Requests"); - exit; - } +```php +$rateLimiter = new \Akirk\Ratelimiter\Ratelimiter(new \Memcache(), $_SERVER["REMOTE_ADDR"]); +try { + // allow a maximum of 100 requests for the IP in 5 minutes + $rateLimiter->limitRequestsInMinutes(100, 5); +} catch (\Akirk\Ratelimiter\RateExceededException $e) { + header("HTTP/1.0 529 Too Many Requests"); + exit; +}``` Remarks ------- @@ -30,12 +30,13 @@ The script creates a memcached entry per IP and minute. If you want to protect multiple resources with different limits, use the third parameter of the constructor to namespace it: - // script1.php - $rateLimiter = new \Akirk\Ratelimiter\Ratelimiter(new \Memcache(), $_SERVER["REMOTE_ADDR"], "script1"); - try { ... } +```php +// script1.php +$rateLimiter = new \Akirk\Ratelimiter\Ratelimiter(new \Memcache(), $_SERVER["REMOTE_ADDR"], "script1"); +try { ... } - // script2.php - $rateLimiter = new \Akirk\Ratelimiter\Ratelimiter(new \Memcache(), $_SERVER["REMOTE_ADDR"], "script2"); - try { ... } +// script2.php +$rateLimiter = new \Akirk\Ratelimiter\Ratelimiter(new \Memcache(), $_SERVER["REMOTE_ADDR"], "script2"); +try { ... }``` You can also use something else as a second parameter, for example a `session_id` to limit the requests per user instead of IP address. From 1f4bfc2fad4b521cc175a43efa256b3e6d0ea0ec Mon Sep 17 00:00:00 2001 From: Michel Bardelmeijer Date: Thu, 17 Sep 2015 23:10:34 +0200 Subject: [PATCH 3/3] Whoops --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index cdd01c5..df7294c 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,8 @@ try { } catch (\Akirk\Ratelimiter\RateExceededException $e) { header("HTTP/1.0 529 Too Many Requests"); exit; -}``` +} +``` Remarks ------- @@ -37,6 +38,7 @@ try { ... } // script2.php $rateLimiter = new \Akirk\Ratelimiter\Ratelimiter(new \Memcache(), $_SERVER["REMOTE_ADDR"], "script2"); -try { ... }``` +try { ... } +``` You can also use something else as a second parameter, for example a `session_id` to limit the requests per user instead of IP address.