Mercurial > hg > nodescore
comparison node_modules/node-static/lib/node-static.js @ 69:333afcfd3f3a
added node_modules to project and fixed path to chronometer
also added deps to installer script
author | tzara <rc-web@kiben.net> |
---|---|
date | Sat, 26 Oct 2013 14:12:50 +0100 |
parents | |
children | 0ae87af84e2f |
comparison
equal
deleted
inserted
replaced
68:b076cd17638c | 69:333afcfd3f3a |
---|---|
1 var fs = require('fs') | |
2 , events = require('events') | |
3 , buffer = require('buffer') | |
4 , http = require('http') | |
5 , url = require('url') | |
6 , path = require('path') | |
7 , mime = require('mime') | |
8 , util = require('./node-static/util'); | |
9 | |
10 // Current version | |
11 var version = [0, 7, 2]; | |
12 | |
13 Server = function (root, options) { | |
14 if (root && (typeof(root) === 'object')) { options = root; root = null } | |
15 | |
16 this.root = path.resolve(root || '.'); | |
17 this.options = options || {}; | |
18 this.cache = 3600; | |
19 | |
20 this.defaultHeaders = {}; | |
21 this.options.headers = this.options.headers || {}; | |
22 | |
23 if ('cache' in this.options) { | |
24 if (typeof(this.options.cache) === 'number') { | |
25 this.cache = this.options.cache; | |
26 } else if (! this.options.cache) { | |
27 this.cache = false; | |
28 } | |
29 } | |
30 | |
31 if ('serverInfo' in this.options) { | |
32 this.serverInfo = this.options.serverInfo.toString(); | |
33 } else { | |
34 this.serverInfo = 'node-static/' + version.join('.'); | |
35 } | |
36 | |
37 this.defaultHeaders['server'] = this.serverInfo; | |
38 | |
39 if (this.cache !== false) { | |
40 this.defaultHeaders['cache-control'] = 'max-age=' + this.cache; | |
41 } | |
42 | |
43 for (var k in this.defaultHeaders) { | |
44 this.options.headers[k] = this.options.headers[k] || | |
45 this.defaultHeaders[k]; | |
46 } | |
47 }; | |
48 | |
49 Server.prototype.serveDir = function (pathname, req, res, finish) { | |
50 var htmlIndex = path.join(pathname, 'index.html'), | |
51 that = this; | |
52 | |
53 fs.stat(htmlIndex, function (e, stat) { | |
54 if (!e) { | |
55 var status = 200; | |
56 var headers = {}; | |
57 var originalPathname = decodeURI(url.parse(req.url).pathname); | |
58 if (originalPathname.length && originalPathname.charAt(originalPathname.length - 1) !== '/') { | |
59 return finish(301, { 'Location': originalPathname + '/' }); | |
60 } else { | |
61 that.respond(null, status, headers, [htmlIndex], stat, req, res, finish); | |
62 } | |
63 } else { | |
64 // Stream a directory of files as a single file. | |
65 fs.readFile(path.join(pathname, 'index.json'), function (e, contents) { | |
66 if (e) { return finish(404, {}) } | |
67 var index = JSON.parse(contents); | |
68 streamFiles(index.files); | |
69 }); | |
70 } | |
71 }); | |
72 function streamFiles(files) { | |
73 util.mstat(pathname, files, function (e, stat) { | |
74 if (e) { return finish(404, {}) } | |
75 that.respond(pathname, 200, {}, files, stat, req, res, finish); | |
76 }); | |
77 } | |
78 }; | |
79 | |
80 Server.prototype.serveFile = function (pathname, status, headers, req, res) { | |
81 var that = this; | |
82 var promise = new(events.EventEmitter); | |
83 | |
84 pathname = this.resolve(pathname); | |
85 | |
86 fs.stat(pathname, function (e, stat) { | |
87 if (e) { | |
88 return promise.emit('error', e); | |
89 } | |
90 that.respond(null, status, headers, [pathname], stat, req, res, function (status, headers) { | |
91 that.finish(status, headers, req, res, promise); | |
92 }); | |
93 }); | |
94 return promise; | |
95 }; | |
96 | |
97 Server.prototype.finish = function (status, headers, req, res, promise, callback) { | |
98 var result = { | |
99 status: status, | |
100 headers: headers, | |
101 message: http.STATUS_CODES[status] | |
102 }; | |
103 | |
104 headers['server'] = this.serverInfo; | |
105 | |
106 if (!status || status >= 400) { | |
107 if (callback) { | |
108 callback(result); | |
109 } else { | |
110 if (promise.listeners('error').length > 0) { | |
111 promise.emit('error', result); | |
112 } | |
113 else { | |
114 res.writeHead(status, headers); | |
115 res.end(); | |
116 } | |
117 } | |
118 } else { | |
119 // Don't end the request here, if we're streaming; | |
120 // it's taken care of in `prototype.stream`. | |
121 if (status !== 200 || req.method !== 'GET') { | |
122 res.writeHead(status, headers); | |
123 res.end(); | |
124 } | |
125 callback && callback(null, result); | |
126 promise.emit('success', result); | |
127 } | |
128 }; | |
129 | |
130 Server.prototype.servePath = function (pathname, status, headers, req, res, finish) { | |
131 var that = this, | |
132 promise = new(events.EventEmitter); | |
133 | |
134 pathname = this.resolve(pathname); | |
135 | |
136 // Make sure we're not trying to access a | |
137 // file outside of the root. | |
138 if (pathname.indexOf(that.root) === 0) { | |
139 fs.stat(pathname, function (e, stat) { | |
140 if (e) { | |
141 finish(404, {}); | |
142 } else if (stat.isFile()) { // Stream a single file. | |
143 that.respond(null, status, headers, [pathname], stat, req, res, finish); | |
144 } else if (stat.isDirectory()) { // Stream a directory of files. | |
145 that.serveDir(pathname, req, res, finish); | |
146 } else { | |
147 finish(400, {}); | |
148 } | |
149 }); | |
150 } else { | |
151 // Forbidden | |
152 finish(403, {}); | |
153 } | |
154 return promise; | |
155 }; | |
156 | |
157 Server.prototype.resolve = function (pathname) { | |
158 return path.resolve(path.join(this.root, pathname)); | |
159 }; | |
160 | |
161 Server.prototype.serve = function (req, res, callback) { | |
162 var that = this, | |
163 promise = new(events.EventEmitter), | |
164 pathname; | |
165 | |
166 var finish = function (status, headers) { | |
167 that.finish(status, headers, req, res, promise, callback); | |
168 }; | |
169 | |
170 try { | |
171 pathname = decodeURI(url.parse(req.url).pathname); | |
172 } | |
173 catch(e) { | |
174 return process.nextTick(function() { | |
175 return finish(400, {}); | |
176 }); | |
177 } | |
178 | |
179 process.nextTick(function () { | |
180 that.servePath(pathname, 200, {}, req, res, finish).on('success', function (result) { | |
181 promise.emit('success', result); | |
182 }).on('error', function (err) { | |
183 promise.emit('error'); | |
184 }); | |
185 }); | |
186 if (! callback) { return promise } | |
187 }; | |
188 | |
189 /* Check if we should consider sending a gzip version of the file based on the | |
190 * file content type and client's Accept-Encoding header value. | |
191 */ | |
192 Server.prototype.gzipOk = function(req, contentType) { | |
193 var enable = this.options.gzip; | |
194 if(enable && | |
195 (typeof enable === 'boolean' || | |
196 (contentType && (enable instanceof RegExp) && enable.test(contentType)))) { | |
197 var acceptEncoding = req.headers['accept-encoding']; | |
198 return acceptEncoding && acceptEncoding.indexOf("gzip") >= 0; | |
199 } | |
200 return false; | |
201 } | |
202 | |
203 /* Send a gzipped version of the file if the options and the client indicate gzip is enabled and | |
204 * we find a .gz file mathing the static resource requested. | |
205 */ | |
206 Server.prototype.respondGzip = function(pathname, status, contentType, _headers, files, stat, req, res, finish) { | |
207 var that = this; | |
208 if(files.length == 1 && this.gzipOk(req, contentType)) { | |
209 var gzFile = files[0] + ".gz"; | |
210 fs.stat(gzFile, function(e, gzStat) { | |
211 if(!e && gzStat.isFile()) { | |
212 //console.log('Serving', gzFile, 'to gzip-capable client instead of', files[0], 'new size is', gzStat.size, 'uncompressed size', stat.size); | |
213 var vary = _headers['Vary']; | |
214 _headers['Vary'] = (vary && vary != 'Accept-Encoding'?vary+', ':'')+'Accept-Encoding'; | |
215 _headers['Content-Encoding'] = 'gzip'; | |
216 stat.size = gzStat.size; | |
217 files = [gzFile]; | |
218 } else { | |
219 //console.log('gzip file not found or error finding it', gzFile, String(e), stat.isFile()); | |
220 } | |
221 that.respondNoGzip(pathname, status, contentType, _headers, files, stat, req, res, finish); | |
222 }); | |
223 } else { | |
224 // Client doesn't want gzip or we're sending multiple files | |
225 that.respondNoGzip(pathname, status, contentType, _headers, files, stat, req, res, finish); | |
226 } | |
227 } | |
228 | |
229 Server.prototype.respondNoGzip = function (pathname, status, contentType, _headers, files, stat, req, res, finish) { | |
230 var mtime = Date.parse(stat.mtime), | |
231 key = pathname || files[0], | |
232 headers = {}, | |
233 clientETag = req.headers['if-none-match'], | |
234 clientMTime = Date.parse(req.headers['if-modified-since']); | |
235 | |
236 | |
237 // Copy default headers | |
238 for (var k in this.options.headers) { headers[k] = this.options.headers[k] } | |
239 // Copy custom headers | |
240 for (var k in _headers) { headers[k] = _headers[k] } | |
241 | |
242 headers['Etag'] = JSON.stringify([stat.ino, stat.size, mtime].join('-')); | |
243 headers['Date'] = new(Date)().toUTCString(); | |
244 headers['Last-Modified'] = new(Date)(stat.mtime).toUTCString(); | |
245 headers['Content-Type'] = contentType; | |
246 headers['Content-Length'] = stat.size; | |
247 | |
248 for (var k in _headers) { headers[k] = _headers[k] } | |
249 | |
250 // Conditional GET | |
251 // If the "If-Modified-Since" or "If-None-Match" headers | |
252 // match the conditions, send a 304 Not Modified. | |
253 if ((clientMTime || clientETag) && | |
254 (!clientETag || clientETag === headers['Etag']) && | |
255 (!clientMTime || clientMTime >= mtime)) { | |
256 // 304 response should not contain entity headers | |
257 ['Content-Encoding', | |
258 'Content-Language', | |
259 'Content-Length', | |
260 'Content-Location', | |
261 'Content-MD5', | |
262 'Content-Range', | |
263 'Content-Type', | |
264 'Expires', | |
265 'Last-Modified'].forEach(function(entityHeader) { | |
266 delete headers[entityHeader]; | |
267 }); | |
268 finish(304, headers); | |
269 } else { | |
270 res.writeHead(status, headers); | |
271 | |
272 this.stream(pathname, files, new(buffer.Buffer)(stat.size), res, function (e, buffer) { | |
273 if (e) { return finish(500, {}) } | |
274 finish(status, headers); | |
275 }); | |
276 } | |
277 }; | |
278 | |
279 Server.prototype.respond = function (pathname, status, _headers, files, stat, req, res, finish) { | |
280 var contentType = _headers['Content-Type'] || | |
281 mime.lookup(files[0]) || | |
282 'application/octet-stream'; | |
283 if(this.options.gzip) { | |
284 this.respondGzip(pathname, status, contentType, _headers, files, stat, req, res, finish); | |
285 } else { | |
286 this.respondNoGzip(pathname, status, contentType, _headers, files, stat, req, res, finish); | |
287 } | |
288 } | |
289 | |
290 Server.prototype.stream = function (pathname, files, buffer, res, callback) { | |
291 (function streamFile(files, offset) { | |
292 var file = files.shift(); | |
293 | |
294 if (file) { | |
295 file = file[0] === '/' ? file : path.join(pathname || '.', file); | |
296 | |
297 // Stream the file to the client | |
298 fs.createReadStream(file, { | |
299 flags: 'r', | |
300 mode: 0666 | |
301 }).on('data', function (chunk) { | |
302 chunk.copy(buffer, offset); | |
303 offset += chunk.length; | |
304 }).on('close', function () { | |
305 streamFile(files, offset); | |
306 }).on('error', function (err) { | |
307 callback(err); | |
308 console.error(err); | |
309 }).pipe(res, { end: false }); | |
310 } else { | |
311 res.end(); | |
312 callback(null, buffer, offset); | |
313 } | |
314 })(files.slice(0), 0); | |
315 }; | |
316 | |
317 // Exports | |
318 exports.Server = Server; | |
319 exports.version = version; | |
320 exports.mime = mime; | |
321 | |
322 | |
323 |