@interopio/gateway-server 0.4.0-beta → 0.5.0-beta

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/changelog.md +18 -0
  2. package/dist/gateway-ent.cjs +25 -2
  3. package/dist/gateway-ent.cjs.map +2 -2
  4. package/dist/gateway-ent.js +25 -2
  5. package/dist/gateway-ent.js.map +2 -2
  6. package/dist/index.cjs +1182 -109
  7. package/dist/index.cjs.map +4 -4
  8. package/dist/index.js +1182 -109
  9. package/dist/index.js.map +4 -4
  10. package/dist/metrics/publisher/rest.cjs +60 -0
  11. package/dist/metrics/publisher/rest.cjs.map +7 -0
  12. package/dist/metrics/publisher/rest.js +23 -0
  13. package/dist/metrics/publisher/rest.js.map +7 -0
  14. package/package.json +12 -12
  15. package/types/gateway-ent.d.ts +2 -1
  16. package/dist/metrics-rest.cjs +0 -21440
  17. package/dist/metrics-rest.cjs.map +0 -7
  18. package/dist/metrics-rest.js +0 -21430
  19. package/dist/metrics-rest.js.map +0 -7
  20. package/src/common/compose.ts +0 -40
  21. package/src/gateway/ent/config.ts +0 -174
  22. package/src/gateway/ent/index.ts +0 -18
  23. package/src/gateway/ent/logging.ts +0 -89
  24. package/src/gateway/ent/server.ts +0 -34
  25. package/src/gateway/metrics/rest.ts +0 -20
  26. package/src/gateway/ws/core.ts +0 -90
  27. package/src/index.ts +0 -3
  28. package/src/logger.ts +0 -6
  29. package/src/mesh/connections.ts +0 -101
  30. package/src/mesh/rest-directory/routes.ts +0 -38
  31. package/src/mesh/ws/broker/core.ts +0 -163
  32. package/src/mesh/ws/cluster/core.ts +0 -107
  33. package/src/mesh/ws/relays/core.ts +0 -159
  34. package/src/metrics/routes.ts +0 -86
  35. package/src/server/address.ts +0 -47
  36. package/src/server/cors.ts +0 -311
  37. package/src/server/exchange.ts +0 -379
  38. package/src/server/monitoring.ts +0 -167
  39. package/src/server/types.ts +0 -69
  40. package/src/server/ws-client-verify.ts +0 -79
  41. package/src/server.ts +0 -316
  42. package/src/utils.ts +0 -10
