diff options
author | Marvin Hagemeister <marvin@deno.com> | 2024-07-09 17:46:10 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-07-09 17:46:10 +0200 |
commit | 07613a6bf26d9112d47fda9e502425395bd78105 (patch) | |
tree | 5ad911d495cfdbff1f6f9cfe63865571ab7e1fa5 | |
parent | 0425dabb84b41d2b7d3204068ce8a1f4a397bce6 (diff) |
fix(node/http): support all `.writeHead()` signatures (#24469)
Implement the missing `.writeHead()` signatures from Node's
`ServerResponse` class that we didn't support.
Fixes https://github.com/denoland/deno/issues/24468
-rw-r--r-- | ext/node/polyfills/http.ts | 58 | ||||
-rw-r--r-- | tests/unit_node/http_test.ts | 89 |
2 files changed, 140 insertions, 7 deletions
diff --git a/ext/node/polyfills/http.ts b/ext/node/polyfills/http.ts index 534bad908..3059da3a6 100644 --- a/ext/node/polyfills/http.ts +++ b/ext/node/polyfills/http.ts @@ -3,7 +3,7 @@ // TODO(petamoriken): enable prefer-primordials for node polyfills // deno-lint-ignore-file prefer-primordials -import { core } from "ext:core/mod.js"; +import { core, primordials } from "ext:core/mod.js"; import { op_fetch_response_upgrade, op_fetch_send, @@ -68,6 +68,7 @@ import { resourceForReadableStream } from "ext:deno_web/06_streams.js"; import { TcpConn } from "ext:deno_net/01_net.js"; const { internalRidSymbol } = core; +const { ArrayIsArray } = primordials; enum STATUS_CODES { /** RFC 7231, 6.2.1 */ @@ -1458,20 +1459,65 @@ export class ServerResponse extends NodeWritable { getHeaderNames() { return Object.keys(this.#headers); } - getHeaders() { + getHeaders(): Record<string, string | number | string[]> { + // @ts-ignore Ignore null __proto__ return { __proto__: null, ...this.#headers }; } hasHeader(name: string) { return Object.hasOwn(this.#headers, name); } - writeHead(status: number, headers: Record<string, string> = {}) { + writeHead( + status: number, + statusMessage?: string, + headers?: + | Record<string, string | number | string[]> + | Array<[string, string]>, + ): this; + writeHead( + status: number, + headers?: + | Record<string, string | number | string[]> + | Array<[string, string]>, + ): this; + writeHead( + status: number, + statusMessageOrHeaders?: + | string + | Record<string, string | number | string[]> + | Array<[string, string]>, + maybeHeaders?: + | Record<string, string | number | string[]> + | Array<[string, string]>, + ): this { this.statusCode = status; - for (const k in headers) { - if (Object.hasOwn(headers, k)) { - this.setHeader(k, headers[k]); + + let headers = null; + if (typeof statusMessageOrHeaders === "string") { + this.statusMessage = statusMessageOrHeaders; + if (maybeHeaders !== undefined) { + headers = maybeHeaders; } + } else if (statusMessageOrHeaders !== undefined) { + headers = statusMessageOrHeaders; } + + if (headers !== null) { + if (ArrayIsArray(headers)) { + headers = headers as Array<[string, string]>; + for (let i = 0; i < headers.length; i++) { + this.appendHeader(headers[i][0], headers[i][1]); + } + } else { + headers = headers as Record<string, string>; + for (const k in headers) { + if (Object.hasOwn(headers, k)) { + this.setHeader(k, headers[k]); + } + } + } + } + return this; } diff --git a/tests/unit_node/http_test.ts b/tests/unit_node/http_test.ts index af88d5f9c..a053f3a27 100644 --- a/tests/unit_node/http_test.ts +++ b/tests/unit_node/http_test.ts @@ -1,7 +1,7 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. import EventEmitter from "node:events"; -import http, { type RequestOptions } from "node:http"; +import http, { type RequestOptions, type ServerResponse } from "node:http"; import url from "node:url"; import https from "node:https"; import net from "node:net"; @@ -142,6 +142,93 @@ Deno.test("[node/http] chunked response", async () => { } }); +Deno.test("[node/http] .writeHead()", async (t) => { + async function testWriteHead( + onRequest: (res: ServerResponse) => void, + onResponse: (res: Response) => void, + ) { + const { promise, resolve } = Promise.withResolvers<void>(); + const server = http.createServer((_req, res) => { + onRequest(res); + res.end(); + }); + server.listen(async () => { + const res = await fetch( + // deno-lint-ignore no-explicit-any + `http://127.0.0.1:${(server.address() as any).port}/`, + ); + await res.body?.cancel(); + + onResponse(res); + + server.close(() => resolve()); + }); + + await promise; + } + + await t.step("send status code", async () => { + await testWriteHead( + (res) => res.writeHead(404), + (res) => { + assertEquals(res.status, 404); + }, + ); + }); + + // TODO(@marvinhagemeister): hyper doesn't support custom status text + // await t.step("send status + custom status text", async () => { + // await testWriteHead( + // (res) => res.writeHead(404, "some text"), + // (res) => { + // assertEquals(res.status, 404); + // assertEquals(res.statusText, "some text"); + // }, + // ); + // }); + + await t.step("send status + custom status text + headers obj", async () => { + await testWriteHead( + (res) => res.writeHead(404, "some text", { foo: "bar" }), + (res) => { + assertEquals(res.status, 404); + // TODO(@marvinhagemeister): hyper doesn't support custom + // status text + // assertEquals(res.statusText, "some text"); + assertEquals(res.headers.get("foo"), "bar"); + }, + ); + }); + + await t.step("send status + headers obj", async () => { + await testWriteHead( + (res) => { + res.writeHead(200, { + foo: "bar", + bar: ["foo1", "foo2"], + foobar: 1, + }); + }, + (res) => { + assertEquals(res.status, 200); + assertEquals(res.headers.get("foo"), "bar"); + assertEquals(res.headers.get("bar"), "foo1, foo2"); + assertEquals(res.headers.get("foobar"), "1"); + }, + ); + }); + + await t.step("send status + headers array", async () => { + await testWriteHead( + (res) => res.writeHead(200, [["foo", "bar"]]), + (res) => { + assertEquals(res.status, 200); + assertEquals(res.headers.get("foo"), "bar"); + }, + ); + }); +}); + // Test empty chunks: https://github.com/denoland/deno/issues/17194 Deno.test("[node/http] empty chunk in the middle of response", async () => { const { promise, resolve } = Promise.withResolvers<void>(); |