dynamic-self-register-proxy 1.0.4 → 1.0.7

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 +180 -51
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dynamic-self-register-proxy",
3
- "version": "1.0.4",
3
+ "version": "1.0.7",
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
@@ -9,7 +9,7 @@ app.use(express.json());
9
9
  // ============================================
10
10
  // CONFIGURATION
11
11
  // ============================================
12
- const PROXY_PORT = process.env.PROXY_PORT || 3005;
12
+ const PROXY_PORT = process.env.PROXY_PORT || 3000;
13
13
  const INTERNAL_PORT_START = 4000;
14
14
  const INTERNAL_PORT_END = 5000;
15
15
  const HEALTH_CHECK_INTERVAL = process.env.HEALTH_CHECK_INTERVAL || 30000; // 30 secondes par défaut
@@ -413,6 +413,52 @@ app.get('/proxy/routes', (req, res) => {
413
413
  });
414
414
  });
415
415
 
416
+ /**
417
+ * POST /proxy/check
418
+ * Déclenche manuellement un health check pour une route spécifique
419
+ * Body: { path: "/myapp" }
420
+ */
421
+ app.post('/proxy/check', async (req, res) => {
422
+ const { path } = req.body;
423
+
424
+ if (!path) {
425
+ return res.status(400).json({
426
+ success: false,
427
+ error: 'Path is required'
428
+ });
429
+ }
430
+
431
+ const normalizedPath = path.startsWith('/') ? path : `/${path}`;
432
+ const route = registry.routes.get(normalizedPath);
433
+
434
+ if (!route) {
435
+ return res.status(404).json({
436
+ success: false,
437
+ error: 'Path not found'
438
+ });
439
+ }
440
+
441
+ console.log(`[MANUAL HEALTH CHECK] Checking ${normalizedPath}...`);
442
+ const isHealthy = await checkServerHealth(normalizedPath, route);
443
+
444
+ if (!isHealthy) {
445
+ await unregisterServer(normalizedPath);
446
+ return res.json({
447
+ success: true,
448
+ path: normalizedPath,
449
+ healthy: false,
450
+ message: 'Server was unhealthy and has been removed'
451
+ });
452
+ }
453
+
454
+ res.json({
455
+ success: true,
456
+ path: normalizedPath,
457
+ healthy: true,
458
+ message: 'Server is healthy'
459
+ });
460
+ });
461
+
416
462
  /**
417
463
  * GET /proxy/health
418
464
  * Health check du proxy
@@ -581,6 +627,40 @@ app.get('/', (req, res) => {
581
627
  .service-link:hover {
582
628
  opacity: 0.9;
583
629
  }
630
+ .service-actions {
631
+ display: flex;
632
+ gap: 1rem;
633
+ align-items: center;
634
+ }
635
+ .check-btn {
636
+ background: rgba(255, 255, 255, 0.1);
637
+ color: #e4e4e7;
638
+ border: 1px solid rgba(255, 255, 255, 0.2);
639
+ padding: 0.5rem 1rem;
640
+ border-radius: 8px;
641
+ cursor: pointer;
642
+ font-size: 0.85rem;
643
+ font-weight: 500;
644
+ transition: all 0.2s;
645
+ }
646
+ .check-btn:hover {
647
+ background: rgba(255, 255, 255, 0.15);
648
+ border-color: rgba(255, 255, 255, 0.3);
649
+ }
650
+ .check-btn.loading {
651
+ opacity: 0.5;
652
+ cursor: not-allowed;
653
+ }
654
+ .check-btn.healthy {
655
+ background: rgba(16, 185, 129, 0.2);
656
+ border-color: rgba(16, 185, 129, 0.4);
657
+ color: #10b981;
658
+ }
659
+ .check-btn.unhealthy {
660
+ background: rgba(239, 68, 68, 0.2);
661
+ border-color: rgba(239, 68, 68, 0.4);
662
+ color: #ef4444;
663
+ }
584
664
  .empty-state {
585
665
  text-align: center;
586
666
  padding: 4rem 2rem;
@@ -648,9 +728,14 @@ app.get('/', (req, res) => {
648
728
  <span>Port interne: ${route.port}</span>
649
729
  <span>Enregistré: ${new Date(route.registeredAt).toLocaleString('fr-FR')}</span>
650
730
  </div>
651
- <a href="${escapeHtml(route.path)}" class="service-link">
652
- Accéder au service
653
- </a>
731
+ <div class="service-actions">
732
+ <a href="${escapeHtml(route.path)}" class="service-link">
733
+ Accéder au service →
734
+ </a>
735
+ <button onclick="checkHealth('${escapeHtml(route.path)}', this)" class="check-btn">
736
+ Vérifier la santé
737
+ </button>
738
+ </div>
654
739
  </div>
655
740
  `).join('')}
656
741
  </div>
@@ -666,6 +751,51 @@ app.get('/', (req, res) => {
666
751
  <p>API: <a href="/proxy/routes">/proxy/routes</a> | <a href="/proxy/health">/proxy/health</a></p>
667
752
  </footer>
668
753
  </div>
754
+
755
+ <script>
756
+ async function checkHealth(path, btn) {
757
+ if (btn.classList.contains('loading')) return;
758
+
759
+ const originalText = btn.innerText;
760
+ btn.innerText = 'Vérification...';
761
+ btn.classList.add('loading');
762
+
763
+ try {
764
+ const response = await fetch('/proxy/check', {
765
+ method: 'POST',
766
+ headers: { 'Content-Type': 'application/json' },
767
+ body: JSON.stringify({ path })
768
+ });
769
+
770
+ const data = await response.json();
771
+
772
+ if (data.healthy) {
773
+ btn.innerText = '✅ Sain';
774
+ btn.classList.remove('loading');
775
+ btn.classList.add('healthy');
776
+ setTimeout(() => {
777
+ btn.innerText = originalText;
778
+ btn.classList.remove('healthy');
779
+ }, 3000);
780
+ } else {
781
+ btn.innerText = '❌ Hors ligne';
782
+ btn.classList.remove('loading');
783
+ btn.classList.add('unhealthy');
784
+
785
+ // Si le serveur a été supprimé, on rafraîchit la liste après un court délai
786
+ setTimeout(() => {
787
+ window.location.reload();
788
+ }, 2000);
789
+ }
790
+ } catch (error) {
791
+ btn.innerText = '⚠️ Erreur';
792
+ btn.classList.remove('loading');
793
+ setTimeout(() => {
794
+ btn.innerText = originalText;
795
+ }, 3000);
796
+ }
797
+ }
798
+ </script>
669
799
  </body>
670
800
  </html>
671
801
  `.trim();
@@ -688,22 +818,51 @@ function escapeHtml(text) {
688
818
  }
689
819
 
690
820
  // ============================================
691
- // PROXY MIDDLEWARE
821
+ // PROXY MIDDLEWARE (PERSISTENT)
692
822
  // ============================================
693
823
 
694
824
  /**
695
- * Router dynamique - détermine la cible en fonction de la requête
825
+ * Middleware de proxy persistant pour éviter les fuites de mémoire (EventEmitter MaxListenersExceededWarning)
826
+ * Créé une seule fois et réutilisé pour toutes les requêtes.
696
827
  */
