Skip to content

Commit 511a3eb

Browse files
authored
Gateway improvements (#445)
* feat: add timeout for resolving file response * fix: enhance error handling in HTTP response to send appropriate status and body * refactor: optimize API creation by caching instances for MAINNET and TAURUS networks * fix: improve error handling in HTTP response by sending response bytes instead of body * refactor: enhance network file retrieval by implementing firstNonNull utility for improved promise resolution * fix: improve error handling in internalRedirect calls to ensure proper response on failure * feat: add download timeout configuration to enhance request handling
1 parent b19c4ac commit 511a3eb

File tree

5 files changed

+91
-26
lines changed

5 files changed

+91
-26
lines changed

gateway/src/config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,5 @@ export const ApiPatternByNetwork: Partial<Record<NetworkId, string>> = {
1010
export const config = {
1111
port: env("GATEWAY_PORT"),
1212
autoDriveApiKey: env("AUTO_DRIVE_API_KEY"),
13+
downloadTimeout: Number(env("DOWNLOAD_TIMEOUT", "30000")),
1314
};

gateway/src/http.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Response, Request } from "express";
22
import { Readable } from "stream";
3+
import { config } from "./config";
34

45
export const internalRedirect = async (
56
req: Request,
@@ -14,8 +15,14 @@ export const internalRedirect = async (
1415

1516
const response = await fetch(_url, {
1617
headers,
18+
signal: AbortSignal.timeout(config.downloadTimeout),
1719
});
1820

21+
if (!response.ok) {
22+
res.status(response.status).send(await response.bytes());
23+
return;
24+
}
25+
1926
const whitelistedHeaders = ["content-type"];
2027
for (const [key, value] of response.headers.entries()) {
2128
if (whitelistedHeaders.includes(key.toLowerCase())) {

gateway/src/index.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,10 @@ app.get("/file/:cid", async (req, res) => {
1919

2020
res.setHeader("X-Network", result.network);
2121

22-
internalRedirect(req, res, result.url);
22+
internalRedirect(req, res, result.url).catch((error) => {
23+
console.error(error);
24+
res.status(500).send("Internal server error");
25+
});
2326
});
2427

2528
app.get("/folder/:cid", async (req, res) => {
@@ -36,7 +39,10 @@ app.get("/folder/:cid", async (req, res) => {
3639

3740
res.setHeader("X-Network", result.network);
3841

39-
internalRedirect(req, res, result.url);
42+
internalRedirect(req, res, result.url).catch((error) => {
43+
console.error(error);
44+
res.status(500).send("Internal server error");
45+
});
4046
});
4147

4248
app.get("/", (req, res) => {

gateway/src/networkComposer.ts

Lines changed: 33 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,45 @@
11
import { NetworkId } from "@autonomys/auto-utils";
22
import { createAutoDriveApi } from "@autonomys/auto-drive";
3-
import { removeFalsy, toPromise } from "./utils";
3+
import { firstNonNull } from "./utils";
44
import { ApiPatternByNetwork, config } from "./config";
55

66
export const getFileNetwork = async (cid: string) => {
77
const networks = [NetworkId.MAINNET, NetworkId.TAURUS] as const;
88

9-
const results = removeFalsy(
10-
await toPromise(
11-
networks.map(async (network) => {
12-
const api = createAutoDriveApi({
13-
network: network,
14-
apiKey: config.autoDriveApiKey,
15-
});
9+
// Create reusable API instances to avoid memory leaks
10+
const apiInstances = {
11+
[NetworkId.MAINNET]: createAutoDriveApi({
12+
network: NetworkId.MAINNET,
13+
apiKey: config.autoDriveApiKey,
14+
}),
15+
[NetworkId.TAURUS]: createAutoDriveApi({
16+
network: NetworkId.TAURUS,
17+
apiKey: config.autoDriveApiKey,
18+
}),
19+
};
1620

17-
const object = await api.searchByNameOrCID(cid);
18-
const fileExists = object.length > 0;
19-
const url = ApiPatternByNetwork[network]?.replace("{cid}", cid);
21+
const promises = networks.map(async (network) => {
22+
try {
23+
const api = apiInstances[network];
24+
const object = await api.searchByNameOrCID(cid);
25+
const fileExists = object.length > 0;
26+
const url = ApiPatternByNetwork[network]?.replace("{cid}", cid);
2027

21-
if (fileExists && url) {
22-
const file = object[0];
23-
return {
24-
type: file.type,
25-
url,
26-
network,
27-
};
28-
}
28+
if (fileExists && url) {
29+
const file = object[0];
30+
return {
31+
type: file.type,
32+
url,
33+
network,
34+
};
35+
}
2936

30-
return null;
31-
})
32-
)
33-
);
37+
return null;
38+
} catch (error) {
39+
console.error(`Error searching network ${network}:`, error);
40+
return null;
41+
}
42+
});
3443

35-
return results.at(0);
44+
return await firstNonNull(promises);
3645
};

gateway/src/utils.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,45 @@ export const getHeaders = (req: express.Request): HeadersInit => {
2626
}
2727
return headers;
2828
};
29+
30+
/**
31+
* Resolves an array of promises and returns the first non-null value.
32+
* Does not wait for all promises to resolve - returns as soon as the first non-null value is found.
33+
* @param promises Array of promises that resolve to potentially null values
34+
* @returns Promise that resolves to the first non-null value, or null if all resolve to null
35+
*/
36+
export const firstNonNull = async <T>(
37+
promises: Promise<T | null>[]
38+
): Promise<T | null> => {
39+
if (promises.length === 0) {
40+
return null;
41+
}
42+
43+
return new Promise((resolve) => {
44+
let resolved = false;
45+
let pendingCount = promises.length;
46+
47+
const checkComplete = () => {
48+
if (pendingCount === 0 && !resolved) {
49+
resolve(null);
50+
}
51+
};
52+
53+
promises.forEach((promise) => {
54+
promise
55+
.then((result) => {
56+
pendingCount--;
57+
if (!resolved && result !== null && result !== undefined) {
58+
resolved = true;
59+
resolve(result);
60+
} else {
61+
checkComplete();
62+
}
63+
})
64+
.catch(() => {
65+
pendingCount--;
66+
checkComplete();
67+
});
68+
});
69+
});
70+
};

0 commit comments

Comments
 (0)