Skip to content

Commit 3493ba7

Browse files
authored
Add server events (#40)
2 parents 966d6ea + 606b41d commit 3493ba7

File tree

1 file changed

+58
-19
lines changed

1 file changed

+58
-19
lines changed

src/Server.ts

Lines changed: 58 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import EventEmitter from "node:events";
12
import http from "node:http";
23
import packageJson from "../package.json" with {type: "json"};
34
import {Request} from "./Request.js";
@@ -6,7 +7,11 @@ import {Response} from "./response/Response.js";
67
import {RouteRegistry} from "./routing/RouteRegistry.js";
78
import {ServerErrorRegistry} from "./ServerErrorRegistry.js";
89

9-
class Server {
10+
/**
11+
* An HTTP server.
12+
* @see {@link Server.Events} for events.
13+
*/
14+
class Server extends EventEmitter<Server.Events> {
1015
/**
1116
* Headers sent with every response.
1217
*/
@@ -15,20 +20,20 @@ class Server {
1520
* This server's route registry.
1621
*/
1722
public readonly routes = new RouteRegistry();
18-
private readonly server: http.Server;
19-
private readonly copyOrigin: boolean;
20-
private readonly handleConditionalRequests: boolean;
21-
2223
/**
2324
* This server's error registry.
2425
*/
2526
public readonly errors = new ServerErrorRegistry();
27+
private readonly server: http.Server;
28+
private readonly copyOrigin: boolean;
29+
private readonly handleConditionalRequests: boolean;
2630

2731
/**
2832
* Create a new HTTP server.
2933
* @param options Server options.
3034
*/
3135
public constructor(options: Server.Options) {
36+
super();
3237
this.server = http.createServer({
3338
joinDuplicateHeaders: true,
3439
}, this.listener.bind(this));
@@ -40,14 +45,33 @@ class Server {
4045
this.copyOrigin = options.copyOrigin ?? false;
4146
this.handleConditionalRequests = options.handleConditionalRequests ?? true;
4247

43-
this.server.listen(options.port);
48+
this.server.listen(options.port, process.env.HOST, () => this.emit("listening"));
49+
50+
this.once("listening", () => {
51+
if (this.listenerCount("error") === 0)
52+
this.on("error", e => console.error("Internal Server Error:", e));
53+
});
4454
}
4555

4656
/** @internal **/
4757
public get _keepAliveTimeout() {
4858
return this.server.keepAliveTimeout;
4959
}
5060

61+
public async close(): Promise<void> {
62+
this.emit("closing");
63+
await Promise.race([
64+
new Promise<void>(resolve => {
65+
this.server.close(() => resolve());
66+
}),
67+
new Promise<void>(resolve => setTimeout(() => {
68+
this.server.closeAllConnections();
69+
resolve();
70+
}, 5000)),
71+
]);
72+
this.emit("closed");
73+
}
74+
5175
private async listener(req: http.IncomingMessage, res: http.ServerResponse) {
5276
let apiRequest: Request;
5377
try {
@@ -79,7 +103,7 @@ class Server {
79103
if (e instanceof RouteRegistry.NoRouteError)
80104
response = this.errors._get(ServerErrorRegistry.ErrorCodes.NO_ROUTE, apiRequest);
81105
else {
82-
console.error("Internal Server Error:", e);
106+
this.emit("error", e as any);
83107
response = this.errors._get(ServerErrorRegistry.ErrorCodes.INTERNAL, apiRequest);
84108
}
85109
}
@@ -131,18 +155,6 @@ class Server {
131155
.split(",")
132156
.map(t => t.trim())
133157
}
134-
135-
public close(): Promise<void> {
136-
return Promise.race([
137-
new Promise<void>(resolve => {
138-
this.server.close(() => resolve());
139-
}),
140-
new Promise<void>(resolve => setTimeout(() => {
141-
this.server.closeAllConnections();
142-
resolve();
143-
}, 5000)),
144-
]);
145-
}
146158
}
147159

148160
namespace Server {
@@ -176,6 +188,33 @@ namespace Server {
176188
*/
177189
readonly handleConditionalRequests?: boolean;
178190
}
191+
192+
/**
193+
* Server events map
194+
*/
195+
export interface Events {
196+
/**
197+
* Server is listening and ready to accept connections.
198+
*/
199+
listening: [void];
200+
201+
/**
202+
* The server is closing and not accepting new connections.
203+
*/
204+
closing: [void];
205+
206+
/**
207+
* All connections have ended and the server has closed.
208+
*/
209+
closed: [void];
210+
211+
/**
212+
* An uncaught error occurred. Client has been sent {@link ServerErrorRegistry.ErrorCodes.INTERNAL} error.
213+
* If no listener is registered when the server begins listening for the first time, a default listener will be
214+
* added to direct errors to stderr.
215+
*/
216+
error: [Error];
217+
}
179218
}
180219

181220
export {Server};

0 commit comments

Comments
 (0)