diff --git a/cli/tests/unit_node/http_test.ts b/cli/tests/unit_node/http_test.ts index e7d743dde3..55160855e1 100644 --- a/cli/tests/unit_node/http_test.ts +++ b/cli/tests/unit_node/http_test.ts @@ -470,13 +470,35 @@ Deno.test("[node/http] server unref", async () => { res.statusCode = status; res.end(""); }); - - // This should let the program to exit without waiting for the + + // This should let the program to exit without waiting for the // server to close. server.unref(); - + server.listen(async () => { }); `); assertEquals(statusCode, 0); }); + +Deno.test("[node/http] ClientRequest handle non-string headers", async () => { + // deno-lint-ignore no-explicit-any + let headers: any; + const def = deferred(); + const req = http.request("http://localhost:4545/echo_server", { + method: "POST", + headers: { 1: 2 }, + }, (resp) => { + headers = resp.headers; + + resp.on("data", () => {}); + + resp.on("end", () => { + def.resolve(); + }); + }); + req.once("error", (e) => def.reject(e)); + req.end(); + await def; + assertEquals(headers!["1"], "2"); +}); diff --git a/ext/node/polyfills/_http_outgoing.ts b/ext/node/polyfills/_http_outgoing.ts index b859d99cad..c4b88ae2fa 100644 --- a/ext/node/polyfills/_http_outgoing.ts +++ b/ext/node/polyfills/_http_outgoing.ts @@ -251,7 +251,8 @@ export class OutgoingMessage extends Stream { this[kOutHeaders] = headers = Object.create(null); } - headers[name.toLowerCase()] = [name, value]; + name = name.toString(); + headers[name.toLowerCase()] = [name, value.toString()]; return this; } @@ -262,6 +263,8 @@ export class OutgoingMessage extends Stream { validateHeaderName(name); validateHeaderValue(name, value); + name = name.toString(); + const field = name.toLowerCase(); const headers = this[kOutHeaders]; if (headers === null || !headers[field]) { @@ -276,10 +279,10 @@ export class OutgoingMessage extends Stream { const existingValues = headers[field][1]; if (Array.isArray(value)) { for (let i = 0, length = value.length; i < length; i++) { - existingValues.push(value[i]); + existingValues.push(value[i].toString()); } } else { - existingValues.push(value); + existingValues.push(value.toString()); } return this; diff --git a/ext/node/polyfills/http.ts b/ext/node/polyfills/http.ts index 101d888f8e..1670ef0b95 100644 --- a/ext/node/polyfills/http.ts +++ b/ext/node/polyfills/http.ts @@ -48,6 +48,7 @@ import { } from "ext:deno_node/internal/errors.ts"; import { getTimerDuration } from "ext:deno_node/internal/timers.mjs"; import { serve, upgradeHttpRaw } from "ext:deno_http/00_serve.js"; +import { createHttpClient } from "ext:deno_fetch/22_http_client.js"; enum STATUS_CODES { /** RFC 7231, 6.2.1 */ @@ -541,13 +542,14 @@ class ClientRequest extends OutgoingMessage { } } - const client = this._getClient(); + const client = this._getClient() ?? createHttpClient({ http2: false }); + this._client = client; const req = core.ops.op_node_http_request( this.method, url, headers, - client, + client.rid, this.method === "POST" || this.method === "PATCH", ); @@ -652,6 +654,10 @@ class ClientRequest extends OutgoingMessage { this.controller.close(); core.opAsync("op_fetch_send", this._req.requestRid).then((res) => { + if (this._timeout) { + this._timeout.onabort = null; + } + this._client.close(); const incoming = new IncomingMessageForClient(this.socket); // TODO(@crowlKats): @@ -665,7 +671,10 @@ class ClientRequest extends OutgoingMessage { incoming.statusCode = res.status; incoming.statusMessage = res.statusText; - incoming._addHeaderLines(res.headers); + incoming._addHeaderLines( + res.headers, + Object.entries(res.headers).flat().length, + ); incoming._bodyRid = res.responseRid; if (this._req.cancelHandleRid !== null) { @@ -793,31 +802,19 @@ class ClientRequest extends OutgoingMessage { }${path}${search}${hash}`; } - setTimeout(timeout: number, callback?: () => void) { - if (timeout == 0) { - // Node's underlying Socket implementation expects a 0 value to disable the - // existing timeout. - if (this.opts.timeout) { - clearTimeout(this.opts.timeout); - this.opts.timeout = undefined; - this.opts.signal = undefined; - } - - return; + setTimeout(msecs: number, callback?: () => void) { + if (this._ended || this._timeout) { + return this; } - const controller = new AbortController(); - this.opts.signal = controller.signal; + msecs = getTimerDuration(msecs, "msecs"); + if (callback) this.once("timeout", callback); - this.opts.timeout = setTimeout(() => { - controller.abort(); + const timeout = AbortSignal.timeout(msecs); + timeout.onabort = () => this.emit("timeout"); + this._timeout = timeout; - this.emit("timeout"); - - if (callback !== undefined) { - callback(); - } - }, timeout); + return this; } _processHeader(headers, key, value, validate) { @@ -860,7 +857,7 @@ function isCookieField(s) { function isContentDispositionField(s) { return s.length === 19 && - StringPrototypeToLowerCase(s) === "content-disposition"; + s.toLowerCase() === "content-disposition"; } const kHeaders = Symbol("kHeaders"); @@ -1111,7 +1108,7 @@ export class IncomingMessageForClient extends NodeReadable { } _addHeaderLineDistinct(field, value, dest) { - field = StringPrototypeToLowerCase(field); + field = field.toLowerCase(); if (!dest[field]) { dest[field] = [value]; } else { @@ -1256,7 +1253,7 @@ function matchKnownFields(field, lowercased) { if (lowercased) { return "\u0000" + field; } - return matchKnownFields(StringPrototypeToLowerCase(field), true); + return matchKnownFields(field.toLowerCase(), true); } function onError(self, error, cb) { diff --git a/ext/node/polyfills/https.ts b/ext/node/polyfills/https.ts index 9261e8c2fb..dfd8f24d9f 100644 --- a/ext/node/polyfills/https.ts +++ b/ext/node/polyfills/https.ts @@ -9,6 +9,7 @@ import { type RequestOptions, } from "ext:deno_node/http.ts"; import { Agent as HttpAgent } from "ext:deno_node/_http_agent.mjs"; +import { createHttpClient } from "ext:deno_fetch/22_http_client.js"; export class Server { constructor() { @@ -80,7 +81,7 @@ class HttpsClientRequest extends ClientRequest { return undefined; } if (caCerts !== undefined) { - return Deno.createHttpClient({ caCerts }); + return createHttpClient({ caCerts, http2: false }); } // const status = await Deno.permissions.query({ // name: "env", @@ -97,13 +98,8 @@ class HttpsClientRequest extends ClientRequest { } const caCert = Deno.readTextFileSync(certFilename); caCerts = [caCert]; - return Deno.createHttpClient({ caCerts }); + return createHttpClient({ caCerts, http2: false }); } - - /*override _createSocket(): Socket { - // deno-lint-ignore no-explicit-any - return { authorized: true } as any; - }*/ } /** Makes a request to an https server. */ diff --git a/test_util/src/lib.rs b/test_util/src/lib.rs index 581abb2b1a..64787b1fa5 100644 --- a/test_util/src/lib.rs +++ b/test_util/src/lib.rs @@ -735,16 +735,7 @@ async fn main_server( *response.status_mut() = StatusCode::from_bytes(status.as_bytes()).unwrap(); } - if let Some(content_type) = parts.headers.get("content-type") { - response - .headers_mut() - .insert("content-type", content_type.clone()); - } - if let Some(user_agent) = parts.headers.get("user-agent") { - response - .headers_mut() - .insert("user-agent", user_agent.clone()); - } + response.headers_mut().extend(parts.headers); Ok(response) } (&hyper::Method::POST, "/echo_multipart_file") => {