mirror of
https://github.com/Unleash/unleash.git
synced 2025-01-31 00:16:47 +01:00
b06613d1b0
### What The heaviest requests we serve are the register and metrics POSTs from our SDKs/clients. This PR adds ratelimiting to /api/client/register, /api/client/metrics, /api/frontend/register and /api/frontend/metrics with a default set to 6000 requests per minute (or 100 rps) for each of the endpoints. It will be overrideable by the environment variables documented. ### Points of discussion @kwasniew already suggested using featuretoggles with variants to control the rate per clientId. I struggled to see if we could dynamically update the middleware after initialisation, so this attempt will need a restart of the pod to update the request limit.
70 lines
2.2 KiB
TypeScript
70 lines
2.2 KiB
TypeScript
import { withDbLock } from './db-lock';
|
|
import { getDbConfig } from '../../test/e2e/helpers/database-config';
|
|
import { IDBOption } from '../types';
|
|
import { Logger } from '../logger';
|
|
|
|
test('should lock access to any action', async () => {
|
|
const lock = withDbLock(getDbConfig() as IDBOption);
|
|
|
|
const asyncAction = (input: string) => Promise.resolve(`result: ${input}`);
|
|
|
|
const result = await lock(asyncAction)('data');
|
|
|
|
expect(result).toBe('result: data');
|
|
});
|
|
|
|
const ms = (millis: number) =>
|
|
new Promise((resolve) => {
|
|
setTimeout(() => resolve('time'), millis);
|
|
});
|
|
|
|
test('should await other actions on lock', async () => {
|
|
const lock = withDbLock(getDbConfig() as IDBOption);
|
|
|
|
const results: string[] = [];
|
|
const slowAsyncAction = (input: string) => {
|
|
return new Promise((resolve) => {
|
|
setTimeout(() => {
|
|
results.push(input);
|
|
resolve(input);
|
|
}, 200);
|
|
});
|
|
};
|
|
const fastAction = async (input: string) => {
|
|
results.push(input);
|
|
};
|
|
|
|
const lockedAction = lock(slowAsyncAction);
|
|
const lockedAnotherAction = lock(fastAction);
|
|
|
|
// deliberately skipped await to simulate another server running slow operation
|
|
lockedAction('first');
|
|
await ms(100); // start fast action after slow action established DB connection
|
|
await lockedAnotherAction('second');
|
|
|
|
expect(results).toStrictEqual(['first', 'second']);
|
|
});
|
|
|
|
test('should handle lock timeout', async () => {
|
|
const timeoutMs = 1;
|
|
let loggedError = '';
|
|
const lock = withDbLock(getDbConfig() as IDBOption, {
|
|
lockKey: 1,
|
|
timeout: timeoutMs,
|
|
logger: {
|
|
error(msg: string) {
|
|
loggedError = msg;
|
|
},
|
|
} as unknown as Logger,
|
|
});
|
|
|
|
// the query should fail because of the timeout. This one is a fallback when timeout
|
|
// was not triggered in the integration test
|
|
const asyncAction = () => Promise.reject(new Error('Query read timeout'));
|
|
|
|
await expect(lock(asyncAction)()).rejects.toStrictEqual(
|
|
new Error('Query read timeout'),
|
|
);
|
|
expect(loggedError).toBe('Locking error: Query read timeout');
|
|
});
|