dynamic-self-register-proxy 1.0.12 → 1.0.15
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/README.md +27 -1
- package/package.json +2 -1
- package/proxy-client.js +144 -144
- package/proxy.js +1257 -1142
package/README.md
CHANGED
|
@@ -38,7 +38,18 @@ dynamic-proxy
|
|
|
38
38
|
npm start
|
|
39
39
|
```
|
|
40
40
|
|
|
41
|
-
Le proxy démarre par défaut sur le port `3000`.
|
|
41
|
+
Le proxy démarre par défaut sur le port `3000`.
|
|
42
|
+
|
|
43
|
+
### Configuration via fichier .env
|
|
44
|
+
|
|
45
|
+
Créez un fichier `.env` à la racine du projet (voir `.env.example`) :
|
|
46
|
+
|
|
47
|
+
```env
|
|
48
|
+
PROXY_NAME=Mon Proxy Dev
|
|
49
|
+
PROXY_PORT=3000
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Configuration via variables d'environnement
|
|
42
53
|
|
|
43
54
|
```bash
|
|
44
55
|
# Windows PowerShell
|
|
@@ -79,6 +90,8 @@ http://localhost:3000/myapp/ → votre application sur le port 4000
|
|
|
79
90
|
|
|
80
91
|
L'URL racine `/` affiche une page listant tous les serveurs enregistrés avec des liens pour y accéder.
|
|
81
92
|
|
|
93
|
+
Le nom du proxy (configurable via `PROXY_NAME`) est affiché dans le titre et l'en-tête de la page.
|
|
94
|
+
|
|
82
95
|
- **Navigateurs** (`Accept: text/html`) → Page HTML avec interface moderne
|
|
83
96
|
- **Clients API** (`Accept: application/json`) → Réponse JSON
|
|
84
97
|
|
|
@@ -92,6 +105,7 @@ curl -H "Accept: application/json" http://localhost:3000/
|
|
|
92
105
|
**Réponse JSON :**
|
|
93
106
|
```json
|
|
94
107
|
{
|
|
108
|
+
"name": "Proxy Server",
|
|
95
109
|
"status": "healthy",
|
|
96
110
|
"uptime": 3600,
|
|
97
111
|
"count": 2,
|
|
@@ -207,6 +221,7 @@ Vérifie l'état du proxy.
|
|
|
207
221
|
**Response:**
|
|
208
222
|
```json
|
|
209
223
|
{
|
|
224
|
+
"name": "Proxy Server",
|
|
210
225
|
"status": "healthy",
|
|
211
226
|
"uptime": 3600,
|
|
212
227
|
"registeredRoutes": 2,
|
|
@@ -421,6 +436,7 @@ Dans votre fichier de configuration Claude Desktop (`claude_desktop_config.json`
|
|
|
421
436
|
"command": "npx",
|
|
422
437
|
"args": ["dynamic-self-register-proxy"],
|
|
423
438
|
"env": {
|
|
439
|
+
"PROXY_NAME": "MCP Gateway",
|
|
424
440
|
"PROXY_PORT": "8080",
|
|
425
441
|
"HEALTH_CHECK_INTERVAL": "60000",
|
|
426
442
|
"HEALTH_CHECK_TIMEOUT": "10000",
|
|
@@ -433,12 +449,22 @@ Dans votre fichier de configuration Claude Desktop (`claude_desktop_config.json`
|
|
|
433
449
|
|
|
434
450
|
## Configuration
|
|
435
451
|
|
|
452
|
+
Le proxy charge automatiquement le fichier `.env` s'il existe (voir `.env.example` pour un template).
|
|
453
|
+
|
|
454
|
+
### Variables du proxy
|
|
455
|
+
|
|
436
456
|
| Variable | Défaut | Description |
|
|
437
457
|
|----------|--------|-------------|
|
|
458
|
+
| `PROXY_NAME` | `Proxy Server` | Nom affiché sur la page d'accueil |
|
|
438
459
|
| `PROXY_PORT` | `3000` | Port d'écoute du proxy |
|
|
439
460
|
| `HEALTH_CHECK_INTERVAL` | `30000` | Intervalle du health check polling (ms) |
|
|
440
461
|
| `HEALTH_CHECK_TIMEOUT` | `5000` | Timeout pour chaque health check (ms) |
|
|
441
462
|
| `HEALTH_CHECK_GRACE_PERIOD` | `60000` | Période de grâce pour les nouveaux serveurs (ms) |
|
|
463
|
+
|
|
464
|
+
### Variables pour les applications clientes
|
|
465
|
+
|
|
466
|
+
| Variable | Défaut | Description |
|
|
467
|
+
|----------|--------|-------------|
|
|
442
468
|
| `PROXY_URL` | `http://localhost:3000` | URL du proxy (pour les apps) |
|
|
443
469
|
| `APP_PATH` | `/example` | Chemin de l'application |
|
|
444
470
|
| `APP_NAME` | `Example App` | Nom de l'application |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dynamic-self-register-proxy",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.15",
|
|
4
4
|
"description": "Dynamic reverse proxy with self-registration API - applications can register themselves and receive an automatically assigned port",
|
|
5
5
|
"main": "proxy-client.js",
|
|
6
6
|
"bin": {
|
|
@@ -53,6 +53,7 @@
|
|
|
53
53
|
"node": ">=18.0.0"
|
|
54
54
|
},
|
|
55
55
|
"dependencies": {
|
|
56
|
+
"dotenv": "^17.2.3",
|
|
56
57
|
"express": "^5.2.1",
|
|
57
58
|
"http-proxy-middleware": "^3.0.5"
|
|
58
59
|
}
|
package/proxy-client.js
CHANGED
|
@@ -1,144 +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;
|
|
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;
|