dynamic-self-register-proxy 1.0.5 → 1.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/package.json +1 -1
- package/proxy.js +89 -60
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.8",
|
|
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": {
|
package/proxy.js
CHANGED
|
@@ -148,19 +148,20 @@ async function checkServerHealth(path, route) {
|
|
|
148
148
|
}
|
|
149
149
|
|
|
150
150
|
/**
|
|
151
|
-
* Désenregistre un serveur
|
|
151
|
+
* Désenregistre un serveur
|
|
152
152
|
* Utilise le mutex pour éviter les conflits avec les inscriptions
|
|
153
153
|
* @param {string} path - Le chemin à désenregistrer
|
|
154
|
+
* @param {string} reason - La raison du désenregistrement (pour les logs)
|
|
154
155
|
* @returns {Promise<void>}
|
|
155
156
|
*/
|
|
156
|
-
async function unregisterServer(path) {
|
|
157
|
+
async function unregisterServer(path, reason = 'unhealthy') {
|
|
157
158
|
await registrationMutex.acquire();
|
|
158
159
|
try {
|
|
159
160
|
const route = registry.routes.get(path);
|
|
160
161
|
if (route) {
|
|
161
162
|
registry.routes.delete(path);
|
|
162
163
|
registry.usedPorts.delete(route.port);
|
|
163
|
-
console.log(`[
|
|
164
|
+
console.log(`[UNREGISTER] ${path} (was on port ${route.port}) - reason: ${reason}`);
|
|
164
165
|
}
|
|
165
166
|
} finally {
|
|
166
167
|
registrationMutex.release();
|
|
@@ -220,7 +221,7 @@ async function performHealthChecks() {
|
|
|
220
221
|
|
|
221
222
|
for (const { path, isHealthy } of results) {
|
|
222
223
|
if (!isHealthy) {
|
|
223
|
-
await unregisterServer(path);
|
|
224
|
+
await unregisterServer(path, 'failed health check');
|
|
224
225
|
}
|
|
225
226
|
}
|
|
226
227
|
|
|
@@ -275,15 +276,29 @@ app.post('/proxy/register', async (req, res) => {
|
|
|
275
276
|
// Vérifie si le path existe déjà
|
|
276
277
|
if (registry.routes.has(normalizedPath)) {
|
|
277
278
|
const existing = registry.routes.get(normalizedPath);
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
279
|
+
|
|
280
|
+
// On vérifie si le serveur existant est réellement encore en vie
|
|
281
|
+
// On relâche temporairement le mutex pour ne pas bloquer tout le proxy pendant le fetch
|
|
282
|
+
registrationMutex.release();
|
|
283
|
+
const isAlive = await checkServerHealth(normalizedPath, existing);
|
|
284
|
+
await registrationMutex.acquire();
|
|
285
|
+
|
|
286
|
+
// Si le serveur est mort, on l'écrase sans erreur
|
|
287
|
+
if (!isAlive) {
|
|
288
|
+
console.log(`[REGISTER] Path ${normalizedPath} was occupied by a dead server. Overwriting...`);
|
|
289
|
+
registry.usedPorts.delete(existing.port);
|
|
290
|
+
registry.routes.delete(normalizedPath);
|
|
291
|
+
} else {
|
|
292
|
+
return res.status(409).json({
|
|
293
|
+
success: false,
|
|
294
|
+
error: 'Path already registered',
|
|
295
|
+
existing: {
|
|
296
|
+
path: normalizedPath,
|
|
297
|
+
port: existing.port,
|
|
298
|
+
name: existing.name
|
|
299
|
+
}
|
|
300
|
+
});
|
|
301
|
+
}
|
|
287
302
|
}
|
|
288
303
|
|
|
289
304
|
// Attribution du port
|
|
@@ -818,22 +833,66 @@ function escapeHtml(text) {
|
|
|
818
833
|
}
|
|
819
834
|
|
|
820
835
|
// ============================================
|
|
821
|
-
// PROXY MIDDLEWARE
|
|
836
|
+
// PROXY MIDDLEWARE (PERSISTENT)
|
|
822
837
|
// ============================================
|
|
823
838
|
|
|
824
839
|
/**
|
|
825
|
-
*
|
|
840
|
+
* Middleware de proxy persistant pour éviter les fuites de mémoire (EventEmitter MaxListenersExceededWarning)
|
|
841
|
+
* Créé une seule fois et réutilisé pour toutes les requêtes.
|
|
826
842
|
*/
|
|
827
|
-
const
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
843
|
+
const persistentProxyMiddleware = createProxyMiddleware({
|
|
844
|
+
target: 'http://localhost', // Cible par défaut (sera surchargée par router)
|
|
845
|
+
router: (req) => req.proxyTarget,
|
|
846
|
+
changeOrigin: true,
|
|
847
|
+
pathRewrite: (path, req) => {
|
|
848
|
+
const routePath = req.proxyRoutePath;
|
|
849
|
+
if (routePath && path.startsWith(routePath)) {
|
|
850
|
+
return path.slice(routePath.length) || '/';
|
|
851
|
+
}
|
|
852
|
+
return path;
|
|
853
|
+
},
|
|
854
|
+
on: {
|
|
855
|
+
proxyReq: (proxyReq, req) => {
|
|
856
|
+
// Ré-écriture du body si présent (nécessaire car express.json() a consommé le stream)
|
|
857
|
+
if (req.body && typeof req.body === 'object' && Object.keys(req.body).length > 0) {
|
|
858
|
+
const bodyData = JSON.stringify(req.body);
|
|
859
|
+
proxyReq.setHeader('Content-Type', 'application/json');
|
|
860
|
+
proxyReq.setHeader('Content-Length', Buffer.byteLength(bodyData));
|
|
861
|
+
proxyReq.write(bodyData);
|
|
862
|
+
}
|
|
863
|
+
console.log(`[PROXY] ${req.method} ${req.path} -> ${req.proxyTarget}`);
|
|
864
|
+
},
|
|
865
|
+
error: (err, req, res) => {
|
|
866
|
+
// Si la réponse a déjà été envoyée, on ne fait rien
|
|
867
|
+
if (res.headersSent) return;
|
|
868
|
+
|
|
869
|
+
console.error(`[PROXY ERROR] ${req.path}:`, err.message);
|
|
870
|
+
|
|
871
|
+
// Si la connexion est refusée, le serveur est probablement mort.
|
|
872
|
+
// On le désenregistre immédiatement pour éviter d'autres erreurs.
|
|
873
|
+
if (err.code === 'ECONNREFUSED' || err.code === 'ETIMEDOUT') {
|
|
874
|
+
const route = findRouteForPath(req.path);
|
|
875
|
+
if (route) {
|
|
876
|
+
const routePath = [...registry.routes.entries()]
|
|
877
|
+
.find(([, v]) => v === route)?.[0];
|
|
878
|
+
if (routePath) {
|
|
879
|
+
unregisterServer(routePath, `connection error (${err.code})`).catch(() => {});
|
|
880
|
+
console.log(`[PROXY] Auto-unregistered ${routePath} due to connection error`);
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
res.status(502).json({
|
|
886
|
+
error: 'Proxy error',
|
|
887
|
+
message: err.message,
|
|
888
|
+
target: req.proxyTarget
|
|
889
|
+
});
|
|
890
|
+
}
|
|
831
891
|
}
|
|
832
|
-
|
|
833
|
-
};
|
|
892
|
+
});
|
|
834
893
|
|
|
835
894
|
/**
|
|
836
|
-
* Middleware
|
|
895
|
+
* Middleware principal qui gère le routage vers le proxy
|
|
837
896
|
*/
|
|
838
897
|
app.use((req, res, next) => {
|
|
839
898
|
// Ignore les routes de l'API proxy
|
|
@@ -841,9 +900,9 @@ app.use((req, res, next) => {
|
|
|
841
900
|
return next();
|
|
842
901
|
}
|
|
843
902
|
|
|
844
|
-
const
|
|
903
|
+
const route = findRouteForPath(req.path);
|
|
845
904
|
|
|
846
|
-
if (!
|
|
905
|
+
if (!route) {
|
|
847
906
|
return res.status(404).json({
|
|
848
907
|
error: 'No route registered for this path',
|
|
849
908
|
path: req.path,
|
|
@@ -851,45 +910,15 @@ app.use((req, res, next) => {
|
|
|
851
910
|
});
|
|
852
911
|
}
|
|
853
912
|
|
|
913
|
+
// Stocke les informations de la route dans l'objet req pour le middleware persistant
|
|
914
|
+
req.proxyTarget = `http://localhost:${route.port}`;
|
|
915
|
+
|
|
854
916
|
// Trouve le path de la route pour le pathRewrite
|
|
855
|
-
|
|
856
|
-
const routePath = [...registry.routes.entries()]
|
|
917
|
+
req.proxyRoutePath = [...registry.routes.entries()]
|
|
857
918
|
.find(([, v]) => v === route)?.[0];
|
|
858
919
|
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
changeOrigin: true,
|
|
862
|
-
pathRewrite: (path) => {
|
|
863
|
-
// Retire le préfixe de la route du path
|
|
864
|
-
if (routePath && path.startsWith(routePath)) {
|
|
865
|
-
const newPath = path.slice(routePath.length) || '/';
|
|
866
|
-
return newPath;
|
|
867
|
-
}
|
|
868
|
-
return path;
|
|
869
|
-
},
|
|
870
|
-
on: {
|
|
871
|
-
proxyReq: (proxyReq, req) => {
|
|
872
|
-
// Ré-écriture du body si présent (nécessaire car express.json() a consommé le stream)
|
|
873
|
-
if (req.body && Object.keys(req.body).length > 0) {
|
|
874
|
-
const bodyData = JSON.stringify(req.body);
|
|
875
|
-
proxyReq.setHeader('Content-Type', 'application/json');
|
|
876
|
-
proxyReq.setHeader('Content-Length', Buffer.byteLength(bodyData));
|
|
877
|
-
proxyReq.write(bodyData);
|
|
878
|
-
}
|
|
879
|
-
console.log(`[PROXY] ${req.method} ${req.path} -> ${target}`);
|
|
880
|
-
},
|
|
881
|
-
error: (err, req, res) => {
|
|
882
|
-
console.error(`[PROXY ERROR] ${req.path}:`, err.message);
|
|
883
|
-
res.status(502).json({
|
|
884
|
-
error: 'Proxy error',
|
|
885
|
-
message: err.message,
|
|
886
|
-
target
|
|
887
|
-
});
|
|
888
|
-
}
|
|
889
|
-
}
|
|
890
|
-
});
|
|
891
|
-
|
|
892
|
-
proxyMiddleware(req, res, next);
|
|
920
|
+
// Délègue au middleware de proxy persistant
|
|
921
|
+
persistentProxyMiddleware(req, res, next);
|
|
893
922
|
});
|
|
894
923
|
|
|
895
924
|
// ============================================
|