54 lines
2.1 KiB
TypeScript
54 lines
2.1 KiB
TypeScript
import type {Server, IncomingMessage, ServerResponse} from 'node:http';
|
|
import type {Duplex} from 'node:stream';
|
|
|
|
interface Mount {
|
|
target: Server | null;
|
|
acquire(target: Server): () => void;
|
|
}
|
|
|
|
const mounts = new WeakMap<Server, Map<string, Mount>>();
|
|
|
|
// Keep one dormant dispatcher per host/path: later Socket.IO services may wrap
|
|
// this listener, so removing it on stop would corrupt their request chain.
|
|
export function mount_http(host: Server, path: string, target: Server): () => void {
|
|
let entries = mounts.get(host);
|
|
if (!entries) {
|
|
entries = new Map();
|
|
mounts.set(host, entries);
|
|
}
|
|
const existing = entries.get(path);
|
|
if (existing) return existing.acquire(target);
|
|
const original = host.listeners('request');
|
|
const prefix = `${path}/`;
|
|
const matches = (request: IncomingMessage) => (request.url ?? '').split('?')[0].startsWith(prefix);
|
|
const mount: Mount = {
|
|
target: null,
|
|
acquire(next) {
|
|
if (this.target) throw new Error(`Relay path already mounted: ${path}`);
|
|
this.target = next;
|
|
return () => { if (this.target === next) this.target = null; };
|
|
},
|
|
};
|
|
const request_listener = (request: IncomingMessage, response: ServerResponse) => {
|
|
if (matches(request)) {
|
|
if (mount.target) mount.target.emit('request', request, response);
|
|
else { response.statusCode = 503; response.end('Relay unavailable'); }
|
|
} else {
|
|
for (const listener of original) listener.call(host, request, response);
|
|
}
|
|
};
|
|
const upgrade_listener = (request: IncomingMessage, socket: Duplex, head: Buffer) => {
|
|
if (!matches(request)) return;
|
|
if (mount.target) mount.target.emit('upgrade', request, socket, head);
|
|
else socket.destroy();
|
|
};
|
|
for (const listener of original) host.removeListener('request', listener);
|
|
host.on('request', request_listener);
|
|
host.on('upgrade', upgrade_listener);
|
|
entries.set(path, mount);
|
|
host.on('close', () => {
|
|
mount.target = null;
|
|
});
|
|
return mount.acquire(target);
|
|
}
|