Skip to content

Commit d7039bd

Browse files
committed
Chapter 20 - Directory creation
1 parent 979c842 commit d7039bd

File tree

4 files changed

+154
-0
lines changed

4 files changed

+154
-0
lines changed

chapter-20/directory-creation.js

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { mkdir } from 'fs/promises';
2+
import { urlPath } from './file-server.js';
3+
4+
// Run file-server.js with node for testing
5+
const mkcolHandler = async (request) => {
6+
const path = urlPath(request.url);
7+
try {
8+
await mkdir(path, { recursive: true });
9+
return { status: 201 };
10+
} catch (error) {
11+
if (error.code != 'EEXIST') throw error;
12+
else return { status: 400, body: 'Not a directory' };
13+
}
14+
};
15+
16+
export default mkcolHandler;

chapter-20/file-server.js

+89
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import { createServer } from 'http';
2+
import { parse } from 'url';
3+
import { resolve, sep } from 'path';
4+
import { createReadStream, createWriteStream } from 'fs';
5+
import { stat, readdir, rmdir, unlink } from 'fs/promises';
6+
import mime from 'mime';
7+
8+
import mkcolHandler from './directory-creation.js';
9+
10+
const methods = Object.create(null);
11+
12+
createServer((request, response) => {
13+
let handler = methods[request.method] || notAllowed;
14+
handler(request)
15+
.catch((error) => {
16+
if (error.status != null) return error;
17+
return { body: String(error), status: 500 };
18+
})
19+
.then(({ body, status = 200, type = 'text/plain' }) => {
20+
response.writeHead(status, { 'Content-Type': type });
21+
if (body && body.pipe) body.pipe(response);
22+
else response.end(body);
23+
});
24+
}).listen(8000);
25+
26+
async function notAllowed(request) {
27+
return {
28+
status: 405,
29+
body: `Method ${request.method} not allowed.`,
30+
};
31+
}
32+
33+
var baseDirectory = process.cwd() + sep + 'test-files';
34+
35+
export function urlPath(url) {
36+
let { pathname } = parse(url);
37+
let path = resolve('test-files' + sep + decodeURIComponent(pathname).slice(1));
38+
if (path != baseDirectory && !path.startsWith(baseDirectory + sep)) {
39+
throw { status: 403, body: 'Forbidden' };
40+
}
41+
return path;
42+
}
43+
44+
methods.GET = async function (request) {
45+
let path = urlPath(request.url);
46+
let stats;
47+
try {
48+
stats = await stat(path);
49+
} catch (error) {
50+
if (error.code != 'ENOENT') throw error;
51+
else return { status: 404, body: 'File not found' };
52+
}
53+
if (stats.isDirectory()) {
54+
return { body: (await readdir(path)).join('\n') };
55+
} else {
56+
return { body: createReadStream(path), type: mime.getType(path) };
57+
}
58+
};
59+
60+
methods.DELETE = async function (request) {
61+
let path = urlPath(request.url);
62+
let stats;
63+
try {
64+
stats = await stat(path);
65+
} catch (error) {
66+
if (error.code != 'ENOENT') throw error;
67+
else return { status: 204 };
68+
}
69+
if (stats.isDirectory()) await rmdir(path);
70+
else await unlink(path);
71+
return { status: 204 };
72+
};
73+
74+
function pipeStream(from, to) {
75+
return new Promise((resolve, reject) => {
76+
from.on('error', reject);
77+
to.on('error', reject);
78+
to.on('finish', resolve);
79+
from.pipe(to);
80+
});
81+
}
82+
83+
methods.PUT = async function (request) {
84+
let path = urlPath(request.url);
85+
await pipeStream(request, createWriteStream(path));
86+
return { status: 204 };
87+
};
88+
89+
methods.MKCOL = mkcolHandler;

chapter-20/package-lock.json

+34
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

chapter-20/package.json

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"name": "file-server",
3+
"version": "1.0.0",
4+
"description": "",
5+
"main": "file-server.js",
6+
"scripts": {
7+
"test": "echo \"Error: no test specified\" && exit 1"
8+
},
9+
"author": "",
10+
"license": "ISC",
11+
"dependencies": {
12+
"mime": "^3.0.0"
13+
},
14+
"type": "module"
15+
}

0 commit comments

Comments
 (0)