mirror of
https://github.com/gethomepage/homepage.git
synced 2026-05-18 11:27:33 +08:00
Enhancement: Cache and reuse keep-alive HTTP(S) agents (#6536)
This commit is contained in:
@@ -224,23 +224,43 @@ function homepageDNSLookupFn() {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const homepageLookup = homepageDNSLookupFn();
|
||||||
|
const agentCache = new Map();
|
||||||
|
|
||||||
|
function getAgent(protocol, disableIpv6) {
|
||||||
|
const cacheKey = `${protocol}:${disableIpv6 ? "ipv4" : "auto"}`;
|
||||||
|
const cachedAgent = agentCache.get(cacheKey);
|
||||||
|
if (cachedAgent) {
|
||||||
|
return cachedAgent;
|
||||||
|
}
|
||||||
|
|
||||||
|
const agentOptions = {
|
||||||
|
keepAlive: true,
|
||||||
|
...(disableIpv6 ? { family: 4, autoSelectFamily: false } : { autoSelectFamilyAttemptTimeout: 500 }),
|
||||||
|
lookup: homepageLookup,
|
||||||
|
};
|
||||||
|
|
||||||
|
const agent =
|
||||||
|
protocol === "https:"
|
||||||
|
? new https.Agent({ ...agentOptions, rejectUnauthorized: false })
|
||||||
|
: new http.Agent(agentOptions);
|
||||||
|
|
||||||
|
agentCache.set(cacheKey, agent);
|
||||||
|
return agent;
|
||||||
|
}
|
||||||
|
|
||||||
export async function httpProxy(url, params = {}) {
|
export async function httpProxy(url, params = {}) {
|
||||||
const constructedUrl = new URL(url);
|
const constructedUrl = new URL(url);
|
||||||
const disableIpv6 = process.env.HOMEPAGE_PROXY_DISABLE_IPV6 === "true";
|
const disableIpv6 = process.env.HOMEPAGE_PROXY_DISABLE_IPV6 === "true";
|
||||||
const agentOptions = {
|
|
||||||
...(disableIpv6 ? { family: 4, autoSelectFamily: false } : { autoSelectFamilyAttemptTimeout: 500 }),
|
|
||||||
lookup: homepageDNSLookupFn(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let request = null;
|
let request = null;
|
||||||
if (constructedUrl.protocol === "https:") {
|
if (constructedUrl.protocol === "https:") {
|
||||||
request = httpsRequest(constructedUrl, {
|
request = httpsRequest(constructedUrl, {
|
||||||
agent: new https.Agent({ ...agentOptions, rejectUnauthorized: false }),
|
agent: getAgent(constructedUrl.protocol, disableIpv6),
|
||||||
...params,
|
...params,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
request = httpRequest(constructedUrl, {
|
request = httpRequest(constructedUrl, {
|
||||||
agent: new http.Agent(agentOptions),
|
agent: getAgent(constructedUrl.protocol, disableIpv6),
|
||||||
...params,
|
...params,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ const { state, cache, logger, dns, net, cookieJar } = vi.hoisted(() => ({
|
|||||||
body: Buffer.from(""),
|
body: Buffer.from(""),
|
||||||
},
|
},
|
||||||
error: null,
|
error: null,
|
||||||
|
lastAgent: null,
|
||||||
lastAgentOptions: null,
|
lastAgentOptions: null,
|
||||||
lastRequestParams: null,
|
lastRequestParams: null,
|
||||||
lastWrittenBody: null,
|
lastWrittenBody: null,
|
||||||
@@ -59,6 +60,7 @@ vi.mock("follow-redirects", async () => {
|
|||||||
state.lastWrittenBody = chunk;
|
state.lastWrittenBody = chunk;
|
||||||
});
|
});
|
||||||
req.end = vi.fn(() => {
|
req.end = vi.fn(() => {
|
||||||
|
state.lastAgent = params?.agent ?? null;
|
||||||
state.lastAgentOptions = params?.agent?.opts ?? null;
|
state.lastAgentOptions = params?.agent?.opts ?? null;
|
||||||
if (state.error) {
|
if (state.error) {
|
||||||
req.emit("error", state.error);
|
req.emit("error", state.error);
|
||||||
@@ -104,6 +106,7 @@ describe("utils/proxy/http cachedRequest", () => {
|
|||||||
headers: { "content-type": "application/json" },
|
headers: { "content-type": "application/json" },
|
||||||
body: Buffer.from(""),
|
body: Buffer.from(""),
|
||||||
};
|
};
|
||||||
|
state.lastAgent = null;
|
||||||
state.lastAgentOptions = null;
|
state.lastAgentOptions = null;
|
||||||
state.lastRequestParams = null;
|
state.lastRequestParams = null;
|
||||||
state.lastWrittenBody = null;
|
state.lastWrittenBody = null;
|
||||||
@@ -307,6 +310,7 @@ describe("utils/proxy/http httpProxy", () => {
|
|||||||
headers: { "content-type": "application/json" },
|
headers: { "content-type": "application/json" },
|
||||||
body: Buffer.from("ok"),
|
body: Buffer.from("ok"),
|
||||||
};
|
};
|
||||||
|
state.lastAgent = null;
|
||||||
state.lastAgentOptions = null;
|
state.lastAgentOptions = null;
|
||||||
state.lastRequestParams = null;
|
state.lastRequestParams = null;
|
||||||
state.lastWrittenBody = null;
|
state.lastWrittenBody = null;
|
||||||
@@ -397,6 +401,7 @@ describe("utils/proxy/http httpProxy", () => {
|
|||||||
|
|
||||||
await httpMod.httpProxy("http://example.com");
|
await httpMod.httpProxy("http://example.com");
|
||||||
|
|
||||||
|
expect(state.lastAgentOptions.keepAlive).toBe(true);
|
||||||
expect(state.lastAgentOptions.family).toBe(4);
|
expect(state.lastAgentOptions.family).toBe(4);
|
||||||
expect(state.lastAgentOptions.autoSelectFamily).toBe(false);
|
expect(state.lastAgentOptions.autoSelectFamily).toBe(false);
|
||||||
});
|
});
|
||||||
@@ -409,6 +414,17 @@ describe("utils/proxy/http httpProxy", () => {
|
|||||||
expect(state.lastAgentOptions.rejectUnauthorized).toBe(false);
|
expect(state.lastAgentOptions.rejectUnauthorized).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("reuses the same keep-alive agent for repeated http requests", async () => {
|
||||||
|
const httpMod = await import("./http");
|
||||||
|
|
||||||
|
await httpMod.httpProxy("http://example.com/first");
|
||||||
|
const firstAgent = state.lastAgent;
|
||||||
|
await httpMod.httpProxy("http://example.com/second");
|
||||||
|
|
||||||
|
expect(state.lastAgentOptions.keepAlive).toBe(true);
|
||||||
|
expect(state.lastAgent).toBe(firstAgent);
|
||||||
|
});
|
||||||
|
|
||||||
it("returns a sanitized error response when the request fails", async () => {
|
it("returns a sanitized error response when the request fails", async () => {
|
||||||
state.error = Object.assign(new Error("boom"), { code: "EHOSTUNREACH" });
|
state.error = Object.assign(new Error("boom"), { code: "EHOSTUNREACH" });
|
||||||
const httpMod = await import("./http");
|
const httpMod = await import("./http");
|
||||||
|
|||||||
Reference in New Issue
Block a user