-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathrate_limiter.ts
More file actions
79 lines (75 loc) · 2.53 KB
/
Copy pathrate_limiter.ts
File metadata and controls
79 lines (75 loc) · 2.53 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
import BaseQueryBuilder from "./builder/base_query_builder";
import { RedisClient } from "@biothings-explorer/utils";
import { Debug } from "@biothings-explorer/utils";
const debug = Debug("bte:call-apis:query");
// Default rate limit of 100 queries per second, which shouldn't ever be reached
// But just in case, this should keep multiple instances from overloading anyone
const DEFAULT_RATE_LIMIT = parseInt(
process.env.DEFAULT_API_RATE_LIMIT ?? "6000",
);
export default class RateCounter {
redisClient: RedisClient;
usage: {
[api: string]: number;
};
constructor(redisClient: RedisClient) {
this.redisClient = redisClient;
this.usage = {};
}
async atLimit(query: BaseQueryBuilder): Promise<boolean> {
const apiKey = `APIUsageCount:${query.APIEdge.association.api_name}`;
const limit =
query.APIEdge.association["x-trapi"]?.rate_limit ?? DEFAULT_RATE_LIMIT;
let usage: string;
if (this.redisClient.clientEnabled) {
try {
usage = await new Promise((resolve, reject) => {
setTimeout(() => reject(), 500);
this.redisClient.client.getTimeout(apiKey).then(
result => resolve(result),
error => reject(error),
);
});
} catch (error) {
debug("Redis failed to respond to APIUsageCount query in time.");
}
}
if (!usage) {
usage = `${this.usage[apiKey] ?? 0}`;
}
return parseInt(usage) >= limit;
}
async count(query: BaseQueryBuilder): Promise<void> {
const apiKey = `APIUsageCount:${query.APIEdge.association.api_name}`;
if (this.redisClient.clientEnabled) {
try {
await new Promise<void>((resolve, reject) => {
setTimeout(() => reject(), 500);
this.redisClient.client.incrTimeout(apiKey).then(
() =>
this.redisClient.client.expireTimeout(apiKey, 60).then(
() => {
setTimeout(() => {
this.redisClient.client.decrTimeout(apiKey).then(
() => null,
error => reject(error),
);
});
resolve();
},
error => reject(error),
),
error => reject(error),
);
});
} catch {
debug("Redis failed to respond to APIUsageCount query in time.");
}
}
if (typeof this.usage[apiKey] == "undefined") this.usage[apiKey] = 0;
this.usage[apiKey] += 1;
setTimeout(() => {
this.usage[apiKey] -= 1;
});
}
}