1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-12-01 16:51:13 -05:00

fix(runtime/http): expose nextRequest() errors in respondWith() (#10384)

This commit is contained in:
Nayeem Rahman 2021-05-19 13:39:52 +01:00 committed by Bert Belder
parent a1125765ec
commit 218ba031f0
No known key found for this signature in database
GPG key ID: 7A77887B2E2ED461
2 changed files with 68 additions and 3 deletions

View file

@ -272,3 +272,48 @@ unitTest(
await promise; await promise;
}, },
); );
unitTest(
{ perms: { net: true } },
async function httpServerNextRequestErrorExposedInResponse() {
const promise = (async () => {
const listener = Deno.listen({ port: 4501 });
const conn = await listener.accept();
const httpConn = Deno.serveHttp(conn);
const event = await httpConn.nextRequest();
assert(event);
// Start polling for the next request before awaiting response.
const nextRequestPromise = httpConn.nextRequest();
const { respondWith } = event;
await assertThrowsAsync(
async () => {
let interval = 0;
await respondWith(
new Response(
new ReadableStream({
start(controller) {
interval = setInterval(() => {
const message = `data: ${Date.now()}\n\n`;
controller.enqueue(new TextEncoder().encode(message));
}, 200);
},
cancel() {
clearInterval(interval);
},
}),
),
);
},
Deno.errors.Http,
"connection closed",
);
// The error from `op_http_request_next` reroutes to `respondWith()`.
assertEquals(await nextRequestPromise, null);
listener.close();
})();
const resp = await fetch("http://127.0.0.1:4501/");
await resp.body!.cancel();
await promise;
},
);

View file

@ -14,6 +14,8 @@
return new HttpConn(rid); return new HttpConn(rid);
} }
const connErrorSymbol = Symbol("connError");
class HttpConn { class HttpConn {
#rid = 0; #rid = 0;
@ -35,10 +37,16 @@
this.#rid, this.#rid,
); );
} catch (error) { } catch (error) {
// A connection error seen here would cause disrupted responses to throw
// a generic `BadResource` error. Instead store this error and replace
// those with it.
this[connErrorSymbol] = error;
if (error instanceof errors.BadResource) { if (error instanceof errors.BadResource) {
return null; return null;
} else if (error instanceof errors.Interrupted) { } else if (error instanceof errors.Interrupted) {
return null; return null;
} else if (error.message.includes("connection closed")) {
return null;
} }
throw error; throw error;
} }
@ -66,7 +74,7 @@
); );
const request = fromInnerRequest(innerRequest, "immutable"); const request = fromInnerRequest(innerRequest, "immutable");
const respondWith = createRespondWith(responseSenderRid, this.#rid); const respondWith = createRespondWith(this, responseSenderRid);
return { request, respondWith }; return { request, respondWith };
} }
@ -97,7 +105,7 @@
); );
} }
function createRespondWith(responseSenderRid) { function createRespondWith(httpConn, responseSenderRid) {
return async function respondWith(resp) { return async function respondWith(resp) {
if (resp instanceof Promise) { if (resp instanceof Promise) {
resp = await resp; resp = await resp;
@ -145,6 +153,11 @@
innerResp.headerList, innerResp.headerList,
], respBody instanceof Uint8Array ? respBody : null); ], respBody instanceof Uint8Array ? respBody : null);
} catch (error) { } catch (error) {
const connError = httpConn[connErrorSymbol];
if (error instanceof errors.BadResource && connError != null) {
// deno-lint-ignore no-ex-assign
error = new connError.constructor(connError.message);
}
if (respBody !== null && respBody instanceof ReadableStream) { if (respBody !== null && respBody instanceof ReadableStream) {
await respBody.cancel(error); await respBody.cancel(error);
} }
@ -173,6 +186,11 @@
value, value,
); );
} catch (error) { } catch (error) {
const connError = httpConn[connErrorSymbol];
if (error instanceof errors.BadResource && connError != null) {
// deno-lint-ignore no-ex-assign
error = new connError.constructor(connError.message);
}
await reader.cancel(error); await reader.cancel(error);
throw error; throw error;
} }
@ -180,7 +198,9 @@
} finally { } finally {
// Once all chunks are sent, and the request body is closed, we can // Once all chunks are sent, and the request body is closed, we can
// close the response body. // close the response body.
try {
await Deno.core.opAsync("op_http_response_close", responseBodyRid); await Deno.core.opAsync("op_http_response_close", responseBodyRid);
} catch { /* pass */ }
} }
} }
}; };