697
- const dynamicRouter = (req) => {
698
- const route = findRouteForPath(req.path);
699
- if (route) {
700
- return `http://localhost:${route.port}`;
828
+ const persistentProxyMiddleware = createProxyMiddleware({
829
+ target: 'http://localhost', // Cible par défaut (sera surchargée par router)
830
+ router: (req) => req.proxyTarget,
831
+ changeOrigin: true,
832
+ pathRewrite: (path, req) => {
833
+ const routePath = req.proxyRoutePath;
834
+ if (routePath && path.startsWith(routePath)) {
835
+ return path.slice(routePath.length) || '/';
836
+ }
837
+ return path;
838
+ },
839
+ on: {
840
+ proxyReq: (proxyReq, req) => {
841
+ // Ré-écriture du body si présent (nécessaire car express.json() a consommé le stream)
842
+ if (req.body && typeof req.body === 'object' && Object.keys(req.body).length > 0) {
843
+ const bodyData = JSON.stringify(req.body);
844
+ proxyReq.setHeader('Content-Type', 'application/json');
845
+ proxyReq.setHeader('Content-Length', Buffer.byteLength(bodyData));
846
+ proxyReq.write(bodyData);
847
+ }
848
+ console.log(`[PROXY] ${req.method} ${req.path} -> ${req.proxyTarget}`);
849
+ },
850
+ error: (err, req, res) => {
851
+ // Si la réponse a déjà été envoyée, on ne fait rien
852
+ if (res.headersSent) return;
853
+
854
+ console.error(`[PROXY ERROR] ${req.path}:`, err.message);
855
+ res.status(502).json({
856
+ error: 'Proxy error',
857
+ message: err.message,
858
+ target: req.proxyTarget
859
+ });
860
+ }
701
861
  }
702
- return null;
703
- };
862
+ });
704
863
 
705
864
  /**
706
- * Middleware de proxy pour toutes les autres requêtes
865
+ * Middleware principal qui gère le routage vers le proxy
707
866
  */
708
867
  app.use((req, res, next) => {
709
868
  // Ignore les routes de l'API proxy
@@ -711,9 +870,9 @@ app.use((req, res, next) => {
711
870
  return next();
712
871
  }
713
872
 
714
- const target = dynamicRouter(req);
873
+ const route = findRouteForPath(req.path);
715
874
 
716
- if (!target) {
875
+ if (!route) {
717
876
  return res.status(404).json({
718
877
  error: 'No route registered for this path',
719
878
  path: req.path,
@@ -721,45 +880,15 @@ app.use((req, res, next) => {
721
880
  });
722
881
  }
723
882
 
883
+ // Stocke les informations de la route dans l'objet req pour le middleware persistant
884
+ req.proxyTarget = `http://localhost:${route.port}`;
885
+
724
886
  // Trouve le path de la route pour le pathRewrite
725
- const route = findRouteForPath(req.path);
726
- const routePath = [...registry.routes.entries()]
887
+ req.proxyRoutePath = [...registry.routes.entries()]
727
888
  .find(([, v]) => v === route)?.[0];
728
889
 
729
- const proxyMiddleware = createProxyMiddleware({
730
- target,
731
- changeOrigin: true,
732
- pathRewrite: (path) => {
733
- // Retire le préfixe de la route du path
734
- if (routePath && path.startsWith(routePath)) {
735
- const newPath = path.slice(routePath.length) || '/';
736
- return newPath;
737
- }
738
- return path;
739
- },
740
- on: {
741
- proxyReq: (proxyReq, req) => {
742
- // Ré-écriture du body si présent (nécessaire car express.json() a consommé le stream)
743
- if (req.body && Object.keys(req.body).length > 0) {
744
- const bodyData = JSON.stringify(req.body);
745
- proxyReq.setHeader('Content-Type', 'application/json');
746
- proxyReq.setHeader('Content-Length', Buffer.byteLength(bodyData));
747
- proxyReq.write(bodyData);
748
- }
749
- console.log(`[PROXY] ${req.method} ${req.path} -> ${target}`);
750
- },
751
- error: (err, req, res) => {
752
- console.error(`[PROXY ERROR] ${req.path}:`, err.message);
753
- res.status(502).json({
754
- error: 'Proxy error',
755
- message: err.message,
756
- target
757
- });
758
- }
759
- }
760
- });
761
-
762
- proxyMiddleware(req, res, next);
890
+ // Délègue au middleware de proxy persistant
891
+ persistentProxyMiddleware(req, res, next);
763
892
  });
764
893
 
765
894
  // ============================================