- VERSION 5.09.16

- feat: Implementa tolerancia de parámetros configurable y mejora estabilidad general del servidor.
- La tolerancia de parametros permite que si un query requiere 3 parametros y se mandan 4, NO mande un error,
	solo manda a la base de datos los parametros correctos y tira los extras, y guarda una "ADVERTENCIA" en el Log de errores.

- Este commit introduce la funcionalidad de `parameterTolerance` configurable y aborda varias mejoras críticas para la estabilidad y eficiencia del jRDC2-Multi.

- Principales cambios y beneficios:
- **Tolerancia de Parámetros**: Añade la propiedad `parameterTolerance` en `config.properties` para controlar el manejo de parámetros de más. Cuando está habilitada, recorta los parámetros excesivos; si está deshabilitada (modo estricto, por defecto), genera un error, aumentando la robustez de la validación.
- **Inicialización Multi-DB Confiable**: Corrige la lógica de inicialización en `Main.AppStart` para `RDCConnector` de DB3 y DB4, asegurando que cada base de datos tenga su propio *pool* de conexiones correctamente configurado.
- **Optimización de Ejecución SQL**: Elimina llamadas duplicadas a `ExecQuery2` y `ExecNonQuery2` en `DBHandlerB4X.bas`, garantizando que solo los parámetros validados se utilicen y evitando ejecuciones redundantes en la base de datos.
- **Refactorización y Limpieza**: Se eliminó la declaración duplicada de `ActiveRequestsCountByDB` en `Main.bas` y la subrutina `Handle0` obsoleta en `Manager.bas`, mejorando la claridad y mantenibilidad del código.
This commit is contained in:
2025-09-18 22:30:22 -06:00
parent 51c829b876
commit 3b352bb105
14 changed files with 537 additions and 466 deletions

View File

