mirror of
https://github.com/discordeno/discordeno.git
synced 2026-06-04 01:40:08 +00:00
more work on rate limiting
This commit is contained in:
@@ -9,7 +9,7 @@ import {
|
||||
Typing_Start_Payload,
|
||||
Voice_State_Update_Payload
|
||||
} from "../types/discord.ts"
|
||||
import { spawnShards } from "./sharding-manager.ts"
|
||||
import { spawnShards } from "./sharding_manager.ts"
|
||||
import {
|
||||
connectWebSocket,
|
||||
isWebSocketCloseEvent,
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
import { sleep } from "../utils/utils.ts";
|
||||
|
||||
export interface Ratelimit {
|
||||
retryAfter: number;
|
||||
limit: number;
|
||||
remaining: number;
|
||||
reset: number;
|
||||
}
|
||||
|
||||
export class Ratelimiter {
|
||||
buckets: Record<string, Ratelimit> = {};
|
||||
|
||||
awaitRatelimit (ratelimit: Ratelimit): Promise<unknown> {
|
||||
if (ratelimit.remaining === 0) {
|
||||
return sleep(ratelimit.retryAfter);
|
||||
}
|
||||
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
addBucket (bucket: string, ratelimit: Ratelimit) {
|
||||
if (this.buckets[bucket]) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise, add this ratelimit to the registry.
|
||||
this.buckets[bucket] = ratelimit;
|
||||
}
|
||||
|
||||
async awaitBucket (bucket: string) {
|
||||
if (this.buckets[bucket]) {
|
||||
// POSSIBLE MEMORY LEAK: Some buckets might never get cleaned up.
|
||||
await this.awaitRatelimit(this.buckets[bucket]);
|
||||
|
||||
// IIRC, we avoid `delete` so v8 doesn't deoptimize this?
|
||||
this.buckets[bucket] = undefined as any;
|
||||
}
|
||||
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,24 @@
|
||||
import { RequestMethod } from "../types/fetch.ts"
|
||||
import { authorization } from "./client.ts"
|
||||
import { sleep } from "../utils/utils.ts"
|
||||
|
||||
// const queue = new Map<string, Queued_Request>()
|
||||
// const ratelimited_paths = new Map<string, Rate_Limited_Path>()
|
||||
const ratelimited_paths = new Map<string, Rate_Limited_Path>()
|
||||
|
||||
export interface Rate_Limited_Path {
|
||||
url: string
|
||||
reset_timestamp: number
|
||||
}
|
||||
|
||||
export const Request_Manager = {
|
||||
// Something off about using run_method with get breaks when using fetch
|
||||
get: (url: string, body?: unknown) => {
|
||||
// TODO: Check rate limit
|
||||
|
||||
get: async (url: string, body?: unknown) => {
|
||||
await check_ratelimits(url)
|
||||
const result = await fetch(url, create_request_body(body))
|
||||
|
||||
// TODO: Handle rate limiting
|
||||
console.log('GET headers', result.headers)
|
||||
console.log("GET headers", result.headers)
|
||||
process_headers(url, result.headers)
|
||||
|
||||
return result.json()
|
||||
},
|
||||
@@ -42,9 +48,8 @@ const create_request_body = (body: unknown, method?: RequestMethod) => {
|
||||
}
|
||||
}
|
||||
|
||||
const run_method = (method: RequestMethod, url: string, body?: unknown) => {
|
||||
// TODO: Check if this url is rate limited
|
||||
|
||||
const run_method = async (method: RequestMethod, url: string, body?: unknown) => {
|
||||
await check_ratelimits(url)
|
||||
const response = await fetch(url, create_request_body(body, method))
|
||||
|
||||
// TODO: Handle ratelimiting
|
||||
@@ -52,3 +57,46 @@ const run_method = (method: RequestMethod, url: string, body?: unknown) => {
|
||||
|
||||
return response.json()
|
||||
}
|
||||
|
||||
const check_ratelimits = async (url: string) => {
|
||||
const ratelimited = ratelimited_paths.get(url)
|
||||
const global = ratelimited_paths.get("global")
|
||||
|
||||
const now = Date.now()
|
||||
if (ratelimited && now < ratelimited.reset_timestamp) await sleep(now - ratelimited.reset_timestamp)
|
||||
if (global && now < global.reset_timestamp) await sleep(now - global.reset_timestamp)
|
||||
}
|
||||
|
||||
const process_headers = (url: string, headers: Headers) => {
|
||||
// If a rate limit response is encountered this will become true and returned
|
||||
let ratelimited = false
|
||||
|
||||
// Get all useful headers
|
||||
const remaining = headers.get("x-ratelimit-remaining")
|
||||
const reset_timestamp = headers.get("x-ratelimit-reset")
|
||||
const retry_after = headers.get('retry-after')
|
||||
const global = headers.get('x-ratelimit-global')
|
||||
|
||||
// If there is no remaining rate limit for this endpoint, we save it in cache
|
||||
if (remaining && remaining === "0") {
|
||||
ratelimited = true
|
||||
|
||||
ratelimited_paths.set(url, {
|
||||
url,
|
||||
reset_timestamp: Number(reset_timestamp)
|
||||
})
|
||||
}
|
||||
|
||||
// If there is no remaining global limit, we save it in cache
|
||||
if (global) {
|
||||
ratelimited = true
|
||||
|
||||
ratelimited_paths.set('global', {
|
||||
url: 'global',
|
||||
reset_timestamp: Date.now() + Number(retry_after)
|
||||
})
|
||||
}
|
||||
|
||||
// Returns a boolean to check if we need to request again once the rate limit resets
|
||||
return ratelimited
|
||||
}
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
import Request_Manager from "./request_manager.ts"
|
||||
import Client from "./client.ts"
|
||||
import { resolveURLs } from "./url.ts"
|
||||
import { baseEndpoints } from "../constants/discord.ts"
|
||||
import { Ratelimit, Ratelimiter } from "./ratelimiter.ts"
|
||||
import { RequestMethod } from "../types/fetch.ts"
|
||||
|
||||
export class RouteAwareRequest_Manager extends Request_Manager {
|
||||
protected currentRatelimit?: Ratelimit
|
||||
ratelimiter = new Ratelimiter()
|
||||
|
||||
constructor(public client: Client, public routeName: string) {
|
||||
super(client)
|
||||
}
|
||||
|
||||
protected resolveURL(url: string) {
|
||||
return resolveURLs(baseEndpoints.BASE_URL, this.routeName, url)
|
||||
}
|
||||
|
||||
async runMethod(method: RequestMethod, url: string, body?: unknown) {
|
||||
if (this.currentRatelimit) {
|
||||
await this.ratelimiter.awaitRatelimit(this.currentRatelimit)
|
||||
}
|
||||
|
||||
const response = await this.baseCreateRequestForMethod(method, url, body)
|
||||
|
||||
// Capture the ratelimit from this request in our cute little store.
|
||||
|
||||
return response.json()
|
||||
}
|
||||
|
||||
protected createRatelimitFromRequest(_request: Request) {}
|
||||
}
|
||||
|
||||
export class RoutedRequest_Manager {
|
||||
protected routeMap = new Map<string, Request_Manager>()
|
||||
|
||||
constructor(public client: Client) {}
|
||||
|
||||
forRoute(routeName: string) {
|
||||
if (this.routeMap.has(routeName)) {
|
||||
return this.routeMap.get(routeName)
|
||||
}
|
||||
|
||||
const routeRequestManager = new RouteAwareRequest_Manager(this.client, routeName)
|
||||
this.routeMap.set(routeName, routeRequestManager)
|
||||
return routeRequestManager
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user