Skip to content
53 changes: 40 additions & 13 deletions src/Server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,27 +26,29 @@ class Server extends EventEmitter<Server.Events> {
*/
public readonly errors = new ServerErrorRegistry();
private readonly server: http.Server;
private readonly port?: number;
private readonly copyOrigin: boolean;
private readonly handleConditionalRequests: boolean;

/**
* Create a new HTTP server.
* @param options Server options.
*/
public constructor(options: Server.Options) {
public constructor(options?: Server.Options) {
super();
this.server = http.createServer({
joinDuplicateHeaders: true,
}, this.listener.bind(this));

this.globalHeaders = new Headers(options.globalHeaders);
this.globalHeaders = new Headers(options?.globalHeaders);
if (!this.globalHeaders.has("server"))
this.globalHeaders.set("Server", `cldn/${packageJson.version}`);

this.copyOrigin = options.copyOrigin ?? false;
this.handleConditionalRequests = options.handleConditionalRequests ?? true;
this.port = options?.port;
this.copyOrigin = options?.copyOrigin ?? false;
this.handleConditionalRequests = options?.handleConditionalRequests ?? true;

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

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

public async close(): Promise<void> {
/**
* Close the server. Will stop accepting new connections and wait for existing connections to close.
* @param [timeout=5000] Maximum time to wait for existing connections to close before forcibly closing them.
*/
public async close(timeout = 5000): Promise<void> {
if (!this.server.listening)
throw new Error("Server is not listening.");
this.emit("closing");
let timeoutId: NodeJS.Timeout;
await Promise.race([
new Promise<void>(resolve => {
timeoutId = setTimeout(() => {
this.server.closeAllConnections();
resolve();
}, timeout)
}),
new Promise<void>(resolve => {
clearTimeout(timeoutId);
this.server.close(() => resolve());
}),
new Promise<void>(resolve => setTimeout(() => {
this.server.closeAllConnections();
resolve();
}, 5000)),
]);
this.emit("closed");
}

/**
* Start listening for connections.
* @param port The HTTP listener port. From 1 to 65535. Ports 1–1023 require privileges.
*/
public listen(port: number): Promise<void> {
if (this.server.listening)
throw new Error("Server is already listening.");
return new Promise(resolve => {
this.server.listen(port, process.env.HOST, () => {
this.emit("listening", port, process.env.HOST);
resolve();
});
});
}

private async listener(req: http.IncomingMessage, res: http.ServerResponse) {
let apiRequest: Request;
try {
Expand Down Expand Up @@ -175,9 +202,9 @@ namespace Server {
export interface Options {
/**
* The HTTP listener port. From 1 to 65535. Ports 1–1023 require
* privileges.
* privileges. If not set, {@link Server#listen|Server.listen()} must be called manually.
*/
readonly port: number;
readonly port?: number;

/**
* Headers to send with every response.
Expand Down Expand Up @@ -207,7 +234,7 @@ namespace Server {
/**
* Server is listening and ready to accept connections.
*/
listening: [void];
listening: [port: number, host?: string];

/**
* The server is closing and not accepting new connections.
Expand Down