@cldn/web-ts
Version:
Class-based Node.js web server
186 lines • 15.9 kB
JavaScript
import { SubnetList } from "@cldn/ip";
import EventEmitter from "node:events";
import http from "node:http";
import packageJson from "./package.json" with { type: "json" };
import { Request } from "./Request.js";
import { EmptyResponse } from "./response/index.js";
import { ThrowableResponse } from "./response/ThrowableResponse.js";
import { RouteRegistry } from "./routing/RouteRegistry.js";
import { ServerErrorRegistry } from "./ServerErrorRegistry.js";
/**
* An HTTP server.
* @see {@link Server.Events} for events.
*/
class Server extends EventEmitter {
/**
* Headers sent with every response.
*/
globalHeaders;
/**
* This server's route registry.
*/
routes = new RouteRegistry();
/** @internal */
_authenticators;
/**
* This server's error registry.
*/
errors = new ServerErrorRegistry();
server;
port;
copyOrigin;
handleConditionalRequests;
/**
* The network of remote addresses of proxies to trust.
*/
trustedProxies;
/**
* Create a new HTTP server.
* @param options Server options.
*/
constructor(options) {
super();
this.server = http.createServer({
joinDuplicateHeaders: true,
}, this.listener.bind(this));
this.globalHeaders = new Headers(options?.globalHeaders);
if (!this.globalHeaders.has("server"))
this.globalHeaders.set("Server", `${packageJson.name}/${packageJson.version}`);
this.port = options?.port;
this.copyOrigin = options?.copyOrigin ?? false;
this.handleConditionalRequests = options?.handleConditionalRequests ?? true;
this._authenticators = options?.authenticators ?? [];
this.trustedProxies = options?.trustedProxies ?? new SubnetList();
if (this.port !== undefined)
this.listen(this.port).then();
this.once("listening", () => {
if (this.listenerCount("error") === 0)
this.on("error", e => console.error("Internal Server Error:", e));
});
}
/** @internal **/
get _keepAliveTimeout() {
return this.server.keepAliveTimeout;
}
/**
* 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.
*/
async close(timeout = 5000) {
if (!this.server.listening)
throw new Error("Server is not listening.");
this.emit("closing");
let timeoutId;
await Promise.race([
new Promise(resolve => {
timeoutId = setTimeout(() => {
this.server.closeAllConnections();
resolve();
}, timeout);
}),
new Promise(resolve => {
clearTimeout(timeoutId);
this.server.close(() => resolve());
}),
]);
this.emit("closed");
}
/**
* Start listening for connections.
* @param port The HTTP listener port. From 1 to 65535. Ports 1–1023 require privileges.
*/
listen(port) {
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();
});
});
}
async listener(req, res) {
let apiRequest;
try {
apiRequest = Request.incomingMessage(req, this);
}
catch (e) {
if (e instanceof Request.BadUrlError) {
await this.errors._get(0 /* ServerErrorRegistry.ErrorCodes.BAD_URL */, null)._send(res);
return;
}
if (e instanceof Request.SocketClosedError)
return;
this.emit("error", e);
await this.errors._get(2 /* ServerErrorRegistry.ErrorCodes.INTERNAL */, null)._send(res);
return;
}
for (const [key, value] of this.globalHeaders)
apiRequest._responseHeaders.set(key, value);
if (this.copyOrigin) {
apiRequest._responseHeaders.set("access-control-allow-origin", apiRequest.headers.get("Origin") ?? "*");
apiRequest._responseHeaders.set("vary", "origin");
}
let response;
try {
response = await this.routes.handle(apiRequest);
}
catch (e) {
if (e instanceof ThrowableResponse) {
response = e.getResponse();
const cause = e.getError();
if (cause !== null)
this.emit("error", cause);
}
else if (e instanceof RouteRegistry.NoRouteError)
response = this.errors._get(1 /* ServerErrorRegistry.ErrorCodes.NO_ROUTE */, apiRequest);
else {
this.emit("error", e);
response = this.errors._get(2 /* ServerErrorRegistry.ErrorCodes.INTERNAL */, apiRequest);
}
}
await this.sendResponse(response, res, apiRequest);
}
async sendResponse(response, res, req) {
conditional: if (this.handleConditionalRequests
&& response.statusCode === 200
&& ["GET" /* Request.Method.GET */, "HEAD" /* Request.Method.HEAD */].includes(req.method)) {
const responseHeaders = response.allHeaders(res, req);
const etag = responseHeaders.get("etag");
const lastModified = responseHeaders.has("last-modified")
? new Date(responseHeaders.get("last-modified"))
: null;
if (etag === null && lastModified === null)
break conditional;
if (req.headers.has("if-match")) {
if (!this.getETags(req.headers.get("if-match"))
.filter(t => !t.startsWith("W/"))
.includes(etag))
return this.errors._get(3 /* ServerErrorRegistry.ErrorCodes.PRECONDITION_FAILED */, req)._send(res, req);
}
else if (req.headers.has("if-unmodified-since")) {
if (lastModified === null
|| lastModified.getTime() > new Date(req.headers.get("if-unmodified-since")).getTime())
return this.errors._get(3 /* ServerErrorRegistry.ErrorCodes.PRECONDITION_FAILED */, req)._send(res, req);
}
if (req.headers.has("if-none-match")) {
if (this.getETags(req.headers.get("if-none-match"))
.includes(etag))
return new EmptyResponse(responseHeaders, 304)._send(res, req);
}
else if (req.headers.has("if-modified-since")) {
if (lastModified !== null
&& lastModified.getTime() <= new Date(req.headers.get("if-modified-since")).getTime())
return new EmptyResponse(responseHeaders, 304)._send(res, req);
}
}
await response._send(res, req);
}
getETags(header) {
return header
.split(",")
.map(t => t.trim());
}
}
export { Server };
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiU2VydmVyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vc3JjL1NlcnZlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQVUsVUFBVSxFQUFDLE1BQU0sVUFBVSxDQUFDO0FBQzdDLE9BQU8sWUFBWSxNQUFNLGFBQWEsQ0FBQztBQUN2QyxPQUFPLElBQUksTUFBTSxXQUFXLENBQUM7QUFDN0IsT0FBTyxXQUFXLE1BQU0sZ0JBQWdCLENBQUMsT0FBTSxJQUFJLEVBQUUsTUFBTSxFQUFDLENBQUM7QUFFN0QsT0FBTyxFQUFDLE9BQU8sRUFBQyxNQUFNLGNBQWMsQ0FBQztBQUNyQyxPQUFPLEVBQUMsYUFBYSxFQUFDLE1BQU0scUJBQXFCLENBQUM7QUFFbEQsT0FBTyxFQUFDLGlCQUFpQixFQUFDLE1BQU0saUNBQWlDLENBQUM7QUFDbEUsT0FBTyxFQUFDLGFBQWEsRUFBQyxNQUFNLDRCQUE0QixDQUFDO0FBQ3pELE9BQU8sRUFBQyxtQkFBbUIsRUFBQyxNQUFNLDBCQUEwQixDQUFDO0FBRTdEOzs7R0FHRztBQUNILE1BQU0sTUFBVSxTQUFRLFlBQTJCO0lBQy9DOztPQUVHO0lBQ2EsYUFBYSxDQUFVO0lBRXZDOztPQUVHO0lBQ2EsTUFBTSxHQUFHLElBQUksYUFBYSxFQUFLLENBQUM7SUFFaEQsZ0JBQWdCO0lBQ0EsZUFBZSxDQUFxQjtJQUVwRDs7T0FFRztJQUNhLE1BQU0sR0FBRyxJQUFJLG1CQUFtQixFQUFLLENBQUM7SUFDckMsTUFBTSxDQUFjO0lBQ3BCLElBQUksQ0FBVTtJQUNkLFVBQVUsQ0FBVTtJQUNwQix5QkFBeUIsQ0FBVTtJQUVwRDs7T0FFRztJQUNhLGNBQWMsQ0FBVTtJQUV4Qzs7O09BR0c7SUFDSCxZQUFtQixPQUEyQjtRQUMxQyxLQUFLLEVBQUUsQ0FBQztRQUNSLElBQUksQ0FBQyxNQUFNLEdBQUcsSUFBSSxDQUFDLFlBQVksQ0FBQztZQUM1QixvQkFBb0IsRUFBRSxJQUFJO1NBQzdCLEVBQUUsSUFB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