chore: add timeout mechanism for fetch calls (#3649)
This commit is contained in:
@ -1,5 +1,5 @@
|
|||||||
import minimatch from 'minimatch';
|
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>;
|
type MakeAuthorizedRequest = (req: ApiRequest) => Promise<Response>;
|
||||||
|
|
||||||
@ -63,7 +63,7 @@ export class GitLfsClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async doUpload(upload: LfsBatchAction, resource: Blob) {
|
private async doUpload(upload: LfsBatchAction, resource: Blob) {
|
||||||
await fetch(decodeURI(upload.href), {
|
await unsentRequest.fetchWithTimeout(decodeURI(upload.href), {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
body: resource,
|
body: resource,
|
||||||
headers: upload.header,
|
headers: upload.header,
|
||||||
|
@ -32,6 +32,7 @@ describe('github API', () => {
|
|||||||
Authorization: 'Bearer token',
|
Authorization: 'Bearer token',
|
||||||
'Content-Type': 'application/json; charset=utf-8',
|
'Content-Type': 'application/json; charset=utf-8',
|
||||||
},
|
},
|
||||||
|
signal: expect.any(AbortSignal),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -211,30 +211,31 @@ export default class GitGateway implements Implementation {
|
|||||||
gitlab_enabled: gitlabEnabled,
|
gitlab_enabled: gitlabEnabled,
|
||||||
bitbucket_enabled: bitbucketEnabled,
|
bitbucket_enabled: bitbucketEnabled,
|
||||||
roles,
|
roles,
|
||||||
} = await fetch(`${this.gatewayUrl}/settings`, {
|
} = await unsentRequest
|
||||||
headers: { Authorization: `Bearer ${token}` },
|
.fetchWithTimeout(`${this.gatewayUrl}/settings`, {
|
||||||
}).then(async res => {
|
headers: { Authorization: `Bearer ${token}` },
|
||||||
const contentType = res.headers.get('Content-Type') || '';
|
})
|
||||||
if (!contentType.includes('application/json') && !contentType.includes('text/json')) {
|
.then(async res => {
|
||||||
throw new APIError(
|
const contentType = res.headers.get('Content-Type') || '';
|
||||||
`Your Git Gateway backend is not returning valid settings. Please make sure it is enabled.`,
|
if (!contentType.includes('application/json') && !contentType.includes('text/json')) {
|
||||||
res.status,
|
throw new APIError(
|
||||||
'Git Gateway',
|
`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) {
|
return body;
|
||||||
throw new APIError(
|
});
|
||||||
`Git Gateway Error: ${body.message ? body.message : body}`,
|
|
||||||
res.status,
|
|
||||||
'Git Gateway',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return body;
|
|
||||||
});
|
|
||||||
this.acceptRoles = roles;
|
this.acceptRoles = roles;
|
||||||
if (githubEnabled) {
|
if (githubEnabled) {
|
||||||
this.backendType = 'github';
|
this.backendType = 'github';
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { flow, fromPairs, map } from 'lodash/fp';
|
import { flow, fromPairs, map } from 'lodash/fp';
|
||||||
import minimatch from 'minimatch';
|
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>;
|
type MakeAuthorizedRequest = (req: ApiRequest) => Promise<Response>;
|
||||||
|
|
||||||
@ -96,7 +96,7 @@ const getResourceUploadURLs = async (
|
|||||||
};
|
};
|
||||||
|
|
||||||
const uploadBlob = (uploadURL: string, blob: Blob) =>
|
const uploadBlob = (uploadURL: string, blob: Blob) =>
|
||||||
fetch(uploadURL, {
|
unsentRequest.fetchWithTimeout(uploadURL, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
body: blob,
|
body: blob,
|
||||||
});
|
});
|
||||||
|
@ -117,6 +117,7 @@ describe('github API', () => {
|
|||||||
Authorization: 'token token',
|
Authorization: 'token token',
|
||||||
'Content-Type': 'application/json; charset=utf-8',
|
'Content-Type': 'application/json; charset=utf-8',
|
||||||
},
|
},
|
||||||
|
signal: expect.any(AbortSignal),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -163,6 +164,7 @@ describe('github API', () => {
|
|||||||
Authorization: 'promise-token',
|
Authorization: 'promise-token',
|
||||||
'Content-Type': 'application/json; charset=utf-8',
|
'Content-Type': 'application/json; charset=utf-8',
|
||||||
},
|
},
|
||||||
|
signal: expect.any(AbortSignal),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -43,6 +43,7 @@ describe('github backend implementation', () => {
|
|||||||
headers: {
|
headers: {
|
||||||
Authorization: 'token token',
|
Authorization: 'token token',
|
||||||
},
|
},
|
||||||
|
signal: expect.any(AbortSignal),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -28,6 +28,7 @@ import {
|
|||||||
runWithLock,
|
runWithLock,
|
||||||
blobToFileObj,
|
blobToFileObj,
|
||||||
contentKeyFromBranch,
|
contentKeyFromBranch,
|
||||||
|
unsentRequest,
|
||||||
} from 'netlify-cms-lib-util';
|
} from 'netlify-cms-lib-util';
|
||||||
import AuthenticationPage from './AuthenticationPage';
|
import AuthenticationPage from './AuthenticationPage';
|
||||||
import { Octokit } from '@octokit/rest';
|
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 };
|
type ApiFile = { id: string; type: string; name: string; path: string; size: number };
|
||||||
|
|
||||||
|
const { fetchWithTimeout: fetch } = unsentRequest;
|
||||||
|
|
||||||
export default class GitHub implements Implementation {
|
export default class GitHub implements Implementation {
|
||||||
lock: AsyncLock;
|
lock: AsyncLock;
|
||||||
api: API | null;
|
api: API | null;
|
||||||
|
@ -8,6 +8,7 @@ import {
|
|||||||
ImplementationFile,
|
ImplementationFile,
|
||||||
EditorialWorkflowError,
|
EditorialWorkflowError,
|
||||||
APIError,
|
APIError,
|
||||||
|
unsentRequest,
|
||||||
} from 'netlify-cms-lib-util';
|
} from 'netlify-cms-lib-util';
|
||||||
import AuthenticationPage from './AuthenticationPage';
|
import AuthenticationPage from './AuthenticationPage';
|
||||||
|
|
||||||
@ -83,13 +84,14 @@ export default class ProxyBackend implements Implementation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async request(payload: { action: string; params: Record<string, unknown> }) {
|
async request(payload: { action: string; params: Record<string, unknown> }) {
|
||||||
const response = await fetch(this.proxyUrl, {
|
const response = await unsentRequest.fetchWithTimeout(this.proxyUrl, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json; charset=utf-8' },
|
headers: { 'Content-Type': 'application/json; charset=utf-8' },
|
||||||
body: JSON.stringify({ branch: this.branch, ...payload }),
|
body: JSON.stringify({ branch: this.branch, ...payload }),
|
||||||
});
|
});
|
||||||
|
|
||||||
const json = await response.json();
|
const json = await response.json();
|
||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
return json;
|
return json;
|
||||||
} else {
|
} else {
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { createEntry } from 'ValueObjects/Entry';
|
import { createEntry } from 'ValueObjects/Entry';
|
||||||
import { selectEntrySlug } from 'Reducers/collections';
|
import { selectEntrySlug } from 'Reducers/collections';
|
||||||
|
import { unsentRequest } from 'netlify-cms-lib-util';
|
||||||
|
|
||||||
|
const { fetchWithTimeout: fetch } = unsentRequest;
|
||||||
|
|
||||||
function getSlug(path) {
|
function getSlug(path) {
|
||||||
return path
|
return path
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
import { pickBy, trimEnd } from 'lodash';
|
import { pickBy, trimEnd } from 'lodash';
|
||||||
import { addParams } from 'Lib/urlHelper';
|
import { addParams } from 'Lib/urlHelper';
|
||||||
|
import { unsentRequest } from 'netlify-cms-lib-util';
|
||||||
|
|
||||||
|
const { fetchWithTimeout: fetch } = unsentRequest;
|
||||||
|
|
||||||
export default class AssetStore {
|
export default class AssetStore {
|
||||||
constructor(config, getToken) {
|
constructor(config, getToken) {
|
||||||
|
@ -3,6 +3,27 @@ import curry from 'lodash/curry';
|
|||||||
import flow from 'lodash/flow';
|
import flow from 'lodash/flow';
|
||||||
import isString from 'lodash/isString';
|
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 =>
|
const decodeParams = paramsString =>
|
||||||
List(paramsString.split('&'))
|
List(paramsString.split('&'))
|
||||||
.map(s => List(s.split('=')).map(decodeURIComponent))
|
.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
|
// This actually performs the built request object
|
||||||
const performRequest = ensureRequestArg(req => {
|
const performRequest = ensureRequestArg(req => {
|
||||||
const args = toFetchArguments(req);
|
const args = toFetchArguments(req);
|
||||||
return fetch(...args);
|
return fetchWithTimeout(...args);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Each of the following functions takes options and returns another
|
// Each of the following functions takes options and returns another
|
||||||
@ -91,4 +112,5 @@ export default {
|
|||||||
withParams,
|
withParams,
|
||||||
withRoot,
|
withRoot,
|
||||||
withNoCache,
|
withNoCache,
|
||||||
|
fetchWithTimeout,
|
||||||
};
|
};
|
||||||
|
Reference in New Issue
Block a user