Grapeoff
@Grapeoff
В чём концепция...?

Как в Loopback4 сделать RateLimit, управляемый условием?

Я пытаюсь реализовать функциональность ограничения частоты запросов (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? Буду признателен за любую помощь или советы по исправлению данной ошибки.
  • Вопрос задан
  • 31 просмотр
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы