diff options
Diffstat (limited to 'file_server.ts')
| -rwxr-xr-x | file_server.ts | 187 |
1 files changed, 168 insertions, 19 deletions
diff --git a/file_server.ts b/file_server.ts index 9dcac8704..d2b9fe0b0 100755 --- a/file_server.ts +++ b/file_server.ts @@ -5,42 +5,191 @@ // TODO Add tests like these: // https://github.com/indexzero/http-server/blob/master/test/http-server-test.js -import { listenAndServe } from "./http"; -import { cwd, readFile, DenoError, ErrorKind, args } from "deno"; +import { listenAndServe, ServerRequest, setContentLength } from "./http"; +import { cwd, readFile, DenoError, ErrorKind, args, stat, readDir } from "deno"; + +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> +`; -const addr = "0.0.0.0:4500"; let currentDir = cwd(); const target = args[1]; if (target) { currentDir = `${currentDir}/${target}`; } +const addr = `0.0.0.0:${args[2] || 4500}`; const encoder = new TextEncoder(); -listenAndServe(addr, async req => { - const fileName = req.url.replace(/\/$/, '/index.html'); - const filePath = currentDir + fileName; - let file; +function modeToString(isDir: boolean, maybeMode: number | null) { + const modeMap = ["---", "--x", "-w-", "-wx", "r--", "r-x", "rw-", "rwx"]; - try { - file = await readFile(filePath); - } catch (e) { - if (e instanceof DenoError && e.kind === ErrorKind.NotFound) { - await req.respond({ status: 404, body: encoder.encode("Not found") }); - } else { - await req.respond({ status: 500, body: encoder.encode("Internal server error") }); + if (maybeMode === null) { + return "(unknown mode)"; + } + const mode = maybeMode!.toString(8); + if (mode.length < 3) { + return "(unknown mode)"; + } + let output = ""; + mode + .split("") + .reverse() + .slice(0, 3) + .forEach(v => { + output = modeMap[+v] + output; + }); + output = `(${isDir ? "d" : "-"}${output})`; + return output; +} + +function fileLenToString(len: number) { + const multipler = 1024; + let base = 1; + const suffix = ["B", "K", "M", "G", "T"]; + let suffixIndex = 0; + + while (base * multipler < len) { + if (suffixIndex >= suffix.length - 1) { + break; } - return; + base *= multipler; + suffixIndex++; } - + + return `${(len / base).toFixed(2)}${suffix[suffixIndex]}`; +} + +function createDirEntryDisplay( + name: string, + path: string, + size: number | null, + mode: number | null, + isDir: boolean +) { + const sizeStr = size === null ? "" : "" + fileLenToString(size!); + return ` + <tr><td class="mode">${modeToString( + isDir, + mode + )}</td><td>${sizeStr}</td><td><a href="${path}">${name}${ + isDir ? "/" : "" + }</a></td> + </tr> + `; +} + +// TODO: simplify this after deno.stat and deno.readDir are fixed +async function serveDir(req: ServerRequest, dirPath: string, dirName: string) { + // dirname has no prefix + const listEntry: string[] = []; + const fileInfos = await readDir(dirPath); + for (const info of fileInfos) { + if (info.name === "index.html" && info.isFile()) { + // in case index.html as dir... + await serveFile(req, info.path); + return; + } + // Yuck! + let mode = null; + try { + mode = (await stat(info.path)).mode; + } catch (e) {} + listEntry.push( + createDirEntryDisplay( + info.name, + dirName + "/" + info.name, + info.isFile() ? info.len : null, + mode, + info.isDirectory() + ) + ); + } + + const page = new TextEncoder().encode( + dirViewerTemplate + .replace("<%DIRNAME%>", dirName + "/") + .replace("<%CONTENTS%>", listEntry.join("")) + ); + const headers = new Headers(); - headers.set('content-type', 'octet-stream'); + headers.set("content-type", "text/html"); + + const res = { + status: 200, + body: page, + headers + }; + setContentLength(res); + await req.respond(res); +} + +async function serveFile(req: ServerRequest, filename: string) { + let file = await readFile(filename); + const headers = new Headers(); + headers.set("content-type", "octet-stream"); const res = { status: 200, body: file, - headers, - } + headers + }; await req.respond(res); +} + +async function serveFallback(req: ServerRequest, e: Error) { + if ( + e instanceof DenoError && + (e as DenoError<any>).kind === ErrorKind.NotFound + ) { + await req.respond({ status: 404, body: encoder.encode("Not found") }); + } else { + await req.respond({ + status: 500, + body: encoder.encode("Internal server error") + }); + } +} + +listenAndServe(addr, async req => { + const fileName = req.url.replace(/\/$/, ""); + const filePath = currentDir + fileName; + + try { + const fileInfo = await stat(filePath); + if (fileInfo.isDirectory()) { + // Bug with deno.stat: name and path not populated + // Yuck! + await serveDir(req, filePath, fileName); + } else { + await serveFile(req, filePath); + } + } catch (e) { + await serveFallback(req, e); + return; + } }); console.log(`HTTP server listening on http://${addr}/`); |
