summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlberto Ricart <alberto@synadia.com>2020-08-12 05:55:38 -0500
committerGitHub <noreply@github.com>2020-08-12 12:55:38 +0200
commit91ff91093dba45c9696d71e74323487cfa4ce406 (patch)
treeb9f4e9898f982ab1427f358b6dc14f8e10df1f38
parentb344a7f81fa00d1611212cac82b631f9d1dffdf0 (diff)
feat(std): added TLS serve abilities to file_server (#6962)
-rw-r--r--[-rwxr-xr-x]std/http/file_server.ts111
-rw-r--r--std/http/file_server_test.ts89
2 files changed, 164 insertions, 36 deletions
diff --git a/std/http/file_server.ts b/std/http/file_server.ts
index ffcf31e54..0ece60560 100755..100644
--- a/std/http/file_server.ts
+++ b/std/http/file_server.ts
@@ -7,7 +7,13 @@
// https://github.com/indexzero/http-server/blob/master/test/http-server-test.js
import { posix, extname } from "../path/mod.ts";
-import { listenAndServe, ServerRequest, Response } from "./server.ts";
+import {
+ listenAndServe,
+ listenAndServeTLS,
+ ServerRequest,
+ Response,
+ HTTPSOptions,
+} from "./server.ts";
import { parse } from "../flags/mod.ts";
import { assert } from "../_util/assert.ts";
@@ -28,6 +34,14 @@ interface FileServerArgs {
// -h --help
h: boolean;
help: boolean;
+ // --host
+ host: string;
+ // -c --cert
+ c: string;
+ cert: string;
+ // -k --key
+ k: string;
+ key: string;
}
const encoder = new TextEncoder();
@@ -49,6 +63,7 @@ const MEDIA_TYPES: Record<string, string> = {
".gz": "application/gzip",
".css": "text/css",
".wasm": "application/wasm",
+ ".mjs": "application/javascript",
};
/** Returns the content-type based on the extension of a path. */
@@ -306,7 +321,19 @@ function html(strings: TemplateStringsArray, ...values: unknown[]): string {
function main(): void {
const CORSEnabled = serverArgs.cors ? true : false;
- const addr = `0.0.0.0:${serverArgs.port ?? serverArgs.p ?? 4507}`;
+ const port = serverArgs.port ?? serverArgs.p ?? 4507;
+ const host = serverArgs.host ?? "0.0.0.0";
+ const addr = `${host}:${port}`;
+ const tlsOpts = {} as HTTPSOptions;
+ tlsOpts.certFile = serverArgs.cert ?? serverArgs.c ?? "";
+ tlsOpts.keyFile = serverArgs.key ?? serverArgs.k ?? "";
+
+ if (tlsOpts.keyFile || tlsOpts.certFile) {
+ if (tlsOpts.keyFile === "" || tlsOpts.certFile === "") {
+ console.log("--key and --cert are required for TLS");
+ serverArgs.h = true;
+ }
+ }
if (serverArgs.h ?? serverArgs.help) {
console.log(`Deno File Server
@@ -321,50 +348,62 @@ function main(): void {
OPTIONS:
-h, --help Prints help information
-p, --port <PORT> Set port
- --cors Enable CORS via the "Access-Control-Allow-Origin" header`);
+ --cors Enable CORS via the "Access-Control-Allow-Origin" header
+ --host <HOST> Hostname (default is 0.0.0.0)
+ -c, --cert <FILE> TLS certificate file (enables TLS)
+ -k, --key <FILE> TLS key file (enables TLS)
+
+ All TLS options are required when one is provided.`);
+
Deno.exit();
}
- listenAndServe(
- addr,
- async (req): Promise<void> => {
- let normalizedUrl = posix.normalize(req.url);
- try {
- normalizedUrl = decodeURIComponent(normalizedUrl);
- } catch (e) {
- if (!(e instanceof URIError)) {
- throw e;
- }
+ const handler = async (req: ServerRequest): Promise<void> => {
+ let normalizedUrl = posix.normalize(req.url);
+ try {
+ normalizedUrl = decodeURIComponent(normalizedUrl);
+ } catch (e) {
+ if (!(e instanceof URIError)) {
+ throw e;
}
- const fsPath = posix.join(target, normalizedUrl);
+ }
+ const fsPath = posix.join(target, normalizedUrl);
- let response: Response | undefined;
+ let response: Response | undefined;
+ try {
+ const fileInfo = await Deno.stat(fsPath);
+ if (fileInfo.isDirectory) {
+ response = await serveDir(req, fsPath);
+ } else {
+ response = await serveFile(req, fsPath);
+ }
+ } catch (e) {
+ console.error(e.message);
+ response = await serveFallback(req, e);
+ } finally {
+ if (CORSEnabled) {
+ assert(response);
+ setCORS(response);
+ }
+ serverLog(req, response!);
try {
- const fileInfo = await Deno.stat(fsPath);
- if (fileInfo.isDirectory) {
- response = await serveDir(req, fsPath);
- } else {
- response = await serveFile(req, fsPath);
- }
+ await req.respond(response!);
} catch (e) {
console.error(e.message);
- response = await serveFallback(req, e);
- } finally {
- if (CORSEnabled) {
- assert(response);
- setCORS(response);
- }
- serverLog(req, response!);
- try {
- await req.respond(response!);
- } catch (e) {
- console.error(e.message);
- }
}
- },
- );
+ }
+ };
- console.log(`HTTP server listening on http://${addr}/`);
+ let proto = "http";
+ if (tlsOpts.keyFile || tlsOpts.certFile) {
+ proto += "s";
+ tlsOpts.hostname = host;
+ tlsOpts.port = port;
+ listenAndServeTLS(tlsOpts, handler);
+ } else {
+ listenAndServe(addr, handler);
+ }
+ console.log(`${proto.toUpperCase()} server listening on ${proto}://${addr}/`);
}
if (import.meta.main) {
diff --git a/std/http/file_server_test.ts b/std/http/file_server_test.ts
index 486bc514c..a25c13a7d 100644
--- a/std/http/file_server_test.ts
+++ b/std/http/file_server_test.ts
@@ -199,3 +199,92 @@ Deno.test("file_server running as library", async function (): Promise<void> {
await killFileServer();
}
});
+
+async function startTlsFileServer({
+ target = ".",
+ port = 4577,
+}: FileServerCfg = {}): Promise<void> {
+ fileServer = Deno.run({
+ cmd: [
+ Deno.execPath(),
+ "run",
+ "--allow-read",
+ "--allow-net",
+ "http/file_server.ts",
+ target,
+ "--host",
+ "localhost",
+ "--cert",
+ "./http/testdata/tls/localhost.crt",
+ "--key",
+ "./http/testdata/tls/localhost.key",
+ "--cors",
+ "-p",
+ `${port}`,
+ ],
+ stdout: "piped",
+ stderr: "null",
+ });
+ // Once fileServer is ready it will write to its stdout.
+ assert(fileServer.stdout != null);
+ const r = new TextProtoReader(new BufReader(fileServer.stdout));
+ const s = await r.readLine();
+ assert(s !== null && s.includes("server listening"));
+}
+
+Deno.test("serveDirectory TLS", async function (): Promise<void> {
+ await startTlsFileServer();
+ try {
+ // Valid request after invalid
+ const conn = await Deno.connectTls({
+ hostname: "localhost",
+ port: 4577,
+ certFile: "./http/testdata/tls/RootCA.pem",
+ });
+
+ await Deno.writeAll(
+ conn,
+ new TextEncoder().encode("GET /http HTTP/1.0\r\n\r\n"),
+ );
+ const res = new Uint8Array(128 * 1024);
+ const nread = await conn.read(res);
+ assert(nread !== null);
+ conn.close();
+ const page = new TextDecoder().decode(res.subarray(0, nread));
+ assert(page.includes("<title>Deno File Server</title>"));
+ } finally {
+ await killFileServer();
+ }
+});
+
+Deno.test("partial TLS arguments fail", async function (): Promise<void> {
+ fileServer = Deno.run({
+ cmd: [
+ Deno.execPath(),
+ "run",
+ "--allow-read",
+ "--allow-net",
+ "http/file_server.ts",
+ ".",
+ "--host",
+ "localhost",
+ "--cert",
+ "./http/testdata/tls/localhost.crt",
+ "-p",
+ `4578`,
+ ],
+ stdout: "piped",
+ stderr: "null",
+ });
+ try {
+ // Once fileServer is ready it will write to its stdout.
+ assert(fileServer.stdout != null);
+ const r = new TextProtoReader(new BufReader(fileServer.stdout));
+ const s = await r.readLine();
+ assert(
+ s !== null && s.includes("--key and --cert are required for TLS"),
+ );
+ } finally {
+ await killFileServer();
+ }
+});