@@ -1,167 +0,0 @@
1
- import getLogger from '../logger.js';
2
- import {getHeapStatistics, writeHeapSnapshot, HeapInfo} from 'node:v8';
3
- import {PathLike} from 'node:fs';
4
- import {access, mkdir, rename, unlink} from 'node:fs/promises';
5
-
6
- const log = getLogger('monitoring');
7
-
8
- export type Options = typeof DEFAULT_OPTIONS;
9
-
10
- export type Command = 'run' | 'dump' | 'stop';
11
- export type Channel = (command?: Command) => Promise<boolean>;
12
-
13
- const DEFAULT_OPTIONS = {
14
- memoryLimit: 1024 * 1024 * 1024, // 1GB
15
- reportInterval: 10 * 60 * 1000, // 10 min
16
- dumpLocation: '.', // current folder
17
- maxBackups: 10,
18
- dumpPrefix: 'Heap'
19
- }
20
-
21
- function fetchStats(): HeapInfo {
22
- return getHeapStatistics();
23
- }
24
-
25
- async function dumpHeap(opts: Options) {
26
- const prefix = opts.dumpPrefix ?? 'Heap';
27
- const target = `${opts.dumpLocation}/${prefix}.heapsnapshot`;
28
- if (log.enabledFor('debug')) {
29
- log.debug(`starting heap dump in ${target}`);
30
- }
31
-
32
- await fileExists(opts.dumpLocation)
33
- .catch(async (_) => {
34
- if (log.enabledFor('debug')) {
35
- log.debug(`dump location ${opts.dumpLocation} does not exists. Will try to create it`);
36
- }
37
- try {
38
- await mkdir(opts.dumpLocation, {recursive: true});
39
- log.info(`dump location dir ${opts.dumpLocation} successfully created`);
40
- } catch (e) {
41
- log.error(`failed to create dump location ${opts.dumpLocation}`);
42
- }
43
- });
44
- const dumpFileName = writeHeapSnapshot(target);
45
- log.info(`heap dumped`);
46
- try {
47
- log.debug(`rolling snapshot backups`);
48
- const lastFileName = `${opts.dumpLocation}/${prefix}.${opts.maxBackups}.heapsnapshot`;
49
- await fileExists(lastFileName)
50
- .then(async () => {
51
- if (log.enabledFor('debug')) {
52
- log.debug(`deleting ${lastFileName}`);
53
- }
54
- try {
55
- await unlink(lastFileName);
56
- } catch (e) {
57
- log.warn(`failed to delete ${lastFileName}`, e);
58
- }
59
- })
60
- .catch(() => {
61
- /* do nothing*/
62
- });
63
- for (let i = opts.maxBackups - 1; i > 0; i--) {
64
- const currentFileName = `${opts.dumpLocation}/${prefix}.${i}.heapsnapshot`;
65
- const nextFileName = `${opts.dumpLocation}/${prefix}.${i + 1}.heapsnapshot`;
66
- await fileExists(currentFileName)
67
- .then(async () => {
68
- try {
69
- await rename(currentFileName, nextFileName);
70
- } catch (e) {
71
- log.warn(`failed to rename ${currentFileName} to ${nextFileName}`, e);
72
- }
73
- })
74
- .catch(() => {
75
- /* do nothing*/
76
- });
77
- }
78
- const firstFileName = `${opts.dumpLocation}/${prefix}.${1}.heapsnapshot`;
79
- try {
80
- await rename(dumpFileName, firstFileName);
81
- } catch (e) {
82
- log.warn(`failed to rename ${dumpFileName} to ${firstFileName}`, e);
83
- }
84
- log.debug('snapshots rolled');
85
- } catch (e) {
86
- log.error('error rolling backups', e);
87
- throw e;
88
- }
89
- }
90
-
91
- async function fileExists(path: PathLike): Promise<void> {
92
- log.trace(`checking file ${path}`);
93
- await access(path);
94
- }
95
-
96
- async function processStats(stats: HeapInfo, state: {
97
- memoryLimitExceeded: boolean,
98
- snapshot?: boolean
99
- }, opts: Options) {
100
- if (log.enabledFor('debug')) {
101
- log.debug(`processing heap stats ${JSON.stringify(stats)}`);
102
- }
103
- const limit = Math.min(opts.memoryLimit, (0.95 * stats.heap_size_limit));
104
- const used = stats.used_heap_size;
105
- log.info(`heap stats ${JSON.stringify(stats)}`);
106
- if (used >= limit) {
107
- log.warn(`used heap ${used} bytes exceeds memory limit ${limit} bytes`);
108
- if (state.memoryLimitExceeded) {
109
- delete state.snapshot;
110
- } else {
111
- state.memoryLimitExceeded = true;
112
- state.snapshot = true;
113
- }
114
- await dumpHeap(opts);
115
- } else {
116
- state.memoryLimitExceeded = false;
117
- delete state.snapshot;
118
- }
119
- }
120
-
121
- export function start(opts?: Partial<Options>): Options & { channel: Channel } {
122
- const merged: Options = {...DEFAULT_OPTIONS, ...opts};
123
-
124
- let stopped = false;
125
- const state = {memoryLimitExceeded: false};
126
- const report = async () => {
127
- const stats = fetchStats();
128
- await processStats(stats, state, merged);
129
- }
130
- const interval = setInterval(report, merged.reportInterval);
131
- const channel = async (command?: Command) => {
132
- if (!stopped) {
133
- command ??= 'run';
134
- switch (command) {
135
- case 'run': {
136
- await report();
137
- break;
138
- }
139
- case 'dump': {
140
- await dumpHeap(merged);
141
- break;
142
- }
143
- case 'stop': {
144
- stopped = true;
145
- clearInterval(interval);
146
- log.info('exit memory diagnostic');
147
- break;
148
- }
149
- }
150
-
151
- }
152
- return stopped;
153
- }
154
-
155
- return {...merged, channel};
156
- }
157
-
158
- async function run({channel}: { channel: Channel }, command?: Command) {
159
- if (!await channel(command)) {
160
- log.warn(`cannot execute command "${command}" already closed`)
161
- }
162
- }
163
-
164
-
165
- export async function stop(m: { channel: Channel }) {
166
- return await run(m, 'stop');
167
- }
@@ -1,69 +0,0 @@
1
- export type Middleware<Request extends ServerHttpRequest = ServerHttpRequest, Response extends ServerHttpResponse = ServerHttpResponse> = ((context: WebExchange<Request, Response>, next: () => Promise<void>) => Promise<void>)[];
2
-
3
- export abstract class WebExchange<Request extends ServerHttpRequest = ServerHttpRequest, Response extends ServerHttpResponse = ServerHttpResponse> {
4
- abstract readonly request: Request
5
- abstract readonly response: Response
6
-
7
- get method(): string | undefined {
8
- return this.request.method;
9
- }
10
-
11
- get path(): string | null | undefined {
12
- return this.request.path;
13
- }
14
- }
15
- export type ReadonlyHeaderValue = string | undefined;
16
- export type HeaderValue = number | ReadonlyHeaderValue;
17
- export type ReadonlyHeaderValues = (readonly string[]) | ReadonlyHeaderValue
18
- export type HeaderValues = (readonly string[]) | HeaderValue
19
-
20
- export interface HttpHeaders {
21
- get(name: string): HeaderValues
22
- list(name: string): string[]
23
- one(name: string): HeaderValue
24
- has(name: string): boolean
25
- keys(): IteratorObject<string>
26
- }
27
-
28
- export interface ReadonlyHttpHeaders extends HttpHeaders {
29
- get(name: string): ReadonlyHeaderValues
30
- one(name: string): string | undefined
31
-
32
- }
33
- export interface MutableHttpHeaders extends HttpHeaders {
34
- get(name: string): HeaderValues
35
- one(name: string): HeaderValue
36
- set(name: string, value: HeaderValues): this
37
- add(name: string, value: string | (readonly string[])): this
38
- }
39
-
40
- export interface HttpMessage<Headers = HttpHeaders> {
41
- readonly headers: Headers
42
- }
43
-
44
- export interface HttpRequest<Headers = HttpHeaders> extends HttpMessage<Headers> {
45
- readonly path: string,
46
- readonly method?: string
47
- readonly URL: URL
48
- readonly protocol: string
49
- /**
50
- * hostname[:port]
51
- */
52
- readonly host?: string
53
- readonly body: Promise<Blob>
54
- }
55
-
56
- export interface HttpResponse<Headers = HttpHeaders> extends HttpMessage<Headers> {
57
- readonly statusCode: number
58
- }
59
-
60
- export type ServerHttpRequest = HttpRequest<ReadonlyHttpHeaders> /* & {
61
- // readonly text: Promise<string>
62
- // readonly json: Promise<unknown>
63
- // readonly socket: http.IncomingMessage['socket']
64
- // readonly _req: http.IncomingMessage
65
- }*/
66
-
67
- export interface ServerHttpResponse extends HttpResponse<MutableHttpHeaders> {
68
- statusCode: number
69
- }
@@ -1,79 +0,0 @@
1
- import {IOGateway} from '@interopio/gateway';
2
- import {GatewayServer} from '../../gateway-server';
3
- import getLogger from '../logger.js';
4
-
5
- const log = getLogger('gateway.ws.client-verify');
6
-
7
- export type ProcessedOriginFilters
8
- = Required<Omit<GatewayServer.OriginFilters, 'blacklist' | 'whitelist'>>
9
- //| Required<Omit<GatewayServer.OriginFilters, 'block' | 'allow'>>
10
- ;
11
-
12
- function acceptsMissing(originFilters: ProcessedOriginFilters): boolean {
13
- switch (originFilters.missing) {
14
- case 'allow': // fall-through
15
- case 'whitelist':
16
- return true;
17
- case 'block': // fall-through
18
- case 'blacklist':
19
- return false;
20
- default:
21
- return false;
22
- }
23
- }
24
-
25
- function tryMatch(originFilters: ProcessedOriginFilters, origin: string): boolean | undefined {
26
- const block = originFilters.block ?? originFilters['blacklist'];
27
- const allow = originFilters.allow ?? originFilters['whitelist'];
28
- if (block.length > 0 && IOGateway.Filtering.valuesMatch(block, origin)) {
29
- log.warn(`origin ${origin} matches block filter`);
30
- return false;
31
- } else if (allow.length > 0 && IOGateway.Filtering.valuesMatch(allow, origin)) {
32
- if (log.enabledFor('debug')) {
33
- log.debug(`origin ${origin} matches allow filter`);
34
- }
35
- return true;
36
- }
37
- }
38
-
39
- function acceptsNonMatched(originFilters: ProcessedOriginFilters): boolean {
40
- switch (originFilters.non_matched) {
41
- case 'allow': // fall-through
42
- case 'whitelist':
43
- return true;
44
- case 'block': // fall-through
45
- case 'blacklist':
46
- return false;
47
- default:
48
- return false;
49
- }
50
- }
51
-
52
- export function acceptsOrigin(origin?: string, originFilters?: ProcessedOriginFilters): boolean {
53
- if (!originFilters) {
54
- return true;
55
- }
56
- if (!origin) {
57
- return acceptsMissing(originFilters);
58
- } else {
59
- const matchResult: boolean | undefined = tryMatch(originFilters, origin);
60
- if (matchResult) {
61
- return matchResult;
62
- } else {
63
- return acceptsNonMatched(originFilters);
64
- }
65
- }
66
- }
67
-
68
- export function regexifyOriginFilters(originFilters?: GatewayServer.OriginFilters): ProcessedOriginFilters | undefined {
69
- if (originFilters) {
70
- const block = (originFilters.block ?? originFilters.blacklist ?? []).map(IOGateway.Filtering.regexify);
71
- const allow = (originFilters.allow ?? originFilters.whitelist ?? []).map(IOGateway.Filtering.regexify);
72
- return {
73
- non_matched: originFilters.non_matched ?? 'allow',
74
- missing: originFilters.missing ?? 'allow',
75
- allow,
76
- block,
77
- }
78
- }
79
- }
package/src/server.ts DELETED
@@ -1,316 +0,0 @@
1
- import {WebSocketServer} from 'ws';
2
- import http from 'node:http';
3
- import https from 'node:https';
4
- import {SecureContextOptions} from 'node:tls';
5
- import {AddressInfo} from 'node:net';
6
- import {readFileSync} from 'node:fs';
7
- import {AsyncLocalStorage} from 'node:async_hooks';
8
- import {IOGateway} from '@interopio/gateway';
9
- import wsGateway from './gateway/ws/core.js';
10
- import NodeConnections from './mesh/connections.js';
11
- import restDirectory from './mesh/rest-directory/routes.js';
12
- import meshBroker from './mesh/ws/broker/core.js';
13
- import meshRelays from './mesh/ws/relays/core.js';
14
- import meshCluster from './mesh/ws/cluster/core.js';
15
- import metrics from './metrics/routes.js';
16
- import {compose} from './common/compose.js';
17
- import {WebExchange, Middleware} from './server/types.js';
18
- import {DefaultWebExchange, HttpServerRequest, HttpServerResponse} from './server/exchange.js';
19
- import {socketKey} from './utils.js';
20
- import getLogger from './logger.js';
21
- import {localIp, portRange} from './server/address.js';
22
- import * as monitoring from './server/monitoring.js';
23
- import {acceptsOrigin, ProcessedOriginFilters, regexifyOriginFilters} from './server/ws-client-verify.js';
24
- import {GatewayServer} from '../gateway-server';
25
- import cors from './server/cors.ts';
26
-
27
- const logger = getLogger('app');
28
-
29
- function secureContextOptions(ssl: GatewayServer.SslConfig): SecureContextOptions {
30
- const options: SecureContextOptions = {};
31
- if (ssl.key) options.key = readFileSync(ssl.key);
32
- if (ssl.cert) options.cert = readFileSync(ssl.cert);
33
- if (ssl.ca) options.ca = readFileSync(ssl.ca);
34
- return options;
35
- }
36
-
37
- type RequestHandler = (req: http.IncomingMessage, res: http.ServerResponse) => void;
38
-
39
- function createListener(middleware: Middleware<HttpServerRequest, HttpServerResponse>,
40
- routes: Map<string, RouteInfo>) {
41
- const storage = new AsyncLocalStorage();
42
-
43
- const listener = compose<WebExchange<HttpServerRequest, HttpServerResponse>>(
44
- async ({response}, next) => {
45
- response.headers.set('server', 'gateway-server');
46
- await next();
47
- },
48
- ...cors({
49
- origins: {allow: [/http:\/\/localhost(:\d+)?/]},
50
- methods: {allow: ['GET', 'HEAD', 'POST', 'DELETE']},
51
- headers: {allow: '*'},
52
- credentials: {allow: true}
53
- }),
54
- ...middleware,
55
- async ({request, response}, next) => {
56
- if (request.method === 'GET' && request.path === '/health') {
57
- response.statusCode = 200;
58
- response._res.end(http.STATUS_CODES[200]);
59
- } else {
60
- await next();
61
- }
62
- },
63
- async ({request, response}, next) => {
64
- if (request.method === 'GET' && request.path === '/') {
65
- response._res.end(`io.Gateway Server`);
66
- } else {
67
- await next();
68
- }
69
- },
70
- async ({request, response}, _next) => {
71
- const route = routes.get(request.path);
72
- if (route) {
73
- response.statusCode = 426;
74
- response._res
75
- .appendHeader('Upgrade', 'websocket')
76
- .appendHeader('Connection', 'Upgrade')
77
- .appendHeader('Content-Type', 'text/plain');
78
- response._res.end(`This service [${request.path}] requires use of the websocket protocol.`);
79
- } else {
80
- response.statusCode = 404;
81
- response._res.end(http.STATUS_CODES[404]);
82
- }
83
- }
84
- );
85
-
86
- return (request: http.IncomingMessage, response: http.ServerResponse) => {
87
- const exchange = new DefaultWebExchange(new HttpServerRequest(request), new HttpServerResponse(response));
88
- return storage.run(exchange, async () => {
89
- if (logger.enabledFor('debug')) {
90
- const socket = exchange.request._req.socket;
91
- if (logger.enabledFor('debug')) {
92
- logger.debug(`received ${exchange.method} request for ${exchange.path} from ${socket.remoteAddress}:${socket.remotePort}`);
93
- }
94
- }
95
- return await listener(exchange);
96
- });
97
- };
98
- }
99
-
100
- function promisify<T>(fn: (callback?: (err?: Error) => void) => T): Promise<T> {
101
- return new Promise<T>((resolve, reject) => {
102
- const r = fn((err?: Error) => {
103
- if (err) {
104
- reject(err);
105
- } else {
106
- resolve(r);
107
- }
108
- });
109
- });
110
- }
111
-
112
- type RouteInfo = {
113
- readonly default?: boolean,
114
- readonly ping?: number,
115
- readonly maxConnections?: number
116
- readonly originFilters?: ProcessedOriginFilters
117
- readonly factory: (server: { endpoint: string, wss: WebSocketServer }) => Promise<{
118
- info?: string,
119
- close?: () => Promise<void>
120
- }>,
121
- // set later in listening
122
- wss?: WebSocketServer,
123
- close?: () => Promise<void>
124
- }
125
-
126
- function memoryMonitor(config?: GatewayServer.ServerConfig['memory']) {
127
- if (config) {
128
- return monitoring.start({
129
- memoryLimit: config.memory_limit,
130
- dumpLocation: config.dump_location,
131
- dumpPrefix: config.dump_prefix,
132
- reportInterval: config.report_interval,
133
- maxBackups: config.max_backups
134
- });
135
- }
136
- }
137
-
138
- function regexAwareReplacer<T>(_key: string, value: T): string | T {
139
- return value instanceof RegExp ? value.toString() : value;
140
- }
141
-
142
- export const Factory = async (options: GatewayServer.ServerConfig): Promise<GatewayServer.Server> => {
143
- const ssl = options.ssl;
144
- const createServer = ssl ? (options: http.ServerOptions, handler: RequestHandler) => https.createServer({...options, ...secureContextOptions(ssl)}, handler) : (options: http.ServerOptions, handler: RequestHandler) => http.createServer(options, handler);
145
- const monitor = memoryMonitor(options.memory);
146
- const middleware: Middleware<HttpServerRequest, HttpServerResponse> = [];
147
- const routes: Map<string, RouteInfo> = new Map<string, RouteInfo>();
148
- const gw = IOGateway.Factory({...options.gateway});
149
- if (options.gateway) {
150
- const config = options.gateway;
151
- routes.set(config.route ?? '/', {
152
- default: config.route === undefined,
153
- ping: options.gateway.ping,
154
- factory: wsGateway.bind(gw),
155
- maxConnections: config.limits?.max_connections,
156
- originFilters: regexifyOriginFilters(config.origins)
157
- });
158
- }
159
- if (options.mesh) {
160
- const connections = new NodeConnections(options.mesh.timeout ?? 60000);
161
- middleware.push(...restDirectory(connections));
162
- const ping = options.mesh.ping ?? 30000;
163
- routes.set('/broker', {factory: meshBroker, ping: ping});
164
- routes.set('/cluster', {factory: meshCluster, ping: ping});
165
- routes.set('/relays', {factory: meshRelays, ping: ping});
166
- }
167
- if (options.metrics) {
168
- middleware.push(...(await metrics(options.metrics)));
169
- }
170
-
171
- const ports = portRange(options.port ?? 0);
172
- const host = options.host;
173
- const serverP: Promise<http.Server> = new Promise((resolve, reject) => {
174
- const onSocketError = (err: Error) => logger.error(`socket error: ${err}`, err);
175
- const server = createServer(
176
- {},
177
- createListener(middleware, routes));
178
-
179
- server.on('error', (e: Error) => {
180
- if (e['code'] === 'EADDRINUSE') {
181
- logger.debug(`port ${e['port']} already in use on address ${e['address']}`);
182
- const {value: port} = ports.next();
183
- if (port) {
184
- logger.info(`retry starting server on port ${port} and host ${host ?? '<unspecified>'}`);
185
- server.close();
186
- server.listen(port, host);
187
- } else {
188
- logger.warn(`all configured port(s) ${options.port} are in use. closing...`);
189
- server.close();
190
- reject(e);
191
- }
192
- } else {
193
- logger.error(`server error: ${e.message}`, e);
194
- reject(e);
195
- }
196
- });
197
- server
198
- .on('listening', async () => {
199
- const info = server.address() as AddressInfo;
200
- for (const [path, route] of routes) {
201
- try {
202
- logger.info(`creating ws server for [${path}]. max connections: ${route.maxConnections ?? '<unlimited>'}, origin filters: ${route.originFilters ? JSON.stringify(route.originFilters, regexAwareReplacer) : '<none>'}`);
203
- const wss = new WebSocketServer({noServer: true});
204
- const endpoint = `${ssl ? 'wss' : 'ws'}://${localIp}:${info.port}${path}`;
205
- const handler = await route.factory({endpoint, wss});
206
- const pingInterval = route.ping;
207
- if (pingInterval) {
208
- const pingIntervalId = setInterval(() => {
209
- for (const client of wss.clients) {
210
- if (client['connected'] === false) {
211
- client.terminate();
212
- }
213
- client['connected'] = false;
214
- client.ping();
215
- }
216
- }, pingInterval);
217
- wss.on('close', () => {
218
- clearInterval(pingIntervalId);
219
- });
220
- }
221
- route.wss = wss;
222
- route.close = handler.close?.bind(handler);
223
- } catch (e) {
224
- logger.warn(`failed to init route ${path}`, e);
225
- }
226
- }
227
- logger.info(`http server listening on ${info.address}:${info.port}`);
228
- resolve(server);
229
- });
230
- server
231
- .on('upgrade', (req, socket, head) => {
232
- socket.addListener('error', onSocketError);
233
- try {
234
- const request = new HttpServerRequest(req);
235
- const path = request.path ?? '/';
236
- const route = (routes.get(path) ?? Array.from(routes.values()).find(route => {
237
- if (path === '/' && route.default === true) {
238
- return true;
239
- }
240
- }));
241
- const host = request.host;
242
- const info = socketKey(request.socket);
243
- if (route?.wss) {
244
- socket.removeListener('error', onSocketError);
245
- const wss = route.wss;
246
- if (route.maxConnections !== undefined && wss.clients?.size >= route.maxConnections) {
247
- logger.warn(`${info} dropping ws connection request from ${host} on ${path}. max connections exceeded.`);
248
- socket.destroy();
249
- return;
250
- }
251
-
252
- const origin = request.headers['origin'];
253
- if (!acceptsOrigin(origin, route.originFilters)) {
254
- logger.info(`${info} dropping ws connection request from ${host} on ${path}. origin ${origin ?? '<missing>'}`);
255
- socket.destroy();
256
- return;
257
- }
258
- if (logger.enabledFor('debug')) {
259
- logger.debug(`${info} accepted new ws connection request from ${host} on ${path}`);
260
- }
261
-
262
- wss.handleUpgrade(req, socket, head, (ws) => {
263
- ws.on('pong', () => ws['connected'] = true);
264
- ws.on('ping', () => {
265
- });
266
- wss.emit('connection', ws, req);
267
- });
268
- } else {
269
- logger.warn(`${info} rejected upgrade request from ${host} on ${path}`);
270
- socket.destroy();
271
- }
272
- } catch (err) {
273
- logger.error(`upgrade error: ${err}`, err);
274
- }
275
- })
276
- .on('close', async () => {
277
- logger.info(`http server closed.`);
278
- });
279
- try {
280
- const {value: port} = ports.next();
281
- server.listen(port, host);
282
- } catch (e) {
283
- logger.error(`error starting web socket server`, e);
284
- reject(e instanceof Error ? e : new Error(`listen failed: ${e}`));
285
- }
286
- });
287
- const server = await serverP;
288
- return new class implements GatewayServer.Server {
289
- readonly gateway = gw;
290
-
291
- async close(): Promise<void> {
292
- for (const [path, route] of routes) {
293
- try {
294
- if (route.close) {
295
- await route.close();
296
- }
297
- logger.info(`stopping ws server for [${path}]. clients: ${route.wss?.clients?.size ?? 0}`);
298
- route.wss?.clients?.forEach(client => {
299
- client.terminate();
300
- });
301
- route.wss?.close();
302
- } catch (e) {
303
- logger.warn(`error closing route ${path}`, e);
304
- }
305
- }
306
- await promisify(cb => {
307
- server.closeAllConnections();
308
- server.close(cb);
309
- });
310
- if (monitor) {
311
- await monitoring.stop(monitor);
312
- }
313
- }
314
- }
315
-
316
- }
package/src/utils.ts DELETED
@@ -1,10 +0,0 @@
1
- import {type Socket} from 'node:net';
2
-
3
- export function socketKey(socket: Socket): string {
4
- const remoteIp = socket.remoteAddress;
5
- if (!remoteIp) {
6
- throw new Error('Socket has no remote address');
7
- }
8
- return `${remoteIp}:${socket.remotePort}`;
9
- }
10
-