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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. 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.5",
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 (utilisé par le health check)
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(`[HEALTH CHECK] Unregistered ${path} (was on port ${route.port}) - server unhealthy`);
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
- return res.status(409).json({
279
- success: false,
280
- error: 'Path already registered',
281
- existing: {
282
- path: normalizedPath,
283
- port: existing.port,
284
- name: existing.name
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
- * Router dynamique - détermine la cible en fonction de la requête
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 dynamicRouter = (req) => {
828
- const route = findRouteForPath(req.path);
829
- if (route) {
830
- return `http://localhost:${route.port}`;
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
- return null;
833
- };
892
+ });
834
893
 
835
894
  /**
836
- * Middleware de proxy pour toutes les autres requêtes
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 target = dynamicRouter(req);
903
+ const route = findRouteForPath(req.path);
845
904
 
846
- if (!target) {
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
- const route = findRouteForPath(req.path);
856
- const routePath = [...registry.routes.entries()]
917
+ req.proxyRoutePath = [...registry.routes.entries()]
857
918
  .find(([, v]) => v === route)?.[0];
858
919
 
859
- const proxyMiddleware = createProxyMiddleware({
860
- target,
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
  // ============================================