stoppable.js 1.8 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546
  1. import https from 'https';
  2. export class Stopper {
  3. constructor(server) {
  4. this.server = server;
  5. this.requestCountPerSocket = new Map();
  6. this.stopped = false;
  7. server.on(server instanceof https.Server ? 'secureConnection' : 'connection', (socket) => {
  8. this.requestCountPerSocket.set(socket, 0);
  9. socket.once('close', () => this.requestCountPerSocket.delete(socket));
  10. });
  11. server.on('request', (req, res) => {
  12. this.requestCountPerSocket.set(req.socket, (this.requestCountPerSocket.get(req.socket) ?? 0) + 1);
  13. res.once('finish', () => {
  14. const pending = (this.requestCountPerSocket.get(req.socket) ?? 0) - 1;
  15. this.requestCountPerSocket.set(req.socket, pending);
  16. if (this.stopped && pending === 0) {
  17. req.socket.end();
  18. }
  19. });
  20. });
  21. }
  22. async stop(hardDestroyAbortSignal) {
  23. let gracefully = true;
  24. await new Promise((resolve) => setImmediate(resolve));
  25. this.stopped = true;
  26. const onAbort = () => {
  27. gracefully = false;
  28. this.requestCountPerSocket.forEach((_, socket) => socket.end());
  29. setImmediate(() => {
  30. this.requestCountPerSocket.forEach((_, socket) => socket.destroy());
  31. });
  32. };
  33. hardDestroyAbortSignal?.addEventListener('abort', onAbort);
  34. const closePromise = new Promise((resolve) => this.server.close(() => {
  35. hardDestroyAbortSignal?.removeEventListener('abort', onAbort);
  36. resolve();
  37. }));
  38. this.requestCountPerSocket.forEach((requests, socket) => {
  39. if (requests === 0)
  40. socket.end();
  41. });
  42. await closePromise;
  43. return gracefully;
  44. }
  45. }
  46. //# sourceMappingURL=stoppable.js.map