server.close() でサーバーが停止しない? Node.js 停止トラブルシューティング完全ガイド

2025-05-27

server.close()とは?

Node.jsでHTTPサーバーやTCPサーバーを作成する際に、http.createServer()net.createServer()などを使用してサーバーオブジェクトを生成します。このサーバーオブジェクトには、close()というメソッドが用意されています。

server.close()は、そのサーバーオブジェクトに対して実行されるメソッドで、以下の役割を果たします。

  • 既存の接続を閉じる
    既に接続されているクライアントとの接続も、可能であれば閉じます。ただし、これは即時ではなく、既存の接続が終了するのを待つ場合もあります。
  • サーバーを停止する
    サーバーがクライアントからの接続を受け付けないようにします。つまり、新しい接続は拒否されます。

具体的な動作と注意点

  • server.close()は、サーバーがlisten()メソッドによってポートを監視している状態でのみ有効です。
  • server.close()を実行した後、サーバーオブジェクトを再度使用して新しい接続を受け付けることはできません。サーバーを再開するには、新しいサーバーオブジェクトを作成する必要があります。
  • server.close()は非同期的に動作するため、完了時にコールバック関数を渡すことができます。コールバック関数は、サーバーが完全に停止した後に実行されます。
  • server.close()を呼び出すと、サーバーは新しい接続を受け付けなくなりますが、既に確立されている接続は、その接続が終了するまで維持されます。

コード例

const http = require('http');

const server = http.createServer((req, res) => {
  res.end('Hello, World!');
});

server.listen(3000, () => {
  console.log('Server listening on port 3000');

  // サーバーを停止する
  setTimeout(() => {
    server.close(() => {
      console.log('Server closed');
    });
  }, 5000); // 5秒後にサーバーを停止する
});

この例では、サーバーが3000番ポートで起動し、5秒後にserver.close()が呼び出されてサーバーが停止します。コールバック関数内で"Server closed"がコンソールに出力されます。



一般的なエラーとトラブルシューティング

    • 原因
      • 既存の接続がまだ終了していない可能性があります。server.close()は、新しい接続を拒否しますが、既存の接続が完了するまで待機します。
      • サーバーがlisten()メソッドでポートを監視していない状態でserver.close()を呼び出している可能性があります。
      • サーバーが他の非同期処理(タイマー、ファイル処理など)を抱えて、それらの処理がserver.close()をブロックしている。
    • トラブルシューティング
      • 既存の接続が終了するまで待機する時間を長くするか、強制的に接続を終了させる方法を検討します。(ただし、強制終了はクライアント側でエラーを引き起こす可能性があります。)
      • server.listen()が呼び出されていることを確認します。
      • 非同期処理の完了を待機してからserver.close()を呼び出す。
      • デバッグツールやログを使用して、接続の状態やサーバーの動作を詳しく確認します。
  1. server.close()のコールバック関数が実行されない

    • 原因
      • サーバーが正常に停止していない可能性があります。
      • コールバック関数にエラーが含まれている可能性があります。
    • トラブルシューティング
      • サーバーの停止処理にエラーがないか確認します。
      • コールバック関数内のエラーハンドリングを適切に行います。
      • サーバーのイベントリスナー('close'イベント)を使い、コールバック関数とは別に、サーバーが閉じたことを確認する。
  2. server.close()後にエラーが発生する(ポートが解放されないなど)

    • 原因
      • OSレベルでポートが解放されるまでに時間がかかる場合があります。
      • サーバーが使用していたリソースが適切に解放されていない可能性があります。
    • トラブルシューティング
      • 少し時間をおいてから再度サーバーを起動してみます。
      • サーバーのコードを見直し、リソースの解放処理が適切に行われているか確認します。
      • OSのポート使用状況を確認し、他のプロセスがポートを使用していないか確認します。
  3. Error: Not runningが発生する。

    • 原因
      • server.listen()が呼ばれる前にserver.close()が呼ばれた。
      • すでにserver.close()が呼ばれたサーバーオブジェクトに対して、再度server.close()を呼んだ。
    • トラブルシューティング
      • server.listen()が呼ばれた後でserver.close()を呼ぶようにコードを修正する。
      • server.close()が呼ばれたかどうかをフラグなどで管理し、再度呼ばれることを防ぐ。

デバッグのヒント

  • OSのツール
    netstatlsofなどのOSのツールを使用して、ポートの使用状況やプロセス情報を確認します。
  • イベントリスナー
    サーバーオブジェクトのイベントリスナー('close'、'error'など)を使用して、サーバーの状態変化を監視します。
  • エラーハンドリング
    try...catchブロックを使用してエラーを捕捉し、適切なエラー処理を行います。
  • デバッガ
    Node.jsのデバッガを使用して、コードの実行をステップごとに確認し、変数の値や関数の呼び出しを調べます。
  • ログ出力
    サーバーの起動、停止、接続の確立、エラーなどをログに出力し、動作を追跡します。


例1:基本的なサーバーの起動と停止

const http = require('http');

const server = http.createServer((req, res) => {
  res.end('Hello, World!');
});

server.listen(3000, () => {
  console.log('サーバーがポート3000で起動しました。');

  // 5秒後にサーバーを停止する
  setTimeout(() => {
    server.close(() => {
      console.log('サーバーが停止しました。');
    });
  }, 5000);
});

この例では、HTTPサーバーを3000番ポートで起動し、5秒後にserver.close()を呼び出してサーバーを停止しています。server.close()のコールバック関数内で、サーバーが停止したことを示すメッセージを出力しています。

