B4J=true Group=Default Group ModulesStructureVersion=1 Type=Class Version=10.3 @EndOfDesignText@ ' Handler class for JSON requests from Web Clients (JavaScript/axios) 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. Private Connector As RDCConnector End Sub ' Subrutina de inicialización de la clase. Se llama cuando se crea un objeto de esta clase. Public Sub Initialize End Sub ' Este es el método principal que maneja las peticiones HTTP entrantes (req) y prepara la respuesta (resp). ' Este es el método principal que maneja las peticiones HTTP entrantes (req) y prepara la respuesta (resp). Sub Handle(req As ServletRequest, resp As ServletResponse) ' --- Headers CORS (Cross-Origin Resource Sharing) --- resp.SetHeader("Access-Control-Allow-Origin", "*") resp.SetHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS") resp.SetHeader("Access-Control-Allow-Headers", "Content-Type") If req.Method = "OPTIONS" Then Return ' Las peticiones OPTIONS no incrementan contadores ni usan BD, así que salimos directamente. End If Dim start As Long = DateTime.Now ' Declaraciones de variables con alcance en toda la subrutina para la limpieza. 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 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" Dim requestsBeforeDecrement As Int = 0 ' Se inicializa en 0. Try ' --- INICIO: Bloque Try que envuelve la lógica principal del Handler --- Dim jsonString As String If req.Method = "POST" And req.ContentType.Contains("application/json") Then Dim Is0 As InputStream = req.InputStream Dim bytes() As Byte = Bit.InputStreamToBytes(Is0) jsonString = BytesToString(bytes, 0, bytes.Length, "UTF8") Is0.Close Else jsonString = req.GetParameter("j") End If 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.") duration = DateTime.Now - start CleanupAndLog(finalDbKey, queryNameForLog, duration, req.RemoteAddress, requestsBeforeDecrement, poolBusyConnectionsForLog, con) Return ' Salida temprana. End If Dim parser As JSONParser parser.Initialize(jsonString) Dim RootMap As Map = parser.NextObject Dim execType As String = RootMap.GetDefault("exec", "") queryNameForLog = RootMap.GetDefault("query", "") '[___new 3.txt, 203] If queryNameForLog = "" Then queryNameForLog = RootMap.GetDefault("exec", "unknown_json_command") '[___new 3.txt, 203] Dim paramsList As List = RootMap.Get("params") If paramsList = Null Or paramsList.IsInitialized = False Then paramsList.Initialize End If ' <<<< ¡CORRECCIÓN CLAVE AQUÍ: RESOLVEMOS finalDbKey del JSON ANTES! >>>> If RootMap.Get("dbx") <> Null Then finalDbKey = RootMap.Get("dbx") '[___new 3.txt, 204] ' <<<< ¡FIN DE CORRECCIÓN CLAVE! >>>> ' --- INICIO: Conteo de peticiones activas para esta finalDbKey (Incrementar) --- ' 1. Aseguramos que el valor inicial sea un Int y lo recuperamos como Int. Dim currentCountFromMap As Int = GlobalParameters.ActiveRequestsCountByDB.GetDefault(finalDbKey, 0).As(Int) GlobalParameters.ActiveRequestsCountByDB.Put(finalDbKey, currentCountFromMap + 1) requestsBeforeDecrement = currentCountFromMap + 1 ' Este es el valor que se registra en query_logs ' Log($"[DEBUG] Handle Increment: dbKey=${finalDbKey}, currentCountFromMap=${currentCountFromMap}, requestsBeforeDecrement=${requestsBeforeDecrement}, Map state: ${GlobalParameters.ActiveRequestsCountByDB}"$) ' --- FIN: Conteo de peticiones activas --- Connector = Main.Connectors.Get(finalDbKey) ' Inicializamos el Connector con la finalDbKey resuelta. If Main.listaDeCP.IndexOf(finalDbKey) = -1 Then SendErrorResponse(resp, 400, "Parámetro 'DB' inválido. El nombre '" & finalDbKey & "' no es válido.") duration = DateTime.Now - start CleanupAndLog(finalDbKey, queryNameForLog, duration, req.RemoteAddress, requestsBeforeDecrement, poolBusyConnectionsForLog, con) Return ' Salida temprana. End If con = Connector.GetConnection(finalDbKey) ' La conexión a la BD se obtiene aquí. ' <<<< ¡AÑADIR ESTE RETRASO ARTIFICIAL PARA LA PRUEBA! >>>> ' Esto forzará a C3P0 a mantener las conexiones ocupadas por más tiempo. ' Si tienes 100 VUs, esto debería hacer que BusyConnections suba. ' Sleep(100) ' Retraso artificial de 100ms para pruebas. ' Log($"[DEBUG - ${finalDbKey}] Retraso artificial de 500ms aplicado. Pool Stats (antes de exec): Busy=${Connector.GetPoolStats.GetDefault("BusyConnections",0).As(Int)}, Total=${Connector.GetPoolStats.GetDefault("TotalConnections",0).As(Int)}"$ ) ' <<<< ¡FIN DEL RETRASO ARTIFICIAL! >>>> ' <<<< BUSY_CONNECTIONS YA SE CAPTURABA BIEN. LO MANTENEMOS. >>>> If Connector.IsInitialized Then Dim poolStats As Map = Connector.GetPoolStats '[___new 3.txt, 204] If poolStats.ContainsKey("BusyConnections") Then poolBusyConnectionsForLog = poolStats.Get("BusyConnections").As(Int) ' Aseguramos que sea Int. End If End If ' <<<< FIN DE CAPTURA! >>>> Dim sqlCommand As String = Connector.GetCommand(finalDbKey, queryNameForLog) 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) SendErrorResponse(resp, 400, errorMessage) duration = DateTime.Now - start CleanupAndLog(finalDbKey, queryNameForLog, duration, req.RemoteAddress, requestsBeforeDecrement, poolBusyConnectionsForLog, con) Return ' Salida temprana. End If If execType.ToLowerCase = "executequery" Then Dim rs As ResultSet If sqlCommand.Contains("?") Or paramsList.Size > 0 Then Dim expectedParams As Int = sqlCommand.Length - sqlCommand.Replace("?", "").Length Dim receivedParams As Int = paramsList.Size Log($"expectedParams: ${expectedParams}, receivedParams: ${receivedParams}"$) 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 rs = con.ExecQuery2(sqlCommand, paramsList) Else rs = con.ExecQuery(sqlCommand) End If Dim ResultList As List ResultList.Initialize Dim jrs As JavaObject = rs Dim rsmd As JavaObject = jrs.RunMethod("getMetaData", Null) Dim cols As Int = rsmd.RunMethod("getColumnCount", Null) Do While rs.NextRow Dim RowMap As Map RowMap.Initialize For i = 1 To cols Dim ColumnName As String = rsmd.RunMethod("getColumnName", Array(i)) Dim value As Object = jrs.RunMethod("getObject", Array(i)) RowMap.Put(ColumnName, value) Next ResultList.Add(RowMap) Loop rs.Close SendSuccessResponse(resp, CreateMap("result": ResultList)) Else If execType.ToLowerCase = "executecommand" Then If sqlCommand.Contains("?") Then 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) SendSuccessResponse(resp, CreateMap("message": "Command executed successfully")) Else SendErrorResponse(resp, 400, "Parámetro 'exec' inválido. '" & execType & "' no es un valor permitido.") ' El flujo continúa hasta la limpieza final si no hay un Return explícito. End If Catch ' --- CATCH: Maneja errores generales de ejecución o de SQL/JSON --- Log(LastException) SendErrorResponse(resp, 500, LastException.Message) queryNameForLog = "error_processing_json" ' Para registrar que hubo un error en el log. End Try ' --- FIN: Bloque Try principal --- ' --- Lógica de logging y limpieza final (para rutas de ejecución normal o después de Catch) --- duration = DateTime.Now - start CleanupAndLog(finalDbKey, queryNameForLog, duration, req.RemoteAddress, requestsBeforeDecrement, poolBusyConnectionsForLog, con) End Sub ' --- NUEVA SUBRUTINA: Centraliza el logging y la limpieza --- Private Sub CleanupAndLog(dbKey As String, qName As String, durMs As Long, clientIp As String, handlerReqs As Int, poolBusyConns As Int, conn As SQL) ' Log($"[DEBUG] CleanupAndLog Entry: dbKey=${dbKey}, handlerReqs=${handlerReqs}, Map state: ${GlobalParameters.ActiveRequestsCountByDB}"$) ' 1. Llama a la subrutina centralizada para registrar el rendimiento. Main.LogQueryPerformance(qName, durMs, dbKey, clientIp, handlerReqs, poolBusyConns) '[___new 3.txt, 207] ' <<<< ¡CORRECCIÓN CLAVE AQUÍ: Aseguramos que currentCount sea Int! >>>> Dim currentCount As Int = GlobalParameters.ActiveRequestsCountByDB.GetDefault(dbKey, 0).As(Int) ' Log($"[DEBUG] CleanupAndLog Before Decrement: dbKey=${dbKey}, currentCount (as Int)=${currentCount}, Map state: ${GlobalParameters.ActiveRequestsCountByDB}"$) If currentCount > 0 Then GlobalParameters.ActiveRequestsCountByDB.Put(dbKey, currentCount - 1) Else 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: 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. If conn <> Null And conn.IsInitialized Then conn.Close End Sub ' --- Subrutinas de ayuda para respuestas JSON --- ' Construye y envía una respuesta JSON de éxito. 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. dataMap.Put("success", True) ' Crea un generador de JSON. Dim jsonGenerator As JSONGenerator jsonGenerator.Initialize(dataMap) ' Establece el tipo de contenido de la respuesta a "application/json". resp.ContentType = "application/json" ' Escribe la cadena JSON generada en el cuerpo de la respuesta HTTP. resp.Write(jsonGenerator.ToString) End Sub ' Construye y envía una respuesta JSON de error. 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. If errorMessage.Contains("Índice de columnas no válido") Or errorMessage.Contains("ORA-17003") Then errorMessage = "NUMERO DE PARAMETROS EQUIVOCADO: " & errorMessage ' Crea un mapa con el estado de error y el mensaje. Dim resMap As Map = CreateMap("success": False, "error": errorMessage) ' Genera la cadena JSON a partir del mapa. 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). resp.Status = statusCode ' Establece el tipo de contenido y escribe la respuesta de error. resp.ContentType = "application/json" resp.Write(jsonGenerator.ToString) End Sub