Я пытаюсь реализовать функциональность ограничения частоты запросов (Rate Limit) в Loopback4, которая бы управлялась условием, заданным в метаданных метода контроллера.
Вот пример моего кода:
Функция проверки ограничения частоты запросов:
import { Context } from '@loopback/core';
import { MetadataInspector } from '@loopback/metadata';
import { HttpErrors, RestBindings } from '@loopback/rest';
import { RateLimitMetadata } from '../decorators/rate-limit.decorator';
const requestCounts = new Map<string, { count: number; timestamp: number }>();
export async function rateLimitMiddleware(ctx: Context) {
const requestContext: Context = await ctx.get(RestBindings.Http.CONTEXT);
const route = requestContext.getSync(RestBindings.Operation.ROUTE);
const controllerClass = route.spec['x-controller-class'];
const controllerMethodName = route.spec['x-operation-name'];
const methodMetadata = MetadataInspector.getMethodMetadata<RateLimitMetadata>(
'rateLimit',
controllerClass.prototype,
controllerMethodName,
);
if (methodMetadata != null) {
const { maxRequests, windowMinutes, condition } = methodMetadata;
if (condition != null && !condition(ctx)) {
return;
}
const windowMs = windowMinutes * 60 * 1000;
const clientIp = ctx.request.ip;
const endpoint = `${ctx.request.method} ${ctx.request.path}`;
const key = `${clientIp}:${endpoint}`;
const now = Date.now();
const requestInfo = requestCounts.get(key);
if (requestInfo == null) {
requestCounts.set(key, { count: 1, timestamp: now });
} else {
if (now - requestInfo.timestamp < windowMs) {
if (requestInfo.count >= maxRequests) {
throw new HttpErrors.TooManyRequests(`Rate limit exceeded. Try again in ${windowMinutes} minute(s).`);
} else {
requestInfo.count++;
}
} else {
requestCounts.set(key, { count: 1, timestamp: now });
}
}
}
}
Класс Sequence:
export class Sequence extends MiddlewareSequence {
async handle(context: RequestContext) {
await rateLimitMiddleware(context, async () => {
await super.handle(context);
});
}
}
Регистрация нового Sequence в приложении:
import { Sequence } from './sequence';
export class MyApplication extends BootMixin(
ServiceMixin(RepositoryMixin(RestApplication)),
) {
constructor(options: ApplicationConfig = {}) {
super(options);
this.sequence(Sequence);
// ...
}
}
Код декоратора
import { MetadataInspector } from '@loopback/metadata';
import { MethodDecoratorFactory } from '@loopback/core';
import { MiddlewareContext } from "@loopback/rest";
export interface RateLimitMetadata {
maxRequests: number;
windowMinutes: number;
condition?: (ctx: MiddlewareContext) => boolean;
}
export function RateLimit(maxRequests: number, windowMinutes: number, condition?: (ctx: MiddlewareContext) => boolean) {
return MethodDecoratorFactory.createDecorator<RateLimitMetadata>(
'rateLimit',
{
maxRequests,
windowMinutes,
condition
},
);
}
Однако я получаю следующую ошибку:
Request GET / failed with status code 500. ResolutionError: The key 'rest.operation.route' is not bound to any value in context RequestContext-KAUQzJReRX2ik839qZBuBg-3 (context: RequestContext-KAUQzJReRX2ik839qZBuBg-3, binding: rest.operation.route)
Самое интересное, что когда я логирую
context
в
Sequence
, то в консоль выводится объект контекста, а когда логирую переданный
context
в
rateLimitMiddleware
, то там ничего не выводится.
Как правильно реализовать ограничение частоты запросов, управляемое условием, в Loopback4? Буду признателен за любую помощь или советы по исправлению данной ошибки.