【TypeScript】【Express】 expressに graceful shutdownを設定する
はじめに
この記事では、Expressアプリケーションに graceful shutdown(優雅な終了処理)を設定する方法について手を動かして勉強します。
実装
3秒後にレスポンスを返す、エンドポイントを用意しました。
import express, { Application, Request, Response } from "express";
const app: Application = express();const PORT = 3000;
app.use(express.json());app.use(express.urlencoded({ extended: true }));
const sleep = (ms: number) => new Promise((res) => setTimeout(res, ms));
app.post( "/", async ( { body: { id } }: Request<{}, {}, { id: number }, {}>, res: Response ) => { console.log("requestID", id) await sleep(3000); return res.status(200).send({ response: id, }); });
const server = app.listen(PORT, () => { console.log(`dev server running at: http://localhost:${PORT}/`);});
process.on("SIGINT", () => { server.close(() => { console.log("Process terminated."); });});process.on("SIGTERM", () => { server.close(() => { console.log("Process terminated."); });});
クライアント側からは、以下のcurlを実行することで、3秒後にレスポンスを取得できます。
$ curl -X POST http://localhost:3000 -d "id=1"{"response":"1"}
リクエストの途中でサーバを落としてみる
forループで10回リクエストを投げます。
$ for i in {1..10}; do curl -X POST http://localhost:3000 -d "id=${i}"; echo ; done
まずはじめにgraceful shutdownをしないで実行します。
import express, { Application, Request, Response } from "express";
const app: Application = express();const PORT = 3000;
app.use(express.json());app.use(express.urlencoded({ extended: true }));
const sleep = (ms: number) => new Promise((res) => setTimeout(res, ms));
app.post( "/", async ( { body: { id } }: Request<{}, {}, { id: number }, {}>, res: Response ) => { console.log("requestID", id) await sleep(3000); return res.status(200).send({ response: id, }); });
const server = app.listen(PORT, () => { console.log(`dev server running at: http://localhost:${PORT}/`);});
// 以下をコメントアウト// process.on("SIGINT", () => {// server.close(() => {// console.log("Process terminated.");// });// });// process.on("SIGTERM", () => {// server.close(() => {// console.log("Process terminated.");// });// });
ターミナルの左側でexpressを実行し、右側でリクエストを投げてみます。
expressをrequestID: 3
が出たあと、サーバを停止します。
右側でcurlを実行してた方は、レスポンスを待たずにEmpty Reploy from server
が返ってきました。
続いて、graceful shutdownのコードで実行してみます。
import express, { Application, Request, Response } from "express";
const app: Application = express();const PORT = 3000;
app.use(express.json());app.use(express.urlencoded({ extended: true }));
const sleep = (ms: number) => new Promise((res) => setTimeout(res, ms));
app.post( "/", async ( { body: { id } }: Request<{}, {}, { id: number }, {}>, res: Response ) => { console.log("requestID", id) await sleep(3000); return res.status(200).send({ response: id, }); });
const server = app.listen(PORT, () => { console.log(`dev server running at: http://localhost:${PORT}/`);});
process.on("SIGINT", () => { server.close(() => { console.log("Process terminated."); });});process.on("SIGTERM", () => { server.close(() => { console.log("Process terminated."); });});
今度はrequestID 3
の後にサーバを落としても、curlでリクエストした方は、{"response": "3"}
が返ってきました。
closeの仕様を確認する
server.close()
を呼んでいますが、closeはどういった仕様なのでしょうか?
型定義のコメントを読むと、以下のことがわかります。
- closeが呼び出されると、新規コネクションの受付を停止する
- 既存の接続は維持する
- すべての接続が終了すると、サーバが終了し、‘close’イベントを発生させる
- callbackは’close’イベントが発生した時に一度だけ実行される
- callbackの’Error’は、サーバをcloseするとき、openでない場合に発生する
実装はこちら https://github.com/nodejs/node/blob/c311dc43cd2579ecb3c9f86a802899e6aa163dae/lib/net.js#L2147-L2184
つまり、先ほどの話では、requestID = 3 は実行中だったのでレスポンスを返したが、requestID = 4 以降は新規コネクションだったためエラーになったということですね。
今回使ったコードはblog-code/2023/04/express-graceful-shutdown/に置いています。
補足
process.on(event, listener)
のeventにはNodeJS.Signalsを指定できます。
Expressのapp.listen()
から作成したserverは、nodeのhttp.Serverを作成しています。
const server = app.listen(PORT, () => { console.log(`dev server running at: http://localhost:${PORT}/`);});
import { TcpSocketConnectOpts, Socket, Server as NetServer, LookupFunction } from 'node:net';
class Server< Request extends typeof IncomingMessage = typeof IncomingMessage, Response extends typeof ServerResponse = typeof ServerResponse,> extends NetServer {
types/node/net.d.tsに書いてあるServerクラス https://github.com/DefinitelyTyped/DefinitelyTyped/blob/9a6a0cdd493eecf71c2d5d428d3b7ba28574af70/types/node/net.d.ts#L472