summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLuca Casonato <hello@lcas.dev>2024-08-01 15:46:05 +0200
committerGitHub <noreply@github.com>2024-08-01 15:46:05 +0200
commit4eda9e64e9fd6075b74742ae877281ee4a7ab155 (patch)
tree93ad39f30c6f4f0cecb842f0dd214b1347ef548d
parentc79cb339ef74e671c39f4bb13879b281eadd9aaa (diff)
fix(ext/http): correctly consume response body in `Deno.serve` (#24811)
Prior to this commit, you could return a `Response` created from a string or Uint8Array multiple times. Now you can't do that anymore.
-rw-r--r--ext/http/00_serve.ts11
-rw-r--r--tests/unit/serve_test.ts45
2 files changed, 56 insertions, 0 deletions
diff --git a/ext/http/00_serve.ts b/ext/http/00_serve.ts
index a58d19d76..8ed1a1d04 100644
--- a/ext/http/00_serve.ts
+++ b/ext/http/00_serve.ts
@@ -436,6 +436,11 @@ function fastSyncResponseOrStream(
const stream = respBody.streamOrStatic;
const body = stream.body;
+ if (body !== undefined) {
+ // We ensure the response has not been consumed yet in the caller of this
+ // function.
+ stream.consumed = true;
+ }
if (TypedArrayPrototypeGetSymbolToStringTag(body) === "Uint8Array") {
innerRequest?.close();
@@ -505,6 +510,12 @@ function mapToCallback(context, callback, onError) {
"Return value from serve handler must be a response or a promise resolving to a response",
);
}
+
+ if (response.bodyUsed) {
+ throw TypeError(
+ "The body of the Response returned from the serve handler has already been consumed.",
+ );
+ }
} catch (error) {
try {
response = await onError(error);
diff --git a/tests/unit/serve_test.ts b/tests/unit/serve_test.ts
index 450ab6d93..353621154 100644
--- a/tests/unit/serve_test.ts
+++ b/tests/unit/serve_test.ts
@@ -631,6 +631,51 @@ Deno.test(
},
);
+Deno.test(
+ { permissions: { net: true } },
+ async function httpServerMultipleResponseBodyConsume() {
+ const ac = new AbortController();
+ const { promise, resolve } = Promise.withResolvers<void>();
+ const response = new Response("Hello World");
+ let hadError = false;
+ const server = Deno.serve({
+ handler: () => {
+ return response;
+ },
+ port: servePort,
+ signal: ac.signal,
+ onListen: onListen(resolve),
+ onError: () => {
+ hadError = true;
+ return new Response("Internal Server Error", { status: 500 });
+ },
+ });
+
+ await promise;
+ assert(!response.bodyUsed);
+
+ const resp = await fetch(`http://127.0.0.1:${servePort}/`, {
+ headers: { "connection": "close" },
+ });
+ assertEquals(resp.status, 200);
+ const text = await resp.text();
+ assertEquals(text, "Hello World");
+ assert(response.bodyUsed);
+
+ const resp2 = await fetch(`http://127.0.0.1:${servePort}/`, {
+ headers: { "connection": "close" },
+ });
+ assertEquals(resp2.status, 500);
+ const text2 = await resp2.text();
+ assertEquals(text2, "Internal Server Error");
+ assert(hadError);
+ assert(response.bodyUsed);
+
+ ac.abort();
+ await server.finished;
+ },
+);
+
Deno.test({ permissions: { net: true } }, async function httpServerOverload1() {
const ac = new AbortController();
const deferred = Promise.withResolvers<void>();