summaryrefslogtreecommitdiff
path: root/std/http/file_server.ts
diff options
context:
space:
mode:
author木杉 <zhmushan@qq.com>2019-12-03 08:14:25 +0800
committerRy Dahl <ry@tinyclouds.org>2019-12-02 16:14:25 -0800
commitcfa4f540baac5beaf168de3e818c882ccbd95136 (patch)
tree5a685c28a9783f5abe3337dd2ab9679c81022419 /std/http/file_server.ts
parent136b5e3da2c689b54b34c46fa41973e0ccca66ab (diff)
better html for file_server (#3423)
Diffstat (limited to 'std/http/file_server.ts')
-rwxr-xr-xstd/http/file_server.ts195
1 files changed, 122 insertions, 73 deletions
diff --git a/std/http/file_server.ts b/std/http/file_server.ts
index 8dc2ee87a..fb8f28081 100755
--- a/std/http/file_server.ts
+++ b/std/http/file_server.ts
@@ -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> => {