PYAE1994's picture
Upload folder using huggingface_hub
dd480ef verified
/**
* Rate Limit Middleware
* Uses KV store for sliding window rate limiting
* Lightweight - primarily I/O bound (KV read/write), minimal CPU
*/
import type { MiddlewareHandler } from 'hono';
import type { Env } from '../types/env';
const RATE_LIMIT_WINDOW_MS = 60_000; // 1 minute
const RATE_LIMIT_MAX_REQUESTS = 20; // 20 requests per minute per IP
export const rateLimitMiddleware: MiddlewareHandler<{ Bindings: Env }> = async (c, next) => {
const ip = c.req.header('CF-Connecting-IP') ?? 'unknown';
const key = `ratelimit:${ip}`;
const raw = await c.env.WFO_CACHE.get(key, 'text');
const now = Date.now();
let count = 1;
if (raw) {
const entry = JSON.parse(raw) as { count: number; windowStart: number };
if (now - entry.windowStart < RATE_LIMIT_WINDOW_MS) {
count = entry.count + 1;
if (count > RATE_LIMIT_MAX_REQUESTS) {
return c.json({
success: false,
error: 'Rate limit exceeded. Max 20 requests/minute.',
retryAfter: Math.ceil((entry.windowStart + RATE_LIMIT_WINDOW_MS - now) / 1000),
}, 429);
}
await c.env.WFO_CACHE.put(key, JSON.stringify({ count, windowStart: entry.windowStart }), {
expirationTtl: 120,
});
} else {
// New window
await c.env.WFO_CACHE.put(key, JSON.stringify({ count: 1, windowStart: now }), {
expirationTtl: 120,
});
}
} else {
await c.env.WFO_CACHE.put(key, JSON.stringify({ count: 1, windowStart: now }), {
expirationTtl: 120,
});
}
c.res.headers.set('X-RateLimit-Limit', String(RATE_LIMIT_MAX_REQUESTS));
c.res.headers.set('X-RateLimit-Remaining', String(Math.max(0, RATE_LIMIT_MAX_REQUESTS - count)));
await next();
};