@grest-ts/ipc 0.0.6 → 0.0.8
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.
- package/LICENSE +21 -21
- package/dist/tsconfig.publish.tsbuildinfo +1 -1
- package/package.json +8 -8
- package/src/client/IPCClient.ts +99 -99
- package/src/common/IPCSocket.ts +251 -251
- package/src/common/IPCTypes.ts +17 -17
- package/src/index-node.ts +6 -6
- package/src/server/HttpHandler.ts +107 -107
- package/src/server/IPCServer.ts +159 -159
- package/src/server/SocketHandler.ts +146 -146
|
@@ -1,107 +1,107 @@
|
|
|
1
|
-
import * as http from 'http';
|
|
2
|
-
import {GGLog} from "@grest-ts/logger";
|
|
3
|
-
import {HttpMethod} from "@grest-ts/common";
|
|
4
|
-
import httpProxy from "http-proxy";
|
|
5
|
-
import findMyWay, {HTTPMethod} from "find-my-way";
|
|
6
|
-
|
|
7
|
-
/** HTTP route handler - receives body, path params, and headers. Returns response data (or void for empty 200) */
|
|
8
|
-
export type HttpRouteHandler = (body: any, pathParams?: Record<string, string>, headers?: Record<string, string>) => Promise<any>;
|
|
9
|
-
|
|
10
|
-
/** Default HTTP handler for unmatched routes (proxying) - gets raw req/res */
|
|
11
|
-
export type DefaultHttpHandler = (req: http.IncomingMessage, res: http.ServerResponse) => Promise<void>;
|
|
12
|
-
|
|
13
|
-
interface RouteStore {
|
|
14
|
-
handler: HttpRouteHandler;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export class HttpHandler {
|
|
18
|
-
|
|
19
|
-
private router = findMyWay<findMyWay.HTTPVersion.V1>();
|
|
20
|
-
private routeResolver: (path: string) => string
|
|
21
|
-
private readonly proxy: httpProxy;
|
|
22
|
-
|
|
23
|
-
constructor() {
|
|
24
|
-
this.proxy = httpProxy.createProxyServer({});
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
public setRouteResolver(routeResolver: (path: string) => string) {
|
|
28
|
-
this.routeResolver = routeResolver
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
public addRoute(method: HttpMethod, path: string, handler: HttpRouteHandler): void {
|
|
32
|
-
const store: RouteStore = {handler};
|
|
33
|
-
this.router.on(method as HTTPMethod, path, () => {
|
|
34
|
-
}, store);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
public removeRoute(method: HttpMethod, path: string): void {
|
|
38
|
-
this.router.off(method as HTTPMethod, path);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
public async handleRequest(
|
|
42
|
-
req: http.IncomingMessage,
|
|
43
|
-
res: http.ServerResponse
|
|
44
|
-
): Promise<void> {
|
|
45
|
-
const method = (req.method || 'GET') as HttpMethod;
|
|
46
|
-
const url = req.url || '/';
|
|
47
|
-
// Strip query string for route matching
|
|
48
|
-
const path = url.split('?')[0];
|
|
49
|
-
|
|
50
|
-
// Try to match a route
|
|
51
|
-
const result = this.router.find(method as HTTPMethod, path);
|
|
52
|
-
if (result?.store) {
|
|
53
|
-
const store = result.store as RouteStore;
|
|
54
|
-
const body = await this.readJsonBody(req);
|
|
55
|
-
// Normalize headers to Record<string, string>
|
|
56
|
-
const headers: Record<string, string> = {};
|
|
57
|
-
for (const [key, value] of Object.entries(req.headers)) {
|
|
58
|
-
if (value !== undefined) {
|
|
59
|
-
headers[key.toLowerCase()] = Array.isArray(value) ? value[0] : value;
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
const pathParams = result.params as Record<string, string> || {};
|
|
63
|
-
try {
|
|
64
|
-
const responseData = await store.handler(body, pathParams, headers);
|
|
65
|
-
this.sendResponse(res, 200, responseData);
|
|
66
|
-
} catch (err: any) {
|
|
67
|
-
GGLog.error(this, `HTTP route error [${method} ${path}]: ${err.message}`, err);
|
|
68
|
-
this.sendResponse(res, 500, {error: err.message});
|
|
69
|
-
}
|
|
70
|
-
return;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
const routeToUrl = this.routeResolver(path)
|
|
74
|
-
if (routeToUrl) {
|
|
75
|
-
GGLog.debug(this, `Proxying ${req.method} ${req.url} -> ${routeToUrl}`);
|
|
76
|
-
this.proxy.web(req, res, {target: routeToUrl}, (err: Error) => {
|
|
77
|
-
GGLog.error(this, "Proxy error", err);
|
|
78
|
-
this.sendResponse(res, 502, {issue: "Proxy error: " + err.message + " (Did the service crash?)"});
|
|
79
|
-
});
|
|
80
|
-
return;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
this.sendResponse(res, 404, {error: `No route for ${method} ${path}! Did you forget to start a service that defines this path?`});
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
private async readJsonBody(req: http.IncomingMessage): Promise<any> {
|
|
87
|
-
return new Promise((resolve) => {
|
|
88
|
-
let bodyStr = '';
|
|
89
|
-
req.on('data', (chunk: Buffer) => {
|
|
90
|
-
bodyStr += chunk.toString();
|
|
91
|
-
});
|
|
92
|
-
req.on('end', () => {
|
|
93
|
-
try {
|
|
94
|
-
resolve(bodyStr ? JSON.parse(bodyStr) : undefined);
|
|
95
|
-
} catch {
|
|
96
|
-
resolve(undefined);
|
|
97
|
-
}
|
|
98
|
-
});
|
|
99
|
-
req.on('error', () => resolve(undefined));
|
|
100
|
-
});
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
private sendResponse(res: http.ServerResponse, status: number, data?: any): void {
|
|
104
|
-
res.writeHead(status, {'Content-Type': 'application/json'});
|
|
105
|
-
res.end(data !== undefined ? JSON.stringify(data) : '');
|
|
106
|
-
}
|
|
107
|
-
}
|
|
1
|
+
import * as http from 'http';
|
|
2
|
+
import {GGLog} from "@grest-ts/logger";
|
|
3
|
+
import {HttpMethod} from "@grest-ts/common";
|
|
4
|
+
import httpProxy from "http-proxy";
|
|
5
|
+
import findMyWay, {HTTPMethod} from "find-my-way";
|
|
6
|
+
|
|
7
|
+
/** HTTP route handler - receives body, path params, and headers. Returns response data (or void for empty 200) */
|
|
8
|
+
export type HttpRouteHandler = (body: any, pathParams?: Record<string, string>, headers?: Record<string, string>) => Promise<any>;
|
|
9
|
+
|
|
10
|
+
/** Default HTTP handler for unmatched routes (proxying) - gets raw req/res */
|
|
11
|
+
export type DefaultHttpHandler = (req: http.IncomingMessage, res: http.ServerResponse) => Promise<void>;
|
|
12
|
+
|
|
13
|
+
interface RouteStore {
|
|
14
|
+
handler: HttpRouteHandler;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export class HttpHandler {
|
|
18
|
+
|
|
19
|
+
private router = findMyWay<findMyWay.HTTPVersion.V1>();
|
|
20
|
+
private routeResolver: (path: string) => string
|
|
21
|
+
private readonly proxy: httpProxy;
|
|
22
|
+
|
|
23
|
+
constructor() {
|
|
24
|
+
this.proxy = httpProxy.createProxyServer({});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
public setRouteResolver(routeResolver: (path: string) => string) {
|
|
28
|
+
this.routeResolver = routeResolver
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
public addRoute(method: HttpMethod, path: string, handler: HttpRouteHandler): void {
|
|
32
|
+
const store: RouteStore = {handler};
|
|
33
|
+
this.router.on(method as HTTPMethod, path, () => {
|
|
34
|
+
}, store);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
public removeRoute(method: HttpMethod, path: string): void {
|
|
38
|
+
this.router.off(method as HTTPMethod, path);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
public async handleRequest(
|
|
42
|
+
req: http.IncomingMessage,
|
|
43
|
+
res: http.ServerResponse
|
|
44
|
+
): Promise<void> {
|
|
45
|
+
const method = (req.method || 'GET') as HttpMethod;
|
|
46
|
+
const url = req.url || '/';
|
|
47
|
+
// Strip query string for route matching
|
|
48
|
+
const path = url.split('?')[0];
|
|
49
|
+
|
|
50
|
+
// Try to match a route
|
|
51
|
+
const result = this.router.find(method as HTTPMethod, path);
|
|
52
|
+
if (result?.store) {
|
|
53
|
+
const store = result.store as RouteStore;
|
|
54
|
+
const body = await this.readJsonBody(req);
|
|
55
|
+
// Normalize headers to Record<string, string>
|
|
56
|
+
const headers: Record<string, string> = {};
|
|
57
|
+
for (const [key, value] of Object.entries(req.headers)) {
|
|
58
|
+
if (value !== undefined) {
|
|
59
|
+
headers[key.toLowerCase()] = Array.isArray(value) ? value[0] : value;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
const pathParams = result.params as Record<string, string> || {};
|
|
63
|
+
try {
|
|
64
|
+
const responseData = await store.handler(body, pathParams, headers);
|
|
65
|
+
this.sendResponse(res, 200, responseData);
|
|
66
|
+
} catch (err: any) {
|
|
67
|
+
GGLog.error(this, `HTTP route error [${method} ${path}]: ${err.message}`, err);
|
|
68
|
+
this.sendResponse(res, 500, {error: err.message});
|
|
69
|
+
}
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const routeToUrl = this.routeResolver(path)
|
|
74
|
+
if (routeToUrl) {
|
|
75
|
+
GGLog.debug(this, `Proxying ${req.method} ${req.url} -> ${routeToUrl}`);
|
|
76
|
+
this.proxy.web(req, res, {target: routeToUrl}, (err: Error) => {
|
|
77
|
+
GGLog.error(this, "Proxy error", err);
|
|
78
|
+
this.sendResponse(res, 502, {issue: "Proxy error: " + err.message + " (Did the service crash?)"});
|
|
79
|
+
});
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
this.sendResponse(res, 404, {error: `No route for ${method} ${path}! Did you forget to start a service that defines this path?`});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
private async readJsonBody(req: http.IncomingMessage): Promise<any> {
|
|
87
|
+
return new Promise((resolve) => {
|
|
88
|
+
let bodyStr = '';
|
|
89
|
+
req.on('data', (chunk: Buffer) => {
|
|
90
|
+
bodyStr += chunk.toString();
|
|
91
|
+
});
|
|
92
|
+
req.on('end', () => {
|
|
93
|
+
try {
|
|
94
|
+
resolve(bodyStr ? JSON.parse(bodyStr) : undefined);
|
|
95
|
+
} catch {
|
|
96
|
+
resolve(undefined);
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
req.on('error', () => resolve(undefined));
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
private sendResponse(res: http.ServerResponse, status: number, data?: any): void {
|
|
104
|
+
res.writeHead(status, {'Content-Type': 'application/json'});
|
|
105
|
+
res.end(data !== undefined ? JSON.stringify(data) : '');
|
|
106
|
+
}
|
|
107
|
+
}
|
package/src/server/IPCServer.ts
CHANGED
|
@@ -1,159 +1,159 @@
|
|
|
1
|
-
import * as http from 'http';
|
|
2
|
-
import * as net from 'net';
|
|
3
|
-
import {GGLog} from "@grest-ts/logger";
|
|
4
|
-
import {Duplex} from "stream";
|
|
5
|
-
import {HttpMethod} from "@grest-ts/common";
|
|
6
|
-
import {HttpHandler} from "./HttpHandler";
|
|
7
|
-
import {SocketHandler, SocketMessage} from "./SocketHandler";
|
|
8
|
-
import {IPCClientRequest, IPCServerRequest} from "../common/IPCTypes";
|
|
9
|
-
import {GGLocator, GGLocatorScope} from "@grest-ts/locator";
|
|
10
|
-
import {GGContext} from "@grest-ts/context";
|
|
11
|
-
import {GG_TRACE} from "@grest-ts/trace";
|
|
12
|
-
|
|
13
|
-
export class IPCServer {
|
|
14
|
-
|
|
15
|
-
private readonly httpServer: http.Server;
|
|
16
|
-
private readonly httpHandler: HttpHandler;
|
|
17
|
-
private readonly socketHandler: SocketHandler;
|
|
18
|
-
|
|
19
|
-
private readonly expectedPort: number;
|
|
20
|
-
private port: number = 0;
|
|
21
|
-
private url: string = '';
|
|
22
|
-
|
|
23
|
-
private readonly connections: Set<net.Socket> = new Set();
|
|
24
|
-
|
|
25
|
-
private readonly scope: GGLocatorScope;
|
|
26
|
-
|
|
27
|
-
constructor(port: number = 0) {
|
|
28
|
-
this.expectedPort = port;
|
|
29
|
-
this.scope = GGLocator.getScope();
|
|
30
|
-
|
|
31
|
-
// Create handlers
|
|
32
|
-
this.httpHandler = new HttpHandler();
|
|
33
|
-
this.socketHandler = new SocketHandler();
|
|
34
|
-
|
|
35
|
-
// Create HTTP server
|
|
36
|
-
this.httpServer = http.createServer(async (req, res) => {
|
|
37
|
-
await new GGContext("LocalRouter").run(async () => {
|
|
38
|
-
GG_TRACE.init();
|
|
39
|
-
await this.httpHandler.handleRequest(req, res);
|
|
40
|
-
});
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
// Handle WebSocket upgrades
|
|
44
|
-
// Note: Incoming TCP connections start with no async context (they originate from libuv/OS layer).
|
|
45
|
-
this.httpServer.on('upgrade', async (req, socket, head) => {
|
|
46
|
-
this.scope.ensureEntered();
|
|
47
|
-
await new GGContext("LocalRouter").run(async () => {
|
|
48
|
-
GG_TRACE.init();
|
|
49
|
-
await this.socketHandler.handleUpgrade(req, socket as Duplex, head);
|
|
50
|
-
});
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
// Track connections for cleanup
|
|
54
|
-
this.httpServer.on('connection', (conn) => {
|
|
55
|
-
this.connections.add(conn);
|
|
56
|
-
conn.on('close', () => this.connections.delete(conn));
|
|
57
|
-
});
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
public static defineRequest<Req, Res>(name: string): IPCServerRequest<Req, Res> {
|
|
61
|
-
return name as IPCServerRequest<Req, Res>;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// -----------------------------------------------
|
|
65
|
-
// Lifecycle
|
|
66
|
-
// -----------------------------------------------
|
|
67
|
-
|
|
68
|
-
public async start(): Promise<boolean> {
|
|
69
|
-
return new Promise<boolean>((resolve, reject) => {
|
|
70
|
-
const errorHandler = (err: any) => {
|
|
71
|
-
if (err.code === 'EADDRINUSE') {
|
|
72
|
-
resolve(false);
|
|
73
|
-
} else {
|
|
74
|
-
reject(err);
|
|
75
|
-
}
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
this.httpServer.once('error', errorHandler);
|
|
79
|
-
|
|
80
|
-
this.httpServer.listen(this.expectedPort, '0.0.0.0', () => {
|
|
81
|
-
this.scope.ensureEntered();
|
|
82
|
-
this.port = (this.httpServer.address() as any).port;
|
|
83
|
-
this.url = `http://localhost:${this.port}`;
|
|
84
|
-
this.httpServer.removeListener('error', errorHandler);
|
|
85
|
-
GGLog.info(this, `Internal server started on port ${this.port}`);
|
|
86
|
-
resolve(true);
|
|
87
|
-
});
|
|
88
|
-
});
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
public async teardown(): Promise<void> {
|
|
92
|
-
// Teardown socket handler (closes clients and WSS)
|
|
93
|
-
await this.socketHandler.teardown();
|
|
94
|
-
|
|
95
|
-
// Close all HTTP connections
|
|
96
|
-
this.connections.forEach(conn => conn.destroy());
|
|
97
|
-
this.connections.clear();
|
|
98
|
-
|
|
99
|
-
// Close the server
|
|
100
|
-
await new Promise<void>((resolve, reject) => {
|
|
101
|
-
this.httpServer.close((err) => {
|
|
102
|
-
this.scope.ensureEntered();
|
|
103
|
-
if (err) reject(err);
|
|
104
|
-
else resolve();
|
|
105
|
-
});
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
GGLog.info(this, 'Internal server stopped');
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
// -----------------------------------------------
|
|
112
|
-
// Handling http/socket requests that services send between each other. Can intercept http here.
|
|
113
|
-
// -----------------------------------------------
|
|
114
|
-
|
|
115
|
-
public interceptHttp(method: HttpMethod, path: string, handler: (body: any, pathParams?: Record<string, string>, headers?: Record<string, string>) => Promise<any>): void {
|
|
116
|
-
this.httpHandler.addRoute(method, path, handler);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
public removeInterceptHttp(method: HttpMethod, path: string): void {
|
|
120
|
-
this.httpHandler.removeRoute(method, path);
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
public setRouteProxyResolver(resolver: (path: string) => string) {
|
|
124
|
-
this.httpHandler.setRouteResolver(resolver);
|
|
125
|
-
this.socketHandler.setRouteResolver(resolver);
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
// -----------------------------------------------
|
|
129
|
-
// Grest Framework internal messaging between either local discovery server or local test server.
|
|
130
|
-
// -----------------------------------------------
|
|
131
|
-
|
|
132
|
-
public onFrameworkMessage<Req, Res>(
|
|
133
|
-
type: IPCServerRequest<Req, Res>,
|
|
134
|
-
handler: (payload: Req, msg: SocketMessage) => Promise<Res>
|
|
135
|
-
): void {
|
|
136
|
-
this.socketHandler.onMessage(type, async (msg) => handler(msg.data as Req, msg));
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
public async sendFrameworkMessage<Req, Res>(
|
|
140
|
-
runtimeId: string,
|
|
141
|
-
type: IPCClientRequest<Req, Res>,
|
|
142
|
-
payload: Req,
|
|
143
|
-
timeoutMs?: number
|
|
144
|
-
): Promise<Res> {
|
|
145
|
-
return this.socketHandler.request<Res>(runtimeId, type, payload, timeoutMs);
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
// -----------------------------------------------
|
|
149
|
-
// Accessors
|
|
150
|
-
// -----------------------------------------------
|
|
151
|
-
|
|
152
|
-
public getPort(): number {
|
|
153
|
-
return this.port;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
public getUrl(): string {
|
|
157
|
-
return this.url;
|
|
158
|
-
}
|
|
159
|
-
}
|
|
1
|
+
import * as http from 'http';
|
|
2
|
+
import * as net from 'net';
|
|
3
|
+
import {GGLog} from "@grest-ts/logger";
|
|
4
|
+
import {Duplex} from "stream";
|
|
5
|
+
import {HttpMethod} from "@grest-ts/common";
|
|
6
|
+
import {HttpHandler} from "./HttpHandler";
|
|
7
|
+
import {SocketHandler, SocketMessage} from "./SocketHandler";
|
|
8
|
+
import {IPCClientRequest, IPCServerRequest} from "../common/IPCTypes";
|
|
9
|
+
import {GGLocator, GGLocatorScope} from "@grest-ts/locator";
|
|
10
|
+
import {GGContext} from "@grest-ts/context";
|
|
11
|
+
import {GG_TRACE} from "@grest-ts/trace";
|
|
12
|
+
|
|
13
|
+
export class IPCServer {
|
|
14
|
+
|
|
15
|
+
private readonly httpServer: http.Server;
|
|
16
|
+
private readonly httpHandler: HttpHandler;
|
|
17
|
+
private readonly socketHandler: SocketHandler;
|
|
18
|
+
|
|
19
|
+
private readonly expectedPort: number;
|
|
20
|
+
private port: number = 0;
|
|
21
|
+
private url: string = '';
|
|
22
|
+
|
|
23
|
+
private readonly connections: Set<net.Socket> = new Set();
|
|
24
|
+
|
|
25
|
+
private readonly scope: GGLocatorScope;
|
|
26
|
+
|
|
27
|
+
constructor(port: number = 0) {
|
|
28
|
+
this.expectedPort = port;
|
|
29
|
+
this.scope = GGLocator.getScope();
|
|
30
|
+
|
|
31
|
+
// Create handlers
|
|
32
|
+
this.httpHandler = new HttpHandler();
|
|
33
|
+
this.socketHandler = new SocketHandler();
|
|
34
|
+
|
|
35
|
+
// Create HTTP server
|
|
36
|
+
this.httpServer = http.createServer(async (req, res) => {
|
|
37
|
+
await new GGContext("LocalRouter").run(async () => {
|
|
38
|
+
GG_TRACE.init();
|
|
39
|
+
await this.httpHandler.handleRequest(req, res);
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
// Handle WebSocket upgrades
|
|
44
|
+
// Note: Incoming TCP connections start with no async context (they originate from libuv/OS layer).
|
|
45
|
+
this.httpServer.on('upgrade', async (req, socket, head) => {
|
|
46
|
+
this.scope.ensureEntered();
|
|
47
|
+
await new GGContext("LocalRouter").run(async () => {
|
|
48
|
+
GG_TRACE.init();
|
|
49
|
+
await this.socketHandler.handleUpgrade(req, socket as Duplex, head);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// Track connections for cleanup
|
|
54
|
+
this.httpServer.on('connection', (conn) => {
|
|
55
|
+
this.connections.add(conn);
|
|
56
|
+
conn.on('close', () => this.connections.delete(conn));
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
public static defineRequest<Req, Res>(name: string): IPCServerRequest<Req, Res> {
|
|
61
|
+
return name as IPCServerRequest<Req, Res>;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// -----------------------------------------------
|
|
65
|
+
// Lifecycle
|
|
66
|
+
// -----------------------------------------------
|
|
67
|
+
|
|
68
|
+
public async start(): Promise<boolean> {
|
|
69
|
+
return new Promise<boolean>((resolve, reject) => {
|
|
70
|
+
const errorHandler = (err: any) => {
|
|
71
|
+
if (err.code === 'EADDRINUSE') {
|
|
72
|
+
resolve(false);
|
|
73
|
+
} else {
|
|
74
|
+
reject(err);
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
this.httpServer.once('error', errorHandler);
|
|
79
|
+
|
|
80
|
+
this.httpServer.listen(this.expectedPort, '0.0.0.0', () => {
|
|
81
|
+
this.scope.ensureEntered();
|
|
82
|
+
this.port = (this.httpServer.address() as any).port;
|
|
83
|
+
this.url = `http://localhost:${this.port}`;
|
|
84
|
+
this.httpServer.removeListener('error', errorHandler);
|
|
85
|
+
GGLog.info(this, `Internal server started on port ${this.port}`);
|
|
86
|
+
resolve(true);
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
public async teardown(): Promise<void> {
|
|
92
|
+
// Teardown socket handler (closes clients and WSS)
|
|
93
|
+
await this.socketHandler.teardown();
|
|
94
|
+
|
|
95
|
+
// Close all HTTP connections
|
|
96
|
+
this.connections.forEach(conn => conn.destroy());
|
|
97
|
+
this.connections.clear();
|
|
98
|
+
|
|
99
|
+
// Close the server
|
|
100
|
+
await new Promise<void>((resolve, reject) => {
|
|
101
|
+
this.httpServer.close((err) => {
|
|
102
|
+
this.scope.ensureEntered();
|
|
103
|
+
if (err) reject(err);
|
|
104
|
+
else resolve();
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
GGLog.info(this, 'Internal server stopped');
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// -----------------------------------------------
|
|
112
|
+
// Handling http/socket requests that services send between each other. Can intercept http here.
|
|
113
|
+
// -----------------------------------------------
|
|
114
|
+
|
|
115
|
+
public interceptHttp(method: HttpMethod, path: string, handler: (body: any, pathParams?: Record<string, string>, headers?: Record<string, string>) => Promise<any>): void {
|
|
116
|
+
this.httpHandler.addRoute(method, path, handler);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
public removeInterceptHttp(method: HttpMethod, path: string): void {
|
|
120
|
+
this.httpHandler.removeRoute(method, path);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
public setRouteProxyResolver(resolver: (path: string) => string) {
|
|
124
|
+
this.httpHandler.setRouteResolver(resolver);
|
|
125
|
+
this.socketHandler.setRouteResolver(resolver);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// -----------------------------------------------
|
|
129
|
+
// Grest Framework internal messaging between either local discovery server or local test server.
|
|
130
|
+
// -----------------------------------------------
|
|
131
|
+
|
|
132
|
+
public onFrameworkMessage<Req, Res>(
|
|
133
|
+
type: IPCServerRequest<Req, Res>,
|
|
134
|
+
handler: (payload: Req, msg: SocketMessage) => Promise<Res>
|
|
135
|
+
): void {
|
|
136
|
+
this.socketHandler.onMessage(type, async (msg) => handler(msg.data as Req, msg));
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
public async sendFrameworkMessage<Req, Res>(
|
|
140
|
+
runtimeId: string,
|
|
141
|
+
type: IPCClientRequest<Req, Res>,
|
|
142
|
+
payload: Req,
|
|
143
|
+
timeoutMs?: number
|
|
144
|
+
): Promise<Res> {
|
|
145
|
+
return this.socketHandler.request<Res>(runtimeId, type, payload, timeoutMs);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// -----------------------------------------------
|
|
149
|
+
// Accessors
|
|
150
|
+
// -----------------------------------------------
|
|
151
|
+
|
|
152
|
+
public getPort(): number {
|
|
153
|
+
return this.port;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
public getUrl(): string {
|
|
157
|
+
return this.url;
|
|
158
|
+
}
|
|
159
|
+
}
|