summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMarvin Hagemeister <marvin@deno.com>2024-07-09 17:46:10 +0200
committerGitHub <noreply@github.com>2024-07-09 17:46:10 +0200
commit07613a6bf26d9112d47fda9e502425395bd78105 (patch)
tree5ad911d495cfdbff1f6f9cfe63865571ab7e1fa5
parent0425dabb84b41d2b7d3204068ce8a1f4a397bce6 (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.ts58
-rw-r--r--tests/unit_node/http_test.ts89
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>();