mirror of
https://github.com/KeymonSoft/jRDC-MultiDB-Hikari.git
synced 2026-04-17 21:06:23 +00:00
- VERSION 5.10.27
- feat(arquitectura): Consolidación de estabilidad y diagnóstico. - refactor: Arquitectura de base de datos local y políticas de logs. - arch(sqlite): Aislamiento total de las conexiones SQLite en SQL_Auth y SQL_Logs. Esto protege las operaciones de autenticación críticas de la alta carga de I/O generada por el subsistema de logs. - feat(logs): Implementación de modo de almacenamiento flexible para logs (disco o en memoria), mejorando la capacidad de testing. - refactor(logs): Se estandariza el límite de retención de registros a 10,000 para todas las tablas de logs, y se renombra la subrutina de limpieza a borraArribaDe10000Logs.
This commit is contained in:
@@ -4,139 +4,134 @@ ModulesStructureVersion=1
|
||||
Type=Class
|
||||
Version=10.3
|
||||
@EndOfDesignText@
|
||||
' Módulo de clase: DBHandlerJSON
|
||||
' Este handler se encarga de procesar las peticiones HTTP que esperan o envían datos en formato JSON.
|
||||
' Es ideal para clientes web (JavaScript, axios, etc.) o servicios que interactúan con el servidor
|
||||
' mediante un API RESTful. Soporta tanto GET con JSON en un parámetro 'j' como POST con JSON
|
||||
' en el cuerpo de la petición.
|
||||
' Class module: DBHandlerJSON
|
||||
' This handler is responsible for processing HTTP requests that expect or send data in JSON format.
|
||||
' It is ideal for web clients (JavaScript, axios, etc.) or services that interact with the server
|
||||
' via a RESTful API. It supports both GET with JSON in a 'j' parameter and POST with JSON
|
||||
' in the request body.
|
||||
|
||||
Sub Class_Globals
|
||||
' Declara una variable privada para mantener una instancia del conector RDC.
|
||||
' Este objeto maneja la comunicación con la base de datos específica de la petición.
|
||||
' Declares a private variable to hold an instance of the RDC connector.
|
||||
' This object manages communication with the request's specific database.
|
||||
Private Connector As RDCConnector
|
||||
End Sub
|
||||
|
||||
' Subrutina de inicialización de la clase. Se llama cuando se crea un objeto de esta clase.
|
||||
' Class initialization subroutine. Called when an object of this class is created.
|
||||
Public Sub Initialize
|
||||
' No se requiere inicialización específica para esta clase en este momento.
|
||||
' No specific initialization is required for this class at this time.
|
||||
End Sub
|
||||
|
||||
' Este es el método principal que maneja las peticiones HTTP entrantes (req) y prepara la respuesta (resp).
|
||||
' This is the main method that handles incoming HTTP requests (req) and prepares the response (resp).
|
||||
Sub Handle(req As ServletRequest, resp As ServletResponse)
|
||||
' --- Headers CORS (Cross-Origin Resource Sharing) ---
|
||||
' Estos encabezados son esenciales para permitir que aplicaciones web (clientes)
|
||||
' alojadas en diferentes dominios puedan comunicarse con este servidor.
|
||||
resp.SetHeader("Access-Control-Allow-Origin", "*") ' Permite peticiones desde cualquier origen.
|
||||
resp.SetHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS") ' Métodos HTTP permitidos.
|
||||
resp.SetHeader("Access-Control-Allow-Headers", "Content-Type") ' Encabezados permitidos.
|
||||
' CORS (Cross-Origin Resource Sharing) Headers
|
||||
' These headers are essential to allow web applications (clients)
|
||||
' hosted on different domains to communicate with this server.
|
||||
resp.SetHeader("Access-Control-Allow-Origin", "*") ' Allows requests from any origin.
|
||||
resp.SetHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS") ' Allowed HTTP methods.
|
||||
resp.SetHeader("Access-Control-Allow-Headers", "Content-Type") ' Allowed headers.
|
||||
|
||||
' Las peticiones OPTIONS son pre-vuelos de CORS y no deben procesar lógica de negocio ni contadores.
|
||||
' OPTIONS requests are CORS pre-flights and should not process business logic or counters.
|
||||
If req.Method = "OPTIONS" Then
|
||||
Return ' Salimos directamente para estas peticiones.
|
||||
Return ' We exit directly for these requests.
|
||||
End If
|
||||
|
||||
Dim start As Long = DateTime.Now ' Registra el tiempo de inicio de la petición para calcular la duración.
|
||||
Dim start As Long = DateTime.Now ' Record the request start time to calculate duration.
|
||||
|
||||
' Declaraciones de variables con alcance en toda la subrutina para asegurar la limpieza final.
|
||||
Dim con As SQL ' La conexión a la BD, se inicializará más tarde.
|
||||
Dim queryNameForLog As String = "unknown_json_command" ' Nombre del comando para el log, con valor por defecto.
|
||||
Dim duration As Long ' La duración total de la petición, calculada antes del log.
|
||||
Dim poolBusyConnectionsForLog As Int = 0 ' Contiene el número de conexiones ocupadas del pool.
|
||||
Dim finalDbKey As String = "DB1" ' Identificador de la base de datos, con valor por defecto "DB1".
|
||||
Dim requestsBeforeDecrement As Int = 0 ' Contador de peticiones activas antes de decrementar, inicializado en 0.
|
||||
' Variable declarations with scope throughout the sub to ensure final cleanup.
|
||||
Dim con As SQL ' The DB connection, will be initialized later.
|
||||
Dim queryNameForLog As String = "unknown_json_command" ' Command name for the log, with a default value.
|
||||
Dim duration As Long ' The total request duration, calculated before logging.
|
||||
Dim poolBusyConnectionsForLog As Int = 0 ' Contains the number of busy connections from the pool.
|
||||
Dim finalDbKey As String = "DB1" ' Database identifier, defaulting to "DB1".
|
||||
Dim requestsBeforeDecrement As Int = 0 ' Active request counter before decrementing, initialized to 0.
|
||||
Dim Total As Int = 0
|
||||
|
||||
Try ' --- INICIO: Bloque Try que envuelve la lógica principal del Handler ---
|
||||
Try ' --- START: Try block wrapping the main Handler logic ---
|
||||
|
||||
Dim jsonString As String
|
||||
' <<<< INICIO: Lógica para manejar peticiones POST con JSON en el cuerpo >>>>
|
||||
' Logic to handle POST requests with JSON in the body
|
||||
If req.Method = "POST" And req.ContentType.Contains("application/json") Then
|
||||
' Si es un POST con JSON en el cuerpo, leemos directamente del InputStream.
|
||||
' If it's a POST with JSON in the body, read directly from the InputStream.
|
||||
Dim Is0 As InputStream = req.InputStream
|
||||
Dim bytes() As Byte = Bit.InputStreamToBytes(Is0) ' Lee el cuerpo completo de la petición.
|
||||
jsonString = BytesToString(bytes, 0, bytes.Length, "UTF8") ' Convierte los bytes a una cadena JSON.
|
||||
Is0.Close ' Cierra explícitamente el InputStream para liberar recursos.
|
||||
Dim bytes() As Byte = Bit.InputStreamToBytes(Is0) ' Read the entire request body.
|
||||
jsonString = BytesToString(bytes, 0, bytes.Length, "UTF8") ' Convert bytes to a JSON string.
|
||||
Is0.Close ' Explicitly close the InputStream to free resources.
|
||||
Else
|
||||
' De lo contrario, asumimos que el JSON viene en el parámetro 'j' de la URL (método legacy/GET).
|
||||
' Otherwise, assume the JSON comes in the 'j' parameter of the URL (legacy/GET method).
|
||||
jsonString = req.GetParameter("j")
|
||||
End If
|
||||
' <<<< FIN: Lógica para manejar peticiones POST con JSON en el cuerpo >>>>
|
||||
|
||||
' Validación inicial: Si no hay JSON, se envía un error 400.
|
||||
' Initial validation: If there is no JSON, send a 400 error.
|
||||
If jsonString = Null Or jsonString = "" Then
|
||||
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
|
||||
Main.LogServerError("ERROR", "DBHandlerJSON.Handle", ErrorMsg, finalDbKey, queryNameForLog, req.RemoteAddress)
|
||||
duration = DateTime.Now - start
|
||||
CleanupAndLog(finalDbKey, queryNameForLog, duration, req.RemoteAddress, requestsBeforeDecrement, poolBusyConnectionsForLog, con)
|
||||
Return
|
||||
End If
|
||||
|
||||
Dim parser As JSONParser
|
||||
parser.Initialize(jsonString) ' Inicializa el parser JSON con la cadena recibida.
|
||||
Dim RootMap As Map = parser.NextObject ' Parsea el JSON a un objeto Map.
|
||||
parser.Initialize(jsonString) ' Initialize the JSON parser with the received string.
|
||||
Dim RootMap As Map = parser.NextObject ' Parse the JSON into a Map object.
|
||||
|
||||
Dim execType As String = RootMap.GetDefault("exec", "") ' Obtiene el tipo de ejecución (ej. "ExecuteQuery").
|
||||
Dim execType As String = RootMap.GetDefault("exec", "") ' Get the execution type (e.g., "ExecuteQuery").
|
||||
|
||||
' Obtiene el nombre de la query. Si no está en "query", busca en "exec".
|
||||
' Get the query name. If not in "query", look in "exec".
|
||||
queryNameForLog = RootMap.GetDefault("query", "")
|
||||
If queryNameForLog = "" Then queryNameForLog = RootMap.GetDefault("exec", "unknown_json_command")
|
||||
|
||||
Dim paramsList As List = RootMap.Get("params") ' Obtiene la lista de parámetros para la query.
|
||||
Dim paramsList As List = RootMap.Get("params") ' Get the list of parameters for the query.
|
||||
If paramsList = Null Or paramsList.IsInitialized = False Then
|
||||
paramsList.Initialize ' Si no hay parámetros, inicializa una lista vacía.
|
||||
paramsList.Initialize ' If no parameters, initialize an empty list.
|
||||
End If
|
||||
|
||||
' <<<< ¡CORRECCIÓN CLAVE: RESOLVEMOS finalDbKey del JSON ANTES de usarla para los contadores! >>>>
|
||||
' Esto asegura que el contador y el conector usen la DB correcta.
|
||||
' Resolve finalDbKey from the JSON BEFORE using it for counters.
|
||||
' This ensures the counter and connector use the correct DB.
|
||||
If RootMap.Get("dbx") <> Null Then finalDbKey = RootMap.Get("dbx")
|
||||
' <<<< ¡FIN DE CORRECCIÓN CLAVE! >>>>
|
||||
|
||||
' --- INICIO: Conteo de peticiones activas para esta finalDbKey (Incrementar) ---
|
||||
' Este bloque incrementa un contador global que rastrea cuántas peticiones están
|
||||
' activas para una base de datos específica en un momento dado.
|
||||
' 1. Aseguramos que el valor inicial sea un Int y lo recuperamos como Int (usando .As(Int)).
|
||||
' --- START: Active request count for this finalDbKey (Increment) ---
|
||||
' This block increments a global counter tracking how many requests
|
||||
' are active for a specific database at any given time.
|
||||
' 1. Ensure the initial value is an Int and retrieve it as Int (using .As(Int)).
|
||||
Dim currentCountFromMap As Int = GlobalParameters.ActiveRequestsCountByDB.GetDefault(finalDbKey, 0).As(Int)
|
||||
GlobalParameters.ActiveRequestsCountByDB.Put(finalDbKey, currentCountFromMap + 1)
|
||||
' requestsBeforeDecrement es el valor del contador justo después de que esta petición lo incrementa.
|
||||
' Este es el valor que se registrará en la tabla 'query_logs'.
|
||||
' requestsBeforeDecrement is the counter value right after this request increments it.
|
||||
' This is the value that will be recorded in the 'query_logs' table.
|
||||
requestsBeforeDecrement = currentCountFromMap + 1
|
||||
' Los logs de depuración para el incremento del contador pueden ser descomentados para una depuración profunda.
|
||||
' Log($"[DEBUG] Handle Increment (JSON): dbKey=${finalDbKey}, currentCountFromMap=${currentCountFromMap}, requestsBeforeDecrement=${requestsBeforeDecrement}, Map state: ${GlobalParameters.ActiveRequestsCountByDB}"$)
|
||||
' --- FIN: Conteo de peticiones activas ---
|
||||
' --- END: Active request count ---
|
||||
|
||||
' Inicializa el Connector con la finalDbKey resuelta.
|
||||
' Initialize the Connector with the resolved finalDbKey.
|
||||
Connector = Main.Connectors.Get(finalDbKey)
|
||||
|
||||
' Validación: Si el dbKey no es válido o no está configurado en Main.listaDeCP.
|
||||
' Validation: If the dbKey is invalid or not configured in Main.listaDeCP.
|
||||
If Main.listaDeCP.IndexOf(finalDbKey) = -1 Then
|
||||
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
|
||||
Main.LogServerError("ERROR", "DBHandlerJSON.Handle", ErrorMsg, finalDbKey, queryNameForLog, req.RemoteAddress)
|
||||
duration = DateTime.Now - start
|
||||
CleanupAndLog(finalDbKey, queryNameForLog, duration, req.RemoteAddress, requestsBeforeDecrement, poolBusyConnectionsForLog, con)
|
||||
Return
|
||||
End If
|
||||
|
||||
con = Connector.GetConnection(finalDbKey) ' ¡La conexión a la BD se obtiene aquí del pool de conexiones!
|
||||
con = Connector.GetConnection(finalDbKey) ' The DB connection is obtained here from the connection pool!
|
||||
|
||||
' <<<< ¡CAPTURAMOS BUSY_CONNECTIONS INMEDIATAMENTE DESPUÉS DE OBTENER LA CONEXIÓN! >>>>
|
||||
' Este bloque captura el número de conexiones actualmente ocupadas en el pool
|
||||
' *después* de que esta petición ha obtenido la suya.
|
||||
' Capture BUSY_CONNECTIONS IMMEDIATELY AFTER getting the connection.
|
||||
' This block captures the number of connections currently busy in the pool
|
||||
' *after* this request has obtained its own.
|
||||
If Connector.IsInitialized Then
|
||||
Dim poolStats As Map = Connector.GetPoolStats
|
||||
If poolStats.ContainsKey("BusyConnections") Then
|
||||
' <<<< ¡CORRECCIÓN CLAVE: Aseguramos que el valor sea Int! >>>>
|
||||
poolBusyConnectionsForLog = poolStats.Get("BusyConnections").As(Int) ' Capturamos el valor.
|
||||
' Ensure the value is Int!
|
||||
poolBusyConnectionsForLog = poolStats.Get("BusyConnections").As(Int) ' We capture the value.
|
||||
' Log($">>>>>>>>>> ${poolStats.Get("BusyConnections")} "$)
|
||||
End If
|
||||
End If
|
||||
' <<<< ¡FIN DE CAPTURA! >>>>
|
||||
|
||||
Dim cachedStatsJSON As Map = Main.LatestPoolStats.Get(finalDbKey).As(Map)
|
||||
|
||||
If cachedStatsJSON.IsInitialized Then
|
||||
' Los valores ya fueron capturados: poolBusyConnectionsForLog y requestsBeforeDecrement
|
||||
' Values were already captured: poolBusyConnectionsForLog and requestsBeforeDecrement
|
||||
cachedStatsJSON.Put("BusyConnections", poolBusyConnectionsForLog)
|
||||
cachedStatsJSON.Put("HandlerActiveRequests", requestsBeforeDecrement)
|
||||
If poolStats.ContainsKey("TotalConnections") Then
|
||||
@@ -145,167 +140,161 @@ Sub Handle(req As ServletRequest, resp As ServletResponse)
|
||||
If poolStats.ContainsKey("IdleConnections") Then
|
||||
cachedStatsJSON.Put("IdleConnections", poolStats.Get("IdleConnections"))
|
||||
End If
|
||||
' Re-escribir el mapa en el cache global (es Thread-Safe)
|
||||
' Re-write the map to the global cache (it's Thread-Safe)
|
||||
Main.LatestPoolStats.Put(finalDbKey, cachedStatsJSON)
|
||||
' Log(Main.LatestPoolStats)
|
||||
End If
|
||||
|
||||
' Log($"Total: ${poolStats.Get("TotalConnections")}, Idle: ${poolStats.Get("IdleConnections")}, busy: ${poolBusyConnectionsForLog}, active: ${requestsBeforeDecrement}"$)
|
||||
|
||||
' Obtiene la sentencia SQL correspondiente al nombre del comando desde config.properties.
|
||||
' Get the SQL statement corresponding to the command name from config.properties.
|
||||
Dim sqlCommand As String = Connector.GetCommand(finalDbKey, queryNameForLog)
|
||||
|
||||
' Validación: Si el comando SQL no fue encontrado en la configuración.
|
||||
' Validation: If the SQL command was not found in the configuration.
|
||||
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}'."$
|
||||
Log(errorMessage)
|
||||
Main.LogServerError("ERROR", "DBHandlerJSON.Handle", errorMessage, finalDbKey, queryNameForLog, req.RemoteAddress) ' <-- Nuevo Log
|
||||
Main.LogServerError("ERROR", "DBHandlerJSON.Handle", errorMessage, finalDbKey, queryNameForLog, req.RemoteAddress)
|
||||
SendErrorResponse(resp, 400, errorMessage)
|
||||
duration = DateTime.Now - start
|
||||
CleanupAndLog(finalDbKey, queryNameForLog, duration, req.RemoteAddress, requestsBeforeDecrement, poolBusyConnectionsForLog, con)
|
||||
Return
|
||||
End If
|
||||
|
||||
' --- Lógica para ejecutar diferentes tipos de comandos basados en el parámetro 'execType' ---
|
||||
' Logic to execute different command types based on the 'execType' parameter
|
||||
If execType.ToLowerCase = "executequery" Then
|
||||
' --- INICIO VALIDACIÓN DE PARÁMETROS CENTRALIZADA ---
|
||||
' --- START CENTRALIZED PARAMETER VALIDATION ---
|
||||
Dim validationResult As ParameterValidationResult = ParameterValidationUtils.ValidateAndAdjustParameters(queryNameForLog, finalDbKey, sqlCommand, paramsList, Connector.IsParameterToleranceEnabled)
|
||||
|
||||
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.
|
||||
Return ' Early exit.
|
||||
End If
|
||||
|
||||
Dim rs As ResultSet
|
||||
' Ejecuta la consulta SQL con la lista de parámetros validada.
|
||||
' Execute the SQL query with the validated parameter list.
|
||||
rs = con.ExecQuery2(sqlCommand, validationResult.ParamsToExecute)
|
||||
' --- FIN VALIDACIÓN DE PARÁMETROS CENTRALIZADA ---
|
||||
' --- END CENTRALIZED PARAMETER VALIDATION ---
|
||||
|
||||
Dim ResultList As List
|
||||
ResultList.Initialize ' Lista para almacenar los resultados de la consulta.
|
||||
Dim jrs As JavaObject = rs ' Objeto Java subyacente del ResultSet para metadatos.
|
||||
Dim rsmd As JavaObject = jrs.RunMethod("getMetaData", Null) ' Metadatos del ResultSet.
|
||||
Dim cols As Int = rsmd.RunMethod("getColumnCount", Null) ' Número de columnas.
|
||||
ResultList.Initialize ' List to store query results.
|
||||
Dim jrs As JavaObject = rs ' Underlying Java object of the ResultSet for metadata.
|
||||
Dim rsmd As JavaObject = jrs.RunMethod("getMetaData", Null) ' ResultSet metadata.
|
||||
Dim cols As Int = rsmd.RunMethod("getColumnCount", Null) ' Number of columns.
|
||||
|
||||
Do While rs.NextRow ' Itera sobre cada fila del resultado.
|
||||
Do While rs.NextRow ' Iterate over each row in the result.
|
||||
Dim RowMap As Map
|
||||
RowMap.Initialize ' Mapa para almacenar los datos de la fila actual.
|
||||
For i = 1 To cols ' Itera sobre cada columna.
|
||||
Dim ColumnName As String = rsmd.RunMethod("getColumnName", Array(i)) ' Nombre de la columna.
|
||||
Dim value As Object = jrs.RunMethod("getObject", Array(i)) ' Valor de la columna.
|
||||
RowMap.Put(ColumnName, value) ' Añade la columna y su valor al mapa de la fila.
|
||||
RowMap.Initialize ' Map to store the current row's data.
|
||||
For i = 1 To cols ' Iterate over each column.
|
||||
Dim ColumnName As String = rsmd.RunMethod("getColumnName", Array(i)) ' Column name.
|
||||
Dim value As Object = jrs.RunMethod("getObject", Array(i)) ' Column value.
|
||||
RowMap.Put(ColumnName, value) ' Add the column and its value to the row map.
|
||||
Next
|
||||
ResultList.Add(RowMap) ' Añade el mapa de la fila a la lista de resultados.
|
||||
ResultList.Add(RowMap) ' Add the row map to the results list.
|
||||
Loop
|
||||
rs.Close ' Cierra el ResultSet.
|
||||
SendSuccessResponse(resp, CreateMap("result": ResultList)) ' Envía la respuesta JSON de éxito.
|
||||
rs.Close ' Close the ResultSet.
|
||||
SendSuccessResponse(resp, CreateMap("result": ResultList)) ' Send the success JSON response.
|
||||
|
||||
Else If execType.ToLowerCase = "executecommand" Then
|
||||
' --- INICIO VALIDACIÓN DE PARÁMETROS CENTRALIZADA ---
|
||||
' --- START CENTRALIZED PARAMETER VALIDATION ---
|
||||
Dim validationResult As ParameterValidationResult = ParameterValidationUtils.ValidateAndAdjustParameters(queryNameForLog, finalDbKey, sqlCommand, paramsList, Connector.IsParameterToleranceEnabled)
|
||||
|
||||
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.
|
||||
Return ' Early exit.
|
||||
End If
|
||||
Dim affectedCount As Int = 1 ' Asumimos éxito (1) si ExecNonQuery2 no lanza una excepción.
|
||||
con.ExecNonQuery2(sqlCommand, validationResult.ParamsToExecute) ' Ejecuta un comando con la lista de parámetros validada.
|
||||
SendSuccessResponse(resp, CreateMap("affectedRows": affectedCount, "message": "Command executed successfully")) ' Envía confirmación de éxito.
|
||||
' --- FIN VALIDACIÓN DE PARÁMETROS CENTRALIZADA ---
|
||||
Dim affectedCount As Int = 1 ' Assume success (1) if ExecNonQuery2 doesn't throw an exception.
|
||||
con.ExecNonQuery2(sqlCommand, validationResult.ParamsToExecute) ' Execute a command with the validated parameter list.
|
||||
SendSuccessResponse(resp, CreateMap("affectedRows": affectedCount, "message": "Command executed successfully")) ' Send success confirmation.
|
||||
' --- END CENTRALIZED PARAMETER VALIDATION ---
|
||||
Else
|
||||
Dim ErrorMsg As String = "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.
|
||||
Main.LogServerError("ERROR", "DBHandlerJSON.Handle", ErrorMsg, finalDbKey, queryNameForLog, req.RemoteAddress)
|
||||
' Flow continues to final cleanup if there is no explicit Return.
|
||||
End If
|
||||
Catch ' --- CATCH: Maneja errores generales de ejecución o de SQL/JSON ---
|
||||
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.
|
||||
queryNameForLog = "error_processing_json" ' Para registrar que hubo un error en el log.
|
||||
End Try ' --- FIN: Bloque Try principal ---
|
||||
Catch ' --- CATCH: Handle general execution or SQL/JSON errors ---
|
||||
Log(LastException) ' Log the full exception.
|
||||
Main.LogServerError("ERROR", "DBHandlerJSON.Handle", LastException.Message, finalDbKey, queryNameForLog, req.RemoteAddress)
|
||||
SendErrorResponse(resp, 500, LastException.Message) ' Send a 500 error to the client.
|
||||
queryNameForLog = "error_processing_json" ' To log that there was an error.
|
||||
End Try ' --- END: Main Try block ---
|
||||
|
||||
' --- Lógica de logging y limpieza final (para rutas de ejecución normal o después de Catch) ---
|
||||
' Este bloque se asegura de que, independientemente de cómo termine la petición (éxito o error),
|
||||
' la duración se calcule y se llamen las subrutinas de limpieza y logging.
|
||||
duration = DateTime.Now - start ' Calcula la duración total de la petición.
|
||||
' Llama a la subrutina centralizada para registrar el rendimiento y limpiar recursos.
|
||||
' --- Final logging and cleanup logic (for normal execution paths or after Catch) ---
|
||||
' This block ensures that, regardless of how the request ends (success or error),
|
||||
' the duration is calculated and the cleanup and logging subs are called.
|
||||
duration = DateTime.Now - start ' Calculate the total request duration.
|
||||
' Call the centralized subroutine to log performance and clean up resources.
|
||||
CleanupAndLog(finalDbKey, queryNameForLog, duration, req.RemoteAddress, requestsBeforeDecrement, poolBusyConnectionsForLog, con)
|
||||
End Sub
|
||||
|
||||
' --- NUEVA SUBRUTINA: Centraliza el logging de rendimiento y la limpieza de recursos ---
|
||||
' Esta subrutina es llamada por Handle en todos los puntos de salida, asegurando
|
||||
' que los contadores se decrementen y las conexiones se cierren de forma consistente.
|
||||
' --- Subroutine: Centralizes performance logging and resource cleanup ---
|
||||
' This subroutine is called by Handle at all exit points, ensuring
|
||||
' that counters are decremented and connections are closed consistently.
|
||||
Private Sub CleanupAndLog(dbKey As String, qName As String, durMs As Long, clientIp As String, handlerReqs As Int, poolBusyConns As Int, conn As SQL)
|
||||
' Los logs de depuración para CleanupAndLog pueden ser descomentados para una depuración profunda.
|
||||
' Log($"[DEBUG] CleanupAndLog Entry (JSON): dbKey=${dbKey}, handlerReqs=${handlerReqs}, Map state: ${GlobalParameters.ActiveRequestsCountByDB}"$)
|
||||
' 1. Llama a la subrutina centralizada en Main para registrar el rendimiento en SQLite.
|
||||
' 1. Call the centralized subroutine in Main to log performance to SQLite.
|
||||
Main.LogQueryPerformance(qName, durMs, dbKey, clientIp, handlerReqs, poolBusyConns)
|
||||
|
||||
' <<<< ¡CORRECCIÓN CLAVE: Aseguramos que currentCount sea Int al obtenerlo del mapa! >>>>
|
||||
' 2. Decrementa el contador de peticiones activas para esta dbKey de forma robusta.
|
||||
' 2. Robustly decrement the active request counter for this dbKey.
|
||||
Dim currentCount As Int = GlobalParameters.ActiveRequestsCountByDB.GetDefault(dbKey, 0).As(Int)
|
||||
' Log($"[DEBUG] CleanupAndLog Before Decrement (JSON): dbKey=${dbKey}, currentCount (as Int)=${currentCount}, Map state: ${GlobalParameters.ActiveRequestsCountByDB}"$)
|
||||
|
||||
If currentCount > 0 Then
|
||||
' Si el contador es positivo, lo decrementamos.
|
||||
' If the counter is positive, decrement it.
|
||||
GlobalParameters.ActiveRequestsCountByDB.Put(dbKey, currentCount - 1)
|
||||
Else
|
||||
' Si el contador ya está en 0 o negativo (lo cual no debería ocurrir con la lógica actual,
|
||||
' pero se maneja para robustez), registramos una advertencia y lo aseguramos en 0.
|
||||
' If the counter is already 0 or negative (which shouldn't happen with current logic,
|
||||
' but is handled for robustness), we log a warning and ensure it is 0.
|
||||
' Log($"ADVERTENCIA: Intento de decrementar ActiveRequestsCountByDB para ${dbKey} que ya estaba en ${currentCount}. Asegurando a 0."$)
|
||||
GlobalParameters.ActiveRequestsCountByDB.Put(dbKey, 0)
|
||||
End If
|
||||
' Log($"[DEBUG] CleanupAndLog After Decrement (JSON): dbKey=${dbKey}, New count (as Int)=${GlobalParameters.ActiveRequestsCountByDB.GetDefault(dbKey,0).As(Int)}, Map state: ${GlobalParameters.ActiveRequestsCountByDB}"$)
|
||||
' <<<< ¡FIN DE CORRECCIÓN CLAVE! >>>>
|
||||
|
||||
' 3. Asegura que la conexión a la BD siempre se cierre y se devuelva al pool de conexiones.
|
||||
' 3. Ensure the DB connection is always closed and returned to the connection pool.
|
||||
If conn <> Null And conn.IsInitialized Then conn.Close
|
||||
End Sub
|
||||
|
||||
' --- Subrutinas de ayuda para respuestas JSON ---
|
||||
' --- Helper subroutines for JSON responses ---
|
||||
|
||||
' Construye y envía una respuesta JSON de éxito.
|
||||
' resp: El objeto ServletResponse para enviar la respuesta.
|
||||
' dataMap: Un mapa que contiene los datos a incluir en la respuesta JSON.
|
||||
' Builds and sends a success JSON response.
|
||||
' resp: The ServletResponse object to send the response.
|
||||
' dataMap: A map containing the data to include in the JSON response.
|
||||
Private Sub SendSuccessResponse(resp As ServletResponse, dataMap As Map)
|
||||
' Añade el campo "success": true al mapa de datos para indicar que todo salió bien.
|
||||
' Add the "success": true field to the data map to indicate everything went well.
|
||||
dataMap.Put("success", True)
|
||||
|
||||
' Crea un generador de JSON.
|
||||
' Create a JSON generator.
|
||||
Dim jsonGenerator As JSONGenerator
|
||||
jsonGenerator.Initialize(dataMap)
|
||||
|
||||
' Establece el tipo de contenido de la respuesta a "application/json".
|
||||
' Set the response content type to "application/json".
|
||||
resp.ContentType = "application/json"
|
||||
' Escribe la cadena JSON generada en el cuerpo de la respuesta HTTP.
|
||||
' Write the generated JSON string to the HTTP response body.
|
||||
resp.Write(jsonGenerator.ToString)
|
||||
End Sub
|
||||
|
||||
' Construye y envía una respuesta JSON de error.
|
||||
' resp: El objeto ServletResponse para enviar la respuesta.
|
||||
' statusCode: El código de estado HTTP (ej. 400 para error del cliente, 500 para error del servidor).
|
||||
' errorMessage: El mensaje de error que se enviará al cliente.
|
||||
' Builds and sends an error JSON response.
|
||||
' resp: The ServletResponse object to send the response.
|
||||
' statusCode: The HTTP status code (e.g., 400 for client error, 500 for server error).
|
||||
' errorMessage: The error message to be sent to the client.
|
||||
Private Sub SendErrorResponse(resp As ServletResponse, statusCode As Int, errorMessage As String)
|
||||
' Personaliza el mensaje de error si es un error común de parámetros de Oracle o JDBC.
|
||||
' Customize the error message if it's a common Oracle or JDBC parameter error.
|
||||
If errorMessage.Contains("Índice de columnas no válido") Or errorMessage.Contains("ORA-17003") Then
|
||||
errorMessage = "NUMERO DE PARAMETROS EQUIVOCADO: " & errorMessage
|
||||
End If
|
||||
|
||||
' Crea un mapa con el estado de error y el mensaje.
|
||||
' Create a map with the error status and message.
|
||||
Dim resMap As Map = CreateMap("success": False, "error": errorMessage)
|
||||
|
||||
' Genera la cadena JSON a partir del mapa.
|
||||
' Generate the JSON string from the map.
|
||||
Dim jsonGenerator As JSONGenerator
|
||||
jsonGenerator.Initialize(resMap)
|
||||
|
||||
' Establece el código de estado HTTP (ej. 400 para error del cliente, 500 para error del servidor).
|
||||
' Set the HTTP status code (e.g., 400 for client error, 500 for server error).
|
||||
resp.Status = statusCode
|
||||
' Establece el tipo de contenido y escribe la respuesta de error.
|
||||
' Set the content type and write the error response.
|
||||
resp.ContentType = "application/json"
|
||||
resp.Write(jsonGenerator.ToString)
|
||||
End Sub
|
||||
End Sub
|
||||
|
||||
Reference in New Issue
Block a user