diff options
author | Luca Casonato <hello@lcas.dev> | 2024-08-01 15:46:05 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-08-01 15:46:05 +0200 |
commit | 4eda9e64e9fd6075b74742ae877281ee4a7ab155 (patch) | |
tree | 93ad39f30c6f4f0cecb842f0dd214b1347ef548d | |
parent | c79cb339ef74e671c39f4bb13879b281eadd9aaa (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.ts | 11 | ||||
-rw-r--r-- | tests/unit/serve_test.ts | 45 |
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>(); |