Мы научимся регулировать запросы для каждого устройства, используя параметра маршрута и уникальные идентификаторы.
В стандартной комплектации 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.
