Мы научимся регулировать запросы для каждого устройства, используя параметра маршрута и уникальные идентификаторы.
В стандартной комплектации Laravel поставляется с мидлваром ThrottleRequests
, который легко можно настроить на ограничение запросов с одного IP-адреса.
Например, приведенный ниже фрагмент в файле маршрутов ограничивает количество запросов, которые могут сделать пользователь или IP-адрес, до 60 в минуту:
Route::middleware('throttle:60,1')->group(function () { Route::get('/products', function () { // }); Route::get('/locations', function () { // }); });
Начиная с Laravel 5.6, мы даже можем динамически ограничивать скорость, на основе атрибута модели User
, указанный в таблице users
. В приведенном ниже примере показан столбец rate_limit
определяющий количество запросов, которые пользователь может сделать в час:
Route::middleware('throttle:rate_limit,60')->group(function () { Route::get('/products', function () { // }); Route::get('/locations', function () { // }); });
Вы можете установить это значение на 3600 по умолчанию, а затем увеличить или уменьшить его для определенных пользователей в соответствии с их потребностями.
Однако, что если вы хотите ограничить конкретный маршрут (или маршруты), но не использовать ни один из этих подходов?
В одном из последних наших проектов пользователи могли добавлять свои IoT устройства (Интернет вещей) и получать уникальный URL-адрес, который использовался как вебхук для отправки данных в нашу систему. Используя уникальный ключ из URL, мне нужно было регулировать создаваемые ими запросы.
На самом деле это довольно легко сделать, если знаешь как — просто нужно понять, как работает имеющийся мидлвар ThrottleRequests
.
Если вы его откроете и посмотрите метод handle()
, то увидите:
public function handle($request, Closure $next, $maxAttempts = 60, $decayMinutes = 1) { $key = $this->resolveRequestSignature($request); $maxAttempts = $this->resolveMaxAttempts($request, $maxAttempts); if ($this->limiter->tooManyAttempts($key, $maxAttempts)) { throw $this->buildException($key, $maxAttempts); } $this->limiter->hit($key, $decayMinutes * 60); $response = $next($request); return $this->addHeaders( $response, $maxAttempts, $this->calculateRemainingAttempts($key, $maxAttempts) ); }
Первая строка метода — это то, что нас интересует:
$key = $this->resolveRequestSignature($request);
Именно здесь мидлвар определяет ключ, который он будет использовать для отслеживания ваших запросов.
Метод в ThrottleRequests
выглядит следующим образом:
protected function resolveRequestSignature($request) { if ($user = $request->user()) { return sha1($user->getAuthIdentifier()); } if ($route = $request->route()) { return sha1($route->getDomain().'|'.$request->ip()); } throw new RuntimeException('Unable to generate the request signature. Route unavailable.'); }
Если вы аутентифицированны, то он будет использовать имя ключа (обычно id
, если вы не меняли это) вашей модели User
. В противном случае, в качестве ключа будет использоваться домен и IP-адрес запроса.
Зная это, нам просто нужно переопределить метод resolveRequestSignature()
для того, чтобы получить наш собственный ключ для запроса:
1. Добавьте новый файл в app/Http/Middleware
под названием CustomThrottleMiddleware
(или как вы хотите называть свой мидлвар) или создайте через php artisan make:middleware CustomThrottleMiddleware
2. Замените содержимое на:
namespace App\Http\Middleware; use Illuminate\Routing\Middleware\ThrottleRequests; class CustomThrottleMiddleware extends ThrottleRequests { protected function resolveRequestSignature($request) { // } }
Мы создали новый мидлвар, которое расширяет ThrottleRequests
. Он настроен на переопределение метода resolveRequestSignature()
, чтобы мы могли установить наш собственный ключ.
Теперь просто нужно добавить свой код в resolveRequestSignature()
, чтобы фактически вернуть ключ. Привожу несколько примеров:
protected function resolveRequestSignature($request) { // Троттлинг по определенному заголовку return $request->header('API-Key'); }
protected function resolveRequestSignature($request) { // Троттлинг по параметру запроса return $request->input('account_id'); }
protected function resolveRequestSignature($request) { // Троттлинг по идентификатору сессии return $request->session(); }
protected function resolveRequestSignature($request) { // Троттлинг по IP к конкретной модели в маршруте // где 'product' является нашей моделью // и этот маршрут настроен для использования 'product' с привязкой к маршруту return $request->route('product') . '|' . $request->ip(); }
В моем случае я выбрал троттлинг по ряду параметров. Уникальный URL-адрес, выдаваемый пользователю, был уникальным только для него, но одинаков для всех IoT устройств такого типа. Например, IoT устройства, которые мы поддерживали, имели типы A, B и C; если у вас было два типа A, то они использовали один и тот же уникальный URL. Наши причины этого не важны для статьи, поэтому я их опущу.
Каждому устройству было разрешено делать один запрос каждые 5 минут, однако, поскольку два устройства типа A были технически двумя отдельными устройствами, то нам нужно было сделать дополнительный способ их разделения.
Чтобы достичь этого, мы просто выясняли, с каким типом IoT-устройства мы работали, а затем разрешали токен соответствующим образом:
protected function resolveRequestSignature($request) { $token = $request->route()->parameter('deviceByToken'); $device = IotDevice::findByToken($token)->firstOrFail(); if ($device->type === 'A') { return $token . '-' . $request->input('unique_parameter_for_type_a_per_device'); } else if ($device->type === 'B') { return $token . '-' . $request->input('unique_parameter_for_type_b_per_device'); } if ($device->type === 'C') { return $token . '-' . $request->input('unique_parameter_for_type_c_per_device'); } return $token; }
4. И последнее, но не менее важное: вам просто нужно зарегистрировать мидлвар для использования, обновив файл app/Http/Kernel.php
следующим образом:
protected $routeMiddleware = [ //... 'customthrottle' => \App\Http\Middleware\CustomThrottleMiddleware::class, ];
Это позволит вам использовать ваш новый мидлвар следующим образом (измените 1 и 5 как вам нужно):
Route::middleware('customthrottle:1,5')->group(function () { Route::get('/products', function () { // }); Route::get('/locations', function () { // }); });
Используя описанный выше подход, мы смогли регулировать запросы, используя уникальный параметр маршрута, присутствующий в каждом из запросов, вместе с уникальным идентификатором, который каждое устройство включало в свою полезную нагрузку для запроса.
Автор: James Bannister
Перевод: Алексей Широков
Наш Телеграм-канал — следите за новостями о Laravel.