mirror of
https://github.com/denoland/deno.git
synced 2025-09-29 21:54:48 +00:00
Serve directory for file server & Fix bufio flush bug (denoland/deno_std#15)
Original: b78f4e9fbd
This commit is contained in:
parent
a691d9257b
commit
b945303329
6 changed files with 240 additions and 27 deletions
189
file_server.ts
189
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") });
|
||||
}
|
||||
return;
|
||||
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;
|
||||
}
|
||||
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}/`);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue