server.js 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  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 = 8000;
  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. StaticServlet.MimeMap = {
  64. 'txt': 'text/plain',
  65. 'html': 'text/html',
  66. 'css': 'text/css',
  67. 'xml': 'application/xml',
  68. 'json': 'application/json',
  69. 'js': 'application/javascript',
  70. 'jpg': 'image/jpeg',
  71. 'jpeg': 'image/jpeg',
  72. 'gif': 'image/gif',
  73. 'png': 'image/png',
  74.   'svg': 'image/svg+xml'
  75. };
  76. StaticServlet.prototype.handleRequest = function(req, res) {
  77. var self = this;
  78. var path = ('../src/' + req.url.pathname).replace('//','/').replace(/%(..)/g, function(match, hex){
  79. return String.fromCharCode(parseInt(hex, 16));
  80. });
  81. var parts = path.split('/');
  82. if (parts[parts.length-1].charAt(0) === '.')
  83. return self.sendForbidden_(req, res, path);
  84. fs.stat(path, function(err, stat) {
  85. if (err)
  86. return self.sendMissing_(req, res, path);
  87. if (stat.isDirectory())
  88. return self.sendDirectory_(req, res, path);
  89. return self.sendFile_(req, res, path);
  90. });
  91. }
  92. StaticServlet.prototype.sendError_ = function(req, res, error) {
  93. res.writeHead(500, {
  94. 'Content-Type': 'text/html'
  95. });
  96. res.write('<!doctype html>\n');
  97. res.write('<title>Internal Server Error</title>\n');
  98. res.write('<h1>Internal Server Error</h1>');
  99. res.write('<pre>' + escapeHtml(util.inspect(error)) + '</pre>');
  100. util.puts('500 Internal Server Error');
  101. util.puts(util.inspect(error));
  102. };
  103. StaticServlet.prototype.sendMissing_ = function(req, res, path) {
  104. path = path.substring(1);
  105. res.writeHead(404, {
  106. 'Content-Type': 'text/html'
  107. });
  108. res.write('<!doctype html>\n');
  109. res.write('<title>404 Not Found</title>\n');
  110. res.write('<h1>Not Found</h1>');
  111. res.write(
  112. '<p>The requested URL ' +
  113. escapeHtml(path) +
  114. ' was not found on this server.</p>'
  115. );
  116. res.end();
  117. util.puts('404 Not Found: ' + path);
  118. };
  119. StaticServlet.prototype.sendForbidden_ = function(req, res, path) {
  120. path = path.substring(1);
  121. res.writeHead(403, {
  122. 'Content-Type': 'text/html'
  123. });
  124. res.write('<!doctype html>\n');
  125. res.write('<title>403 Forbidden</title>\n');
  126. res.write('<h1>Forbidden</h1>');
  127. res.write(
  128. '<p>You do not have permission to access ' +
  129. escapeHtml(path) + ' on this server.</p>'
  130. );
  131. res.end();
  132. util.puts('403 Forbidden: ' + path);
  133. };
  134. StaticServlet.prototype.sendRedirect_ = function(req, res, redirectUrl) {
  135. res.writeHead(301, {
  136. 'Content-Type': 'text/html',
  137. 'Location': redirectUrl
  138. });
  139. res.write('<!doctype html>\n');
  140. res.write('<title>301 Moved Permanently</title>\n');
  141. res.write('<h1>Moved Permanently</h1>');
  142. res.write(
  143. '<p>The document has moved <a href="' +
  144. redirectUrl +
  145. '">here</a>.</p>'
  146. );
  147. res.end();
  148. util.puts('301 Moved Permanently: ' + redirectUrl);
  149. };
  150. StaticServlet.prototype.sendFile_ = function(req, res, path) {
  151. var self = this;
  152. var file = fs.createReadStream(path);
  153. res.writeHead(200, {
  154. 'Content-Type': StaticServlet.
  155. MimeMap[path.split('.').pop()] || 'text/plain'
  156. });
  157. if (req.method === 'HEAD') {
  158. res.end();
  159. } else {
  160. file.on('data', res.write.bind(res));
  161. file.on('close', function() {
  162. res.end();
  163. });
  164. file.on('error', function(error) {
  165. self.sendError_(req, res, error);
  166. });
  167. }
  168. };
  169. StaticServlet.prototype.sendDirectory_ = function(req, res, path) {
  170. var self = this;
  171. if (path.match(/[^\/]$/)) {
  172. req.url.pathname += '/';
  173. var redirectUrl = url.format(url.parse(url.format(req.url)));
  174. return self.sendRedirect_(req, res, redirectUrl);
  175. }
  176. fs.readdir(path, function(err, files) {
  177. if (err)
  178. return self.sendError_(req, res, error);
  179. if (!files.length)
  180. return self.writeDirectoryIndex_(req, res, path, []);
  181. var remaining = files.length;
  182. files.forEach(function(fileName, index) {
  183. fs.stat(path + '/' + fileName, function(err, stat) {
  184. if (err)
  185. return self.sendError_(req, res, err);
  186. if (stat.isDirectory()) {
  187. files[index] = fileName + '/';
  188. }
  189. if (!(--remaining))
  190. return self.writeDirectoryIndex_(req, res, path, files);
  191. });
  192. });
  193. });
  194. };
  195. StaticServlet.prototype.writeDirectoryIndex_ = function(req, res, path, files) {
  196. path = path.substring(1);
  197. res.writeHead(200, {
  198. 'Content-Type': 'text/html'
  199. });
  200. if (req.method === 'HEAD') {
  201. res.end();
  202. return;
  203. }
  204. res.write('<!doctype html>\n');
  205. res.write('<title>' + escapeHtml(path) + '</title>\n');
  206. res.write('<style>\n');
  207. res.write(' ol { list-style-type: none; font-size: 1.2em; }\n');
  208. res.write('</style>\n');
  209. res.write('<h1>Directory: ' + escapeHtml(path) + '</h1>');
  210. res.write('<ol>');
  211. files.forEach(function(fileName) {
  212. if (fileName.charAt(0) !== '.') {
  213. res.write('<li><a href="' +
  214. escapeHtml(fileName) + '">' +
  215. escapeHtml(fileName) + '</a></li>');
  216. }
  217. });
  218. res.write('</ol>');
  219. res.end();
  220. };
  221. // Must be last,
  222. main(process.argv);