diff options
Diffstat (limited to 'std')
-rwxr-xr-x | std/http/file_server.ts | 195 | ||||
-rw-r--r-- | std/http/file_server_test.ts | 10 | ||||
-rw-r--r-- | std/http/testdata/simple_https_server.ts | 6 |
3 files changed, 129 insertions, 82 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> => { diff --git a/std/http/file_server_test.ts b/std/http/file_server_test.ts index 77467b8c8..7d7e024e7 100644 --- a/std/http/file_server_test.ts +++ b/std/http/file_server_test.ts @@ -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(); } diff --git a/std/http/testdata/simple_https_server.ts b/std/http/testdata/simple_https_server.ts index 655457c94..21d1181cf 100644 --- a/std/http/testdata/simple_https_server.ts +++ b/std/http/testdata/simple_https_server.ts @@ -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 }); |