Twan07 commited on
Commit
8441e9f
·
verified ·
1 Parent(s): 24761b1

Create models/proxy-to-server.ts

Browse files
Files changed (1) hide show
  1. exocore-web/models/proxy-to-server.ts +200 -0
exocore-web/models/proxy-to-server.ts ADDED
@@ -0,0 +1,200 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import http from 'http';
2
+ import fs from 'fs';
3
+ import path from 'path';
4
+ import { Router } from 'express';
5
+ import type { Request, Response, NextFunction } from 'express';
6
+
7
+ const routesJsonPath = path.join(__dirname, 'routes.json');
8
+ const routesJsonFile = path.basename(routesJsonPath);
9
+ const routesJsonDir = path.dirname(routesJsonPath);
10
+
11
+ let activeRoutesRouter = Router();
12
+
13
+ const errorHtmlContent = `<!DOCTYPE html>
14
+ <html><head><title>Server Error</title></head>
15
+ <body style="font-family: sans-serif; text-align: center; margin-top: 10%;">
16
+   <h1>502 Bad Gateway</h1>
17
+   <p>The backend service appears to be offline or misconfigured.</p>
18
+ </body></html>`;
19
+
20
+ function sendErrorHtmlPage(res: Response, statusCode: number = 502) {
21
+   if (res.headersSent) return;
22
+   res.status(statusCode).type('text/html').send(errorHtmlContent);
23
+ }
24
+
25
+ interface RouteConfig {
26
+   method: string;
27
+   path: string;
28
+ }
29
+
30
+ interface RoutesFile {
31
+ port: number;
32
+   routes: RouteConfig[];
33
+ }
34
+
35
+ let allRoutes: RouteConfig[] = [];
36
+ let currentProxyPort: number | null = null;
37
+ let portOnlineStatus: Record<number, boolean> = {};
38
+ let isCheckingPort = false;
39
+
40
+ async function isPortOnline(port: number): Promise<boolean> {
41
+   return new Promise(resolve => {
42
+     const req = http.request({ hostname: 'localhost', port, method: 'HEAD', timeout: 500 }, () => {
43
+ req.destroy();
44
+       resolve(true);
45
+     });
46
+     req.on('error', () => resolve(false));
47
+     req.on('timeout', () => {
48
+       req.destroy();
49
+       resolve(false);
50
+     });
51
+     req.end();
52
+   });
53
+ }
54
+
55
+ function rebuildActiveRouter() {
56
+   const newRouter = Router();
57
+
58
+ if (!currentProxyPort || !portOnlineStatus[currentProxyPort]) {
59
+ activeRoutesRouter = newRouter;
60
+ console.log('[ProxyToServerTS] ✅ Router is active but empty (backend port is offline or not configured).');
61
+ return;
62
+ }
63
+
64
+   allRoutes.forEach(route => {
65
+     const method = route.method.trim().toLowerCase();
66
+
67
+     if (typeof (newRouter as any)[method] === 'function') {
68
+       (newRouter as any)[method](route.path, (req: Request, res: Response) => {
69
+ const targetPort = currentProxyPort as number;
70
+         const options: http.RequestOptions = {
71
+           hostname: 'localhost',
72
+           port: targetPort,
73
+           path: req.originalUrl,
74
+           method: req.method,
75
+           headers: { ...req.headers, host: `localhost:${targetPort}` }
76
+         };
77
+
78
+ if (options.headers) {
79
+ const headers = options.headers as http.OutgoingHttpHeaders;
80
+ if (headers.connection) {
81
+ delete headers.connection;
82
+ }
83
+ }
84
+
85
+         const backendRequest = http.request(options, backendResponse => {
86
+           if (backendResponse.statusCode && backendResponse.statusCode >= 400) {
87
+             sendErrorHtmlPage(res, backendResponse.statusCode);
88
+             backendResponse.resume();
89
+             return;
90
+           }
91
+
92
+           res.writeHead(backendResponse.statusCode || 200, backendResponse.headers);
93
+           backendResponse.pipe(res);
94
+         });
95
+
96
+         backendRequest.on('error', (err) => {
97
+ console.error(`[ProxyToServerTS] Backend request error for port ${targetPort}:`, err.message);
98
+ sendErrorHtmlPage(res, 503)
99
+ });
100
+         req.pipe(backendRequest);
101
+       });
102
+     }
103
+   });
104
+
105
+   activeRoutesRouter = newRouter;
106
+ console.log(`[ProxyToServerTS] ✅ Router rebuilt successfully for port ${currentProxyPort} with ${allRoutes.length} routes.`);
107
+ }
108
+
109
+ function loadRoutesFromFile() {
110
+   try {
111
+ console.log(`[ProxyToServerTS] Attempting to load routes from ${routesJsonPath}`);
112
+     if (!fs.existsSync(routesJsonPath)) {
113
+ console.warn(`[ProxyToServerTS] 🟡 routes.json not found. Waiting for the file to be created...`);
114
+ if (currentProxyPort !== null) {
115
+ allRoutes = [];
116
+ currentProxyPort = null;
117
+ portOnlineStatus = {};
118
+ rebuildActiveRouter();
119
+ }
120
+       return;
121
+ }
122
+
123
+     const content = fs.readFileSync(routesJsonPath, 'utf8');
124
+     const parsed = JSON.parse(content) as RoutesFile;
125
+
126
+ if (typeof parsed.port !== 'number') {
127
+ console.error(`[ProxyToServerTS] ❌ 'port' is missing or not a number in routes.json.`);
128
+ currentProxyPort = null;
129
+ allRoutes = [];
130
+ rebuildActiveRouter();
131
+ return;
132
+ }
133
+
134
+ if (currentProxyPort !== parsed.port) {
135
+ console.log(`[ProxyToServerTS] Port configuration changed from ${currentProxyPort || 'none'} to ${parsed.port}.`);
136
+ currentProxyPort = parsed.port;
137
+ portOnlineStatus = {};
138
+ }
139
+
140
+     if (!Array.isArray(parsed.routes)) {
141
+ console.warn(`[ProxyToServerTS] Invalid format: 'routes' key is not an array.`);
142
+ return;
143
+ }
144
+
145
+     allRoutes = parsed.routes.filter(route => {
146
+ if (!route.path || typeof route.path !== 'string' || !route.method) {
147
+ console.warn(`[ProxyToServerTS] Invalid route found (missing path or method). Skipping.`, route);
148
+ return false;
149
+ }
150
+ return true;
151
+ });
152
+
153
+ console.log(`[ProxyToServerTS] ✔️ Loaded ${allRoutes.length} routes for port ${currentProxyPort}.`);
154
+ checkPortStatus();
155
+
156
+   } catch (err) {
157
+     console.error(`[ProxyToServerTS] ❌ Error loading or parsing routes.json: ${(err as Error).message}`);
158
+ currentProxyPort = null;
159
+ allRoutes = [];
160
+ rebuildActiveRouter();
161
+   }
162
+ }
163
+
164
+ async function checkPortStatus() {
165
+   if (isCheckingPort || currentProxyPort === null) return;
166
+   isCheckingPort = true;
167
+
168
+ const portToCheck = currentProxyPort;
169
+ const wasOnline = portOnlineStatus[portToCheck];
170
+ const isOnline = await isPortOnline(portToCheck);
171
+
172
+   if (wasOnline !== isOnline) {
173
+ console.log(`[ProxyToServerTS] Port ${portToCheck} is now ${isOnline ? '🟢 ONLINE' : '🔴 OFFLINE'}`);
174
+ portOnlineStatus[portToCheck] = isOnline;
175
+ rebuildActiveRouter();
176
+ }
177
+
178
+ isCheckingPort = false;
179
+ }
180
+
181
+ function setupWatcherAndInterval() {
182
+   fs.watch(routesJsonDir, { persistent: true }, (eventType, filename) => {
183
+     if (filename === routesJsonFile) {
184
+       console.log(`[ProxyToServerTS] 🔄 Change detected in ${routesJsonFile}. Reloading...`);
185
+       loadRoutesFromFile();
186
+     }
187
+   });
188
+
189
+   setInterval(checkPortStatus, 30000);
190
+ }
191
+
192
+ loadRoutesFromFile();
193
+ setupWatcherAndInterval();
194
+
195
+ const mainProxyRouter = Router();
196
+ mainProxyRouter.use((req: Request, res: Response, next: NextFunction) => {
197
+   activeRoutesRouter(req, res, next);
198
+ });
199
+
200
+ export { mainProxyRouter as serverProxy };