dynamic-self-register-proxy 1.0.0
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 +15 -0
- package/README.md +371 -0
- package/package.json +57 -0
- package/proxy-client.js +144 -0
- package/proxy.js +415 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
ISC License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026
|
|
4
|
+
|
|
5
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
6
|
+
purpose with or without fee is hereby granted, provided that the above
|
|
7
|
+
copyright notice and this permission notice appear in all copies.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
10
|
+
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
11
|
+
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
12
|
+
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
13
|
+
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
14
|
+
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
15
|
+
PERFORMANCE OF THIS SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
# Dynamic Self-Register Proxy
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/dynamic-self-register-proxy)
|
|
4
|
+
[](https://opensource.org/licenses/ISC)
|
|
5
|
+
|
|
6
|
+
Un reverse proxy Node.js avec API d'auto-enregistrement, permettant à vos applications de s'enregistrer dynamiquement et de recevoir un port automatiquement attribué.
|
|
7
|
+
|
|
8
|
+
## Cas d'usage
|
|
9
|
+
|
|
10
|
+
- **Conteneur Docker unique** exposant un seul port vers l'extérieur
|
|
11
|
+
- **Microservices dynamiques** qui démarrent/s'arrêtent sans configuration manuelle
|
|
12
|
+
- **Environnement de développement** avec plusieurs applications Node.js
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm install dynamic-self-register-proxy
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Ou pour une installation globale :
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npm install -g dynamic-self-register-proxy
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Démarrage rapide
|
|
27
|
+
|
|
28
|
+
### 1. Démarrer le proxy
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
# Via npx (sans installation globale)
|
|
32
|
+
npx dynamic-self-register-proxy
|
|
33
|
+
|
|
34
|
+
# Ou si installé globalement
|
|
35
|
+
dynamic-proxy
|
|
36
|
+
|
|
37
|
+
# Ou via npm script (développement local)
|
|
38
|
+
npm start
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Le proxy démarre par défaut sur le port `3000`. Pour changer le port :
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
# Windows PowerShell
|
|
45
|
+
$env:PROXY_PORT="3002"; npx dynamic-self-register-proxy
|
|
46
|
+
|
|
47
|
+
# Linux/Mac
|
|
48
|
+
PROXY_PORT=3002 npx dynamic-self-register-proxy
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### 2. Enregistrer une application
|
|
52
|
+
|
|
53
|
+
Votre application doit s'enregistrer au démarrage :
|
|
54
|
+
|
|
55
|
+
```javascript
|
|
56
|
+
const response = await fetch('http://localhost:3000/proxy/register', {
|
|
57
|
+
method: 'POST',
|
|
58
|
+
headers: { 'Content-Type': 'application/json' },
|
|
59
|
+
body: JSON.stringify({
|
|
60
|
+
path: '/myapp', // Chemin d'accès via le proxy
|
|
61
|
+
name: 'My App' // Nom de l'application (optionnel)
|
|
62
|
+
})
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
const { port } = await response.json();
|
|
66
|
+
// port = 4000 (attribué automatiquement)
|
|
67
|
+
|
|
68
|
+
// Démarrez votre serveur sur ce port
|
|
69
|
+
app.listen(port);
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### 3. Accéder à votre application
|
|
73
|
+
|
|
74
|
+
```
|
|
75
|
+
http://localhost:3000/myapp/ → votre application sur le port 4000
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## API du Proxy
|
|
79
|
+
|
|
80
|
+
| Endpoint | Méthode | Description |
|
|
81
|
+
|----------|---------|-------------|
|
|
82
|
+
| `/proxy/register` | POST | Enregistre une nouvelle route |
|
|
83
|
+
| `/proxy/unregister` | DELETE | Supprime une route |
|
|
84
|
+
| `/proxy/routes` | GET | Liste toutes les routes |
|
|
85
|
+
| `/proxy/health` | GET | Health check du proxy |
|
|
86
|
+
|
|
87
|
+
### POST /proxy/register
|
|
88
|
+
|
|
89
|
+
Enregistre une nouvelle route et attribue un port.
|
|
90
|
+
|
|
91
|
+
**Request:**
|
|
92
|
+
```json
|
|
93
|
+
{
|
|
94
|
+
"path": "/myapp",
|
|
95
|
+
"name": "My Application",
|
|
96
|
+
"port": 4005
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
| Champ | Type | Requis | Description |
|
|
101
|
+
|-------|------|--------|-------------|
|
|
102
|
+
| `path` | string | ✅ | Chemin URL pour accéder à l'app |
|
|
103
|
+
| `name` | string | ❌ | Nom descriptif |
|
|
104
|
+
| `port` | number | ❌ | Port spécifique (sinon auto-attribué) |
|
|
105
|
+
|
|
106
|
+
**Response (201):**
|
|
107
|
+
```json
|
|
108
|
+
{
|
|
109
|
+
"success": true,
|
|
110
|
+
"path": "/myapp",
|
|
111
|
+
"port": 4000,
|
|
112
|
+
"name": "My Application",
|
|
113
|
+
"message": "Route registered. Start your server on port 4000"
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
**Erreurs:**
|
|
118
|
+
- `400` - Path manquant
|
|
119
|
+
- `409` - Path ou port déjà utilisé
|
|
120
|
+
|
|
121
|
+
### DELETE /proxy/unregister
|
|
122
|
+
|
|
123
|
+
Supprime une route enregistrée.
|
|
124
|
+
|
|
125
|
+
**Request:**
|
|
126
|
+
```json
|
|
127
|
+
{
|
|
128
|
+
"path": "/myapp"
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
**Response (200):**
|
|
133
|
+
```json
|
|
134
|
+
{
|
|
135
|
+
"success": true,
|
|
136
|
+
"path": "/myapp",
|
|
137
|
+
"freedPort": 4000
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### GET /proxy/routes
|
|
142
|
+
|
|
143
|
+
Liste toutes les routes enregistrées.
|
|
144
|
+
|
|
145
|
+
**Response:**
|
|
146
|
+
```json
|
|
147
|
+
{
|
|
148
|
+
"count": 2,
|
|
149
|
+
"routes": [
|
|
150
|
+
{
|
|
151
|
+
"path": "/api",
|
|
152
|
+
"port": 4000,
|
|
153
|
+
"name": "API Server",
|
|
154
|
+
"registeredAt": "2026-01-27T10:00:00.000Z",
|
|
155
|
+
"target": "http://localhost:4000"
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
"path": "/web",
|
|
159
|
+
"port": 4001,
|
|
160
|
+
"name": "Web App",
|
|
161
|
+
"registeredAt": "2026-01-27T10:01:00.000Z",
|
|
162
|
+
"target": "http://localhost:4001"
|
|
163
|
+
}
|
|
164
|
+
],
|
|
165
|
+
"availablePorts": 998
|
|
166
|
+
}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### GET /proxy/health
|
|
170
|
+
|
|
171
|
+
Vérifie l'état du proxy.
|
|
172
|
+
|
|
173
|
+
**Response:**
|
|
174
|
+
```json
|
|
175
|
+
{
|
|
176
|
+
"status": "healthy",
|
|
177
|
+
"uptime": 3600,
|
|
178
|
+
"registeredRoutes": 2,
|
|
179
|
+
"usedPorts": 2
|
|
180
|
+
}
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
## Intégration dans votre application
|
|
184
|
+
|
|
185
|
+
### Exemple complet avec Express
|
|
186
|
+
|
|
187
|
+
```javascript
|
|
188
|
+
const express = require('express');
|
|
189
|
+
|
|
190
|
+
const PROXY_URL = process.env.PROXY_URL || 'http://localhost:3000';
|
|
191
|
+
const APP_PATH = process.env.APP_PATH || '/myapp';
|
|
192
|
+
const APP_NAME = process.env.APP_NAME || 'My App';
|
|
193
|
+
|
|
194
|
+
async function start() {
|
|
195
|
+
// 1. S'enregistrer auprès du proxy
|
|
196
|
+
const res = await fetch(`${PROXY_URL}/proxy/register`, {
|
|
197
|
+
method: 'POST',
|
|
198
|
+
headers: { 'Content-Type': 'application/json' },
|
|
199
|
+
body: JSON.stringify({ path: APP_PATH, name: APP_NAME })
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
const { port } = await res.json();
|
|
203
|
+
|
|
204
|
+
// 2. Créer l'application
|
|
205
|
+
const app = express();
|
|
206
|
+
|
|
207
|
+
app.get('/', (req, res) => {
|
|
208
|
+
res.json({ message: 'Hello!' });
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
// 3. Démarrer sur le port attribué
|
|
212
|
+
const server = app.listen(port, () => {
|
|
213
|
+
console.log(`App accessible via ${PROXY_URL}${APP_PATH}`);
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
// 4. Se désenregistrer à l'arrêt
|
|
217
|
+
process.on('SIGTERM', async () => {
|
|
218
|
+
await fetch(`${PROXY_URL}/proxy/unregister`, {
|
|
219
|
+
method: 'DELETE',
|
|
220
|
+
headers: { 'Content-Type': 'application/json' },
|
|
221
|
+
body: JSON.stringify({ path: APP_PATH })
|
|
222
|
+
});
|
|
223
|
+
server.close();
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
start();
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
### Utilisation du ProxyClient
|
|
231
|
+
|
|
232
|
+
Le package inclut un client helper pour faciliter l'enregistrement :
|
|
233
|
+
|
|
234
|
+
```javascript
|
|
235
|
+
const ProxyClient = require('dynamic-self-register-proxy');
|
|
236
|
+
|
|
237
|
+
const proxy = new ProxyClient('http://localhost:3000');
|
|
238
|
+
const { port } = await proxy.register('/myapp', 'My App');
|
|
239
|
+
|
|
240
|
+
app.listen(port);
|
|
241
|
+
|
|
242
|
+
// Arrêt propre automatique
|
|
243
|
+
proxy.setupGracefulShutdown(server);
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
Méthodes disponibles :
|
|
247
|
+
|
|
248
|
+
- `register(path, name, port?)` - Enregistre une route
|
|
249
|
+
- `unregister()` - Supprime l'enregistrement
|
|
250
|
+
- `listRoutes()` - Liste toutes les routes
|
|
251
|
+
- `health()` - Vérifie l'état du proxy
|
|
252
|
+
- `setupGracefulShutdown(server)` - Configure l'arrêt propre
|
|
253
|
+
- `setupHealthRoute(app, options?)` - Ajoute la route de health check
|
|
254
|
+
|
|
255
|
+
## Docker
|
|
256
|
+
|
|
257
|
+
### Build et lancement
|
|
258
|
+
|
|
259
|
+
```bash
|
|
260
|
+
# Build
|
|
261
|
+
npm run docker:build
|
|
262
|
+
|
|
263
|
+
# Démarrer (proxy + 2 apps exemples)
|
|
264
|
+
npm run docker:up
|
|
265
|
+
|
|
266
|
+
# Arrêter
|
|
267
|
+
npm run docker:down
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
### Accès
|
|
271
|
+
|
|
272
|
+
- Proxy : `http://localhost:8081`
|
|
273
|
+
- App 1 : `http://localhost:8081/app1`
|
|
274
|
+
- App 2 : `http://localhost:8081/app2`
|
|
275
|
+
- Routes : `http://localhost:8081/proxy/routes`
|
|
276
|
+
|
|
277
|
+
### Ajouter votre propre service
|
|
278
|
+
|
|
279
|
+
Dans `docker-compose.yml` :
|
|
280
|
+
|
|
281
|
+
```yaml
|
|
282
|
+
services:
|
|
283
|
+
my-service:
|
|
284
|
+
build: ./my-service
|
|
285
|
+
environment:
|
|
286
|
+
- PROXY_URL=http://proxy:3000
|
|
287
|
+
- APP_PATH=/my-service
|
|
288
|
+
- APP_NAME=My Service
|
|
289
|
+
depends_on:
|
|
290
|
+
- proxy
|
|
291
|
+
networks:
|
|
292
|
+
- proxy-network
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
## Health Check Polling
|
|
296
|
+
|
|
297
|
+
Le proxy vérifie périodiquement que les serveurs enregistrés sont toujours actifs. Si un serveur ne répond pas correctement, il est automatiquement désenregistré.
|
|
298
|
+
|
|
299
|
+
### Implémentation requise
|
|
300
|
+
|
|
301
|
+
Chaque application enregistrée **doit** implémenter la route `GET /proxy/health` qui retourne un code **200** :
|
|
302
|
+
|
|
303
|
+
```javascript
|
|
304
|
+
// Avec Express
|
|
305
|
+
app.get('/proxy/health', (req, res) => {
|
|
306
|
+
res.status(200).json({ status: 'healthy' });
|
|
307
|
+
});
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
### Avec ProxyClient
|
|
311
|
+
|
|
312
|
+
Le helper `ProxyClient` fournit une méthode pour ajouter automatiquement cette route :
|
|
313
|
+
|
|
314
|
+
```javascript
|
|
315
|
+
const ProxyClient = require('dynamic-self-register-proxy');
|
|
316
|
+
|
|
317
|
+
const proxy = new ProxyClient('http://localhost:3000');
|
|
318
|
+
const app = express();
|
|
319
|
+
|
|
320
|
+
// Ajoute automatiquement GET /proxy/health
|
|
321
|
+
proxy.setupHealthRoute(app);
|
|
322
|
+
|
|
323
|
+
// Ou avec un health check personnalisé
|
|
324
|
+
proxy.setupHealthRoute(app, {
|
|
325
|
+
healthCheck: async () => {
|
|
326
|
+
// Vérifier la connexion à la DB, etc.
|
|
327
|
+
const dbOk = await checkDatabase();
|
|
328
|
+
return dbOk;
|
|
329
|
+
}
|
|
330
|
+
});
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
### Comportement
|
|
334
|
+
|
|
335
|
+
- Le proxy vérifie tous les serveurs toutes les **30 secondes** (par défaut)
|
|
336
|
+
- Timeout de **5 secondes** par requête de health check
|
|
337
|
+
- Si la réponse n'est pas un code **200**, le serveur est automatiquement désenregistré
|
|
338
|
+
- Les erreurs de connexion (serveur arrêté, timeout) entraînent aussi le désenregistrement
|
|
339
|
+
|
|
340
|
+
## Configuration
|
|
341
|
+
|
|
342
|
+
| Variable | Défaut | Description |
|
|
343
|
+
|----------|--------|-------------|
|
|
344
|
+
| `PROXY_PORT` | `3000` | Port d'écoute du proxy |
|
|
345
|
+
| `HEALTH_CHECK_INTERVAL` | `30000` | Intervalle du health check polling (ms) |
|
|
346
|
+
| `HEALTH_CHECK_TIMEOUT` | `5000` | Timeout pour chaque health check (ms) |
|
|
347
|
+
| `PROXY_URL` | `http://localhost:3000` | URL du proxy (pour les apps) |
|
|
348
|
+
| `APP_PATH` | `/example` | Chemin de l'application |
|
|
349
|
+
| `APP_NAME` | `Example App` | Nom de l'application |
|
|
350
|
+
|
|
351
|
+
## Plage de ports
|
|
352
|
+
|
|
353
|
+
Par défaut, le proxy attribue des ports entre **4000** et **5000** (1000 ports disponibles).
|
|
354
|
+
|
|
355
|
+
Pour modifier cette plage, éditez `proxy.js` :
|
|
356
|
+
|
|
357
|
+
```javascript
|
|
358
|
+
const INTERNAL_PORT_START = 4000;
|
|
359
|
+
const INTERNAL_PORT_END = 5000;
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
## Limitations
|
|
363
|
+
|
|
364
|
+
- Les routes sont stockées en mémoire (perdues au redémarrage du proxy)
|
|
365
|
+
- Pas de persistance des enregistrements
|
|
366
|
+
- Pas d'authentification sur l'API de registration
|
|
367
|
+
- Les applications doivent implémenter `GET /proxy/health` pour ne pas être désenregistrées automatiquement
|
|
368
|
+
|
|
369
|
+
## Licence
|
|
370
|
+
|
|
371
|
+
ISC
|
package/package.json
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "dynamic-self-register-proxy",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Dynamic reverse proxy with self-registration API - applications can register themselves and receive an automatically assigned port",
|
|
5
|
+
"main": "proxy-client.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"dynamic-self-register-proxy": "proxy.js",
|
|
8
|
+
"dynamic-proxy": "proxy.js"
|
|
9
|
+
},
|
|
10
|
+
"exports": {
|
|
11
|
+
".": "./proxy-client.js",
|
|
12
|
+
"./client": "./proxy-client.js",
|
|
13
|
+
"./server": "./proxy.js"
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"proxy.js",
|
|
17
|
+
"proxy-client.js",
|
|
18
|
+
"README.md",
|
|
19
|
+
"LICENSE"
|
|
20
|
+
],
|
|
21
|
+
"scripts": {
|
|
22
|
+
"test": "echo \"Error: no test specified\" && exit 1",
|
|
23
|
+
"start": "node proxy.js",
|
|
24
|
+
"example": "node example-app.js"
|
|
25
|
+
},
|
|
26
|
+
"keywords": [
|
|
27
|
+
"proxy",
|
|
28
|
+
"reverse-proxy",
|
|
29
|
+
"dynamic-proxy",
|
|
30
|
+
"self-register",
|
|
31
|
+
"auto-register",
|
|
32
|
+
"microservices",
|
|
33
|
+
"docker",
|
|
34
|
+
"gateway",
|
|
35
|
+
"api-gateway",
|
|
36
|
+
"load-balancer",
|
|
37
|
+
"http-proxy",
|
|
38
|
+
"express"
|
|
39
|
+
],
|
|
40
|
+
"author": "Matthieu Pesnot-Pin",
|
|
41
|
+
"license": "ISC",
|
|
42
|
+
"repository": {
|
|
43
|
+
"type": "git",
|
|
44
|
+
"url": "git+https://github.com/matthieu-music/dynamic-self-register-proxy.git"
|
|
45
|
+
},
|
|
46
|
+
"homepage": "https://github.com/matthieu-music/dynamic-self-register-proxy#readme",
|
|
47
|
+
"bugs": {
|
|
48
|
+
"url": "https://github.com/matthieu-music/dynamic-self-register-proxy/issues"
|
|
49
|
+
},
|
|
50
|
+
"engines": {
|
|
51
|
+
"node": ">=18.0.0"
|
|
52
|
+
},
|
|
53
|
+
"dependencies": {
|
|
54
|
+
"express": "^5.2.1",
|
|
55
|
+
"http-proxy-middleware": "^3.0.5"
|
|
56
|
+
}
|
|
57
|
+
}
|
package/proxy-client.js
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ProxyClient - Helper pour s'enregistrer auprès du Dynamic Proxy
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* const ProxyClient = require('./proxy-client');
|
|
6
|
+
* const proxy = new ProxyClient('http://localhost:3000');
|
|
7
|
+
* const { port } = await proxy.register('/myapp', 'My App');
|
|
8
|
+
* app.listen(port);
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
class ProxyClient {
|
|
12
|
+
/**
|
|
13
|
+
* @param {string} proxyUrl - URL du proxy (ex: 'http://localhost:3000')
|
|
14
|
+
*/
|
|
15
|
+
constructor(proxyUrl = 'http://localhost:3000') {
|
|
16
|
+
this.proxyUrl = proxyUrl;
|
|
17
|
+
this.registeredPath = null;
|
|
18
|
+
this.assignedPort = null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Enregistre une route auprès du proxy
|
|
23
|
+
* @param {string} path - Chemin URL (ex: '/myapp')
|
|
24
|
+
* @param {string} [name] - Nom de l'application
|
|
25
|
+
* @param {number} [port] - Port spécifique (optionnel, sinon auto-attribué)
|
|
26
|
+
* @returns {Promise<{success: boolean, path: string, port: number, name: string}>}
|
|
27
|
+
*/
|
|
28
|
+
async register(path, name, port) {
|
|
29
|
+
const body = { path, name };
|
|
30
|
+
if (port) body.port = port;
|
|
31
|
+
|
|
32
|
+
const response = await fetch(`${this.proxyUrl}/proxy/register`, {
|
|
33
|
+
method: 'POST',
|
|
34
|
+
headers: { 'Content-Type': 'application/json' },
|
|
35
|
+
body: JSON.stringify(body)
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const data = await response.json();
|
|
39
|
+
|
|
40
|
+
if (!response.ok) {
|
|
41
|
+
throw new Error(data.error || 'Registration failed');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
this.registeredPath = data.path;
|
|
45
|
+
this.assignedPort = data.port;
|
|
46
|
+
|
|
47
|
+
return data;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Supprime l'enregistrement de cette application
|
|
52
|
+
* @returns {Promise<{success: boolean, path: string, freedPort: number}>}
|
|
53
|
+
*/
|
|
54
|
+
async unregister() {
|
|
55
|
+
if (!this.registeredPath) {
|
|
56
|
+
return { success: false, error: 'Not registered' };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
try {
|
|
60
|
+
const response = await fetch(`${this.proxyUrl}/proxy/unregister`, {
|
|
61
|
+
method: 'DELETE',
|
|
62
|
+
headers: { 'Content-Type': 'application/json' },
|
|
63
|
+
body: JSON.stringify({ path: this.registeredPath })
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
const data = await response.json();
|
|
67
|
+
|
|
68
|
+
if (response.ok) {
|
|
69
|
+
this.registeredPath = null;
|
|
70
|
+
this.assignedPort = null;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return data;
|
|
74
|
+
} catch (error) {
|
|
75
|
+
console.error('Unregister error:', error.message);
|
|
76
|
+
return { success: false, error: error.message };
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Liste toutes les routes enregistrées
|
|
82
|
+
* @returns {Promise<{count: number, routes: Array, availablePorts: number}>}
|
|
83
|
+
*/
|
|
84
|
+
async listRoutes() {
|
|
85
|
+
const response = await fetch(`${this.proxyUrl}/proxy/routes`);
|
|
86
|
+
return response.json();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Vérifie l'état du proxy
|
|
91
|
+
* @returns {Promise<{status: string, uptime: number, registeredRoutes: number}>}
|
|
92
|
+
*/
|
|
93
|
+
async health() {
|
|
94
|
+
const response = await fetch(`${this.proxyUrl}/proxy/health`);
|
|
95
|
+
return response.json();
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Configure les handlers pour un arrêt propre
|
|
100
|
+
* @param {http.Server} server - Instance du serveur HTTP/Express
|
|
101
|
+
*/
|
|
102
|
+
setupGracefulShutdown(server) {
|
|
103
|
+
const shutdown = async () => {
|
|
104
|
+
console.log('Shutting down...');
|
|
105
|
+
await this.unregister();
|
|
106
|
+
server.close(() => {
|
|
107
|
+
console.log('Server closed.');
|
|
108
|
+
process.exit(0);
|
|
109
|
+
});
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
process.on('SIGTERM', shutdown);
|
|
113
|
+
process.on('SIGINT', shutdown);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Ajoute la route /proxy/health requise par le proxy pour le health check polling
|
|
118
|
+
* @param {express.Application} app - Instance de l'application Express
|
|
119
|
+
* @param {object} [options] - Options de configuration
|
|
120
|
+
* @param {function} [options.healthCheck] - Fonction personnalisée pour vérifier la santé (doit retourner true/false)
|
|
121
|
+
*/
|
|
122
|
+
setupHealthRoute(app, options = {}) {
|
|
123
|
+
app.get('/proxy/health', async (req, res) => {
|
|
124
|
+
try {
|
|
125
|
+
// Si une fonction de health check personnalisée est fournie
|
|
126
|
+
if (options.healthCheck) {
|
|
127
|
+
const isHealthy = await options.healthCheck();
|
|
128
|
+
if (isHealthy) {
|
|
129
|
+
return res.status(200).json({ status: 'healthy' });
|
|
130
|
+
} else {
|
|
131
|
+
return res.status(503).json({ status: 'unhealthy' });
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Par défaut, retourne 200 si le serveur répond
|
|
136
|
+
res.status(200).json({ status: 'healthy' });
|
|
137
|
+
} catch (error) {
|
|
138
|
+
res.status(503).json({ status: 'unhealthy', error: error.message });
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
module.exports = ProxyClient;
|
package/proxy.js
ADDED
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const express = require('express');
|
|
4
|
+
const { createProxyMiddleware } = require('http-proxy-middleware');
|
|
5
|
+
|
|
6
|
+
const app = express();
|
|
7
|
+
app.use(express.json());
|
|
8
|
+
|
|
9
|
+
// ============================================
|
|
10
|
+
// CONFIGURATION
|
|
11
|
+
// ============================================
|
|
12
|
+
const PROXY_PORT = process.env.PROXY_PORT || 3005;
|
|
13
|
+
const INTERNAL_PORT_START = 4000;
|
|
14
|
+
const INTERNAL_PORT_END = 5000;
|
|
15
|
+
const HEALTH_CHECK_INTERVAL = process.env.HEALTH_CHECK_INTERVAL || 30000; // 30 secondes par défaut
|
|
16
|
+
const HEALTH_CHECK_TIMEOUT = process.env.HEALTH_CHECK_TIMEOUT || 5000; // 5 secondes timeout
|
|
17
|
+
|
|
18
|
+
// ============================================
|
|
19
|
+
// REGISTRY - Stockage des routes en mémoire
|
|
20
|
+
// ============================================
|
|
21
|
+
const registry = {
|
|
22
|
+
routes: new Map(), // path -> { port, name, registeredAt }
|
|
23
|
+
usedPorts: new Set(), // Ports déjà attribués
|
|
24
|
+
nextPort: INTERNAL_PORT_START
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Trouve le prochain port disponible
|
|
29
|
+
*/
|
|
30
|
+
function getNextAvailablePort() {
|
|
31
|
+
while (registry.usedPorts.has(registry.nextPort)) {
|
|
32
|
+
registry.nextPort++;
|
|
33
|
+
if (registry.nextPort > INTERNAL_PORT_END) {
|
|
34
|
+
registry.nextPort = INTERNAL_PORT_START;
|
|
35
|
+
// Vérifie si tous les ports sont utilisés
|
|
36
|
+
if (registry.usedPorts.size >= (INTERNAL_PORT_END - INTERNAL_PORT_START)) {
|
|
37
|
+
throw new Error('No available ports');
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
const port = registry.nextPort;
|
|
42
|
+
registry.nextPort++;
|
|
43
|
+
return port;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Trouve la route correspondant à un chemin de requête
|
|
48
|
+
*/
|
|
49
|
+
function findRouteForPath(requestPath) {
|
|
50
|
+
// Tri des routes par longueur décroissante pour matcher le plus spécifique d'abord
|
|
51
|
+
const sortedPaths = [...registry.routes.keys()].sort((a, b) => b.length - a.length);
|
|
52
|
+
|
|
53
|
+
for (const routePath of sortedPaths) {
|
|
54
|
+
if (requestPath.startsWith(routePath)) {
|
|
55
|
+
return registry.routes.get(routePath);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// ============================================
|
|
62
|
+
// HEALTH CHECK POLLING
|
|
63
|
+
// ============================================
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Vérifie la santé d'un serveur enregistré
|
|
67
|
+
* @param {string} path - Le chemin enregistré
|
|
68
|
+
* @param {object} route - Les informations de la route
|
|
69
|
+
* @returns {Promise<boolean>} - true si le serveur est en bonne santé
|
|
70
|
+
*/
|
|
71
|
+
async function checkServerHealth(path, route) {
|
|
72
|
+
const healthUrl = `http://localhost:${route.port}/proxy/health`;
|
|
73
|
+
|
|
74
|
+
try {
|
|
75
|
+
const controller = new AbortController();
|
|
76
|
+
const timeoutId = setTimeout(() => controller.abort(), HEALTH_CHECK_TIMEOUT);
|
|
77
|
+
|
|
78
|
+
const response = await fetch(healthUrl, {
|
|
79
|
+
method: 'GET',
|
|
80
|
+
signal: controller.signal
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
clearTimeout(timeoutId);
|
|
84
|
+
|
|
85
|
+
return response.status === 200;
|
|
86
|
+
} catch (error) {
|
|
87
|
+
// Timeout, connexion refusée, ou autre erreur
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Désenregistre un serveur (utilisé par le health check)
|
|
94
|
+
* @param {string} path - Le chemin à désenregistrer
|
|
95
|
+
*/
|
|
96
|
+
function unregisterServer(path) {
|
|
97
|
+
const route = registry.routes.get(path);
|
|
98
|
+
if (route) {
|
|
99
|
+
registry.routes.delete(path);
|
|
100
|
+
registry.usedPorts.delete(route.port);
|
|
101
|
+
console.log(`[HEALTH CHECK] Unregistered ${path} (was on port ${route.port}) - server unhealthy`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Effectue un health check sur tous les serveurs enregistrés
|
|
107
|
+
*/
|
|
108
|
+
async function performHealthChecks() {
|
|
109
|
+
if (registry.routes.size === 0) {
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
console.log(`[HEALTH CHECK] Checking ${registry.routes.size} registered server(s)...`);
|
|
114
|
+
|
|
115
|
+
const checks = [];
|
|
116
|
+
|
|
117
|
+
for (const [path, route] of registry.routes.entries()) {
|
|
118
|
+
checks.push(
|
|
119
|
+
checkServerHealth(path, route).then(isHealthy => ({
|
|
120
|
+
path,
|
|
121
|
+
route,
|
|
122
|
+
isHealthy
|
|
123
|
+
}))
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const results = await Promise.all(checks);
|
|
128
|
+
|
|
129
|
+
for (const { path, isHealthy } of results) {
|
|
130
|
+
if (!isHealthy) {
|
|
131
|
+
unregisterServer(path);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const healthyCount = results.filter(r => r.isHealthy).length;
|
|
136
|
+
const unhealthyCount = results.length - healthyCount;
|
|
137
|
+
|
|
138
|
+
if (unhealthyCount > 0) {
|
|
139
|
+
console.log(`[HEALTH CHECK] Complete: ${healthyCount} healthy, ${unhealthyCount} removed`);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Démarre le polling de health check
|
|
145
|
+
*/
|
|
146
|
+
function startHealthCheckPolling() {
|
|
147
|
+
console.log(`[HEALTH CHECK] Starting polling every ${HEALTH_CHECK_INTERVAL}ms`);
|
|
148
|
+
setInterval(performHealthChecks, HEALTH_CHECK_INTERVAL);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// ============================================
|
|
152
|
+
// API D'ENREGISTREMENT
|
|
153
|
+
// ============================================
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* POST /proxy/register
|
|
157
|
+
* Enregistre une nouvelle route
|
|
158
|
+
* Body: { path: "/myapp", name: "My Application", port?: 4001 }
|
|
159
|
+
* Response: { success: true, path: "/myapp", port: 4001 }
|
|
160
|
+
*/
|
|
161
|
+
app.post('/proxy/register', (req, res) => {
|
|
162
|
+
try {
|
|
163
|
+
const { path, name, port: requestedPort } = req.body;
|
|
164
|
+
|
|
165
|
+
if (!path) {
|
|
166
|
+
return res.status(400).json({
|
|
167
|
+
success: false,
|
|
168
|
+
error: 'Path is required'
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Normalise le path (doit commencer par /)
|
|
173
|
+
const normalizedPath = path.startsWith('/') ? path : `/${path}`;
|
|
174
|
+
|
|
175
|
+
// Vérifie si le path existe déjà
|
|
176
|
+
if (registry.routes.has(normalizedPath)) {
|
|
177
|
+
const existing = registry.routes.get(normalizedPath);
|
|
178
|
+
return res.status(409).json({
|
|
179
|
+
success: false,
|
|
180
|
+
error: 'Path already registered',
|
|
181
|
+
existing: {
|
|
182
|
+
path: normalizedPath,
|
|
183
|
+
port: existing.port,
|
|
184
|
+
name: existing.name
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Attribution du port
|
|
190
|
+
let port;
|
|
191
|
+
if (requestedPort) {
|
|
192
|
+
// Port spécifique demandé
|
|
193
|
+
if (registry.usedPorts.has(requestedPort)) {
|
|
194
|
+
return res.status(409).json({
|
|
195
|
+
success: false,
|
|
196
|
+
error: `Port ${requestedPort} already in use`
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
port = requestedPort;
|
|
200
|
+
} else {
|
|
201
|
+
// Attribution automatique
|
|
202
|
+
port = getNextAvailablePort();
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Enregistrement
|
|
206
|
+
registry.routes.set(normalizedPath, {
|
|
207
|
+
port,
|
|
208
|
+
name: name || normalizedPath,
|
|
209
|
+
registeredAt: new Date().toISOString()
|
|
210
|
+
});
|
|
211
|
+
registry.usedPorts.add(port);
|
|
212
|
+
|
|
213
|
+
console.log(`[REGISTER] ${normalizedPath} -> localhost:${port} (${name || 'unnamed'})`);
|
|
214
|
+
|
|
215
|
+
res.json({
|
|
216
|
+
success: true,
|
|
217
|
+
path: normalizedPath,
|
|
218
|
+
port,
|
|
219
|
+
name: name || normalizedPath,
|
|
220
|
+
message: `Route registered. Start your server on port ${port}`
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
} catch (error) {
|
|
224
|
+
console.error('[REGISTER ERROR]', error.message);
|
|
225
|
+
res.status(500).json({
|
|
226
|
+
success: false,
|
|
227
|
+
error: error.message
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* DELETE /proxy/unregister
|
|
234
|
+
* Supprime une route
|
|
235
|
+
* Body: { path: "/myapp" }
|
|
236
|
+
*/
|
|
237
|
+
app.delete('/proxy/unregister', (req, res) => {
|
|
238
|
+
try {
|
|
239
|
+
const { path } = req.body;
|
|
240
|
+
|
|
241
|
+
if (!path) {
|
|
242
|
+
return res.status(400).json({
|
|
243
|
+
success: false,
|
|
244
|
+
error: 'Path is required'
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const normalizedPath = path.startsWith('/') ? path : `/${path}`;
|
|
249
|
+
|
|
250
|
+
if (!registry.routes.has(normalizedPath)) {
|
|
251
|
+
return res.status(404).json({
|
|
252
|
+
success: false,
|
|
253
|
+
error: 'Path not found'
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const route = registry.routes.get(normalizedPath);
|
|
258
|
+
registry.routes.delete(normalizedPath);
|
|
259
|
+
registry.usedPorts.delete(route.port);
|
|
260
|
+
|
|
261
|
+
console.log(`[UNREGISTER] ${normalizedPath} (was on port ${route.port})`);
|
|
262
|
+
|
|
263
|
+
res.json({
|
|
264
|
+
success: true,
|
|
265
|
+
path: normalizedPath,
|
|
266
|
+
freedPort: route.port
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
} catch (error) {
|
|
270
|
+
console.error('[UNREGISTER ERROR]', error.message);
|
|
271
|
+
res.status(500).json({
|
|
272
|
+
success: false,
|
|
273
|
+
error: error.message
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* GET /proxy/routes
|
|
280
|
+
* Liste toutes les routes enregistrées
|
|
281
|
+
*/
|
|
282
|
+
app.get('/proxy/routes', (req, res) => {
|
|
283
|
+
const routes = [];
|
|
284
|
+
registry.routes.forEach((value, path) => {
|
|
285
|
+
routes.push({
|
|
286
|
+
path,
|
|
287
|
+
port: value.port,
|
|
288
|
+
name: value.name,
|
|
289
|
+
registeredAt: value.registeredAt,
|
|
290
|
+
target: `http://localhost:${value.port}`
|
|
291
|
+
});
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
res.json({
|
|
295
|
+
count: routes.length,
|
|
296
|
+
routes,
|
|
297
|
+
availablePorts: (INTERNAL_PORT_END - INTERNAL_PORT_START) - registry.usedPorts.size
|
|
298
|
+
});
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* GET /proxy/health
|
|
303
|
+
* Health check du proxy
|
|
304
|
+
*/
|
|
305
|
+
app.get('/proxy/health', (req, res) => {
|
|
306
|
+
res.json({
|
|
307
|
+
status: 'healthy',
|
|
308
|
+
uptime: process.uptime(),
|
|
309
|
+
registeredRoutes: registry.routes.size,
|
|
310
|
+
usedPorts: registry.usedPorts.size
|
|
311
|
+
});
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
// ============================================
|
|
315
|
+
// PROXY MIDDLEWARE
|
|
316
|
+
// ============================================
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Router dynamique - détermine la cible en fonction de la requête
|
|
320
|
+
*/
|
|
321
|
+
const dynamicRouter = (req) => {
|
|
322
|
+
const route = findRouteForPath(req.path);
|
|
323
|
+
if (route) {
|
|
324
|
+
return `http://localhost:${route.port}`;
|
|
325
|
+
}
|
|
326
|
+
return null;
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Middleware de proxy pour toutes les autres requêtes
|
|
331
|
+
*/
|
|
332
|
+
app.use((req, res, next) => {
|
|
333
|
+
// Ignore les routes de l'API proxy
|
|
334
|
+
if (req.path.startsWith('/proxy/')) {
|
|
335
|
+
return next();
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
const target = dynamicRouter(req);
|
|
339
|
+
|
|
340
|
+
if (!target) {
|
|
341
|
+
return res.status(404).json({
|
|
342
|
+
error: 'No route registered for this path',
|
|
343
|
+
path: req.path,
|
|
344
|
+
hint: 'Register a route with POST /proxy/register'
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// Trouve le path de la route pour le pathRewrite
|
|
349
|
+
const route = findRouteForPath(req.path);
|
|
350
|
+
const routePath = [...registry.routes.entries()]
|
|
351
|
+
.find(([, v]) => v === route)?.[0];
|
|
352
|
+
|
|
353
|
+
const proxyMiddleware = createProxyMiddleware({
|
|
354
|
+
target,
|
|
355
|
+
changeOrigin: true,
|
|
356
|
+
pathRewrite: (path) => {
|
|
357
|
+
// Retire le préfixe de la route du path
|
|
358
|
+
if (routePath && path.startsWith(routePath)) {
|
|
359
|
+
const newPath = path.slice(routePath.length) || '/';
|
|
360
|
+
return newPath;
|
|
361
|
+
}
|
|
362
|
+
return path;
|
|
363
|
+
},
|
|
364
|
+
on: {
|
|
365
|
+
proxyReq: (proxyReq, req) => {
|
|
366
|
+
// Ré-écriture du body si présent (nécessaire car express.json() a consommé le stream)
|
|
367
|
+
if (req.body && Object.keys(req.body).length > 0) {
|
|
368
|
+
const bodyData = JSON.stringify(req.body);
|
|
369
|
+
proxyReq.setHeader('Content-Type', 'application/json');
|
|
370
|
+
proxyReq.setHeader('Content-Length', Buffer.byteLength(bodyData));
|
|
371
|
+
proxyReq.write(bodyData);
|
|
372
|
+
}
|
|
373
|
+
console.log(`[PROXY] ${req.method} ${req.path} -> ${target}`);
|
|
374
|
+
},
|
|
375
|
+
error: (err, req, res) => {
|
|
376
|
+
console.error(`[PROXY ERROR] ${req.path}:`, err.message);
|
|
377
|
+
res.status(502).json({
|
|
378
|
+
error: 'Proxy error',
|
|
379
|
+
message: err.message,
|
|
380
|
+
target
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
proxyMiddleware(req, res, next);
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
// ============================================
|
|
390
|
+
// DÉMARRAGE DU SERVEUR
|
|
391
|
+
// ============================================
|
|
392
|
+
|
|
393
|
+
app.listen(PROXY_PORT, () => {
|
|
394
|
+
console.log('='.repeat(50));
|
|
395
|
+
console.log('🚀 Dynamic Proxy Server');
|
|
396
|
+
console.log('='.repeat(50));
|
|
397
|
+
console.log(`Listening on port ${PROXY_PORT}`);
|
|
398
|
+
console.log(`Internal ports range: ${INTERNAL_PORT_START}-${INTERNAL_PORT_END}`);
|
|
399
|
+
console.log(`Health check interval: ${HEALTH_CHECK_INTERVAL}ms`);
|
|
400
|
+
console.log('');
|
|
401
|
+
console.log('API Endpoints:');
|
|
402
|
+
console.log(' POST /proxy/register - Register a new route');
|
|
403
|
+
console.log(' DELETE /proxy/unregister - Remove a route');
|
|
404
|
+
console.log(' GET /proxy/routes - List all routes');
|
|
405
|
+
console.log(' GET /proxy/health - Health check');
|
|
406
|
+
console.log('');
|
|
407
|
+
console.log('Note: Registered servers must implement GET /proxy/health');
|
|
408
|
+
console.log(' returning status 200 to stay registered.');
|
|
409
|
+
console.log('='.repeat(50));
|
|
410
|
+
|
|
411
|
+
// Démarre le polling de health check
|
|
412
|
+
startHealthCheckPolling();
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
module.exports = { app, registry };
|