例2:既存の接続が終了するのを待ってからサーバーを停止する

const http = require('http');

const server = http.createServer((req, res) => {
  res.end('Hello, World!');
});

server.listen(3000, () => {
  console.log('サーバーがポート3000で起動しました。');

  // 10秒後にサーバーを停止する
  setTimeout(() => {
    console.log('サーバーを停止します...');
    server.close(() => {
      console.log('サーバーが停止しました。');
    });
  }, 10000);

  // 5秒後に接続を維持したままリクエストを送信
  setTimeout(() => {
    http.get('http://localhost:3000', (res) => {
      console.log('リクエストを送信しました。');
      res.on('data', (chunk) => {
        console.log(`データ: ${chunk}`);
      });
      res.on('end', () => {
        console.log('レスポンスが終了しました。');
      });
    });
  }, 5000);
});

この例では、5秒後にリクエストを送信し、10秒後にサーバーを停止します。server.close()は、リクエストのレスポンスが終了するまで待機してからサーバーを停止します。

例3:エラーハンドリングを含むサーバーの停止

const http = require('http');

const server = http.createServer((req, res) => {
  res.end('Hello, World!');
});

server.listen(3000, () => {
  console.log('サーバーがポート3000で起動しました。');

  // 5秒後にサーバーを停止する
  setTimeout(() => {
    server.close((err) => {
      if (err) {
        console.error('サーバー停止中にエラーが発生しました:', err);
      } else {
        console.log('サーバーが正常に停止しました。');
      }
    });
  }, 5000);
});

この例では、server.close()のコールバック関数でエラーハンドリングを行っています。サーバーの停止中にエラーが発生した場合、エラーメッセージを出力します。

const http = require('http');

const server = http.createServer((req, res) => {
  res.end('Hello, World!');
});

server.listen(3000, () => {
  console.log('サーバーがポート3000で起動しました。');

  // 5秒後にサーバーを強制的に停止する(推奨されません)
  setTimeout(() => {
    server.close(() => {
      console.log('サーバーが停止しました。');
    });
    server.unref(); // listenしているソケットを閉じる。
  }, 5000);
});


シグナルハンドリングによるサーバー停止

Node.jsプロセスは、OSからのシグナル(例:SIGINTSIGTERM)を受け取ることができます。これらのシグナルを捕捉し、サーバーを安全に停止することができます。

const http = require('http');

const server = http.createServer((req, res) => {
  res.end('Hello, World!');
});

server.listen(3000, () => {
  console.log('サーバーがポート3000で起動しました。');
});

// SIGINT(Ctrl+C)シグナルを捕捉
process.on('SIGINT', () => {
  console.log('SIGINTシグナルを受け取りました。サーバーを停止します...');
  server.close(() => {
    console.log('サーバーが停止しました。');
    process.exit(0); // プロセスを終了
  });
});

// SIGTERMシグナルを捕捉
process.on('SIGTERM', () => {
  console.log('SIGTERMシグナルを受け取りました。サーバーを停止します...');
  server.close(() => {
    console.log('サーバーが停止しました。');
    process.exit(0); // プロセスを終了
  });
});

この例では、SIGINT(Ctrl+C)とSIGTERMシグナルを捕捉し、server.close()を呼び出してサーバーを停止しています。process.exit(0)は、プロセスを正常に終了させるために使用されます。

クラスタリングによるサーバー再起動

Node.jsのクラスタリング機能を使用すると、複数のワーカープロセスでサーバーを起動できます。ワーカープロセスが終了した場合、マスタープロセスが新しいワーカープロセスを起動することで、サーバーの可用性を高めることができます。

const cluster = require('cluster');
const http = require('http');
const os = require('os');

if (cluster.isMaster) {
  // マスタープロセス
  const numCPUs = os.cpus().length;
  for (let i = 0; i < numCPUs; i++) {
    cluster.fork(); // ワーカープロセスを生成
  }

  cluster.on('exit', (worker, code, signal) => {
    console.log(`ワーカープロセス ${worker.process.pid} が終了しました。新しいワーカープロセスを起動します...`);
    cluster.fork(); // 新しいワーカープロセスを生成
  });
} else {
  // ワーカープロセス
  const server = http.createServer((req, res) => {
    res.end('Hello, World!');
  });

  server.listen(3000, () => {
    console.log(`ワーカープロセス ${process.pid} がポート3000で起動しました。`);
  });
}

この例では、マスタープロセスがワーカープロセスを生成し、ワーカープロセスがサーバーを起動します。ワーカープロセスが終了した場合、マスタープロセスが新しいワーカープロセスを起動します。この方法では、server.close()を直接呼び出すのではなく、ワーカープロセスを再起動することでサーバーを再起動します。

サーバーマネージャーによる管理

PM2やDockerなどのサーバーマネージャーを使用すると、サーバーの起動、停止、再起動を管理できます。これらのツールは、サーバーの監視やログ管理などの機能も提供します。

  • Docker
    • docker run -p 3000:3000 my-node-appでコンテナを起動
    • docker stop <container_id>でコンテナを停止
    • docker restart <container_id>でコンテナを再起動
  • PM2
    • pm2 start app.jsでサーバーを起動
    • pm2 stop app.jsでサーバーを停止
    • pm2 restart app.jsでサーバーを再起動

これらのツールを使用すると、server.close()を直接呼び出す必要はありません。