├── src ├── ThrottlingException.php ├── TypeUtil.php └── ThrottlingMiddleware.php ├── composer.json └── LICENSE /src/ThrottlingException.php: -------------------------------------------------------------------------------- 1 | 22 | */ 23 | class ThrottlingException extends TooManyRequestsHttpException 24 | { 25 | // 26 | } 27 | -------------------------------------------------------------------------------- /src/TypeUtil.php: -------------------------------------------------------------------------------- 1 | 20 | */ 21 | class TypeUtil 22 | { 23 | /** 24 | * Convert the given value to numeric. 25 | * 26 | * @param int|float|string $v 27 | * 28 | * @return int|float 29 | */ 30 | public static function convertNumeric($v) 31 | { 32 | return $v === '' ? 0 : $v + 0; 33 | } 34 | 35 | /** 36 | * Convert the given value to boolean. 37 | * 38 | * @param bool|string $v 39 | * 40 | * @return bool 41 | */ 42 | public static function convertBoolean($v) 43 | { 44 | return $v === 'false' ? false : (bool) $v; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "alt-three/throttle", 3 | "description": "A request rate limiter for Laravel 5.2+", 4 | "keywords": ["throttle", "http", "throttle", "rate limit", "rate limiter", "Throttle", "Alt Three"], 5 | "license": "MIT", 6 | "authors": [ 7 | { 8 | "name": "Alt Three", 9 | "email": "support@alt-three.com" 10 | } 11 | ], 12 | "require": { 13 | "php": "^7.1.3", 14 | "illuminate/cache": "5.5.*|5.6.*|5.7.*", 15 | "illuminate/http": "5.5.*|5.6.*|5.7.*" 16 | }, 17 | "require-dev": { 18 | "graham-campbell/analyzer": "^2.1", 19 | "graham-campbell/testbench": "^5.1", 20 | "phpunit/phpunit": "^6.5|^7.0" 21 | }, 22 | "autoload": { 23 | "psr-4": { 24 | "AltThree\\Throttle\\": "src/" 25 | } 26 | }, 27 | "config": { 28 | "preferred-install": "dist" 29 | }, 30 | "extra": { 31 | "branch-alias": { 32 | "dev-master": "3.1-dev" 33 | } 34 | }, 35 | "minimum-stability": "dev", 36 | "prefer-stable": true 37 | } 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016-2018 Alt Three Services Limited 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/ThrottlingMiddleware.php: -------------------------------------------------------------------------------- 1 | 24 | */ 25 | class ThrottlingMiddleware 26 | { 27 | /** 28 | * The rate limiter instance. 29 | * 30 | * @var \Illuminate\Cache\RateLimiter 31 | */ 32 | protected $limiter; 33 | 34 | /** 35 | * The URIs that should be excluded. 36 | * 37 | * @var string[] 38 | */ 39 | protected $except = []; 40 | 41 | /** 42 | * Create a new throttling middleware instance. 43 | * 44 | * @param \Illuminate\Cache\RateLimiter $limiter 45 | * 46 | * @return void 47 | */ 48 | public function __construct(RateLimiter $limiter) 49 | { 50 | $this->limiter = $limiter; 51 | } 52 | 53 | /** 54 | * Handle an incoming request. 55 | * 56 | * @param \Illuminate\Http\Request $request 57 | * @param \Closure $next 58 | * @param int|string $limit 59 | * @param int|float|string $decay 60 | * @param bool|string $global 61 | * @param bool|string $headers 62 | * 63 | * @throws \AltThree\Throttle\ThrottlingException 64 | * 65 | * @return mixed 66 | */ 67 | public function handle(Request $request, Closure $next, $limit = 60, $decay = 1, $global = false, $headers = true) 68 | { 69 | return $this->safeHandle( 70 | $request, 71 | $next, 72 | TypeUtil::convertNumeric($limit), 73 | TypeUtil::convertNumeric($decay), 74 | TypeUtil::convertBoolean($global), 75 | TypeUtil::convertBoolean($headers) 76 | ); 77 | } 78 | 79 | /** 80 | * Handle an incoming request, with correct types. 81 | * 82 | * @param \Illuminate\Http\Request $request 83 | * @param \Closure $next 84 | * @param int $limit 85 | * @param int|float $decay 86 | * @param bool $global 87 | * @param bool $headers 88 | * 89 | * @throws \AltThree\Throttle\ThrottlingException 90 | * 91 | * @return mixed 92 | */ 93 | protected function safeHandle(Request $request, Closure $next, int $limit, $decay, bool $global, bool $headers) 94 | { 95 | if ($this->shouldPassThrough($request)) { 96 | return $next($request); 97 | } 98 | 99 | $key = $global ? sha1($request->ip()) : $request->fingerprint(); 100 | 101 | if ($this->limiter->tooManyAttempts($key, $limit, $decay)) { 102 | throw $this->buildException($key, $limit, $headers); 103 | } 104 | 105 | $this->limiter->hit($key, $decay); 106 | 107 | $response = $next($request); 108 | 109 | $response->headers->add($this->getHeaders($key, $limit, $headers)); 110 | 111 | return $response; 112 | } 113 | 114 | /** 115 | * Create a too many requests http exception. 116 | * 117 | * @param string $key 118 | * @param int $limit 119 | * @param bool $headers 120 | * 121 | * @return \AltThree\Throttle\ThrottlingException 122 | */ 123 | protected function buildException(string $key, int $limit, bool $headers) 124 | { 125 | $after = $this->limiter->availableIn($key); 126 | 127 | $exception = new ThrottlingException($after, 'Rate limit exceeded.'); 128 | 129 | $headers = $this->getHeaders($key, $limit, $headers, $after, $exception->getHeaders()); 130 | 131 | $exception->setHeaders($headers); 132 | 133 | return $exception; 134 | } 135 | 136 | /** 137 | * Get the limit header information. 138 | * 139 | * @param string $key 140 | * @param int $limit 141 | * @param bool $add 142 | * @param int|null $after 143 | * @param array $merge 144 | * 145 | * @return array 146 | */ 147 | protected function getHeaders(string $key, int $limit, bool $add = true, int $after = null, array $merge = []) 148 | { 149 | $remaining = $after === null ? $this->limiter->retriesLeft($key, $limit) : 0; 150 | 151 | $headers = $add ? ['X-RateLimit-Limit' => $limit, 'X-RateLimit-Remaining' => $remaining] : []; 152 | 153 | return array_merge($headers, $merge); 154 | } 155 | 156 | /** 157 | * Determine if the request has a URI that should pass through. 158 | * 159 | * @param \Illuminate\Http\Request $request 160 | * 161 | * @return bool 162 | */ 163 | protected function shouldPassThrough(Request $request) 164 | { 165 | foreach ($this->except as $except) { 166 | if ($except !== '/') { 167 | $except = trim($except, '/'); 168 | } 169 | 170 | if ($request->is($except)) { 171 | return true; 172 | } 173 | } 174 | 175 | return false; 176 | } 177 | } 178 | --------------------------------------------------------------------------------