Skip to content

Commit b3d9f8f

Browse files
authored
Improve server close and listen (#41)
2 parents bb07719 + 4447f45 commit b3d9f8f

File tree

1 file changed

+40
-13
lines changed

1 file changed

+40
-13
lines changed

src/Server.ts

Lines changed: 40 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -26,27 +26,29 @@ class Server extends EventEmitter<Server.Events> {
2626
*/
2727
public readonly errors = new ServerErrorRegistry();
2828
private readonly server: http.Server;
29+
private readonly port?: number;
2930
private readonly copyOrigin: boolean;
3031
private readonly handleConditionalRequests: boolean;
3132

3233
/**
3334
* Create a new HTTP server.
3435
* @param options Server options.
3536
*/
36-
public constructor(options: Server.Options) {
37+
public constructor(options?: Server.Options) {
3738
super();
3839
this.server = http.createServer({
3940
joinDuplicateHeaders: true,
4041
}, this.listener.bind(this));
4142

42-
this.globalHeaders = new Headers(options.globalHeaders);
43+
this.globalHeaders = new Headers(options?.globalHeaders);
4344
if (!this.globalHeaders.has("server"))
4445
this.globalHeaders.set("Server", `cldn/${packageJson.version}`);
4546

46-
this.copyOrigin = options.copyOrigin ?? false;
47-
this.handleConditionalRequests = options.handleConditionalRequests ?? true;
47+
this.port = options?.port;
48+
this.copyOrigin = options?.copyOrigin ?? false;
49+
this.handleConditionalRequests = options?.handleConditionalRequests ?? true;
4850

49-
this.server.listen(options.port, process.env.HOST, () => this.emit("listening"));
51+
if (this.port !== undefined) this.listen(this.port).then();
5052

5153
this.once("listening", () => {
5254
if (this.listenerCount("error") === 0)
@@ -59,20 +61,45 @@ class Server extends EventEmitter<Server.Events> {
5961
return this.server.keepAliveTimeout;
6062
}
6163

62-
public async close(): Promise<void> {
64+
/**
65+
* Close the server. Will stop accepting new connections and wait for existing connections to close.
66+
* @param [timeout=5000] Maximum time to wait for existing connections to close before forcibly closing them.
67+
*/
68+
public async close(timeout = 5000): Promise<void> {
69+
if (!this.server.listening)
70+
throw new Error("Server is not listening.");
6371
this.emit("closing");
72+
let timeoutId: NodeJS.Timeout;
6473
await Promise.race([
6574
new Promise<void>(resolve => {
75+
timeoutId = setTimeout(() => {
76+
this.server.closeAllConnections();
77+
resolve();
78+
}, timeout)
79+
}),
80+
new Promise<void>(resolve => {
81+
clearTimeout(timeoutId);
6682
this.server.close(() => resolve());
6783
}),
68-
new Promise<void>(resolve => setTimeout(() => {
69-
this.server.closeAllConnections();
70-
resolve();
71-
}, 5000)),
7284
]);
7385
this.emit("closed");
7486
}
7587

88+
/**
89+
* Start listening for connections.
90+
* @param port The HTTP listener port. From 1 to 65535. Ports 1–1023 require privileges.
91+
*/
92+
public listen(port: number): Promise<void> {
93+
if (this.server.listening)
94+
throw new Error("Server is already listening.");
95+
return new Promise(resolve => {
96+
this.server.listen(port, process.env.HOST, () => {
97+
this.emit("listening", port, process.env.HOST);
98+
resolve();
99+
});
100+
});
101+
}
102+
76103
private async listener(req: http.IncomingMessage, res: http.ServerResponse) {
77104
let apiRequest: Request;
78105
try {
@@ -175,9 +202,9 @@ namespace Server {
175202
export interface Options {
176203
/**
177204
* The HTTP listener port. From 1 to 65535. Ports 1–1023 require
178-
* privileges.
205+
* privileges. If not set, {@link Server#listen|Server.listen()} must be called manually.
179206
*/
180-
readonly port: number;
207+
readonly port?: number;
181208

182209
/**
183210
* Headers to send with every response.
@@ -207,7 +234,7 @@ namespace Server {
207234
/**
208235
* Server is listening and ready to accept connections.
209236
*/
210-
listening: [void];
237+
listening: [port: number, host?: string];
211238

212239
/**
213240
* The server is closing and not accepting new connections.

0 commit comments

Comments
 (0)