server.js 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119
  1. #!/usr/bin/env node
  2. /*
  3. * Simple web server. Only handles GET requests (for now at least). Takes the
  4. * site's root directory as the first argument.
  5. *
  6. * TODO:
  7. * - Write a help message
  8. * - Implement proper error handling
  9. * - Remove die
  10. * - Use an approach closer to JavaScript
  11. * - Fallback method for updating 'files' on systems that don't report filename
  12. * - Optionally print diagnostic information (verbose flag)
  13. */
  14. "use strict";
  15. import path from 'node:path';
  16. import argv from 'node:process';
  17. import { createServer } from 'node:https';
  18. import { readFileSync, readdirSync, existsSync, watch} from 'node:fs';
  19. import { die } from './die.js';
  20. import { getOpt } from './getOpt.js';
  21. function fetch(url) {
  22. let head = { statusCode: '', contentType: ''};
  23. let contents = '';
  24. const notFoundMsg = `
  25. <!doctype html>
  26. <head>
  27. <meta charset="utf-8">
  28. <title>404: not found</title>
  29. </head>
  30. <body>
  31. <h1>404: not found</h1>
  32. <p>
  33. The server couldn't find the requested page. Possible reasons
  34. include:
  35. <ol>
  36. <li>Poorly formatted URL
  37. <li>The requested page doesn't yet, or will never, exist.
  38. <li>A server error has ocurred. In this case, the fault lies on the
  39. braindead developer (me) who coded it.
  40. </ol>
  41. </body>
  42. </html>`
  43. let baseUrl = path.basename(url);
  44. if (!baseUrl) baseUrl = 'index';
  45. if (baseUrl.endsWith('.css'))
  46. head.contentType = 'text/css';
  47. else
  48. head.contentType = 'text/html';
  49. const file = files.find(f => f.includes(baseUrl));
  50. if (file) {
  51. head.statusCode = 200;
  52. contents = readFileSync(path.join(root, file));
  53. } else {
  54. head.statusCode = 404;
  55. contents = notFoundMsg;
  56. }
  57. return [head, contents];
  58. }
  59. const opts = getOpt({
  60. verbose: { type: 'flag', short: 'v'},
  61. quiet: { type: 'flag', short: 'q'},
  62. key: { type: 'value', short: 'k'},
  63. cert: { type: 'value', short: 'c'},
  64. port: {
  65. type: 'value', short: 'p', default: 8000,
  66. convert: v => parseInt(v), validate: v => v ? true : false,
  67. }}, { bundle: true });
  68. const root = process.argv.at(-1);
  69. // Test if files exist
  70. if (!existsSync(opts.key)) die("Couldn't stat private-key");
  71. if (!existsSync(opts.cert)) die("Couldn't stat certificate");
  72. if (!existsSync(root)) die("Couldn't stat root dir");
  73. const files = readdirSync(root, { recursive: true });
  74. const options = {
  75. key: readFileSync(opts.key),
  76. cert: readFileSync(opts.cert)
  77. };
  78. // Set up a watch to update the 'files' array on directory change
  79. if (opts.verbose) console.log('Setting up watches...');
  80. watch(root, { recursive: true }, (eventType, file) => {
  81. if (!file)
  82. die("System don't support reporting filename on directory change");
  83. if (eventType == 'rename') {
  84. if (opts.verbose) console.log('Detected rename event.');
  85. if (files.includes(file)) {
  86. if (opts.verbose) console.log(`Removing file ${file} to tree`);
  87. files.splice(files.indexOf(file), 1);
  88. } else {
  89. if (opts.verbose) console.log(`Addding file ${file} to tree`);
  90. files.push(file);
  91. }
  92. }
  93. });
  94. const server = createServer(options, (req, res) => {
  95. if (opts.verbose) console.log(`Fetching URL: ${req.url}`);
  96. let [head, contents] = fetch(decodeURIComponent(req.url));
  97. res.writeHead(head.statusCode, {'Content-Type': head.contentType});
  98. res.write(contents);
  99. res.end();
  100. });
  101. if (!opts.quiet) console.log(`Starting server at port ${opts.port}...`);
  102. server.listen(opts.port);