- 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

@@ -17,7 +17,7 @@ Sub Class_Globals
' La siguiente sección de constantes y utilidades se compila condicionalmente
' 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.
#if VERSION1
' #if VERSION1
' 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 _
,T_DOUBLE = 6, T_BOOLEAN = 7, T_BLOB = 8 As Byte
@@ -25,7 +25,7 @@ Sub Class_Globals
Private bc As ByteConverter
' Utilidad para comprimir/descomprimir streams de datos (usado en V1).
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
' 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.
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}"$
Log(ErrorMsg)
SendPlainTextError(resp, 400, ErrorMsg) ' Envía una respuesta de error al cliente.
' No se llama a CleanupAndLog aquí, ya que el contador de peticiones no se ha incrementado
' y no se ha obtenido ninguna conexión del pool.
Return ' Termina la ejecución del handler.
Main.LogServerError("ERROR", "DBHandlerB4X.Handle", ErrorMsg, dbKey, Null, req.RemoteAddress) ' <-- Nuevo Log
SendPlainTextError(resp, 400, ErrorMsg)
Return
End If
' === 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)
Return ' Salida temprana si hay un error.
End If
#if VERSION1
' #if VERSION1
' Estas ramas se compilan solo si #if VERSION1 está activo (para protocolo antiguo).
Else if method = "query" Then
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)
Return
End If
#end if
' #end if
Else if method = "batch2" Then
' Ejecuta un lote de comandos (INSERT, UPDATE, DELETE) utilizando el protocolo V2.
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.
End If
Else
' Si el método solicitado no es reconocido, se registra un error y se envía una respuesta adecuada.
Log("Unknown method: " & method)
Dim ErrorMsg As String = "Unknown method: " & method
Log(ErrorMsg)
Main.LogServerError("ERROR", "DBHandlerB4X.Handle", ErrorMsg, dbKey, method, req.RemoteAddress) ' <-- Nuevo Log
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
CleanupAndLog(dbKey, q, duration, req.RemoteAddress, requestsBeforeDecrement, poolBusyConnectionsForLog, con)
Return ' Salida temprana.
Return
End If
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.
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.
q = "error_in_b4x_handler" ' Aseguramos un valor para 'q' en caso de excepción.
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
Dim errorMessage As String = $"El comando '${cmd.Name}' no fue encontrado en el config.properties de '${DB}'."$
Log(errorMessage)
Main.LogServerError("ERROR", "DBHandlerB4X.ExecuteQuery2", errorMessage, DB, cmd.Name, Null)
' Envía un error 400 (Bad Request) al cliente informando del problema.
SendPlainTextError(resp, 400, errorMessage)
Return "error" ' Retorna un texto para el log.
End If
' <<< FIN NUEVA VALIDACIÓN >>>
' --- INICIO VALIDACIÓN DE PARÁMETROS ---
' Comprueba si el SQL espera parámetros o si se recibieron parámetros.
If sqlCommand.Contains("?") Or (cmd.Parameters <> Null And cmd.Parameters.Length > 0) Then
' Cuenta cuántos '?' hay en la sentencia SQL para saber cuántos parámetros se esperan.
Dim expectedParams As Int = sqlCommand.Length - sqlCommand.Replace("?", "").Length
' Cuenta cuántos parámetros se recibieron.
Dim receivedParams As Int
If cmd.Parameters = Null Then receivedParams = 0 Else receivedParams = cmd.Parameters.Length
' 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
' <<< INICIO VALIDACIÓN DE PARÁMETROS CENTRALIZADA >>>
' Convertimos el array de Object() de cmd.Parameters a una List para la utilidad de validación.
Dim paramsAsList As List
paramsAsList.Initialize
If cmd.Parameters <> Null Then
For Each p As Object In cmd.Parameters
paramsAsList.Add(p)
Next
End If
' --- FIN VALIDACIÓN ---
' Ejecuta la consulta SQL con los parámetros proporcionados.
Dim rs As ResultSet = con.ExecQuery2(sqlCommand, cmd.Parameters)
Dim validationResult As ParameterValidationResult = ParameterValidationUtils.ValidateAndAdjustParameters(cmd.Name, DB, sqlCommand, paramsAsList, Connector.IsParameterToleranceEnabled)
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).
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.
Dim errorMessage As String = $"El comando '${cmd.Name}' no fue encontrado en el config.properties de '${DB}'."$
Log(errorMessage)
Main.LogServerError("ERROR", "DBHandlerB4X.ExecuteBatch2", errorMessage, DB, cmd.Name, Null)
SendPlainTextError(resp, 400, errorMessage)
Return "error"
End If
' <<< FIN NUEVA VALIDACIÓN >>>
' --- INICIO VALIDACIÓN DE PARÁMETROS DENTRO DEL BATCH ---
If sqlCommand.Contains("?") Or (cmd.Parameters <> Null And cmd.Parameters.Length > 0) Then
Dim expectedParams As Int = sqlCommand.Length - sqlCommand.Replace("?", "").Length
Dim receivedParams As Int
If cmd.Parameters = Null Then receivedParams = 0 Else receivedParams = cmd.Parameters.Length
' Si el número de parámetros no coincide, deshace la transacción y envía error.
If expectedParams <> receivedParams Then
con.Rollback
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
' <<< INICIO VALIDACIÓN DE PARÁMETROS CENTRALIZADA DENTRO DEL BATCH >>>
' Convertimos el array de Object() de cmd.Parameters a una List para la utilidad de validación.
Dim paramsAsList As List
paramsAsList.Initialize
If cmd.Parameters <> Null Then
For Each p As Object In cmd.Parameters
paramsAsList.Add(p)
Next
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
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.
con.Rollback ' Se deshacen todos los cambios hechos en la transacció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.
End Try
@@ -410,7 +413,7 @@ End Sub
' --- 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.
#if VERSION1
'#if VERSION1
' 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
@@ -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.
Dim errorMessage As String = $"El comando '${queryName}' no fue encontrado en el config.properties de '${DB}'."$
Log(errorMessage)
Main.LogServerError("ERROR", "DBHandlerB4X.ExecuteBatch (V1)", errorMessage, DB, queryName, Null)
SendPlainTextError(resp, 400, errorMessage)
Return "error"
End If
' <<< FIN NUEVA VALIDACIÓN >>>
' <<< FIN NUEVA VALIDACIÓN >>>
' --- INICIO VALIDACIÓN DE PARÁMETROS DENTRO DEL BATCH (V1) ---
If sqlCommand.Contains("?") Or (params <> Null And params.Size > 0) Then
Dim expectedParams As Int = sqlCommand.Length - sqlCommand.Replace("?", "").Length
Dim receivedParams As Int
If params = Null Then receivedParams = 0 Else receivedParams = params.Size
' <<< INICIO VALIDACIÓN DE PARÁMETROS CENTRALIZADA DENTRO DEL BATCH (V1) >>>
Dim validationResult As ParameterValidationResult = ParameterValidationUtils.ValidateAndAdjustParameters(queryName, DB, sqlCommand, params, Connector.IsParameterToleranceEnabled)
If expectedParams <> receivedParams Then
con.Rollback
Dim errorMessage As String = $"Número de parametros equivocado para "${queryName}". Se esperaban ${expectedParams} y se recibieron ${receivedParams}."$
Log(errorMessage)
SendPlainTextError(resp, 400, errorMessage)
Return "error"
End If
End If
' --- FIN VALIDACIÓN ---
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, 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
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
con.Rollback
Log(LastException)
Main.LogServerError("ERROR", "DBHandlerB4X.ExecuteBatch (V1)", LastException.Message, DB, "batch_execution_error_v1", Null)
SendPlainTextError(resp, 500, LastException.Message)
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
Dim errorMessage As String = $"El comando '${queryName}' no fue encontrado en el config.properties de '${DB}'."$
Log(errorMessage)
Main.LogServerError("ERROR", "DBHandlerB4X.ExecuteQuery (V1)", errorMessage, DB, queryName, Null)
SendPlainTextError(resp, 400, errorMessage)
Return "error"
End If
' <<< FIN NUEVA VALIDACIÓN >>>
' <<< FIN NUEVA VALIDACIÓN >>>
' --- INICIO VALIDACIÓN DE PARÁMETROS (V1) ---
If theSql.Contains("?") Or (params <> Null And params.Size > 0) Then
Dim expectedParams As Int = theSql.Length - theSql.Replace("?", "").Length
Dim receivedParams As Int
If params = Null Then receivedParams = 0 Else receivedParams = params.Size
' <<< INICIO VALIDACIÓN DE PARÁMETROS CENTRALIZADA (V1) >>>
Dim validationResult As ParameterValidationResult = ParameterValidationUtils.ValidateAndAdjustParameters(queryName, DB, theSql, params, Connector.IsParameterToleranceEnabled)
If expectedParams <> receivedParams Then
Dim errorMessage As String = $"Número de parametros equivocado para "${queryName}". Se esperaban ${expectedParams} y se recibieron ${receivedParams}."$
Log(errorMessage)
SendPlainTextError(resp, 400, errorMessage)
Return "error"
End If
End If
' --- FIN VALIDACIÓN ---
If validationResult.Success = False Then
SendPlainTextError(resp, 400, validationResult.ErrorMessage)
Return "error" ' Salida temprana si la validación falla.
End If
' Ejecuta la consulta.
Dim rs As ResultSet = con.ExecQuery2(theSql, params)
' Ejecuta la consulta con la lista de parámetros validada.
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
@@ -691,7 +685,7 @@ Private Sub ReadList(in As InputStream) As List
Return l1
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.
' 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
' 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.
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 Sub