mirror of
https://github.com/WorldEditAxe/eaglerproxy.git
synced 2024-12-03 22:14:12 -08:00
fix some bugs
This commit is contained in:
parent
4b465802d1
commit
26275fa0d7
|
@ -15,8 +15,8 @@ export const config: Config = {
|
||||||
cache: {
|
cache: {
|
||||||
useCache: true,
|
useCache: true,
|
||||||
folderName: "skinCache",
|
folderName: "skinCache",
|
||||||
skinCacheLifetime: 60 * 1000,
|
skinCacheLifetime: 60 * 60 * 1000,
|
||||||
skinCachePruneInterval: 5000,
|
skinCachePruneInterval: 10 * 60 * 1000,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
motd: true
|
motd: true
|
||||||
|
|
|
@ -20,7 +20,8 @@ export default class DiskDB<T extends any> {
|
||||||
|
|
||||||
public async filter(f: (v: T) => boolean) {
|
public async filter(f: (v: T) => boolean) {
|
||||||
for (const file of await fs.readdir(this.folder)) {
|
for (const file of await fs.readdir(this.folder)) {
|
||||||
if (!f(this.decoder(await fs.readFile(file)))) await fs.rm(file);
|
const fp = path.join(this.folder, file);
|
||||||
|
if (!f(this.decoder(await fs.readFile(fp)))) await fs.rm(fp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
63
src/proxy/ratelimit/ExponentialBackoffRequestController.ts
Normal file
63
src/proxy/ratelimit/ExponentialBackoffRequestController.ts
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
const wait = (ms: number) => new Promise((res) => setTimeout(res, ms));
|
||||||
|
|
||||||
|
export default class ExponentialBackoffRequestController {
|
||||||
|
public queue: Task[];
|
||||||
|
public flushQueueAfterTries: number;
|
||||||
|
public baseDelay: number;
|
||||||
|
ended: boolean;
|
||||||
|
aborted: boolean;
|
||||||
|
|
||||||
|
constructor(baseDelay: number = 3000, triesBeforeFlush: number = 10) {
|
||||||
|
this.flushQueueAfterTries = triesBeforeFlush;
|
||||||
|
this.baseDelay = baseDelay;
|
||||||
|
this.queue = [];
|
||||||
|
this.ended = false;
|
||||||
|
this.aborted = false;
|
||||||
|
setTimeout(() => this.tick(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async tick() {
|
||||||
|
while (true) {
|
||||||
|
if (this.ended) break;
|
||||||
|
for (const task of this.queue) {
|
||||||
|
if (this.ended || this.aborted) break;
|
||||||
|
let times = 0,
|
||||||
|
breakOut = false;
|
||||||
|
while (true) {
|
||||||
|
try {
|
||||||
|
await task();
|
||||||
|
break;
|
||||||
|
} catch (err) {
|
||||||
|
times++;
|
||||||
|
await wait(this.baseDelay * 2 ** times);
|
||||||
|
if (times > this.flushQueueAfterTries) {
|
||||||
|
this.queue.forEach((task) => task(new Error("Controller overload!")));
|
||||||
|
breakOut = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (breakOut) break;
|
||||||
|
}
|
||||||
|
if (this.aborted) this.aborted = false;
|
||||||
|
this.queue = [];
|
||||||
|
await wait(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public end() {
|
||||||
|
this.ended = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public flush() {
|
||||||
|
this.aborted = false;
|
||||||
|
this.queue.forEach((task) => task(new Error("Aborted")));
|
||||||
|
this.queue = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public queueTask(task: Task): void {
|
||||||
|
this.queue.push(task);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Task = (err?: object) => void | Promise<void>;
|
|
@ -11,6 +11,7 @@ import { Logger } from "../../logger.js";
|
||||||
import fetch from "node-fetch";
|
import fetch from "node-fetch";
|
||||||
import Jimp from "jimp";
|
import Jimp from "jimp";
|
||||||
import { ImageEditor } from "./ImageEditor.js";
|
import { ImageEditor } from "./ImageEditor.js";
|
||||||
|
import ExponentialBackoffRequestController from "../ratelimit/ExponentialBackoffRequestController.js";
|
||||||
|
|
||||||
// TODO: convert all functions to use MineProtocol's UUID manipulation functions
|
// TODO: convert all functions to use MineProtocol's UUID manipulation functions
|
||||||
|
|
||||||
|
@ -81,7 +82,10 @@ export namespace EaglerSkins {
|
||||||
return new Promise<Buffer>(async (res, rej) => {
|
return new Promise<Buffer>(async (res, rej) => {
|
||||||
const skin = await fetch(skinUrl);
|
const skin = await fetch(skinUrl);
|
||||||
if (skin.status != 200) {
|
if (skin.status != 200) {
|
||||||
rej(`Tried to fetch ${skinUrl}, got HTTP ${skin.status} instead!`);
|
rej({
|
||||||
|
url: skinUrl,
|
||||||
|
status: skin.status,
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
res(Buffer.from(await skin.arrayBuffer()));
|
res(Buffer.from(await skin.arrayBuffer()));
|
||||||
|
@ -89,6 +93,20 @@ export namespace EaglerSkins {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function safeDownloadSkin(skinUrl: string, backoff: ExponentialBackoffRequestController): Promise<Buffer> {
|
||||||
|
return new Promise((res, rej) => {
|
||||||
|
backoff.queueTask(async (err) => {
|
||||||
|
if (err) return rej(err);
|
||||||
|
try {
|
||||||
|
res(await downloadSkin(skinUrl));
|
||||||
|
} catch (err) {
|
||||||
|
if (err.status == 429) throw new Error("Ratelimited!");
|
||||||
|
else rej("Unexpected HTTP status code: " + err.status);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export function readClientDownloadSkinRequestPacket(message: Buffer): ClientDownloadSkinRequest {
|
export function readClientDownloadSkinRequestPacket(message: Buffer): ClientDownloadSkinRequest {
|
||||||
const ret: ClientDownloadSkinRequest = {
|
const ret: ClientDownloadSkinRequest = {
|
||||||
id: null,
|
id: null,
|
||||||
|
|
|
@ -11,11 +11,13 @@ import { SCChannelMessagePacket } from "../packets/channel/SCChannelMessage.js";
|
||||||
import { EaglerSkins } from "./EaglerSkins.js";
|
import { EaglerSkins } from "./EaglerSkins.js";
|
||||||
import { ImageEditor } from "./ImageEditor.js";
|
import { ImageEditor } from "./ImageEditor.js";
|
||||||
import { MineProtocol } from "../Protocol.js";
|
import { MineProtocol } from "../Protocol.js";
|
||||||
|
import ExponentialBackoffRequestController from "../ratelimit/ExponentialBackoffRequestController.js";
|
||||||
|
|
||||||
export class SkinServer {
|
export class SkinServer {
|
||||||
public allowedSkinDomains: string[];
|
public allowedSkinDomains: string[];
|
||||||
public cache: DiskDB<CachedSkin>;
|
public cache: DiskDB<CachedSkin>;
|
||||||
public proxy: Proxy;
|
public proxy: Proxy;
|
||||||
|
public backoffController: ExponentialBackoffRequestController;
|
||||||
public usingNative: boolean;
|
public usingNative: boolean;
|
||||||
public usingCache: boolean;
|
public usingCache: boolean;
|
||||||
private _logger: Logger;
|
private _logger: Logger;
|
||||||
|
@ -29,13 +31,14 @@ export class SkinServer {
|
||||||
cacheFolder,
|
cacheFolder,
|
||||||
(v) => exportCachedSkin(v),
|
(v) => exportCachedSkin(v),
|
||||||
(b) => readCachedSkin(b),
|
(b) => readCachedSkin(b),
|
||||||
(k) => k
|
(k) => k.replaceAll("-", "")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
this.proxy = proxy ?? PROXY;
|
this.proxy = proxy ?? PROXY;
|
||||||
this.usingCache = useCache;
|
this.usingCache = useCache;
|
||||||
this.usingNative = native;
|
this.usingNative = native;
|
||||||
this.lifetime = cacheLifetime;
|
this.lifetime = cacheLifetime;
|
||||||
|
this.backoffController = new ExponentialBackoffRequestController();
|
||||||
this._logger = new Logger("SkinServer");
|
this._logger = new Logger("SkinServer");
|
||||||
this._logger.info("Started EaglercraftX skin server.");
|
this._logger.info("Started EaglercraftX skin server.");
|
||||||
if (useCache) this.deleteTask = setInterval(async () => await this.cache.filter((ent) => Date.now() < ent.expires), sweepInterval);
|
if (useCache) this.deleteTask = setInterval(async () => await this.cache.filter((ent) => Date.now() < ent.expires), sweepInterval);
|
||||||
|
@ -88,17 +91,16 @@ export class SkinServer {
|
||||||
skin = null;
|
skin = null;
|
||||||
if (this.usingCache) {
|
if (this.usingCache) {
|
||||||
(cacheHit = await this.cache.get(parsedPacket_1.uuid)), (skin = cacheHit != null ? cacheHit.data : null);
|
(cacheHit = await this.cache.get(parsedPacket_1.uuid)), (skin = cacheHit != null ? cacheHit.data : null);
|
||||||
|
|
||||||
if (!skin) {
|
if (!skin) {
|
||||||
this._logger.info("cache miss: getting skin");
|
const fetched = await EaglerSkins.safeDownloadSkin(parsedPacket_1.url, this.backoffController);
|
||||||
const fetched = await EaglerSkins.downloadSkin(parsedPacket_1.url);
|
|
||||||
skin = fetched;
|
skin = fetched;
|
||||||
await this.cache.set(parsedPacket_1.uuid, {
|
await this.cache.set(parsedPacket_1.uuid, {
|
||||||
uuid: parsedPacket_1.uuid,
|
uuid: parsedPacket_1.uuid,
|
||||||
expires: Date.now() + this.lifetime,
|
expires: Date.now() + this.lifetime,
|
||||||
data: fetched,
|
data: fetched,
|
||||||
});
|
});
|
||||||
this._logger.info("downloaded skin, saved to cache");
|
}
|
||||||
} else this._logger.info("hit success: got skin");
|
|
||||||
} else {
|
} else {
|
||||||
skin = await EaglerSkins.downloadSkin(parsedPacket_1.url);
|
skin = await EaglerSkins.downloadSkin(parsedPacket_1.url);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user