Skip to content

Commit

Permalink
feat: make close function as public
Browse files Browse the repository at this point in the history
The goal is to allow end-user to close the server and trigger
the close promises callbacks.

Issue: #25
  • Loading branch information
gquittet committed Jun 10, 2024
1 parent e5d5fd8 commit d9e0a1e
Show file tree
Hide file tree
Showing 6 changed files with 36 additions and 16 deletions.
41 changes: 30 additions & 11 deletions src/core/improvedServer.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import type { Server } from "#interface/server";
import type { IStatus } from "#interface/status";
import type http from "node:http";
import { exit } from "node:process";
import config from "#config/index";
import SocketsPool from "#core/socketsPool";
import State from "#core/state";
import onRequest from "#util/onRequest";
import sleep from "#util/sleep";

const { livenessEndpoint, readinessEndpoint } = config;

const improvedServer = <TServer extends Server>(
server: TServer,
serverStatus: IStatus,
): TServer & { stop: () => Promise<void> } => {
const improvedServer = <TServer extends Server>(server: TServer, serverStatus: IStatus) => {
const { healthCheck, kubernetes } = config;
const socketsPool = SocketsPool();
const secureSocketsPool = SocketsPool();
Expand Down Expand Up @@ -65,28 +65,47 @@ const improvedServer = <TServer extends Server>(
);
}

const stop = async (): Promise<void> => {
const stop = async (
args: { value: number; body?: Error; type?: string } = { value: 0 },
): Promise<void> => {
if (!server.listening || stopping) {
return;
}

stopping = true;

const { timeout, closePromises } = config;

let error: Error | undefined;
if (args.body && args.body.message) {
error = args.body;
} else if (args.type) {
error = new Error(args.type);
}

serverStatus.set(State.SHUTTING_DOWN, error);

await sleep(timeout);

await Promise.all(closePromises.map((closePromise) => closePromise()));

server.removeAllListeners("request");
server.on("request", (_: http.IncomingMessage, res: http.ServerResponse) => {
if (!res.headersSent) res.setHeader("connection", "close");
});

await Promise.all([socketsPool.closeAll(), secureSocketsPool.closeAll()]);

return new Promise((resolve, reject) => {
server.close((error) => {
if (error) {
reject(error);
} else {
resolve();
await new Promise((resolve, reject) => {
server.close((err) => {
if (err) {
return reject(err);
}
return resolve(error);
});

serverStatus.set(State.SHUTDOWN, error);
exit(args.value);
});
};

Expand Down
5 changes: 2 additions & 3 deletions src/core/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { EventEmitter } from "events";
import ImprovedServer from "#core/improvedServer";
import Status from "#core/status";
import init from "#util/init";
import shutdown from "#util/shutdown";

const core = (server: Server): ICore => {
const _emitter = new EventEmitter();
Expand All @@ -15,8 +14,8 @@ const core = (server: Server): ICore => {
init: function () {
return init(this);
},
shutdown: function (type: string, value: number, error?: Error) {
return shutdown(_server, this)(type, value, error);
stop: function (args?: { type?: string; value: number; body?: Error }) {
return _server.stop(args);
},
// eslint-disable-next-line @typescript-eslint/no-explicit-any
on: (name: string, callback: (...args: any[]) => void) => _emitter.on(name, callback),
Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const buildGracefulServer = (server: Server, options?: IGracefulServerOptions):
isReady: () => gracefulServer.status.isReady(),
setReady: () => gracefulServer.status.setReady(),
on: gracefulServer.on,
stop: async () => await gracefulServer.stop(),
};
};

Expand Down
2 changes: 1 addition & 1 deletion src/interface/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import type { EventEmitter } from "events";
export type ICore = {
status: IStatus;
init: () => ICore;
shutdown: (type: string, value: number, error?: Error) => Promise<void>;
stop: (args?: { type?: string; value: number; body?: Error }) => Promise<void>;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
on: (name: string, callback: (...args: any[]) => void) => EventEmitter;
};
1 change: 1 addition & 0 deletions src/interface/gracefulServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ export type IGracefulServer = {
setReady: () => void;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
on: (name: string, callback: (...args: any[]) => void) => EventEmitter;
stop: () => Promise<void>;
};
2 changes: 1 addition & 1 deletion src/util/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import signals from "#core/signals";
const init = (parent: ICore) => {
for (const signal of signals) {
(process as NodeJS.EventEmitter).on(signal.type, async (body) => {
await parent.shutdown(signal.type, signal.code, body);
await parent.stop({ type: signal.type, value: signal.code, body });
});
}
return parent;
Expand Down

0 comments on commit d9e0a1e

Please sign in to comment.