install
npm install lru-cache
Code
import Koa, { Context, Next } from "koa";
import { LRUCache } from "lru-cache";
import { Debug } from "../util";
const THRESHOLD_MS_DANGEROUS_INTERVAL = 500; // 이 시간보다 짧게 연속 요청하면 위험한 것으로 간주
const THRESHOLD_CNT_CONTINUOUS_REQUEST = 50; // 이 횟수 이상으로 연속 요청하면 위험한 것으로 간주
const BLOCK_MS = 5_000; // 차단 유지 시간(ms)
interface ClientInfo {
lastRequestTime: number;
warnings: number;
blockStartTime?: number;
}
class RateLimiter {
private cache: LRUCache<string, ClientInfo>;
constructor() {
this.cache = new LRUCache<string, ClientInfo>({
max: 10, // 최대 10개의 클라이언트 IP를 저장
ttl: 1000 * 60 * 60 // 1시간 후에 정보가 만료됩니다 (필요에 따라 조정)
});
}
public handleRequest(ctx: Context, next: Next) {
const clientIp = ctx.ip;
const currentTime = Date.now();
let clientInfo = this.cache.get(clientIp);
if (clientInfo) {
const timeDiff = currentTime - clientInfo.lastRequestTime;
if (timeDiff < THRESHOLD_MS_DANGEROUS_INTERVAL) { // 500ms 이내의 요청은 경고 카운트 증가
clientInfo.warnings += 1;
}
// 경고 카운트 체크하여 차단
if (clientInfo.warnings >= THRESHOLD_CNT_CONTINUOUS_REQUEST) {
if (clientInfo.warnings === THRESHOLD_CNT_CONTINUOUS_REQUEST) {
Debug.TLogError(`BLOCKED: ${clientIp}`);
}
if (!clientInfo.blockStartTime) {
clientInfo.blockStartTime = currentTime;
}
if (currentTime - clientInfo.blockStartTime < BLOCK_MS) { // 지정 시간만큼 차단
ctx.status = 403;
return;
} else {
// 차단 시간이 지난 후 초기화
clientInfo.warnings = 0;
clientInfo.blockStartTime = undefined;
}
}
clientInfo.lastRequestTime = currentTime;
this.cache.set(clientIp, clientInfo); // 업데이트된 정보 저장
} else {
// 새 클라이언트 정보 추가
this.cache.set(clientIp, { lastRequestTime: currentTime, warnings: 0 });
Debug.TLog(`New Client: ${clientIp}`);
}
return next();
}
}
export function applyRateLimiter(app: Koa) {
const rateLimiter = new RateLimiter();
app.use((ctx, next) => rateLimiter.handleRequest(ctx, next));
}