UNPKG

@cldn/web-ts

Version:
186 lines 15.9 kB
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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