web-server.js 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255
  1. #!/usr/bin/env node
  2. var util = require('util'),
  3. http = require('http'),
  4. fs = require('fs'),
  5. url = require('url'),
  6. events = require('events');
  7. var DEFAULT_PORT = 8090;
  8. function main(argv) {
  9. new HttpServer({
  10. 'GET':createServlet(StaticServlet),
  11. 'HEAD':createServlet(StaticServlet)
  12. }).start(Number(argv[2]) || DEFAULT_PORT);
  13. }
  14. function escapeHtml(value) {
  15. return value.toString().
  16. replace('<', '&lt;').
  17. replace('>', '&gt;').
  18. replace('"', '&quot;');
  19. }
  20. function createServlet(Class) {
  21. var servlet = new Class();
  22. return servlet.handleRequest.bind(servlet);
  23. }
  24. /**
  25. * An Http server implementation that uses a map of methods to decide
  26. * action routing.
  27. *
  28. * @param {Object} Map of method => Handler function
  29. */
  30. function HttpServer(handlers) {
  31. this.handlers = handlers;
  32. this.server = http.createServer(this.handleRequest_.bind(this));
  33. }
  34. HttpServer.prototype.start = function (port) {
  35. this.port = port;
  36. this.server.listen(port);
  37. util.puts('Http Server running at http://localhost:' + port + '/');
  38. };
  39. HttpServer.prototype.parseUrl_ = function (urlString) {
  40. var parsed = url.parse(urlString);
  41. parsed.pathname = url.resolve('/', parsed.pathname);
  42. return url.parse(url.format(parsed), true);
  43. };
  44. HttpServer.prototype.handleRequest_ = function (req, res) {
  45. var logEntry = req.method + ' ' + req.url;
  46. if (req.headers['user-agent']) {
  47. logEntry += ' ' + req.headers['user-agent'];
  48. }
  49. util.puts(logEntry);
  50. req.url = this.parseUrl_(req.url);
  51. var handler = this.handlers[req.method];
  52. if (!handler) {
  53. res.writeHead(501);
  54. res.end();
  55. } else {
  56. handler.call(this, req, res);
  57. }
  58. };
  59. /**
  60. * Handles static content.
  61. */
  62. function StaticServlet() {
  63. }
  64. StaticServlet.MimeMap = {
  65. 'txt':'text/plain',
  66. 'html':'text/html',
  67. 'css':'text/css',
  68. 'xml':'application/xml',
  69. 'json':'application/json',
  70. 'js':'application/javascript',
  71. 'jpg':'image/jpeg',
  72. 'jpeg':'image/jpeg',
  73. 'gif':'image/gif',
  74. 'png':'image/png',
  75. 'svg':'image/svg+xml'
  76. };
  77. StaticServlet.prototype.handleRequest = function (req, res) {
  78. var self = this;
  79. var path = ('./' + req.url.pathname).replace('//', '/').replace(/%(..)/g, function (match, hex) {
  80. return String.fromCharCode(parseInt(hex, 16));
  81. });
  82. var parts = path.split('/');
  83. if (parts[parts.length - 1].charAt(0) === '.')
  84. return self.sendForbidden_(req, res, path);
  85. fs.stat(path, function (err, stat) {
  86. if (err)
  87. return self.sendMissing_(req, res, path);
  88. if (stat.isDirectory())
  89. //return self.sendRedirect_(req, res, path + 'app/index.html');
  90. return self.sendDirectory_(req, res, path );
  91. return self.sendFile_(req, res, path);
  92. //return self.sendRedirect_(req, res, path + 'app/index.html');
  93. });
  94. }
  95. StaticServlet.prototype.sendError_ = function (req, res, error) {
  96. res.writeHead(500, {
  97. 'Content-Type':'text/html'
  98. });
  99. res.write('<!doctype html>\n');
  100. res.write('<title>Internal Server Error</title>\n');
  101. res.write('<h1>Internal Server Error</h1>');
  102. res.write('<pre>' + escapeHtml(util.inspect(error)) + '</pre>');
  103. util.puts('500 Internal Server Error');
  104. util.puts(util.inspect(error));
  105. };
  106. StaticServlet.prototype.sendMissing_ = function (req, res, path) {
  107. path = path.substring(1);
  108. res.writeHead(404, {
  109. 'Content-Type':'text/html'
  110. });
  111. res.write('<!doctype html>\n');
  112. res.write('<title>404 Not Found</title>\n');
  113. res.write('<h1>Not Found</h1>');
  114. res.write(
  115. '<p>The requested URL ' +
  116. escapeHtml(path) +
  117. ' was not found on this server.</p>'
  118. );
  119. res.end();
  120. util.puts('404 Not Found: ' + path);
  121. };
  122. StaticServlet.prototype.sendForbidden_ = function (req, res, path) {
  123. path = path.substring(1);
  124. res.writeHead(403, {
  125. 'Content-Type':'text/html'
  126. });
  127. res.write('<!doctype html>\n');
  128. res.write('<title>403 Forbidden</title>\n');
  129. res.write('<h1>Forbidden</h1>');
  130. res.write(
  131. '<p>You do not have permission to access ' +
  132. escapeHtml(path) + ' on this server.</p>'
  133. );
  134. res.end();
  135. util.puts('403 Forbidden: ' + path);
  136. };
  137. StaticServlet.prototype.sendRedirect_ = function (req, res, redirectUrl) {
  138. res.writeHead(301, {
  139. 'Content-Type':'text/html',
  140. 'Location':redirectUrl
  141. });
  142. res.write('<!doctype html>\n');
  143. res.write('<title>301 Moved Permanently</title>\n');
  144. res.write('<h1>Moved Permanently</h1>');
  145. res.write(
  146. '<p>The document has moved <a href="' +
  147. redirectUrl +
  148. '">here</a>.</p>'
  149. );
  150. res.end();
  151. util.puts('301 Moved Permanently: ' + redirectUrl);
  152. };
  153. StaticServlet.prototype.sendFile_ = function (req, res, path) {
  154. var self = this;
  155. var file = fs.createReadStream(path);
  156. res.writeHead(200, {
  157. 'Content-Type':StaticServlet.
  158. MimeMap[path.split('.').pop()] || 'text/plain'
  159. });
  160. if (req.method === 'HEAD') {
  161. res.end();
  162. } else {
  163. file.on('data', res.write.bind(res));
  164. file.on('close', function () {
  165. res.end();
  166. });
  167. file.on('error', function (error) {
  168. self.sendError_(req, res, error);
  169. });
  170. }
  171. };
  172. StaticServlet.prototype.sendDirectory_ = function (req, res, path) {
  173. var self = this;
  174. if (path.match(/[^\/]$/)) {
  175. req.url.pathname += '/';
  176. var redirectUrl = url.format(url.parse(url.format(req.url)));
  177. return self.sendRedirect_(req, res, redirectUrl);
  178. }
  179. fs.readdir(path, function (err, files) {
  180. if (err)
  181. return self.sendError_(req, res, error);
  182. if (!files.length)
  183. return self.writeDirectoryIndex_(req, res, path, []);
  184. var remaining = files.length;
  185. files.forEach(function (fileName, index) {
  186. fs.stat(path + '/' + fileName, function (err, stat) {
  187. if (err)
  188. return self.sendError_(req, res, err);
  189. if (stat.isDirectory()) {
  190. files[index] = fileName + '/';
  191. }
  192. if (!(--remaining))
  193. return self.writeDirectoryIndex_(req, res, path, files);
  194. });
  195. });
  196. });
  197. };
  198. StaticServlet.prototype.writeDirectoryIndex_ = function (req, res, path, files) {
  199. path = path.substring(1);
  200. res.writeHead(200, {
  201. 'Content-Type':'text/html'
  202. });
  203. if (req.method === 'HEAD') {
  204. res.end();
  205. return;
  206. }
  207. res.write('<!doctype html>\n');
  208. res.write('<title>' + escapeHtml(path) + '</title>\n');
  209. res.write('<style>\n');
  210. res.write(' ol { list-style-type: none; font-size: 1.2em; }\n');
  211. res.write('</style>\n');
  212. res.write('<h1>Directory: ' + escapeHtml(path) + '</h1>');
  213. res.write('<ol>');
  214. files.forEach(function (fileName) {
  215. if (fileName.charAt(0) !== '.') {
  216. res.write('<li><a href="' +
  217. escapeHtml(fileName) + '">' +
  218. escapeHtml(fileName) + '</a></li>');
  219. }
  220. });
  221. res.write('</ol>');
  222. res.end();
  223. };
  224. // Must be last,
  225. main(process.argv);