Files
2026-10-22 17:07:08 +08:00

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);
}