chore: add timeout mechanism for fetch calls (#3649)

This commit is contained in:
Kunal Kundu 2020-05-12 19:21:13 +05:30 committed by GitHub
parent 334304ed52
commit 3e34e52440
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 66 additions and 28 deletions

View File

@ -1,5 +1,5 @@
import minimatch from 'minimatch';
import { ApiRequest, PointerFile } from 'netlify-cms-lib-util';
import { ApiRequest, PointerFile, unsentRequest } from 'netlify-cms-lib-util';
type MakeAuthorizedRequest = (req: ApiRequest) => Promise<Response>;
@ -63,7 +63,7 @@ export class GitLfsClient {
}
private async doUpload(upload: LfsBatchAction, resource: Blob) {
await fetch(decodeURI(upload.href), {
await unsentRequest.fetchWithTimeout(decodeURI(upload.href), {
method: 'PUT',
body: resource,
headers: upload.header,

View File

@ -32,6 +32,7 @@ describe('github API', () => {
Authorization: 'Bearer token',
'Content-Type': 'application/json; charset=utf-8',
},
signal: expect.any(AbortSignal),
});
});

View File

@ -211,30 +211,31 @@ export default class GitGateway implements Implementation {
gitlab_enabled: gitlabEnabled,
bitbucket_enabled: bitbucketEnabled,
roles,
} = await fetch(`${this.gatewayUrl}/settings`, {
headers: { Authorization: `Bearer ${token}` },
}).then(async res => {
const contentType = res.headers.get('Content-Type') || '';
if (!contentType.includes('application/json') && !contentType.includes('text/json')) {
throw new APIError(
`Your Git Gateway backend is not returning valid settings. Please make sure it is enabled.`,
res.status,
'Git Gateway',
);
}
} = await unsentRequest
.fetchWithTimeout(`${this.gatewayUrl}/settings`, {
headers: { Authorization: `Bearer ${token}` },
})
.then(async res => {
const contentType = res.headers.get('Content-Type') || '';
if (!contentType.includes('application/json') && !contentType.includes('text/json')) {
throw new APIError(
`Your Git Gateway backend is not returning valid settings. Please make sure it is enabled.`,
res.status,
'Git Gateway',
);
}
const body = await res.json();
const body = await res.json();
if (!res.ok) {
throw new APIError(
`Git Gateway Error: ${body.message ? body.message : body}`,
res.status,
'Git Gateway',
);
}
if (!res.ok) {
throw new APIError(
`Git Gateway Error: ${body.message ? body.message : body}`,
res.status,
'Git Gateway',
);
}
return body;
});
return body;
});
this.acceptRoles = roles;
if (githubEnabled) {
this.backendType = 'github';

View File

@ -1,6 +1,6 @@
import { flow, fromPairs, map } from 'lodash/fp';
import minimatch from 'minimatch';
import { ApiRequest, PointerFile } from 'netlify-cms-lib-util';
import { ApiRequest, PointerFile, unsentRequest } from 'netlify-cms-lib-util';
type MakeAuthorizedRequest = (req: ApiRequest) => Promise<Response>;
@ -96,7 +96,7 @@ const getResourceUploadURLs = async (
};
const uploadBlob = (uploadURL: string, blob: Blob) =>
fetch(uploadURL, {
unsentRequest.fetchWithTimeout(uploadURL, {
method: 'PUT',
body: blob,
});

View File

@ -117,6 +117,7 @@ describe('github API', () => {
Authorization: 'token token',
'Content-Type': 'application/json; charset=utf-8',
},
signal: expect.any(AbortSignal),
});
});
@ -163,6 +164,7 @@ describe('github API', () => {
Authorization: 'promise-token',
'Content-Type': 'application/json; charset=utf-8',
},
signal: expect.any(AbortSignal),
});
});
});

View File

@ -43,6 +43,7 @@ describe('github backend implementation', () => {
headers: {
Authorization: 'token token',
},
signal: expect.any(AbortSignal),
});
});

View File

@ -28,6 +28,7 @@ import {
runWithLock,
blobToFileObj,
contentKeyFromBranch,
unsentRequest,
} from 'netlify-cms-lib-util';
import AuthenticationPage from './AuthenticationPage';
import { Octokit } from '@octokit/rest';
@ -40,6 +41,8 @@ const MAX_CONCURRENT_DOWNLOADS = 10;
type ApiFile = { id: string; type: string; name: string; path: string; size: number };
const { fetchWithTimeout: fetch } = unsentRequest;
export default class GitHub implements Implementation {
lock: AsyncLock;
api: API | null;

View File

@ -8,6 +8,7 @@ import {
ImplementationFile,
EditorialWorkflowError,
APIError,
unsentRequest,
} from 'netlify-cms-lib-util';
import AuthenticationPage from './AuthenticationPage';
@ -83,13 +84,14 @@ export default class ProxyBackend implements Implementation {
}
async request(payload: { action: string; params: Record<string, unknown> }) {
const response = await fetch(this.proxyUrl, {
const response = await unsentRequest.fetchWithTimeout(this.proxyUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json; charset=utf-8' },
body: JSON.stringify({ branch: this.branch, ...payload }),
});
const json = await response.json();
if (response.ok) {
return json;
} else {

View File

@ -1,6 +1,9 @@
import _ from 'lodash';
import { createEntry } from 'ValueObjects/Entry';
import { selectEntrySlug } from 'Reducers/collections';
import { unsentRequest } from 'netlify-cms-lib-util';
const { fetchWithTimeout: fetch } = unsentRequest;
function getSlug(path) {
return path

View File

@ -1,5 +1,8 @@
import { pickBy, trimEnd } from 'lodash';
import { addParams } from 'Lib/urlHelper';
import { unsentRequest } from 'netlify-cms-lib-util';
const { fetchWithTimeout: fetch } = unsentRequest;
export default class AssetStore {
constructor(config, getToken) {

View File

@ -3,6 +3,27 @@ import curry from 'lodash/curry';
import flow from 'lodash/flow';
import isString from 'lodash/isString';
let controller;
let signal;
if (typeof window !== 'undefined') {
controller = window.AbortController && new AbortController();
signal = controller && controller.signal;
}
const timeout = 60;
const fetchWithTimeout = (input, init) => {
if (controller && signal && !init.signal) {
setTimeout(() => controller.abort(), timeout * 1000);
return fetch(input, { ...init, signal }).catch(e => {
if (e.name === 'AbortError') {
throw new Error(`Request timed out after ${timeout} seconds`);
}
throw e;
});
}
return fetch(input, init);
};
const decodeParams = paramsString =>
List(paramsString.split('&'))
.map(s => List(s.split('=')).map(decodeURIComponent))
@ -51,7 +72,7 @@ const ensureRequestArg2 = func => (arg, req) => func(arg, maybeRequestArg(req));
// This actually performs the built request object
const performRequest = ensureRequestArg(req => {
const args = toFetchArguments(req);
return fetch(...args);
return fetchWithTimeout(...args);
});
// Each of the following functions takes options and returns another
@ -91,4 +112,5 @@ export default {
withParams,
withRoot,
withNoCache,
fetchWithTimeout,
};