@@ -10,174 +10,154 @@ Version=10.3
Sub Process_Globals Sub Process_Globals
'- VERSION X.XX.XX (cambios a implementar) ' - VERSION X.XX.XX (cambios a implementar)
'- Agregar que se puedan usar cualquier cantidad de archivos config.properties ' - Agregar que se puedan usar cualquier cantidad de archivos config.properties
'- Agregar que se pueda recargar solo un archivo de configuracion o todos a la vez. ' - Agregar que se pueda recargar solo un archivo de configuracion o todos a la vez.
'- Agregar que el "Test" del manager revise (con el query de Jorge) cuantas conexiones hay actualmente activas, ' - Agregar que el "Test" del manager revise (con el query de Jorge) cuantas conexiones hay actualmente activas,
'- o si no en el test, un nuevo handler, talvez "Conexiones". ' - o si no en el test, un nuevo handler, talvez "Conexiones".
'- Agregar una forma de probar con carga el servidor ' - Agregar una forma de probar con carga el servidor
'- Agregar la opcion de "Queries lentos" '
' - VERSION 5.09.16
'- VERSION 5.09.15 ' feat: Implementa tolerancia de parámetros configurable y mejora estabilidad general del servidor.
' - feat: Consolidación de mejoras en monitoreo, gestión de pools y hot-swap de configuración. ' La tolerancia de parametros permite que si un query requiere 3 parametros y se mandan 4, NO mande un error,
' ' solo manda a la base de datos los parametros correctos y tira los extras, y guarda una "ADVERTENCIA" en el Log de errores.
' Este commit integra y consolida todas las mejoras recientes en la robustez, '
' monitoreo de rendimiento y flexibilidad del servidor jRDC2-Multi. ' Este commit introduce la funcionalidad de `parameterTolerance` configurable y aborda varias mejoras críticas para la estabilidad y eficiencia del jRDC2-Multi.
' '
' **Módulos Afectados:** Main.bas, RDCConnector.bas, DBHandlerB4X.bas, DBHandlerJSON.bas, Manager.bas, GlobalParameters.bas. ' Principales cambios y beneficios:
' ' - **Tolerancia de Parámetros**: Añade la propiedad `parameterTolerance` en `config.properties` para controlar el manejo de parámetros de más. Cuando está habilitada, recorta los parámetros excesivos; si está deshabilitada (modo estricto, por defecto), genera un error, aumentando la robustez de la validación.
' **Cambios Clave Implementados:** ' - **Inicialización Multi-DB Confiable**: Corrige la lógica de inicialización en `Main.AppStart` para `RDCConnector` de DB3 y DB4, asegurando que cada base de datos tenga su propio *pool* de conexiones correctamente configurado.
' ' - **Optimización de Ejecución SQL**: Elimina llamadas duplicadas a `ExecQuery2` y `ExecNonQuery2` en `DBHandlerB4X.bas`, garantizando que solo los parámetros validados se utilicen y evitando ejecuciones redundantes en la base de datos.
' 1. **Monitoreo Preciso y Robusto del Pool de Conexiones (C3P0) y Peticiones Activas:** ' - **Refactorización y Limpieza**: Se eliminó la declaración duplicada de `ActiveRequestsCountByDB` en `Main.bas` y la subrutina `Handle0` obsoleta en `Manager.bas`, mejorando la claridad y mantenibilidad del código.
' * Se corrigieron las métricas de `BusyConnections` y `TotalConnections` en `query_logs` y el panel `Manager` para reflejar el estado real del pool de C3P0. Esto se logró capturando `BusyConnections` directamente del pool *inmediatamente después* de que un handler adquiere una conexión en `DBHandlerJSON.bas` y `DBHandlerB4X.bas` [1, 2]. '
' * El contador de peticiones activas por base de datos (`GlobalParameters.ActiveRequestsCountByDB`) ahora se incrementa y decrementa de forma consistente. Se asegura que la `dbKey` se resuelva *antes* de la operación de conteo y se aplica una coerción explícita a `Int` (`.As(Int)`) para todas las operaciones, resolviendo inconsistencias de tipo [3, 4]. ' - VERSION 5.09.15
' * La lógica de decremento en la subrutina `Private Sub CleanupAndLog` (presente en `DBHandlerJSON.bas` y `DBHandlerB4X.bas`) se hizo más robusta, verificando que el contador sea mayor que cero antes de decrementar para evitar valores negativos [5]. '
' * Beneficio: Monitoreo preciso y fiable en tiempo real del uso del pool de conexiones y la carga de peticiones activas, mejorando el diagnóstico y la estabilidad del servidor [5, 6]. ' 1. **Nuevas Funcionalidades en el Panel de Administración (Manager):**
' ' * Se añadió el comando `slowqueries` al `Manager` para permitir la visualización de las 20 consultas más lentas registradas en la tabla `query_logs` de SQLite [22].
' 2. **Implementación Completa de "Hot-Swap" para Recarga de Configuraciones de DB:** ' * Se mejoró el comando `totalcon` en `Manager.bas` para mostrar estadísticas detalladas de *todos* los pools de conexión C3P0 configurados, obteniendo métricas en tiempo real (TotalConnections, BusyConnections, IdleConnections, etc.) de cada `RDCConnector` [2, 22].
' * La lógica del comando `reload` en `Manager.bas` fue completamente rediseñada para permitir la recarga dinámica de configuraciones de bases de datos sin reiniciar el servidor [7, 8]. ' * Beneficio: Mayor visibilidad y control proactivo sobre el rendimiento y el uso de recursos del servidor desde la interfaz de administración.
' * Se utiliza una instancia de `java.util.concurrent.locks.ReentrantLock` (`MainConnectorsLock`) declarada e inicializada en `Main.bas` para proteger de forma atómica la lectura y reemplazo del mapa `Main.Connectors`, que es compartido por múltiples hilos [9, 10]. '
' * Se añadió un método `Public Sub Close` en `RDCConnector.bas` [8], que utiliza `JavaObject` para invocar el método `close()` del `ConnectionPool` (C3P0) subyacente. Esto permite un cierre ordenado de los pools de conexión antiguos, liberando sus recursos de la base de datos de manera limpia durante el "hot-swap" [11, 12]. ' 2. **Optimización de la Gestión de Logs (`query_logs`):**
' * La implementación en `Manager.bas` incluye un manejo seguro del bloqueo sin `Finally` (usando una bandera booleana `lockAcquired`) y lógica de validación para abortar la recarga si ocurren errores críticos, manteniendo los conectores antiguos activos para evitar interrupciones del servicio [12, 13]. ' * Se implementó un `Public timerLogs As Timer` en `Main.bas` [conversación], que se inicializa en `AppStart` y ejecuta periódicamente (cada 10 minutos) la subrutina `borraArribaDe15000Logs`.
' * Beneficio: Capacidad crítica para actualizar configuraciones de conexión a bases de datos en caliente, mejorando la disponibilidad, simplificando el mantenimiento y previniendo fugas de recursos [14]. ' * La subrutina `borraArribaDe15000Logs` recorta la tabla `query_logs` en `users.db` para mantener solo los 15,000 registros más recientes, y luego realiza un `vacuum` para optimizar el espacio en disco utilizado por la base de datos SQLite [conversación].
' ' * Beneficio: Prevención del crecimiento excesivo de la base de datos de logs de rendimiento, manteniendo un historial manejable y optimizando el uso del almacenamiento a largo plazo.
' 3. **Manejo Mejorado de Peticiones POST con JSON en el Cuerpo:** '
' * `DBHandlerJSON.bas` fue modificado para detectar y procesar correctamente las peticiones POST que envían el payload JSON directamente en el cuerpo (con `Content-Type: application/json`), en lugar de solo en el parámetro `j` de la URL [14, 15]. ' - VERSION 5.09.14 (Ahora consolidado en 5.09.15)
' * Se asegura la lectura completa del `InputStream` y su cierre explícito para liberar recursos [15, 16]. ' -feat: Implementación robusta de monitoreo de pool de conexiones y peticiones activas
' * Beneficio: Compatibilidad con estándares API web modernos, mejorando la robustez y la adherencia a los estándares sin comprometer la retrocompatibilidad con el "Método Legacy" (GET con parámetro `j`) [16]. ' -Este commit resuelve problemas críticos en el monitoreo del pool de conexiones (C3P0) y el conteo de peticiones activas por base de datos, mejorando significativamente la visibilidad y fiabilidad del rendimiento del servidor jRDC2-Multi.
' ' -Problemas Identificados y Resueltos:
' 4. **Robustecimiento de la Inicialización del Pool de Conexiones (C3P0):** ' -1. **Métricas de `BusyConnections` y `TotalConnections` inconsistentes o siempre en `0` en el `Manager` y `query_logs`:**
' * La subrutina `Initialize` en `RDCConnector.bas` fue reordenada y fortalecida. Ahora asegura que la configuración de C3P0 se cargue completamente en la variable de clase `config` y que todas las propiedades del pool se apliquen mediante `jo.RunMethod` *inmediatamente después* de `pool.Initialize` y *antes* de que el pool intente adquirir conexiones [17, 18]. ' * **Problema**: Anteriormente, la métrica `busy_connections` en `query_logs` a menudo reportaba `0` o no reflejaba el estado real. De manera similar, el panel de `Manager?command=totalcon` consistentemente mostraba `BusyConnections: 0` y `TotalConnections` estancadas en `InitialPoolSize`, a pesar de que Oracle sí reportaba conexiones activas. Esto generaba confusión sobre el uso real y la expansión del pool.
' * Se añadieron las líneas `jo.RunMethod("setAcquireRetryAttempts", Array As Object(1))` y `jo.RunMethod("setBreakAfterAcquireFailure", Array As Object(True))` en `RDCConnector.bas`. Estas son cruciales para forzar a C3P0 a lanzar una `SQLException` explícita si falla al crear las conexiones iniciales, en lugar de fallar silenciosamente [18, 19]. ' * **Solución**: Se modificó la lógica en los *handlers* (`DBHandlerJSON.bas` y `DBHandlerB4X.bas`) para capturar la métrica `BusyConnections` directamente del pool de C3P0 **inmediatamente después de que el *handler* adquiere una conexión** (`con = Connector.GetConnection(finalDbKey)`). Este valor se pasa explícitamente a la subrutina `Main.LogQueryPerformance` para su registro en `query_logs` y para ser consumido por `Manager.bas` a través de `RDCConnector.GetPoolStats`. Esto garantiza que el valor registrado y reportado refleje con precisión el número de conexiones activas en el instante de su adquisición. Pruebas exhaustivas confirmaron que C3P0 sí reporta conexiones ocupadas y sí expande `TotalConnections` hasta `MaxPoolSize` cuando la demanda lo exige.
' * Se implementó una "activación forzada" del pool (`Dim tempCon As SQL = pool.GetConnection` seguido de `tempCon.Close`) dentro de un bloque `Try...Catch` en `RDCConnector.Initialize`. Esto obliga al pool a establecer las conexiones iniciales (`InitialPoolSize`) con la configuración ya aplicada, permitiendo la captura de errores reales si la conexión a la base de datos falla [20]. ' -2. **Contador `handler_active_requests` no decrementaba correctamente:**
' * Beneficio: Diagnóstico temprano y preciso de problemas de conexión a la base de datos, evitando situaciones donde `TotalConnections` mostraba `0` o la `jdbcUrl` aparecía truncada [19, 21]. ' * **Problema**: El contador de peticiones activas por base de datos (`GlobalParameters.ActiveRequestsCountByDB`) no mostraba un decremento consistente, resultando en un conteo que solo aumentaba o mostraba valores erráticos en los logs.
' ' * **Solución**:
' 5. **Nuevas Funcionalidades en el Panel de Administración (Manager):** ' * Se aseguró la declaración `Public ActiveRequestsCountByDB As Map` en `GlobalParameters.bas`.
' * Se añadió el comando `slowqueries` al `Manager` para permitir la visualización de las 20 consultas más lentas registradas en la tabla `query_logs` de SQLite [22]. ' * Se garantizó su inicialización como un `srvr.CreateThreadSafeMap` en `Main.AppStart` para un manejo concurrente seguro de los contadores.
' * Se mejoró el comando `totalcon` en `Manager.bas` para mostrar estadísticas detalladas de *todos* los pools de conexión C3P0 configurados, obteniendo métricas en tiempo real (TotalConnections, BusyConnections, IdleConnections, etc.) de cada `RDCConnector` [2, 22]. ' * En `DBHandlerJSON.bas`, la `dbKey` (obtenida del parámetro `dbx` del JSON) ahora se resuelve *antes* de incrementar el contador, asegurando que el incremento y el decremento se apliquen siempre a la misma clave de base de datos correcta.
' * Beneficio: Mayor visibilidad y control proactivo sobre el rendimiento y el uso de recursos del servidor desde la interfaz de administración. ' * Se implementó una coerción explícita a `Int` (`.As(Int)`) para todas las operaciones de lectura y escritura (`GetDefault`, `Put`) en `GlobalParameters.ActiveRequestsCountByDB`, resolviendo problemas de tipo que causaban inconsistencias y el fallo en el decremento.
' ' * La lógica de decremento en `Private Sub CleanupAndLog` (presente en ambos *handlers*) se hizo más robusta, verificando que el contador sea mayor que cero antes de decrementar para evitar valores negativos.
' 6. **Optimización de la Gestión de Logs (`query_logs`):** ' -Beneficios de estos Cambios:
' * Se implementó un `Public timerLogs As Timer` en `Main.bas` [conversación], que se inicializa en `AppStart` y ejecuta periódicamente (cada 10 minutos) la subrutina `borraArribaDe15000Logs`. ' * **Monitoreo Preciso y Fiable**: Las métricas `busy_connections` y `handler_active_requests` en `query_logs` y el panel `Manager` ahora son totalmente fiables, proporcionando una visión clara y en tiempo real del uso del pool de conexiones y la carga de peticiones activas por base de datos.
' * La subrutina `borraArribaDe15000Logs` recorta la tabla `query_logs` en `users.db` para mantener solo los 15,000 registros más recientes, y luego realiza un `vacuum` para optimizar el espacio en disco utilizado por la base de datos SQLite [conversación]. ' * **Diagnóstico Mejorado**: La visibilidad interna del estado del pool de C3P0 durante las pruebas confirma que la configuración de `RDCConnector` es correcta y que el pool se expande y contrae según lo esperado por la demanda.
' * Beneficio: Prevención del crecimiento excesivo de la base de datos de logs de rendimiento, manteniendo un historial manejable y optimizando el uso del almacenamiento a largo plazo. ' * **Robustez del Código**: La gestión de contadores de peticiones activas es ahora consistente, thread-safe y a prueba de fallos de tipo, mejorando la estabilidad general del servidor bajo carga.
'
'- VERSION 5.09.14 (Ahora consolidado en 5.09.15) ' - VERSION 5.09.13.3 (Ahora consolidado en 5.09.15)
' -feat: Implementación robusta de monitoreo de pool de conexiones y peticiones activas ' - Implementación de "Hot-Swap" para recarga de configuraciones de DB sin reiniciar el servidor.
' -Este commit resuelve problemas críticos en el monitoreo del pool de conexiones (C3P0) y el conteo de peticiones activas por base de datos, mejorando significativamente la visibilidad y fiabilidad del rendimiento del servidor jRDC2-Multi. ' - Migración a ReentrantLock para sincronización debido a incompatibilidad con 'Sync'.
' -Problemas Identificados y Resueltos: ' - **Problemas Resueltos:**
' -1. **Métricas de `BusyConnections` y `TotalConnections` inconsistentes o siempre en `0` en el `Manager` y `query_logs`:** ' - 1. **Falta de "Hot-Swap" en `reload`:** El comando `reload` en `Manager.bas` no permitía la recarga dinámica de las configuraciones de la base de datos (config.properties) sin necesidad de reiniciar el servidor. La implementación anterior simplemente re-inicializaba las instancias existentes de `RDCConnector` in-situ, sin liberar los recursos de los pools de conexión anteriores, lo cual era ineficiente y propenso a errores.
' * **Problema**: Anteriormente, la métrica `busy_connections` en `query_logs` a menudo reportaba `0` o no reflejaba el estado real. De manera similar, el panel de `Manager?command=totalcon` consistentemente mostraba `BusyConnections: 0` y `TotalConnections` estancadas en `InitialPoolSize`, a pesar de que Oracle sí reportaba conexiones activas. Esto generaba confusión sobre el uso real y la expansión del pool. ' - 2. **Ausencia de un mecanismo de cierre de pools:** No existía un método `Close` en `RDCConnector.bas` que permitiera cerrar ordenadamente los `ConnectionPool` (C3P0) y liberar las conexiones a la base de datos, lo que era crítico para un "hot-swap" limpio .
' * **Solución**: Se modificó la lógica en los *handlers* (`DBHandlerJSON.bas` y `DBHandlerB4X.bas`) para capturar la métrica `BusyConnections` directamente del pool de C3P0 **inmediatamente después de que el *handler* adquiere una conexión** (`con = Connector.GetConnection(finalDbKey)`). Este valor se pasa explícitamente a la subrutina `Main.LogQueryPerformance` para su registro en `query_logs` y para ser consumido por `Manager.bas` a través de `RDCConnector.GetPoolStats`. Esto garantiza que el valor registrado y reportado refleje con precisión el número de conexiones activas en el instante de su adquisición. Pruebas exhaustivas confirmaron que C3P0 sí reporta conexiones ocupadas y sí expande `TotalConnections` hasta `MaxPoolSize` cuando la demanda lo exige. ' - 3. **Incompatibilidad con `Sync`:** La palabra clave `Sync` de B4X no era reconocida por el entorno de desarrollo del usuario, impidiendo su uso para la sincronización de hilos necesaria en el "hot-swap".
' -2. **Contador `handler_active_requests` no decrementaba correctamente:** ' - 4. **Ausencia de `Finally` en B4X:** La palabra clave `Finally` (común en otros lenguajes como Java para asegurar la liberación de recursos) no está disponible directamente en B4X, lo cual planteó un desafío para garantizar la liberación del `ReentrantLock` de forma segura.
' * **Problema**: El contador de peticiones activas por base de datos (`GlobalParameters.ActiveRequestsCountByDB`) no mostraba un decremento consistente, resultando en un conteo que solo aumentaba o mostraba valores erráticos en los logs. ' - **Cambios Implementados:**
' * **Solución**: ' - **En `Main.bas`:**
' * Se aseguró la declaracn `Public ActiveRequestsCountByDB As Map` en `GlobalParameters.bas`. ' - * **Declaración de `MainConnectorsLock`:** Se añadió `Public MainConnectorsLock As JavaObject` en `Sub Process_Globals` para declarar una instancia de `java.util.concurrent.locks.ReentrantLock`, que servirá como objeto de bloqueo global para proteger el mapa `Main.Connectors`.
' * Se garantizó su inicialización como un `srvr.CreateThreadSafeMap` en `Main.AppStart` para un manejo concurrente seguro de los contadores. ' - * **Inicialización de `MainConnectorsLock`:** Se inicializó `MainConnectorsLock.InitializeNewInstance("java.util.concurrent.locks.ReentrantLock", Null)` en `Sub AppStart`, asegurando que el objeto de bloqueo esté listo al inicio del servidor.
' * En `DBHandlerJSON.bas`, la `dbKey` (obtenida del parámetro `dbx` del JSON) ahora se resuelve *antes* de incrementar el contador, asegurando que el incremento y el decremento se apliquen siempre a la misma clave de base de datos correcta. ' - **En `RDCConnector.bas`:**
' * Se implementó una coerción explícita a `Int` (`.As(Int)`) para todas las operaciones de lectura y escritura (`GetDefault`, `Put`) en `GlobalParameters.ActiveRequestsCountByDB`, resolviendo problemas de tipo que causaban inconsistencias y el fallo en el decremento. ' - * **Método `Public Sub Close`:** Se añadió esta subrutina al final del módulo. Utiliza `JavaObject` para invocar `joPool.RunMethod("close", Null)` sobre la instancia subyacente de C3P0, permitiendo un cierre ordenado y la liberación de todas las conexiones del pool .
' * La lógica de decremento en `Private Sub CleanupAndLog` (presente en ambos *handlers*) se hizo más robusta, verificando que el contador sea mayor que cero antes de decrementar para evitar valores negativos. ' - **En `Manager.bas`:**
' -Beneficios de estos Cambios: ' - * **Reemplazo completo de la lógica `If Command = "reload" Then`:**
' * **Monitoreo Preciso y Fiable**: Las métricas `busy_connections` y `handler_active_requests` en `query_logs` y el panel `Manager` ahora son totalmente fiables, proporcionando una visión clara y en tiempo real del uso del pool de conexiones y la carga de peticiones activas por base de datos. ' - * **Creación de `newConnectors`:** Se crea un mapa temporal (`Dim newConnectors As Map`) para inicializar las **nuevas instancias** de `RDCConnector` con la configuración fresca de los archivos `.properties` .
' * **Diagnóstico Mejorado**: La visibilidad interna del estado del pool de C3P0 durante las pruebas confirma que la configuración de `RDCConnector` es correcta y que el pool se expande y contrae según lo esperado por la demanda. ' - * **Preservación de `oldConnectors`:** Se almacena una referencia al mapa `Main.Connectors` actual en un nuevo mapa (`Dim oldConnectors As Map`) para tener acceso a los conectores antiguos que necesitan ser cerrados .
' * **Robustez del Código**: La gestión de contadores de peticiones activas es ahora consistente, thread-safe y a prueba de fallos de tipo, mejorando la estabilidad general del servidor bajo carga. ' - * **Sincronización con `ReentrantLock`:** Para proteger la manipulación del mapa `Main.Connectors` (que es compartido por múltiples hilos), se utilizan `Main.MainConnectorsLock.RunMethod("lock", Null)` y `Main.MainConnectorsLock.RunMethod("unlock", Null)`. Esto asegura que el reemplazo del mapa sea atómico, es decir, que solo un hilo pueda acceder a `Main.Connectors` durante la lectura y la escritura .
' - * **Manejo de Bloqueo Seguro sin `Finally`:** Dado que `Finally` no está disponible en B4X, se implementó un patrón con una bandera booleana (`lockAcquired`) dentro de un bloque `Try...Catch` para garantizar que `unlock()` siempre se ejecute si `lock()` fue exitoso, previniendo interbloqueos .
'- VERSION 5.09.13.3 (Ahora consolidado en 5.09.15) ' - * **Cierre explícito de `oldConnectors`:** Después de que los `newConnectors` reemplazan a los `oldConnectors`, se itera sobre el mapa `oldConnectors` y se llama a `oldRDC.Close` para cada conector, liberando sus recursos de base de datos de manera limpia .
'- Implementación de "Hot-Swap" para recarga de configuraciones de DB sin reiniciar el servidor. ' - * **Validación de inicialización y control de errores:** Se agregó lógica para verificar el éxito de la inicialización de los nuevos conectores y abortar el "hot-swap" si ocurre un error crítico, manteniendo los conectores antiguos activos para evitar una interrupción del servicio .
'- Migración a ReentrantLock para sincronización debido a incompatibilidad con 'Sync'. ' - * **Registro detallado:** Se mejoró la salida del log HTML del `Manager` para mostrar el proceso de recarga, las estadísticas de los pools recién inicializados y el cierre de los antiguos, incluyendo JSON detallado de las métricas de C3P0 .
'- **Problemas Resueltos:** ' - • Beneficio: Estos cambios dotan al servidor jRDC2-Multi de una capacidad crítica para actualizar sus configuraciones de conexión a bases de datos en caliente, sin necesidad de reiniciar el servicio. Esto mejora la disponibilidad, simplifica el mantenimiento y previene fugas de recursos al asegurar el cierre ordenado de los pools de conexión antiguos.
'- 1. **Falta de "Hot-Swap" en `reload`:** El comando `reload` en `Manager.bas` no permitía la recarga dinámica de las configuraciones de la base de datos (config.properties) sin necesidad de reiniciar el servidor. La implementación anterior simplemente re-inicializaba las instancias existentes de `RDCConnector` in-situ, sin liberar los recursos de los pools de conexión anteriores, lo cual era ineficiente y propenso a errores. '
'- 2. **Ausencia de un mecanismo de cierre de pools:** No existía un método `Close` en `RDCConnector.bas` que permitiera cerrar ordenadamente los `ConnectionPool` (C3P0) y liberar las conexiones a la base de datos, lo que era crítico para un "hot-swap" limpio . ' - VERSION 5.09.13.2 (Ahora consolidado en 5.09.15)
'- 3. **Incompatibilidad con `Sync`:** La palabra clave `Sync` de B4X no era reconocida por el entorno de desarrollo del usuario, impidiendo su uso para la sincronización de hilos necesaria en el "hot-swap". ' - Módulo: DBHandlerJSON.bas
'- 4. **Ausencia de `Finally` en B4X:** La palabra clave `Finally` (común en otros lenguajes como Java para asegurar la liberación de recursos) no está disponible directamente en B4X, lo cual planteó un desafío para garantizar la liberación del `ReentrantLock` de forma segura. ' - Descripción de Cambios: Manejo de Peticiones POST con Content-Type: application/json
'- **Cambios Implementados:** ' - • Problema Identificado: La implementación anterior de DBHandlerJSON procesaba las peticiones POST esperando que el payload JSON se encontrara en el parámetro j de la URL (req.GetParameter("j")). Esto impedía la correcta lectura de peticiones POST que utilizaban Content-Type: application/json, donde el JSON se envía directamente en el cuerpo de la petición (InputStream). Como resultado, los clientes recibían un error indicando la ausencia del parámetro j .
'- **En `Main.bas`:** ' - • Solución Implementada:
'- * **Declaración de `MainConnectorsLock`:** Se añadió `Public MainConnectorsLock As JavaObject` en `Sub Process_Globals` para declarar una instancia de `java.util.concurrent.locks.ReentrantLock`, que servirá como objeto de bloqueo global para proteger el mapa `Main.Connectors`. ' - 1. Se modificó la lógica en el método Handle para detectar explícitamente las peticiones POST con Content-Type igual a application/json.
'- * **Inicialización de `MainConnectorsLock`:** Se inicializó `MainConnectorsLock.InitializeNewInstance("java.util.concurrent.locks.ReentrantLock", Null)` en `Sub AppStart`, asegurando que el objeto de bloqueo esté listo al inicio del servidor. ' - 2. En estos casos, el payload JSON ahora se lee directamente del InputStream de la petición (req.InputStream).
'- **En `RDCConnector.bas`:** ' - 3. Se utilizó Bit.InputStreamToBytes(Is0) para leer el cuerpo completo de la petición a un Array de bytes, seguido de BytesToString para convertirlo en la cadena JSON.
'- * **Método `Public Sub Close`:** Se añadió esta subrutina al final del módulo. Utiliza `JavaObject` para invocar `joPool.RunMethod("close", Null)` sobre la instancia subyacente de C3P0, permitiendo un cierre ordenado y la liberación de todas las conexiones del pool . ' - 4. Se añadió el cierre explícito del InputStream (Is0.Close) para asegurar la liberación de recursos .
'- **En `Manager.bas`:** ' - 5. Se corrigió el nombre de la variable Is a Is0 para evitar un conflicto con la palabra reservada Is de B4X .
'- * **Reemplazo completo de la lógica `If Command = "reload" Then`:** ' - 6. Se actualizó el mensaje de error para aclarar que el JSON puede faltar tanto en el parámetro j como en el cuerpo de la petición.
'- * **Creación de `newConnectors`:** Se crea un mapa temporal (`Dim newConnectors As Map`) para inicializar las **nuevas instancias** de `RDCConnector` con la configuración fresca de los archivos `.properties` . ' - • Beneficio: Esta corrección asegura que el DBHandlerJSON sea compatible con el "Método Recomendado" de POST con application/json, mejorando la robustez y la adherencia a los estándares de las APIs web, Sin comprometer la retrocompatibilidad con el "Método Legacy" (GET con parámetro j).
'- * **Preservación de `oldConnectors`:** Se almacena una referencia al mapa `Main.Connectors` actual en un nuevo mapa (`Dim oldConnectors As Map`) para tener acceso a los conectores antiguos que necesitan ser cerrados . '
'- * **Sincronización con `ReentrantLock`:** Para proteger la manipulación del mapa `Main.Connectors` (que es compartido por múltiples hilos), se utilizan `Main.MainConnectorsLock.RunMethod("lock", Null)` y `Main.MainConnectorsLock.RunMethod("unlock", Null)`. Esto asegura que el reemplazo del mapa sea atómico, es decir, que solo un hilo pueda acceder a `Main.Connectors` durante la lectura y la escritura . ' - VERSION 5.09.13 (Ahora consolidado en 5.09.15)
'- * **Manejo de Bloqueo Seguro sin `Finally`:** Dado que `Finally` no está disponible en B4X, se implementó un patrón con una bandera booleana (`lockAcquired`) dentro de un bloque `Try...Catch` para garantizar que `unlock()` siempre se ejecute si `lock()` fue exitoso, previniendo interbloqueos . ' feat: Mejora la inicialización del pool de conexiones y el soporte multi-DB.
'- * **Cierre explícito de `oldConnectors`:** Después de que los `newConnectors` reemplazan a los `oldConnectors`, se itera sobre el mapa `oldConnectors` y se llama a `oldRDC.Close` para cada conector, liberando sus recursos de base de datos de manera limpia . '
'- * **Validación de inicialización y control de errores:** Se agregó lógica para verificar el éxito de la inicialización de los nuevos conectores y abortar el "hot-swap" si ocurre un error crítico, manteniendo los conectores antiguos activos para evitar una interrupción del servicio . ' **Problemas Resueltos:**
'- * **Registro detallado:** Se mejoró la salida del log HTML del `Manager` para mostrar el proceso de recarga, las estadísticas de los pools recién inicializados y el cierre de los antiguos, incluyendo JSON detallado de las métricas de C3P0 . '
'- • Beneficio: Estos cambios dotan al servidor jRDC2-Multi de una capacidad crítica para actualizar sus configuraciones de conexión a bases de datos en caliente, sin necesidad de reiniciar el servicio. Esto mejora la disponibilidad, simplifica el mantenimiento y previene fugas de recursos al asegurar el cierre ordenado de los pools de conexión antiguos. ' 1. **Inicialización de `TotalConnections: 0` en todos los pools:** Anteriormente, el Log mostraba 0 conexiones inicializadas para todas las bases de datos (DB1, DB2, DB3, DB4) durante `AppStart`, a pesar de que los `handlers` de `DBHandlerB4X` y `DBHandlerJSON` podían conectarse más tarde bajo demanda. Esto indicaba un fallo silencioso en la creación de conexiones iniciales por parte de C3P0.
' 2. **Configuración inconsistente de C3P0:** Parámetros críticos de C3P0 como `acquireRetryAttempts` y `breakAfterAcquireFailure` no se aplicaban correctamente al inicio, manteniendo los valores por defecto que ocultaban errores de conexión.
'- VERSION 5.09.13.2 (Ahora consolidado en 5.09.15) ' 3. **`jdbcUrl` truncado/vacío:** Se observó que la `jdbcUrl` aparecía truncada o vacía en algunos logs de C3P0, indicando un problema en la carga de la configuración.
'- Módulo: DBHandlerJSON.bas '
'- Descripción de Cambios: Manejo de Peticiones POST con Content-Type: application/json ' **Cambios Implementados:**
'- • Problema Identificado: La implementación anterior de DBHandlerJSON procesaba las peticiones POST esperando que el payload JSON se encontrara en el parámetro j de la URL (req.GetParameter("j")). Esto impedía la correcta lectura de peticiones POST que utilizaban Content-Type: application/json, donde el JSON se envía directamente en el cuerpo de la petición (InputStream). Como resultado, los clientes recibían un error indicando la ausencia del parámetro j . '
'- • Solución Implementada: ' **En `Main.bas`:**
'- 1. Se modificó la lógica en el método Handle para detectar explícitamente las peticiones POST con Content-Type igual a application/json. '
'- 2. En estos casos, el payload JSON ahora se lee directamente del InputStream de la petición (req.InputStream). ' * **Declaración de conectores:** Se aseguró la declaración de variables `Dim conX As RDCConnector` separadas para cada conector (con1, con2, con3, con4) para evitar conflictos de variables y asegurar la inicialización correcta.
'- 3. Se utilizó Bit.InputStreamToBytes(Is0) para leer el cuerpo completo de la petición a un Array de bytes, seguido de BytesToString para convertirlo en la cadena JSON. '
'- 4. Se añadió el cierre explícito del InputStream (Is0.Close) para asegurar la liberación de recursos . ' **En `RDCConnector.bas`:**
'- 5. Se corrigió el nombre de la variable Is a Is0 para evitar un conflicto con la palabra reservada Is de B4X . '
'- 6. Se actualizó el mensaje de error para aclarar que el JSON puede faltar tanto en el parámetro j como en el cuerpo de la petición. ' * **Corrección de shadowing de `config`:** Se modificó `LoadConfigMap(DB)` para asignar directamente a la variable de clase `config` (eliminando `Dim` local), resolviendo el problema de la `jdbcUrl` truncada y asegurando que cada `RDCConnector` use su configuración específica de manera persistente.
'- • Beneficio: Esta corrección asegura que el DBHandlerJSON sea compatible con el "Método Recomendado" de POST con application/json, mejorando la robustez y la adherencia a los estándares de las APIs web, Sin comprometer la retrocompatibilidad con el "Método Legacy" (GET con parámetro j). ' * **Reordenamiento y robustecimiento de `Initialize`:**
' * **Carga de `config`:** Se asegura que `config` se cargue completamente en la variable de clase antes de cualquier operación del pool.
'- VERSION 5.09.13 (Ahora consolidado en 5.09.15) ' * **Configuración de C3P0:** Todas las propiedades del pool (incluyendo `setInitialPoolSize`, `setMinPoolSize`, `setMaxPoolSize`, `setMaxIdleTime`, etc. ahora se aplican mediante `jo.RunMethod` *inmediatamente después* de `pool.Initialize` y *antes* de que el pool intente adquirir conexiones.
' feat: Mejora la inicialización del pool de conexiones y el soporte multi-DB. ' * **Forzar reportes de errores:** Se añadieron las líneas `jo.RunMethod("setAcquireRetryAttempts", Array As Object(1))` y `jo.RunMethod("setBreakAfterAcquireFailure", Array As Object(True))`. Estas son cruciales para forzar a C3P0 a lanzar una `SQLException` explícita si falla al crear las conexiones iniciales, en lugar de fallar silenciosamente.
' ' * **Activación forzada del pool:** Se implementó `Dim tempCon As SQL = pool.GetConnection` seguido de `tempCon.Close` dentro de un bloque `Try...Catch`. Esto obliga al pool a establecer las conexiones iniciales (`InitialPoolSize`) con la configuración ya aplicada, permitiendo la captura de errores reales si la conexión falla.
' **Problemas Resueltos:** '
' ' - VERSION 5.09.08
' 1. **Inicialización de `TotalConnections: 0` en todos los pools:** Anteriormente, el Log mostraba 0 conexiones inicializadas para todas las bases de datos (DB1, DB2, DB3, DB4) durante `AppStart`, a pesar de que los `handlers` de `DBHandlerB4X` y `DBHandlerJSON` podían conectarse más tarde bajo demanda. Esto indicaba un fallo silencioso en la creación de conexiones iniciales por parte de C3P0. ' - Se agregó que se puedan configurar en el config.properties los siguientes parametros:
' 2. **Configuración inconsistente de C3P0:** Parámetros críticos de C3P0 como `acquireRetryAttempts` y `breakAfterAcquireFailure` no se aplicaban correctamente al inicio, manteniendo los valores por defecto que ocultaban errores de conexión. ' - - setInitialPoolSize = 3
' 3. **`jdbcUrl` truncado/vacío:** Se observó que la `jdbcUrl` aparecía truncada o vacía en algunos logs de C3P0, indicando un problema en la carga de la configuración. ' - - setMinPoolSize = 2
' ' - - setMaxPoolSize = 5
' **Cambios Implementados:** ' - Se agregaron en duro a RDConnector los siguientes parametros:
' ' - - setMaxIdleTime <-- Tiempo máximo de inactividad de la conexión.
' **En `Main.bas`:** ' - - setMaxConnectionAge <-- Tiempo de vida máximo de una conexión.
' ' - - setCheckoutTimeout <-- Tiempo máximo de espera por una conexión.
' * **Declaración de conectores:** Se aseguró la declaración de variables `Dim conX As RDCConnector` separadas para cada conector (con1, con2, con3, con4) para evitar conflictos de variables y asegurar la inicialización correcta. ' - Se agregó en el config.properties, al final del "JdbcUrl" este parametro, que le indica al servidor de Oracle
' ' - el nombre del cliente que se está conectando "?v$session.program=jRDC_Multi"
' **En `RDCConnector.bas`:** ' - VERSION 5.09.08
' ' - Se cambio el codigo para que en lugar de esperar un mapa con los parametros del query y nombres de los parametros (par1, par2, etc) para definir el ordenamiento, ahora se espera una lista [1,"2",3], y el orden de los parametros se toma directamente del orden en el que se mandan, de la misma forma que en B4A.
' * **Corrección de shadowing de `config`:** Se modificó `LoadConfigMap(DB)` para asignar directamente a la variable de clase `config` (eliminando `Dim` local), resolviendo el problema de la `jdbcUrl` truncada y asegurando que cada `RDCConnector` use su configuración específica de manera persistente. ' - VERSION 5.09.04
' * **Reordenamiento y robustecimiento de `Initialize`:** ' - Se cambio el nombre del handler de B4X a DBHandlerB4X.
' * **Carga de `config`:** Se asegura que `config` se cargue completamente en la variable de clase antes de cualquier operación del pool. ' - Se quitaron los handlers que ya no servian.
' * **Configuración de C3P0:** Todas las propiedades del pool (incluyendo `setInitialPoolSize`, `setMinPoolSize`, `setMaxPoolSize`, `setMaxIdleTime`, etc. ahora se aplican mediante `jo.RunMethod` *inmediatamente después* de `pool.Initialize` y *antes* de que el pool intente adquirir conexiones. ' - VERSION 5.09.01
' * **Forzar reportes de errores:** Se añadieron las líneas `jo.RunMethod("setAcquireRetryAttempts", Array As Object(1))` y `jo.RunMethod("setBreakAfterAcquireFailure", Array As Object(True))`. Estas son cruciales para forzar a C3P0 a lanzar una `SQLException` explícita si falla al crear las conexiones iniciales, en lugar de fallar silenciosamente. ' - Se corrigieron errores en "Manager".
' * **Activación forzada del pool:** Se implementó `Dim tempCon As SQL = pool.GetConnection` seguido de `tempCon.Close` dentro de un bloque `Try...Catch`. Esto obliga al pool a establecer las conexiones iniciales (`InitialPoolSize`) con la configuración ya aplicada, permitiendo la captura de errores reales si la conexión falla. ' - Se cambiaron nombres de handlers.
' - Se corrigio un error en la ruta de "www/login.html".
'- VERSION 5.09.08 ' - VERSION 5.08.31
'- Se agregó que se puedan configurar en el config.properties los siguientes parametros: ' - Se corrigio que no avisaba cuando el query no requeria parametros y si se enviaban (en el JSONHandler)
'- - setInitialPoolSize = 3 ' - VERSION 5.08.30
'- - setMinPoolSize = 2 ' - Se cambiaron los 4 handlers de B4A a uno solo que toma el DB de la ruta automáticamente.
'- - setMaxPoolSize = 5 ' - Se agregaron validaciones del numero de parametros y si el query no los requiere o se dan de mas o de menos, manda un error especificando eso, ya no se reciben errores directos de la base de datos, esto fue tanto para B4A como para JSON.
'- Se agregaron en duro a RDConnector los siguientes parametros: ' - Se modificó el Readme.md para incluir todos estos cambios.
'- - setMaxIdleTime <-- Tiempo máximo de inactividad de la conexión. ' - VERSION 5.08.25
'- - setMaxConnectionAge <-- Tiempo de vida máximo de una conexión. ' - Se modificaron los archivos de reinicio de los servicios (servidor y Bow) y se cambio el menu del "manager" para que a seccion de "reload" incluya la liga a reinciar Bow.
'- - setCheckoutTimeout <-- Tiempo máximo de espera por una conexión. ' - VERSION 5.08.02
'- Se agregó en el config.properties, al final del "JdbcUrl" este parametro, que le indica al servidor de Oracle ' - Se hizo un cambio para tratar de que las conexiones se "identifiquen" con Oracle y Jorge pueda saber que conexiones/recursos estamos ocupando
'- el nombre del cliente que se está conectando "?v$session.program=jRDC_Multi" ' - VERSION 4.11.14
'- VERSION 5.09.08 ' - Se agregó el parametro "setMaxPoolSize=5" para que solo genere 5 conexiones a la base de datos, antes generaba 15.
'- Se cambio el codigo para que en lugar de esperar un mapa con los parametros del query y nombres de los parametros (par1, par2, etc) para definir el ordenamiento, ahora se espera una lista [1,"2",3], y el orden de los parametros se toma directamente del orden en el que se mandan, de la misma forma que en B4A. ' - Se quitaron lineas previamente comentadas.
'- VERSION 5.09.04 ' - VERSION 4.11.09
'- Se cambio el nombre del handler de B4X a DBHandlerB4X. ' - Commit inicial on Nov 9, 2024
'- Se quitaron los handlers que ya no servian.
'- VERSION 5.09.01
'- Se corrigieron errores en "Manager".
'- Se cambiaron nombres de handlers.
'- Se corrigio un error en la ruta de "www/login.html".
'- VERSION 5.08.31
'- Se corrigio que no avisaba cuando el query no requeria parametros y si se enviaban (en el JSONHandler)
'- VERSION 5.08.30
'- Se cambiaron los 4 handlers de B4A a uno solo que toma el DB de la ruta automáticamente.
'- Se agregaron validaciones del numero de parametros y si el query no los requiere o se dan de mas o de menos, manda un error especificando eso, ya no se reciben errores directos de la base de datos, esto fue tanto para B4A como para JSON.
'- Se modificó el Readme.md para incluir todos estos cambios.
'- VERSION 5.08.25
'- Se modificaron los archivos de reinicio de los servicios (servidor y Bow) y se cambio el menu del "manager" para que a seccion de "reload" incluya la liga a reinciar Bow.
'- VERSION 5.08.02
'- Se hizo un cambio para tratar de que las conexiones se "identifiquen" con Oracle y Jorge pueda saber que conexiones/recursos estamos ocupando
'- VERSION 4.11.14
'- Se agregó el parametro "setMaxPoolSize=5" para que solo genere 5 conexiones a la base de datos, antes generaba 15.
'- Se quitaron lineas previamente comentadas.
'- VERSION 4.11.09
'- Commit inicial on Nov 9, 2024
End Sub End Sub

View File

@@ -33,7 +33,6 @@ Public Sub Handle(req As ServletRequest, resp As ServletResponse)
Try Try
Dim storedHash As String = Main.SQL1.ExecQuerySingleResult2("SELECT password_hash FROM users WHERE username = ?", Array As String(currentUser)) Dim storedHash As String = Main.SQL1.ExecQuerySingleResult2("SELECT password_hash FROM users WHERE username = ?", Array As String(currentUser))
Log("--- Probando con contraseña fija ---")
Log("Valor de la BD (storedHash): " & storedHash) Log("Valor de la BD (storedHash): " & storedHash)
If storedHash = Null Or bc.checkpw(currentPass, storedHash) = False Then ' <<--- CAMBIO CLAVE AQUÍ If storedHash = Null Or bc.checkpw(currentPass, storedHash) = False Then ' <<--- CAMBIO CLAVE AQUÍ
resp.Write("<script>alert('Error: La contraseña actual es incorrecta.'); history.back();</script>") resp.Write("<script>alert('Error: La contraseña actual es incorrecta.'); history.back();</script>")

View File

@@ -17,7 +17,7 @@ Sub Class_Globals
' La siguiente sección de constantes y utilidades se compila condicionalmente ' La siguiente sección de constantes y utilidades se compila condicionalmente
' solo si la directiva #if VERSION1 está activa. Esto es para dar soporte ' solo si la directiva #if VERSION1 está activa. Esto es para dar soporte
' a una versión antigua del protocolo de comunicación de DBRequestManager. ' a una versión antigua del protocolo de comunicación de DBRequestManager.
#if VERSION1 ' #if VERSION1
' Constantes para identificar los tipos de datos en la serialización personalizada (protocolo V1). ' Constantes para identificar los tipos de datos en la serialización personalizada (protocolo V1).
Private const T_NULL = 0, T_STRING = 1, T_SHORT = 2, T_INT = 3, T_LONG = 4, T_FLOAT = 5 _ Private const T_NULL = 0, T_STRING = 1, T_SHORT = 2, T_INT = 3, T_LONG = 4, T_FLOAT = 5 _
,T_DOUBLE = 6, T_BOOLEAN = 7, T_BLOB = 8 As Byte ,T_DOUBLE = 6, T_BOOLEAN = 7, T_BLOB = 8 As Byte
@@ -25,7 +25,7 @@ Sub Class_Globals
Private bc As ByteConverter Private bc As ByteConverter
' Utilidad para comprimir/descomprimir streams de datos (usado en V1). ' Utilidad para comprimir/descomprimir streams de datos (usado en V1).
Private cs As CompressedStreams Private cs As CompressedStreams
#end if ' #end if
' Mapa para convertir tipos de columna JDBC de fecha/hora a los nombres de métodos de Java ' Mapa para convertir tipos de columna JDBC de fecha/hora a los nombres de métodos de Java
' para obtener los valores correctos de ResultSet. ' para obtener los valores correctos de ResultSet.
@@ -68,13 +68,11 @@ Sub Handle(req As ServletRequest, resp As ServletResponse)
' Verifica si el dbKey extraído corresponde a una base de datos configurada y cargada en Main. ' Verifica si el dbKey extraído corresponde a una base de datos configurada y cargada en Main.
If Main.Connectors.ContainsKey(dbKey) = False Then If Main.Connectors.ContainsKey(dbKey) = False Then
' Si la base de datos no es válida, se construye un mensaje de error y se envía.
Dim ErrorMsg As String = $"Invalid DB key specified in URL: '${dbKey}'. Valid keys are: ${Main.listaDeCP}"$ Dim ErrorMsg As String = $"Invalid DB key specified in URL: '${dbKey}'. Valid keys are: ${Main.listaDeCP}"$
Log(ErrorMsg) Log(ErrorMsg)
SendPlainTextError(resp, 400, ErrorMsg) ' Envía una respuesta de error al cliente. Main.LogServerError("ERROR", "DBHandlerB4X.Handle", ErrorMsg, dbKey, Null, req.RemoteAddress) ' <-- Nuevo Log
' No se llama a CleanupAndLog aquí, ya que el contador de peticiones no se ha incrementado SendPlainTextError(resp, 400, ErrorMsg)
' y no se ha obtenido ninguna conexión del pool. Return
Return ' Termina la ejecución del handler.
End If End If
' === FIN DE LA LÓGICA DINÁMICA === ' === FIN DE LA LÓGICA DINÁMICA ===
@@ -130,7 +128,7 @@ Sub Handle(req As ServletRequest, resp As ServletResponse)
CleanupAndLog(dbKey, "error_in_" & method, duration, req.RemoteAddress, requestsBeforeDecrement, poolBusyConnectionsForLog, con) CleanupAndLog(dbKey, "error_in_" & method, duration, req.RemoteAddress, requestsBeforeDecrement, poolBusyConnectionsForLog, con)
Return ' Salida temprana si hay un error. Return ' Salida temprana si hay un error.
End If End If
#if VERSION1 ' #if VERSION1
' Estas ramas se compilan solo si #if VERSION1 está activo (para protocolo antiguo). ' Estas ramas se compilan solo si #if VERSION1 está activo (para protocolo antiguo).
Else if method = "query" Then Else if method = "query" Then
in = cs.WrapInputStream(in, "gzip") ' Descomprime el stream de entrada si es protocolo V1. in = cs.WrapInputStream(in, "gzip") ' Descomprime el stream de entrada si es protocolo V1.
@@ -148,7 +146,7 @@ Sub Handle(req As ServletRequest, resp As ServletResponse)
CleanupAndLog(dbKey, "error_in_" & method, duration, req.RemoteAddress, requestsBeforeDecrement, poolBusyConnectionsForLog, con) CleanupAndLog(dbKey, "error_in_" & method, duration, req.RemoteAddress, requestsBeforeDecrement, poolBusyConnectionsForLog, con)
Return Return
End If End If
#end if ' #end if
Else if method = "batch2" Then Else if method = "batch2" Then
' Ejecuta un lote de comandos (INSERT, UPDATE, DELETE) utilizando el protocolo V2. ' Ejecuta un lote de comandos (INSERT, UPDATE, DELETE) utilizando el protocolo V2.
q = ExecuteBatch2(dbKey, con, in, resp) q = ExecuteBatch2(dbKey, con, in, resp)
@@ -158,18 +156,19 @@ Sub Handle(req As ServletRequest, resp As ServletResponse)
Return ' Salida temprana si hay un error. Return ' Salida temprana si hay un error.
End If End If
Else Else
' Si el método solicitado no es reconocido, se registra un error y se envía una respuesta adecuada. Dim ErrorMsg As String = "Unknown method: " & method
Log("Unknown method: " & method) Log(ErrorMsg)
Main.LogServerError("ERROR", "DBHandlerB4X.Handle", ErrorMsg, dbKey, method, req.RemoteAddress) ' <-- Nuevo Log
SendPlainTextError(resp, 500, "unknown method") SendPlainTextError(resp, 500, "unknown method")
q = "unknown_method_handler" ' Aseguramos un valor para 'q' para que el log sea informativo. q = "unknown_method_handler"
duration = DateTime.Now - start duration = DateTime.Now - start
CleanupAndLog(dbKey, q, duration, req.RemoteAddress, requestsBeforeDecrement, poolBusyConnectionsForLog, con) CleanupAndLog(dbKey, q, duration, req.RemoteAddress, requestsBeforeDecrement, poolBusyConnectionsForLog, con)
Return ' Salida temprana. Return
End If End If
Catch ' --- CATCH: Maneja errores generales de ejecución o de SQL --- Catch ' --- CATCH: Maneja errores generales de ejecución o de SQL ---
' Si ocurre una excepción inesperada durante el procesamiento de la petición.
Log(LastException) ' Registra la excepción completa en el log. Log(LastException) ' Registra la excepción completa en el log.
Main.LogServerError("ERROR", "DBHandlerB4X.Handle", LastException.Message, dbKey, q, req.RemoteAddress) ' <-- Nuevo Log
SendPlainTextError(resp, 500, LastException.Message) ' Envía un error 500 al cliente. SendPlainTextError(resp, 500, LastException.Message) ' Envía un error 500 al cliente.
q = "error_in_b4x_handler" ' Aseguramos un valor para 'q' en caso de excepción. q = "error_in_b4x_handler" ' Aseguramos un valor para 'q' en caso de excepción.
End Try ' --- FIN: Bloque Try principal --- End Try ' --- FIN: Bloque Try principal ---
@@ -238,34 +237,33 @@ Private Sub ExecuteQuery2 (DB As String, con As SQL, in As InputStream, resp As
If sqlCommand = Null Or sqlCommand = "null" Or sqlCommand.Trim = "" Then If sqlCommand = Null Or sqlCommand = "null" Or sqlCommand.Trim = "" Then
Dim errorMessage As String = $"El comando '${cmd.Name}' no fue encontrado en el config.properties de '${DB}'."$ Dim errorMessage As String = $"El comando '${cmd.Name}' no fue encontrado en el config.properties de '${DB}'."$
Log(errorMessage) Log(errorMessage)
Main.LogServerError("ERROR", "DBHandlerB4X.ExecuteQuery2", errorMessage, DB, cmd.Name, Null)
' Envía un error 400 (Bad Request) al cliente informando del problema. ' Envía un error 400 (Bad Request) al cliente informando del problema.
SendPlainTextError(resp, 400, errorMessage) SendPlainTextError(resp, 400, errorMessage)
Return "error" ' Retorna un texto para el log. Return "error" ' Retorna un texto para el log.
End If End If
' <<< FIN NUEVA VALIDACIÓN >>> ' <<< FIN NUEVA VALIDACIÓN >>>
' --- INICIO VALIDACIÓN DE PARÁMETROS --- ' <<< INICIO VALIDACIÓN DE PARÁMETROS CENTRALIZADA >>>
' Comprueba si el SQL espera parámetros o si se recibieron parámetros. ' Convertimos el array de Object() de cmd.Parameters a una List para la utilidad de validación.
If sqlCommand.Contains("?") Or (cmd.Parameters <> Null And cmd.Parameters.Length > 0) Then Dim paramsAsList As List
' Cuenta cuántos '?' hay en la sentencia SQL para saber cuántos parámetros se esperan. paramsAsList.Initialize
Dim expectedParams As Int = sqlCommand.Length - sqlCommand.Replace("?", "").Length If cmd.Parameters <> Null Then
' Cuenta cuántos parámetros se recibieron. For Each p As Object In cmd.Parameters
Dim receivedParams As Int paramsAsList.Add(p)
If cmd.Parameters = Null Then receivedParams = 0 Else receivedParams = cmd.Parameters.Length Next
' Compara el número de parámetros esperados con los recibidos.
If expectedParams <> receivedParams Then
Dim errorMessage As String = $"Número de parametros equivocado para "${cmd.Name}". Se esperaban ${expectedParams} y se recibieron ${receivedParams}."$
Log(errorMessage)
' Si no coinciden, envía un error 400 al cliente.
SendPlainTextError(resp, 400, errorMessage)
Return "error"
End If
End If End If
' --- FIN VALIDACIÓN ---
' Ejecuta la consulta SQL con los parámetros proporcionados. Dim validationResult As ParameterValidationResult = ParameterValidationUtils.ValidateAndAdjustParameters(cmd.Name, DB, sqlCommand, paramsAsList, Connector.IsParameterToleranceEnabled)
Dim rs As ResultSet = con.ExecQuery2(sqlCommand, cmd.Parameters)
If validationResult.Success = False Then
SendPlainTextError(resp, 400, validationResult.ErrorMessage)
Return "error" ' Salida temprana si la validación falla.
End If
' Ejecuta la consulta SQL con la lista de parámetros validada.
Dim rs As ResultSet = con.ExecQuery2(sqlCommand, validationResult.ParamsToExecute)
' <<< FIN VALIDACIÓN DE PARÁMETROS CENTRALIZADA >>>
' Si el límite es 0 o negativo, lo establece a un valor muy alto (máximo entero). ' Si el límite es 0 o negativo, lo establece a un valor muy alto (máximo entero).
If limit <= 0 Then limit = 0x7fffffff 'max int If limit <= 0 Then limit = 0x7fffffff 'max int
@@ -367,28 +365,32 @@ Private Sub ExecuteBatch2(DB As String, con As SQL, in As InputStream, resp As S
con.Rollback ' Deshace la transacción si un comando es inválido. con.Rollback ' Deshace la transacción si un comando es inválido.
Dim errorMessage As String = $"El comando '${cmd.Name}' no fue encontrado en el config.properties de '${DB}'."$ Dim errorMessage As String = $"El comando '${cmd.Name}' no fue encontrado en el config.properties de '${DB}'."$
Log(errorMessage) Log(errorMessage)
Main.LogServerError("ERROR", "DBHandlerB4X.ExecuteBatch2", errorMessage, DB, cmd.Name, Null)
SendPlainTextError(resp, 400, errorMessage) SendPlainTextError(resp, 400, errorMessage)
Return "error" Return "error"
End If End If
' <<< FIN NUEVA VALIDACIÓN >>> ' <<< FIN NUEVA VALIDACIÓN >>>
' --- INICIO VALIDACIÓN DE PARÁMETROS DENTRO DEL BATCH --- ' <<< INICIO VALIDACIÓN DE PARÁMETROS CENTRALIZADA DENTRO DEL BATCH >>>
If sqlCommand.Contains("?") Or (cmd.Parameters <> Null And cmd.Parameters.Length > 0) Then ' Convertimos el array de Object() de cmd.Parameters a una List para la utilidad de validación.
Dim expectedParams As Int = sqlCommand.Length - sqlCommand.Replace("?", "").Length Dim paramsAsList As List
Dim receivedParams As Int paramsAsList.Initialize
If cmd.Parameters = Null Then receivedParams = 0 Else receivedParams = cmd.Parameters.Length If cmd.Parameters <> Null Then
' Si el número de parámetros no coincide, deshace la transacción y envía error. For Each p As Object In cmd.Parameters
If expectedParams <> receivedParams Then paramsAsList.Add(p)
con.Rollback Next
Dim errorMessage As String = $"Número de parametros equivocado para "${cmd.Name}". Se esperaban ${expectedParams} y se recibieron ${receivedParams}."$
Log(errorMessage)
SendPlainTextError(resp, 400, errorMessage)
Return "error"
End If
End If End If
' --- FIN VALIDACIÓN ---
con.ExecNonQuery2(sqlCommand, cmd.Parameters) ' Ejecuta el comando (no es una consulta, no devuelve filas). Dim validationResult As ParameterValidationResult = ParameterValidationUtils.ValidateAndAdjustParameters(cmd.Name, DB, sqlCommand, paramsAsList, Connector.IsParameterToleranceEnabled)
If validationResult.Success = False Then
con.Rollback ' ¡Importante hacer rollback si la validación falla dentro de una transacción!
SendPlainTextError(resp, 400, validationResult.ErrorMessage)
Return "error" ' Salida temprana si la validación falla.
End If
con.ExecNonQuery2(sqlCommand, validationResult.ParamsToExecute) ' Ejecuta el comando con la lista de parámetros validada.
' <<< FIN VALIDACIÓN DE PARÁMETROS CENTRALIZADA DENTRO DEL BATCH >>>
Next Next
res.Rows.Add(Array As Object(0)) ' Añade una fila simbólica al resultado para indicar éxito. res.Rows.Add(Array As Object(0)) ' Añade una fila simbólica al resultado para indicar éxito.
@@ -397,6 +399,7 @@ Private Sub ExecuteBatch2(DB As String, con As SQL, in As InputStream, resp As S
' Si cualquier comando falla, se captura el error. ' Si cualquier comando falla, se captura el error.
con.Rollback ' Se deshacen todos los cambios hechos en la transacción. con.Rollback ' Se deshacen todos los cambios hechos en la transacción.
Log(LastException) ' Registra la excepción. Log(LastException) ' Registra la excepción.
Main.LogServerError("ERROR", "DBHandlerB4X.ExecuteBatch2", LastException.Message, DB, "batch_execution_error", Null)
SendPlainTextError(resp, 500, LastException.Message) ' Envía un error 500 al cliente. SendPlainTextError(resp, 500, LastException.Message) ' Envía un error 500 al cliente.
End Try End Try
@@ -410,7 +413,7 @@ End Sub
' --- Subrutinas para manejar la ejecución de queries y batches (Protocolo V1 - Compilación Condicional) --- ' --- Subrutinas para manejar la ejecución de queries y batches (Protocolo V1 - Compilación Condicional) ---
' Este código se compila solo si #if VERSION1 está activo, para mantener compatibilidad con clientes antiguos. ' Este código se compila solo si #if VERSION1 está activo, para mantener compatibilidad con clientes antiguos.
#if VERSION1 '#if VERSION1
' Ejecuta un lote de comandos usando el protocolo V1. ' Ejecuta un lote de comandos usando el protocolo V1.
Private Sub ExecuteBatch(DB As String, con As SQL, in As InputStream, resp As ServletResponse) As String Private Sub ExecuteBatch(DB As String, con As SQL, in As InputStream, resp As ServletResponse) As String
@@ -434,28 +437,23 @@ Private Sub ExecuteBatch(DB As String, con As SQL, in As InputStream, resp As Se
con.Rollback ' Deshace la transacción si un comando es inválido. con.Rollback ' Deshace la transacción si un comando es inválido.
Dim errorMessage As String = $"El comando '${queryName}' no fue encontrado en el config.properties de '${DB}'."$ Dim errorMessage As String = $"El comando '${queryName}' no fue encontrado en el config.properties de '${DB}'."$
Log(errorMessage) Log(errorMessage)
Main.LogServerError("ERROR", "DBHandlerB4X.ExecuteBatch (V1)", errorMessage, DB, queryName, Null)
SendPlainTextError(resp, 400, errorMessage) SendPlainTextError(resp, 400, errorMessage)
Return "error" Return "error"
End If End If
' <<< FIN NUEVA VALIDACIÓN >>> ' <<< FIN NUEVA VALIDACIÓN >>>
' --- INICIO VALIDACIÓN DE PARÁMETROS DENTRO DEL BATCH (V1) --- ' <<< INICIO VALIDACIÓN DE PARÁMETROS CENTRALIZADA DENTRO DEL BATCH (V1) >>>
If sqlCommand.Contains("?") Or (params <> Null And params.Size > 0) Then Dim validationResult As ParameterValidationResult = ParameterValidationUtils.ValidateAndAdjustParameters(queryName, DB, sqlCommand, params, Connector.IsParameterToleranceEnabled)
Dim expectedParams As Int = sqlCommand.Length - sqlCommand.Replace("?", "").Length
Dim receivedParams As Int
If params = Null Then receivedParams = 0 Else receivedParams = params.Size
If expectedParams <> receivedParams Then If validationResult.Success = False Then
con.Rollback con.Rollback ' ¡Importante hacer rollback si la validación falla dentro de una transacción!
Dim errorMessage As String = $"Número de parametros equivocado para "${queryName}". Se esperaban ${expectedParams} y se recibieron ${receivedParams}."$ SendPlainTextError(resp, 400, validationResult.ErrorMessage)
Log(errorMessage) Return "error" ' Salida temprana si la validación falla.
SendPlainTextError(resp, 400, errorMessage) End If
Return "error"
End If
End If
' --- FIN VALIDACIÓN ---
con.ExecNonQuery2(sqlCommand, params) ' Ejecuta el comando. con.ExecNonQuery2(sqlCommand, validationResult.ParamsToExecute) ' Ejecuta el comando con la lista de parámetros validada.
' <<< FIN VALIDACIÓN DE PARÁMETROS CENTRALIZADA DENTRO DEL BATCH (V1) >>>
Next Next
con.TransactionSuccessful ' Confirma la transacción. con.TransactionSuccessful ' Confirma la transacción.
@@ -473,6 +471,7 @@ Private Sub ExecuteBatch(DB As String, con As SQL, in As InputStream, resp As Se
Catch Catch
con.Rollback con.Rollback
Log(LastException) Log(LastException)
Main.LogServerError("ERROR", "DBHandlerB4X.ExecuteBatch (V1)", LastException.Message, DB, "batch_execution_error_v1", Null)
SendPlainTextError(resp, 500, LastException.Message) SendPlainTextError(resp, 500, LastException.Message)
End Try End Try
@@ -495,28 +494,23 @@ Private Sub ExecuteQuery(DB As String, con As SQL, in As InputStream, resp As Se
If theSql = Null Or theSql ="null" Or theSql.Trim = "" Then If theSql = Null Or theSql ="null" Or theSql.Trim = "" Then
Dim errorMessage As String = $"El comando '${queryName}' no fue encontrado en el config.properties de '${DB}'."$ Dim errorMessage As String = $"El comando '${queryName}' no fue encontrado en el config.properties de '${DB}'."$
Log(errorMessage) Log(errorMessage)
Main.LogServerError("ERROR", "DBHandlerB4X.ExecuteQuery (V1)", errorMessage, DB, queryName, Null)
SendPlainTextError(resp, 400, errorMessage) SendPlainTextError(resp, 400, errorMessage)
Return "error" Return "error"
End If End If
' <<< FIN NUEVA VALIDACIÓN >>> ' <<< FIN NUEVA VALIDACIÓN >>>
' --- INICIO VALIDACIÓN DE PARÁMETROS (V1) --- ' <<< INICIO VALIDACIÓN DE PARÁMETROS CENTRALIZADA (V1) >>>
If theSql.Contains("?") Or (params <> Null And params.Size > 0) Then Dim validationResult As ParameterValidationResult = ParameterValidationUtils.ValidateAndAdjustParameters(queryName, DB, theSql, params, Connector.IsParameterToleranceEnabled)
Dim expectedParams As Int = theSql.Length - theSql.Replace("?", "").Length
Dim receivedParams As Int
If params = Null Then receivedParams = 0 Else receivedParams = params.Size
If expectedParams <> receivedParams Then If validationResult.Success = False Then
Dim errorMessage As String = $"Número de parametros equivocado para "${queryName}". Se esperaban ${expectedParams} y se recibieron ${receivedParams}."$ SendPlainTextError(resp, 400, validationResult.ErrorMessage)
Log(errorMessage) Return "error" ' Salida temprana si la validación falla.
SendPlainTextError(resp, 400, errorMessage) End If
Return "error"
End If
End If
' --- FIN VALIDACIÓN ---
' Ejecuta la consulta. ' Ejecuta la consulta con la lista de parámetros validada.
Dim rs As ResultSet = con.ExecQuery2(theSql, params) Dim rs As ResultSet = con.ExecQuery2(theSql, validationResult.ParamsToExecute)
' <<< FIN VALIDACIÓN DE PARÁMETROS CENTRALIZADA (V1) >>>
If limit <= 0 Then limit = 0x7fffffff 'max int If limit <= 0 Then limit = 0x7fffffff 'max int
@@ -691,7 +685,7 @@ Private Sub ReadList(in As InputStream) As List
Return l1 Return l1
End Sub End Sub
#end If ' Fin del bloque de compilación condicional para VERSION1 '#end If ' Fin del bloque de compilación condicional para VERSION1
' Envía una respuesta de error en formato de texto plano. ' Envía una respuesta de error en formato de texto plano.
' Esto evita la página de error HTML por defecto que genera resp.SendError. ' Esto evita la página de error HTML por defecto que genera resp.SendError.
@@ -716,6 +710,8 @@ Private Sub SendPlainTextError(resp As ServletResponse, statusCode As Int, error
Catch Catch
' Si algo falla al intentar enviar la respuesta de error, lo registra en el log ' Si algo falla al intentar enviar la respuesta de error, lo registra en el log
' para que no se pierda la causa original del problema. ' para que no se pierda la causa original del problema.
Log("Error sending plain text error response: " & LastException) Dim ErrorMsg As String = "Error sending plain text error response: " & LastException
Log(ErrorMsg)
Main.LogServerError("ERROR", "DBHandlerB4X.SendPlainTextError", ErrorMsg, Null, Null, Null)
End Try End Try
End Sub End Sub

View File

@@ -63,11 +63,12 @@ Sub Handle(req As ServletRequest, resp As ServletResponse)
' Validación inicial: Si no hay JSON, se envía un error 400. ' Validación inicial: Si no hay JSON, se envía un error 400.
If jsonString = Null Or jsonString = "" Then If jsonString = Null Or jsonString = "" Then
SendErrorResponse(resp, 400, "Falta el parámetro 'j' en el URL o el cuerpo JSON en la petición.") Dim ErrorMsg As String = "Falta el parámetro 'j' en el URL o el cuerpo JSON en la petición."
SendErrorResponse(resp, 400, ErrorMsg)
Main.LogServerError("ERROR", "DBHandlerJSON.Handle", ErrorMsg, finalDbKey, queryNameForLog, req.RemoteAddress) ' <-- Nuevo Log
duration = DateTime.Now - start duration = DateTime.Now - start
' Llama a CleanupAndLog para registrar que hubo un error, pero con contadores a 0 o inicializados.
CleanupAndLog(finalDbKey, queryNameForLog, duration, req.RemoteAddress, requestsBeforeDecrement, poolBusyConnectionsForLog, con) CleanupAndLog(finalDbKey, queryNameForLog, duration, req.RemoteAddress, requestsBeforeDecrement, poolBusyConnectionsForLog, con)
Return ' Salida temprana si no hay JSON válido. Return
End If End If
Dim parser As JSONParser Dim parser As JSONParser
@@ -108,10 +109,12 @@ Sub Handle(req As ServletRequest, resp As ServletResponse)
' Validación: Si el dbKey no es válido o no está configurado en Main.listaDeCP. ' Validación: Si el dbKey no es válido o no está configurado en Main.listaDeCP.
If Main.listaDeCP.IndexOf(finalDbKey) = -1 Then If Main.listaDeCP.IndexOf(finalDbKey) = -1 Then
SendErrorResponse(resp, 400, "Parámetro 'DB' inválido. El nombre '" & finalDbKey & "' no es válido.") Dim ErrorMsg As String = "Parámetro 'DB' inválido. El nombre '" & finalDbKey & "' no es válido."
SendErrorResponse(resp, 400, ErrorMsg)
Main.LogServerError("ERROR", "DBHandlerJSON.Handle", ErrorMsg, finalDbKey, queryNameForLog, req.RemoteAddress) ' <-- Nuevo Log
duration = DateTime.Now - start duration = DateTime.Now - start
CleanupAndLog(finalDbKey, queryNameForLog, duration, req.RemoteAddress, requestsBeforeDecrement, poolBusyConnectionsForLog, con) CleanupAndLog(finalDbKey, queryNameForLog, duration, req.RemoteAddress, requestsBeforeDecrement, poolBusyConnectionsForLog, con)
Return ' Salida temprana si la DB no es válida. Return
End If End If
con = Connector.GetConnection(finalDbKey) ' ¡La conexión a la BD se obtiene aquí del pool de conexiones! con = Connector.GetConnection(finalDbKey) ' ¡La conexión a la BD se obtiene aquí del pool de conexiones!
@@ -135,31 +138,30 @@ Sub Handle(req As ServletRequest, resp As ServletResponse)
If sqlCommand = Null Or sqlCommand = "null" Or sqlCommand.Trim = "" Then If sqlCommand = Null Or sqlCommand = "null" Or sqlCommand.Trim = "" Then
Dim errorMessage As String = $"El comando '${queryNameForLog}' no fue encontrado en el config.properties de '${finalDbKey}'."$ Dim errorMessage As String = $"El comando '${queryNameForLog}' no fue encontrado en el config.properties de '${finalDbKey}'."$
Log(errorMessage) Log(errorMessage)
Main.LogServerError("ERROR", "DBHandlerJSON.Handle", errorMessage, finalDbKey, queryNameForLog, req.RemoteAddress) ' <-- Nuevo Log
SendErrorResponse(resp, 400, errorMessage) SendErrorResponse(resp, 400, errorMessage)
duration = DateTime.Now - start duration = DateTime.Now - start
CleanupAndLog(finalDbKey, queryNameForLog, duration, req.RemoteAddress, requestsBeforeDecrement, poolBusyConnectionsForLog, con) CleanupAndLog(finalDbKey, queryNameForLog, duration, req.RemoteAddress, requestsBeforeDecrement, poolBusyConnectionsForLog, con)
Return ' Salida temprana. Return
End If End If
' --- Lógica para ejecutar diferentes tipos de comandos basados en el parámetro 'execType' --- ' --- Lógica para ejecutar diferentes tipos de comandos basados en el parámetro 'execType' ---
If execType.ToLowerCase = "executequery" Then If execType.ToLowerCase = "executequery" Then
Dim rs As ResultSet ' --- INICIO VALIDACIÓN DE PARÁMETROS CENTRALIZADA ---
' Validación de parámetros para ExecuteQuery. Dim validationResult As ParameterValidationResult = ParameterValidationUtils.ValidateAndAdjustParameters(queryNameForLog, finalDbKey, sqlCommand, paramsList, Connector.IsParameterToleranceEnabled)
If sqlCommand.Contains("?") Or paramsList.Size > 0 Then
Dim expectedParams As Int = sqlCommand.Length - sqlCommand.Replace("?", "").Length If validationResult.Success = False Then
Dim receivedParams As Int = paramsList.Size SendErrorResponse(resp, 400, validationResult.ErrorMessage)
Log($"expectedParams: ${expectedParams}, receivedParams: ${receivedParams}"$) duration = DateTime.Now - start
If expectedParams <> receivedParams Then CleanupAndLog(finalDbKey, queryNameForLog, duration, req.RemoteAddress, requestsBeforeDecrement, poolBusyConnectionsForLog, con)
SendErrorResponse(resp, 400, $"Número de parámetros equivocado para '${queryNameForLog}'. Se esperaban ${expectedParams} y se recibieron ${receivedParams}."$) Return ' Salida temprana.
duration = DateTime.Now - start
CleanupAndLog(finalDbKey, queryNameForLog, duration, req.RemoteAddress, requestsBeforeDecrement, poolBusyConnectionsForLog, con)
Return ' Salida temprana.
End If
rs = con.ExecQuery2(sqlCommand, paramsList) ' Ejecuta la consulta con parámetros.
Else
rs = con.ExecQuery(sqlCommand) ' Ejecuta la consulta sin parámetros.
End If End If
Dim rs As ResultSet
' Ejecuta la consulta SQL con la lista de parámetros validada.
rs = con.ExecQuery2(sqlCommand, validationResult.ParamsToExecute)
' --- FIN VALIDACIÓN DE PARÁMETROS CENTRALIZADA ---
Dim ResultList As List Dim ResultList As List
ResultList.Initialize ' Lista para almacenar los resultados de la consulta. ResultList.Initialize ' Lista para almacenar los resultados de la consulta.
Dim jrs As JavaObject = rs ' Objeto Java subyacente del ResultSet para metadatos. Dim jrs As JavaObject = rs ' Objeto Java subyacente del ResultSet para metadatos.
@@ -180,29 +182,29 @@ Sub Handle(req As ServletRequest, resp As ServletResponse)
SendSuccessResponse(resp, CreateMap("result": ResultList)) ' Envía la respuesta JSON de éxito. SendSuccessResponse(resp, CreateMap("result": ResultList)) ' Envía la respuesta JSON de éxito.
Else If execType.ToLowerCase = "executecommand" Then Else If execType.ToLowerCase = "executecommand" Then
' Validación de parámetros para ExecuteCommand. ' --- INICIO VALIDACIÓN DE PARÁMETROS CENTRALIZADA ---
If sqlCommand.Contains("?") Then Dim validationResult As ParameterValidationResult = ParameterValidationUtils.ValidateAndAdjustParameters(queryNameForLog, finalDbKey, sqlCommand, paramsList, Connector.IsParameterToleranceEnabled)
Dim expectedParams As Int = sqlCommand.Length - sqlCommand.Replace("?", "").Length
Dim receivedParams As Int = paramsList.Size
If expectedParams <> receivedParams Then
SendErrorResponse(resp, 400, $"Número de parámetros equivocado para '${queryNameForLog}'. Se esperaban ${expectedParams} y se recibieron ${receivedParams}."$)
duration = DateTime.Now - start
CleanupAndLog(finalDbKey, queryNameForLog, duration, req.RemoteAddress, requestsBeforeDecrement, poolBusyConnectionsForLog, con)
Return ' Salida temprana.
End If
End If
con.ExecNonQuery2(sqlCommand, paramsList) ' Ejecuta un comando (INSERT, UPDATE, DELETE).
SendSuccessResponse(resp, CreateMap("message": "Command executed successfully")) ' Envía confirmación de éxito.
If validationResult.Success = False Then
SendErrorResponse(resp, 400, validationResult.ErrorMessage)
duration = DateTime.Now - start
CleanupAndLog(finalDbKey, queryNameForLog, duration, req.RemoteAddress, requestsBeforeDecrement, poolBusyConnectionsForLog, con)
Return ' Salida temprana.
End If
con.ExecNonQuery2(sqlCommand, validationResult.ParamsToExecute) ' Ejecuta un comando con la lista de parámetros validada.
SendSuccessResponse(resp, CreateMap("message": "Command executed successfully")) ' Envía confirmación de éxito.
' --- FIN VALIDACIÓN DE PARÁMETROS CENTRALIZADA ---
Else Else
' Si el tipo de ejecución no es reconocido. Dim ErrorMsg As String = "Parámetro 'exec' inválido. '" & execType & "' no es un valor permitido."
SendErrorResponse(resp, 400, "Parámetro 'exec' inválido. '" & execType & "' no es un valor permitido.") SendErrorResponse(resp, 400, ErrorMsg)
Main.LogServerError("ERROR", "DBHandlerJSON.Handle", ErrorMsg, finalDbKey, queryNameForLog, req.RemoteAddress) ' <-- Nuevo Log
' El flujo continúa hasta la limpieza final si no hay un Return explícito. ' El flujo continúa hasta la limpieza final si no hay un Return explícito.
End If End If
Catch ' --- CATCH: Maneja errores generales de ejecución o de SQL/JSON --- Catch ' --- CATCH: Maneja errores generales de ejecución o de SQL/JSON ---
' Si ocurre una excepción inesperada durante el procesamiento de la petición.
Log(LastException) ' Registra la excepción completa en el log. Log(LastException) ' Registra la excepción completa en el log.
Main.LogServerError("ERROR", "DBHandlerJSON.Handle", LastException.Message, finalDbKey, queryNameForLog, req.RemoteAddress) ' <-- Nuevo Log
SendErrorResponse(resp, 500, LastException.Message) ' Envía un error 500 al cliente. SendErrorResponse(resp, 500, LastException.Message) ' Envía un error 500 al cliente.
queryNameForLog = "error_processing_json" ' Para registrar que hubo un error en el log. queryNameForLog = "error_processing_json" ' Para registrar que hubo un error en el log.
End Try ' --- FIN: Bloque Try principal --- End Try ' --- FIN: Bloque Try principal ---

View File

@@ -11,13 +11,19 @@ DriverClass=oracle.jdbc.driver.OracleDriver
#GOHAN ---> server #GOHAN ---> server
#JdbcUrl=jdbc:oracle:thin:@//10.0.0.205:1521/DBKMT #JdbcUrl=jdbc:oracle:thin:@//10.0.0.205:1521/DBKMT
#JdbcUrl=jdbc:oracle:thin:@//10.0.0.236:1521/DBKMT #JdbcUrl=jdbc:oracle:thin:@//10.0.0.236:1521/DBKMT
JdbcUrl=jdbc:oracle:thin:@//192.168.101.13:1521/DBKMT?v$session.program=jRDC_Multi JdbcUrl=jdbc:oracle:thin:@//192.168.101.13:1521/DBKMT?v$session.program=jRDC_MultiSALMA
# Configuración del pool de conexiones para DB2 # Configuración del pool de conexiones
InitialPoolSize=3 InitialPoolSize=3
MinPoolSize=2 MinPoolSize=2
MaxPoolSize=10 MaxPoolSize=15
AcquireIncrement=5 AcquireIncrement=2
# Configuración de tolerancia de parámetros:
# 1 = Habilita la tolerancia a parámetros de más (se recortarán los excesivos).
# 0 = Deshabilita la tolerancia (el servidor será estricto y lanzará un error si hay parámetros de más).
# Por defecto, si no se especifica o el valor es diferente de 1, la tolerancia estará DESHABILITADA (modo estricto).
parameterTolerance=1
# SVR-KEYMON-PRODUCCION--> Usuario # SVR-KEYMON-PRODUCCION--> Usuario
User=SALMA User=SALMA
@@ -50,7 +56,7 @@ Debug=true
sql.traeConexion=select 'DB2' as conexion from dual sql.traeConexion=select 'DB2' as conexion from dual
sql.select_soporte=select * from GUNA.soporte sql.select_soporte=select * from GUNA.soporte
sql.select_conexion=SELECT 'OK' AS VALOR FROM DUAL sql.select_conexion=SELECT 'OK' AS VALOR FROM DUAL
sql.traeConexion4=SELECT (?) AS p1, (?) AS p2, (?) AS p3 FROM DUAL sql.traeConexion4=SELECT 'DB2' as db, (?) AS p1, (?) AS p2, (?) AS p3 FROM DUAL
sql.select_version=select cat_ve_version from cat_version sql.select_version=select cat_ve_version from cat_version
sql.select_version_GV2=select cat_ve_version from GUNA.cat_version sql.select_version_GV2=select cat_ve_version from GUNA.cat_version
sql.selectAlmacen=select * from cat_almacen where cat_al_id = ? sql.selectAlmacen=select * from cat_almacen where cat_al_id = ?

View File

@@ -11,20 +11,21 @@ DriverClass=oracle.jdbc.driver.OracleDriver
#GOHAN ---> server #GOHAN ---> server
#JdbcUrl=jdbc:oracle:thin:@//10.0.0.205:1521/DBKMT #JdbcUrl=jdbc:oracle:thin:@//10.0.0.205:1521/DBKMT
#JdbcUrl=jdbc:oracle:thin:@//10.0.0.236:1521/DBKMT #JdbcUrl=jdbc:oracle:thin:@//10.0.0.236:1521/DBKMT
JdbcUrl=jdbc:oracle:thin:@//192.168.101.12:1521/DBKMT?v$session.program=jRDC_Multi JdbcUrl=jdbc:oracle:thin:@//192.168.101.13:1521/DBKMT?v$session.program=jRDC_MultiSALMA
# Configuración del pool de conexiones para DB2 # Configuración del pool de conexiones
InitialPoolSize=3 InitialPoolSize=3
MinPoolSize=2 MinPoolSize=2
MaxPoolSize=10 MaxPoolSize=15
AcquireIncrement=5 AcquireIncrement=2
# SVR-KEYMON-PRODUCCION--> Usuario # SVR-KEYMON-PRODUCCION--> Usuario
#User=GUNA User=SALMA
#Password=GUNAD2015M Password=SALMAD2016M
#User=TORRADOCONAUTO
#Password=TORRADOCONAUTOD2016M
User=TORRADOCONAUTO
Password=TORRADOCONAUTOD2016M
#--> Puertos #--> Puertos
#SAC - DFR - MDA / GOHAN -->COBRANZA #SAC - DFR - MDA / GOHAN -->COBRANZA
@@ -46,8 +47,19 @@ Debug=true
################# #################
################## ##################
sql.traeConexion=select 'DB3' as conexion from dual sql.traeConexion=select 'DB2' as conexion from dual
sql.select_soporte=select * from GUNA.soporte sql.select_soporte=select * from GUNA.soporte
sql.select_conexion=SELECT 'OK' AS VALOR FROM DUAL
sql.traeConexion4=SELECT 'DB2' as db, (?) AS p1, (?) AS p2, (?) AS p3 FROM DUAL
sql.select_version=select cat_ve_version from cat_version
sql.select_version_GV2=select cat_ve_version from GUNA.cat_version
sql.selectAlmacen=select * from cat_almacen where cat_al_id = ?
sql.sv=select * from cat_rutas where CAT_RU_RUTA = ?
sql.verify_device=select * from kelloggs.GUIDs where almacen = ? and ruta = ?
sql.registarMovil=insert into kelloggs.GUIDs (almacen, ruta, guid, estatus) values (?, ?, ?, 'ok')
sql.update_usuario_guna_nobajas=UPDATE GUNA.CAT_LOGINS SET CAT_LO_ESTATUS = 'Activo',CAT_LO_CONECTADO ='0' WHERE CAT_LO_ESTATUS != 'Baja' and CAT_LO_USUARIO = (?)
sql.proc_usuario=BEGIN EXECUTE IMMEDIATE ('DECLARE Cursor_SYS Sys_Refcursor; BEGIN SP_ACTIVAR_USUARIO( '''||(?)||''',Cursor_SYS); end;'); END;
sql.select_almacenes_KELL=select CAT_AG_ID, CAT_AG_NOMBRE from KELLOGGS.cat_agencias order by CAT_AG_NOMBRE sql.select_almacenes_KELL=select CAT_AG_ID, CAT_AG_NOMBRE from KELLOGGS.cat_agencias order by CAT_AG_NOMBRE
sql.select_almacenes_GUNA=select CAT_AG_ID, CAT_AG_NOMBRE from GUNA.cat_agencias order by CAT_AG_NOMBRE sql.select_almacenes_GUNA=select CAT_AG_ID, CAT_AG_NOMBRE from GUNA.cat_agencias order by CAT_AG_NOMBRE

View File

@@ -11,13 +11,13 @@ DriverClass=oracle.jdbc.driver.OracleDriver
#GOHAN ---> server #GOHAN ---> server
#JdbcUrl=jdbc:oracle:thin:@//10.0.0.205:1521/DBKMT #JdbcUrl=jdbc:oracle:thin:@//10.0.0.205:1521/DBKMT
#JdbcUrl=jdbc:oracle:thin:@//10.0.0.236:1521/DBKMT #JdbcUrl=jdbc:oracle:thin:@//10.0.0.236:1521/DBKMT
JdbcUrl=jdbc:oracle:thin:@//192.168.101.13:1521/DBKMT?v$session.program=jRDC_Multi JdbcUrl=jdbc:oracle:thin:@//192.168.101.13:1521/DBKMT?v$session.program=jRDC_MultiSALMA
# Configuración del pool de conexiones para DB2 # Configuración del pool de conexiones
InitialPoolSize=3 InitialPoolSize=3
MinPoolSize=2 MinPoolSize=2
MaxPoolSize=10 MaxPoolSize=15
AcquireIncrement=5 AcquireIncrement=2
# SVR-KEYMON-PRODUCCION--> Usuario # SVR-KEYMON-PRODUCCION--> Usuario
User=SALMA User=SALMA
@@ -31,7 +31,7 @@ Password=SALMAD2016M
#SAC - DFR - MDA / GOHAN -->COBRANZA #SAC - DFR - MDA / GOHAN -->COBRANZA
#ServerPort=1783 #ServerPort=1783
#GUNA - SALMA - DURAKELO - DBC / SVR-KEYMON-PRODUCCION --> DISTRIBUIDORAS #GUNA - SALMA - DURAKELO - DBC / SVR-KEYMON-PRODUCCION --> DISTRIBUIDORAS
ServerPort=9000 ServerPort=9010
#CMG - TORRADO / TRUNKS -->COBRANZA/ GM #CMG - TORRADO / TRUNKS -->COBRANZA/ GM
#ServerPort=1781 #ServerPort=1781
@@ -47,9 +47,19 @@ Debug=true
################# #################
################## ##################
sql.traeConexion=select 'DB4' as conexion from dual sql.traeConexion=select 'DB2' as conexion from dual
sql.select_soporte=select * from GUNA.soporte sql.select_soporte=select * from GUNA.soporte
sql.select_conexion=SELECT 'OK' AS VALOR FROM DUAL sql.select_conexion=SELECT 'OK' AS VALOR FROM DUAL
sql.traeConexion4=SELECT 'DB2' as db, (?) AS p1, (?) AS p2, (?) AS p3 FROM DUAL
sql.select_version=select cat_ve_version from cat_version
sql.select_version_GV2=select cat_ve_version from GUNA.cat_version
sql.selectAlmacen=select * from cat_almacen where cat_al_id = ?
sql.sv=select * from cat_rutas where CAT_RU_RUTA = ?
sql.verify_device=select * from kelloggs.GUIDs where almacen = ? and ruta = ?
sql.registarMovil=insert into kelloggs.GUIDs (almacen, ruta, guid, estatus) values (?, ?, ?, 'ok')
sql.update_usuario_guna_nobajas=UPDATE GUNA.CAT_LOGINS SET CAT_LO_ESTATUS = 'Activo',CAT_LO_CONECTADO ='0' WHERE CAT_LO_ESTATUS != 'Baja' and CAT_LO_USUARIO = (?)
sql.proc_usuario=BEGIN EXECUTE IMMEDIATE ('DECLARE Cursor_SYS Sys_Refcursor; BEGIN SP_ACTIVAR_USUARIO( '''||(?)||''',Cursor_SYS); end;'); END;
sql.select_almacenes_KELL=select CAT_AG_ID, CAT_AG_NOMBRE from KELLOGGS.cat_agencias order by CAT_AG_NOMBRE sql.select_almacenes_KELL=select CAT_AG_ID, CAT_AG_NOMBRE from KELLOGGS.cat_agencias order by CAT_AG_NOMBRE
sql.select_almacenes_GUNA=select CAT_AG_ID, CAT_AG_NOMBRE from GUNA.cat_agencias order by CAT_AG_NOMBRE sql.select_almacenes_GUNA=select CAT_AG_ID, CAT_AG_NOMBRE from GUNA.cat_agencias order by CAT_AG_NOMBRE

View File

@@ -13,11 +13,17 @@ DriverClass=oracle.jdbc.driver.OracleDriver
#JdbcUrl=jdbc:oracle:thin:@//10.0.0.236:1521/DBKMT #JdbcUrl=jdbc:oracle:thin:@//10.0.0.236:1521/DBKMT
JdbcUrl=jdbc:oracle:thin:@//192.168.101.10:1521/DBKMT?v$session.program=jRDC_Multi JdbcUrl=jdbc:oracle:thin:@//192.168.101.10:1521/DBKMT?v$session.program=jRDC_Multi
# Configuración del pool de conexiones para DB1 # Configuración del pool de conexiones
InitialPoolSize=3 InitialPoolSize=3
MinPoolSize=2 MinPoolSize=2
MaxPoolSize=10 MaxPoolSize=15
AcquireIncrement=5 AcquireIncrement=2
# Configuración de tolerancia de parámetros:
# 1 = Habilita la tolerancia a parámetros de más (se recortarán los excesivos).
# 0 = Deshabilita la tolerancia (el servidor será estricto y lanzará un error si hay parámetros de más).
# Por defecto, si no se especifica o el valor es diferente de 1, la tolerancia estará DESHABILITADA (modo estricto).
parameterTolerance=1
# SVR-KEYMON-PRODUCCION--> Usuario # SVR-KEYMON-PRODUCCION--> Usuario
User=GUNA User=GUNA
@@ -52,7 +58,7 @@ sql.select_revisaClienteCredito_GUNA2=select (select count(CAT_CL_CODIGO) from G
sql.traeConexion=select 'DB1' as conexion from dual sql.traeConexion=select 'DB1' as conexion from dual
sql.traeConexion2=select 'DB1' as conexion from dual sql.traeConexion2=select 'DB1' as conexion from dual
sql.traeConexion3=select '1' as par1, 2 as par2 from dual sql.traeConexion3=select '1' as par1, 2 as par2 from dual
sql.traeConexion4=SELECT (?) AS p1, (?) AS p2, (?) AS p3 FROM DUAL sql.traeConexion4=SELECT 'DB1' as db, (?) AS p1, (?) AS p2, (?) AS p3 FROM DUAL
sql.select_soporte=select * from GUNA.soporte sql.select_soporte=select * from GUNA.soporte
sql.select_conexion=SELECT 'OK' AS VALOR FROM DUAL sql.select_conexion=SELECT 'OK' AS VALOR FROM DUAL
sql.selectAlmacen=select cat_al_id, cat_al_desc, cat_al_archftp from cat_almacen where cat_al_id = ? sql.selectAlmacen=select cat_al_id, cat_al_desc, cat_al_archftp from cat_almacen where cat_al_id = ?

View File

@@ -380,112 +380,3 @@ Sub Handle(req As ServletRequest, resp As ServletResponse)
If GlobalParameters.mpLogs.IsInitialized Then GlobalParameters.mpLogs.Put(Command, "Manager : " & Command & " - Time : " & DateTime.Time(DateTime.Now)) If GlobalParameters.mpLogs.IsInitialized Then GlobalParameters.mpLogs.Put(Command, "Manager : " & Command & " - Time : " & DateTime.Time(DateTime.Now))
End Sub End Sub
Sub Handle0(req As ServletRequest, resp As ServletResponse)
' 1. --- Bloque de Seguridad (se mantiene igual) ---
If req.GetSession.GetAttribute2("user_is_authorized", False) = False Then
resp.SendRedirect("/login")
Return
End If
Dim Command As String = req.GetParameter("command")
If Command = "" Then Command = "ping"
Log($"Command: ${Command}"$)
resp.ContentType = "text/html"
' 2. --- Construimos la ESTRUCTURA de la página ---
Dim sb As StringBuilder
sb.Initialize
' Estilos para la página
sb.Append("<html><head><style>")
sb.Append("body {font-family: sans-serif; margin: 2em; background-color: #f9f9f9;} ")
sb.Append("h1, h2 {color: #333;} hr {margin: 2em 0; border: 0; border-top: 1px solid #ddd;} ")
sb.Append("form {background: #f0f0f0; padding: 1.5em; border-radius: 8px; max-width: 400px; margin-bottom: 2em;} ")
sb.Append("input {display: block; width: 95%; padding: 8px; margin-bottom: 10px; border: 1px solid #ccc; border-radius: 4px;} ")
sb.Append("button {padding: 10px 15px; border: none; background-color: #007bff; color: white; cursor: pointer; border-radius: 4px;} ")
sb.Append(".nav a, .logout a {text-decoration: none; margin-right: 10px; color: #007bff;} ")
sb.Append(".output {background: #fff; padding: 1em; border: 1px solid #eee; border-radius: 8px;} ")
sb.Append("</style></head><body>")
' Cabecera y bienvenida
sb.Append("<h1>Panel de Administración jRDC</h1>")
sb.Append($"Bienvenido, <b>${req.GetSession.GetAttribute("username")}</b><br>"$)
' Menú de navegación (se define una sola vez)
sb.Append("<p class='nav'>")
sb.Append($"<a href="/test">Test</a> | <a href="/manager?command=ping">Ping</a> | <a href="/manager?command=reload">Reload</a> | <a href="/manager?command=rpm2">Reiniciar (pm2)</a> | <a href="/manager?command=reviveBow">Revive Bow</a>"$)
sb.Append("</p>")
' Formulario para cambiar contraseña
sb.Append("<hr>")
sb.Append("<h2>Cambiar Contraseña</h2>")
sb.Append("<form action='/changepass' method='post'>")
sb.Append("Contraseña Actual: <input type='password' name='current_password' required><br>")
sb.Append("Nueva Contraseña: <input type='password' name='new_password' required><br>")
sb.Append("Confirmar Nueva Contraseña: <input type='password' name='confirm_password' required><br>")
sb.Append("<button type='submit'>Actualizar Contraseña</button>")
sb.Append("</form>")
' Sección para el resultado del comando
sb.Append("<hr><h2>Resultado del Comando: '" & Command & "'</h2>")
sb.Append("<div class='output'>")
' 3. --- Lógica de TUS COMANDOS (modificada para usar sb.Append) ---
If Command = "reload" Then
Private estaDB As String = ""
For i = 0 To Main.listaDeCP.Size - 1
Main.Connectors.Get(Main.listaDeCP.get(i)).As(RDCConnector).Initialize(Main.listaDeCP.get(i))
If Main.listaDeCP.get(i) <> "DB1" Then estaDB = "." & Main.listaDeCP.get(i) Else estaDB = ""
sb.Append($"Recargando config${estaDB}.properties ($DateTime{DateTime.Now})<br/>"$)
sb.Append($"Queries en config.properties: <b>${Main.Connectors.Get(Main.listaDeCP.get(i)).As(RDCConnector).commands.Size}</b><br/>"$)
sb.Append($"<b>JdbcUrl:</b> ${Main.Connectors.Get(Main.listaDeCP.get(i)).As(RDCConnector).config.Get("JdbcUrl")}</b><br/>"$)
sb.Append($"<b>User:</b> ${Main.Connectors.Get(Main.listaDeCP.get(i)).As(RDCConnector).config.Get("User")}</b><br/>"$)
sb.Append($"<b>ServerPort:</b> ${Main.srvr.Port}</b><br/><br/>"$)
Next
else If Command = "stop" Then
' Tu código para "stop"
else If Command = "rsx" Then
Log($"Ejecutamos ${File.DirApp}\start.bat"$)
sb.Append($"Ejecutamos ${File.DirApp}\start.bat"$)
Public shl As Shell
shl.Initialize("shl","cmd",Array("/c",File.DirApp & "\start.bat " & Main.srvr.Port))
shl.WorkingDirectory = File.DirApp
shl.Run(-1)
else If Command = "rpm2" Then
Log($"Ejecutamos ${File.DirApp}\reiniciaProcesoPM2.bat"$)
sb.Append($"Ejecutamos ${File.DirApp}\reiniciaProcesoPM2.bat"$)
Public shl As Shell
shl.Initialize("shl","cmd",Array("/c",File.DirApp & "\reiniciaProcesoPM2.bat " & Main.srvr.Port))
shl.WorkingDirectory = File.DirApp
shl.Run(-1)
else If Command = "reviveBow" Then
Log($"Ejecutamos ${File.DirApp}\reiniciaProcesoBow.bat"$)
sb.Append($"Ejecutamos ${File.DirApp}\reiniciaProcesoBow.bat<br><br>"$)
sb.Append($"!!!BOW REINICIANDO!!!"$)
Public shl As Shell
shl.Initialize("shl","cmd",Array("/c",File.DirApp & "\reiniciaProcesoBow.bat " & Main.srvr.Port))
shl.WorkingDirectory = File.DirApp
shl.Run(-1)
else if Command = "totalrequests" Then
If GlobalParameters.mpTotalRequests.IsInitialized Then
j.Initialize(GlobalParameters.mpTotalRequests)
sb.Append(j.ToString)
End If
else if Command = "ping" Then
sb.Append($"Pong ($DateTime{DateTime.Now})"$)
End If
'...(aquí continuaría el resto de tus Else If)...
' 4. --- Cerramos la página y la enviamos TODA JUNTA ---
sb.Append("</div>") ' Cierre de div.output
sb.Append("<p class='logout'><a href='/logout'>Cerrar Sesión</a></p>")
sb.Append("</body></html>")
resp.Write(sb.ToString) ' Se envía toda la página de una vez
' Lógica final de logs (se mantiene igual)
If GlobalParameters.mpLogs.IsInitialized Then GlobalParameters.mpLogs.Put(Command, "Manager : " & Command & " - Time : " & DateTime.Time(DateTime.Now))
End Sub

View File

@@ -0,0 +1,69 @@
B4J=true
Group=Default Group
ModulesStructureVersion=1
Type=StaticCode
Version=10.3
@EndOfDesignText@
' Archivo: ParameterValidationUtils.bas
' Módulo de utilidad: ParameterValidationUtils
' Centraliza la lógica de validación y ajuste de parámetros SQL.
' Ahora soporta recorte de parámetros excesivos.
Sub Process_Globals
' El Type ParameterValidationResult está declarado en Main.bas, no se declara aquí.
End Sub
' Valida y ajusta la lista de parámetros para la ejecución SQL, aplicando la lógica de tolerancia.
' Retorna un ParameterValidationResult indicando éxito/error y los parámetros a usar.
Public Sub ValidateAndAdjustParameters (CommandName As String, DBKey As String, sqlCommand As String, receivedParams As List, IsToleranceEnabled As Boolean) As ParameterValidationResult
Dim res As ParameterValidationResult
res.Initialize
res.Success = True ' Asumimos éxito inicialmente
Log(">>>> IsToleranceEnabled: " & IsToleranceEnabled)
' Aseguramos que receivedParams esté inicializada, incluso si está vacía o Null
If receivedParams = Null Or receivedParams.IsInitialized = False Then
receivedParams.Initialize ' Inicializa una lista vacía si es Null o no inicializada.
End If
' Contar cuántos '?' hay en la sentencia SQL para saber cuántos parámetros se esperan.
Dim expectedParams As Int = sqlCommand.Length - sqlCommand.Replace("?", "").Length
Dim receivedParamsSize As Int = receivedParams.Size
If receivedParamsSize < expectedParams Then
' Caso 1: Se recibieron MENOS parámetros de los esperados. Esto es un error.
res.Success = False
res.ErrorMessage = $"ERROR: Número de parámetros insuficiente para "${CommandName}" (DB: ${DBKey}). Se esperaban ${expectedParams} y se recibieron ${receivedParamsSize}."$
Log(res.ErrorMessage)
Main.LogServerError("ERROR", "ParameterValidationUtils.ValidateAndAdjustParameters", res.ErrorMessage, DBKey, CommandName, Null) ' <-- Nuevo Log
Return res
Else If receivedParamsSize > expectedParams Then
' Caso 2: Se recibieron MÁS parámetros de los esperados.
If IsToleranceEnabled Then ' Solo recortamos si la tolerancia está habilitada
Dim adjustedParams As List
adjustedParams.Initialize
For i = 0 To expectedParams - 1
adjustedParams.Add(receivedParams.Get(i))
Next
res.ParamsToExecute = adjustedParams
res.Success = True
Dim WarningMsg As String = $"ADVERTENCIA: Se recibieron más parámetros de los esperados para "${CommandName}" (DB: ${DBKey}). Se esperaban ${expectedParams} y se recibieron ${receivedParamsSize}. Se ajustó la lista de parámetros a ${expectedParams} elementos."$
Log(WarningMsg)
Main.LogServerError("ADVERTENCIA", "ParameterValidationUtils.ValidateAndAdjustParameters", WarningMsg, DBKey, CommandName, Null) ' <-- Nuevo Log [6]
Else
' Si la tolerancia NO está habilitada, esto es un error crítico.
res.Success = False
res.ErrorMessage = $"ERROR: Número de parámetros excesivo para "${CommandName}" (DB: ${DBKey}). Se esperaban ${expectedParams} y se recibieron ${receivedParamsSize}. La tolerancia a parámetros de más está DESHABILITADA."$
Log(res.ErrorMessage)
Main.LogServerError("ERROR", "ParameterValidationUtils.ValidateAndAdjustParameters", res.ErrorMessage, DBKey, CommandName, Null)
Return res
End If
Else
' Caso 3: Se recibieron el número EXACTO de parámetros. Todo bien.
res.ParamsToExecute = receivedParams ' Usamos la lista original tal cual.
res.Success = True ' Confirmamos éxito.
End If
Return res
End Sub

View File

@@ -31,6 +31,9 @@ Sub Class_Globals
' Almacena la configuración completa (DriverClass, JdbcUrl, User, Password, InitialPoolSize, etc.) ' Almacena la configuración completa (DriverClass, JdbcUrl, User, Password, InitialPoolSize, etc.)
' cargada de su respectivo archivo .properties. ' cargada de su respectivo archivo .properties.
Public config As Map Public config As Map
' Indica si la tolerancia a parámetros de más está activa.
Public IsParameterToleranceEnabled As Boolean
End Sub End Sub
' Subrutina de inicialización para el conector de una base de datos específica. ' Subrutina de inicialización para el conector de una base de datos específica.
@@ -45,6 +48,16 @@ Public Sub Initialize(DB As String)
' para que la configuración cargada del archivo sea persistente para esta instancia del conector. ' para que la configuración cargada del archivo sea persistente para esta instancia del conector.
config = LoadConfigMap(DB) config = LoadConfigMap(DB)
' Leer la configuración de tolerancia de parámetros
Dim toleranceSetting As Int = config.GetDefault("parameterTolerance", 0).As(Int) ' Por defecto, 0 (estricto)
IsParameterToleranceEnabled = (toleranceSetting = 1) ' La tolerancia se habilita si el valor es 1
If IsParameterToleranceEnabled Then
Log($"RDCConnector.Initialize para ${DB}: Tolerancia a parámetros de más, HABILITADA."$)
Else
Log($"RDCConnector.Initialize para ${DB}: Tolerancia a parámetros de más, DESHABILITADA (modo estricto)."$)
End If
' Bloque Try-Catch para la inicialización y configuración del pool. ' Bloque Try-Catch para la inicialización y configuración del pool.
' Esto es esencial para capturar cualquier error crítico que impida la conexión inicial a la base de datos. ' Esto es esencial para capturar cualquier error crítico que impida la conexión inicial a la base de datos.
Try Try
@@ -78,7 +91,7 @@ Public Sub Initialize(DB As String)
' LÍNEAS CRÍTICAS PARA FORZAR UN COMPORTAMIENTO NO SILENCIOSO DE C3P0: ' LÍNEAS CRÍTICAS PARA FORZAR UN COMPORTAMIENTO NO SILENCIOSO DE C3P0:
' Por defecto, C3P0 puede reintentar muchas veces y no lanzar una excepción si las conexiones iniciales fallan. ' Por defecto, C3P0 puede reintentar muchas veces y no lanzar una excepción si las conexiones iniciales fallan.
' Estas líneas fuerzan a C3P0 a ser estricto y reportar errores de inmediato. ' Estas líneas fuerzan a C3P0 a ser estricto y reportar errores de inmediato.
jo.RunMethod("setAcquireRetryAttempts", Array As Object(1)) ' Limita los intentos iniciales de adquisición a 1. jo.RunMethod("setAcquireRetryAttempts", Array As Object(2)) ' Limita los intentos iniciales de adquisición a 1.
jo.RunMethod("setBreakAfterAcquireFailure", Array As Object(True)) ' ¡Forza a C3P0 a lanzar una excepción si falla al adquirir conexiones! jo.RunMethod("setBreakAfterAcquireFailure", Array As Object(True)) ' ¡Forza a C3P0 a lanzar una excepción si falla al adquirir conexiones!
' PASO 4: Forzar la creación de conexiones iniciales y verificar el estado. ' PASO 4: Forzar la creación de conexiones iniciales y verificar el estado.
@@ -138,7 +151,9 @@ Public Sub Initialize(DB As String)
Catch Catch
' Si ocurre un error durante la inicialización del pool o al forzar la conexión, ' Si ocurre un error durante la inicialización del pool o al forzar la conexión,
' este Log es CRÍTICO para el diagnóstico, especialmente en un entorno de producción. ' este Log es CRÍTICO para el diagnóstico, especialmente en un entorno de producción.
Log($"RDCConnector.Initialize para ${DB}: ERROR CRÍTICO al inicializar/forzar conexión: ${LastException.Message}"$) Dim ErrorMsg As String = $"RDCConnector.Initialize para ${DB}: ERROR CRÍTICO al inicializar/forzar conexión: ${LastException.Message}"$
Log(ErrorMsg)
Main.LogServerError("ERROR", "RDCConnector.Initialize", ErrorMsg, DB, Null, Null)
End Try End Try
' Configuración de depuración de queries. Se activa automáticamente si el proyecto se ejecuta en modo DEBUG. ' Configuración de depuración de queries. Se activa automáticamente si el proyecto se ejecuta en modo DEBUG.
@@ -178,7 +193,9 @@ Public Sub GetCommand(DB As String, Key As String) As String
' Obtiene los comandos de la DB específica del mapa global Main.commandsMap. ' Obtiene los comandos de la DB específica del mapa global Main.commandsMap.
commands = Main.commandsMap.Get(DB).As(Map) commands = Main.commandsMap.Get(DB).As(Map)
If commands.ContainsKey("sql." & Key) = False Then If commands.ContainsKey("sql." & Key) = False Then
Log($"RDCConnector.GetCommand: *** Comando no encontrado: '${Key}' para DB: '${DB}' ***"$) ' Log importante si un comando no se encuentra. Dim ErrorMsg As String = $"RDCConnector.GetCommand: *** Comando no encontrado: '${Key}' para DB: '${DB}' ***"$
Log(ErrorMsg)
Main.LogServerError("ERROR", "RDCConnector.GetCommand", ErrorMsg, DB, Key, Null) ' Log importante si un comando no se encuentra.
End If End If
Return commands.Get("sql." & Key) ' Retorna la sentencia SQL. Return commands.Get("sql." & Key) ' Retorna la sentencia SQL.
End Sub End Sub
@@ -285,12 +302,16 @@ Public Sub GetPoolStats As Map
Catch Catch
' Si ocurre un error al obtener las estadísticas, se registra y se añade un mensaje de error al mapa. ' Si ocurre un error al obtener las estadísticas, se registra y se añade un mensaje de error al mapa.
Log("RDCConnector.GetPoolStats: ERROR CRÍTICO al obtener estadísticas del pool: " & LastException.Message) Dim ErrorMsg As String = "RDCConnector.GetPoolStats: ERROR CRÍTICO al obtener estadísticas del pool: " & LastException.Message
Log(ErrorMsg)
Main.LogServerError("ERROR", "RDCConnector.GetPoolStats", ErrorMsg, "Todas", Null, Null) ' <-- Nuevo Log
stats.Put("Error", LastException.Message) stats.Put("Error", LastException.Message)
End Try End Try
Else Else
' Si el pool no está inicializado, se registra una advertencia y se devuelve un mapa con un error. ' Si el pool no está inicializado, se registra una advertencia y se devuelve un mapa con un error.
Log("RDCConnector.GetPoolStats: ADVERTENCIA: Pool NO está inicializado. Retornando mapa con error.") Dim WarningMsg As String = "RDCConnector.GetPoolStats: ADVERTENCIA: Pool NO está inicializado. Retornando mapa con error."
Log(WarningMsg)
Main.LogServerError("ADVERTENCIA", "RDCConnector.GetPoolStats", WarningMsg, "Todas", Null, Null) ' <-- Nuevo Log
stats.Put("Error", "Pool de conexiones no inicializado para esta DB.") stats.Put("Error", "Pool de conexiones no inicializado para esta DB.")
End If End If

View File

@@ -22,7 +22,7 @@ Sub Handle(req As ServletRequest, resp As ServletResponse)
' Dim con As SQL = Main.rdcConnectorDB1.GetConnection("") ' Dim con As SQL = Main.rdcConnectorDB1.GetConnection("")
Dim con As SQL = Main.Connectors.Get("DB1").As(RDCConnector).GetConnection("") Dim con As SQL = Main.Connectors.Get("DB1").As(RDCConnector).GetConnection("")
resp.Write("Connection successful.</br></br>") resp.Write("Connection successful.</br></br>")
Private estaDB As String = "" Dim estaDB As String = ""
Log(Main.listaDeCP) Log(Main.listaDeCP)
For i = 0 To Main.listaDeCP.Size - 1 For i = 0 To Main.listaDeCP.Size - 1
If Main.listaDeCP.get(i) <> "" Then estaDB = "." & Main.listaDeCP.get(i) If Main.listaDeCP.get(i) <> "" Then estaDB = "." & Main.listaDeCP.get(i)

View File

@@ -32,9 +32,10 @@ Library8=jsql
Library9=bcrypt Library9=bcrypt
Module1=Cambios Module1=Cambios
Module10=Manager Module10=Manager
Module11=ping Module11=ParameterValidationUtils
Module12=RDCConnector Module12=ping
Module13=TestHandler Module13=RDCConnector
Module14=TestHandler
Module2=ChangePassHandler Module2=ChangePassHandler
Module3=DBHandlerB4X Module3=DBHandlerB4X
Module4=DBHandlerJSON Module4=DBHandlerJSON
@@ -45,7 +46,7 @@ Module8=LoginHandler
Module9=LogoutHandler Module9=LogoutHandler
NumberOfFiles=10 NumberOfFiles=10
NumberOfLibraries=9 NumberOfLibraries=9
NumberOfModules=13 NumberOfModules=14
Version=10.3 Version=10.3
@EndOfDesignText@ @EndOfDesignText@
'Non-UI application (console / server application) 'Non-UI application (console / server application)
@@ -53,7 +54,7 @@ Version=10.3
#Region Project Attributes #Region Project Attributes
#CommandLineArgs: #CommandLineArgs:
#MergeLibraries: True #MergeLibraries: True
' VERSION 5.09.15 ' VERSION 5.09.16
'########################################################################################################### '###########################################################################################################
'###################### PULL ############################################################# '###################### PULL #############################################################
'Ctrl + click ide://run?file=%WINDIR%\System32\cmd.exe&Args=/c&Args=git&Args=pull 'Ctrl + click ide://run?file=%WINDIR%\System32\cmd.exe&Args=/c&Args=git&Args=pull
@@ -107,11 +108,15 @@ Sub Process_Globals
' Objeto de bloqueo (ReentrantLock) para proteger el mapa Main.Connectors durante operaciones de recarga (Hot-Swap). ' Objeto de bloqueo (ReentrantLock) para proteger el mapa Main.Connectors durante operaciones de recarga (Hot-Swap).
Public MainConnectorsLock As JavaObject Public MainConnectorsLock As JavaObject
' Mapa para contar las peticiones activas por cada base de datos. Es thread-safe.
Public ActiveRequestsCountByDB As Map
' Timer para ejecutar tareas periódicas, como la limpieza de logs. ' Timer para ejecutar tareas periódicas, como la limpieza de logs.
Public timerLogs As Timer Public timerLogs As Timer
' Tipo para encapsular el resultado de la validación de parámetros.
Type ParameterValidationResult ( _
Success As Boolean, _
ErrorMessage As String, _
ParamsToExecute As List _ ' La lista de parámetros final a usar en la ejecución SQL
)
End Sub End Sub
Sub AppStart (Args() As String) Sub AppStart (Args() As String)
@@ -149,12 +154,20 @@ Sub AppStart (Args() As String)
' === 4. INICIALIZACIÓN DEL CONECTOR PARA LA BASE DE DATOS PRINCIPAL (DB1) === ' === 4. INICIALIZACIÓN DEL CONECTOR PARA LA BASE DE DATOS PRINCIPAL (DB1) ===
' DB1 siempre usa el archivo 'config.properties' por defecto. ' 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. Try
con1.Initialize("DB1") ' Inicializa la instancia del conector para "DB1". Dim con1 As RDCConnector ' Declara una variable específica y única para el conector de DB1.
Connectors.Put("DB1", con1) ' Asocia el identificador "DB1" con su instancia de RDCConnector. con1.Initialize("DB1") ' Inicializa la instancia del conector para "DB1".
srvr.Port = con1.serverPort ' El puerto del servidor HTTP se obtiene del config.properties de DB1. Connectors.Put("DB1", con1) ' Asocia el identificador "DB1" con su instancia de RDCConnector.
listaDeCP.Add("DB1") ' Añade "DB1" a la lista de bases de datos gestionadas. srvr.Port = con1.serverPort ' El puerto del servidor HTTP se obtiene del config.properties de DB1.
Log($"Main.AppStart: Conector 'DB1' inicializado exitosamente en puerto: ${srvr.Port}"$) 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}"$)
Catch
Dim ErrorMsg As String = $"Main.AppStart: ERROR CRÍTICO al inicializar conector 'DB1': ${LastException.Message}"$
Log(ErrorMsg)
LogServerError("ERROR", "Main.AppStart", ErrorMsg, "DB1", Null, Null)
' Si DB1 falla, el servidor no puede arrancar correctamente.
ExitApplication
End Try
' === 5. DETECCIÓN E INICIALIZACIÓN DE BASES DE DATOS ADICIONALES (DB2, DB3, DB4) === ' === 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) ' El servidor busca archivos de configuración adicionales (ej. config.DB2.properties)
@@ -164,27 +177,48 @@ Sub AppStart (Args() As String)
For i = 0 To cpFiles.Size - 1 For i = 0 To cpFiles.Size - 1
' Procesa 'config.DB2.properties' ' Procesa 'config.DB2.properties'
If cpFiles.Get(i) = "config.DB2.properties" Then If cpFiles.Get(i) = "config.DB2.properties" Then
Dim con2 As RDCConnector ' Declara una variable específica y única para el conector de DB2. Try
con2.Initialize("DB2") ' Inicializa la instancia del conector para "DB2". Dim con2 As RDCConnector
Connectors.Put("DB2", con2) ' Asocia "DB2" con su instancia de RDCConnector. con2.Initialize("DB2")
listaDeCP.Add("DB2") ' Añade "DB2" a la lista de bases de datos. Connectors.Put("DB2", con2)
Log("Main.AppStart: Conector 'DB2' inicializado exitosamente.") listaDeCP.Add("DB2")
Log("Main.AppStart: Conector 'DB2' inicializado exitosamente.")
Catch
Dim ErrorMsg As String = $"Main.AppStart: ERROR al inicializar conector 'DB2': ${LastException.Message}"$
Log(ErrorMsg)
LogServerError("ERROR", "Main.AppStart", ErrorMsg, "DB2", Null, Null)
' Si un conector secundario falla, el servidor puede continuar, pero es importante saberlo.
End Try
End If End If
' Procesa 'config.DB3.properties' ' Procesa 'config.DB3.properties'
If cpFiles.Get(i) = "config.DB3.properties" Then If cpFiles.Get(i) = "config.DB3.properties" Then
Dim con3 As RDCConnector ' Declara una variable específica y única para el conector de DB3. Try
con3.Initialize("DB3") ' Inicializa la instancia del conector para "DB3". Dim con3 As RDCConnector
Connectors.Put("DB3", con3) ' Asocia "DB3" con su instancia de RDCConnector. con3.Initialize("DB3")
listaDeCP.Add("DB3") ' Añade "DB3" a la lista de bases de datos. Connectors.Put("DB3", con3)
Log("Main.AppStart: Conector 'DB3' inicializado exitosamente.") listaDeCP.Add("DB3")
Log("Main.AppStart: Conector 'DB3' inicializado exitosamente.")
Catch
Dim ErrorMsg As String = $"Main.AppStart: ERROR al inicializar conector 'DB3': ${LastException.Message}"$
Log(ErrorMsg)
LogServerError("ERROR", "Main.AppStart", ErrorMsg, "DB3", Null, Null)
' Si un conector secundario falla, el servidor puede continuar, pero es importante saberlo.
End Try
End If End If
' Procesa 'config.DB4.properties' ' Procesa 'config.DB4.properties'
If cpFiles.Get(i) = "config.DB4.properties" Then If cpFiles.Get(i) = "config.DB4.properties" Then
Dim con4 As RDCConnector ' Declara una variable específica y única para el conector de DB4. Try
con4.Initialize("DB4") ' Inicializa la instancia del conector para "DB4". Dim con4 As RDCConnector
Connectors.Put("DB4", con4) ' Asocia "DB4" con su instancia de RDCConnector. con4.Initialize("DB4")
listaDeCP.Add("DB4") ' Añade "DB4" a la lista de bases de datos. Connectors.Put("DB4", con4)
Log("Main.AppStart: Conector 'DB4' inicializado exitosamente.") listaDeCP.Add("DB4")
Log("Main.AppStart: Conector 'DB4' inicializado exitosamente.")
Catch
Dim ErrorMsg As String = $"Main.AppStart: ERROR al inicializar conector 'DB4': ${LastException.Message}"$
Log(ErrorMsg)
LogServerError("ERROR", "Main.AppStart", ErrorMsg, "DB4", Null, Null)
' Si un conector secundario falla, el servidor puede continuar, pero es importante saberlo.
End Try
End If End If
Next Next
Else Else
@@ -261,6 +295,15 @@ Sub InitializeSQLiteDatabase
' Inserta el usuario por defecto en la tabla 'users'. ' Inserta el usuario por defecto en la tabla 'users'.
SQL1.ExecNonQuery2("INSERT INTO users (username, password_hash) VALUES (?, ?)", Array As Object(defaultUser, hashedPass)) SQL1.ExecNonQuery2("INSERT INTO users (username, password_hash) VALUES (?, ?)", Array As Object(defaultUser, hashedPass))
Log($"Usuario por defecto creado -> user: ${defaultUser}, pass: ${defaultPass}"$) Log($"Usuario por defecto creado -> user: ${defaultUser}, pass: ${defaultPass}"$)
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)
' >>> INICIO: Creación de la tabla errores con columnas de error/advertencia <<<
Log("Creando tabla 'errores' para registrar eventos.")
Dim createErrorsTable As String = "CREATE TABLE errores (id INTEGER PRIMARY KEY AUTOINCREMENT, timestamp INTEGER, type TEXT, source TEXT, message TEXT, db_key TEXT, command_name TEXT, client_ip TEXT)"
SQL1.ExecNonQuery(createErrorsTable)
' >>> FIN: Creación de la tabla errores <<<
Else Else
' Si el archivo de la base de datos ya existe, simplemente se abre. ' Si el archivo de la base de datos ya existe, simplemente se abre.
SQL1.InitializeSQLite(File.DirApp, dbFileName, True) SQL1.InitializeSQLite(File.DirApp, dbFileName, True)
@@ -311,6 +354,19 @@ Sub InitializeSQLiteDatabase
Log("Añadiendo columna 'handler_active_requests' a query_logs.") Log("Añadiendo columna 'handler_active_requests' a query_logs.")
SQL1.ExecNonQuery("ALTER TABLE query_logs ADD COLUMN handler_active_requests INTEGER DEFAULT 0") SQL1.ExecNonQuery("ALTER TABLE query_logs ADD COLUMN handler_active_requests INTEGER DEFAULT 0")
End If End If
' >>> INICIO: Lógica de migración para 'errores' si la DB ya existía <<<
Log("Verificando y migrando tabla 'errores' si es necesario.")
If SQL1.ExecQuerySingleResult("SELECT name FROM sqlite_master WHERE type='table' AND name='errores'") = Null Then
Log("Tabla 'errores' no encontrada, creándola.")
Dim createErrorsTable As String = "CREATE TABLE errores (id INTEGER PRIMARY KEY AUTOINCREMENT, timestamp INTEGER, type TEXT, source TEXT, message TEXT, db_key TEXT, command_name TEXT, client_ip TEXT)"
SQL1.ExecNonQuery(createErrorsTable)
Else
' Si la tabla ya existe, podrías añadir lógica para ALTER TABLE si se añaden nuevas columnas en el futuro.
' Por ahora, asumimos que la estructura inicial es suficiente.
Log("Tabla 'errores' ya existe.")
End If
' >>> FIN: Lógica de migración para 'errores' <<<
End If End If
' >>> FIN: Lógica de migración (ALTER TABLE) <<< ' >>> FIN: Lógica de migración (ALTER TABLE) <<<
End If End If
@@ -330,13 +386,33 @@ Public Sub LogQueryPerformance(QueryName As String, DurationMs As Long, DbKey As
End Try End Try
End Sub End Sub
' Subrutina para registrar errores y advertencias en la tabla 'errores'.
' Type: "ERROR" o "ADVERTENCIA"
' Source: Módulo.Subrutina donde ocurrió el evento (ej. "DBHandlerJSON.Handle")
' Message: El mensaje descriptivo del error/advertencia.
' DBKey: La clave de la base de datos involucrada (ej. "DB1", "DB2"), Null si no aplica.
' CommandName: El nombre del comando SQL (ej. "select_user"), Null si no aplica.
' ClientIp: La dirección IP del cliente que originó la petición, Null si no aplica.
Public Sub LogServerError(Type0 As String, Source As String, Message As String, DBKey As String, CommandName As String, ClientIp As String)
Try
SQL1.ExecNonQuery2("INSERT INTO errores (timestamp, type, source, message, db_key, command_name, client_ip) VALUES (?, ?, ?, ?, ?, ?, ?)", _
Array As Object(DateTime.Now, Type0, Source, Message, DBKey, CommandName, ClientIp))
Catch
Log("ERROR CRÍTICO: Fallo al guardar el log de error/advertencia en SQLite (Main.LogServerError): " & LastException.Message)
' En este punto, no podemos hacer mucho más que loggear el fallo en la consola,
' para evitar un bucle infinito de errores de logging.
End Try
End Sub
' Subrutina de evento para el Timer 'timerLogs'. ' Subrutina de evento para el Timer 'timerLogs'.
' Se ejecuta periódicamente (cada 10 minutos) para limpiar la tabla de logs. ' Se ejecuta periódicamente (cada 10 minutos) para limpiar la tabla de logs.
Sub TimerLogs_Tick Sub TimerLogs_Tick
Try Try
borraArribaDe15000Logs ' Llama a la función para limpiar los logs. borraArribaDe15000Logs ' Llama a la función para limpiar los logs.
Catch Catch
Log("ERROR en TimerLogs_Tick al intentar borrar logs: " & LastException.Message) Dim ErrorMsg As String = "ERROR en TimerLogs_Tick al intentar borrar logs: " & LastException.Message
Log(ErrorMsg)
LogServerError("ERROR", "Main.TimerLogs_Tick", ErrorMsg, Null, "log_cleanup", Null) ' <-- Nuevo Log
End Try End Try
End Sub End Sub

View File

@@ -4,6 +4,7 @@ ModuleBookmarks10=
ModuleBookmarks11= ModuleBookmarks11=
ModuleBookmarks12= ModuleBookmarks12=
ModuleBookmarks13= ModuleBookmarks13=
ModuleBookmarks14=
ModuleBookmarks2= ModuleBookmarks2=
ModuleBookmarks3= ModuleBookmarks3=
ModuleBookmarks4= ModuleBookmarks4=
@@ -18,6 +19,7 @@ ModuleBreakpoints10=
ModuleBreakpoints11= ModuleBreakpoints11=
ModuleBreakpoints12= ModuleBreakpoints12=
ModuleBreakpoints13= ModuleBreakpoints13=
ModuleBreakpoints14=
ModuleBreakpoints2= ModuleBreakpoints2=
ModuleBreakpoints3= ModuleBreakpoints3=
ModuleBreakpoints4= ModuleBreakpoints4=
@@ -32,14 +34,15 @@ ModuleClosedNodes10=
ModuleClosedNodes11= ModuleClosedNodes11=
ModuleClosedNodes12= ModuleClosedNodes12=
ModuleClosedNodes13= ModuleClosedNodes13=
ModuleClosedNodes14=
ModuleClosedNodes2= ModuleClosedNodes2=
ModuleClosedNodes3= ModuleClosedNodes3=9,10,11,12,13,14,15,16
ModuleClosedNodes4= ModuleClosedNodes4=
ModuleClosedNodes5= ModuleClosedNodes5=
ModuleClosedNodes6= ModuleClosedNodes6=
ModuleClosedNodes7= ModuleClosedNodes7=
ModuleClosedNodes8= ModuleClosedNodes8=
ModuleClosedNodes9= ModuleClosedNodes9=
NavigationStack=Main,Process_Globals,48,0,Manager,Class_Globals,9,0,Manager,Initialize,14,0,Manager,Handle0,463,0,Manager,Handle,157,4,faviconHandler,Handle,31,0,Main,AppStart,76,1,Main,TimerLogs_Tick,291,0,Main,LogQueryPerformance,281,0,Cambios,Process_Globals,41,0 NavigationStack=DBHandlerB4X,ExecuteBatch2,386,0,DBHandlerB4X,ExecuteBatch,449,0,DBHandlerB4X,ExecuteQuery,506,0,DBHandlerJSON,Handle,148,0,Main,Process_Globals,59,0,Manager,Handle,375,0,TestHandler,Handle,25,1,ChangePassHandler,Handle,17,0,Main,AppStart,157,0,Cambios,Process_Globals,13,6
SelectedBuild=0 SelectedBuild=0
VisibleModules=3,4,12,1,2,5,10,6 VisibleModules=3,4,13,1,10,11,14,2