forked from marijnh/Eloquent-JavaScript
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathfile_server_promises.js
More file actions
135 lines (118 loc) · 4.65 KB
/
file_server_promises.js
File metadata and controls
135 lines (118 loc) · 4.65 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
var http = require("http"), fs = require("fs");
var Promise = require("promise");
var methods = Object.create(null);
// Remember that promises can either fail or succeed. The `then`
// method takes two callbacks, one to handle success and one to handle
// failure. The strategy for dealing with exceptions and other failure
// is to notice them in the second callback passed here, and return a
// 500 response.
//
// On success, the promise returned by respondTo should return an
// object with a `code` property indicating the response code, and
// optional `body` and `type` properties. The body can be a stream to
// directly pipe into the response, or a string.
http.createServer(function(request, response) {
respondTo(request).then(function(data) {
response.writeHead(data.code, {"Content-Type": data.type || "text/plain"});
if (data.body && data.body.pipe)
data.body.pipe(response);
else
response.end(data.body);
}, function(error) {
response.writeHead(500);
response.end(error.toString());
console.log("Response failed: ", error.stack);
});
}).listen(8000);
function respondTo(request) {
if (request.method in methods)
return methods[request.method](urlToPath(request.url), request);
else
return Promise.resolve({code: 405,
body: "Method " + request.method + " not allowed."});
}
function urlToPath(url) {
var path = require("url").parse(url).pathname;
var decoded = decodeURIComponent(path);
return "." + decoded.replace(/(\/|\\)\.\.(\/|\\|$)/g, "/");
}
// Wrap the fs functions that we need with Promise.denodeify, so that
// they return promises instead of directly taking a callback and
// passing it an error argument.
var fsp = {};
["stat", "readdir", "rmdir", "unlink", "mkdir"].forEach(function(method) {
fsp[method] = Promise.denodeify(fs[method]);
});
// Since several functions need to call `fsp.stat` and handle failures
// that indicate non-existent files in a special way, this is a
// convenience wrapper that converts file-not-found failures into
// success with a null value.
//
// Remember that calling the `then` method returns *another* promise,
// and that having a failure handler return normally replaces the
// failure a success (using the returned value). We're passing null
// for the success handler here (letting through normall successes
// unchanged), and changing one kind of failure into success.
function inspectPath(path) {
return fsp.stat(path).then(null, function(error) {
if (error.code == "ENOENT") return null;
else throw error;
});
}
// We can get by with much less explicit error handling, now that
// failures automatically propagate back. The new promise returned by
// `then`, as returned from this function, will use one of the values
// returned here (objects with `code` properties) as its value. When a
// handler passed to `then` returns another promise (as in the case
// when the path refers to a directory), that promise will be
// connected the promise returned by `then`, determining when and how
// it is resolved.
methods.GET = function(path) {
return inspectPath(path).then(function(stats) {
if (!stats) // Does not exist
return {code: 404, body: "File not found"};
else if (stats.isDirectory())
return fsp.readdir(path).then(function(files) {
return {code: 200, body: files.join("\n")};
});
else
return {code: 200,
type: require("mime").lookup(path),
body: fs.createReadStream(path)};
});
};
var noContent = {code: 204};
function returnNoContent() { return noContent; }
// Though failure is propagated automatically, we still have to
// arrange for `noContent` to be returned when an action finishes,
// which is the role of `returnNoContent` success handler.
methods.DELETE = function(path) {
return inspectPath(path).then(function(stats) {
if (!stats)
return noContent;
else if (stats.isDirectory())
return fsp.rmdir(path).then(returnNoContent);
else
return fsp.unlink(path).then(returnNoContent);
});
};
// To wrap a stream, we have to define our own promise, since
// Promise.denodeify can only wrap simple functions.
methods.PUT = function(path, request) {
return new Promise(function(success, failure) {
var outStream = fs.createWriteStream(path);
outStream.on("error", failure);
outStream.on("finish", success.bind(null, noContent));
request.pipe(outStream);
});
};
methods.MKCOL = function(path, request) {
return inspectPath(path).then(function(stats) {
if (!stats)
return fsp.mkdir(path).then(returnNoContent);
if (stats.isDirectory())
return noContent;
else
return {code: 400, body: "File exists"};
});
};