UNPKG

5.03 kBJavaScriptView Raw
1/**
2 Licensed to the Apache Software Foundation (ASF) under one
3 or more contributor license agreements. See the NOTICE file
4 distributed with this work for additional information
5 regarding copyright ownership. The ASF licenses this file
6 to you under the Apache License, Version 2.0 (the
7 "License"); you may not use this file except in compliance
8 with the License. You may obtain a copy of the License at
9
10 http://www.apache.org/licenses/LICENSE-2.0
11
12 Unless required by applicable law or agreed to in writing,
13 software distributed under the License is distributed on an
14 "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 KIND, either express or implied. See the License for the
16 specific language governing permissions and limitations
17 under the License.
18 */
19
20var fs = require('fs'),
21 http = require('http'),
22 url = require('url'),
23 path = require('path'),
24 Q = require('q'),
25 stream = require('./stream');
26
27/**
28 * @desc Launches a server with the specified options and optional custom handlers.
29 * @param {{root: string, port: number, urlPathProcessor: ?function, streamHandler: ?function, serverExtender: ?function}} opts
30 * @returns {*|promise}
31 */
32module.exports = function (opts) {
33 var deferred = Q.defer();
34
35 var root = opts.root;
36 var port = opts.port || 8000;
37
38 var server = http.createServer(function (request, response) {
39 function do404() {
40 console.log('404 ' + request.url);
41 response.writeHead(404, {'Content-Type': 'text/plain'});
42 response.write('404 Not Found\n');
43 response.end();
44 }
45
46 function do302(where) {
47 console.log('302 ' + request.url);
48 response.setHeader('Location', where);
49 response.writeHead(302, {'Content-Type': 'text/plain'});
50 response.end();
51 }
52
53 function do304() {
54 console.log('304 ' + request.url);
55 response.writeHead(304, {'Content-Type': 'text/plain'});
56 response.end();
57 }
58
59 function isFileChanged(path) {
60 var mtime = fs.statSync(path).mtime,
61 itime = request.headers['if-modified-since'];
62 return !itime || new Date(mtime) > new Date(itime);
63 }
64
65 var urlPath = url.parse(request.url).pathname;
66
67 var filePath;
68 if (opts.urlPathProcessor) {
69 var result = opts.urlPathProcessor(urlPath, request, response, do302, do404);
70 if (!result) {
71 return;
72 }
73 filePath = result.filePath;
74 }
75 if (!filePath) {
76 if (!root) {
77 throw new Error('No server root directory HAS BEEN specified!');
78 }
79 filePath = path.join(root, urlPath);
80 }
81
82 fs.exists(filePath, function (exists) {
83 if (!exists) {
84 do404();
85 return;
86 }
87 if (fs.statSync(filePath).isDirectory()) {
88 var index = path.join(filePath, 'index.html');
89 try {
90 if (fs.statSync(index)) {
91 filePath = index;
92 }
93 } catch (e) {
94 }
95 }
96 if (fs.statSync(filePath).isDirectory()) {
97 if (!/\/$/.test(urlPath)) {
98 do302(request.url + '/');
99 return;
100 }
101 console.log('200 ' + request.url);
102 response.writeHead(200, {'Content-Type': 'text/html'});
103 response.write('<html><head><title>Directory listing of ' + urlPath + '</title></head>');
104 response.write('<h3>Items in this directory</h3>');
105 response.write('<ul>');
106 fs.readdirSync(filePath).forEach(function (file) {
107 if (file) {
108 response.write('<li><a href="' + file + '">' + file + '</a></li>\n');
109 }
110 });
111
112 response.write('</ul>');
113 response.end();
114 } else if (!isFileChanged(filePath)) {
115 do304();
116 } else {
117 (opts.streamHandler && opts.streamHandler(filePath, request, response)) || stream(filePath, request, response);
118 }
119 });
120 }).on('listening', function () {
121 console.log('Static file server running on port ' + port + ' (i.e. http://localhost:' + port + ')\nCTRL + C to shut down');
122 deferred.resolve({server: server, port: port});
123 }).on('error', function (e) {
124 if (e && e.toString().indexOf('EADDRINUSE') !== -1) {
125 port++;
126 server.listen(port);
127 } else {
128 deferred.reject(e);
129 }
130 }).listen(port);
131
132 if (opts.serverExtender) {
133 opts.serverExtender(server, root);
134 }
135
136 return deferred.promise;
137};