Skip to content

Commit

Permalink
feat(node): HTTPS server (denoland#19362)
Browse files Browse the repository at this point in the history
  • Loading branch information
crowlKats committed Jun 13, 2023
1 parent d2c6384 commit b4ae37a
Show file tree
Hide file tree
Showing 3 changed files with 97 additions and 16 deletions.
32 changes: 32 additions & 0 deletions cli/tests/unit_node/http_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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<void>();
const promise2 = deferred<void>();
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();
});
36 changes: 25 additions & 11 deletions ext/node/polyfills/http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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<Deno.HttpConn> = new Set();
#listener?: Deno.Listener;

Expand All @@ -1464,12 +1465,24 @@ class ServerImpl extends EventEmitter {
#servePromise: Deferred<void>;
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);
}
}

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -1536,6 +1549,7 @@ class ServerImpl extends EventEmitter {
this.#addr!.port = port;
this.emit("listening");
},
...this._additionalServeOptions?.(),
},
);
if (this.#unref) {
Expand Down Expand Up @@ -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. */
Expand Down
45 changes: 40 additions & 5 deletions ext/node/polyfills/https.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 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");
}

export class Server {
constructor() {
notImplemented("https.Server.prototype.constructor");
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 {
Expand Down

0 comments on commit b4ae37a

Please sign in to comment.