diff --git a/apps/node/src/routes/proxy.ts b/apps/node/src/routes/proxy.ts index 8cc922b..a52e62f 100644 --- a/apps/node/src/routes/proxy.ts +++ b/apps/node/src/routes/proxy.ts @@ -26,7 +26,6 @@ export default class ProxyRoute extends Route { res.status(400).json(RouteMessages.badRequest("No URL provided")); return; } - // TODO: handle rate limiting? and/or caching? try { const response = await axios.get(url, { headers: { diff --git a/apps/proxy/package.json b/apps/proxy/package.json index cbfcfbc..abf4ce7 100644 --- a/apps/proxy/package.json +++ b/apps/proxy/package.json @@ -17,6 +17,7 @@ "server": "workspace:*", "ts-node": "^10.9.1", "typescript": "^5.2.2", - "utils": "workspace:*" + "utils": "workspace:*", + "node-cache": "^5.1.2" } } diff --git a/apps/proxy/src/routes/proxy.ts b/apps/proxy/src/routes/proxy.ts index 99ccba0..9e83224 100644 --- a/apps/proxy/src/routes/proxy.ts +++ b/apps/proxy/src/routes/proxy.ts @@ -1,9 +1,41 @@ import { Request, Response } from "express"; +import Cache from "node-cache"; import { Route, RouteMessages } from "server"; import { nodeManager } from ".."; const IGNORED_PATHS = ["/favicon.ico"]; +const cache = new Cache({ + stdTTL: 300, // 5 minutes +}); +type CachedRequest = { + nodeId: string; + status: number; + headers: any; + data: any; +}; + +/** + * Logs a request + * + * @param nodeId the node ID that handled the request + * @param url the URL of the request + * @param status the status code of the request + * @param time the time it took to handle the request (or true if it was cached) + */ +function log( + nodeId: string, + url: string, + status: number, + time: number | boolean +) { + console.log( + `[node: ${nodeId}] ${url} - ${status} (${ + time === true ? "cached" : time + "ms" + })` + ); +} + export default class ProxyRoute extends Route { constructor() { super({ path: "/" }); @@ -29,8 +61,19 @@ export default class ProxyRoute extends Route { } try { + const cachedRequest = cache.get(url); + if (cachedRequest) { + log(cachedRequest.nodeId, url, cachedRequest.status, true); + res + .status(cachedRequest.status) + .set(cachedRequest.headers) + .json(cachedRequest.data); + return; + } + const before = Date.now(); const response = await node.fetch(url); + const nodeId = response.headers["x-proxy-node"]; const data = response.data; if (response.status === 500) { @@ -38,15 +81,16 @@ export default class ProxyRoute extends Route { return; } - // Log the request - console.log( - `[node: ${response.headers["x-proxy-node"]}] ${url} - ${ - response.status - } (${Date.now() - before}ms)` - ); + log(nodeId, url, response.status, Date.now() - before); // Return the JSON response res.status(response.status).set(response.headers).json(data); + cache.set(url, { + nodeId: nodeId, + status: response.status, + headers: response.headers, + data: data, + } as CachedRequest); } catch (ex: any) { res.status(500).json(RouteMessages.internalServerError(ex.message || ex)); } diff --git a/package.json b/package.json index d912702..dc68422 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "dependencies": { "@types/express": "^4.17.21", "infisical-node": "^1.5.0", + "node-cache": "^5.1.2", "tsup": "^7.2.0" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 856bfcf..73c4e4d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,6 +14,9 @@ importers: infisical-node: specifier: ^1.5.0 version: 1.5.0 + node-cache: + specifier: ^5.1.2 + version: 5.1.2 tsup: specifier: ^7.2.0 version: 7.2.0 @@ -75,6 +78,9 @@ importers: server: specifier: workspace:* version: link:../../packages/server + timed-cache: + specifier: ^2.0.0 + version: 2.0.0 ts-node: specifier: ^10.9.1 version: 10.9.1(@types/node@20.5.2)(typescript@5.2.2) @@ -1414,6 +1420,11 @@ packages: escape-string-regexp: 1.0.5 dev: true + /clone@2.1.2: + resolution: {integrity: sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==} + engines: {node: '>=0.8'} + dev: false + /color-convert@1.9.3: resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} dependencies: @@ -3287,6 +3298,13 @@ packages: engines: {node: '>= 0.6'} dev: false + /node-cache@5.1.2: + resolution: {integrity: sha512-t1QzWwnk4sjLWaQAS8CHgOJ+RAfmHpxFWmc36IWTiWHQfs0w5JDMBS1b1ZxQteo0vVVuWJvIUKHDkkeK7vIGCg==} + engines: {node: '>= 8.0.0'} + dependencies: + clone: 2.1.2 + dev: false + /node-releases@2.0.13: resolution: {integrity: sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==} dev: true @@ -4096,6 +4114,10 @@ packages: any-promise: 1.3.0 dev: false + /timed-cache@2.0.0: + resolution: {integrity: sha512-9owe3VtDCtZKo8bfk5bSC4tSzIRP65doXI0i2oFWNP4VjQDwoRIsADynZQZz6XXK7exL7bOuI3HExFQ9LGi3tQ==} + dev: false + /titleize@3.0.0: resolution: {integrity: sha512-KxVu8EYHDPBdUYdKZdKtU2aj2XfEx9AfjXxE/Aj0vT06w2icA09Vus1rh6eSu1y01akYg6BjIK/hxyLJINoMLQ==} engines: {node: '>=12'}