chore: add timeout mechanism for fetch calls (#3649)
This commit is contained in:
parent
334304ed52
commit
3e34e52440
@ -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,
|
||||
|
@ -32,6 +32,7 @@ describe('github API', () => {
|
||||
Authorization: 'Bearer token',
|
||||
'Content-Type': 'application/json; charset=utf-8',
|
||||
},
|
||||
signal: expect.any(AbortSignal),
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -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';
|
||||
|
@ -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,
|
||||
});
|
||||
|
@ -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),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -43,6 +43,7 @@ describe('github backend implementation', () => {
|
||||
headers: {
|
||||
Authorization: 'token token',
|
||||
},
|
||||
signal: expect.any(AbortSignal),
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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) {
|
||||
|
@ -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,
|
||||
};
|
||||
|
Loading…
x
Reference in New Issue
Block a user