summaryrefslogtreecommitdiff
path: root/cli/tests
diff options
context:
space:
mode:
authorLuca Casonato <hello@lcas.dev>2022-12-20 09:46:45 +0100
committerGitHub <noreply@github.com>2022-12-20 08:46:45 +0000
commit8e947bb674725195a4d2a754445ee71029108f61 (patch)
treece4e859c2f9bc5443e88781a09960a7fb1859afa /cli/tests
parent948f85216a15e4ef489af21bb532a9b201b0364c (diff)
fix(ext/http): close stream on resp body error (#17126)
Previously, errored streaming response bodies did not cause the HTTP stream to be aborted. It instead caused the stream to be closed gracefully, which had the result that the client could not detect the difference between a successful response and an errored response. This commit fixes the issue by aborting the stream on error.
Diffstat (limited to 'cli/tests')
-rw-r--r--cli/tests/unit/http_test.ts123
1 files changed, 123 insertions, 0 deletions
diff --git a/cli/tests/unit/http_test.ts b/cli/tests/unit/http_test.ts
index 347551362..73bf07b68 100644
--- a/cli/tests/unit/http_test.ts
+++ b/cli/tests/unit/http_test.ts
@@ -2614,6 +2614,129 @@ Deno.test({
},
});
+async function httpServerWithErrorBody(
+ listener: Deno.Listener,
+ compression: boolean,
+): Promise<Deno.HttpConn> {
+ const conn = await listener.accept();
+ listener.close();
+ const httpConn = Deno.serveHttp(conn);
+ const e = await httpConn.nextRequest();
+ assert(e);
+ const { respondWith } = e;
+ const originalErr = new Error("boom");
+ const rs = new ReadableStream({
+ async start(controller) {
+ controller.enqueue(new Uint8Array([65]));
+ await delay(1000);
+ controller.error(originalErr);
+ },
+ });
+ const init = compression ? { headers: { "content-type": "text/plain" } } : {};
+ const response = new Response(rs, init);
+ const err = await assertRejects(() => respondWith(response));
+ assert(err === originalErr);
+ return httpConn;
+}
+
+for (const compression of [true, false]) {
+ Deno.test({
+ name: `http server errors stream if response body errors (http/1.1${
+ compression ? " + compression" : ""
+ })`,
+ permissions: { net: true },
+ async fn() {
+ const hostname = "localhost";
+ const port = 4501;
+
+ const listener = Deno.listen({ hostname, port });
+ const server = httpServerWithErrorBody(listener, compression);
+
+ const conn = await Deno.connect({ hostname, port });
+ const msg = new TextEncoder().encode(
+ `GET / HTTP/1.1\r\nHost: ${hostname}:${port}\r\n\r\n`,
+ );
+ const nwritten = await conn.write(msg);
+ assertEquals(nwritten, msg.byteLength);
+
+ const buf = new Uint8Array(1024);
+ const nread = await conn.read(buf);
+ assert(nread);
+ const data = new TextDecoder().decode(buf.subarray(0, nread));
+ assert(data.endsWith("1\r\nA\r\n"));
+ const nread2 = await conn.read(buf); // connection should be closed now because the stream errored
+ assertEquals(nread2, null);
+ conn.close();
+
+ const httpConn = await server;
+ httpConn.close();
+ },
+ });
+
+ Deno.test({
+ name: `http server errors stream if response body errors (http/1.1 + fetch${
+ compression ? " + compression" : ""
+ })`,
+ permissions: { net: true },
+ async fn() {
+ const hostname = "localhost";
+ const port = 4501;
+
+ const listener = Deno.listen({ hostname, port });
+ const server = httpServerWithErrorBody(listener, compression);
+
+ const resp = await fetch(`http://${hostname}:${port}/`);
+ assert(resp.body);
+ const reader = resp.body.getReader();
+ const result = await reader.read();
+ assert(!result.done);
+ assertEquals(result.value, new Uint8Array([65]));
+ const err = await assertRejects(() => reader.read());
+ assert(err instanceof TypeError);
+ assert(err.message.includes("unexpected EOF"));
+
+ const httpConn = await server;
+ httpConn.close();
+ },
+ });
+
+ Deno.test({
+ name: `http server errors stream if response body errors (http/2 + fetch${
+ compression ? " + compression" : ""
+ }))`,
+ permissions: { net: true, read: true },
+ async fn() {
+ const hostname = "localhost";
+ const port = 4501;
+
+ const listener = Deno.listenTls({
+ hostname,
+ port,
+ certFile: "cli/tests/testdata/tls/localhost.crt",
+ keyFile: "cli/tests/testdata/tls/localhost.key",
+ alpnProtocols: ["h2"],
+ });
+ const server = httpServerWithErrorBody(listener, compression);
+
+ const caCert = Deno.readTextFileSync("cli/tests/testdata/tls/RootCA.pem");
+ const client = Deno.createHttpClient({ caCerts: [caCert] });
+ const resp = await fetch(`https://${hostname}:${port}/`, { client });
+ client.close();
+ assert(resp.body);
+ const reader = resp.body.getReader();
+ const result = await reader.read();
+ assert(!result.done);
+ assertEquals(result.value, new Uint8Array([65]));
+ const err = await assertRejects(() => reader.read());
+ assert(err instanceof TypeError);
+ assert(err.message.includes("unexpected internal error encountered"));
+
+ const httpConn = await server;
+ httpConn.close();
+ },
+ });
+}
+
function chunkedBodyReader(h: Headers, r: BufReader): Deno.Reader {
// Based on https://tools.ietf.org/html/rfc2616#section-19.4.6
const tp = new TextProtoReader(r);