Try a promise oriented fetch timeout for perspective

If this mitigates issues of failed requests, this means we
can switch over to this version of time out based fetch
requests.

CORL-1277
This commit is contained in:
nick-funk
2020-08-12 11:52:46 -06:00
parent d67c3d2907
commit 0e8449c5cd
2 changed files with 91 additions and 11 deletions
+79 -1
View File
@@ -1,8 +1,9 @@
import AbortController from "abort-controller";
import http from "http";
import https from "https";
import { capitalize } from "lodash";
import { clearLongTimeout } from "long-settimeout";
import fetch, { RequestInit, Response } from "node-fetch";
import fetch, { RequestInfo, RequestInit, Response } from "node-fetch";
import { URL } from "url";
import { version } from "coral-common/version";
@@ -58,6 +59,83 @@ export function generateFetchOptions(
};
}
/**
* Create a fetch request with a time out.
* Inspired by: https://www.lowmess.com/blog/fetch-with-timeout/
*
* @param name gives the request headers user agent a name
* @param url the url you want to make a request against
* @param options fetch options, will override preset defaults
* @param timeMs time in milliseconds to wait for response
*/
export const fetchWithTimeout = (
name: string,
url: RequestInfo,
options = {},
timeMs?: number
) => {
const controller = new AbortController();
// Create HTTP agents to improve connection performance.
const agents = {
https: new https.Agent({
keepAlive: true,
}),
http: new http.Agent({
keepAlive: true,
}),
};
// agent will select the correct agent to use for reusing the agent.
const agent = (agentUrl: URL) =>
agentUrl.protocol === "http:" ? agents.http : agents.https;
const defaultHeaders = {
"User-Agent": `Coral ${capitalize(name)}/${version}`,
};
const config: FetchOptions = {
agent,
headers: {
...defaultHeaders,
},
// Limit response sizes to 2MB of response data. 1e6B is 1MB.
size: 2e6,
// Do not follow redirects automatically, and do not error if we
// encounter one. We'll treat the response from the request as the
// endpoints final response.
redirect: "manual",
// Attach the controller signal to abort the request after the timeout
// is reached.
signal: controller.signal,
...options,
};
const time = timeMs ? timeMs : 10000;
const timeOut = setTimeout(() => {
controller.abort();
}, time);
return fetch(url, config)
.then((response) => {
clearTimeout(timeOut);
if (!response.ok) {
throw new Error(`${response.status}: ${response.statusText}`);
}
return response;
})
.catch((error) => {
if (error.name === "AbortError") {
throw new Error("fetch response timed out");
}
throw new Error(error.message);
});
};
export const createFetch = ({
name,
options: { headers: defaultBaseHeaders = {}, ...defaultOptions } = {},
@@ -4,9 +4,7 @@ import { URL } from "url";
import { TOXICITY_ENDPOINT_DEFAULT } from "coral-common/constants";
import { LanguageCode } from "coral-common/helpers";
import { getURLWithCommentID } from "coral-server/models/story";
import { createFetch } from "coral-server/services/fetch";
const fetch = createFetch({ name: "perspective" });
import { fetchWithTimeout } from "coral-server/services/fetch";
/**
* Language is the language key that is supported by the Perspective API in the
@@ -137,14 +135,18 @@ export async function sendToPerspective(
try {
// Create the request and send it.
const res = await fetch(url.toString(), {
method: "POST",
headers: {
"Content-Type": "application/json",
const res = await fetchWithTimeout(
"perspective",
url.toString(),
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body,
},
timeout,
body,
});
timeout
);
if (!res.ok) {
return {
ok: res.ok,