mirror of
https://github.com/denoland/std.git
synced 2024-11-22 04:59:05 +00:00
1611 lines
43 KiB
TypeScript
1611 lines
43 KiB
TypeScript
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
|
import { ConnInfo, serve, serveListener, Server, serveTls } from "./server.ts";
|
|
import { mockConn as createMockConn } from "./_mock_conn.ts";
|
|
import { dirname, fromFileUrl, join, resolve } from "../path/mod.ts";
|
|
import { writeAll } from "../streams/write_all.ts";
|
|
import { readAll } from "../streams/read_all.ts";
|
|
import { deferred, delay } from "../async/mod.ts";
|
|
import {
|
|
assert,
|
|
assertEquals,
|
|
assertNotEquals,
|
|
assertRejects,
|
|
assertStrictEquals,
|
|
assertThrows,
|
|
unreachable,
|
|
} from "../testing/asserts.ts";
|
|
|
|
const moduleDir = dirname(fromFileUrl(import.meta.url));
|
|
const testdataDir = resolve(moduleDir, "testdata");
|
|
|
|
type AcceptCallSideEffect = ({
|
|
acceptCallCount,
|
|
}: {
|
|
acceptCallCount: number;
|
|
}) => void | Promise<void>;
|
|
|
|
class MockListener implements Deno.Listener {
|
|
conn: Deno.Conn;
|
|
#closed = false;
|
|
#rejectionError?: Error;
|
|
#rejectionCount: number;
|
|
#acceptCallSideEffect: AcceptCallSideEffect;
|
|
acceptCallTimes: number[] = [];
|
|
acceptCallIntervals: number[] = [];
|
|
acceptCallCount = 0;
|
|
|
|
constructor({
|
|
conn,
|
|
rejectionError,
|
|
rejectionCount = Infinity,
|
|
acceptCallSideEffect = () => {},
|
|
}: {
|
|
conn: Deno.Conn;
|
|
rejectionError?: Error;
|
|
rejectionCount?: number;
|
|
acceptCallSideEffect?: AcceptCallSideEffect;
|
|
}) {
|
|
this.conn = conn;
|
|
this.#rejectionError = rejectionError;
|
|
this.#rejectionCount = rejectionCount;
|
|
this.#acceptCallSideEffect = acceptCallSideEffect;
|
|
}
|
|
|
|
get addr(): Deno.Addr {
|
|
return this.conn.localAddr;
|
|
}
|
|
|
|
get rid(): number {
|
|
return 4505;
|
|
}
|
|
|
|
#shouldReject(): boolean {
|
|
return (
|
|
typeof this.#rejectionError !== "undefined" &&
|
|
this.acceptCallCount <= this.#rejectionCount
|
|
);
|
|
}
|
|
|
|
async accept(): Promise<Deno.Conn> {
|
|
if (this.#closed) {
|
|
throw new Deno.errors.BadResource("MockListener has closed");
|
|
}
|
|
|
|
const now = performance.now();
|
|
this.acceptCallIntervals.push(now - (this.acceptCallTimes.at(-1) ?? now));
|
|
this.acceptCallTimes.push(now);
|
|
this.acceptCallCount++;
|
|
this.#acceptCallSideEffect({ acceptCallCount: this.acceptCallCount });
|
|
|
|
await delay(0);
|
|
|
|
return this.#shouldReject()
|
|
? Promise.reject(this.#rejectionError)
|
|
: Promise.resolve(this.conn);
|
|
}
|
|
|
|
close() {
|
|
this.#closed = true;
|
|
}
|
|
|
|
async *[Symbol.asyncIterator](): AsyncIterableIterator<Deno.Conn> {
|
|
while (true) {
|
|
if (this.#closed) {
|
|
break;
|
|
}
|
|
|
|
const now = performance.now();
|
|
this.acceptCallIntervals.push(now - (this.acceptCallTimes.at(-1) ?? now));
|
|
this.acceptCallTimes.push(now);
|
|
this.acceptCallCount++;
|
|
this.#acceptCallSideEffect({ acceptCallCount: this.acceptCallCount });
|
|
|
|
await delay(0);
|
|
|
|
if (this.#shouldReject()) {
|
|
throw this.#rejectionError;
|
|
}
|
|
|
|
yield this.conn;
|
|
}
|
|
}
|
|
|
|
ref() {
|
|
}
|
|
|
|
unref() {
|
|
}
|
|
}
|
|
|
|
Deno.test(
|
|
"Server.addrs should expose the addresses the server is listening on",
|
|
async () => {
|
|
const listenerOneOptions = {
|
|
hostname: "127.0.0.1",
|
|
port: 4505,
|
|
};
|
|
const listenerTwoOptions = {
|
|
hostname: "127.0.0.1",
|
|
port: 8080,
|
|
};
|
|
const listenerOne = Deno.listen(listenerOneOptions);
|
|
const listenerTwo = Deno.listen(listenerTwoOptions);
|
|
|
|
const addrHostname = "0.0.0.0";
|
|
const addrPort = 3000;
|
|
const handler = () => new Response();
|
|
|
|
const server = new Server({
|
|
port: addrPort,
|
|
hostname: addrHostname,
|
|
handler,
|
|
});
|
|
const servePromiseOne = server.serve(listenerOne);
|
|
const servePromiseTwo = server.serve(listenerTwo);
|
|
const servePromiseThree = server.listenAndServe();
|
|
|
|
try {
|
|
assertEquals(server.addrs.length, 3);
|
|
assertEquals(server.addrs[0].transport, "tcp");
|
|
assertEquals(
|
|
(server.addrs[0] as Deno.NetAddr).hostname,
|
|
listenerOneOptions.hostname,
|
|
);
|
|
assertEquals(
|
|
(server.addrs[0] as Deno.NetAddr).port,
|
|
listenerOneOptions.port,
|
|
);
|
|
assertEquals(server.addrs[1].transport, "tcp");
|
|
assertEquals(
|
|
(server.addrs[1] as Deno.NetAddr).hostname,
|
|
listenerTwoOptions.hostname,
|
|
);
|
|
assertEquals(
|
|
(server.addrs[1] as Deno.NetAddr).port,
|
|
listenerTwoOptions.port,
|
|
);
|
|
assertEquals(server.addrs[2].transport, "tcp");
|
|
assertEquals((server.addrs[2] as Deno.NetAddr).hostname, addrHostname);
|
|
assertEquals((server.addrs[2] as Deno.NetAddr).port, addrPort);
|
|
} finally {
|
|
server.close();
|
|
await servePromiseOne;
|
|
await servePromiseTwo;
|
|
await servePromiseThree;
|
|
}
|
|
},
|
|
);
|
|
|
|
Deno.test("Server.closed should expose whether it is closed", () => {
|
|
const handler = () => new Response();
|
|
const server = new Server({ handler });
|
|
try {
|
|
assertEquals(server.closed, false);
|
|
} finally {
|
|
server.close();
|
|
assertEquals(server.closed, true);
|
|
}
|
|
});
|
|
|
|
Deno.test(
|
|
"Server.close should throw an error if the server is already closed",
|
|
() => {
|
|
const handler = () => new Response();
|
|
const server = new Server({ handler });
|
|
server.close();
|
|
|
|
assertThrows(() => server.close(), Deno.errors.Http, "Server closed");
|
|
},
|
|
);
|
|
|
|
Deno.test(
|
|
"Server.serve should throw an error if the server is already closed",
|
|
async () => {
|
|
const handler = () => new Response();
|
|
const server = new Server({ handler });
|
|
server.close();
|
|
|
|
const listenOptions = {
|
|
hostname: "localhost",
|
|
port: 4505,
|
|
};
|
|
const listener = Deno.listen(listenOptions);
|
|
|
|
await assertRejects(
|
|
() => server.serve(listener),
|
|
Deno.errors.Http,
|
|
"Server closed",
|
|
);
|
|
|
|
try {
|
|
listener.close();
|
|
} catch (error) {
|
|
if (!(error instanceof Deno.errors.BadResource)) {
|
|
throw error;
|
|
}
|
|
}
|
|
},
|
|
);
|
|
|
|
Deno.test(
|
|
"Server.listenAndServe should throw an error if the server is already closed",
|
|
async () => {
|
|
const handler = () => new Response();
|
|
const server = new Server({ handler });
|
|
server.close();
|
|
|
|
await assertRejects(
|
|
() => server.listenAndServe(),
|
|
Deno.errors.Http,
|
|
"Server closed",
|
|
);
|
|
},
|
|
);
|
|
|
|
Deno.test(
|
|
"Server.listenAndServeTls should throw an error if the server is already closed",
|
|
async () => {
|
|
const handler = () => new Response();
|
|
const server = new Server({ handler });
|
|
server.close();
|
|
|
|
const certFile = join(testdataDir, "tls/localhost.crt");
|
|
const keyFile = join(testdataDir, "tls/localhost.key");
|
|
|
|
await assertRejects(
|
|
() => server.listenAndServeTls(certFile, keyFile),
|
|
Deno.errors.Http,
|
|
"Server closed",
|
|
);
|
|
},
|
|
);
|
|
|
|
Deno.test(
|
|
"serveListener should not overwrite an abort signal handler",
|
|
async () => {
|
|
const listenOptions = {
|
|
hostname: "localhost",
|
|
port: 4505,
|
|
};
|
|
const listener = Deno.listen(listenOptions);
|
|
const handler = () => new Response();
|
|
const onAbort = () => {};
|
|
const abortController = new AbortController();
|
|
|
|
abortController.signal.onabort = onAbort;
|
|
|
|
const servePromise = serveListener(listener, handler, {
|
|
signal: abortController.signal,
|
|
});
|
|
|
|
try {
|
|
assertStrictEquals(abortController.signal.onabort, onAbort);
|
|
} finally {
|
|
abortController.abort();
|
|
await servePromise;
|
|
}
|
|
},
|
|
);
|
|
|
|
Deno.test(
|
|
"serve should not overwrite an abort signal handler",
|
|
async () => {
|
|
const handler = () => new Response();
|
|
const onAbort = () => {};
|
|
const abortController = new AbortController();
|
|
|
|
abortController.signal.onabort = onAbort;
|
|
|
|
const servePromise = serve(handler, {
|
|
hostname: "localhost",
|
|
port: 4505,
|
|
signal: abortController.signal,
|
|
});
|
|
|
|
try {
|
|
assertStrictEquals(abortController.signal.onabort, onAbort);
|
|
} finally {
|
|
abortController.abort();
|
|
await servePromise;
|
|
}
|
|
},
|
|
);
|
|
|
|
Deno.test(
|
|
"serveTls should not overwrite an abort signal handler",
|
|
async () => {
|
|
const certFile = join(testdataDir, "tls/localhost.crt");
|
|
const keyFile = join(testdataDir, "tls/localhost.key");
|
|
const handler = () => new Response();
|
|
const onAbort = () => {};
|
|
const abortController = new AbortController();
|
|
|
|
abortController.signal.onabort = onAbort;
|
|
|
|
const servePromise = serveTls(handler, {
|
|
hostname: "localhost",
|
|
port: 4505,
|
|
certFile,
|
|
keyFile,
|
|
signal: abortController.signal,
|
|
});
|
|
|
|
try {
|
|
assertStrictEquals(abortController.signal.onabort, onAbort);
|
|
} finally {
|
|
abortController.abort();
|
|
await servePromise;
|
|
}
|
|
},
|
|
);
|
|
|
|
Deno.test(
|
|
"serveListener should not throw if abort when the server is already closed",
|
|
async () => {
|
|
const listenOptions = {
|
|
hostname: "localhost",
|
|
port: 4505,
|
|
};
|
|
const listener = Deno.listen(listenOptions);
|
|
const handler = () => new Response();
|
|
const abortController = new AbortController();
|
|
|
|
const servePromise = serveListener(listener, handler, {
|
|
signal: abortController.signal,
|
|
});
|
|
|
|
abortController.abort();
|
|
|
|
try {
|
|
abortController.abort();
|
|
} finally {
|
|
await servePromise;
|
|
}
|
|
},
|
|
);
|
|
|
|
Deno.test(
|
|
"serve should not throw if abort when the server is already closed",
|
|
async () => {
|
|
const handler = () => new Response();
|
|
const abortController = new AbortController();
|
|
|
|
const servePromise = serve(handler, {
|
|
hostname: "localhost",
|
|
port: 4505,
|
|
signal: abortController.signal,
|
|
});
|
|
|
|
abortController.abort();
|
|
|
|
try {
|
|
abortController.abort();
|
|
} finally {
|
|
await servePromise;
|
|
}
|
|
},
|
|
);
|
|
|
|
Deno.test(
|
|
"serveTls should not throw if abort when the server is already closed",
|
|
async () => {
|
|
const certFile = join(testdataDir, "tls/localhost.crt");
|
|
const keyFile = join(testdataDir, "tls/localhost.key");
|
|
const handler = () => new Response();
|
|
const abortController = new AbortController();
|
|
|
|
const servePromise = serveTls(handler, {
|
|
hostname: "localhost",
|
|
port: 4505,
|
|
certFile,
|
|
keyFile,
|
|
signal: abortController.signal,
|
|
});
|
|
|
|
abortController.abort();
|
|
|
|
try {
|
|
abortController.abort();
|
|
} finally {
|
|
await servePromise;
|
|
}
|
|
},
|
|
);
|
|
|
|
Deno.test(`Server.serve should response with internal server error if response body is already consumed`, async () => {
|
|
const listenOptions = {
|
|
hostname: "localhost",
|
|
port: 4505,
|
|
};
|
|
const listener = Deno.listen(listenOptions);
|
|
|
|
const url = `http://${listenOptions.hostname}:${listenOptions.port}`;
|
|
const body = "Internal Server Error";
|
|
const status = 500;
|
|
|
|
async function handler() {
|
|
const response = new Response("Hello, world!");
|
|
await response.text();
|
|
return response;
|
|
}
|
|
|
|
const server = new Server({ handler });
|
|
const servePromise = server.serve(listener);
|
|
|
|
try {
|
|
const response = await fetch(url);
|
|
assertEquals(await response.text(), body);
|
|
assertEquals(response.status, status);
|
|
} finally {
|
|
server.close();
|
|
await servePromise;
|
|
}
|
|
});
|
|
|
|
Deno.test(`Server.serve should handle requests`, async () => {
|
|
const listenOptions = {
|
|
hostname: "localhost",
|
|
port: 4505,
|
|
};
|
|
const listener = Deno.listen(listenOptions);
|
|
|
|
const url = `http://${listenOptions.hostname}:${listenOptions.port}`;
|
|
const status = 418;
|
|
const method = "GET";
|
|
const body = `${method}: ${url} - Hello Deno on HTTP!`;
|
|
|
|
const handler = () => new Response(body, { status });
|
|
|
|
const server = new Server({ handler });
|
|
const servePromise = server.serve(listener);
|
|
|
|
try {
|
|
const response = await fetch(url, { method });
|
|
assertEquals(await response.text(), body);
|
|
assertEquals(response.status, status);
|
|
} finally {
|
|
server.close();
|
|
await servePromise;
|
|
}
|
|
});
|
|
|
|
Deno.test(`Server.listenAndServe should handle requests`, async () => {
|
|
const hostname = "localhost";
|
|
const port = 4505;
|
|
const url = `http://${hostname}:${port}`;
|
|
const status = 418;
|
|
const method = "POST";
|
|
const body = `${method}: ${url} - Hello Deno on HTTP!`;
|
|
|
|
const handler = () => new Response(body, { status });
|
|
|
|
const server = new Server({ hostname, port, handler });
|
|
const servePromise = server.listenAndServe();
|
|
|
|
try {
|
|
const response = await fetch(url, { method });
|
|
assertEquals(await response.text(), body);
|
|
assertEquals(response.status, status);
|
|
} finally {
|
|
server.close();
|
|
await servePromise;
|
|
}
|
|
});
|
|
|
|
Deno.test({
|
|
// PermissionDenied: Permission denied (os error 13)
|
|
// Will pass if run as root user.
|
|
ignore: true,
|
|
name: `Server.listenAndServe should handle requests on the default HTTP port`,
|
|
fn: async () => {
|
|
const addr = "localhost";
|
|
const url = `http://${addr}`;
|
|
const status = 418;
|
|
const method = "PATCH";
|
|
const body = `${method}: ${url} - Hello Deno on HTTP!`;
|
|
|
|
const handler = () => new Response(body, { status });
|
|
|
|
const server = new Server({ hostname: addr, handler });
|
|
const servePromise = server.listenAndServe();
|
|
|
|
try {
|
|
const response = await fetch(url, { method });
|
|
assertEquals(await response.text(), body);
|
|
assertEquals(response.status, status);
|
|
} finally {
|
|
server.close();
|
|
await servePromise;
|
|
}
|
|
},
|
|
});
|
|
|
|
Deno.test(`Server.listenAndServeTls should handle requests`, async () => {
|
|
const hostname = "localhost";
|
|
const port = 4505;
|
|
const addr = `${hostname}:${port}`;
|
|
const certFile = join(testdataDir, "tls/localhost.crt");
|
|
const keyFile = join(testdataDir, "tls/localhost.key");
|
|
const url = `http://${addr}`;
|
|
const status = 418;
|
|
const method = "DELETE";
|
|
const body = `${method}: ${url} - Hello Deno on HTTPS!`;
|
|
|
|
const handler = () => new Response(body, { status });
|
|
|
|
const server = new Server({ hostname, port, handler });
|
|
const servePromise = server.listenAndServeTls(certFile, keyFile);
|
|
|
|
try {
|
|
// Invalid certificate, connection should throw on first read or write
|
|
// but should not crash the server.
|
|
const badConn = await Deno.connectTls({
|
|
hostname,
|
|
port,
|
|
// missing certFile
|
|
});
|
|
|
|
await assertRejects(
|
|
() => badConn.read(new Uint8Array(1)),
|
|
Deno.errors.InvalidData,
|
|
"invalid peer certificate contents: invalid peer certificate: UnknownIssuer",
|
|
"Read with missing certFile didn't throw an InvalidData error when it should have.",
|
|
);
|
|
|
|
badConn.close();
|
|
|
|
// Valid request after invalid
|
|
const conn = await Deno.connectTls({
|
|
hostname,
|
|
port,
|
|
certFile: join(testdataDir, "tls/RootCA.pem"),
|
|
});
|
|
|
|
await writeAll(
|
|
conn,
|
|
new TextEncoder().encode(`${method.toUpperCase()} / HTTP/1.0\r\n\r\n`),
|
|
);
|
|
|
|
const response = new TextDecoder().decode(await readAll(conn));
|
|
|
|
conn.close();
|
|
|
|
assert(response.includes(`HTTP/1.0 ${status}`), "Status code not correct");
|
|
assert(response.includes(body), "Response body not correct");
|
|
} finally {
|
|
server.close();
|
|
await servePromise;
|
|
}
|
|
});
|
|
|
|
Deno.test({
|
|
// PermissionDenied: Permission denied (os error 13)
|
|
// Will pass if run as root user.
|
|
ignore: true,
|
|
name:
|
|
`Server.listenAndServeTls should handle requests on the default HTTPS port`,
|
|
fn: async () => {
|
|
const hostname = "localhost";
|
|
const port = 443;
|
|
const addr = hostname;
|
|
const certFile = join(testdataDir, "tls/localhost.crt");
|
|
const keyFile = join(testdataDir, "tls/localhost.key");
|
|
const url = `http://${addr}`;
|
|
const status = 418;
|
|
const method = "PUT";
|
|
const body = `${method}: ${url} - Hello Deno on HTTPS!`;
|
|
|
|
const handler = () => new Response(body, { status });
|
|
|
|
const server = new Server({ hostname, port, handler });
|
|
const servePromise = server.listenAndServeTls(certFile, keyFile);
|
|
|
|
try {
|
|
// Invalid certificate, connection should throw on first read or write
|
|
// but should not crash the server.
|
|
const badConn = await Deno.connectTls({
|
|
hostname,
|
|
port,
|
|
// missing certFile
|
|
});
|
|
|
|
await assertRejects(
|
|
() => badConn.read(new Uint8Array(1)),
|
|
Deno.errors.InvalidData,
|
|
"invalid peer certificate contents: invalid peer certificate: UnknownIssuer",
|
|
"Read with missing certFile didn't throw an InvalidData error when it should have.",
|
|
);
|
|
|
|
badConn.close();
|
|
|
|
// Valid request after invalid
|
|
const conn = await Deno.connectTls({
|
|
hostname,
|
|
port,
|
|
certFile: join(testdataDir, "tls/RootCA.pem"),
|
|
});
|
|
|
|
await writeAll(
|
|
conn,
|
|
new TextEncoder().encode(`${method.toUpperCase()} / HTTP/1.0\r\n\r\n`),
|
|
);
|
|
|
|
const response = new TextDecoder().decode(await readAll(conn));
|
|
|
|
conn.close();
|
|
|
|
assert(
|
|
response.includes(`HTTP/1.0 ${status}`),
|
|
"Status code not correct",
|
|
);
|
|
assert(response.includes(body), "Response body not correct");
|
|
} finally {
|
|
server.close();
|
|
await servePromise;
|
|
}
|
|
},
|
|
});
|
|
|
|
Deno.test(`serve should handle requests`, async () => {
|
|
const listenOptions = {
|
|
hostname: "localhost",
|
|
port: 4505,
|
|
};
|
|
const listener = Deno.listen(listenOptions);
|
|
|
|
const url = `http://${listenOptions.hostname}:${listenOptions.port}`;
|
|
const status = 418;
|
|
const method = "GET";
|
|
const body = `${method}: ${url} - Hello Deno on HTTP!`;
|
|
|
|
const handler = () => new Response(body, { status });
|
|
const abortController = new AbortController();
|
|
|
|
const servePromise = serveListener(listener, handler, {
|
|
signal: abortController.signal,
|
|
});
|
|
|
|
try {
|
|
const response = await fetch(url, { method });
|
|
assertEquals(await response.text(), body);
|
|
assertEquals(response.status, status);
|
|
} finally {
|
|
abortController.abort();
|
|
await servePromise;
|
|
}
|
|
});
|
|
|
|
Deno.test(`serve should handle requests`, async () => {
|
|
const hostname = "localhost";
|
|
const port = 4505;
|
|
const url = `http://${hostname}:${port}`;
|
|
const status = 418;
|
|
const method = "POST";
|
|
const body = `${method}: ${url} - Hello Deno on HTTP!`;
|
|
|
|
const handler = () => new Response(body, { status });
|
|
const abortController = new AbortController();
|
|
|
|
const servePromise = serve(handler, {
|
|
hostname,
|
|
port,
|
|
signal: abortController.signal,
|
|
});
|
|
|
|
try {
|
|
const response = await fetch(url, { method });
|
|
assertEquals(await response.text(), body);
|
|
assertEquals(response.status, status);
|
|
} finally {
|
|
abortController.abort();
|
|
await servePromise;
|
|
}
|
|
});
|
|
|
|
Deno.test(`serve listens on the port 8000 by default`, async () => {
|
|
const url = "http://localhost:8000";
|
|
const body = "Hello from port 8000";
|
|
|
|
const handler = () => new Response(body);
|
|
const abortController = new AbortController();
|
|
|
|
const servePromise = serve(handler, {
|
|
signal: abortController.signal,
|
|
});
|
|
servePromise.catch(() => {});
|
|
|
|
try {
|
|
const response = await fetch(url);
|
|
assertEquals(await response.text(), body);
|
|
} finally {
|
|
abortController.abort();
|
|
await servePromise;
|
|
}
|
|
});
|
|
|
|
Deno.test(`serve should handle websocket requests`, async () => {
|
|
const hostname = "localhost";
|
|
const port = 4505;
|
|
const url = `ws://${hostname}:${port}`;
|
|
const message = `${url} - Hello Deno on WebSocket!`;
|
|
|
|
const abortController = new AbortController();
|
|
|
|
const servePromise = serve(
|
|
(request) => {
|
|
const { socket, response } = Deno.upgradeWebSocket(request);
|
|
// Return the received message as it is
|
|
socket.onmessage = (event) => socket.send(event.data);
|
|
return response;
|
|
},
|
|
{
|
|
hostname,
|
|
port,
|
|
signal: abortController.signal,
|
|
},
|
|
);
|
|
|
|
const ws = new WebSocket(url);
|
|
try {
|
|
ws.onopen = () => ws.send(message);
|
|
const response = await new Promise((resolve) => {
|
|
ws.onmessage = (event) => resolve(event.data);
|
|
});
|
|
assertEquals(response, message);
|
|
} finally {
|
|
ws.close();
|
|
abortController.abort();
|
|
await servePromise;
|
|
}
|
|
});
|
|
|
|
Deno.test(`Server.listenAndServeTls should handle requests`, async () => {
|
|
const hostname = "localhost";
|
|
const port = 4505;
|
|
const addr = `${hostname}:${port}`;
|
|
const certFile = join(testdataDir, "tls/localhost.crt");
|
|
const keyFile = join(testdataDir, "tls/localhost.key");
|
|
const url = `http://${addr}`;
|
|
const status = 418;
|
|
const method = "PATCH";
|
|
const body = `${method}: ${url} - Hello Deno on HTTPS!`;
|
|
|
|
const handler = () => new Response(body, { status });
|
|
const abortController = new AbortController();
|
|
|
|
const servePromise = serveTls(handler, {
|
|
hostname,
|
|
port,
|
|
certFile,
|
|
keyFile,
|
|
signal: abortController.signal,
|
|
});
|
|
|
|
try {
|
|
// Invalid certificate, connection should throw on first read or write
|
|
// but should not crash the server.
|
|
const badConn = await Deno.connectTls({
|
|
hostname,
|
|
port,
|
|
// missing certFile
|
|
});
|
|
|
|
await assertRejects(
|
|
() => badConn.read(new Uint8Array(1)),
|
|
Deno.errors.InvalidData,
|
|
"invalid peer certificate contents: invalid peer certificate: UnknownIssuer",
|
|
"Read with missing certFile didn't throw an InvalidData error when it should have.",
|
|
);
|
|
|
|
badConn.close();
|
|
|
|
// Valid request after invalid
|
|
const conn = await Deno.connectTls({
|
|
hostname,
|
|
port,
|
|
certFile: join(testdataDir, "tls/RootCA.pem"),
|
|
});
|
|
|
|
await writeAll(
|
|
conn,
|
|
new TextEncoder().encode(`${method.toUpperCase()} / HTTP/1.0\r\n\r\n`),
|
|
);
|
|
|
|
const response = new TextDecoder().decode(await readAll(conn));
|
|
|
|
conn.close();
|
|
|
|
assert(response.includes(`HTTP/1.0 ${status}`), "Status code not correct");
|
|
assert(response.includes(body), "Response body not correct");
|
|
} finally {
|
|
abortController.abort();
|
|
await servePromise;
|
|
}
|
|
});
|
|
|
|
Deno.test(
|
|
"Server should not reject when the listener is closed (though the server will continually try and fail to accept connections on the listener until it is closed)",
|
|
async () => {
|
|
const listener = Deno.listen({ port: 4505 });
|
|
const handler = () => new Response();
|
|
const server = new Server({ handler });
|
|
listener.close();
|
|
|
|
let servePromise;
|
|
|
|
try {
|
|
servePromise = server.serve(listener);
|
|
await delay(10);
|
|
} finally {
|
|
server.close();
|
|
await servePromise;
|
|
}
|
|
},
|
|
);
|
|
|
|
Deno.test(
|
|
"Server should not reject when there is a tls handshake with tcp corruption",
|
|
async () => {
|
|
const conn = createMockConn();
|
|
const rejectionError = new Deno.errors.InvalidData(
|
|
"test-tcp-corruption-error",
|
|
);
|
|
const listener = new MockListener({ conn, rejectionError });
|
|
const handler = () => new Response();
|
|
const server = new Server({ handler });
|
|
|
|
let servePromise;
|
|
|
|
try {
|
|
servePromise = server.serve(listener);
|
|
await delay(10);
|
|
} finally {
|
|
server.close();
|
|
await servePromise;
|
|
}
|
|
},
|
|
);
|
|
|
|
Deno.test(
|
|
"Server should not reject when the tls session is aborted",
|
|
async () => {
|
|
const conn = createMockConn();
|
|
const rejectionError = new Deno.errors.ConnectionReset(
|
|
"test-tls-session-aborted-error",
|
|
);
|
|
const listener = new MockListener({ conn, rejectionError });
|
|
const handler = () => new Response();
|
|
const server = new Server({ handler });
|
|
|
|
let servePromise;
|
|
|
|
try {
|
|
servePromise = server.serve(listener);
|
|
await delay(10);
|
|
} finally {
|
|
server.close();
|
|
await servePromise;
|
|
}
|
|
},
|
|
);
|
|
|
|
Deno.test("Server should not reject when the socket is closed", async () => {
|
|
const conn = createMockConn();
|
|
const rejectionError = new Deno.errors.NotConnected(
|
|
"test-socket-closed-error",
|
|
);
|
|
const listener = new MockListener({ conn, rejectionError });
|
|
const handler = () => new Response();
|
|
const server = new Server({ handler });
|
|
|
|
let servePromise;
|
|
|
|
try {
|
|
servePromise = server.serve(listener);
|
|
await delay(10);
|
|
} finally {
|
|
server.close();
|
|
await servePromise;
|
|
}
|
|
});
|
|
|
|
Deno.test(
|
|
"Server should implement a backoff delay when accepting a connection throws an expected error and reset the backoff when successfully accepting a connection again",
|
|
async () => {
|
|
// acceptDelay(n) = 5 * 2^n for n=0...7 capped at 1000 afterwards.
|
|
const expectedBackoffDelays = [
|
|
5,
|
|
10,
|
|
20,
|
|
40,
|
|
80,
|
|
160,
|
|
320,
|
|
640,
|
|
1000,
|
|
1000,
|
|
];
|
|
const rejectionCount = expectedBackoffDelays.length;
|
|
|
|
let resolver: (value: unknown) => void;
|
|
|
|
// Construct a promise we know will only resolve after listener.accept() has
|
|
// been called enough times to assert on our expected backoff delays, i.e.
|
|
// the number of rejections + 1 success.
|
|
const expectedBackoffDelaysCompletedPromise = new Promise((resolve) => {
|
|
resolver = resolve;
|
|
});
|
|
|
|
const acceptCallSideEffect = ({
|
|
acceptCallCount,
|
|
}: {
|
|
acceptCallCount: number;
|
|
}) => {
|
|
if (acceptCallCount > rejectionCount + 1) {
|
|
resolver(undefined);
|
|
}
|
|
};
|
|
|
|
const conn = createMockConn();
|
|
const rejectionError = new Deno.errors.NotConnected(
|
|
"test-socket-closed-error",
|
|
);
|
|
|
|
const listener = new MockListener({
|
|
conn,
|
|
rejectionError,
|
|
rejectionCount,
|
|
acceptCallSideEffect,
|
|
});
|
|
|
|
const handler = () => new Response();
|
|
const server = new Server({ handler });
|
|
const servePromise = server.serve(listener);
|
|
|
|
// Wait for all the expected failures / backoff periods to have completed.
|
|
await expectedBackoffDelaysCompletedPromise;
|
|
|
|
server.close();
|
|
await servePromise;
|
|
|
|
listener.acceptCallIntervals.shift();
|
|
console.log("\n Accept call intervals vs expected backoff intervals:");
|
|
console.table(
|
|
listener.acceptCallIntervals.map((col, i) => [
|
|
col,
|
|
expectedBackoffDelays[i] ?? "<1000, reset",
|
|
]),
|
|
);
|
|
|
|
// Assert that the time between the accept calls is greater than or equal to
|
|
// the expected backoff delay.
|
|
for (let i = 0; i < rejectionCount; i++) {
|
|
assertEquals(
|
|
listener.acceptCallIntervals[i] >= expectedBackoffDelays[i],
|
|
true,
|
|
);
|
|
}
|
|
|
|
// Assert that the backoff delay has been reset following successfully
|
|
// accepting a connection, i.e. it doesn't remain at 1000ms.
|
|
assertEquals(listener.acceptCallIntervals[rejectionCount] < 1000, true);
|
|
},
|
|
);
|
|
|
|
Deno.test("Server should not leak async ops when closed", () => {
|
|
const hostname = "127.0.0.1";
|
|
const port = 4505;
|
|
const handler = () => new Response();
|
|
const server = new Server({ port, hostname, handler });
|
|
server.listenAndServe();
|
|
server.close();
|
|
// Otherwise, the test would fail with: AssertionError: Test case is leaking async ops.
|
|
});
|
|
|
|
Deno.test("Server should abort accept backoff delay when closing", async () => {
|
|
const hostname = "127.0.0.1";
|
|
const port = 4505;
|
|
const handler = () => new Response();
|
|
|
|
const rejectionError = new Deno.errors.NotConnected(
|
|
"test-socket-closed-error",
|
|
);
|
|
const rejectionCount = 1;
|
|
const conn = createMockConn();
|
|
|
|
const listener = new MockListener({
|
|
conn,
|
|
rejectionError,
|
|
rejectionCount,
|
|
});
|
|
|
|
const server = new Server({ port, hostname, handler });
|
|
server.serve(listener);
|
|
|
|
// Wait until the connection is rejected and the backoff delay starts.
|
|
await delay(0);
|
|
|
|
// Close the server, this should end the test without still having an active timer that would trigger an
|
|
// AssertionError: Test case is leaking async ops.
|
|
server.close();
|
|
});
|
|
|
|
Deno.test("Server should reject if the listener throws an unexpected error accepting a connection", async () => {
|
|
const conn = createMockConn();
|
|
const rejectionError = new Error("test-unexpected-error");
|
|
const listener = new MockListener({ conn, rejectionError });
|
|
const handler = () => new Response();
|
|
const server = new Server({ handler });
|
|
await assertRejects(
|
|
() => server.serve(listener),
|
|
Error,
|
|
rejectionError.message,
|
|
);
|
|
});
|
|
|
|
Deno.test(
|
|
"Server should reject if the listener throws an unexpected error accepting a connection",
|
|
async () => {
|
|
const conn = createMockConn();
|
|
const rejectionError = new Error("test-unexpected-error");
|
|
const listener = new MockListener({ conn, rejectionError });
|
|
const handler = () => new Response();
|
|
const server = new Server({ handler });
|
|
await assertRejects(
|
|
() => server.serve(listener),
|
|
Error,
|
|
rejectionError.message,
|
|
);
|
|
},
|
|
);
|
|
|
|
Deno.test(
|
|
"Server should not reject when the connection is closed before the message is complete",
|
|
async () => {
|
|
const listenOptions = {
|
|
hostname: "localhost",
|
|
port: 4505,
|
|
};
|
|
const listener = Deno.listen(listenOptions);
|
|
|
|
const onRequest = deferred();
|
|
const postRespondWith = deferred();
|
|
|
|
const handler = async () => {
|
|
onRequest.resolve();
|
|
|
|
await delay(0);
|
|
|
|
try {
|
|
return new Response("test-response");
|
|
} finally {
|
|
postRespondWith.resolve();
|
|
}
|
|
};
|
|
|
|
const server = new Server({ handler });
|
|
const servePromise = server.serve(listener);
|
|
|
|
const conn = await Deno.connect(listenOptions);
|
|
|
|
await writeAll(conn, new TextEncoder().encode(`GET / HTTP/1.0\r\n\r\n`));
|
|
|
|
await onRequest;
|
|
conn.close();
|
|
|
|
await postRespondWith;
|
|
server.close();
|
|
|
|
await servePromise;
|
|
},
|
|
);
|
|
|
|
Deno.test("Server should not reject when the handler throws", async () => {
|
|
const listenOptions = {
|
|
hostname: "localhost",
|
|
port: 4505,
|
|
};
|
|
const listener = Deno.listen(listenOptions);
|
|
|
|
const postRespondWith = deferred();
|
|
|
|
const handler = () => {
|
|
try {
|
|
throw new Error("test-error");
|
|
} finally {
|
|
postRespondWith.resolve();
|
|
}
|
|
};
|
|
|
|
const server = new Server({ handler });
|
|
const servePromise = server.serve(listener);
|
|
|
|
const conn = await Deno.connect(listenOptions);
|
|
|
|
await writeAll(conn, new TextEncoder().encode(`GET / HTTP/1.0\r\n\r\n`));
|
|
|
|
await postRespondWith;
|
|
conn.close();
|
|
server.close();
|
|
await servePromise;
|
|
});
|
|
|
|
Deno.test("Server should not close the http2 downstream connection when the response stream throws", async () => {
|
|
const listenOptions = {
|
|
hostname: "localhost",
|
|
port: 4505,
|
|
certFile: join(testdataDir, "tls/localhost.crt"),
|
|
keyFile: join(testdataDir, "tls/localhost.key"),
|
|
alpnProtocols: ["h2"],
|
|
};
|
|
const listener = Deno.listenTls(listenOptions);
|
|
const url = `https://${listenOptions.hostname}:${listenOptions.port}/`;
|
|
|
|
let n = 0;
|
|
const a = deferred();
|
|
const connections = new Set();
|
|
|
|
const handler = (_req: Request, connInfo: ConnInfo) => {
|
|
connections.add(connInfo);
|
|
return new Response(
|
|
new ReadableStream({
|
|
async start(controller) {
|
|
n++;
|
|
if (n === 3) {
|
|
throw new Error("test-error");
|
|
}
|
|
await a;
|
|
controller.enqueue(new TextEncoder().encode("a"));
|
|
controller.close();
|
|
},
|
|
}),
|
|
);
|
|
};
|
|
|
|
const server = new Server({ handler });
|
|
const servePromise = server.serve(listener);
|
|
|
|
const caCert = await Deno.readTextFile(
|
|
join(testdataDir, "tls/RootCA.pem"),
|
|
);
|
|
const client = Deno.createHttpClient({
|
|
caCerts: [caCert],
|
|
});
|
|
const resp1 = await fetch(url, { client });
|
|
const resp2 = await fetch(url, { client });
|
|
|
|
const err = await assertRejects(async () => {
|
|
const resp3 = await fetch(url, { client });
|
|
const _data = await resp3.text();
|
|
});
|
|
assert(err);
|
|
a.resolve();
|
|
assertEquals(await resp1.text(), "a");
|
|
assertEquals(await resp2.text(), "a");
|
|
|
|
const numConns = connections.size;
|
|
assertEquals(
|
|
numConns,
|
|
1,
|
|
`fetch should have reused a single connection, but used ${numConns} instead.`,
|
|
);
|
|
assertEquals(n, 3, "The handler should have been called three times");
|
|
|
|
client.close();
|
|
server.close();
|
|
await servePromise;
|
|
});
|
|
|
|
Deno.test("Server should be able to parse IPV6 addresses", async () => {
|
|
const hostname = "[::1]";
|
|
const port = 4505;
|
|
const url = `http://${hostname}:${port}`;
|
|
const method = "GET";
|
|
const status = 418;
|
|
const body = `${method}: ${url} - Hello Deno on HTTP!`;
|
|
|
|
const handler = () => new Response(body, { status });
|
|
const abortController = new AbortController();
|
|
|
|
const servePromise = serve(handler, {
|
|
hostname,
|
|
port,
|
|
signal: abortController.signal,
|
|
});
|
|
|
|
try {
|
|
const response = await fetch(url, { method });
|
|
assertEquals(await response.text(), body);
|
|
assertEquals(response.status, status);
|
|
} finally {
|
|
abortController.abort();
|
|
await servePromise;
|
|
}
|
|
});
|
|
|
|
Deno.test("Server.serve can be called multiple times", async () => {
|
|
const listenerOneOptions = {
|
|
hostname: "localhost",
|
|
port: 4505,
|
|
};
|
|
const listenerTwoOptions = {
|
|
hostname: "localhost",
|
|
port: 8080,
|
|
};
|
|
const listenerOne = Deno.listen(listenerOneOptions);
|
|
const listenerTwo = Deno.listen(listenerTwoOptions);
|
|
|
|
const handler = (_request: Request, connInfo: ConnInfo) => {
|
|
if ((connInfo.localAddr as Deno.NetAddr).port === listenerOneOptions.port) {
|
|
return new Response("Hello listener one!");
|
|
} else if (
|
|
(connInfo.localAddr as Deno.NetAddr).port === listenerTwoOptions.port
|
|
) {
|
|
return new Response("Hello listener two!");
|
|
}
|
|
|
|
unreachable();
|
|
};
|
|
|
|
const server = new Server({ handler });
|
|
const servePromiseOne = server.serve(listenerOne);
|
|
const servePromiseTwo = server.serve(listenerTwo);
|
|
|
|
try {
|
|
const responseOne = await fetch(
|
|
`http://${listenerOneOptions.hostname}:${listenerOneOptions.port}`,
|
|
);
|
|
assertEquals(await responseOne.text(), "Hello listener one!");
|
|
|
|
const responseTwo = await fetch(
|
|
`http://${listenerTwoOptions.hostname}:${listenerTwoOptions.port}`,
|
|
);
|
|
assertEquals(await responseTwo.text(), "Hello listener two!");
|
|
} finally {
|
|
server.close();
|
|
await servePromiseOne;
|
|
await servePromiseTwo;
|
|
}
|
|
});
|
|
|
|
Deno.test(
|
|
"Server.listenAndServe should throw if called multiple times",
|
|
async () => {
|
|
const handler = () => unreachable();
|
|
|
|
const server = new Server({ port: 4505, handler });
|
|
const servePromise = server.listenAndServe();
|
|
|
|
try {
|
|
await assertRejects(() => server.listenAndServe(), Deno.errors.AddrInUse);
|
|
} finally {
|
|
server.close();
|
|
await servePromise;
|
|
}
|
|
},
|
|
);
|
|
|
|
Deno.test(
|
|
"Server.listenAndServeTls should throw if called multiple times",
|
|
async () => {
|
|
const handler = () => unreachable();
|
|
|
|
const certFile = join(testdataDir, "tls/localhost.crt");
|
|
const keyFile = join(testdataDir, "tls/localhost.key");
|
|
|
|
const server = new Server({ port: 4505, handler });
|
|
const servePromise = server.listenAndServeTls(certFile, keyFile);
|
|
|
|
try {
|
|
await assertRejects(
|
|
() => server.listenAndServeTls(certFile, keyFile),
|
|
Deno.errors.AddrInUse,
|
|
);
|
|
} finally {
|
|
server.close();
|
|
await servePromise;
|
|
}
|
|
},
|
|
);
|
|
|
|
Deno.test(
|
|
"Handler is called with the request instance and connection information",
|
|
async () => {
|
|
const hostname = "127.0.0.1";
|
|
const port = 4505;
|
|
const addr = `${hostname}:${port}`;
|
|
|
|
let receivedRequest: Request;
|
|
let receivedConnInfo: ConnInfo;
|
|
|
|
const handler = (request: Request, connInfo: ConnInfo) => {
|
|
receivedRequest = request;
|
|
receivedConnInfo = connInfo;
|
|
|
|
return new Response("Hello Deno!");
|
|
};
|
|
|
|
const server = new Server({ hostname, port, handler });
|
|
const servePromise = server.listenAndServe();
|
|
|
|
const url = `http://${addr}/`;
|
|
|
|
try {
|
|
const response = await fetch(url);
|
|
await response.text();
|
|
|
|
assertEquals(receivedRequest!.url, url);
|
|
assertEquals(receivedConnInfo!.localAddr.transport, "tcp");
|
|
assertEquals(
|
|
(receivedConnInfo!.localAddr as Deno.NetAddr).hostname,
|
|
hostname,
|
|
);
|
|
assertEquals((receivedConnInfo!.localAddr as Deno.NetAddr).port, port);
|
|
assertEquals(receivedConnInfo!.remoteAddr.transport, "tcp");
|
|
assertEquals(
|
|
(receivedConnInfo!.remoteAddr as Deno.NetAddr).hostname,
|
|
hostname,
|
|
);
|
|
} finally {
|
|
server.close();
|
|
await servePromise;
|
|
}
|
|
},
|
|
);
|
|
|
|
Deno.test("Default onError is called when Handler throws", async () => {
|
|
const hostname = "localhost";
|
|
const port = 4505;
|
|
const url = `http://${hostname}:${port}`;
|
|
const handler = (_request: Request, _connInfo: ConnInfo) => {
|
|
throw new Error("I failed to serve the request");
|
|
};
|
|
const abortController = new AbortController();
|
|
|
|
const servePromise = serve(handler, {
|
|
hostname,
|
|
port,
|
|
signal: abortController.signal,
|
|
});
|
|
|
|
try {
|
|
const response = await fetch(url);
|
|
assertEquals(await response.text(), "Internal Server Error");
|
|
assertEquals(response.status, 500);
|
|
} finally {
|
|
abortController.abort();
|
|
await servePromise;
|
|
}
|
|
});
|
|
|
|
Deno.test("Custom onError is called when Handler throws", async () => {
|
|
const hostname = "localhost";
|
|
const port = 4505;
|
|
const url = `http://${hostname}:${port}`;
|
|
const handler = (_request: Request, _connInfo: ConnInfo) => {
|
|
throw new Error("I failed to serve the request");
|
|
};
|
|
const onError = (_error: unknown) => {
|
|
return new Response("custom error page", { status: 500 });
|
|
};
|
|
const abortController = new AbortController();
|
|
|
|
const servePromise = serve(handler, {
|
|
hostname,
|
|
port,
|
|
onError,
|
|
signal: abortController.signal,
|
|
});
|
|
|
|
try {
|
|
const response = await fetch(url);
|
|
assertEquals(await response.text(), "custom error page");
|
|
assertEquals(response.status, 500);
|
|
} finally {
|
|
abortController.abort();
|
|
await servePromise;
|
|
}
|
|
});
|
|
|
|
Deno.test("Custom onError is called when Handler throws", async () => {
|
|
const listenOptions = {
|
|
hostname: "localhost",
|
|
port: 4505,
|
|
};
|
|
const listener = Deno.listen(listenOptions);
|
|
|
|
const url = `http://${listenOptions.hostname}:${listenOptions.port}`;
|
|
const handler = (_request: Request, _connInfo: ConnInfo) => {
|
|
throw new Error("I failed to serve the request");
|
|
};
|
|
const onError = (_error: unknown) => {
|
|
return new Response("custom error page", { status: 500 });
|
|
};
|
|
const abortController = new AbortController();
|
|
|
|
const servePromise = serveListener(listener, handler, {
|
|
onError,
|
|
signal: abortController.signal,
|
|
});
|
|
|
|
try {
|
|
const response = await fetch(url);
|
|
assertEquals(await response.text(), "custom error page");
|
|
assertEquals(response.status, 500);
|
|
} finally {
|
|
abortController.abort();
|
|
await servePromise;
|
|
}
|
|
});
|
|
|
|
Deno.test("Server.listenAndServeTls should support custom onError", async () => {
|
|
const hostname = "localhost";
|
|
const port = 4505;
|
|
const certFile = join(testdataDir, "tls/localhost.crt");
|
|
const keyFile = join(testdataDir, "tls/localhost.key");
|
|
const status = 500;
|
|
const method = "PATCH";
|
|
const body = "custom error page";
|
|
|
|
const handler = () => {
|
|
throw new Error("I failed to serve the request.");
|
|
};
|
|
const onError = (_error: unknown) => new Response(body, { status });
|
|
const abortController = new AbortController();
|
|
|
|
const servePromise = serveTls(handler, {
|
|
hostname,
|
|
port,
|
|
certFile,
|
|
keyFile,
|
|
onError,
|
|
signal: abortController.signal,
|
|
});
|
|
|
|
try {
|
|
const conn = await Deno.connectTls({
|
|
hostname,
|
|
port,
|
|
certFile: join(testdataDir, "tls/RootCA.pem"),
|
|
});
|
|
|
|
await writeAll(
|
|
conn,
|
|
new TextEncoder().encode(
|
|
`${method.toUpperCase()} / HTTP/1.0\r\n\r\n`,
|
|
),
|
|
);
|
|
|
|
const response = new TextDecoder().decode(await readAll(conn));
|
|
|
|
conn.close();
|
|
|
|
assert(
|
|
response.includes(`HTTP/1.0 ${status}`),
|
|
"Status code not correct",
|
|
);
|
|
assert(
|
|
response.includes(body),
|
|
"Response body not correct",
|
|
);
|
|
} finally {
|
|
abortController.abort();
|
|
await servePromise;
|
|
}
|
|
});
|
|
|
|
Deno.test("serve - onListen callback is called when the server started listening", () => {
|
|
const abortController = new AbortController();
|
|
return serve((_) => new Response("hello"), {
|
|
async onListen({ hostname, port }) {
|
|
const responseText = await (await fetch("http://localhost:8000/")).text();
|
|
assertEquals(hostname, "0.0.0.0");
|
|
assertEquals(port, 8000);
|
|
assertEquals(responseText, "hello");
|
|
abortController.abort();
|
|
},
|
|
signal: abortController.signal,
|
|
});
|
|
});
|
|
|
|
Deno.test("serve - onListen callback is called with ephemeral port", () => {
|
|
const abortController = new AbortController();
|
|
return serve((_) => new Response("hello"), {
|
|
port: 0,
|
|
async onListen({ hostname, port }) {
|
|
assertEquals(hostname, "0.0.0.0");
|
|
assertNotEquals(port, 0);
|
|
const responseText = await (await fetch(`http://localhost:${port}/`))
|
|
.text();
|
|
assertEquals(responseText, "hello");
|
|
abortController.abort();
|
|
},
|
|
signal: abortController.signal,
|
|
});
|
|
});
|
|
|
|
Deno.test("serve - doesn't print the message when onListen set to undefined", async () => {
|
|
const command = new Deno.Command(Deno.execPath(), {
|
|
args: [
|
|
"eval",
|
|
`
|
|
import { serve } from "./http/server.ts";
|
|
serve(() => new Response("hello"), { onListen: undefined });
|
|
Deno.exit(0);
|
|
`,
|
|
],
|
|
});
|
|
const { code, stdout } = await command.output();
|
|
assertEquals(code, 0);
|
|
assertEquals(new TextDecoder().decode(stdout), "");
|
|
});
|
|
|
|
Deno.test("serve - can print customized start-up message in onListen handler", async () => {
|
|
const command = new Deno.Command(Deno.execPath(), {
|
|
args: [
|
|
"eval",
|
|
`
|
|
import { serve } from "./http/server.ts";
|
|
serve(() => new Response("hello"), { onListen({ port, hostname }) {
|
|
console.log("Server started at " + hostname + " port " + port);
|
|
} });
|
|
Deno.exit(0);
|
|
`,
|
|
],
|
|
});
|
|
const { stdout, code } = await command.output();
|
|
assertEquals(code, 0);
|
|
assertEquals(
|
|
new TextDecoder().decode(stdout),
|
|
"Server started at 0.0.0.0 port 8000\n",
|
|
);
|
|
});
|
|
|
|
Deno.test("serveTls - onListen callback is called with ephemeral port", () => {
|
|
const abortController = new AbortController();
|
|
return serveTls((_) => new Response("hello"), {
|
|
port: 0,
|
|
certFile: join(testdataDir, "tls/localhost.crt"),
|
|
keyFile: join(testdataDir, "tls/localhost.key"),
|
|
async onListen({ hostname, port }) {
|
|
assertEquals(hostname, "0.0.0.0");
|
|
assertNotEquals(port, 0);
|
|
const caCert = await Deno.readTextFile(
|
|
join(testdataDir, "tls/RootCA.pem"),
|
|
);
|
|
const client = Deno.createHttpClient({ caCerts: [caCert] });
|
|
const responseText =
|
|
await (await fetch(`https://localhost:${port}/`, { client }))
|
|
.text();
|
|
client.close();
|
|
assertEquals(responseText, "hello");
|
|
abortController.abort();
|
|
},
|
|
signal: abortController.signal,
|
|
});
|
|
});
|
|
|
|
Deno.test("serveTls - cert, key can be injected directly from memory rather than file system.", () => {
|
|
const abortController = new AbortController();
|
|
return serveTls((_) => new Response("hello"), {
|
|
port: 0,
|
|
cert: Deno.readTextFileSync(join(testdataDir, "tls/localhost.crt")),
|
|
key: Deno.readTextFileSync(join(testdataDir, "tls/localhost.key")),
|
|
async onListen({ hostname, port }) {
|
|
assertEquals(hostname, "0.0.0.0");
|
|
assertNotEquals(port, 0);
|
|
const caCert = await Deno.readTextFile(
|
|
join(testdataDir, "tls/RootCA.pem"),
|
|
);
|
|
const client = Deno.createHttpClient({ caCerts: [caCert] });
|
|
const responseText = await (
|
|
await fetch(`https://localhost:${port}/`, { client })
|
|
).text();
|
|
client.close();
|
|
assertEquals(responseText, "hello");
|
|
abortController.abort();
|
|
},
|
|
signal: abortController.signal,
|
|
});
|
|
});
|