summaryrefslogtreecommitdiff
path: root/packages/taler-wallet-core/src/util/RequestThrottler.ts
diff options
context:
space:
mode:
authorFlorian Dold <florian.dold@gmail.com>2020-08-03 13:00:48 +0530
committerFlorian Dold <florian.dold@gmail.com>2020-08-03 13:01:05 +0530
commitffd2a62c3f7df94365980302fef3bc3376b48182 (patch)
tree270af6f16b4cc7f5da2afdba55c8bc9dbea5eca5 /packages/taler-wallet-core/src/util/RequestThrottler.ts
parentaa481e42675fb7c4dcbbeec0ba1c61e1953b9596 (diff)
downloadwallet-core-ffd2a62c3f7df94365980302fef3bc3376b48182.tar.gz
wallet-core-ffd2a62c3f7df94365980302fef3bc3376b48182.tar.bz2
wallet-core-ffd2a62c3f7df94365980302fef3bc3376b48182.zip
modularize repo, use pnpm, improve typechecking
Diffstat (limited to 'packages/taler-wallet-core/src/util/RequestThrottler.ts')
-rw-r--r--packages/taler-wallet-core/src/util/RequestThrottler.ts129
1 files changed, 129 insertions, 0 deletions
diff --git a/packages/taler-wallet-core/src/util/RequestThrottler.ts b/packages/taler-wallet-core/src/util/RequestThrottler.ts
new file mode 100644
index 000000000..6f51a72bc
--- /dev/null
+++ b/packages/taler-wallet-core/src/util/RequestThrottler.ts
@@ -0,0 +1,129 @@
+/*
+ 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, timestampDifference } from "../util/time";
+import { URL } from "./url";
+
+/**
+ * Maximum request per second, per origin.
+ */
+const MAX_PER_SECOND = 50;
+
+/**
+ * Maximum request per minute, per origin.
+ */
+const MAX_PER_MINUTE = 100;
+
+/**
+ * Maximum request per hour, per origin.
+ */
+const MAX_PER_HOUR = 1000;
+
+/**
+ * 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 = timestampDifference(now, this.lastUpdate);
+ if (d.d_ms === "forever") {
+ throw Error("assertion failed");
+ }
+ const d_s = d.d_ms / 1000;
+ this.tokensSecond = Math.min(
+ MAX_PER_SECOND,
+ this.tokensSecond + d_s / 1000,
+ );
+ this.tokensMinute = Math.min(
+ MAX_PER_MINUTE,
+ this.tokensMinute + (d_s / 1000) * 60,
+ );
+ this.tokensHour = Math.min(
+ MAX_PER_HOUR,
+ this.tokensHour + (d_s / 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();
+ }
+}