Skip to content

Commit

Permalink
Merge pull request #28 from UseMuffin/dev
Browse files Browse the repository at this point in the history
Dev
  • Loading branch information
ADmad authored Apr 23, 2018
2 parents 7b8539f + c6050b5 commit 9eef7ed
Show file tree
Hide file tree
Showing 10 changed files with 218 additions and 138 deletions.
6 changes: 0 additions & 6 deletions .semver

This file was deleted.

22 changes: 15 additions & 7 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,9 @@ language: php

php:
- 7.0
- 5.5
- 5.6
- 7.1

dist: trusty
- 7.2

env:
global:
Expand All @@ -16,16 +14,24 @@ matrix:
fast_finish: true

include:
- php: 5.6
env: PREFER_LOWEST=1

- php: 7.0
env: PHPCS=1 DEFAULT=0

- php: 7.1
env: PHPSTAN=1 DEFAULT=0

before_script:
- if [[ $TRAVIS_PHP_VERSION != 7.* ]]; then phpenv config-rm xdebug.ini; fi
- if [[ $TRAVIS_PHP_VERSION != 7.0 ]]; then phpenv config-rm xdebug.ini; fi

- composer install --prefer-dist --no-interaction
- if [[ $PHPSTAN = 1 ]]; then composer require --dev phpstan/phpstan; fi

- if [[ $TRAVIS_PHP_VERSION =~ 5.[56] ]] ; then echo yes | pecl install apcu-4.0.10; fi
- if [[ $TRAVIS_PHP_VERSION = 7.0 ]] ; then echo yes | pecl install apcu-5.1.7; fi
- if [[ $PHPSTAN != 1 && $PREFER_LOWEST != 1 ]]; then composer install --no-interaction; fi
- if [[ $PHPSTAN != 1 && $PREFER_LOWEST = 1 ]]; then composer update --no-interaction --prefer-lowest --prefer-stable; fi

- echo 'extension = apcu.so' >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini
- echo 'apc.enable_cli = 1' >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini

script:
Expand All @@ -34,6 +40,8 @@ script:

- if [[ $PHPCS = 1 ]]; then vendor/bin/phpcs -p --extensions=php --standard=vendor/cakephp/cakephp-codesniffer/CakePHP ./src ./tests; fi

- if [[ $PHPSTAN = 1 ]]; then vendor/bin/phpstan analyse -l 4 src; fi

