mirror of
https://github.com/KeymonSoft/jRDC-Multi.git
synced 2026-04-18 05:09:32 +00:00
- 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:
164
DBHandlerB4X.bas
164
DBHandlerB4X.bas
@@ -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
|
||||
Reference in New Issue
Block a user