- examples/big-suite: E-Commerce 10 functions, 6 layers, real depends_on - provider: nodejs packages now wrapped in ESM zip (buildJSDeployZip) - provider: loadPackageLiteral reads raw file, nodejs zip via source_dir - all 10 functions verified working: python/node/ruby/go runtimes
63 lines
1.5 KiB
JavaScript
63 lines
1.5 KiB
JavaScript
// Rate-limiter: проверяет X-Client-ID, считает запросы в памяти пода
|
|
// В реальном проекте — Redis. Здесь: in-memory для демонстрации.
|
|
|
|
const counters = {};
|
|
const WINDOW_MS = 60_000;
|
|
const LIMIT = 100;
|
|
|
|
exports.handler = async (ctx) => {
|
|
const req = ctx.request;
|
|
const clientId = (req.headers && req.headers["x-client-id"]) || "anonymous";
|
|
const now = Date.now();
|
|
|
|
if (!counters[clientId]) {
|
|
counters[clientId] = { count: 0, windowStart: now };
|
|
}
|
|
|
|
const c = counters[clientId];
|
|
if (now - c.windowStart > WINDOW_MS) {
|
|
c.count = 0;
|
|
c.windowStart = now;
|
|
}
|
|
|
|
c.count++;
|
|
|
|
const allowed = c.count <= LIMIT;
|
|
const remaining = Math.max(0, LIMIT - c.count);
|
|
const resetAt = new Date(c.windowStart + WINDOW_MS).toISOString();
|
|
|
|
if (!allowed) {
|
|
return {
|
|
status: 429,
|
|
body: JSON.stringify({
|
|
error: "rate limit exceeded",
|
|
client_id: clientId,
|
|
limit: LIMIT,
|
|
reset_at: resetAt,
|
|
}),
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
"X-RateLimit-Limit": String(LIMIT),
|
|
"X-RateLimit-Remaining": "0",
|
|
"X-RateLimit-Reset": resetAt,
|
|
},
|
|
};
|
|
}
|
|
|
|
return {
|
|
body: JSON.stringify({
|
|
allowed: true,
|
|
client_id: clientId,
|
|
requests_in_window: c.count,
|
|
remaining,
|
|
limit: LIMIT,
|
|
reset_at: resetAt,
|
|
}),
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
"X-RateLimit-Limit": String(LIMIT),
|
|
"X-RateLimit-Remaining": String(remaining),
|
|
},
|
|
};
|
|
};
|