summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cli/tests/unit/http_test.ts45
-rw-r--r--runtime/js/40_http.js26
2 files changed, 68 insertions, 3 deletions
diff --git a/cli/tests/unit/http_test.ts b/cli/tests/unit/http_test.ts
index ab43323bd..d4c35545f 100644
--- a/cli/tests/unit/http_test.ts
+++ b/cli/tests/unit/http_test.ts
@@ -272,3 +272,48 @@ unitTest(
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;
+ },
+);
diff --git a/runtime/js/40_http.js b/runtime/js/40_http.js
index eb3c58a63..eb4d214ca 100644
--- a/runtime/js/40_http.js
+++ b/runtime/js/40_http.js
@@ -14,6 +14,8 @@
return new HttpConn(rid);
}
+ const connErrorSymbol = Symbol("connError");
+
class HttpConn {
#rid = 0;
@@ -35,10 +37,16 @@
this.#rid,
);
} 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) {
return null;
} else if (error instanceof errors.Interrupted) {
return null;
+ } else if (error.message.includes("connection closed")) {
+ return null;
}
throw error;
}
@@ -66,7 +74,7 @@
);
const request = fromInnerRequest(innerRequest, "immutable");
- const respondWith = createRespondWith(responseSenderRid, this.#rid);
+ const respondWith = createRespondWith(this, responseSenderRid);
return { request, respondWith };
}
@@ -97,7 +105,7 @@
);
}
- function createRespondWith(responseSenderRid) {
+ function createRespondWith(httpConn, responseSenderRid) {
return async function respondWith(resp) {
if (resp instanceof Promise) {
resp = await resp;
@@ -145,6 +153,11 @@
innerResp.headerList,
], respBody instanceof Uint8Array ? respBody : null);
} 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) {
await respBody.cancel(error);
}
@@ -173,6 +186,11 @@
value,
);
} 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);
throw error;
}
@@ -180,7 +198,9 @@
} finally {
// Once all chunks are sent, and the request body is closed, we can
// close the response body.
- await Deno.core.opAsync("op_http_response_close", responseBodyRid);
+ try {
+ await Deno.core.opAsync("op_http_response_close", responseBodyRid);
+ } catch { /* pass */ }
}
}
};