Skip to content

Commit

Permalink
Return 408s if webview requests take too long
Browse files Browse the repository at this point in the history
For microsoft#166860

This makes our webview service worker return a 408 if a request takes over 30 seconds. Previously we would keep the request open without responding
  • Loading branch information
mjbvz committed Jan 30, 2023
1 parent 62bd5c1 commit facd8ba
Showing 1 changed file with 31 additions and 14 deletions.
45 changes: 31 additions & 14 deletions src/vs/workbench/contrib/webview/browser/pre/service-worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,18 @@ const remoteAuthority = searchParams.get('remoteAuthority');
*/
const resourceBaseAuthority = searchParams.get('vscode-resource-base-authority');

const resolveTimeout = 30000;
const resolveTimeout = 30_000;

/**
* @template T
* @typedef {{ status: 'ok'; value: T } | { status: 'timeout' }} RequestStoreResult
*/

/**
* @template T
* @typedef {{
* resolve: (x: T) => void,
* promise: Promise<T>
* resolve: (x: RequestStoreResult<T>) => void,
* promise: Promise<RequestStoreResult<T>>
* }} RequestStoreEntry
*/

Expand All @@ -47,27 +52,29 @@ class RequestStore {
}

/**
* @returns {{ requestId: number, promise: Promise<T> }}
* @returns {{ requestId: number, promise: Promise<RequestStoreResult<T>> }}
*/
create() {
const requestId = ++this.requestPool;

/** @type {undefined | ((x: T) => void)} */
/** @type {undefined | ((x: RequestStoreResult<T>) => void)} */
let resolve;

/** @type {Promise<T>} */
/** @type {Promise<RequestStoreResult<T>>} */
const promise = new Promise(r => resolve = r);

/** @type {RequestStoreEntry<T>} */
const entry = { resolve: /** @type {(x: T) => void} */ (resolve), promise };
const entry = { resolve: /** @type {(x: RequestStoreResult<T>) => void} */ (resolve), promise };

this.map.set(requestId, entry);

const dispose = () => {
clearTimeout(timeout);
const existingEntry = this.map.get(requestId);
if (existingEntry === entry) {
return this.map.delete(requestId);
existingEntry.resolve({ status: 'timeout' });
this.map.delete(requestId);
return;
}
};
const timeout = setTimeout(dispose, resolveTimeout);
Expand All @@ -84,7 +91,7 @@ class RequestStore {
if (!entry) {
return false;
}
entry.resolve(result);
entry.resolve({ status: 'ok', value: result });
this.map.delete(requestId);
return true;
}
Expand Down Expand Up @@ -120,6 +127,9 @@ const notFound = () =>
const methodNotAllowed = () =>
new Response('Method Not Allowed', { status: 405, });

const requestTimeout = () =>
new Response('Request Timeout', { status: 408, });

sw.addEventListener('message', async (event) => {
switch (event.data.channel) {
case 'version': {
Expand Down Expand Up @@ -238,10 +248,15 @@ async function processResourceRequest(event, requestUrlComponents) {
const shouldTryCaching = (event.request.method === 'GET');

/**
* @param {ResourceResponse} entry
* @param {RequestStoreResult<ResourceResponse>} result
* @param {Response | undefined} cachedResponse
*/
const resolveResourceEntry = (entry, cachedResponse) => {
const resolveResourceEntry = (result, cachedResponse) => {
if (result.status === 'timeout') {
return requestTimeout();
}

const entry = result.value;
if (entry.status === 304) { // Not modified
if (cachedResponse) {
return cachedResponse.clone();
Expand Down Expand Up @@ -384,13 +399,15 @@ async function processLocalhostRequest(event, requestUrl) {
const origin = requestUrl.origin;

/**
* @param {string | undefined} redirectOrigin
* @param {RequestStoreResult<string | undefined>} result
* @return {Promise<Response>}
*/
const resolveRedirect = async (redirectOrigin) => {
if (!redirectOrigin) {
const resolveRedirect = async (result) => {
if (result.status !== 'ok' || !result.value) {
return fetch(event.request);
}

const redirectOrigin = result.value;
const location = event.request.url.replace(new RegExp(`^${requestUrl.origin}(/|$)`), `${redirectOrigin}$1`);
return new Response(null, {
status: 302,
Expand Down

0 comments on commit facd8ba

Please sign in to comment.