after_success:
- if [[ $DEFAULT = 1 && $TRAVIS_PHP_VERSION = 7.0 ]]; then bash <(curl -s https://codecov.io/bash); fi

Expand Down
37 changes: 29 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ app in a given time frame.

## Installation

```
```bash
composer require muffin/throttle
```
To make your application load the plugin either run:
Expand All @@ -26,7 +26,7 @@ To make your application load the plugin either run:
./bin/cake plugin load Muffin/Throttle
```

or add the following line to ``config/bootstrap.php``:
or add the following line to `config/bootstrap.php`:

```php
Plugin::load('Muffin/Throttle');
Expand Down Expand Up @@ -65,12 +65,14 @@ public function middleware($middleware)
// Various other middlewares for error handling, routing etc. added here.

$throttleMiddleware = new ThrottleMiddleware([
'message' => 'Rate limit exceeded',
'response' => [
'body' => 'Rate limit exceeded'
],
'interval' => '+1 hour',
'limit' => 300,
'identifier' => function (ServerRequestInterface $request) {
if (null !== $request->header('Authorization')) {
return str_replace('Bearer ', '', $request->header('Authorization'));
if (null !== $request->getHeaderLine('Authorization')) {
return str_replace('Bearer ', '', $request->getHeaderLine('Authorization'));
}
return $request->clientIp();
}
Expand Down Expand Up @@ -110,6 +112,23 @@ your configuration array:

To disable the headers set `headers` key to `false`.

### Customize response object

You may use `type` and `headers` subkeys of the `response` array (as you would do with a `Response` object) if you want to return a different message as the default one:

```php
new ThrottleMiddleware([
'response' => [
'body' => json_encode(['error' => 'Rate limit exceeded']),
'type' => 'json',
'headers' => [
'Custom-Header' => 'custom_value'
]
],
'limit' => 300
]);
```

### Using the Dispatch Filter

In `bootstrap.php`:
Expand All @@ -131,12 +150,14 @@ easily change that by passing your own configuration:

```php
DispatcherFactory::add('Muffin/Throttle.Throttle', [
'message' => 'Rate limit exceeded',
'response' => [
'body' => 'Rate limit exceeded'
],
'interval' => '+1 hour',
'limit' => 300,
'identifier' => function (Request $request) {
if (null !== $request->header('Authorization')) {
return str_replace('Bearer ', '', $request->header('Authorization'));
if (null !== $request->getHeaderLine('Authorization')) {
return str_replace('Bearer ', '', $request->getHeaderLine('Authorization'));
}
return $request->clientIp();
}
Expand Down
1 change: 0 additions & 1 deletion VERSION

This file was deleted.

6 changes: 3 additions & 3 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,11 @@
"source": "https://github.com/usemuffin/throttle"
},
"require": {
"cakephp/cakephp": "~3.0"
"cakephp/cakephp": "^3.5"
},
"require-dev": {
"cakephp/cakephp-codesniffer": "2.*",
"phpunit/phpunit": "<6.0"
"cakephp/cakephp-codesniffer": "^3.0",
"phpunit/phpunit": "^5.7.14|^6.0"
},
"autoload": {
"psr-4": {
Expand Down
36 changes: 23 additions & 13 deletions src/Middleware/ThrottleMiddleware.php
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
<?php
namespace Muffin\Throttle\Middleware;

use Cake\Cache\Cache;
use Cake\Core\InstanceConfigTrait;
use Cake\Http\Response;
use Cake\Http\ServerRequest;
use Muffin\Throttle\ThrottleTrait;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Zend\Diactoros\Stream;

class ThrottleMiddleware
{
Expand Down Expand Up @@ -35,23 +33,35 @@ public function __construct($config = [])
/**
* Called when the class is used as a function
*
* @param \Psr\Http\Message\ServerRequestInterface $request Request object
* @param \Psr\Http\Message\ResponseInterface $response Response object
* @param callable $next Next class in middelware
* @return \Psr\Http\Message\ResponseInterface
* @param \Cake\Http\ServerRequest $request Request object
* @param \Cake\Http\Response $response Response object
* @param callable $next Next class in middleware
* @return \Cake\Http\Response
*/
public function __invoke(ServerRequestInterface $request, ResponseInterface $response, callable $next)
public function __invoke(ServerRequest $request, Response $response, callable $next)
{
$this->_setIdentifier($request);
$this->_initCache();
$this->_count = $this->_touch();

if ($this->_count > $this->getConfig('limit')) {
$stream = new Stream('php://memory', 'wb+');
$stream->write((string)$this->getConfig('message'));
$config = $this->getConfig();

if ($this->_count > $config['limit']) {
if (is_array($config['response']['headers'])) {
foreach ($config['response']['headers'] as $name => $value) {
$response = $response->withHeader($name, $value);
}
}

if (isset($config['message'])) {
$message = $config['message'];
} else {
$message = $config['response']['body'];
}

return $response->withStatus(429)
->withBody($stream);
->withType($config['response']['type'])
->withStringBody($message);
}

$response = $next($request, $response);
Expand Down
42 changes: 28 additions & 14 deletions src/Routing/Filter/ThrottleFilter.php
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
<?php
namespace Muffin\Throttle\Routing\Filter;

use Cake\Cache\Cache;
use Cake\Event\Event;
use Cake\Network\Request;
use Cake\Network\Response;
use Cake\Http\Response;
use Cake\Routing\DispatcherFilter;
use InvalidArgumentException;
use Muffin\Throttle\ThrottleTrait;

class ThrottleFilter extends DispatcherFilter
Expand All @@ -22,33 +19,50 @@ class ThrottleFilter extends DispatcherFilter
public function __construct($config = [])
{
$config += $this->_setConfiguration();

parent::__construct($config);
}

/**
* beforeDispatch.
*
* @param \Cake\Event\Event $event Event instance
* @return mixed Cake\Network\Response when limit is reached, void otherwise
* @return mixed \Cake\Http\Response when limit is reached, void otherwise
*/
public function beforeDispatch(Event $event)
{
$this->_setIdentifier($event->data['request']);
$this->_setIdentifier($event->getData('request'));
$this->_initCache();
$this->_count = $this->_touch();

$config = $this->getConfig();

// client has not exceeded rate limit
if ($this->_count <= $this->config('limit')) {
$this->_setHeaders($event->data['response']);
if ($this->_count <= $config['limit']) {
$this->_setHeaders($event->getData('response'));

return;
}

if (isset($config['message'])) {
$message = $config['message'];
} else {
$message = $config['response']['body'];
}

// client has reached rate limit
$event->stopPropagation();
$response = new Response(['body' => $this->config('message')]);
$response->httpCodes([429 => 'Too Many Requests']);
$response->statusCode(429);
$response = new Response([
'body' => $message,
'status' => 429,
'type' => $config['response']['type']
]);

if (is_array($config['response']['headers'])) {
foreach ($config['response']['headers'] as $name => $value) {
$response = $response->withHeader($name, $value);
}
}

return $response;
}
Expand All @@ -57,12 +71,12 @@ public function beforeDispatch(Event $event)
* afterDispatch.
*
* @param \Cake\Event\Event $event Event instance
* @return \Cake\Network\Response Response instance
* @return \Cake\Http\Response Response instance
*/
public function afterDispatch(Event $event)
{
$this->_setHeaders($event->data['response']);
$this->_setHeaders($event->getData('response'));

return $event->data['response'];
return $event->getData('response');
}
}
Loading

0 comments on commit 9eef7ed

Please sign in to comment.