1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-12-22 23:34:47 -05:00

better html for file_server (#3423)

This commit is contained in:
木杉 2019-12-03 08:14:25 +08:00 committed by Ry Dahl
parent 136b5e3da2
commit cfa4f540ba
3 changed files with 129 additions and 82 deletions

View file

@ -7,8 +7,7 @@
// https://github.com/indexzero/http-server/blob/master/test/http-server-test.js
const { ErrorKind, cwd, args, stat, readDir, open } = Deno;
import { contentType } from "../media_types/mod.ts";
import { extname, posix } from "../path/mod.ts";
import { posix } from "../path/mod.ts";
import {
listenAndServe,
ServerRequest,
@ -16,33 +15,14 @@ import {
Response
} from "./server.ts";
const dirViewerTemplate = `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Deno File Server</title>
<style>
td {
padding: 0 1rem;
}
td.mode {
font-family: Courier;
}
</style>
</head>
<body>
<h1>Index of <%DIRNAME%></h1>
<table>
<tr><th>Mode</th><th>Size</th><th>Name</th></tr>
<%CONTENTS%>
</table>
</body>
</html>
`;
interface EntryInfo {
mode: string;
size: string;
url: string;
name: string;
}
const encoder = new TextEncoder();
const serverArgs = args.slice();
let CORSEnabled = false;
// TODO: switch to flags if we later want to add more options
@ -58,7 +38,6 @@ const target = posix.isAbsolute(targetArg)
? posix.normalize(targetArg)
: posix.join(cwd(), targetArg);
const addr = `0.0.0.0:${serverArgs[2] || 4500}`;
const encoder = new TextEncoder();
function modeToString(isDir: boolean, maybeMode: number | null): string {
const modeMap = ["---", "--x", "-w-", "-wx", "r--", "r-x", "rw-", "rwx"];
@ -99,25 +78,6 @@ function fileLenToString(len: number): string {
return `${(len / base).toFixed(2)}${suffix[suffixIndex]}`;
}
function createDirEntryDisplay(
name: string,
url: string,
size: number | null,
mode: number | null,
isDir: boolean
): string {
const sizeStr = size === null ? "" : "" + fileLenToString(size!);
return `
<tr><td class="mode">${modeToString(
isDir,
mode
)}</td><td>${sizeStr}</td><td><a href="${url}">${name}${
isDir ? "/" : ""
}</a></td>
</tr>
`;
}
async function serveFile(
req: ServerRequest,
filePath: string
@ -126,7 +86,7 @@ async function serveFile(
const fileInfo = await stat(filePath);
const headers = new Headers();
headers.set("content-length", fileInfo.len.toString());
headers.set("content-type", contentType(extname(filePath)) || "text/plain");
headers.set("content-type", "text/plain");
const res = {
status: 200,
@ -141,12 +101,8 @@ async function serveDir(
req: ServerRequest,
dirPath: string
): Promise<Response> {
interface ListItem {
name: string;
template: string;
}
const dirUrl = `/${posix.relative(target, dirPath)}`;
const listEntry: ListItem[] = [];
const listEntry: EntryInfo[] = [];
const fileInfos = await readDir(dirPath);
for (const fileInfo of fileInfos) {
const filePath = posix.join(dirPath, fileInfo.name);
@ -161,29 +117,17 @@ async function serveDir(
mode = (await stat(filePath)).mode;
} catch (e) {}
listEntry.push({
mode: modeToString(fileInfo.isDirectory(), mode),
size: fileInfo.isFile() ? fileLenToString(fileInfo.len) : "",
name: fileInfo.name,
template: createDirEntryDisplay(
fileInfo.name,
fileUrl,
fileInfo.isFile() ? fileInfo.len : null,
mode,
fileInfo.isDirectory()
)
url: fileUrl
});
}
const formattedDirUrl = `${dirUrl.replace(/\/$/, "")}/`;
const page = new TextEncoder().encode(
dirViewerTemplate.replace("<%DIRNAME%>", formattedDirUrl).replace(
"<%CONTENTS%>",
listEntry
.sort((a, b): number =>
a.name.toLowerCase() > b.name.toLowerCase() ? 1 : -1
)
.map((v): string => v.template)
.join("")
)
listEntry.sort((a, b) =>
a.name.toLowerCase() > b.name.toLowerCase() ? 1 : -1
);
const formattedDirUrl = `${dirUrl.replace(/\/$/, "")}/`;
const page = encoder.encode(dirViewerTemplate(formattedDirUrl, listEntry));
const headers = new Headers();
headers.set("content-type", "text/html");
@ -232,6 +176,111 @@ function setCORS(res: Response): void {
);
}
function dirViewerTemplate(dirname: string, entries: EntryInfo[]): string {
return html`
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Deno File Server</title>
<style>
:root {
--background-color: #fafafa;
--color: rgba(0, 0, 0, 0.87);
}
@media (prefers-color-scheme: dark) {
:root {
--background-color: #303030;
--color: #fff;
}
}
@media (min-width: 960px) {
main {
max-width: 960px;
}
body {
padding-left: 32px;
padding-right: 32px;
}
}
@media (min-width: 600px) {
main {
padding-left: 24px;
padding-right: 24px;
}
}
body {
background: var(--background-color);
color: var(--color);
font-family: "Roboto", "Helvetica", "Arial", sans-serif;
font-weight: 400;
line-height: 1.43;
font-size: 0.875rem;
}
a {
color: #2196f3;
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
table th {
text-align: left;
}
table td {
padding: 12px 24px 0 0;
}
</style>
</head>
<body>
<main>
<h1>Index of ${dirname}</h1>
<table>
<tr>
<th>Mode</th>
<th>Size</th>
<th>Name</th>
</tr>
${entries.map(
entry => html`
<tr>
<td class="mode">
${entry.mode}
</td>
<td>
${entry.size}
</td>
<td>
<a href="${entry.url}">${entry.name}</a>
</td>
</tr>
`
)}
</table>
</main>
</body>
</html>
`;
}
function html(strings: TemplateStringsArray, ...values: unknown[]): string {
const l = strings.length - 1;
let html = "";
for (let i = 0; i < l; i++) {
let v = values[i];
if (v instanceof Array) {
v = v.join("");
}
const s = strings[i] + v;
html += s;
}
html += strings[l];
return html;
}
listenAndServe(
addr,
async (req): Promise<void> => {

View file

@ -36,10 +36,6 @@ test(async function serveFile(): Promise<void> {
const res = await fetch("http://localhost:4500/README.md");
assert(res.headers.has("access-control-allow-origin"));
assert(res.headers.has("access-control-allow-headers"));
assertEquals(
res.headers.get("content-type"),
"text/markdown; charset=utf-8"
);
const downloadedFile = await res.text();
const localFile = new TextDecoder().decode(
await Deno.readFile("README.md")
@ -63,10 +59,10 @@ test(async function serveDirectory(): Promise<void> {
// TODO: `mode` should work correctly in the future.
// Correct this test case accordingly.
Deno.build.os !== "win" &&
assert(/<td class="mode">\([a-zA-Z-]{10}\)<\/td>/.test(page));
assert(/<td class="mode">(\s)*\([a-zA-Z-]{10}\)(\s)*<\/td>/.test(page));
Deno.build.os === "win" &&
assert(/<td class="mode">\(unknown mode\)<\/td>/.test(page));
assert(page.includes(`<td><a href="/README.md">README.md</a></td>`));
assert(/<td class="mode">(\s)*\(unknown mode\)(\s)*<\/td>/.test(page));
assert(page.includes(`<a href="/README.md">README.md</a>`));
} finally {
killFileServer();
}

View file

@ -6,10 +6,12 @@ const tlsOptions = {
hostname: "localhost",
port: 4503,
certFile: "./http/testdata/tls/localhost.crt",
keyFile: "./http/testdata/tls/localhost.key",
keyFile: "./http/testdata/tls/localhost.key"
};
const s = serveTLS(tlsOptions);
console.log(`Simple HTTPS server listening on ${tlsOptions.hostname}:${tlsOptions.port}`);
console.log(
`Simple HTTPS server listening on ${tlsOptions.hostname}:${tlsOptions.port}`
);
const body = new TextEncoder().encode("Hello HTTPS");
for await (const req of s) {
req.respond({ body });