mirror of
https://github.com/KeymonSoft/jRDC-MultiDB-Hikari.git
synced 2026-04-17 21:06:23 +00:00
Commit inicial
This commit is contained in:
131
SSEHandler.bas
Normal file
131
SSEHandler.bas
Normal file
@@ -0,0 +1,131 @@
|
||||
B4J=true
|
||||
Group=Default Group
|
||||
ModulesStructureVersion=1
|
||||
Type=Class
|
||||
Version=10.3
|
||||
@EndOfDesignText@
|
||||
' Handler class: StatsSSEHandler.b4j
|
||||
' Gestiona y transmite en tiempo real las estadísticas del pool de conexiones vía Server-Sent Events (SSE).
|
||||
' Opera en modo Singleton: una única instancia maneja todas las conexiones.
|
||||
|
||||
Sub Class_Globals
|
||||
' Almacena de forma centralizada a todos los clientes (navegadores) conectados.
|
||||
' La clave es un ID único y el valor es el canal de comunicación (OutputStream).
|
||||
Private Connections As Map
|
||||
|
||||
' Timer #1 ("El Vigilante"): Se encarga de detectar y eliminar conexiones muertas.
|
||||
Private RemoveTimer As Timer
|
||||
|
||||
' Timer #2 ("El Informante"): Se encarga de recolectar y enviar los datos de estadísticas.
|
||||
Dim StatsTimer As Timer
|
||||
Dim const UPDATE_INTERVAL_MS As Long = 2000 ' Intervalo de envío de estadísticas: 2 segundos.
|
||||
End Sub
|
||||
|
||||
' Se ejecuta UNA SOLA VEZ cuando el servidor arranca, gracias al modo Singleton.
|
||||
Public Sub Initialize
|
||||
Log("Stats SSE Handler Initialized (Singleton Mode)")
|
||||
|
||||
' Crea el mapa de conexiones, asegurando que sea seguro para el manejo de múltiples hilos.
|
||||
Connections = Main.srvr.CreateThreadSafeMap
|
||||
|
||||
' Configura y activa el timer para la limpieza de conexiones cada 5 segundos.
|
||||
' NOTA: El EventName "RemoveTimer" debe coincidir con el nombre de la subrutina del tick.
|
||||
RemoveTimer.Initialize("RemoveTimer", 5000)
|
||||
RemoveTimer.Enabled = True
|
||||
|
||||
' Configura y activa el timer para el envío de estadísticas.
|
||||
' NOTA: El EventName "StatsTimer" debe coincidir con el nombre de la subrutina del tick.
|
||||
StatsTimer.Initialize("StatsTimer", UPDATE_INTERVAL_MS)
|
||||
StatsTimer.Enabled = True
|
||||
End Sub
|
||||
|
||||
' Es el punto de entrada principal. Atiende todas las peticiones HTTP dirigidas a este handler.
|
||||
Sub Handle(req As ServletRequest, resp As ServletResponse)
|
||||
|
||||
Log($"StatsTimerinicializado: ${StatsTimer.IsInitialized}, StatsTimer habilitado: ${StatsTimer.Enabled}"$)
|
||||
StatsTimer.Initialize("StatsTimer", 2000)
|
||||
StatsTimer.Enabled = True
|
||||
|
||||
' Filtro de seguridad: verifica si el usuario tiene una sesión autorizada.
|
||||
If req.GetSession.GetAttribute2("user_is_authorized", False) = False Then
|
||||
resp.SendRedirect("/login")
|
||||
Return
|
||||
End If
|
||||
|
||||
' Procesa únicamente las peticiones GET, que son las que usan los navegadores para iniciar una conexión SSE.
|
||||
If req.Method = "GET" Then
|
||||
' Mantiene la petición activa de forma asíncrona para poder enviar datos en el futuro.
|
||||
Dim reqJO As JavaObject = req
|
||||
reqJO.RunMethod("startAsync", Null)
|
||||
|
||||
' Registra al nuevo cliente para que empiece a recibir eventos.
|
||||
SSE.AddTarget("stats", resp)
|
||||
Else
|
||||
' Rechaza cualquier otro método HTTP (POST, PUT, etc.) con un error.
|
||||
resp.SendError(405, "Method Not Allowed")
|
||||
End If
|
||||
End Sub
|
||||
|
||||
' --- LÓGICA DE LOS TIMERS ---
|
||||
|
||||
' Evento del Timer #1 ("El Vigilante"): se dispara cada 5 segundos.
|
||||
Sub RemoveTimer_Tick
|
||||
' Log("REMOVETIMER TICK")
|
||||
' Optimización: si no hay nadie conectado, no hace nada.
|
||||
If Connections.Size = 0 Then Return
|
||||
|
||||
' Itera sobre todos los clientes para verificar si siguen activos.
|
||||
For Each key As String In Connections.Keys
|
||||
Try
|
||||
' Envía un evento "ping" silencioso. Si la conexión está viva, no pasa nada.
|
||||
SSE.SendMessage(Connections.Get(key), "ping", "", 0, "")
|
||||
Catch
|
||||
' Si el envío falla, la conexión está muerta. Se procede a la limpieza.
|
||||
Log("######################")
|
||||
Log("## Removing (timer cleanup): " & key)
|
||||
Log("######################")
|
||||
Connections.Remove(key)
|
||||
End Try
|
||||
Next
|
||||
End Sub
|
||||
|
||||
' Evento del Timer #2 ("El Informante"): se dispara cada 2 segundos.
|
||||
public Sub StatsTimer_Tick
|
||||
' Optimización: si no hay nadie conectado, no realiza el trabajo pesado.
|
||||
If Connections.Size = 0 Then Return
|
||||
|
||||
Try
|
||||
' Prepara un mapa para almacenar las estadísticas recolectadas.
|
||||
Dim allPoolStats As Map
|
||||
allPoolStats.Initialize
|
||||
|
||||
' Bloquea el acceso a los conectores para leer sus datos de forma segura.
|
||||
Main.MainConnectorsLock.RunMethod("lock", Null)
|
||||
For Each dbKey As String In Main.listaDeCP
|
||||
Dim connector As RDCConnector
|
||||
If Main.Connectors.ContainsKey(dbKey) Then
|
||||
connector = Main.Connectors.Get(dbKey)
|
||||
If connector.IsInitialized Then
|
||||
allPoolStats.Put(dbKey, connector.GetPoolStats)
|
||||
Else
|
||||
allPoolStats.Put(dbKey, CreateMap("Error": "Conector no inicializado"))
|
||||
End If
|
||||
End If
|
||||
Next
|
||||
' Libera el bloqueo para que otras partes del programa puedan usar los conectores.
|
||||
Main.MainConnectorsLock.RunMethod("unlock", Null)
|
||||
|
||||
' Convierte el mapa de estadísticas a un formato de texto JSON.
|
||||
Dim j As JSONGenerator
|
||||
j.Initialize(allPoolStats)
|
||||
Dim jsonStats As String = j.ToString
|
||||
|
||||
' Llama al "locutor" para enviar el JSON a todos los clientes conectados.
|
||||
SSE.Broadcast("stats", "stats_update", jsonStats, 0)
|
||||
|
||||
Catch
|
||||
' Captura y registra cualquier error que ocurra durante la recolección de datos.
|
||||
Log($"[SSE] Error CRÍTICO durante la adquisición de estadísticas: ${LastException.Message}"$)
|
||||
End Try
|
||||
End Sub
|
||||
|
||||
Reference in New Issue
Block a user