Node.js 앱을 올바르게 종료

사진 제공: Aleksandar Cvetanovic on Unsplash


요청을 잘 처리하고 새 요청을 수락하지 않도록 하려면 앱을 올바르게 종료하는 것이 중요합니다. 웹 서버를 예로 들어 보겠습니다.

const http = require('http');

const server = http.createServer(function (req, res) {
  setTimeout(function () {
    res.writeHead(200, {'Content-Type': 'text/plain'});
    res.end('Hello World\n');
  }, 4000);
}).listen(9090, function (err) {
  console.log('listening http://localhost:9090/');
  console.log('pid is ' + process.pid);
});





보시다시피 서버가 제대로 종료되지 않고 처리 요청이 올바른 응답을 받지 못합니다. 먼저 문제를 해결하기 위해 노드 프로세스가 어떻게 종료되는지 이해해야 합니다.

프로세스는 종료되려고 할 때 신호를 받습니다. 그들은 다른 종류의 signals입니다. 특히 다음 세 가지에 초점을 맞출 것입니다.
  • SIGINT: 키보드에서 종료합니다(Ctrl + C).
  • SIGQUIT: 키보드에서 종료합니다(Ctrl +\). 또한 core dump 파일을 생성합니다.
  • SIGTERM: 운영 체제를 종료합니다(예: kill 명령 사용).

  • Node.js는 프로세스가 신호를 받으면 이벤트를 발생시킵니다. 이러한 이벤트에 대한 핸들러를 작성할 수 있습니다. 이 경우 보류 중인 요청을 처리하고 새 요청을 받지 않도록 서버를 닫습니다.

    // ...
    function handleExit(signal) {
      console.log(`Received ${signal}. Close my server properly.`)
      server.close(function () {
        process.exit(0);
      });
    }
    
    process.on('SIGINT', handleExit);
    process.on('SIGQUIT', handleExit);
    process.on('SIGTERM', handleExit);
    





    이제 우리 서버는 요청을 잘 처리하고 올바르게 종료합니다. 의 nice article에서 자세한 내용을 읽을 수 있습니다. 데이터베이스가 있는 서버를 올바르게 종료하는 방법에 대해 자세히 설명합니다.

    또한 death(만든 JP Richardson)이라는 이 논리를 처리하는 노드 모듈도 있습니다.

    const ON_DEATH = require('death')
    
    ON_DEATH(function(signal, err) {
      // clean up code here
    })
    



    때로는 충분하지 않습니다



    최근에 제대로 종료하기 위해 서버가 새로운 요청을 수락해야 하는 상황에 직면했습니다. 몇 가지 설명을 드리겠습니다. 내 서버가 웹후크를 구독했습니다. 이 웹후크는 구독 할당량이 제한되어 있습니다. 따라서 이 할당량을 초과하지 않으려면 서버 종료 시 제대로 구독을 취소해야 합니다. 구독 취소 워크플로는 다음과 같습니다.
  • webhook에 구독 취소 요청 보내기
  • 웹후크가 서버에 구독 취소 확인 요청을 보냈습니다
  • .
  • 서버는 구독 취소를 확인하기 위해 특정 토큰으로 응답해야 합니다
  • .

    이벤트 루프가 비어 있으면 Node.js 프로세스가 자동으로 닫힙니다. 1.과 2. 사이에서 볼 수 있듯이 이벤트 루프가 비어 있으므로 프로세스가 종료되고 성공적으로 구독을 취소할 수 없습니다.

    다음은 우리가 사용할 새 서버 코드베이스입니다.

    const server = http.createServer(function (req, res) {
      const params = qs.decode(req.url.split("?")[1]);
      if (params.mode) {
        res.writeHead(200);
        res.write(params.challenge);
        res.end();
      } else {
        let body = "";
        req.on("data", chunk => {
          body += chunk;
        });
        req.on("end", () => {
          console.log('event', JSON.parse(body))
          res.writeHead(200);
          res.end();
        });
      }
    }).listen(9090, function (err) {
      console.log('listening http://localhost:9090/');
      console.log('pid is ' + process.pid);
      fetch('http://localhost:3000/webhook?mode=subscribe&callback=http://localhost:9090')
    });
    


    두 가지 변경 사항이 있습니다. 서버가 포트 9090에서 수신을 시작하면 서버를 웹후크에 구독하라는 첫 번째 요청을 보냅니다.

    // ...
    
    fetch('http://localhost:3000/webhook?mode=subscribe&callback=http://localhost:9090')
    
    // ...
    


    또한 challenge 라는 토큰으로 응답하여 웹후크 구독을 확인할 수 있도록 서버의 요청 핸들러를 변경했습니다.

    // ...
    
    if (params.mode) {
      res.writeHead(200);
      res.write(params.challenge);
      res.end();
    } else {
     // ...
    }
    
    // ...
    


    웹후크에 요청을 보내도록 handleExit 함수의 구현을 변경해 보겠습니다. webhook에 서버 구독을 취소하도록 요청합니다.

    function handleExit(signal) {
      console.log(`Received ${signal}. Close my server properly.`)
      fetch('http://localhost:3000/webhook?mode=unsubscribe&callback=http://localhost:9090')
    }
    


    웹후크가 구독 취소를 확인할 때 서버 프로세스를 종료하라는 챌린지로 응답하는 코드를 업데이트해야 합니다.

    // ...
    
    if (params.mode) {
      res.writeHead(200);
      res.write(params.challenge);
      res.end();
      if (params.mode === 'unsubscribe') {
        server.close(function () {
          process.exit(0);
        });
      }
    } else {
      // ...
    }
    
    // ...
    


    웹후크가 구독 취소를 확인하면 서버를 닫아 새 요청을 받지 않고 프로세스를 올바르게 종료합니다. 이것이 Twitch API webhook 구독/구독 취소가 작동하는 방식입니다.

    서버를 종료하려고 할 때 서버가 어떻게 작동하는지 봅시다. 좀 더 시각적으로 보이도록 몇 가지 로그를 추가했습니다.



    보시다시피 제대로 종료되지 않습니다. 구독 취소를 확인하는 웹후크 요청을 받기 전에 서버의 프로세스가 종료됩니다. 따라서 webhook은 계속해서 이벤트를 서버로 보냅니다.

    이 문제를 해결하려면 Node.js 프로세스가 종료되지 않도록 해야 합니다. 프로세스를 일시 중지하고 exiting onprocess.stdin.resume과 같은 비활성화 기본 동작을 재정의하는 메서드Ctrl+C를 사용할 수 있습니다.

    const http = require('http');
    
    process.stdin.resume();
    
    // ...
    


    그리고 지금?



    이제 프로세스를 종료하기 전에 확인을 기다립니다.

    이 기사에 제시된 모든 소스로 repository을 만들었습니다.

    도움이 되길 바랍니다 🙌


    피드백 감사합니다 🙏 질문이 있으시면 저에게 트윗해주세요!

    좋은 웹페이지 즐겨찾기