diff --git a/cli/tests/unit_node/http_test.ts b/cli/tests/unit_node/http_test.ts index 8f87b1fd27..0d15bf8897 100644 --- a/cli/tests/unit_node/http_test.ts +++ b/cli/tests/unit_node/http_test.ts @@ -6,6 +6,7 @@ import https from "node:https"; import { assert, assertEquals, + fail, } from "../../../test_util/std/testing/asserts.ts"; import { assertSpyCalls, spy } from "../../../test_util/std/testing/mock.ts"; import { deferred } from "../../../test_util/std/async/deferred.ts"; @@ -617,3 +618,34 @@ Deno.test("[node/http] ClientRequest search params", async () => { await def; assertEquals(body, "foo=bar"); }); + +Deno.test("[node/http] HTTPS server", async () => { + const promise = deferred(); + const promise2 = deferred(); + const client = Deno.createHttpClient({ + caCerts: [Deno.readTextFileSync("cli/tests/testdata/tls/RootCA.pem")], + }); + const server = https.createServer({ + cert: Deno.readTextFileSync("cli/tests/testdata/tls/localhost.crt"), + key: Deno.readTextFileSync("cli/tests/testdata/tls/localhost.key"), + }, (_req, res) => { + res.end("success!"); + }); + server.listen(() => { + // deno-lint-ignore no-explicit-any + fetch(`https://localhost:${(server.address() as any).port}`, { + client, + }).then(async (res) => { + assertEquals(res.status, 200); + assertEquals(await res.text(), "success!"); + server.close(); + promise2.resolve(); + }); + }) + .on("error", () => fail()); + server.on("close", () => { + promise.resolve(); + }); + await Promise.all([promise, promise2]); + client.close(); +}); diff --git a/ext/node/polyfills/http.ts b/ext/node/polyfills/http.ts index 697de64149..a207f57ce9 100644 --- a/ext/node/polyfills/http.ts +++ b/ext/node/polyfills/http.ts @@ -18,6 +18,7 @@ import { nextTick } from "ext:deno_node/_next_tick.ts"; import { validateBoolean, validateInteger, + validateObject, validatePort, } from "ext:deno_node/internal/validators.mjs"; import { @@ -1443,16 +1444,16 @@ export class IncomingMessageForServer extends NodeReadable { } } -type ServerHandler = ( +export type ServerHandler = ( req: IncomingMessageForServer, res: ServerResponse, ) => void; -export function Server(handler?: ServerHandler): ServerImpl { - return new ServerImpl(handler); +export function Server(opts, requestListener?: ServerHandler): ServerImpl { + return new ServerImpl(opts, requestListener); } -class ServerImpl extends EventEmitter { +export class ServerImpl extends EventEmitter { #httpConnections: Set = new Set(); #listener?: Deno.Listener; @@ -1464,12 +1465,24 @@ class ServerImpl extends EventEmitter { #servePromise: Deferred; listening = false; - constructor(handler?: ServerHandler) { + constructor(opts, requestListener?: ServerHandler) { super(); + + if (typeof opts === "function") { + requestListener = opts; + opts = kEmptyObject; + } else if (opts == null) { + opts = kEmptyObject; + } else { + validateObject(opts, "options"); + } + + this._opts = opts; + this.#servePromise = deferred(); this.#servePromise.then(() => this.emit("close")); - if (handler !== undefined) { - this.on("request", handler); + if (requestListener !== undefined) { + this.on("request", requestListener); } } @@ -1498,12 +1511,12 @@ class ServerImpl extends EventEmitter { port, } as Deno.NetAddr; this.listening = true; - nextTick(() => this.#serve()); + nextTick(() => this._serve()); return this; } - #serve() { + _serve() { const ac = new AbortController(); const handler = (request: Request, info: Deno.ServeHandlerInfo) => { const req = new IncomingMessageForServer(request, info.remoteAddr); @@ -1536,6 +1549,7 @@ class ServerImpl extends EventEmitter { this.#addr!.port = port; this.emit("listening"); }, + ...this._additionalServeOptions?.(), }, ); if (this.#unref) { @@ -1598,8 +1612,8 @@ class ServerImpl extends EventEmitter { Server.prototype = ServerImpl.prototype; -export function createServer(handler?: ServerHandler) { - return Server(handler); +export function createServer(opts, requestListener?: ServerHandler) { + return Server(opts, requestListener); } /** Makes an HTTP request. */ diff --git a/ext/node/polyfills/https.ts b/ext/node/polyfills/https.ts index dfd8f24d9f..b0b8004169 100644 --- a/ext/node/polyfills/https.ts +++ b/ext/node/polyfills/https.ts @@ -10,14 +10,49 @@ import { } 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"; +import { + type ServerHandler, + ServerImpl as HttpServer, +} from "ext:deno_node/http.ts"; +import { validateObject } from "ext:deno_node/internal/validators.mjs"; +import { kEmptyObject } from "ext:deno_node/internal/util.mjs"; +import { Buffer } from "ext:deno_node/buffer.ts"; -export class Server { - constructor() { - notImplemented("https.Server.prototype.constructor"); +export class Server extends HttpServer { + constructor(opts, requestListener?: ServerHandler) { + if (typeof opts === "function") { + requestListener = opts; + opts = kEmptyObject; + } else if (opts == null) { + opts = kEmptyObject; + } else { + validateObject(opts, "options"); + } + + if (opts.cert && Array.isArray(opts.cert)) { + notImplemented("https.Server.opts.cert array type"); + } + + if (opts.key && Array.isArray(opts.key)) { + notImplemented("https.Server.opts.key array type"); + } + + super(opts, requestListener); + } + + _additionalServeOptions() { + return { + cert: this._opts.cert instanceof Buffer + ? this._opts.cert.toString() + : this._opts.cert, + key: this._opts.key instanceof Buffer + ? this._opts.key.toString() + : this._opts.key, + }; } } -export function createServer() { - notImplemented("https.createServer"); +export function createServer(opts, requestListener?: ServerHandler) { + return new Server(opts, requestListener); } interface HttpsRequestOptions extends RequestOptions {