AppType=StandardJava Build1=Default,b4j.JRDCMulti File1=config.DB2.properties File10=stop.bat File2=config.DB3.properties File3=config.DB4.properties File4=config.properties File5=login.html File6=reiniciaProcesoBow.bat File7=reiniciaProcesoPM2.bat File8=start.bat File9=start2.bat FileGroup1=Default Group FileGroup10=Default Group FileGroup2=Default Group FileGroup3=Default Group FileGroup4=Default Group FileGroup5=Default Group FileGroup6=Default Group FileGroup7=Default Group FileGroup8=Default Group FileGroup9=Default Group Group=Default Group Library1=byteconverter Library2=javaobject Library3=jcore Library4=jrandomaccessfile Library5=jserver Library6=jshell Library7=json Library8=jsql Library9=bcrypt Module1=Cambios Module10=Manager Module11=ping Module12=RDCConnector Module13=TestHandler Module2=ChangePassHandler Module3=DBHandlerB4X Module4=DBHandlerJSON Module5=DoLoginHandler Module6=faviconHandler Module7=GlobalParameters Module8=LoginHandler Module9=LogoutHandler NumberOfFiles=10 NumberOfLibraries=9 NumberOfModules=13 Version=10.3 @EndOfDesignText@ 'Non-UI application (console / server application) #Region Project Attributes #CommandLineArgs: #MergeLibraries: True ' VERSION 5.09.14 '########################################################################################################### '###################### PULL ############################################################# 'Ctrl + click ide://run?file=%WINDIR%\System32\cmd.exe&Args=/c&Args=git&Args=pull '########################################################################################################### '###################### PUSH ############################################################# 'Ctrl + click ide://run?file=%WINDIR%\System32\WindowsPowerShell\v1.0\powershell.exe&Args=github&Args=..\..\ '########################################################################################################### '###################### PUSH TORTOISE GIT ######################################################### 'Ctrl + click ide://run?file=%WINDIR%\System32\WindowsPowerShell\v1.0\powershell.exe&Args=TortoiseGitProc&Args=/command:commit&Args=/path:"../"&Args=/closeonend:2 '########################################################################################################### #End Region 'change based on the jdbc jar file '#AdditionalJar: mysql-connector-java-5.1.27-bin '#AdditionalJar: postgresql-42.7.0 #AdditionalJar: ojdbc11 ' Librería para manejar la base de datos SQLite #AdditionalJar: sqlite-jdbc-3.7.2 Sub Process_Globals ' --- Variables globales accesibles desde cualquier parte del proyecto --- Public srvr As Server ' El objeto principal del servidor HTTP de B4J. Public const VERSION As Float = 2.23 ' La versión actual de este servidor jRDC modificado. ' Tipos personalizados para la serialización y deserialización de datos ' entre el cliente B4X (DBRequestManager) y el servidor jRDC2. Type DBCommand (Name As String, Parameters() As Object) ' Define un comando SQL. Type DBResult (Tag As Object, Columns As Map, Rows As List) ' Define la estructura de un resultado de consulta. Public listaDeCP As List ' Contiene una lista de los identificadores de bases de datos configuradas (ej. "DB1", "DB2"). Private cpFiles As List ' Una lista temporal para almacenar los nombres de archivos encontrados en el directorio. ' Mapas globales para gestionar los conectores de base de datos y los comandos SQL. Public Connectors, commandsMap As Map ' Connectors: Almacena las instancias de RDCConnector por DB. ' commandsMap: Almacena los comandos SQL cargados para cada DB. Public SQL1 As SQL ' Objeto SQL para interactuar con la base de datos de usuarios (SQLite). Private bc As BCrypt ' Objeto para realizar operaciones de hashing de contraseñas de forma segura (para autenticación). Public MainConnectorsLock As JavaObject ' Objeto de bloqueo para proteger Main.Connectors Public ActiveRequestsCountByDB As Map End Sub Sub AppStart (Args() As String) ' --- Subrutina principal que se ejecuta al iniciar la aplicación --- ' 1. Inicializa la base de datos local de usuarios (SQLite). ' Esta base de datos se crea automáticamente si no existe y contiene los usuarios para el panel de administración. InitializeSQLiteDatabase ' 2. Inicializa los mapas globales definidos en GlobalParameters.bas. ' Estos mapas se usan para monitorear el servidor y gestionar configuraciones dinámicas. GlobalParameters.mpLogs.Initialize ' Mapa para almacenar logs de actividad. GlobalParameters.mpTotalRequests.Initialize ' Mapa para contar peticiones por endpoint/DB. GlobalParameters.mpTotalConnections.Initialize ' Mapa para almacenar el estado de los pools de conexión por DB. GlobalParameters.mpBlockConnection.Initialize ' Mapa para gestionar IPs bloqueadas (si la funcionalidad está activa). GlobalParameters.ActiveRequestsCountByDB = srvr.CreateThreadSafeMap ' Aseguramos que sea thread-safe para conteo de peticiones activas por DB [___new 3.txt, conversación] ' 3. Inicializa las estructuras principales del servidor HTTP. listaDeCP.Initialize ' Inicializa la lista que contendrá los IDs de las bases de datos. srvr.Initialize("") ' Inicializa el objeto servidor HTTP. Connectors = srvr.CreateThreadSafeMap ' Crea un mapa seguro para almacenar instancias de RDCConnector (un conector por DB). commandsMap.Initialize ' Inicializa el mapa que almacenará los comandos SQL cargados de los archivos de configuración. ' <<<< NUEVA INICIALIZACIÓN: Creamos una instancia de ReentrantLock para proteger Main.Connectors >>>> MainConnectorsLock.InitializeNewInstance("java.util.concurrent.locks.ReentrantLock", Null) ' <<<< HASTA AQUÍ LA NUEVA INICIALIZACIÓN >>>> ' === 4. INICIALIZACIÓN DEL CONECTOR PARA LA BASE DE DATOS PRINCIPAL (DB1) === ' DB1 siempre usa el archivo 'config.properties' por defecto. Dim con1 As RDCConnector ' Declara una variable específica y única para el conector de DB1. con1.Initialize("DB1") ' Inicializa la instancia del conector para "DB1". Connectors.Put("DB1", con1) ' Asocia el identificador "DB1" con su instancia de RDCConnector. srvr.Port = con1.serverPort ' El puerto del servidor HTTP se obtiene del config.properties de DB1. listaDeCP.Add("DB1") ' Añade "DB1" a la lista de bases de datos gestionadas.] Log($"Main.AppStart: Conector 'DB1' inicializado exitosamente en puerto: ${srvr.Port}"$) ' === 5. DETECCIÓN E INICIALIZACIÓN DE BASES DE DATOS ADICIONALES (DB2, DB3, DB4) === ' El servidor busca archivos de configuración adicionales (ej. config.DB2.properties) ' en el mismo directorio donde se ejecuta el JAR. cpFiles = File.ListFiles("./") If cpFiles.Size > 0 Then For i = 0 To cpFiles.Size - 1 ' Procesa 'config.DB2.properties' If cpFiles.Get(i) = "config.DB2.properties" Then Dim con2 As RDCConnector ' Declara una variable específica y única para el conector de DB2. con2.Initialize("DB2") ' Inicializa la instancia del conector para "DB2". Connectors.Put("DB2", con2) ' Asocia "DB2" con su instancia de RDCConnector. listaDeCP.Add("DB2") ' Añade "DB2" a la lista de bases de datos. Log("Main.AppStart: Conector 'DB2' inicializado exitosamente.") End If ' Procesa 'config.DB3.properties' If cpFiles.Get(i) = "config.DB3.properties" Then Dim con3 As RDCConnector ' Declara una variable específica y única para el conector de DB3. con3.Initialize("DB3") ' Inicializa la instancia del conector para "DB3". Connectors.Put("DB3", con3) ' Asocia "DB3" con su instancia de RDCConnector. listaDeCP.Add("DB3") ' Añade "DB3" a la lista de bases de datos. Log("Main.AppStart: Conector 'DB3' inicializado exitosamente.") End If ' Procesa 'config.DB4.properties' If cpFiles.Get(i) = "config.DB4.properties" Then Dim con4 As RDCConnector ' Declara una variable específica y única para el conector de DB4. con4.Initialize("DB4") ' Inicializa la instancia del conector para "DB4". Connectors.Put("DB4", con4) ' Asocia "DB4" con su instancia de RDCConnector. listaDeCP.Add("DB4") ' Añade "DB4" a la lista de bases de datos. Log("Main.AppStart: Conector 'DB4' inicializado exitosamente.") End If Next Else Log("Main.AppStart: No se encontraron archivos de configuración adicionales (config.DBx.properties).") End If ' Log final de las bases de datos que el servidor está gestionando. Dim sbListaDeCP_Log As StringBuilder sbListaDeCP_Log.Initialize For Each item As String In listaDeCP sbListaDeCP_Log.Append(item).Append(", ") Next If sbListaDeCP_Log.Length > 0 Then sbListaDeCP_Log.Remove(sbListaDeCP_Log.Length - 2, sbListaDeCP_Log.Length) ' Elimina la última ", " End If Log($"Main.AppStart: Bases de datos configuradas y listas: [${sbListaDeCP_Log.ToString}]"$) ' === 6. REGISTRO DE HANDLERS HTTP PARA EL SERVIDOR === ' Asocia rutas URL específicas con clases que manejarán las peticiones correspondientes. ' El último parámetro (True) indica que el handler se ejecutará en un nuevo hilo, ' lo que es recomendable para la mayoría de los casos para evitar bloqueos. srvr.AddHandler("/ping", "ping", True) ' Endpoint simple para verificar si el servidor está activo. srvr.AddHandler("/test", "TestHandler", True) ' Endpoint para pruebas de conexión y estado del servidor. srvr.AddHandler("/login", "LoginHandler", True) ' Muestra la página HTML de login. srvr.AddHandler("/dologin", "DoLoginHandler", True) ' Procesa el intento de inicio de sesión. srvr.AddHandler("/logout", "LogoutHandler", True) ' Cierra la sesión del usuario. srvr.AddHandler("/changepass", "ChangePassHandler", True) ' Permite a los usuarios cambiar su contraseña. srvr.AddHandler("/manager", "Manager", True) ' Panel de administración del servidor (requiere autenticación). srvr.AddHandler("/DBJ", "DBHandlerJSON", False) ' Handler para clientes web (ej. JavaScript, Node.js) que usan JSON. srvr.AddHandler("/dbrquery", "DBHandlerJSON", False) ' Un alias para el handler JSON, por si se usa en clientes específicos. srvr.AddHandler("/favicon.ico", "faviconHandler", True) ' Sirve el icono de la página (favicon). srvr.AddHandler("/*", "DBHandlerB4X", False) ' Handler por defecto para clientes B4X (DBRequestManager), ' procesa peticiones dinámicamente según la URL. ' 7. Inicia el servidor HTTP. srvr.Start Log("===========================================================") Log($"-=== jRDC está funcionando en el puerto: ${srvr.Port} (versión = $1.2{VERSION}) ===-"$) Log("===========================================================") ' 8. Inicia el bucle de mensajes de B4J. Es esencial para que la aplicación ' de servidor continúe ejecutándose y procesando eventos. StartMessageLoop End Sub ' --- Subrutina para inicializar la base de datos de usuarios local (SQLite) --- ' Esta base de datos se utiliza para almacenar credenciales de usuarios que pueden ' acceder al panel de administración del servidor jRDC y los logs de queries. Sub InitializeSQLiteDatabase Dim dbFileName As String = "users.db" ' Nombre del archivo de la base de datos SQLite. ' Verifica si el archivo de la base de datos ya existe en el directorio de la aplicación. If File.Exists(File.DirApp, dbFileName) = False Then Log("Creando nueva base de datos de usuarios: " & dbFileName) ' Inicializa la conexión a la base de datos SQLite, creándola si no existe (último parámetro en True). SQL1.InitializeSQLite(File.DirApp, dbFileName, True) ' Define y ejecuta la sentencia SQL para crear la tabla 'users'. Dim createUserTable As String = "CREATE TABLE users (username TEXT PRIMARY KEY, password_hash TEXT NOT NULL)" SQL1.ExecNonQuery(createUserTable) ' >>> INICIO: Creación de la tabla query_logs con las nuevas columnas desde CERO <<< Log("Creando tabla 'query_logs' con columnas de rendimiento.") Dim createQueryLogsTable As String = "CREATE TABLE query_logs (id INTEGER PRIMARY KEY AUTOINCREMENT, query_name TEXT, duration_ms INTEGER, timestamp INTEGER, db_key TEXT, client_ip TEXT, busy_connections INTEGER, handler_active_requests INTEGER)" SQL1.ExecNonQuery(createQueryLogsTable) ' >>> FIN: Creación de la tabla query_logs <<< ' Crea un usuario por defecto para facilitar el primer acceso al panel de administración. Dim defaultUser As String = "admin" Dim defaultPass As String = "12345" ' Genera un hash seguro de la contraseña usando BCrypt, lo cual es crucial para la seguridad. Dim hashedPass As String = bc.hashpw(defaultPass, bc.gensalt) ' Inserta el usuario por defecto en la tabla 'users'. SQL1.ExecNonQuery2("INSERT INTO users (username, password_hash) VALUES (?, ?)", Array As Object(defaultUser, hashedPass)) Log($"Usuario por defecto creado -> user: ${defaultUser}, pass: ${defaultPass}"$) Else ' Si el archivo de la base de datos ya existe, simplemente se abre. SQL1.InitializeSQLite(File.DirApp, dbFileName, True) Log("Base de datos de usuarios cargada.") ' >>> INICIO: Lógica de migración (ALTER TABLE) si la DB ya existía <<< Log("Verificando y migrando tabla 'query_logs' si es necesario.") ' Primero, verificar si la tabla query_logs existe. If SQL1.ExecQuerySingleResult("SELECT name FROM sqlite_master WHERE type='table' AND name='query_logs'") = Null Then Log("Tabla 'query_logs' no encontrada, creándola con columnas de rendimiento.") Dim createQueryLogsTable As String = "CREATE TABLE query_logs (id INTEGER PRIMARY KEY AUTOINCREMENT, query_name TEXT, duration_ms INTEGER, timestamp INTEGER, db_key TEXT, client_ip TEXT, busy_connections INTEGER, handler_active_requests INTEGER)" SQL1.ExecNonQuery(createQueryLogsTable) Else ' Si la tabla query_logs ya existe, entonces verificamos y añadimos las columnas faltantes. Dim columnExists As Boolean Dim rs As ResultSet ' --- VERIFICAR Y AÑADIR busy_connections --- columnExists = False ' Ejecutamos PRAGMA sin WHERE y lo filtramos en código. rs = SQL1.ExecQuery("PRAGMA table_info(query_logs)") Do While rs.NextRow If rs.GetString("name").EqualsIgnoreCase("busy_connections") Then columnExists = True Exit ' La columna ya existe, salimos del bucle. End If Loop rs.Close ' ¡Importante cerrar el ResultSet! If columnExists = False Then Log("Añadiendo columna 'busy_connections' a query_logs.") SQL1.ExecNonQuery("ALTER TABLE query_logs ADD COLUMN busy_connections INTEGER DEFAULT 0") End If ' --- VERIFICAR Y AÑADIR handler_active_requests --- columnExists = False rs = SQL1.ExecQuery("PRAGMA table_info(query_logs)") ' Ejecutamos PRAGMA nuevamente para esta columna. Do While rs.NextRow If rs.GetString("name").EqualsIgnoreCase("handler_active_requests") Then columnExists = True Exit ' La columna ya existe, salimos del bucle. End If Loop rs.Close ' ¡Importante cerrar el ResultSet! If columnExists = False Then Log("Añadiendo columna 'handler_active_requests' a query_logs.") SQL1.ExecNonQuery("ALTER TABLE query_logs ADD COLUMN handler_active_requests INTEGER DEFAULT 0") End If End If ' >>> FIN: Lógica de migración (ALTER TABLE) <<< End If End Sub ' Subrutina para registrar las métricas de rendimiento de las queries Public Sub LogQueryPerformance(QueryName As String, DurationMs As Long, DbKey As String, ClientIp As String, HandlerActiveRequests As Int, PoolBusyConnections As Int) Try ' El valor PoolBusyConnections ya se recibe directamente del handler. ' Removemos la lógica anterior de obtenerlo del conector. ' Dim connector As RDCConnector = Main.Connectors.Get(DbKey).As(RDCConnector) ' Dim poolBusyConnections As Int = 0 ' If connector.IsInitialized Then ' Dim poolStats As Map = connector.GetPoolStats ' If poolStats.ContainsKey("BusyConnections") Then ' poolBusyConnections = poolStats.Get("BusyConnections") ' End If ' Else ' Log($"ADVERTENCIA: Conector RDC para ${DbKey} no inicializado al intentar loguear rendimiento."$) ' End If ' Insertamos los datos en la tabla query_logs de SQLite SQL1.ExecNonQuery2("INSERT INTO query_logs (query_name, duration_ms, timestamp, db_key, client_ip, busy_connections, handler_active_requests) VALUES (?, ?, ?, ?, ?, ?, ?)", _ Array As Object(QueryName, DurationMs, DateTime.Now, DbKey, ClientIp, PoolBusyConnections, HandlerActiveRequests)) Catch Log("Error al guardar log de query en SQLite (Main.LogQueryPerformance): " & LastException.Message) End Try End Sub