mirror of
https://github.com/denoland/deno.git
synced 2025-03-03 17:34:47 -05:00
feat(std): added TLS serve abilities to file_server (#6962)
This commit is contained in:
parent
b344a7f81f
commit
91ff91093d
2 changed files with 164 additions and 36 deletions
111
std/http/file_server.ts
Executable file → Normal file
111
std/http/file_server.ts
Executable file → Normal file
|
@ -7,7 +7,13 @@
|
||||||
// https://github.com/indexzero/http-server/blob/master/test/http-server-test.js
|
// https://github.com/indexzero/http-server/blob/master/test/http-server-test.js
|
||||||
|
|
||||||
import { posix, extname } from "../path/mod.ts";
|
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 { parse } from "../flags/mod.ts";
|
||||||
import { assert } from "../_util/assert.ts";
|
import { assert } from "../_util/assert.ts";
|
||||||
|
|
||||||
|
@ -28,6 +34,14 @@ interface FileServerArgs {
|
||||||
// -h --help
|
// -h --help
|
||||||
h: boolean;
|
h: boolean;
|
||||||
help: boolean;
|
help: boolean;
|
||||||
|
// --host
|
||||||
|
host: string;
|
||||||
|
// -c --cert
|
||||||
|
c: string;
|
||||||
|
cert: string;
|
||||||
|
// -k --key
|
||||||
|
k: string;
|
||||||
|
key: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const encoder = new TextEncoder();
|
const encoder = new TextEncoder();
|
||||||
|
@ -49,6 +63,7 @@ const MEDIA_TYPES: Record<string, string> = {
|
||||||
".gz": "application/gzip",
|
".gz": "application/gzip",
|
||||||
".css": "text/css",
|
".css": "text/css",
|
||||||
".wasm": "application/wasm",
|
".wasm": "application/wasm",
|
||||||
|
".mjs": "application/javascript",
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Returns the content-type based on the extension of a path. */
|
/** 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 {
|
function main(): void {
|
||||||
const CORSEnabled = serverArgs.cors ? true : false;
|
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) {
|
if (serverArgs.h ?? serverArgs.help) {
|
||||||
console.log(`Deno File Server
|
console.log(`Deno File Server
|
||||||
|
@ -321,50 +348,62 @@ function main(): void {
|
||||||
OPTIONS:
|
OPTIONS:
|
||||||
-h, --help Prints help information
|
-h, --help Prints help information
|
||||||
-p, --port <PORT> Set port
|
-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();
|
Deno.exit();
|
||||||
}
|
}
|
||||||
|
|
||||||
listenAndServe(
|
const handler = async (req: ServerRequest): Promise<void> => {
|
||||||
addr,
|
let normalizedUrl = posix.normalize(req.url);
|
||||||
async (req): Promise<void> => {
|
try {
|
||||||
let normalizedUrl = posix.normalize(req.url);
|
normalizedUrl = decodeURIComponent(normalizedUrl);
|
||||||
try {
|
} catch (e) {
|
||||||
normalizedUrl = decodeURIComponent(normalizedUrl);
|
if (!(e instanceof URIError)) {
|
||||||
} catch (e) {
|
throw 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 {
|
try {
|
||||||
const fileInfo = await Deno.stat(fsPath);
|
await req.respond(response!);
|
||||||
if (fileInfo.isDirectory) {
|
|
||||||
response = await serveDir(req, fsPath);
|
|
||||||
} else {
|
|
||||||
response = await serveFile(req, fsPath);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e.message);
|
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) {
|
if (import.meta.main) {
|
||||||
|
|
|
@ -199,3 +199,92 @@ Deno.test("file_server running as library", async function (): Promise<void> {
|
||||||
await killFileServer();
|
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();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
Loading…
Add table
Reference in a new issue