-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathdocument-server.js
197 lines (159 loc) · 6.21 KB
/
document-server.js
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
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
var util = require('util');
var http = require('http');
var url = require('url');
var fs = require('fs');
var path = require('path');
var base_dir = getBaseDir();
exports.server = http.createServer(onRequest);
function onRequest(request, response) { //all incoming requests are initially handled by this method
if (request.url === '/favicon.ico') {
//requests from a browser send another request for favicon.ico, this block just silence those requests
response.writeHead(200, {'Content-Type': 'image/x-icon'} );
response.end();
return;
}
var requestedPath = path.normalize(base_dir + request.url);
if (path.existsSync(requestedPath))
handleIncomingRequest(requestedPath, request, response);
else
create404Response(response, requestedPath);
}
// Main request handlers
function handleIncomingRequest(requestedPath, request, response) {
var stat = fs.lstatSync(requestedPath);
if(request.method == 'GET')
handleGETRequest(requestedPath, stat, request, response);
else if (request.method == 'PUT')
handlePUTRequest(requestedPath, stat, request, response);
else if (request.method == 'DELETE')
handleDELETERequest(requestedPath, stat, request, response);
else
create405Response(response); //If the request method is not allowed return 405 response
}
function handleGETRequest(requestedPath, stat, request, response) {
var respondWith304 = false;
var ifModifiedSince = request.headers['if-modified-since'];
if(ifModifiedSince) {
var fileModificationTimeStamp = Date.parse(stat.mtime);
var ifModifiedSinceTimeStamp = Date.parse(ifModifiedSince);
// respond with 304 if the fileModificationDate is earlier than the if-modified-since header
respondWith304 = fileModificationTimeStamp <= ifModifiedSinceTimeStamp;
}
if(respondWith304) {
create304Response(response);
}
else {
if(stat.isFile())
addFileToResponseBody(requestedPath, stat, response);
else
listDirectoryContent(requestedPath, stat, request, response);
}
}
function handlePUTRequest(requestedPath, stat, request, response) {
var fullBody = '';
request.on('data', function(chunk) {
//start capturing the body of the request
fullBody += chunk.toString();
});
request.on('end', function() {
//at this point we have received the complete body of the request
if (stat.isFile()) {
//if the uri is referencing an already existing file, then we consider it an unpdate to the existing file
//refer to: http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html
fs.writeFileSync(requestedPath, fullBody);
response.writeHead(200, {'Content-Type': 'text/plain'});
response.end('200 OK - File updated successfully: ' + requestedPath.replace('\\', '/'));
}
else {
var newFileOrDirectoryName = getNewFileOrDirectoryName(requestedPath); //get a name for the new resource
var newFileOrDirPath = path.normalize(requestedPath + '//' + newFileOrDirectoryName );
var uri = request.url + '\/' + newFileOrDirectoryName;
if(fullBody)
fs.writeFileSync(newFileOrDirPath, fullBody); // write new file if the body is not empty
else
fs.mkdirSync(newFileOrDirPath); // create a new directory if the body is empty
response.writeHead(201, {'Content-Type': 'text/plain', 'Location' : uri});
response.end('201 - File or Directory created successfully: ' + uri);
}
});
}
function handleDELETERequest(requestedPath, stat, request, response) {
if(stat.isFile())
fs.unlinkSync(requestedPath);
else
fs.rmdirSync(requestedPath);
create204Response(response);
}
//response methods
function create204Response(response) {
response.writeHead(204, {'Content-Type': 'text/plain'});
response.end();
}
function create304Response(response) {
response.writeHead(304, {'Content-Type': 'text/plain'});
response.end();
}
function create400Response(response) {
response.writeHead(400, {'Content-Type': 'text/plain'});
response.end('400 Bad Request - Invalid input');
}
function create404Response(response, requestedPath) {
response.writeHead(404, {'Content-Type': 'text/plain'});
response.end('404 Not Found - File Or Directory not found: ' + requestedPath);
}
function create405Response(response) {
response.writeHead(405, {'Content-Type': 'text/plain', 'Allow' : 'GET, PUT, DELETE'});
response.end();
}
//Helper methods
function getNewFileOrDirectoryName(requestedPath) {
//return the name of the new file or directory that will be created
//it looks for the maximmum extension and increment it by one
//for example, if the base_dir contains only one file named FileOrDir_1,
//this method returns FileOrDir_2
var dir = fs.readdirSync(requestedPath);
var newFileIndex = 0;
for (i = 0; i < dir.length; i++){
if(dir[i].indexOf('FileOrDir_') >= 0){
var index = parseInt(dir[i].slice(10));
if (index > newFileIndex)
newFileIndex = index;
}
}
return 'FileOrDir_' + (++newFileIndex);
}
function listDirectoryContent(requestedPath, stat, request, response) {
response.statusCode = 200;
response.setHeader("Content-Type", "text/xml");
response.write('<?xml version="1.0" ?>');
response.write('<dir>');
var dir = fs.readdirSync(requestedPath);
for (i = 0; i < dir.length; i++)
response.write('<item uri="' + request.url + '\/' + dir[i] + '" />');
response.end('</dir>');
}
function addFileToResponseBody(requestedPath, stat, response) {
response.statusCode = 200;
response.setHeader('Content-Type', 'text/plain');
response.setHeader('Content-Length', stat.size);
response.setHeader('Last-Modified', stat.mtime);
var data = fs.readFileSync(requestedPath, 'ascii');
response.end(data);
}
function getBaseDir() {
var dir = process.cwd();
for (i = 0; i < process.argv.length; i++) {
if(process.argv[i] == '-d') {
if(i == process.argv.length - 1)
throw new Error('invalid number of arguments, Please startup the server using "node DocumentServer.js" or "node DocumentServer.js -d BASE_DIR"');
else
dir = process.argv[i+1];
break;
}
}
dir = path.normalize(dir);
var stats = fs.lstatSync(dir);
if(!stats.isDirectory())
throw new Error('The supplied BASE_DIR does not exists');
return dir;
}