aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorFlorian Dold <florian.dold@gmail.com>2019-12-09 13:29:42 +0100
committerFlorian Dold <florian.dold@gmail.com>2019-12-09 13:29:42 +0100
commit3f71aff19f3d7223167c36fe0cf661571501eed0 (patch)
tree14b25edddee09dc380dff4ebb7916b10bf65fa79 /src
parent1fea75bca3951d39c0a45faf3e903fcec77f9c4f (diff)
oops, missing file
Diffstat (limited to 'src')
-rw-r--r--src/util/RequestThrottler.ts116
1 files changed, 116 insertions, 0 deletions
diff --git a/src/util/RequestThrottler.ts b/src/util/RequestThrottler.ts
new file mode 100644
index 000000000..48a607296
--- /dev/null
+++ b/src/util/RequestThrottler.ts
@@ -0,0 +1,116 @@
+/*
+ This file is part of GNU Taler
+ (C) 2019 GNUnet e.V.
+
+ GNU Taler is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+
+/**
+ * Implementation of token bucket throttling.
+ */
+
+/**
+ * Imports.
+ */
+import { getTimestampNow, Timestamp } from "../walletTypes";
+
+/**
+ * Maximum request per second, per origin.
+ */
+const MAX_PER_SECOND = 20;
+
+/**
+ * Maximum request per minute, per origin.
+ */
+const MAX_PER_MINUTE = 100;
+
+/**
+ * Maximum request per hour, per origin.
+ */
+const MAX_PER_HOUR = 5000;
+
+
+/**
+ * Throttling state for one origin.
+ */
+class OriginState {
+ private tokensSecond: number = MAX_PER_SECOND;
+ private tokensMinute: number = MAX_PER_MINUTE;
+ private tokensHour: number = MAX_PER_HOUR;
+ private lastUpdate = getTimestampNow();
+
+ private refill(): void {
+ const now = getTimestampNow();
+ const d = now.t_ms - this.lastUpdate.t_ms;
+ this.tokensSecond = Math.max(MAX_PER_SECOND, this.tokensSecond + (d / 1000));
+ this.tokensMinute = Math.max(MAX_PER_MINUTE, this.tokensMinute + (d / 1000 * 60));
+ this.tokensHour = Math.max(MAX_PER_HOUR, this.tokensHour + (d / 1000 * 60 * 60));
+ this.lastUpdate = now;
+ }
+
+ /**
+ * Return true if the request for this origin should be throttled.
+ * Otherwise, take a token out of the respective buckets.
+ */
+ applyThrottle(): boolean {
+ this.refill();
+ if (this.tokensSecond < 1) {
+ console.log("request throttled (per second limit exceeded)");
+ return true;
+ }
+ if (this.tokensMinute < 1) {
+ console.log("request throttled (per minute limit exceeded)");
+ return true;
+ }
+ if (this.tokensHour < 1) {
+ console.log("request throttled (per hour limit exceeded)");
+ return true;
+ }
+ this.tokensSecond--;
+ this.tokensMinute--;
+ this.tokensHour--;
+ return false;
+ }
+}
+
+/**
+ * Request throttler, used as a "last layer of defense" when some
+ * other part of the re-try logic is broken and we're sending too
+ * many requests to the same exchange/bank/merchant.
+ */
+export class RequestThrottler {
+ private perOriginInfo: { [origin: string]: OriginState } = {};
+
+ /**
+ * Get the throttling state for an origin, or
+ * initialize if no state is associated with the
+ * origin yet.
+ */
+ private getState(origin: string): OriginState {
+ const s = this.perOriginInfo[origin];
+ if (s) {
+ return s;
+ }
+ const ns = this.perOriginInfo[origin] = new OriginState();
+ return ns;
+ }
+
+ /**
+ * Apply throttling to a request.
+ *
+ * @returns whether the request should be throttled.
+ */
+ applyThrottle(requestUrl: string): boolean {
+ const origin = new URL(requestUrl).origin;
+ return this.getState(origin).applyThrottle();
+ }
+}