server.js 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113
  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 { readFileSync, readdirSync, existsSync, watch} from 'node:fs';
  17. import { argv } from 'node:process';
  18. import { createServer } from 'node:https';
  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. 'priv-key': { type: 'value', short: 'k'},
  61. cert: { type: 'value', short: 'c'},
  62. port:
  63. {
  64. type: 'value', short: 'p', default: 8000,
  65. convert: v => parseInt(v), validate: v => v ? true : false,
  66. }
  67. });
  68. const root = process.argv.at(-1);
  69. // Test if files exist
  70. if (!existsSync(opts['priv-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['priv-key']),
  76. cert: readFileSync(opts.cert)
  77. };
  78. // Set up a watch to update the 'files' array on directory change
  79. watch(root, { recursive: true }, (eventType, file) => {
  80. if (!file)
  81. die("System don't support reporting filename on directory change");
  82. if (eventType == 'rename') {
  83. if (files.includes(file))
  84. files.splice(files.indexOf(file), 1);
  85. else
  86. files.push(file);
  87. }
  88. });
  89. const server = createServer(options, (req, res) => {
  90. let [head, contents] = fetch(req.url);
  91. res.writeHead(head.statusCode, {'Content-Type': head.contentType});
  92. res.write(contents);
  93. res.end();
  94. });
  95. console.log(`Starting server at port ${opts.port}...`)
  96. server.listen(opts.port);