Shun Miyazawa 1 год назад
Родитель
Сommit
5a641437ed

+ 8 - 4
apps/app/src/features/rate-limiter/middleware/consume-points.ts

@@ -1,9 +1,11 @@
-import { type RateLimiterMongo, type RateLimiterRes } from 'rate-limiter-flexible';
+import { type RateLimiterRes } from 'rate-limiter-flexible';
 
 
 import { DEFAULT_MAX_REQUESTS, type IApiRateLimitConfig } from '../config';
 import { DEFAULT_MAX_REQUESTS, type IApiRateLimitConfig } from '../config';
 
 
+import { rateLimiterFactory } from './rate-limiter-factory';
+
 export const consumePoints = async(
 export const consumePoints = async(
-    rateLimiter: RateLimiterMongo, method: string, key: string | null, customizedConfig?: IApiRateLimitConfig, maxRequestsMultiplier?: number,
+    method: string, endpoint: string, key: string | null, customizedConfig?: IApiRateLimitConfig, maxRequestsMultiplier?: number,
 ): Promise<RateLimiterRes | undefined> => {
 ): Promise<RateLimiterRes | undefined> => {
   if (key == null) {
   if (key == null) {
     return;
     return;
@@ -21,7 +23,9 @@ export const consumePoints = async(
     maxRequests *= maxRequestsMultiplier;
     maxRequests *= maxRequestsMultiplier;
   }
   }
 
 
-  rateLimiter.points = maxRequests;
-  const rateLimiterRes = await rateLimiter.consume(key, 1);
+  const rateLimiter = rateLimiterFactory.getOrCreateRateLimiter(endpoint, maxRequests);
+
+  const pointsToConsume = 1;
+  const rateLimiterRes = await rateLimiter.consume(key, pointsToConsume);
   return rateLimiterRes;
   return rateLimiterRes;
 };
 };

+ 10 - 7
apps/app/src/features/rate-limiter/middleware/factory.ts

@@ -9,7 +9,6 @@ import { DEFAULT_USERS_PER_IP_PROSPECTION, type IApiRateLimitConfig } from '../c
 import { generateApiRateLimitConfig } from '../utils/config-generator';
 import { generateApiRateLimitConfig } from '../utils/config-generator';
 
 
 import { consumePoints } from './consume-points';
 import { consumePoints } from './consume-points';
-import { rateLimiter } from './rate-limiter-mongo-client';
 
 
 const logger = loggerFactory('growi:middleware:api-rate-limit');
 const logger = loggerFactory('growi:middleware:api-rate-limit');
 
 
@@ -34,8 +33,10 @@ const valuesWithRegExp = Object.values(configWithRegExp);
  * @param customizedConfig
  * @param customizedConfig
  * @returns
  * @returns
  */
  */
-const consumePointsByUser = async(method: string, key: string | null, customizedConfig?: IApiRateLimitConfig): Promise<RateLimiterRes | undefined> => {
-  return consumePoints(rateLimiter, method, key, customizedConfig);
+const consumePointsByUser = async(
+    method: string, endpoint: string, key: string | null, customizedConfig?: IApiRateLimitConfig,
+): Promise<RateLimiterRes | undefined> => {
+  return consumePoints(method, endpoint, key, customizedConfig);
 };
 };
 
 
 /**
 /**
@@ -45,9 +46,11 @@ const consumePointsByUser = async(method: string, key: string | null, customized
  * @param customizedConfig
  * @param customizedConfig
  * @returns
  * @returns
  */
  */
-const consumePointsByIp = async(method: string, key: string | null, customizedConfig?: IApiRateLimitConfig): Promise<RateLimiterRes | undefined> => {
+const consumePointsByIp = async(
+    method: string, endpoint: string, key: string | null, customizedConfig?: IApiRateLimitConfig,
+): Promise<RateLimiterRes | undefined> => {
   const maxRequestsMultiplier = customizedConfig?.usersPerIpProspection ?? DEFAULT_USERS_PER_IP_PROSPECTION;
   const maxRequestsMultiplier = customizedConfig?.usersPerIpProspection ?? DEFAULT_USERS_PER_IP_PROSPECTION;
-  return consumePoints(rateLimiter, method, key, customizedConfig, maxRequestsMultiplier);
+  return consumePoints(method, endpoint, key, customizedConfig, maxRequestsMultiplier);
 };
 };
 
 
 
 
@@ -80,7 +83,7 @@ export const middlewareFactory = (): Handler => {
     // check for the current user
     // check for the current user
     if (req.user != null) {
     if (req.user != null) {
       try {
       try {
-        await consumePointsByUser(req.method, keyForUser, customizedConfig);
+        await consumePointsByUser(req.method, endpoint, keyForUser, customizedConfig);
       }
       }
       catch {
       catch {
         logger.error(`${req.user._id}: too many request at ${endpoint}`);
         logger.error(`${req.user._id}: too many request at ${endpoint}`);
@@ -90,7 +93,7 @@ export const middlewareFactory = (): Handler => {
 
 
     // check for ip
     // check for ip
     try {
     try {
-      await consumePointsByIp(req.method, keyForIp, customizedConfig);
+      await consumePointsByIp(req.method, endpoint, keyForIp, customizedConfig);
     }
     }
     catch {
     catch {
       logger.error(`${req.ip}: too many request at ${endpoint}`);
       logger.error(`${req.ip}: too many request at ${endpoint}`);

+ 38 - 0
apps/app/src/features/rate-limiter/middleware/rate-limiter-factory.ts

@@ -0,0 +1,38 @@
+import { connection } from 'mongoose';
+import { type IRateLimiterMongoOptions, RateLimiterMongo } from 'rate-limiter-flexible';
+
+import { DEFAULT_DURATION_SEC } from '../config';
+
+class RateLimiterFactory {
+
+  private rateLimiters: Map<string, RateLimiterMongo> = new Map();
+
+  private generateKey(endpoint: string): string {
+    return `rate_limiter_${endpoint}`;
+  }
+
+  getOrCreateRateLimiter(endpoint: string, maxRequests: number): RateLimiterMongo {
+    const key = this.generateKey(endpoint);
+
+    if (this.rateLimiters.has(key)) {
+      const instance = this.rateLimiters.get(key);
+      if (instance != null) {
+        return instance;
+      }
+    }
+
+    const opts: IRateLimiterMongoOptions = {
+      storeClient: connection,
+      duration: DEFAULT_DURATION_SEC,
+      points: maxRequests,
+    };
+
+    const rateLimiter = new RateLimiterMongo(opts);
+    this.rateLimiters.set(key, rateLimiter);
+
+    return rateLimiter;
+  }
+
+}
+
+export const rateLimiterFactory = new RateLimiterFactory();

+ 0 - 11
apps/app/src/features/rate-limiter/middleware/rate-limiter-mongo-client.ts

@@ -1,11 +0,0 @@
-import { connection } from 'mongoose';
-import { type IRateLimiterMongoOptions, RateLimiterMongo } from 'rate-limiter-flexible';
-
-import { DEFAULT_DURATION_SEC } from '../config';
-
-const opts: IRateLimiterMongoOptions = {
-  storeClient: connection,
-  duration: DEFAULT_DURATION_SEC, // set default value
-};
-
-export const rateLimiter = new RateLimiterMongo(opts);