mirror of
https://github.com/KeymonSoft/jRDC-Multi.git
synced 2026-04-18 13:19:20 +00:00
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2ec8f5973f | |||
| e04cdded47 | |||
| 674eb2c81b | |||
|
|
09d40879ca | ||
|
|
b426c06eb2 | ||
| 48dbd1f034 | |||
|
|
2f9569b585 | ||
| 9d82925dec |
319
B4AHandler.bas
319
B4AHandler.bas
@@ -1,319 +0,0 @@
|
|||||||
B4J=true
|
|
||||||
Group=Default Group
|
|
||||||
ModulesStructureVersion=1
|
|
||||||
Type=Class
|
|
||||||
Version=4.19
|
|
||||||
@EndOfDesignText@
|
|
||||||
'Handler class
|
|
||||||
Sub Class_Globals
|
|
||||||
' #if VERSION1
|
|
||||||
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
|
|
||||||
Private bc As ByteConverter
|
|
||||||
Private cs As CompressedStreams
|
|
||||||
' #end if
|
|
||||||
Private DateTimeMethods As Map
|
|
||||||
Private Connector As RDCConnector
|
|
||||||
End Sub
|
|
||||||
|
|
||||||
Public Sub Initialize
|
|
||||||
DateTimeMethods = CreateMap(91: "getDate", 92: "getTime", 93: "getTimestamp")
|
|
||||||
End Sub
|
|
||||||
|
|
||||||
Sub Handle(req As ServletRequest, resp As ServletResponse)
|
|
||||||
Log("********************* DB1 ********************")
|
|
||||||
Dim start As Long = DateTime.Now
|
|
||||||
Dim q As String
|
|
||||||
Dim in As InputStream = req.InputStream
|
|
||||||
Dim method As String = req.GetParameter("method")
|
|
||||||
Connector = Main.Connectors.Get("DB1")
|
|
||||||
Dim con As SQL
|
|
||||||
Try
|
|
||||||
con = Connector.GetConnection("DB1")
|
|
||||||
If method = "query2" Then
|
|
||||||
q = ExecuteQuery2("DB1", con, in, resp)
|
|
||||||
'#if VERSION1
|
|
||||||
Else if method = "query" Then
|
|
||||||
in = cs.WrapInputStream(in, "gzip")
|
|
||||||
q = ExecuteQuery("DB1", con, in, resp)
|
|
||||||
Else if method = "batch" Then
|
|
||||||
in = cs.WrapInputStream(in, "gzip")
|
|
||||||
q = ExecuteBatch("DB1", con, in, resp)
|
|
||||||
'#end if
|
|
||||||
Else if method = "batch2" Then
|
|
||||||
q = ExecuteBatch2("DB1", con, in, resp)
|
|
||||||
Else
|
|
||||||
Log("Unknown method: " & method)
|
|
||||||
resp.SendError(500, "unknown method")
|
|
||||||
End If
|
|
||||||
Catch
|
|
||||||
Log(LastException)
|
|
||||||
resp.SendError(500, LastException.Message)
|
|
||||||
End Try
|
|
||||||
If con <> Null And con.IsInitialized Then con.Close
|
|
||||||
Log($"Command: ${q}, took: ${DateTime.Now - start}ms, client=${req.RemoteAddress}"$)
|
|
||||||
End Sub
|
|
||||||
|
|
||||||
Private Sub ExecuteQuery2 (DB As String, con As SQL, in As InputStream, resp As ServletResponse) As String
|
|
||||||
Dim ser As B4XSerializator
|
|
||||||
Dim m As Map = ser.ConvertBytesToObject(Bit.InputStreamToBytes(in))
|
|
||||||
Dim cmd As DBCommand = m.Get("command")
|
|
||||||
Dim limit As Int = m.Get("limit")
|
|
||||||
Dim rs As ResultSet = con.ExecQuery2(Connector.GetCommand(DB, cmd.Name), cmd.Parameters)
|
|
||||||
If limit <= 0 Then limit = 0x7fffffff 'max int
|
|
||||||
Dim jrs As JavaObject = rs
|
|
||||||
Dim rsmd As JavaObject = jrs.RunMethod("getMetaData", Null)
|
|
||||||
Dim cols As Int = rs.ColumnCount
|
|
||||||
Dim res As DBResult
|
|
||||||
res.Initialize
|
|
||||||
res.columns.Initialize
|
|
||||||
res.Tag = Null 'without this the Tag properly will not be serializable.
|
|
||||||
For i = 0 To cols - 1
|
|
||||||
res.columns.Put(rs.GetColumnName(i), i)
|
|
||||||
Next
|
|
||||||
res.Rows.Initialize
|
|
||||||
Do While rs.NextRow And limit > 0
|
|
||||||
Dim row(cols) As Object
|
|
||||||
For i = 0 To cols - 1
|
|
||||||
Dim ct As Int = rsmd.RunMethod("getColumnType", Array(i + 1))
|
|
||||||
'check whether it is a blob field
|
|
||||||
If ct = -2 Or ct = 2004 Or ct = -3 Or ct = -4 Then
|
|
||||||
row(i) = rs.GetBlob2(i)
|
|
||||||
Else If ct = 2005 Then
|
|
||||||
row(i) = rs.GetString2(i)
|
|
||||||
Else if ct = 2 Or ct = 3 Then
|
|
||||||
row(i) = rs.GetDouble2(i)
|
|
||||||
Else If DateTimeMethods.ContainsKey(ct) Then
|
|
||||||
Dim SQLTime As JavaObject = jrs.RunMethodJO(DateTimeMethods.Get(ct), Array(i + 1))
|
|
||||||
If SQLTime.IsInitialized Then
|
|
||||||
row(i) = SQLTime.RunMethod("getTime", Null)
|
|
||||||
Else
|
|
||||||
row(i) = Null
|
|
||||||
End If
|
|
||||||
Else
|
|
||||||
row(i) = jrs.RunMethod("getObject", Array(i + 1))
|
|
||||||
End If
|
|
||||||
Next
|
|
||||||
res.Rows.Add(row)
|
|
||||||
Loop
|
|
||||||
rs.Close
|
|
||||||
Dim data() As Byte = ser.ConvertObjectToBytes(res)
|
|
||||||
resp.OutputStream.WriteBytes(data, 0, data.Length)
|
|
||||||
Return "query: " & cmd.Name
|
|
||||||
End Sub
|
|
||||||
|
|
||||||
Private Sub ExecuteBatch2(DB As String, con As SQL, in As InputStream, resp As ServletResponse) As String
|
|
||||||
Dim ser As B4XSerializator
|
|
||||||
Dim m As Map = ser.ConvertBytesToObject(Bit.InputStreamToBytes(in))
|
|
||||||
Dim commands As List = m.Get("commands")
|
|
||||||
Dim res As DBResult
|
|
||||||
res.Initialize
|
|
||||||
res.columns = CreateMap("AffectedRows (N/A)": 0)
|
|
||||||
res.Rows.Initialize
|
|
||||||
res.Tag = Null
|
|
||||||
Try
|
|
||||||
con.BeginTransaction
|
|
||||||
For Each cmd As DBCommand In commands
|
|
||||||
con.ExecNonQuery2(Connector.GetCommand(DB, cmd.Name), _
|
|
||||||
cmd.Parameters)
|
|
||||||
Next
|
|
||||||
res.Rows.Add(Array As Object(0))
|
|
||||||
con.TransactionSuccessful
|
|
||||||
Catch
|
|
||||||
con.Rollback
|
|
||||||
Log(LastException)
|
|
||||||
resp.SendError(500, LastException.Message)
|
|
||||||
End Try
|
|
||||||
Dim data() As Byte = ser.ConvertObjectToBytes(res)
|
|
||||||
resp.OutputStream.WriteBytes(data, 0, data.Length)
|
|
||||||
Return $"batch (size=${commands.Size})"$
|
|
||||||
End Sub
|
|
||||||
|
|
||||||
'#if VERSION1
|
|
||||||
|
|
||||||
Private Sub ExecuteBatch(DB As String, con As SQL, in As InputStream, resp As ServletResponse) As String
|
|
||||||
Dim clientVersion As Float = ReadObject(in) 'ignore
|
|
||||||
Dim numberOfStatements As Int = ReadInt(in)
|
|
||||||
Dim res(numberOfStatements) As Int
|
|
||||||
Try
|
|
||||||
con.BeginTransaction
|
|
||||||
For i = 0 To numberOfStatements - 1
|
|
||||||
Dim queryName As String = ReadObject(in)
|
|
||||||
Dim params As List = ReadList(in)
|
|
||||||
con.ExecNonQuery2(Connector.GetCommand(DB, queryName), _
|
|
||||||
params)
|
|
||||||
Next
|
|
||||||
con.TransactionSuccessful
|
|
||||||
|
|
||||||
Dim out As OutputStream = cs.WrapOutputStream(resp.OutputStream, "gzip")
|
|
||||||
WriteObject(Main.VERSION, out)
|
|
||||||
WriteObject("batch", out)
|
|
||||||
WriteInt(res.Length, out)
|
|
||||||
For Each r As Int In res
|
|
||||||
WriteInt(r, out)
|
|
||||||
Next
|
|
||||||
out.Close
|
|
||||||
Catch
|
|
||||||
con.Rollback
|
|
||||||
Log(LastException)
|
|
||||||
resp.SendError(500, LastException.Message)
|
|
||||||
End Try
|
|
||||||
Return $"batch (size=${numberOfStatements})"$
|
|
||||||
End Sub
|
|
||||||
|
|
||||||
Private Sub ExecuteQuery(DB As String, con As SQL, in As InputStream, resp As ServletResponse) As String
|
|
||||||
' Log("==== ExecuteQuery ==== ")
|
|
||||||
Dim clientVersion As Float = ReadObject(in) 'ignore
|
|
||||||
Dim queryName As String = ReadObject(in)
|
|
||||||
Dim limit As Int = ReadInt(in)
|
|
||||||
Dim params As List = ReadList(in)
|
|
||||||
' Log("EL QUERY: |" & queryName & "|")
|
|
||||||
Private theSql As String = Connector.GetCommand(DB, queryName)
|
|
||||||
' Log(theSql)
|
|
||||||
' Log(params)
|
|
||||||
' Log(params.Size)
|
|
||||||
Dim rs As ResultSet = con.ExecQuery2(theSql, params)
|
|
||||||
If limit <= 0 Then limit = 0x7fffffff 'max int
|
|
||||||
Dim jrs As JavaObject = rs
|
|
||||||
Dim rsmd As JavaObject = jrs.RunMethod("getMetaData", Null)
|
|
||||||
Dim cols As Int = rs.ColumnCount
|
|
||||||
Dim out As OutputStream = cs.WrapOutputStream(resp.OutputStream, "gzip")
|
|
||||||
WriteObject(Main.VERSION, out)
|
|
||||||
WriteObject("query", out)
|
|
||||||
WriteInt(rs.ColumnCount, out)
|
|
||||||
' Log($"cols: ${cols}"$)
|
|
||||||
For i = 0 To cols - 1
|
|
||||||
WriteObject(rs.GetColumnName(i), out)
|
|
||||||
Next
|
|
||||||
|
|
||||||
Do While rs.NextRow And limit > 0
|
|
||||||
WriteByte(1, out)
|
|
||||||
For i = 0 To cols - 1
|
|
||||||
Dim ct As Int = rsmd.RunMethod("getColumnType", Array(i + 1))
|
|
||||||
'check whether it is a blob field
|
|
||||||
If ct = -2 Or ct = 2004 Or ct = -3 Or ct = -4 Then
|
|
||||||
WriteObject(rs.GetBlob2(i), out)
|
|
||||||
Else
|
|
||||||
WriteObject(jrs.RunMethod("getObject", Array(i + 1)), out)
|
|
||||||
End If
|
|
||||||
Next
|
|
||||||
Loop
|
|
||||||
WriteByte(0, out)
|
|
||||||
out.Close
|
|
||||||
rs.Close
|
|
||||||
|
|
||||||
Return "query: " & queryName
|
|
||||||
End Sub
|
|
||||||
|
|
||||||
Private Sub WriteByte(value As Byte, out As OutputStream)
|
|
||||||
out.WriteBytes(Array As Byte(value), 0, 1)
|
|
||||||
End Sub
|
|
||||||
|
|
||||||
Private Sub WriteObject(o As Object, out As OutputStream)
|
|
||||||
Dim data() As Byte
|
|
||||||
If o = Null Then
|
|
||||||
out.WriteBytes(Array As Byte(T_NULL), 0, 1)
|
|
||||||
Else If o Is Short Then
|
|
||||||
out.WriteBytes(Array As Byte(T_SHORT), 0, 1)
|
|
||||||
data = bc.ShortsToBytes(Array As Short(o))
|
|
||||||
Else If o Is Int Then
|
|
||||||
out.WriteBytes(Array As Byte(T_INT), 0, 1)
|
|
||||||
data = bc.IntsToBytes(Array As Int(o))
|
|
||||||
Else If o Is Float Then
|
|
||||||
out.WriteBytes(Array As Byte(T_FLOAT), 0, 1)
|
|
||||||
data = bc.FloatsToBytes(Array As Float(o))
|
|
||||||
Else If o Is Double Then
|
|
||||||
out.WriteBytes(Array As Byte(T_DOUBLE), 0, 1)
|
|
||||||
data = bc.DoublesToBytes(Array As Double(o))
|
|
||||||
Else If o Is Long Then
|
|
||||||
out.WriteBytes(Array As Byte(T_LONG), 0, 1)
|
|
||||||
data = bc.LongsToBytes(Array As Long(o))
|
|
||||||
Else If o Is Boolean Then
|
|
||||||
out.WriteBytes(Array As Byte(T_BOOLEAN), 0, 1)
|
|
||||||
Dim b As Boolean = o
|
|
||||||
Dim data(1) As Byte
|
|
||||||
If b Then data(0) = 1 Else data(0) = 0
|
|
||||||
Else If GetType(o) = "[B" Then
|
|
||||||
data = o
|
|
||||||
out.WriteBytes(Array As Byte(T_BLOB), 0, 1)
|
|
||||||
WriteInt(data.Length, out)
|
|
||||||
Else 'If o Is String Then (treat all other values as string)
|
|
||||||
out.WriteBytes(Array As Byte(T_STRING), 0, 1)
|
|
||||||
data = bc.StringToBytes(o, "UTF8")
|
|
||||||
WriteInt(data.Length, out)
|
|
||||||
End If
|
|
||||||
If data.Length > 0 Then out.WriteBytes(data, 0, data.Length)
|
|
||||||
End Sub
|
|
||||||
|
|
||||||
Private Sub ReadObject(In As InputStream) As Object
|
|
||||||
Dim data(1) As Byte
|
|
||||||
In.ReadBytes(data, 0, 1)
|
|
||||||
Select data(0)
|
|
||||||
Case T_NULL
|
|
||||||
Return Null
|
|
||||||
Case T_SHORT
|
|
||||||
Dim data(2) As Byte
|
|
||||||
Return bc.ShortsFromBytes(ReadBytesFully(In, data, data.Length))(0)
|
|
||||||
Case T_INT
|
|
||||||
Dim data(4) As Byte
|
|
||||||
Return bc.IntsFromBytes(ReadBytesFully(In, data, data.Length))(0)
|
|
||||||
Case T_LONG
|
|
||||||
Dim data(8) As Byte
|
|
||||||
Return bc.LongsFromBytes(ReadBytesFully(In, data, data.Length))(0)
|
|
||||||
Case T_FLOAT
|
|
||||||
Dim data(4) As Byte
|
|
||||||
Return bc.FloatsFromBytes(ReadBytesFully(In, data, data.Length))(0)
|
|
||||||
Case T_DOUBLE
|
|
||||||
Dim data(8) As Byte
|
|
||||||
Return bc.DoublesFromBytes(ReadBytesFully(In, data, data.Length))(0)
|
|
||||||
Case T_BOOLEAN
|
|
||||||
Dim b As Byte = ReadByte(In)
|
|
||||||
Return b = 1
|
|
||||||
Case T_BLOB
|
|
||||||
Dim len As Int = ReadInt(In)
|
|
||||||
Dim data(len) As Byte
|
|
||||||
Return ReadBytesFully(In, data, data.Length)
|
|
||||||
Case Else
|
|
||||||
Dim len As Int = ReadInt(In)
|
|
||||||
Dim data(len) As Byte
|
|
||||||
ReadBytesFully(In, data, data.Length)
|
|
||||||
Return BytesToString(data, 0, data.Length, "UTF8")
|
|
||||||
End Select
|
|
||||||
End Sub
|
|
||||||
|
|
||||||
Private Sub ReadBytesFully(In As InputStream, Data() As Byte, Len As Int) As Byte()
|
|
||||||
Dim count = 0, Read As Int
|
|
||||||
Do While count < Len And Read > -1
|
|
||||||
Read = In.ReadBytes(Data, count, Len - count)
|
|
||||||
count = count + Read
|
|
||||||
Loop
|
|
||||||
Return Data
|
|
||||||
End Sub
|
|
||||||
|
|
||||||
Private Sub WriteInt(i As Int, out As OutputStream)
|
|
||||||
Dim data() As Byte
|
|
||||||
data = bc.IntsToBytes(Array As Int(i))
|
|
||||||
out.WriteBytes(data, 0, data.Length)
|
|
||||||
End Sub
|
|
||||||
|
|
||||||
Private Sub ReadInt(In As InputStream) As Int
|
|
||||||
Dim data(4) As Byte
|
|
||||||
Return bc.IntsFromBytes(ReadBytesFully(In, data, data.Length))(0)
|
|
||||||
End Sub
|
|
||||||
|
|
||||||
Private Sub ReadByte(In As InputStream) As Byte
|
|
||||||
Dim data(1) As Byte
|
|
||||||
In.ReadBytes(data, 0, 1)
|
|
||||||
Return data(0)
|
|
||||||
End Sub
|
|
||||||
|
|
||||||
Private Sub ReadList(in As InputStream) As List
|
|
||||||
Dim len As Int = ReadInt(in)
|
|
||||||
Dim l1 As List
|
|
||||||
l1.Initialize
|
|
||||||
For i = 0 To len - 1
|
|
||||||
l1.Add(ReadObject(in))
|
|
||||||
Next
|
|
||||||
Return l1
|
|
||||||
End Sub
|
|
||||||
'#end If
|
|
||||||
169
Cambios.bas
Normal file
169
Cambios.bas
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
B4J=true
|
||||||
|
Group=Default Group
|
||||||
|
ModulesStructureVersion=1
|
||||||
|
Type=StaticCode
|
||||||
|
Version=10.3
|
||||||
|
@EndOfDesignText@
|
||||||
|
' ########################################
|
||||||
|
' ##### HISTORIAL DE CAMBIOS #####
|
||||||
|
' ########################################
|
||||||
|
Sub Process_Globals
|
||||||
|
'- VERSION X.XX.XX (cabios a implementar)
|
||||||
|
'- 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 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".
|
||||||
|
'- Agregar una forma de probar con carga el servidor
|
||||||
|
'- Agregar la opcion de "Queries lentos"
|
||||||
|
|
||||||
|
' VERSION 5.09.14
|
||||||
|
' -feat: Implementación robusta de monitoreo de pool de conexiones y peticiones activas
|
||||||
|
|
||||||
|
' -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:
|
||||||
|
|
||||||
|
' -1. **Métricas de `BusyConnections` y `TotalConnections` inconsistentes o siempre en `0` en el `Manager` y `query_logs`:**
|
||||||
|
' * **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.
|
||||||
|
' * **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.
|
||||||
|
|
||||||
|
' -2. **Contador `handler_active_requests` no decrementaba correctamente:**
|
||||||
|
' * **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**:
|
||||||
|
' * Se aseguró la declaración `Public ActiveRequestsCountByDB As Map` en `GlobalParameters.bas`.
|
||||||
|
' * Se garantizó su inicialización como un `srvr.CreateThreadSafeMap` en `Main.AppStart` para un manejo concurrente seguro de los contadores.
|
||||||
|
' * 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.
|
||||||
|
' * 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.
|
||||||
|
|
||||||
|
' -Beneficios de estos Cambios:
|
||||||
|
|
||||||
|
' * **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.
|
||||||
|
' * **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.
|
||||||
|
' * **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.13.3
|
||||||
|
'- Implementación de "Hot-Swap" para recarga de configuraciones de DB sin reiniciar el servidor.
|
||||||
|
'- Migración a ReentrantLock para sincronización debido a incompatibilidad con 'Sync'.
|
||||||
|
|
||||||
|
'- **Problemas Resueltos:**
|
||||||
|
|
||||||
|
'- 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 .
|
||||||
|
'- 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".
|
||||||
|
'- 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.
|
||||||
|
|
||||||
|
'- **Cambios Implementados:**
|
||||||
|
|
||||||
|
'- **En `Main.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`.
|
||||||
|
'- * **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 `RDCConnector.bas`:**
|
||||||
|
'- * **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 .
|
||||||
|
|
||||||
|
'- **En `Manager.bas`:**
|
||||||
|
'- * **Reemplazo completo de la lógica `If Command = "reload" Then`:**
|
||||||
|
'- * **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` .
|
||||||
|
'- * **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 .
|
||||||
|
'- * **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 .
|
||||||
|
'- * **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 .
|
||||||
|
'- * **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.
|
||||||
|
|
||||||
|
'- VERSION 5.09.13.2
|
||||||
|
'- Módulo: DBHandlerJSON.bas
|
||||||
|
'- Descripción de Cambios: Manejo de Peticiones POST con Content-Type: application/json
|
||||||
|
'- • 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:
|
||||||
|
'- 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).
|
||||||
|
'- 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 .
|
||||||
|
'- 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.
|
||||||
|
'- • 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).
|
||||||
|
|
||||||
|
'- VERSION 5.09.13
|
||||||
|
' feat: Mejora la inicialización del pool de conexiones y el soporte multi-DB.
|
||||||
|
'
|
||||||
|
' - Este commit aborda y resuelve varios problemas críticos relacionados con la inicialización
|
||||||
|
' del pool de conexiones (C3P0) para múltiples bases de datos y la depuración de logs
|
||||||
|
' en el servidor jRDC2-Multi.
|
||||||
|
'
|
||||||
|
' **Problemas Resueltos:**
|
||||||
|
'
|
||||||
|
' 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.
|
||||||
|
' 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.
|
||||||
|
'
|
||||||
|
' **Cambios Implementados:**
|
||||||
|
'
|
||||||
|
' **En `Main.bas`:**
|
||||||
|
'
|
||||||
|
' * **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.
|
||||||
|
|
||||||
|
' **En `RDCConnector.bas`:**
|
||||||
|
'
|
||||||
|
' * **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.
|
||||||
|
' * **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.
|
||||||
|
' * **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.
|
||||||
|
' * **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.
|
||||||
|
|
||||||
|
|
||||||
|
'- VERSION 5.09.08
|
||||||
|
'- Se agregó que se puedan configurar en el config.properties los siguientes parametros:
|
||||||
|
'
|
||||||
|
' - setInitialPoolSize = 3
|
||||||
|
' - setMinPoolSize = 2
|
||||||
|
' - setMaxPoolSize = 5
|
||||||
|
'
|
||||||
|
'- Se agregaron en duro a RDConnector los siguientes parametros:
|
||||||
|
'
|
||||||
|
' - setMaxIdleTime <-- Tiempo máximo de inactividad de la conexión.
|
||||||
|
' - setMaxConnectionAge <-- Tiempo de vida máximo de una conexión.
|
||||||
|
' - setCheckoutTimeout <-- Tiempo máximo de espera por una conexión.
|
||||||
|
'
|
||||||
|
'- 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"
|
||||||
|
|
||||||
|
'- 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.
|
||||||
|
|
||||||
|
'- VERSION 5.09.04
|
||||||
|
'- Se cambio el nombre del handler de B4X a DBHandlerB4X.
|
||||||
|
'- 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
|
||||||
@@ -35,7 +35,7 @@ Public Sub Handle(req As ServletRequest, resp As ServletResponse)
|
|||||||
|
|
||||||
Log("--- Probando con contraseña fija ---")
|
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("12345", 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>")
|
||||||
Return
|
Return
|
||||||
End If
|
End If
|
||||||
|
|||||||
320
DB1.bas
320
DB1.bas
@@ -1,320 +0,0 @@
|
|||||||
B4J=true
|
|
||||||
Group=Default Group
|
|
||||||
ModulesStructureVersion=1
|
|
||||||
Type=Class
|
|
||||||
Version=10
|
|
||||||
@EndOfDesignText@
|
|
||||||
'Handler class
|
|
||||||
Sub Class_Globals
|
|
||||||
' #if VERSION1
|
|
||||||
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
|
|
||||||
Private bc As ByteConverter
|
|
||||||
Private cs As CompressedStreams
|
|
||||||
' #end if
|
|
||||||
Private DateTimeMethods As Map
|
|
||||||
End Sub
|
|
||||||
|
|
||||||
Public Sub Initialize
|
|
||||||
DateTimeMethods = CreateMap(91: "getDate", 92: "getTime", 93: "getTimestamp")
|
|
||||||
End Sub
|
|
||||||
|
|
||||||
Sub Handle(req As ServletRequest, resp As ServletResponse)
|
|
||||||
Log("***********************************************")
|
|
||||||
Dim start As Long = DateTime.Now
|
|
||||||
Dim q As String
|
|
||||||
Dim in As InputStream = req.InputStream
|
|
||||||
Dim method As String = req.GetParameter("method")
|
|
||||||
Dim con As SQL
|
|
||||||
Try
|
|
||||||
log(">>>>>> " & Main.rdcConnectorDB1.config)
|
|
||||||
con = Main.rdcConnectorDB1.GetConnection("DB1")
|
|
||||||
If method = "query2" Then
|
|
||||||
q = ExecuteQuery2(con, in, resp)
|
|
||||||
'#if VERSION1
|
|
||||||
Else if method = "query" Then
|
|
||||||
in = cs.WrapInputStream(in, "gzip")
|
|
||||||
q = ExecuteQuery(con, in, resp)
|
|
||||||
Else if method = "batch" Then
|
|
||||||
in = cs.WrapInputStream(in, "gzip")
|
|
||||||
q = ExecuteBatch(con, in, resp)
|
|
||||||
'#end if
|
|
||||||
Else if method = "batch2" Then
|
|
||||||
q = ExecuteBatch2(con, in, resp)
|
|
||||||
Else
|
|
||||||
Log("Unknown method: " & method)
|
|
||||||
resp.SendError(500, "unknown method")
|
|
||||||
End If
|
|
||||||
Catch
|
|
||||||
Log(LastException)
|
|
||||||
resp.SendError(500, LastException.Message)
|
|
||||||
End Try
|
|
||||||
If con <> Null And con.IsInitialized Then con.Close
|
|
||||||
Log($"Command: ${q}, took: ${DateTime.Now - start}ms, client=${req.RemoteAddress}"$)
|
|
||||||
End Sub
|
|
||||||
|
|
||||||
Private Sub ExecuteQuery2 (con As SQL, in As InputStream, resp As ServletResponse) As String
|
|
||||||
Dim ser As B4XSerializator
|
|
||||||
Dim m As Map = ser.ConvertBytesToObject(Bit.InputStreamToBytes(in))
|
|
||||||
Dim cmd As DBCommand = m.Get("command")
|
|
||||||
Dim limit As Int = m.Get("limit")
|
|
||||||
Dim rs As ResultSet = con.ExecQuery2(Main.rdcConnectorDB1.GetCommand(cmd.Name), cmd.Parameters)
|
|
||||||
If limit <= 0 Then limit = 0x7fffffff 'max int
|
|
||||||
Dim jrs As JavaObject = rs
|
|
||||||
Dim rsmd As JavaObject = jrs.RunMethod("getMetaData", Null)
|
|
||||||
Dim cols As Int = rs.ColumnCount
|
|
||||||
Dim res As DBResult
|
|
||||||
res.Initialize
|
|
||||||
res.columns.Initialize
|
|
||||||
res.Tag = Null 'without this the Tag properly will not be serializable.
|
|
||||||
For i = 0 To cols - 1
|
|
||||||
res.columns.Put(rs.GetColumnName(i), i)
|
|
||||||
Next
|
|
||||||
res.Rows.Initialize
|
|
||||||
Do While rs.NextRow And limit > 0
|
|
||||||
Dim row(cols) As Object
|
|
||||||
For i = 0 To cols - 1
|
|
||||||
Dim ct As Int = rsmd.RunMethod("getColumnType", Array(i + 1))
|
|
||||||
'check whether it is a blob field
|
|
||||||
If ct = -2 Or ct = 2004 Or ct = -3 Or ct = -4 Then
|
|
||||||
row(i) = rs.GetBlob2(i)
|
|
||||||
Else If ct = 2005 Then
|
|
||||||
row(i) = rs.GetString2(i)
|
|
||||||
Else if ct = 2 Or ct = 3 Then
|
|
||||||
row(i) = rs.GetDouble2(i)
|
|
||||||
Else If DateTimeMethods.ContainsKey(ct) Then
|
|
||||||
Dim SQLTime As JavaObject = jrs.RunMethodJO(DateTimeMethods.Get(ct), Array(i + 1))
|
|
||||||
If SQLTime.IsInitialized Then
|
|
||||||
row(i) = SQLTime.RunMethod("getTime", Null)
|
|
||||||
Else
|
|
||||||
row(i) = Null
|
|
||||||
End If
|
|
||||||
Else
|
|
||||||
row(i) = jrs.RunMethod("getObject", Array(i + 1))
|
|
||||||
End If
|
|
||||||
Next
|
|
||||||
res.Rows.Add(row)
|
|
||||||
Loop
|
|
||||||
rs.Close
|
|
||||||
Dim data() As Byte = ser.ConvertObjectToBytes(res)
|
|
||||||
resp.OutputStream.WriteBytes(data, 0, data.Length)
|
|
||||||
Return "query: " & cmd.Name
|
|
||||||
End Sub
|
|
||||||
|
|
||||||
Private Sub ExecuteBatch2(con As SQL, in As InputStream, resp As ServletResponse) As String
|
|
||||||
Dim ser As B4XSerializator
|
|
||||||
Dim m As Map = ser.ConvertBytesToObject(Bit.InputStreamToBytes(in))
|
|
||||||
Dim commands As List = m.Get("commands")
|
|
||||||
Dim res As DBResult
|
|
||||||
res.Initialize
|
|
||||||
res.columns = CreateMap("AffectedRows (N/A)": 0)
|
|
||||||
res.Rows.Initialize
|
|
||||||
res.Tag = Null
|
|
||||||
Try
|
|
||||||
con.BeginTransaction
|
|
||||||
For Each cmd As DBCommand In commands
|
|
||||||
con.ExecNonQuery2(Main.rdcConnectorDB1.GetCommand(cmd.Name), _
|
|
||||||
cmd.Parameters)
|
|
||||||
Next
|
|
||||||
res.Rows.Add(Array As Object(0))
|
|
||||||
con.TransactionSuccessful
|
|
||||||
Catch
|
|
||||||
con.Rollback
|
|
||||||
Log(LastException)
|
|
||||||
resp.SendError(500, LastException.Message)
|
|
||||||
End Try
|
|
||||||
Dim data() As Byte = ser.ConvertObjectToBytes(res)
|
|
||||||
resp.OutputStream.WriteBytes(data, 0, data.Length)
|
|
||||||
Return $"batch (size=${commands.Size})"$
|
|
||||||
End Sub
|
|
||||||
|
|
||||||
'#if VERSION1
|
|
||||||
|
|
||||||
Private Sub ExecuteBatch(con As SQL, in As InputStream, resp As ServletResponse) As String
|
|
||||||
Dim clientVersion As Float = ReadObject(in) 'ignore
|
|
||||||
Dim numberOfStatements As Int = ReadInt(in)
|
|
||||||
Dim res(numberOfStatements) As Int
|
|
||||||
Try
|
|
||||||
con.BeginTransaction
|
|
||||||
For i = 0 To numberOfStatements - 1
|
|
||||||
Dim queryName As String = ReadObject(in)
|
|
||||||
Dim params As List = ReadList(in)
|
|
||||||
con.ExecNonQuery2(Main.rdcConnectorDB1.GetCommand(queryName), _
|
|
||||||
params)
|
|
||||||
Next
|
|
||||||
con.TransactionSuccessful
|
|
||||||
|
|
||||||
Dim out As OutputStream = cs.WrapOutputStream(resp.OutputStream, "gzip")
|
|
||||||
WriteObject(Main.VERSION, out)
|
|
||||||
WriteObject("batch", out)
|
|
||||||
WriteInt(res.Length, out)
|
|
||||||
For Each r As Int In res
|
|
||||||
WriteInt(r, out)
|
|
||||||
Next
|
|
||||||
out.Close
|
|
||||||
Catch
|
|
||||||
con.Rollback
|
|
||||||
Log(LastException)
|
|
||||||
resp.SendError(500, LastException.Message)
|
|
||||||
End Try
|
|
||||||
Return $"batch (size=${numberOfStatements})"$
|
|
||||||
End Sub
|
|
||||||
|
|
||||||
Private Sub ExecuteQuery (con As SQL, in As InputStream, resp As ServletResponse) As String
|
|
||||||
' Log("==== ExecuteQuery ==== ")
|
|
||||||
Dim clientVersion As Float = ReadObject(in) 'ignore
|
|
||||||
Dim queryName As String = ReadObject(in)
|
|
||||||
Dim limit As Int = ReadInt(in)
|
|
||||||
Dim params As List = ReadList(in)
|
|
||||||
' Log("EL QUERY: |" & queryName & "|")
|
|
||||||
Private theSql As String = Main.rdcConnectorDB1.GetCommand(queryName)
|
|
||||||
' Log(theSql)
|
|
||||||
' Log(params)
|
|
||||||
' Log(params.Size)
|
|
||||||
Dim rs As ResultSet = con.ExecQuery2(theSql, params)
|
|
||||||
If limit <= 0 Then limit = 0x7fffffff 'max int
|
|
||||||
Dim jrs As JavaObject = rs
|
|
||||||
Dim rsmd As JavaObject = jrs.RunMethod("getMetaData", Null)
|
|
||||||
Dim cols As Int = rs.ColumnCount
|
|
||||||
Dim out As OutputStream = cs.WrapOutputStream(resp.OutputStream, "gzip")
|
|
||||||
WriteObject(Main.VERSION, out)
|
|
||||||
WriteObject("query", out)
|
|
||||||
WriteInt(rs.ColumnCount, out)
|
|
||||||
' Log($"cols: ${cols}"$)
|
|
||||||
For i = 0 To cols - 1
|
|
||||||
WriteObject(rs.GetColumnName(i), out)
|
|
||||||
Next
|
|
||||||
|
|
||||||
Do While rs.NextRow And limit > 0
|
|
||||||
WriteByte(1, out)
|
|
||||||
For i = 0 To cols - 1
|
|
||||||
Dim ct As Int = rsmd.RunMethod("getColumnType", Array(i + 1))
|
|
||||||
'check whether it is a blob field
|
|
||||||
If ct = -2 Or ct = 2004 Or ct = -3 Or ct = -4 Then
|
|
||||||
WriteObject(rs.GetBlob2(i), out)
|
|
||||||
Else
|
|
||||||
WriteObject(jrs.RunMethod("getObject", Array(i + 1)), out)
|
|
||||||
End If
|
|
||||||
Next
|
|
||||||
Loop
|
|
||||||
WriteByte(0, out)
|
|
||||||
out.Close
|
|
||||||
rs.Close
|
|
||||||
|
|
||||||
Return "query: " & queryName
|
|
||||||
End Sub
|
|
||||||
|
|
||||||
Private Sub WriteByte(value As Byte, out As OutputStream)
|
|
||||||
out.WriteBytes(Array As Byte(value), 0, 1)
|
|
||||||
End Sub
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Private Sub WriteObject(o As Object, out As OutputStream)
|
|
||||||
Dim data() As Byte
|
|
||||||
If o = Null Then
|
|
||||||
out.WriteBytes(Array As Byte(T_NULL), 0, 1)
|
|
||||||
Else If o Is Short Then
|
|
||||||
out.WriteBytes(Array As Byte(T_SHORT), 0, 1)
|
|
||||||
data = bc.ShortsToBytes(Array As Short(o))
|
|
||||||
Else If o Is Int Then
|
|
||||||
out.WriteBytes(Array As Byte(T_INT), 0, 1)
|
|
||||||
data = bc.IntsToBytes(Array As Int(o))
|
|
||||||
Else If o Is Float Then
|
|
||||||
out.WriteBytes(Array As Byte(T_FLOAT), 0, 1)
|
|
||||||
data = bc.FloatsToBytes(Array As Float(o))
|
|
||||||
Else If o Is Double Then
|
|
||||||
out.WriteBytes(Array As Byte(T_DOUBLE), 0, 1)
|
|
||||||
data = bc.DoublesToBytes(Array As Double(o))
|
|
||||||
Else If o Is Long Then
|
|
||||||
out.WriteBytes(Array As Byte(T_LONG), 0, 1)
|
|
||||||
data = bc.LongsToBytes(Array As Long(o))
|
|
||||||
Else If o Is Boolean Then
|
|
||||||
out.WriteBytes(Array As Byte(T_BOOLEAN), 0, 1)
|
|
||||||
Dim b As Boolean = o
|
|
||||||
Dim data(1) As Byte
|
|
||||||
If b Then data(0) = 1 Else data(0) = 0
|
|
||||||
Else If GetType(o) = "[B" Then
|
|
||||||
data = o
|
|
||||||
out.WriteBytes(Array As Byte(T_BLOB), 0, 1)
|
|
||||||
WriteInt(data.Length, out)
|
|
||||||
Else 'If o Is String Then (treat all other values as string)
|
|
||||||
out.WriteBytes(Array As Byte(T_STRING), 0, 1)
|
|
||||||
data = bc.StringToBytes(o, "UTF8")
|
|
||||||
WriteInt(data.Length, out)
|
|
||||||
End If
|
|
||||||
If data.Length > 0 Then out.WriteBytes(data, 0, data.Length)
|
|
||||||
End Sub
|
|
||||||
|
|
||||||
Private Sub ReadObject(In As InputStream) As Object
|
|
||||||
Dim data(1) As Byte
|
|
||||||
In.ReadBytes(data, 0, 1)
|
|
||||||
Select data(0)
|
|
||||||
Case T_NULL
|
|
||||||
Return Null
|
|
||||||
Case T_SHORT
|
|
||||||
Dim data(2) As Byte
|
|
||||||
Return bc.ShortsFromBytes(ReadBytesFully(In, data, data.Length))(0)
|
|
||||||
Case T_INT
|
|
||||||
Dim data(4) As Byte
|
|
||||||
Return bc.IntsFromBytes(ReadBytesFully(In, data, data.Length))(0)
|
|
||||||
Case T_LONG
|
|
||||||
Dim data(8) As Byte
|
|
||||||
Return bc.LongsFromBytes(ReadBytesFully(In, data, data.Length))(0)
|
|
||||||
Case T_FLOAT
|
|
||||||
Dim data(4) As Byte
|
|
||||||
Return bc.FloatsFromBytes(ReadBytesFully(In, data, data.Length))(0)
|
|
||||||
Case T_DOUBLE
|
|
||||||
Dim data(8) As Byte
|
|
||||||
Return bc.DoublesFromBytes(ReadBytesFully(In, data, data.Length))(0)
|
|
||||||
Case T_BOOLEAN
|
|
||||||
Dim b As Byte = ReadByte(In)
|
|
||||||
Return b = 1
|
|
||||||
Case T_BLOB
|
|
||||||
Dim len As Int = ReadInt(In)
|
|
||||||
Dim data(len) As Byte
|
|
||||||
Return ReadBytesFully(In, data, data.Length)
|
|
||||||
Case Else
|
|
||||||
Dim len As Int = ReadInt(In)
|
|
||||||
Dim data(len) As Byte
|
|
||||||
ReadBytesFully(In, data, data.Length)
|
|
||||||
Return BytesToString(data, 0, data.Length, "UTF8")
|
|
||||||
End Select
|
|
||||||
End Sub
|
|
||||||
|
|
||||||
Private Sub ReadBytesFully(In As InputStream, Data() As Byte, Len As Int) As Byte()
|
|
||||||
Dim count = 0, Read As Int
|
|
||||||
Do While count < Len And Read > -1
|
|
||||||
Read = In.ReadBytes(Data, count, Len - count)
|
|
||||||
count = count + Read
|
|
||||||
Loop
|
|
||||||
Return Data
|
|
||||||
End Sub
|
|
||||||
|
|
||||||
Private Sub WriteInt(i As Int, out As OutputStream)
|
|
||||||
Dim data() As Byte
|
|
||||||
data = bc.IntsToBytes(Array As Int(i))
|
|
||||||
out.WriteBytes(data, 0, data.Length)
|
|
||||||
End Sub
|
|
||||||
|
|
||||||
Private Sub ReadInt(In As InputStream) As Int
|
|
||||||
Dim data(4) As Byte
|
|
||||||
Return bc.IntsFromBytes(ReadBytesFully(In, data, data.Length))(0)
|
|
||||||
End Sub
|
|
||||||
|
|
||||||
Private Sub ReadByte(In As InputStream) As Byte
|
|
||||||
Dim data(1) As Byte
|
|
||||||
In.ReadBytes(data, 0, 1)
|
|
||||||
Return data(0)
|
|
||||||
End Sub
|
|
||||||
|
|
||||||
Private Sub ReadList(in As InputStream) As List
|
|
||||||
Dim len As Int = ReadInt(in)
|
|
||||||
Dim l1 As List
|
|
||||||
l1.Initialize
|
|
||||||
For i = 0 To len - 1
|
|
||||||
l1.Add(ReadObject(in))
|
|
||||||
Next
|
|
||||||
Return l1
|
|
||||||
End Sub
|
|
||||||
'#end If
|
|
||||||
319
DB1Handler.bas
319
DB1Handler.bas
@@ -1,319 +0,0 @@
|
|||||||
B4J=true
|
|
||||||
Group=Default Group
|
|
||||||
ModulesStructureVersion=1
|
|
||||||
Type=Class
|
|
||||||
Version=4.19
|
|
||||||
@EndOfDesignText@
|
|
||||||
'Handler class
|
|
||||||
Sub Class_Globals
|
|
||||||
' #if VERSION1
|
|
||||||
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
|
|
||||||
Private bc As ByteConverter
|
|
||||||
Private cs As CompressedStreams
|
|
||||||
' #end if
|
|
||||||
Private DateTimeMethods As Map
|
|
||||||
Private Connector As RDCConnector
|
|
||||||
End Sub
|
|
||||||
|
|
||||||
Public Sub Initialize
|
|
||||||
DateTimeMethods = CreateMap(91: "getDate", 92: "getTime", 93: "getTimestamp")
|
|
||||||
End Sub
|
|
||||||
|
|
||||||
Sub Handle(req As ServletRequest, resp As ServletResponse)
|
|
||||||
Log("********************* DB1 ********************")
|
|
||||||
Dim start As Long = DateTime.Now
|
|
||||||
Dim q As String
|
|
||||||
Dim in As InputStream = req.InputStream
|
|
||||||
Dim method As String = req.GetParameter("method")
|
|
||||||
Connector = Main.Connectors.Get("DB1")
|
|
||||||
Dim con As SQL
|
|
||||||
Try
|
|
||||||
con = Connector.GetConnection("DB1")
|
|
||||||
If method = "query2" Then
|
|
||||||
q = ExecuteQuery2("DB1", con, in, resp)
|
|
||||||
'#if VERSION1
|
|
||||||
Else if method = "query" Then
|
|
||||||
in = cs.WrapInputStream(in, "gzip")
|
|
||||||
q = ExecuteQuery("DB1", con, in, resp)
|
|
||||||
Else if method = "batch" Then
|
|
||||||
in = cs.WrapInputStream(in, "gzip")
|
|
||||||
q = ExecuteBatch("DB1", con, in, resp)
|
|
||||||
'#end if
|
|
||||||
Else if method = "batch2" Then
|
|
||||||
q = ExecuteBatch2("DB1", con, in, resp)
|
|
||||||
Else
|
|
||||||
Log("Unknown method: " & method)
|
|
||||||
resp.SendError(500, "unknown method")
|
|
||||||
End If
|
|
||||||
Catch
|
|
||||||
Log(LastException)
|
|
||||||
resp.SendError(500, LastException.Message)
|
|
||||||
End Try
|
|
||||||
If con <> Null And con.IsInitialized Then con.Close
|
|
||||||
Log($"Command: ${q}, took: ${DateTime.Now - start}ms, client=${req.RemoteAddress}"$)
|
|
||||||
End Sub
|
|
||||||
|
|
||||||
Private Sub ExecuteQuery2 (DB As String, con As SQL, in As InputStream, resp As ServletResponse) As String
|
|
||||||
Dim ser As B4XSerializator
|
|
||||||
Dim m As Map = ser.ConvertBytesToObject(Bit.InputStreamToBytes(in))
|
|
||||||
Dim cmd As DBCommand = m.Get("command")
|
|
||||||
Dim limit As Int = m.Get("limit")
|
|
||||||
Dim rs As ResultSet = con.ExecQuery2(Connector.GetCommand(DB, cmd.Name), cmd.Parameters)
|
|
||||||
If limit <= 0 Then limit = 0x7fffffff 'max int
|
|
||||||
Dim jrs As JavaObject = rs
|
|
||||||
Dim rsmd As JavaObject = jrs.RunMethod("getMetaData", Null)
|
|
||||||
Dim cols As Int = rs.ColumnCount
|
|
||||||
Dim res As DBResult
|
|
||||||
res.Initialize
|
|
||||||
res.columns.Initialize
|
|
||||||
res.Tag = Null 'without this the Tag properly will not be serializable.
|
|
||||||
For i = 0 To cols - 1
|
|
||||||
res.columns.Put(rs.GetColumnName(i), i)
|
|
||||||
Next
|
|
||||||
res.Rows.Initialize
|
|
||||||
Do While rs.NextRow And limit > 0
|
|
||||||
Dim row(cols) As Object
|
|
||||||
For i = 0 To cols - 1
|
|
||||||
Dim ct As Int = rsmd.RunMethod("getColumnType", Array(i + 1))
|
|
||||||
'check whether it is a blob field
|
|
||||||
If ct = -2 Or ct = 2004 Or ct = -3 Or ct = -4 Then
|
|
||||||
row(i) = rs.GetBlob2(i)
|
|
||||||
Else If ct = 2005 Then
|
|
||||||
row(i) = rs.GetString2(i)
|
|
||||||
Else if ct = 2 Or ct = 3 Then
|
|
||||||
row(i) = rs.GetDouble2(i)
|
|
||||||
Else If DateTimeMethods.ContainsKey(ct) Then
|
|
||||||
Dim SQLTime As JavaObject = jrs.RunMethodJO(DateTimeMethods.Get(ct), Array(i + 1))
|
|
||||||
If SQLTime.IsInitialized Then
|
|
||||||
row(i) = SQLTime.RunMethod("getTime", Null)
|
|
||||||
Else
|
|
||||||
row(i) = Null
|
|
||||||
End If
|
|
||||||
Else
|
|
||||||
row(i) = jrs.RunMethod("getObject", Array(i + 1))
|
|
||||||
End If
|
|
||||||
Next
|
|
||||||
res.Rows.Add(row)
|
|
||||||
Loop
|
|
||||||
rs.Close
|
|
||||||
Dim data() As Byte = ser.ConvertObjectToBytes(res)
|
|
||||||
resp.OutputStream.WriteBytes(data, 0, data.Length)
|
|
||||||
Return "query: " & cmd.Name
|
|
||||||
End Sub
|
|
||||||
|
|
||||||
Private Sub ExecuteBatch2(DB As String, con As SQL, in As InputStream, resp As ServletResponse) As String
|
|
||||||
Dim ser As B4XSerializator
|
|
||||||
Dim m As Map = ser.ConvertBytesToObject(Bit.InputStreamToBytes(in))
|
|
||||||
Dim commands As List = m.Get("commands")
|
|
||||||
Dim res As DBResult
|
|
||||||
res.Initialize
|
|
||||||
res.columns = CreateMap("AffectedRows (N/A)": 0)
|
|
||||||
res.Rows.Initialize
|
|
||||||
res.Tag = Null
|
|
||||||
Try
|
|
||||||
con.BeginTransaction
|
|
||||||
For Each cmd As DBCommand In commands
|
|
||||||
con.ExecNonQuery2(Connector.GetCommand(DB, cmd.Name), _
|
|
||||||
cmd.Parameters)
|
|
||||||
Next
|
|
||||||
res.Rows.Add(Array As Object(0))
|
|
||||||
con.TransactionSuccessful
|
|
||||||
Catch
|
|
||||||
con.Rollback
|
|
||||||
Log(LastException)
|
|
||||||
resp.SendError(500, LastException.Message)
|
|
||||||
End Try
|
|
||||||
Dim data() As Byte = ser.ConvertObjectToBytes(res)
|
|
||||||
resp.OutputStream.WriteBytes(data, 0, data.Length)
|
|
||||||
Return $"batch (size=${commands.Size})"$
|
|
||||||
End Sub
|
|
||||||
|
|
||||||
'#if VERSION1
|
|
||||||
|
|
||||||
Private Sub ExecuteBatch(DB As String, con As SQL, in As InputStream, resp As ServletResponse) As String
|
|
||||||
Dim clientVersion As Float = ReadObject(in) 'ignore
|
|
||||||
Dim numberOfStatements As Int = ReadInt(in)
|
|
||||||
Dim res(numberOfStatements) As Int
|
|
||||||
Try
|
|
||||||
con.BeginTransaction
|
|
||||||
For i = 0 To numberOfStatements - 1
|
|
||||||
Dim queryName As String = ReadObject(in)
|
|
||||||
Dim params As List = ReadList(in)
|
|
||||||
con.ExecNonQuery2(Connector.GetCommand(DB, queryName), _
|
|
||||||
params)
|
|
||||||
Next
|
|
||||||
con.TransactionSuccessful
|
|
||||||
|
|
||||||
Dim out As OutputStream = cs.WrapOutputStream(resp.OutputStream, "gzip")
|
|
||||||
WriteObject(Main.VERSION, out)
|
|
||||||
WriteObject("batch", out)
|
|
||||||
WriteInt(res.Length, out)
|
|
||||||
For Each r As Int In res
|
|
||||||
WriteInt(r, out)
|
|
||||||
Next
|
|
||||||
out.Close
|
|
||||||
Catch
|
|
||||||
con.Rollback
|
|
||||||
Log(LastException)
|
|
||||||
resp.SendError(500, LastException.Message)
|
|
||||||
End Try
|
|
||||||
Return $"batch (size=${numberOfStatements})"$
|
|
||||||
End Sub
|
|
||||||
|
|
||||||
Private Sub ExecuteQuery(DB As String, con As SQL, in As InputStream, resp As ServletResponse) As String
|
|
||||||
' Log("==== ExecuteQuery ==== ")
|
|
||||||
Dim clientVersion As Float = ReadObject(in) 'ignore
|
|
||||||
Dim queryName As String = ReadObject(in)
|
|
||||||
Dim limit As Int = ReadInt(in)
|
|
||||||
Dim params As List = ReadList(in)
|
|
||||||
' Log("EL QUERY: |" & queryName & "|")
|
|
||||||
Private theSql As String = Connector.GetCommand(DB, queryName)
|
|
||||||
' Log(theSql)
|
|
||||||
' Log(params)
|
|
||||||
' Log(params.Size)
|
|
||||||
Dim rs As ResultSet = con.ExecQuery2(theSql, params)
|
|
||||||
If limit <= 0 Then limit = 0x7fffffff 'max int
|
|
||||||
Dim jrs As JavaObject = rs
|
|
||||||
Dim rsmd As JavaObject = jrs.RunMethod("getMetaData", Null)
|
|
||||||
Dim cols As Int = rs.ColumnCount
|
|
||||||
Dim out As OutputStream = cs.WrapOutputStream(resp.OutputStream, "gzip")
|
|
||||||
WriteObject(Main.VERSION, out)
|
|
||||||
WriteObject("query", out)
|
|
||||||
WriteInt(rs.ColumnCount, out)
|
|
||||||
' Log($"cols: ${cols}"$)
|
|
||||||
For i = 0 To cols - 1
|
|
||||||
WriteObject(rs.GetColumnName(i), out)
|
|
||||||
Next
|
|
||||||
|
|
||||||
Do While rs.NextRow And limit > 0
|
|
||||||
WriteByte(1, out)
|
|
||||||
For i = 0 To cols - 1
|
|
||||||
Dim ct As Int = rsmd.RunMethod("getColumnType", Array(i + 1))
|
|
||||||
'check whether it is a blob field
|
|
||||||
If ct = -2 Or ct = 2004 Or ct = -3 Or ct = -4 Then
|
|
||||||
WriteObject(rs.GetBlob2(i), out)
|
|
||||||
Else
|
|
||||||
WriteObject(jrs.RunMethod("getObject", Array(i + 1)), out)
|
|
||||||
End If
|
|
||||||
Next
|
|
||||||
Loop
|
|
||||||
WriteByte(0, out)
|
|
||||||
out.Close
|
|
||||||
rs.Close
|
|
||||||
|
|
||||||
Return "query: " & queryName
|
|
||||||
End Sub
|
|
||||||
|
|
||||||
Private Sub WriteByte(value As Byte, out As OutputStream)
|
|
||||||
out.WriteBytes(Array As Byte(value), 0, 1)
|
|
||||||
End Sub
|
|
||||||
|
|
||||||
Private Sub WriteObject(o As Object, out As OutputStream)
|
|
||||||
Dim data() As Byte
|
|
||||||
If o = Null Then
|
|
||||||
out.WriteBytes(Array As Byte(T_NULL), 0, 1)
|
|
||||||
Else If o Is Short Then
|
|
||||||
out.WriteBytes(Array As Byte(T_SHORT), 0, 1)
|
|
||||||
data = bc.ShortsToBytes(Array As Short(o))
|
|
||||||
Else If o Is Int Then
|
|
||||||
out.WriteBytes(Array As Byte(T_INT), 0, 1)
|
|
||||||
data = bc.IntsToBytes(Array As Int(o))
|
|
||||||
Else If o Is Float Then
|
|
||||||
out.WriteBytes(Array As Byte(T_FLOAT), 0, 1)
|
|
||||||
data = bc.FloatsToBytes(Array As Float(o))
|
|
||||||
Else If o Is Double Then
|
|
||||||
out.WriteBytes(Array As Byte(T_DOUBLE), 0, 1)
|
|
||||||
data = bc.DoublesToBytes(Array As Double(o))
|
|
||||||
Else If o Is Long Then
|
|
||||||
out.WriteBytes(Array As Byte(T_LONG), 0, 1)
|
|
||||||
data = bc.LongsToBytes(Array As Long(o))
|
|
||||||
Else If o Is Boolean Then
|
|
||||||
out.WriteBytes(Array As Byte(T_BOOLEAN), 0, 1)
|
|
||||||
Dim b As Boolean = o
|
|
||||||
Dim data(1) As Byte
|
|
||||||
If b Then data(0) = 1 Else data(0) = 0
|
|
||||||
Else If GetType(o) = "[B" Then
|
|
||||||
data = o
|
|
||||||
out.WriteBytes(Array As Byte(T_BLOB), 0, 1)
|
|
||||||
WriteInt(data.Length, out)
|
|
||||||
Else 'If o Is String Then (treat all other values as string)
|
|
||||||
out.WriteBytes(Array As Byte(T_STRING), 0, 1)
|
|
||||||
data = bc.StringToBytes(o, "UTF8")
|
|
||||||
WriteInt(data.Length, out)
|
|
||||||
End If
|
|
||||||
If data.Length > 0 Then out.WriteBytes(data, 0, data.Length)
|
|
||||||
End Sub
|
|
||||||
|
|
||||||
Private Sub ReadObject(In As InputStream) As Object
|
|
||||||
Dim data(1) As Byte
|
|
||||||
In.ReadBytes(data, 0, 1)
|
|
||||||
Select data(0)
|
|
||||||
Case T_NULL
|
|
||||||
Return Null
|
|
||||||
Case T_SHORT
|
|
||||||
Dim data(2) As Byte
|
|
||||||
Return bc.ShortsFromBytes(ReadBytesFully(In, data, data.Length))(0)
|
|
||||||
Case T_INT
|
|
||||||
Dim data(4) As Byte
|
|
||||||
Return bc.IntsFromBytes(ReadBytesFully(In, data, data.Length))(0)
|
|
||||||
Case T_LONG
|
|
||||||
Dim data(8) As Byte
|
|
||||||
Return bc.LongsFromBytes(ReadBytesFully(In, data, data.Length))(0)
|
|
||||||
Case T_FLOAT
|
|
||||||
Dim data(4) As Byte
|
|
||||||
Return bc.FloatsFromBytes(ReadBytesFully(In, data, data.Length))(0)
|
|
||||||
Case T_DOUBLE
|
|
||||||
Dim data(8) As Byte
|
|
||||||
Return bc.DoublesFromBytes(ReadBytesFully(In, data, data.Length))(0)
|
|
||||||
Case T_BOOLEAN
|
|
||||||
Dim b As Byte = ReadByte(In)
|
|
||||||
Return b = 1
|
|
||||||
Case T_BLOB
|
|
||||||
Dim len As Int = ReadInt(In)
|
|
||||||
Dim data(len) As Byte
|
|
||||||
Return ReadBytesFully(In, data, data.Length)
|
|
||||||
Case Else
|
|
||||||
Dim len As Int = ReadInt(In)
|
|
||||||
Dim data(len) As Byte
|
|
||||||
ReadBytesFully(In, data, data.Length)
|
|
||||||
Return BytesToString(data, 0, data.Length, "UTF8")
|
|
||||||
End Select
|
|
||||||
End Sub
|
|
||||||
|
|
||||||
Private Sub ReadBytesFully(In As InputStream, Data() As Byte, Len As Int) As Byte()
|
|
||||||
Dim count = 0, Read As Int
|
|
||||||
Do While count < Len And Read > -1
|
|
||||||
Read = In.ReadBytes(Data, count, Len - count)
|
|
||||||
count = count + Read
|
|
||||||
Loop
|
|
||||||
Return Data
|
|
||||||
End Sub
|
|
||||||
|
|
||||||
Private Sub WriteInt(i As Int, out As OutputStream)
|
|
||||||
Dim data() As Byte
|
|
||||||
data = bc.IntsToBytes(Array As Int(i))
|
|
||||||
out.WriteBytes(data, 0, data.Length)
|
|
||||||
End Sub
|
|
||||||
|
|
||||||
Private Sub ReadInt(In As InputStream) As Int
|
|
||||||
Dim data(4) As Byte
|
|
||||||
Return bc.IntsFromBytes(ReadBytesFully(In, data, data.Length))(0)
|
|
||||||
End Sub
|
|
||||||
|
|
||||||
Private Sub ReadByte(In As InputStream) As Byte
|
|
||||||
Dim data(1) As Byte
|
|
||||||
In.ReadBytes(data, 0, 1)
|
|
||||||
Return data(0)
|
|
||||||
End Sub
|
|
||||||
|
|
||||||
Private Sub ReadList(in As InputStream) As List
|
|
||||||
Dim len As Int = ReadInt(in)
|
|
||||||
Dim l1 As List
|
|
||||||
l1.Initialize
|
|
||||||
For i = 0 To len - 1
|
|
||||||
l1.Add(ReadObject(in))
|
|
||||||
Next
|
|
||||||
Return l1
|
|
||||||
End Sub
|
|
||||||
'#end If
|
|
||||||
@@ -1,266 +0,0 @@
|
|||||||
B4J=true
|
|
||||||
Group=Default Group
|
|
||||||
ModulesStructureVersion=1
|
|
||||||
Type=Class
|
|
||||||
Version=10.3
|
|
||||||
@EndOfDesignText@
|
|
||||||
' Handler class for JSON requests from Web Clients (JavaScript/axios)
|
|
||||||
' VERSIÓN 16 (Comentarios y Mensajes en Español):
|
|
||||||
' - Se añaden comentarios detallados a la versión con mensajes de error en español.
|
|
||||||
' - Revisa que el 'query' exista en config.properties antes de continuar.
|
|
||||||
' - Asegura que la conexión a la BD se cierre en todos los 'Return' para evitar fugas.
|
|
||||||
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.
|
|
||||||
' En este caso, no se necesita ninguna inicialización específica.
|
|
||||||
Public Sub Initialize
|
|
||||||
|
|
||||||
End Sub
|
|
||||||
|
|
||||||
' 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)
|
|
||||||
Log("============== DB1JsonHandler ==============")
|
|
||||||
' --- Headers CORS (Cross-Origin Resource Sharing) ---
|
|
||||||
' Estos encabezados son necesarios para permitir que un cliente web (ej. una página con JavaScript)
|
|
||||||
' que se encuentra en un dominio diferente pueda hacer peticiones a 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 en la petición.
|
|
||||||
|
|
||||||
' El método OPTIONS es una "petición de comprobación previa" (preflight request) que envían los navegadores
|
|
||||||
' para verificar los permisos CORS antes de enviar la petición real (ej. POST).
|
|
||||||
' Si es una petición OPTIONS, simplemente terminamos la ejecución sin procesar nada más.
|
|
||||||
If req.Method = "OPTIONS" Then Return
|
|
||||||
|
|
||||||
' Establece "DB1" como el nombre de la base de datos por defecto.
|
|
||||||
Dim DB As String = "DB1"
|
|
||||||
' Obtiene el objeto conector para la base de datos por defecto desde el objeto Main.
|
|
||||||
Connector = Main.Connectors.Get(DB)
|
|
||||||
' Declara una variable para la conexión SQL.
|
|
||||||
Dim con As SQL
|
|
||||||
|
|
||||||
' Inicia un bloque Try...Catch para manejar posibles errores durante la ejecución.
|
|
||||||
Try
|
|
||||||
' Obtiene el valor del parámetro 'j' de la petición. Se espera que contenga una cadena JSON.
|
|
||||||
Dim jsonString As String = req.GetParameter("j")
|
|
||||||
' Verifica si el parámetro 'j' es nulo o está vacío.
|
|
||||||
If jsonString = Null Or jsonString = "" Then
|
|
||||||
' Si falta el parámetro, envía una respuesta de error 400 (Bad Request) y termina la ejecución.
|
|
||||||
SendErrorResponse(resp, 400, "Falta el parametro 'j' en el URL")
|
|
||||||
Return
|
|
||||||
End If
|
|
||||||
|
|
||||||
' Crea un objeto JSONParser para analizar la cadena JSON.
|
|
||||||
Dim parser As JSONParser
|
|
||||||
parser.Initialize(jsonString)
|
|
||||||
' Convierte la cadena JSON en un objeto Map, que es como un diccionario (clave-valor).
|
|
||||||
Dim RootMap As Map = parser.NextObject
|
|
||||||
|
|
||||||
' Extrae los datos necesarios del JSON.
|
|
||||||
Dim execType As String = RootMap.GetDefault("exec", "") ' Tipo de ejecución: "executeQuery" o "executeCommand".
|
|
||||||
Dim queryName As String = RootMap.Get("query") ' Nombre del comando SQL (definido en config.properties).
|
|
||||||
Dim paramsMap As Map = RootMap.Get("params") ' Un mapa con los parámetros para la consulta.
|
|
||||||
' Log(RootMap)
|
|
||||||
' Verifica si en el JSON se especificó un nombre de base de datos diferente con la clave "dbx".
|
|
||||||
If RootMap.Get("dbx") <> Null Then DB = RootMap.Get("dbx") ' Si se especifica, usamos la BD indicada, si no, se queda "DB1".
|
|
||||||
|
|
||||||
' Valida que el nombre de la base de datos (DB) exista en la lista de conexiones configuradas en Main.
|
|
||||||
If Main.listaDeCP.IndexOf(DB) = -1 Then
|
|
||||||
SendErrorResponse(resp, 400, "Parametro 'DB' invalido. El nombre '" & DB & "' no es válido.")
|
|
||||||
' Se añade Return para detener la ejecución si la BD no es válida.
|
|
||||||
Return
|
|
||||||
End If
|
|
||||||
|
|
||||||
' Prepara una lista para almacenar las claves de los parámetros.
|
|
||||||
Dim paramKeys As List
|
|
||||||
paramKeys.Initialize
|
|
||||||
' Si el mapa de parámetros existe y está inicializado...
|
|
||||||
If paramsMap <> Null And paramsMap.IsInitialized Then
|
|
||||||
' ...itera sobre todas las claves y las añade a la lista 'paramKeys'.
|
|
||||||
For Each key As String In paramsMap.Keys
|
|
||||||
paramKeys.Add(key)
|
|
||||||
Next
|
|
||||||
End If
|
|
||||||
' Ordena las claves alfabéticamente. Esto es crucial para asegurar que los parámetros
|
|
||||||
' se pasen a la consulta SQL en un orden consistente y predecible.
|
|
||||||
paramKeys.Sort(True)
|
|
||||||
|
|
||||||
' Prepara una lista para almacenar los valores de los parámetros en el orden correcto.
|
|
||||||
Dim orderedParams As List
|
|
||||||
orderedParams.Initialize
|
|
||||||
' Itera sobre la lista de claves ya ordenada.
|
|
||||||
For Each key As String In paramKeys
|
|
||||||
' Añade el valor correspondiente a cada clave a la lista 'orderedParams'.
|
|
||||||
orderedParams.Add(paramsMap.Get(key))
|
|
||||||
Next
|
|
||||||
|
|
||||||
' Obtiene una conexión a la base de datos del pool de conexiones.
|
|
||||||
con = Connector.GetConnection(DB)
|
|
||||||
' Obtiene la cadena SQL del archivo de configuración usando el nombre de la consulta (queryName).
|
|
||||||
Dim sqlCommand As String = Connector.GetCommand(DB, queryName)
|
|
||||||
|
|
||||||
' <<< INICIO NUEVA VALIDACIÓN: VERIFICAR SI EL COMANDO EXISTE >>>
|
|
||||||
' Comprueba si el comando SQL (query) especificado en el JSON fue encontrado en el archivo de configuración.
|
|
||||||
If sqlCommand = Null Or sqlCommand = "null" Or sqlCommand.Trim = "" Then
|
|
||||||
' Si no se encontró el comando, crea un mensaje de error claro.
|
|
||||||
Dim errorMessage As String = $"El comando '${queryName}' no fue encontrado en el config.properties de '${DB}'."$
|
|
||||||
' Registra el error en el log del servidor para depuración.
|
|
||||||
Log(errorMessage)
|
|
||||||
' Envía una respuesta de error 400 (Bad Request) al cliente en formato JSON.
|
|
||||||
SendErrorResponse(resp, 400, errorMessage)
|
|
||||||
' Cierra la conexión a la base de datos antes de salir para evitar fugas de conexión.
|
|
||||||
If con <> Null And con.IsInitialized Then con.Close
|
|
||||||
' Detiene la ejecución del método Handle para esta petición.
|
|
||||||
Return
|
|
||||||
End If
|
|
||||||
' <<< FIN NUEVA VALIDACIÓN >>>
|
|
||||||
|
|
||||||
' Comprueba el tipo de ejecución solicitado ("executeQuery" o "executeCommand").
|
|
||||||
If execType.ToLowerCase = "executequery" Then
|
|
||||||
' Declara una variable para almacenar el resultado de la consulta.
|
|
||||||
Dim rs As ResultSet
|
|
||||||
|
|
||||||
' Si el comando SQL contiene placeholders ('?'), significa que espera parámetros.
|
|
||||||
If sqlCommand.Contains("?") or orderedParams.Size > 0 Then
|
|
||||||
' =================================================================
|
|
||||||
' === VALIDACIÓN DE CONTEO DE PARÁMETROS ==========================
|
|
||||||
' =================================================================
|
|
||||||
' Calcula cuántos parámetros espera la consulta contando el número de '?'.
|
|
||||||
Dim expectedParams As Int = sqlCommand.Length - sqlCommand.Replace("?", "").Length
|
|
||||||
' Obtiene cuántos parámetros se recibieron.
|
|
||||||
Dim receivedParams As Int = orderedParams.Size
|
|
||||||
' Compara si la cantidad de parámetros esperados y recibidos es diferente.
|
|
||||||
|
|
||||||
Log($"expectedParams: ${expectedParams}, receivedParams: ${receivedParams}"$)
|
|
||||||
|
|
||||||
If expectedParams <> receivedParams Then
|
|
||||||
' Si no coinciden, envía un error 400 detallado.
|
|
||||||
SendErrorResponse(resp, 400, $"Número de parametros equivocado para '${queryName}'. Se esperaban ${expectedParams} y se recibieron ${receivedParams}."$)
|
|
||||||
' Cierra la conexión antes de salir para evitar fugas.
|
|
||||||
If con <> Null And con.IsInitialized Then con.Close
|
|
||||||
' Detiene la ejecución para evitar un error en la base de datos.
|
|
||||||
Return
|
|
||||||
End If
|
|
||||||
' =================================================================
|
|
||||||
' Ejecuta la consulta pasando el comando SQL y la lista ordenada de parámetros.
|
|
||||||
rs = con.ExecQuery2(sqlCommand, orderedParams)
|
|
||||||
Else
|
|
||||||
' Si no hay '?', ejecuta la consulta directamente sin parámetros.
|
|
||||||
rs = con.ExecQuery(sqlCommand)
|
|
||||||
End If
|
|
||||||
|
|
||||||
' --- Procesamiento de resultados ---
|
|
||||||
' Prepara una lista para almacenar todas las filas del resultado.
|
|
||||||
Dim ResultList As List
|
|
||||||
ResultList.Initialize
|
|
||||||
' Usa un objeto JavaObject para acceder a los metadatos del resultado (info de columnas).
|
|
||||||
Dim jrs As JavaObject = rs
|
|
||||||
Dim rsmd As JavaObject = jrs.RunMethod("getMetaData", Null)
|
|
||||||
' Obtiene el número de columnas en el resultado.
|
|
||||||
Dim cols As Int = rsmd.RunMethod("getColumnCount", Null)
|
|
||||||
|
|
||||||
' Itera sobre cada fila del resultado (ResultSet).
|
|
||||||
Do While rs.NextRow
|
|
||||||
' Crea un mapa para almacenar los datos de la fila actual (columna -> valor).
|
|
||||||
Dim RowMap As Map
|
|
||||||
RowMap.Initialize
|
|
||||||
' Itera sobre cada columna de la fila.
|
|
||||||
For i = 1 To cols
|
|
||||||
' Obtiene el nombre de la columna.
|
|
||||||
Dim ColumnName As String = rsmd.RunMethod("getColumnName", Array(i))
|
|
||||||
' Obtiene el valor de la columna.
|
|
||||||
Dim value As Object = jrs.RunMethod("getObject", Array(i))
|
|
||||||
' Añade la pareja (nombre_columna, valor) al mapa de la fila.
|
|
||||||
RowMap.Put(ColumnName, value)
|
|
||||||
Next
|
|
||||||
' Añade el mapa de la fila a la lista de resultados.
|
|
||||||
ResultList.Add(RowMap)
|
|
||||||
Loop
|
|
||||||
' Cierra el ResultSet para liberar recursos de la base de datos.
|
|
||||||
rs.Close
|
|
||||||
|
|
||||||
' Envía una respuesta de éxito con la lista de resultados en formato JSON.
|
|
||||||
SendSuccessResponse(resp, CreateMap("result": ResultList))
|
|
||||||
|
|
||||||
Else If execType.ToLowerCase = "executecommand" Then
|
|
||||||
' Si es un comando (INSERT, UPDATE, DELETE), también valida los parámetros.
|
|
||||||
If sqlCommand.Contains("?") Then
|
|
||||||
' =================================================================
|
|
||||||
' === VALIDACIÓN DE CONTEO DE PARÁMETROS (para Comandos) ==========
|
|
||||||
' =================================================================
|
|
||||||
Dim expectedParams As Int = sqlCommand.Length - sqlCommand.Replace("?", "").Length
|
|
||||||
Dim receivedParams As Int = orderedParams.Size
|
|
||||||
If expectedParams <> receivedParams Then
|
|
||||||
SendErrorResponse(resp, 400, $"Número de parametros equivocado para '${queryName}'. Se esperaban ${expectedParams} y se recibieron ${receivedParams}."$)
|
|
||||||
' Cierra la conexión antes de salir.
|
|
||||||
If con <> Null And con.IsInitialized Then con.Close
|
|
||||||
' Detiene la ejecución.
|
|
||||||
Return
|
|
||||||
End If
|
|
||||||
' =================================================================
|
|
||||||
End If
|
|
||||||
|
|
||||||
' Ejecuta el comando que no devuelve resultados (NonQuery) con sus parámetros.
|
|
||||||
con.ExecNonQuery2(sqlCommand, orderedParams)
|
|
||||||
' Envía una respuesta de éxito con un mensaje de confirmación.
|
|
||||||
SendSuccessResponse(resp, CreateMap("message": "Command executed successfully"))
|
|
||||||
|
|
||||||
Else
|
|
||||||
' Si el valor de 'exec' no es ni "executeQuery" ni "executeCommand", envía un error.
|
|
||||||
SendErrorResponse(resp, 400, "Parametro 'exec' inválido. '" & execType & "' no es un valor permitido.")
|
|
||||||
End If
|
|
||||||
|
|
||||||
Catch
|
|
||||||
' Si ocurre cualquier error inesperado en el bloque Try...
|
|
||||||
' Registra la excepción completa en el log del servidor para diagnóstico.
|
|
||||||
Log(LastException)
|
|
||||||
' Envía una respuesta de error 500 (Internal Server Error) con el mensaje de la excepción.
|
|
||||||
SendErrorResponse(resp, 500, LastException.Message)
|
|
||||||
End Try
|
|
||||||
|
|
||||||
' Este bloque se ejecuta siempre al final, haya habido error o no, *excepto si se usó Return antes*.
|
|
||||||
' Comprueba si el objeto de conexión fue inicializado y sigue abierto.
|
|
||||||
If con <> Null And con.IsInitialized Then
|
|
||||||
' Cierra la conexión para devolverla al pool y que pueda ser reutilizada.
|
|
||||||
' Esto es fundamental para no agotar las conexiones a la base de datos.
|
|
||||||
con.Close
|
|
||||||
End If
|
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
320
DB2Handler.bas
320
DB2Handler.bas
@@ -1,320 +0,0 @@
|
|||||||
B4J=true
|
|
||||||
Group=Default Group
|
|
||||||
ModulesStructureVersion=1
|
|
||||||
Type=Class
|
|
||||||
Version=10
|
|
||||||
@EndOfDesignText@
|
|
||||||
'Handler class
|
|
||||||
Sub Class_Globals
|
|
||||||
' #if VERSION1
|
|
||||||
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
|
|
||||||
Private bc As ByteConverter
|
|
||||||
Private cs As CompressedStreams
|
|
||||||
' #end if
|
|
||||||
Private DateTimeMethods As Map
|
|
||||||
Private Connector As RDCConnector
|
|
||||||
End Sub
|
|
||||||
|
|
||||||
Public Sub Initialize
|
|
||||||
DateTimeMethods = CreateMap(91: "getDate", 92: "getTime", 93: "getTimestamp")
|
|
||||||
End Sub
|
|
||||||
|
|
||||||
Sub Handle(req As ServletRequest, resp As ServletResponse)
|
|
||||||
Log("********************* DB2 ********************")
|
|
||||||
Dim start As Long = DateTime.Now
|
|
||||||
Dim q As String
|
|
||||||
Dim in As InputStream = req.InputStream
|
|
||||||
Dim method As String = req.GetParameter("method")
|
|
||||||
Connector = Main.Connectors.Get("DB2")
|
|
||||||
Dim con As SQL
|
|
||||||
Try
|
|
||||||
con = Connector.GetConnection("DB2")
|
|
||||||
If method = "query2" Then
|
|
||||||
q = ExecuteQuery2("DB2", con, in, resp)
|
|
||||||
'#if VERSION1
|
|
||||||
Else if method = "query" Then
|
|
||||||
in = cs.WrapInputStream(in, "gzip")
|
|
||||||
q = ExecuteQuery("DB2", con, in, resp)
|
|
||||||
Else if method = "batch" Then
|
|
||||||
in = cs.WrapInputStream(in, "gzip")
|
|
||||||
q = ExecuteBatch("DB2", con, in, resp)
|
|
||||||
'#end if
|
|
||||||
Else if method = "batch2" Then
|
|
||||||
q = ExecuteBatch2("DB2", con, in, resp)
|
|
||||||
Else
|
|
||||||
Log("Unknown method: " & method)
|
|
||||||
resp.SendError(500, "unknown method")
|
|
||||||
End If
|
|
||||||
Catch
|
|
||||||
Log(LastException)
|
|
||||||
resp.SendError(500, LastException.Message)
|
|
||||||
End Try
|
|
||||||
If con <> Null And con.IsInitialized Then con.Close
|
|
||||||
Log($"Command: ${q}, took: ${DateTime.Now - start}ms, client=${req.RemoteAddress}"$)
|
|
||||||
End Sub
|
|
||||||
|
|
||||||
Private Sub ExecuteQuery2 (DB As String, con As SQL, in As InputStream, resp As ServletResponse) As String
|
|
||||||
Log("==== ExecuteQuery2 ==== ")
|
|
||||||
Dim ser As B4XSerializator
|
|
||||||
Dim m As Map = ser.ConvertBytesToObject(Bit.InputStreamToBytes(in))
|
|
||||||
Dim cmd As DBCommand = m.Get("command")
|
|
||||||
Dim limit As Int = m.Get("limit")
|
|
||||||
Dim rs As ResultSet = con.ExecQuery2(Connector.GetCommand(DB, cmd.Name), cmd.Parameters)
|
|
||||||
If limit <= 0 Then limit = 0x7fffffff 'max int
|
|
||||||
Dim jrs As JavaObject = rs
|
|
||||||
Dim rsmd As JavaObject = jrs.RunMethod("getMetaData", Null)
|
|
||||||
Dim cols As Int = rs.ColumnCount
|
|
||||||
Dim res As DBResult
|
|
||||||
res.Initialize
|
|
||||||
res.columns.Initialize
|
|
||||||
res.Tag = Null 'without this the Tag properly will not be serializable.
|
|
||||||
For i = 0 To cols - 1
|
|
||||||
res.columns.Put(rs.GetColumnName(i), i)
|
|
||||||
Next
|
|
||||||
res.Rows.Initialize
|
|
||||||
Do While rs.NextRow And limit > 0
|
|
||||||
Dim row(cols) As Object
|
|
||||||
For i = 0 To cols - 1
|
|
||||||
Dim ct As Int = rsmd.RunMethod("getColumnType", Array(i + 1))
|
|
||||||
'check whether it is a blob field
|
|
||||||
If ct = -2 Or ct = 2004 Or ct = -3 Or ct = -4 Then
|
|
||||||
row(i) = rs.GetBlob2(i)
|
|
||||||
Else If ct = 2005 Then
|
|
||||||
row(i) = rs.GetString2(i)
|
|
||||||
Else if ct = 2 Or ct = 3 Then
|
|
||||||
row(i) = rs.GetDouble2(i)
|
|
||||||
Else If DateTimeMethods.ContainsKey(ct) Then
|
|
||||||
Dim SQLTime As JavaObject = jrs.RunMethodJO(DateTimeMethods.Get(ct), Array(i + 1))
|
|
||||||
If SQLTime.IsInitialized Then
|
|
||||||
row(i) = SQLTime.RunMethod("getTime", Null)
|
|
||||||
Else
|
|
||||||
row(i) = Null
|
|
||||||
End If
|
|
||||||
Else
|
|
||||||
row(i) = jrs.RunMethod("getObject", Array(i + 1))
|
|
||||||
End If
|
|
||||||
Next
|
|
||||||
res.Rows.Add(row)
|
|
||||||
Loop
|
|
||||||
rs.Close
|
|
||||||
Dim data() As Byte = ser.ConvertObjectToBytes(res)
|
|
||||||
resp.OutputStream.WriteBytes(data, 0, data.Length)
|
|
||||||
Return "query: " & cmd.Name
|
|
||||||
End Sub
|
|
||||||
|
|
||||||
Private Sub ExecuteBatch2(DB As String, con As SQL, in As InputStream, resp As ServletResponse) As String
|
|
||||||
Dim ser As B4XSerializator
|
|
||||||
Dim m As Map = ser.ConvertBytesToObject(Bit.InputStreamToBytes(in))
|
|
||||||
Dim commands As List = m.Get("commands")
|
|
||||||
Dim res As DBResult
|
|
||||||
res.Initialize
|
|
||||||
res.columns = CreateMap("AffectedRows (N/A)": 0)
|
|
||||||
res.Rows.Initialize
|
|
||||||
res.Tag = Null
|
|
||||||
Try
|
|
||||||
con.BeginTransaction
|
|
||||||
For Each cmd As DBCommand In commands
|
|
||||||
con.ExecNonQuery2(Connector.GetCommand(DB, cmd.Name), _
|
|
||||||
cmd.Parameters)
|
|
||||||
Next
|
|
||||||
res.Rows.Add(Array As Object(0))
|
|
||||||
con.TransactionSuccessful
|
|
||||||
Catch
|
|
||||||
con.Rollback
|
|
||||||
Log(LastException)
|
|
||||||
resp.SendError(500, LastException.Message)
|
|
||||||
End Try
|
|
||||||
Dim data() As Byte = ser.ConvertObjectToBytes(res)
|
|
||||||
resp.OutputStream.WriteBytes(data, 0, data.Length)
|
|
||||||
Return $"batch (size=${commands.Size})"$
|
|
||||||
End Sub
|
|
||||||
|
|
||||||
'#if VERSION1
|
|
||||||
|
|
||||||
Private Sub ExecuteBatch(DB As String, con As SQL, in As InputStream, resp As ServletResponse) As String
|
|
||||||
Dim clientVersion As Float = ReadObject(in) 'ignore
|
|
||||||
Dim numberOfStatements As Int = ReadInt(in)
|
|
||||||
Dim res(numberOfStatements) As Int
|
|
||||||
Try
|
|
||||||
con.BeginTransaction
|
|
||||||
For i = 0 To numberOfStatements - 1
|
|
||||||
Dim queryName As String = ReadObject(in)
|
|
||||||
Dim params As List = ReadList(in)
|
|
||||||
con.ExecNonQuery2(Connector.GetCommand(DB, queryName), _
|
|
||||||
params)
|
|
||||||
Next
|
|
||||||
con.TransactionSuccessful
|
|
||||||
|
|
||||||
Dim out As OutputStream = cs.WrapOutputStream(resp.OutputStream, "gzip")
|
|
||||||
WriteObject(Main.VERSION, out)
|
|
||||||
WriteObject("batch", out)
|
|
||||||
WriteInt(res.Length, out)
|
|
||||||
For Each r As Int In res
|
|
||||||
WriteInt(r, out)
|
|
||||||
Next
|
|
||||||
out.Close
|
|
||||||
Catch
|
|
||||||
con.Rollback
|
|
||||||
Log(LastException)
|
|
||||||
resp.SendError(500, LastException.Message)
|
|
||||||
End Try
|
|
||||||
Return $"batch (size=${numberOfStatements})"$
|
|
||||||
End Sub
|
|
||||||
|
|
||||||
Private Sub ExecuteQuery (DB As String, con As SQL, in As InputStream, resp As ServletResponse) As String
|
|
||||||
Log("==== ExecuteQuery ==== ")
|
|
||||||
Dim clientVersion As Float = ReadObject(in) 'ignore
|
|
||||||
Dim queryName As String = ReadObject(in)
|
|
||||||
Dim limit As Int = ReadInt(in)
|
|
||||||
Dim params As List = ReadList(in)
|
|
||||||
' Log("EL QUERY: |" & queryName & "|")
|
|
||||||
Private theSql As String = Connector.GetCommand(DB, queryName)
|
|
||||||
' Log(theSql)
|
|
||||||
' Log(params)
|
|
||||||
' Log(params.Size)
|
|
||||||
Dim rs As ResultSet = con.ExecQuery2(theSql, params)
|
|
||||||
If limit <= 0 Then limit = 0x7fffffff 'max int
|
|
||||||
Dim jrs As JavaObject = rs
|
|
||||||
Dim rsmd As JavaObject = jrs.RunMethod("getMetaData", Null)
|
|
||||||
Dim cols As Int = rs.ColumnCount
|
|
||||||
Dim out As OutputStream = cs.WrapOutputStream(resp.OutputStream, "gzip")
|
|
||||||
WriteObject(Main.VERSION, out)
|
|
||||||
WriteObject("query", out)
|
|
||||||
WriteInt(rs.ColumnCount, out)
|
|
||||||
' Log($"cols: ${cols}"$)
|
|
||||||
For i = 0 To cols - 1
|
|
||||||
WriteObject(rs.GetColumnName(i), out)
|
|
||||||
Next
|
|
||||||
|
|
||||||
Do While rs.NextRow And limit > 0
|
|
||||||
WriteByte(1, out)
|
|
||||||
For i = 0 To cols - 1
|
|
||||||
Dim ct As Int = rsmd.RunMethod("getColumnType", Array(i + 1))
|
|
||||||
'check whether it is a blob field
|
|
||||||
If ct = -2 Or ct = 2004 Or ct = -3 Or ct = -4 Then
|
|
||||||
WriteObject(rs.GetBlob2(i), out)
|
|
||||||
Else
|
|
||||||
WriteObject(jrs.RunMethod("getObject", Array(i + 1)), out)
|
|
||||||
End If
|
|
||||||
Next
|
|
||||||
Loop
|
|
||||||
WriteByte(0, out)
|
|
||||||
out.Close
|
|
||||||
rs.Close
|
|
||||||
|
|
||||||
Return "query: " & queryName
|
|
||||||
End Sub
|
|
||||||
|
|
||||||
Private Sub WriteByte(value As Byte, out As OutputStream)
|
|
||||||
out.WriteBytes(Array As Byte(value), 0, 1)
|
|
||||||
End Sub
|
|
||||||
|
|
||||||
Private Sub WriteObject(o As Object, out As OutputStream)
|
|
||||||
Dim data() As Byte
|
|
||||||
If o = Null Then
|
|
||||||
out.WriteBytes(Array As Byte(T_NULL), 0, 1)
|
|
||||||
Else If o Is Short Then
|
|
||||||
out.WriteBytes(Array As Byte(T_SHORT), 0, 1)
|
|
||||||
data = bc.ShortsToBytes(Array As Short(o))
|
|
||||||
Else If o Is Int Then
|
|
||||||
out.WriteBytes(Array As Byte(T_INT), 0, 1)
|
|
||||||
data = bc.IntsToBytes(Array As Int(o))
|
|
||||||
Else If o Is Float Then
|
|
||||||
out.WriteBytes(Array As Byte(T_FLOAT), 0, 1)
|
|
||||||
data = bc.FloatsToBytes(Array As Float(o))
|
|
||||||
Else If o Is Double Then
|
|
||||||
out.WriteBytes(Array As Byte(T_DOUBLE), 0, 1)
|
|
||||||
data = bc.DoublesToBytes(Array As Double(o))
|
|
||||||
Else If o Is Long Then
|
|
||||||
out.WriteBytes(Array As Byte(T_LONG), 0, 1)
|
|
||||||
data = bc.LongsToBytes(Array As Long(o))
|
|
||||||
Else If o Is Boolean Then
|
|
||||||
out.WriteBytes(Array As Byte(T_BOOLEAN), 0, 1)
|
|
||||||
Dim b As Boolean = o
|
|
||||||
Dim data(1) As Byte
|
|
||||||
If b Then data(0) = 1 Else data(0) = 0
|
|
||||||
Else If GetType(o) = "[B" Then
|
|
||||||
data = o
|
|
||||||
out.WriteBytes(Array As Byte(T_BLOB), 0, 1)
|
|
||||||
WriteInt(data.Length, out)
|
|
||||||
Else 'If o Is String Then (treat all other values as string)
|
|
||||||
out.WriteBytes(Array As Byte(T_STRING), 0, 1)
|
|
||||||
data = bc.StringToBytes(o, "UTF8")
|
|
||||||
WriteInt(data.Length, out)
|
|
||||||
End If
|
|
||||||
If data.Length > 0 Then out.WriteBytes(data, 0, data.Length)
|
|
||||||
End Sub
|
|
||||||
|
|
||||||
Private Sub ReadObject(In As InputStream) As Object
|
|
||||||
Dim data(1) As Byte
|
|
||||||
In.ReadBytes(data, 0, 1)
|
|
||||||
Select data(0)
|
|
||||||
Case T_NULL
|
|
||||||
Return Null
|
|
||||||
Case T_SHORT
|
|
||||||
Dim data(2) As Byte
|
|
||||||
Return bc.ShortsFromBytes(ReadBytesFully(In, data, data.Length))(0)
|
|
||||||
Case T_INT
|
|
||||||
Dim data(4) As Byte
|
|
||||||
Return bc.IntsFromBytes(ReadBytesFully(In, data, data.Length))(0)
|
|
||||||
Case T_LONG
|
|
||||||
Dim data(8) As Byte
|
|
||||||
Return bc.LongsFromBytes(ReadBytesFully(In, data, data.Length))(0)
|
|
||||||
Case T_FLOAT
|
|
||||||
Dim data(4) As Byte
|
|
||||||
Return bc.FloatsFromBytes(ReadBytesFully(In, data, data.Length))(0)
|
|
||||||
Case T_DOUBLE
|
|
||||||
Dim data(8) As Byte
|
|
||||||
Return bc.DoublesFromBytes(ReadBytesFully(In, data, data.Length))(0)
|
|
||||||
Case T_BOOLEAN
|
|
||||||
Dim b As Byte = ReadByte(In)
|
|
||||||
Return b = 1
|
|
||||||
Case T_BLOB
|
|
||||||
Dim len As Int = ReadInt(In)
|
|
||||||
Dim data(len) As Byte
|
|
||||||
Return ReadBytesFully(In, data, data.Length)
|
|
||||||
Case Else
|
|
||||||
Dim len As Int = ReadInt(In)
|
|
||||||
Dim data(len) As Byte
|
|
||||||
ReadBytesFully(In, data, data.Length)
|
|
||||||
Return BytesToString(data, 0, data.Length, "UTF8")
|
|
||||||
End Select
|
|
||||||
End Sub
|
|
||||||
|
|
||||||
Private Sub ReadBytesFully(In As InputStream, Data() As Byte, Len As Int) As Byte()
|
|
||||||
Dim count = 0, Read As Int
|
|
||||||
Do While count < Len And Read > -1
|
|
||||||
Read = In.ReadBytes(Data, count, Len - count)
|
|
||||||
count = count + Read
|
|
||||||
Loop
|
|
||||||
Return Data
|
|
||||||
End Sub
|
|
||||||
|
|
||||||
Private Sub WriteInt(i As Int, out As OutputStream)
|
|
||||||
Dim data() As Byte
|
|
||||||
data = bc.IntsToBytes(Array As Int(i))
|
|
||||||
out.WriteBytes(data, 0, data.Length)
|
|
||||||
End Sub
|
|
||||||
|
|
||||||
Private Sub ReadInt(In As InputStream) As Int
|
|
||||||
Dim data(4) As Byte
|
|
||||||
Return bc.IntsFromBytes(ReadBytesFully(In, data, data.Length))(0)
|
|
||||||
End Sub
|
|
||||||
|
|
||||||
Private Sub ReadByte(In As InputStream) As Byte
|
|
||||||
Dim data(1) As Byte
|
|
||||||
In.ReadBytes(data, 0, 1)
|
|
||||||
Return data(0)
|
|
||||||
End Sub
|
|
||||||
|
|
||||||
Private Sub ReadList(in As InputStream) As List
|
|
||||||
Dim len As Int = ReadInt(in)
|
|
||||||
Dim l1 As List
|
|
||||||
l1.Initialize
|
|
||||||
For i = 0 To len - 1
|
|
||||||
l1.Add(ReadObject(in))
|
|
||||||
Next
|
|
||||||
Return l1
|
|
||||||
End Sub
|
|
||||||
'#end If
|
|
||||||
319
DB3Handler.bas
319
DB3Handler.bas
@@ -1,319 +0,0 @@
|
|||||||
B4J=true
|
|
||||||
Group=Default Group
|
|
||||||
ModulesStructureVersion=1
|
|
||||||
Type=Class
|
|
||||||
Version=10
|
|
||||||
@EndOfDesignText@
|
|
||||||
'Handler class
|
|
||||||
Sub Class_Globals
|
|
||||||
' #if VERSION1
|
|
||||||
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
|
|
||||||
Private bc As ByteConverter
|
|
||||||
Private cs As CompressedStreams
|
|
||||||
' #end if
|
|
||||||
Private DateTimeMethods As Map
|
|
||||||
Private Connector As RDCConnector
|
|
||||||
End Sub
|
|
||||||
|
|
||||||
Public Sub Initialize
|
|
||||||
DateTimeMethods = CreateMap(91: "getDate", 92: "getTime", 93: "getTimestamp")
|
|
||||||
End Sub
|
|
||||||
|
|
||||||
Sub Handle(req As ServletRequest, resp As ServletResponse)
|
|
||||||
Log("********************* DB3 ********************")
|
|
||||||
Dim start As Long = DateTime.Now
|
|
||||||
Dim q As String
|
|
||||||
Dim in As InputStream = req.InputStream
|
|
||||||
Dim method As String = req.GetParameter("method")
|
|
||||||
Connector = Main.Connectors.Get("DB3")
|
|
||||||
Dim con As SQL
|
|
||||||
Try
|
|
||||||
con = Connector.GetConnection("DB3")
|
|
||||||
If method = "query2" Then
|
|
||||||
q = ExecuteQuery2("DB3", con, in, resp)
|
|
||||||
'#if VERSION1
|
|
||||||
Else if method = "query" Then
|
|
||||||
in = cs.WrapInputStream(in, "gzip")
|
|
||||||
q = ExecuteQuery("DB3", con, in, resp)
|
|
||||||
Else if method = "batch" Then
|
|
||||||
in = cs.WrapInputStream(in, "gzip")
|
|
||||||
q = ExecuteBatch("DB3", con, in, resp)
|
|
||||||
'#end if
|
|
||||||
Else if method = "batch2" Then
|
|
||||||
q = ExecuteBatch2("DB3", con, in, resp)
|
|
||||||
Else
|
|
||||||
Log("Unknown method: " & method)
|
|
||||||
resp.SendError(500, "unknown method")
|
|
||||||
End If
|
|
||||||
Catch
|
|
||||||
Log(LastException)
|
|
||||||
resp.SendError(500, LastException.Message)
|
|
||||||
End Try
|
|
||||||
If con <> Null And con.IsInitialized Then con.Close
|
|
||||||
Log($"Command: ${q}, took: ${DateTime.Now - start}ms, client=${req.RemoteAddress}"$)
|
|
||||||
End Sub
|
|
||||||
|
|
||||||
Private Sub ExecuteQuery2 (DB As String, con As SQL, in As InputStream, resp As ServletResponse) As String
|
|
||||||
Dim ser As B4XSerializator
|
|
||||||
Dim m As Map = ser.ConvertBytesToObject(Bit.InputStreamToBytes(in))
|
|
||||||
Dim cmd As DBCommand = m.Get("command")
|
|
||||||
Dim limit As Int = m.Get("limit")
|
|
||||||
Dim rs As ResultSet = con.ExecQuery2(Connector.GetCommand(DB, cmd.Name), cmd.Parameters)
|
|
||||||
If limit <= 0 Then limit = 0x7fffffff 'max int
|
|
||||||
Dim jrs As JavaObject = rs
|
|
||||||
Dim rsmd As JavaObject = jrs.RunMethod("getMetaData", Null)
|
|
||||||
Dim cols As Int = rs.ColumnCount
|
|
||||||
Dim res As DBResult
|
|
||||||
res.Initialize
|
|
||||||
res.columns.Initialize
|
|
||||||
res.Tag = Null 'without this the Tag properly will not be serializable.
|
|
||||||
For i = 0 To cols - 1
|
|
||||||
res.columns.Put(rs.GetColumnName(i), i)
|
|
||||||
Next
|
|
||||||
res.Rows.Initialize
|
|
||||||
Do While rs.NextRow And limit > 0
|
|
||||||
Dim row(cols) As Object
|
|
||||||
For i = 0 To cols - 1
|
|
||||||
Dim ct As Int = rsmd.RunMethod("getColumnType", Array(i + 1))
|
|
||||||
'check whether it is a blob field
|
|
||||||
If ct = -2 Or ct = 2004 Or ct = -3 Or ct = -4 Then
|
|
||||||
row(i) = rs.GetBlob2(i)
|
|
||||||
Else If ct = 2005 Then
|
|
||||||
row(i) = rs.GetString2(i)
|
|
||||||
Else if ct = 2 Or ct = 3 Then
|
|
||||||
row(i) = rs.GetDouble2(i)
|
|
||||||
Else If DateTimeMethods.ContainsKey(ct) Then
|
|
||||||
Dim SQLTime As JavaObject = jrs.RunMethodJO(DateTimeMethods.Get(ct), Array(i + 1))
|
|
||||||
If SQLTime.IsInitialized Then
|
|
||||||
row(i) = SQLTime.RunMethod("getTime", Null)
|
|
||||||
Else
|
|
||||||
row(i) = Null
|
|
||||||
End If
|
|
||||||
Else
|
|
||||||
row(i) = jrs.RunMethod("getObject", Array(i + 1))
|
|
||||||
End If
|
|
||||||
Next
|
|
||||||
res.Rows.Add(row)
|
|
||||||
Loop
|
|
||||||
rs.Close
|
|
||||||
Dim data() As Byte = ser.ConvertObjectToBytes(res)
|
|
||||||
resp.OutputStream.WriteBytes(data, 0, data.Length)
|
|
||||||
Return "query: " & cmd.Name
|
|
||||||
End Sub
|
|
||||||
|
|
||||||
Private Sub ExecuteBatch2(DB As String, con As SQL, in As InputStream, resp As ServletResponse) As String
|
|
||||||
Dim ser As B4XSerializator
|
|
||||||
Dim m As Map = ser.ConvertBytesToObject(Bit.InputStreamToBytes(in))
|
|
||||||
Dim commands As List = m.Get("commands")
|
|
||||||
Dim res As DBResult
|
|
||||||
res.Initialize
|
|
||||||
res.columns = CreateMap("AffectedRows (N/A)": 0)
|
|
||||||
res.Rows.Initialize
|
|
||||||
res.Tag = Null
|
|
||||||
Try
|
|
||||||
con.BeginTransaction
|
|
||||||
For Each cmd As DBCommand In commands
|
|
||||||
con.ExecNonQuery2(Connector.GetCommand(DB, cmd.Name), _
|
|
||||||
cmd.Parameters)
|
|
||||||
Next
|
|
||||||
res.Rows.Add(Array As Object(0))
|
|
||||||
con.TransactionSuccessful
|
|
||||||
Catch
|
|
||||||
con.Rollback
|
|
||||||
Log(LastException)
|
|
||||||
resp.SendError(500, LastException.Message)
|
|
||||||
End Try
|
|
||||||
Dim data() As Byte = ser.ConvertObjectToBytes(res)
|
|
||||||
resp.OutputStream.WriteBytes(data, 0, data.Length)
|
|
||||||
Return $"batch (size=${commands.Size})"$
|
|
||||||
End Sub
|
|
||||||
|
|
||||||
'#if VERSION1
|
|
||||||
|
|
||||||
Private Sub ExecuteBatch(DB As String, con As SQL, in As InputStream, resp As ServletResponse) As String
|
|
||||||
Dim clientVersion As Float = ReadObject(in) 'ignore
|
|
||||||
Dim numberOfStatements As Int = ReadInt(in)
|
|
||||||
Dim res(numberOfStatements) As Int
|
|
||||||
Try
|
|
||||||
con.BeginTransaction
|
|
||||||
For i = 0 To numberOfStatements - 1
|
|
||||||
Dim queryName As String = ReadObject(in)
|
|
||||||
Dim params As List = ReadList(in)
|
|
||||||
con.ExecNonQuery2(Connector.GetCommand(DB, queryName), _
|
|
||||||
params)
|
|
||||||
Next
|
|
||||||
con.TransactionSuccessful
|
|
||||||
|
|
||||||
Dim out As OutputStream = cs.WrapOutputStream(resp.OutputStream, "gzip")
|
|
||||||
WriteObject(Main.VERSION, out)
|
|
||||||
WriteObject("batch", out)
|
|
||||||
WriteInt(res.Length, out)
|
|
||||||
For Each r As Int In res
|
|
||||||
WriteInt(r, out)
|
|
||||||
Next
|
|
||||||
out.Close
|
|
||||||
Catch
|
|
||||||
con.Rollback
|
|
||||||
Log(LastException)
|
|
||||||
resp.SendError(500, LastException.Message)
|
|
||||||
End Try
|
|
||||||
Return $"batch (size=${numberOfStatements})"$
|
|
||||||
End Sub
|
|
||||||
|
|
||||||
Private Sub ExecuteQuery (DB As String, con As SQL, in As InputStream, resp As ServletResponse) As String
|
|
||||||
' Log("==== ExecuteQuery ==== ")
|
|
||||||
Dim clientVersion As Float = ReadObject(in) 'ignore
|
|
||||||
Dim queryName As String = ReadObject(in)
|
|
||||||
Dim limit As Int = ReadInt(in)
|
|
||||||
Dim params As List = ReadList(in)
|
|
||||||
' Log("EL QUERY: |" & queryName & "|")
|
|
||||||
Private theSql As String = Connector.GetCommand(DB, queryName)
|
|
||||||
' Log(theSql)
|
|
||||||
' Log(params)
|
|
||||||
' Log(params.Size)
|
|
||||||
Dim rs As ResultSet = con.ExecQuery2(theSql, params)
|
|
||||||
If limit <= 0 Then limit = 0x7fffffff 'max int
|
|
||||||
Dim jrs As JavaObject = rs
|
|
||||||
Dim rsmd As JavaObject = jrs.RunMethod("getMetaData", Null)
|
|
||||||
Dim cols As Int = rs.ColumnCount
|
|
||||||
Dim out As OutputStream = cs.WrapOutputStream(resp.OutputStream, "gzip")
|
|
||||||
WriteObject(Main.VERSION, out)
|
|
||||||
WriteObject("query", out)
|
|
||||||
WriteInt(rs.ColumnCount, out)
|
|
||||||
' Log($"cols: ${cols}"$)
|
|
||||||
For i = 0 To cols - 1
|
|
||||||
WriteObject(rs.GetColumnName(i), out)
|
|
||||||
Next
|
|
||||||
|
|
||||||
Do While rs.NextRow And limit > 0
|
|
||||||
WriteByte(1, out)
|
|
||||||
For i = 0 To cols - 1
|
|
||||||
Dim ct As Int = rsmd.RunMethod("getColumnType", Array(i + 1))
|
|
||||||
'check whether it is a blob field
|
|
||||||
If ct = -2 Or ct = 2004 Or ct = -3 Or ct = -4 Then
|
|
||||||
WriteObject(rs.GetBlob2(i), out)
|
|
||||||
Else
|
|
||||||
WriteObject(jrs.RunMethod("getObject", Array(i + 1)), out)
|
|
||||||
End If
|
|
||||||
Next
|
|
||||||
Loop
|
|
||||||
WriteByte(0, out)
|
|
||||||
out.Close
|
|
||||||
rs.Close
|
|
||||||
|
|
||||||
Return "query: " & queryName
|
|
||||||
End Sub
|
|
||||||
|
|
||||||
Private Sub WriteByte(value As Byte, out As OutputStream)
|
|
||||||
out.WriteBytes(Array As Byte(value), 0, 1)
|
|
||||||
End Sub
|
|
||||||
|
|
||||||
Private Sub WriteObject(o As Object, out As OutputStream)
|
|
||||||
Dim data() As Byte
|
|
||||||
If o = Null Then
|
|
||||||
out.WriteBytes(Array As Byte(T_NULL), 0, 1)
|
|
||||||
Else If o Is Short Then
|
|
||||||
out.WriteBytes(Array As Byte(T_SHORT), 0, 1)
|
|
||||||
data = bc.ShortsToBytes(Array As Short(o))
|
|
||||||
Else If o Is Int Then
|
|
||||||
out.WriteBytes(Array As Byte(T_INT), 0, 1)
|
|
||||||
data = bc.IntsToBytes(Array As Int(o))
|
|
||||||
Else If o Is Float Then
|
|
||||||
out.WriteBytes(Array As Byte(T_FLOAT), 0, 1)
|
|
||||||
data = bc.FloatsToBytes(Array As Float(o))
|
|
||||||
Else If o Is Double Then
|
|
||||||
out.WriteBytes(Array As Byte(T_DOUBLE), 0, 1)
|
|
||||||
data = bc.DoublesToBytes(Array As Double(o))
|
|
||||||
Else If o Is Long Then
|
|
||||||
out.WriteBytes(Array As Byte(T_LONG), 0, 1)
|
|
||||||
data = bc.LongsToBytes(Array As Long(o))
|
|
||||||
Else If o Is Boolean Then
|
|
||||||
out.WriteBytes(Array As Byte(T_BOOLEAN), 0, 1)
|
|
||||||
Dim b As Boolean = o
|
|
||||||
Dim data(1) As Byte
|
|
||||||
If b Then data(0) = 1 Else data(0) = 0
|
|
||||||
Else If GetType(o) = "[B" Then
|
|
||||||
data = o
|
|
||||||
out.WriteBytes(Array As Byte(T_BLOB), 0, 1)
|
|
||||||
WriteInt(data.Length, out)
|
|
||||||
Else 'If o Is String Then (treat all other values as string)
|
|
||||||
out.WriteBytes(Array As Byte(T_STRING), 0, 1)
|
|
||||||
data = bc.StringToBytes(o, "UTF8")
|
|
||||||
WriteInt(data.Length, out)
|
|
||||||
End If
|
|
||||||
If data.Length > 0 Then out.WriteBytes(data, 0, data.Length)
|
|
||||||
End Sub
|
|
||||||
|
|
||||||
Private Sub ReadObject(In As InputStream) As Object
|
|
||||||
Dim data(1) As Byte
|
|
||||||
In.ReadBytes(data, 0, 1)
|
|
||||||
Select data(0)
|
|
||||||
Case T_NULL
|
|
||||||
Return Null
|
|
||||||
Case T_SHORT
|
|
||||||
Dim data(2) As Byte
|
|
||||||
Return bc.ShortsFromBytes(ReadBytesFully(In, data, data.Length))(0)
|
|
||||||
Case T_INT
|
|
||||||
Dim data(4) As Byte
|
|
||||||
Return bc.IntsFromBytes(ReadBytesFully(In, data, data.Length))(0)
|
|
||||||
Case T_LONG
|
|
||||||
Dim data(8) As Byte
|
|
||||||
Return bc.LongsFromBytes(ReadBytesFully(In, data, data.Length))(0)
|
|
||||||
Case T_FLOAT
|
|
||||||
Dim data(4) As Byte
|
|
||||||
Return bc.FloatsFromBytes(ReadBytesFully(In, data, data.Length))(0)
|
|
||||||
Case T_DOUBLE
|
|
||||||
Dim data(8) As Byte
|
|
||||||
Return bc.DoublesFromBytes(ReadBytesFully(In, data, data.Length))(0)
|
|
||||||
Case T_BOOLEAN
|
|
||||||
Dim b As Byte = ReadByte(In)
|
|
||||||
Return b = 1
|
|
||||||
Case T_BLOB
|
|
||||||
Dim len As Int = ReadInt(In)
|
|
||||||
Dim data(len) As Byte
|
|
||||||
Return ReadBytesFully(In, data, data.Length)
|
|
||||||
Case Else
|
|
||||||
Dim len As Int = ReadInt(In)
|
|
||||||
Dim data(len) As Byte
|
|
||||||
ReadBytesFully(In, data, data.Length)
|
|
||||||
Return BytesToString(data, 0, data.Length, "UTF8")
|
|
||||||
End Select
|
|
||||||
End Sub
|
|
||||||
|
|
||||||
Private Sub ReadBytesFully(In As InputStream, Data() As Byte, Len As Int) As Byte()
|
|
||||||
Dim count = 0, Read As Int
|
|
||||||
Do While count < Len And Read > -1
|
|
||||||
Read = In.ReadBytes(Data, count, Len - count)
|
|
||||||
count = count + Read
|
|
||||||
Loop
|
|
||||||
Return Data
|
|
||||||
End Sub
|
|
||||||
|
|
||||||
Private Sub WriteInt(i As Int, out As OutputStream)
|
|
||||||
Dim data() As Byte
|
|
||||||
data = bc.IntsToBytes(Array As Int(i))
|
|
||||||
out.WriteBytes(data, 0, data.Length)
|
|
||||||
End Sub
|
|
||||||
|
|
||||||
Private Sub ReadInt(In As InputStream) As Int
|
|
||||||
Dim data(4) As Byte
|
|
||||||
Return bc.IntsFromBytes(ReadBytesFully(In, data, data.Length))(0)
|
|
||||||
End Sub
|
|
||||||
|
|
||||||
Private Sub ReadByte(In As InputStream) As Byte
|
|
||||||
Dim data(1) As Byte
|
|
||||||
In.ReadBytes(data, 0, 1)
|
|
||||||
Return data(0)
|
|
||||||
End Sub
|
|
||||||
|
|
||||||
Private Sub ReadList(in As InputStream) As List
|
|
||||||
Dim len As Int = ReadInt(in)
|
|
||||||
Dim l1 As List
|
|
||||||
l1.Initialize
|
|
||||||
For i = 0 To len - 1
|
|
||||||
l1.Add(ReadObject(in))
|
|
||||||
Next
|
|
||||||
Return l1
|
|
||||||
End Sub
|
|
||||||
'#end If
|
|
||||||
319
DB4Handler.bas
319
DB4Handler.bas
@@ -1,319 +0,0 @@
|
|||||||
B4J=true
|
|
||||||
Group=Default Group
|
|
||||||
ModulesStructureVersion=1
|
|
||||||
Type=Class
|
|
||||||
Version=10
|
|
||||||
@EndOfDesignText@
|
|
||||||
'Handler class
|
|
||||||
Sub Class_Globals
|
|
||||||
' #if VERSION1
|
|
||||||
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
|
|
||||||
Private bc As ByteConverter
|
|
||||||
Private cs As CompressedStreams
|
|
||||||
' #end if
|
|
||||||
Private DateTimeMethods As Map
|
|
||||||
Private Connector As RDCConnector
|
|
||||||
End Sub
|
|
||||||
|
|
||||||
Public Sub Initialize
|
|
||||||
DateTimeMethods = CreateMap(91: "getDate", 92: "getTime", 93: "getTimestamp")
|
|
||||||
End Sub
|
|
||||||
|
|
||||||
Sub Handle(req As ServletRequest, resp As ServletResponse)
|
|
||||||
Log("********************* DB4 ********************")
|
|
||||||
Dim start As Long = DateTime.Now
|
|
||||||
Dim q As String
|
|
||||||
Dim in As InputStream = req.InputStream
|
|
||||||
Dim method As String = req.GetParameter("method")
|
|
||||||
Connector = Main.Connectors.Get("DB4")
|
|
||||||
Dim con As SQL
|
|
||||||
Try
|
|
||||||
con = Connector.GetConnection("DB4")
|
|
||||||
If method = "query2" Then
|
|
||||||
q = ExecuteQuery2("DB4", con, in, resp)
|
|
||||||
'#if VERSION1
|
|
||||||
Else if method = "query" Then
|
|
||||||
in = cs.WrapInputStream(in, "gzip")
|
|
||||||
q = ExecuteQuery("DB4", con, in, resp)
|
|
||||||
Else if method = "batch" Then
|
|
||||||
in = cs.WrapInputStream(in, "gzip")
|
|
||||||
q = ExecuteBatch("DB4", con, in, resp)
|
|
||||||
'#end if
|
|
||||||
Else if method = "batch2" Then
|
|
||||||
q = ExecuteBatch2("DB4", con, in, resp)
|
|
||||||
Else
|
|
||||||
Log("Unknown method: " & method)
|
|
||||||
resp.SendError(500, "unknown method")
|
|
||||||
End If
|
|
||||||
Catch
|
|
||||||
Log(LastException)
|
|
||||||
resp.SendError(500, LastException.Message)
|
|
||||||
End Try
|
|
||||||
If con <> Null And con.IsInitialized Then con.Close
|
|
||||||
Log($"Command: ${q}, took: ${DateTime.Now - start}ms, client=${req.RemoteAddress}"$)
|
|
||||||
End Sub
|
|
||||||
|
|
||||||
Private Sub ExecuteQuery2 (DB As String, con As SQL, in As InputStream, resp As ServletResponse) As String
|
|
||||||
Dim ser As B4XSerializator
|
|
||||||
Dim m As Map = ser.ConvertBytesToObject(Bit.InputStreamToBytes(in))
|
|
||||||
Dim cmd As DBCommand = m.Get("command")
|
|
||||||
Dim limit As Int = m.Get("limit")
|
|
||||||
Dim rs As ResultSet = con.ExecQuery2(Connector.GetCommand(DB, cmd.Name), cmd.Parameters)
|
|
||||||
If limit <= 0 Then limit = 0x7fffffff 'max int
|
|
||||||
Dim jrs As JavaObject = rs
|
|
||||||
Dim rsmd As JavaObject = jrs.RunMethod("getMetaData", Null)
|
|
||||||
Dim cols As Int = rs.ColumnCount
|
|
||||||
Dim res As DBResult
|
|
||||||
res.Initialize
|
|
||||||
res.columns.Initialize
|
|
||||||
res.Tag = Null 'without this the Tag properly will not be serializable.
|
|
||||||
For i = 0 To cols - 1
|
|
||||||
res.columns.Put(rs.GetColumnName(i), i)
|
|
||||||
Next
|
|
||||||
res.Rows.Initialize
|
|
||||||
Do While rs.NextRow And limit > 0
|
|
||||||
Dim row(cols) As Object
|
|
||||||
For i = 0 To cols - 1
|
|
||||||
Dim ct As Int = rsmd.RunMethod("getColumnType", Array(i + 1))
|
|
||||||
'check whether it is a blob field
|
|
||||||
If ct = -2 Or ct = 2004 Or ct = -3 Or ct = -4 Then
|
|
||||||
row(i) = rs.GetBlob2(i)
|
|
||||||
Else If ct = 2005 Then
|
|
||||||
row(i) = rs.GetString2(i)
|
|
||||||
Else if ct = 2 Or ct = 3 Then
|
|
||||||
row(i) = rs.GetDouble2(i)
|
|
||||||
Else If DateTimeMethods.ContainsKey(ct) Then
|
|
||||||
Dim SQLTime As JavaObject = jrs.RunMethodJO(DateTimeMethods.Get(ct), Array(i + 1))
|
|
||||||
If SQLTime.IsInitialized Then
|
|
||||||
row(i) = SQLTime.RunMethod("getTime", Null)
|
|
||||||
Else
|
|
||||||
row(i) = Null
|
|
||||||
End If
|
|
||||||
Else
|
|
||||||
row(i) = jrs.RunMethod("getObject", Array(i + 1))
|
|
||||||
End If
|
|
||||||
Next
|
|
||||||
res.Rows.Add(row)
|
|
||||||
Loop
|
|
||||||
rs.Close
|
|
||||||
Dim data() As Byte = ser.ConvertObjectToBytes(res)
|
|
||||||
resp.OutputStream.WriteBytes(data, 0, data.Length)
|
|
||||||
Return "query: " & cmd.Name
|
|
||||||
End Sub
|
|
||||||
|
|
||||||
Private Sub ExecuteBatch2(DB As String, con As SQL, in As InputStream, resp As ServletResponse) As String
|
|
||||||
Dim ser As B4XSerializator
|
|
||||||
Dim m As Map = ser.ConvertBytesToObject(Bit.InputStreamToBytes(in))
|
|
||||||
Dim commands As List = m.Get("commands")
|
|
||||||
Dim res As DBResult
|
|
||||||
res.Initialize
|
|
||||||
res.columns = CreateMap("AffectedRows (N/A)": 0)
|
|
||||||
res.Rows.Initialize
|
|
||||||
res.Tag = Null
|
|
||||||
Try
|
|
||||||
con.BeginTransaction
|
|
||||||
For Each cmd As DBCommand In commands
|
|
||||||
con.ExecNonQuery2(Connector.GetCommand(DB, cmd.Name), _
|
|
||||||
cmd.Parameters)
|
|
||||||
Next
|
|
||||||
res.Rows.Add(Array As Object(0))
|
|
||||||
con.TransactionSuccessful
|
|
||||||
Catch
|
|
||||||
con.Rollback
|
|
||||||
Log(LastException)
|
|
||||||
resp.SendError(500, LastException.Message)
|
|
||||||
End Try
|
|
||||||
Dim data() As Byte = ser.ConvertObjectToBytes(res)
|
|
||||||
resp.OutputStream.WriteBytes(data, 0, data.Length)
|
|
||||||
Return $"batch (size=${commands.Size})"$
|
|
||||||
End Sub
|
|
||||||
|
|
||||||
'#if VERSION1
|
|
||||||
|
|
||||||
Private Sub ExecuteBatch(DB As String, con As SQL, in As InputStream, resp As ServletResponse) As String
|
|
||||||
Dim clientVersion As Float = ReadObject(in) 'ignore
|
|
||||||
Dim numberOfStatements As Int = ReadInt(in)
|
|
||||||
Dim res(numberOfStatements) As Int
|
|
||||||
Try
|
|
||||||
con.BeginTransaction
|
|
||||||
For i = 0 To numberOfStatements - 1
|
|
||||||
Dim queryName As String = ReadObject(in)
|
|
||||||
Dim params As List = ReadList(in)
|
|
||||||
con.ExecNonQuery2(Connector.GetCommand(DB, queryName), _
|
|
||||||
params)
|
|
||||||
Next
|
|
||||||
con.TransactionSuccessful
|
|
||||||
|
|
||||||
Dim out As OutputStream = cs.WrapOutputStream(resp.OutputStream, "gzip")
|
|
||||||
WriteObject(Main.VERSION, out)
|
|
||||||
WriteObject("batch", out)
|
|
||||||
WriteInt(res.Length, out)
|
|
||||||
For Each r As Int In res
|
|
||||||
WriteInt(r, out)
|
|
||||||
Next
|
|
||||||
out.Close
|
|
||||||
Catch
|
|
||||||
con.Rollback
|
|
||||||
Log(LastException)
|
|
||||||
resp.SendError(500, LastException.Message)
|
|
||||||
End Try
|
|
||||||
Return $"batch (size=${numberOfStatements})"$
|
|
||||||
End Sub
|
|
||||||
|
|
||||||
Private Sub ExecuteQuery (DB As String, con As SQL, in As InputStream, resp As ServletResponse) As String
|
|
||||||
' Log("==== ExecuteQuery ==== ")
|
|
||||||
Dim clientVersion As Float = ReadObject(in) 'ignore
|
|
||||||
Dim queryName As String = ReadObject(in)
|
|
||||||
Dim limit As Int = ReadInt(in)
|
|
||||||
Dim params As List = ReadList(in)
|
|
||||||
' Log("EL QUERY: |" & queryName & "|")
|
|
||||||
Private theSql As String = Connector.GetCommand(DB, queryName)
|
|
||||||
' Log(theSql)
|
|
||||||
' Log(params)
|
|
||||||
' Log(params.Size)
|
|
||||||
Dim rs As ResultSet = con.ExecQuery2(theSql, params)
|
|
||||||
If limit <= 0 Then limit = 0x7fffffff 'max int
|
|
||||||
Dim jrs As JavaObject = rs
|
|
||||||
Dim rsmd As JavaObject = jrs.RunMethod("getMetaData", Null)
|
|
||||||
Dim cols As Int = rs.ColumnCount
|
|
||||||
Dim out As OutputStream = cs.WrapOutputStream(resp.OutputStream, "gzip")
|
|
||||||
WriteObject(Main.VERSION, out)
|
|
||||||
WriteObject("query", out)
|
|
||||||
WriteInt(rs.ColumnCount, out)
|
|
||||||
' Log($"cols: ${cols}"$)
|
|
||||||
For i = 0 To cols - 1
|
|
||||||
WriteObject(rs.GetColumnName(i), out)
|
|
||||||
Next
|
|
||||||
|
|
||||||
Do While rs.NextRow And limit > 0
|
|
||||||
WriteByte(1, out)
|
|
||||||
For i = 0 To cols - 1
|
|
||||||
Dim ct As Int = rsmd.RunMethod("getColumnType", Array(i + 1))
|
|
||||||
'check whether it is a blob field
|
|
||||||
If ct = -2 Or ct = 2004 Or ct = -3 Or ct = -4 Then
|
|
||||||
WriteObject(rs.GetBlob2(i), out)
|
|
||||||
Else
|
|
||||||
WriteObject(jrs.RunMethod("getObject", Array(i + 1)), out)
|
|
||||||
End If
|
|
||||||
Next
|
|
||||||
Loop
|
|
||||||
WriteByte(0, out)
|
|
||||||
out.Close
|
|
||||||
rs.Close
|
|
||||||
|
|
||||||
Return "query: " & queryName
|
|
||||||
End Sub
|
|
||||||
|
|
||||||
Private Sub WriteByte(value As Byte, out As OutputStream)
|
|
||||||
out.WriteBytes(Array As Byte(value), 0, 1)
|
|
||||||
End Sub
|
|
||||||
|
|
||||||
Private Sub WriteObject(o As Object, out As OutputStream)
|
|
||||||
Dim data() As Byte
|
|
||||||
If o = Null Then
|
|
||||||
out.WriteBytes(Array As Byte(T_NULL), 0, 1)
|
|
||||||
Else If o Is Short Then
|
|
||||||
out.WriteBytes(Array As Byte(T_SHORT), 0, 1)
|
|
||||||
data = bc.ShortsToBytes(Array As Short(o))
|
|
||||||
Else If o Is Int Then
|
|
||||||
out.WriteBytes(Array As Byte(T_INT), 0, 1)
|
|
||||||
data = bc.IntsToBytes(Array As Int(o))
|
|
||||||
Else If o Is Float Then
|
|
||||||
out.WriteBytes(Array As Byte(T_FLOAT), 0, 1)
|
|
||||||
data = bc.FloatsToBytes(Array As Float(o))
|
|
||||||
Else If o Is Double Then
|
|
||||||
out.WriteBytes(Array As Byte(T_DOUBLE), 0, 1)
|
|
||||||
data = bc.DoublesToBytes(Array As Double(o))
|
|
||||||
Else If o Is Long Then
|
|
||||||
out.WriteBytes(Array As Byte(T_LONG), 0, 1)
|
|
||||||
data = bc.LongsToBytes(Array As Long(o))
|
|
||||||
Else If o Is Boolean Then
|
|
||||||
out.WriteBytes(Array As Byte(T_BOOLEAN), 0, 1)
|
|
||||||
Dim b As Boolean = o
|
|
||||||
Dim data(1) As Byte
|
|
||||||
If b Then data(0) = 1 Else data(0) = 0
|
|
||||||
Else If GetType(o) = "[B" Then
|
|
||||||
data = o
|
|
||||||
out.WriteBytes(Array As Byte(T_BLOB), 0, 1)
|
|
||||||
WriteInt(data.Length, out)
|
|
||||||
Else 'If o Is String Then (treat all other values as string)
|
|
||||||
out.WriteBytes(Array As Byte(T_STRING), 0, 1)
|
|
||||||
data = bc.StringToBytes(o, "UTF8")
|
|
||||||
WriteInt(data.Length, out)
|
|
||||||
End If
|
|
||||||
If data.Length > 0 Then out.WriteBytes(data, 0, data.Length)
|
|
||||||
End Sub
|
|
||||||
|
|
||||||
Private Sub ReadObject(In As InputStream) As Object
|
|
||||||
Dim data(1) As Byte
|
|
||||||
In.ReadBytes(data, 0, 1)
|
|
||||||
Select data(0)
|
|
||||||
Case T_NULL
|
|
||||||
Return Null
|
|
||||||
Case T_SHORT
|
|
||||||
Dim data(2) As Byte
|
|
||||||
Return bc.ShortsFromBytes(ReadBytesFully(In, data, data.Length))(0)
|
|
||||||
Case T_INT
|
|
||||||
Dim data(4) As Byte
|
|
||||||
Return bc.IntsFromBytes(ReadBytesFully(In, data, data.Length))(0)
|
|
||||||
Case T_LONG
|
|
||||||
Dim data(8) As Byte
|
|
||||||
Return bc.LongsFromBytes(ReadBytesFully(In, data, data.Length))(0)
|
|
||||||
Case T_FLOAT
|
|
||||||
Dim data(4) As Byte
|
|
||||||
Return bc.FloatsFromBytes(ReadBytesFully(In, data, data.Length))(0)
|
|
||||||
Case T_DOUBLE
|
|
||||||
Dim data(8) As Byte
|
|
||||||
Return bc.DoublesFromBytes(ReadBytesFully(In, data, data.Length))(0)
|
|
||||||
Case T_BOOLEAN
|
|
||||||
Dim b As Byte = ReadByte(In)
|
|
||||||
Return b = 1
|
|
||||||
Case T_BLOB
|
|
||||||
Dim len As Int = ReadInt(In)
|
|
||||||
Dim data(len) As Byte
|
|
||||||
Return ReadBytesFully(In, data, data.Length)
|
|
||||||
Case Else
|
|
||||||
Dim len As Int = ReadInt(In)
|
|
||||||
Dim data(len) As Byte
|
|
||||||
ReadBytesFully(In, data, data.Length)
|
|
||||||
Return BytesToString(data, 0, data.Length, "UTF8")
|
|
||||||
End Select
|
|
||||||
End Sub
|
|
||||||
|
|
||||||
Private Sub ReadBytesFully(In As InputStream, Data() As Byte, Len As Int) As Byte()
|
|
||||||
Dim count = 0, Read As Int
|
|
||||||
Do While count < Len And Read > -1
|
|
||||||
Read = In.ReadBytes(Data, count, Len - count)
|
|
||||||
count = count + Read
|
|
||||||
Loop
|
|
||||||
Return Data
|
|
||||||
End Sub
|
|
||||||
|
|
||||||
Private Sub WriteInt(i As Int, out As OutputStream)
|
|
||||||
Dim data() As Byte
|
|
||||||
data = bc.IntsToBytes(Array As Int(i))
|
|
||||||
out.WriteBytes(data, 0, data.Length)
|
|
||||||
End Sub
|
|
||||||
|
|
||||||
Private Sub ReadInt(In As InputStream) As Int
|
|
||||||
Dim data(4) As Byte
|
|
||||||
Return bc.IntsFromBytes(ReadBytesFully(In, data, data.Length))(0)
|
|
||||||
End Sub
|
|
||||||
|
|
||||||
Private Sub ReadByte(In As InputStream) As Byte
|
|
||||||
Dim data(1) As Byte
|
|
||||||
In.ReadBytes(data, 0, 1)
|
|
||||||
Return data(0)
|
|
||||||
End Sub
|
|
||||||
|
|
||||||
Private Sub ReadList(in As InputStream) As List
|
|
||||||
Dim len As Int = ReadInt(in)
|
|
||||||
Dim l1 As List
|
|
||||||
l1.Initialize
|
|
||||||
For i = 0 To len - 1
|
|
||||||
l1.Add(ReadObject(in))
|
|
||||||
Next
|
|
||||||
Return l1
|
|
||||||
End Sub
|
|
||||||
'#end If
|
|
||||||
@@ -35,91 +35,136 @@ End Sub
|
|||||||
|
|
||||||
' Método principal que maneja cada petición HTTP que llega a este servlet.
|
' Método principal que maneja cada petición HTTP que llega a este servlet.
|
||||||
Sub Handle(req As ServletRequest, resp As ServletResponse)
|
Sub Handle(req As ServletRequest, resp As ServletResponse)
|
||||||
' === INICIO DE LA LÓGICA DINÁMICA ===
|
' === INICIO DE LA LÓGICA DINÁMICA (Extracción de dbKey de la URL) ===
|
||||||
' Extrae la URI completa de la petición (ej. /DB1/endpoint).
|
|
||||||
Dim URI As String = req.RequestURI
|
Dim URI As String = req.RequestURI
|
||||||
' Variable para almacenar la "llave" o identificador de la base de datos (ej. "DB1").
|
Dim dbKey As String ' Usamos dbKey para consistencia con tu código original.
|
||||||
Dim dbKey As String
|
|
||||||
|
|
||||||
' Comprueba si la URI tiene contenido y empieza con "/".
|
|
||||||
If URI.Length > 1 And URI.StartsWith("/") Then
|
If URI.Length > 1 And URI.StartsWith("/") Then
|
||||||
' Extrae la parte de la URI que viene después del primer "/".
|
dbKey = URI.Substring(1) '[DBHandlerB4X.bas.txt, 51]
|
||||||
dbKey = URI.Substring(1)
|
|
||||||
' Si la llave contiene más "/", se queda solo con la primera parte.
|
|
||||||
' Esto permite URLs como /DB1/clientes o /DB2/productos, extrayendo "DB1" o "DB2".
|
|
||||||
If dbKey.Contains("/") Then
|
If dbKey.Contains("/") Then
|
||||||
dbKey = dbKey.SubString2(0, dbKey.IndexOf("/"))
|
dbKey = dbKey.SubString2(0, dbKey.IndexOf("/")) '[DBHandlerB4X.bas.txt, 51]
|
||||||
End If
|
End If
|
||||||
Else
|
Else
|
||||||
' Si la URI está vacía o es "/", usa "DB1" como la base de datos por defecto.
|
dbKey = "DB1" '[DBHandlerB4X.bas.txt, 51]
|
||||||
dbKey = "DB1"
|
|
||||||
End If
|
End If
|
||||||
|
dbKey = dbKey.ToUpperCase '[DBHandlerB4X.bas.txt, 52]
|
||||||
|
|
||||||
' Convierte la llave a mayúsculas para que no sea sensible a mayúsculas/minúsculas (ej. "db1" se convierte en "DB1").
|
If Main.Connectors.ContainsKey(dbKey) = False Then '[DBHandlerB4X.bas.txt, 52]
|
||||||
dbKey = dbKey.ToUpperCase
|
Dim ErrorMsg As String = $"Invalid DB key specified in URL: '${dbKey}'. Valid keys are: ${Main.listaDeCP}"$ '[DBHandlerB4X.bas.txt, 52]
|
||||||
|
Log(ErrorMsg) '[DBHandlerB4X.bas.txt, 52]
|
||||||
' Verifica si la llave de la base de datos extraída existe en la configuración de conectores.
|
SendPlainTextError(resp, 400, ErrorMsg) '[DBHandlerB4X.bas.txt, 52]
|
||||||
If Main.Connectors.ContainsKey(dbKey) = False Then
|
' Aquí no se necesita CleanupAndLog, ya que el contador no se ha incrementado
|
||||||
' Si no existe, crea un mensaje de error claro.
|
' y no se ha obtenido ninguna conexión del pool aún.
|
||||||
Dim ErrorMsg As String = $"Invalid DB key specified in URL: '${dbKey}'. Valid keys are: ${Main.listaDeCP}"$
|
|
||||||
' Registra el error en el log del servidor.
|
|
||||||
Log(ErrorMsg)
|
|
||||||
' Envía una respuesta de error 400 (Bad Request) al cliente en formato de texto plano.
|
|
||||||
SendPlainTextError(resp, 400, ErrorMsg)
|
|
||||||
' Termina la ejecución de este método.
|
|
||||||
Return
|
Return
|
||||||
End If
|
End If
|
||||||
' === FIN DE LA LÓGICA DINÁMICA ===
|
' === FIN DE LA LÓGICA DINÁMICA ===
|
||||||
|
|
||||||
Log("********************* " & dbKey & " ********************")
|
Log("********************* " & dbKey & " ********************") '[DBHandlerB4X.bas.txt, 53]
|
||||||
' Guarda el tiempo de inicio para medir la duración de la petición.
|
|
||||||
Dim start As Long = DateTime.Now
|
Dim start As Long = DateTime.Now '[___new 3.txt, 203]
|
||||||
' Variable para almacenar el nombre del comando SQL a ejecutar.
|
|
||||||
Dim q As String
|
' --- INICIO: Conteo de peticiones activas para esta dbKey (Incrementar) ---
|
||||||
' Obtiene el stream de entrada de la petición, que contiene los datos enviados por el cliente.
|
Dim currentActiveRequests As Int = GlobalParameters.ActiveRequestsCountByDB.GetDefault(dbKey, 0) '[___new 3.txt, 205]
|
||||||
Dim in As InputStream = req.InputStream
|
GlobalParameters.ActiveRequestsCountByDB.Put(dbKey, currentActiveRequests + 1) '[___new 3.txt, 205]
|
||||||
' Obtiene el parámetro "method" de la URL (ej. ?method=query2).
|
Dim requestsBeforeDecrement As Int = currentActiveRequests + 1 '[___new 3.txt, 207]
|
||||||
Dim method As String = req.GetParameter("method")
|
' --- FIN: Conteo de peticiones activas ---
|
||||||
' Obtiene el conector correspondiente a la base de datos seleccionada.
|
|
||||||
Connector = Main.Connectors.Get(dbKey)
|
' Declaraciones de variables con alcance en toda la subrutina para la limpieza.
|
||||||
' Declara la variable para la conexión a la base de datos.
|
Dim q As String = "unknown_b4x_command" ' Nombre del comando para el log, con valor por defecto.
|
||||||
Dim con As SQL
|
Dim con As SQL ' La conexión a la BD, se inicializará más tarde.
|
||||||
Try
|
Dim duration As Long ' La duración de la petición, calculada antes del log.
|
||||||
' Obtiene una conexión del pool de conexiones.
|
Dim poolBusyConnectionsForLog As Int = 0 ' Contiene el número de conexiones ocupadas del pool.
|
||||||
con = Connector.GetConnection(dbKey)
|
|
||||||
Log("Metodo: " & method)
|
Try ' --- INICIO: Bloque Try que envuelve la lógica principal del Handler ---
|
||||||
' Determina qué función ejecutar basándose en el parámetro "method".
|
Dim in As InputStream = req.InputStream '[DBHandlerB4X.bas.txt, 53]
|
||||||
|
Dim method As String = req.GetParameter("method") '[DBHandlerB4X.bas.txt, 53]
|
||||||
|
Connector = Main.Connectors.Get(dbKey) '[DBHandlerB4X.bas.txt, 54]
|
||||||
|
|
||||||
|
con = Connector.GetConnection(dbKey) ' La conexión a la BD se obtiene aquí. [DBHandlerB4X.bas.txt, 54]
|
||||||
|
|
||||||
|
' <<<< ¡BUSY_CONNECTIONS YA SE CAPTURABA BIEN! >>>>
|
||||||
|
If Connector.IsInitialized Then
|
||||||
|
Dim poolStats As Map = Connector.GetPoolStats '[___new 3.txt, 204]
|
||||||
|
If poolStats.ContainsKey("BusyConnections") Then
|
||||||
|
poolBusyConnectionsForLog = poolStats.Get("BusyConnections") ' Capturamos el valor.
|
||||||
|
End If
|
||||||
|
End If
|
||||||
|
' <<<< ¡FIN DE CAPTURA! >>>>
|
||||||
|
|
||||||
|
Log("Metodo: " & method) '[DBHandlerB4X.bas.txt, 54]
|
||||||
|
|
||||||
If method = "query2" Then
|
If method = "query2" Then
|
||||||
' Ejecuta una consulta usando el protocolo más nuevo (B4XSerializator).
|
q = ExecuteQuery2(dbKey, con, in, resp) '[DBHandlerB4X.bas.txt, 54]
|
||||||
q = ExecuteQuery2(dbKey, con, in, resp)
|
If q = "error" Then ' Si ExecuteQuery2 devolvió un error de validación.
|
||||||
|
duration = DateTime.Now - start
|
||||||
|
CleanupAndLog(dbKey, "error_in_" & method, duration, req.RemoteAddress, requestsBeforeDecrement, poolBusyConnectionsForLog, con)
|
||||||
|
Return
|
||||||
|
End If
|
||||||
'#if VERSION1
|
'#if VERSION1
|
||||||
Else if method = "query" Then
|
Else if method = "query" Then
|
||||||
' Protocolo antiguo: descomprime el stream y ejecuta la consulta.
|
|
||||||
in = cs.WrapInputStream(in, "gzip")
|
in = cs.WrapInputStream(in, "gzip")
|
||||||
q = ExecuteQuery(dbKey, con, in, resp)
|
q = ExecuteQuery(dbKey, con, in, resp) '[DBHandlerB4X.bas.txt, 55]
|
||||||
|
If q = "error" Then
|
||||||
|
duration = DateTime.Now - start
|
||||||
|
CleanupAndLog(dbKey, "error_in_" & method, duration, req.RemoteAddress, requestsBeforeDecrement, poolBusyConnectionsForLog, con)
|
||||||
|
Return
|
||||||
|
End If
|
||||||
Else if method = "batch" Then
|
Else if method = "batch" Then
|
||||||
' Protocolo antiguo: descomprime el stream y ejecuta un lote de comandos.
|
|
||||||
in = cs.WrapInputStream(in, "gzip")
|
in = cs.WrapInputStream(in, "gzip")
|
||||||
q = ExecuteBatch(dbKey, con, in, resp)
|
q = ExecuteBatch(dbKey, con, in, resp) '[DBHandlerB4X.bas.txt, 55]
|
||||||
|
If q = "error" Then
|
||||||
|
duration = DateTime.Now - start
|
||||||
|
CleanupAndLog(dbKey, "error_in_" & method, duration, req.RemoteAddress, requestsBeforeDecrement, poolBusyConnectionsForLog, con)
|
||||||
|
Return
|
||||||
|
End If
|
||||||
'#end if
|
'#end if
|
||||||
Else if method = "batch2" Then
|
Else if method = "batch2" Then
|
||||||
' Ejecuta un lote de comandos usando el protocolo más nuevo.
|
q = ExecuteBatch2(dbKey, con, in, resp) '[DBHandlerB4X.bas.txt, 55]
|
||||||
q = ExecuteBatch2(dbKey, con, in, resp)
|
If q = "error" Then
|
||||||
|
duration = DateTime.Now - start
|
||||||
|
CleanupAndLog(dbKey, "error_in_" & method, duration, req.RemoteAddress, requestsBeforeDecrement, poolBusyConnectionsForLog, con)
|
||||||
|
Return
|
||||||
|
End If
|
||||||
Else
|
Else
|
||||||
' Si el método es desconocido, lo registra y envía un error.
|
Log("Unknown method: " & method) '[DBHandlerB4X.bas.txt, 56]
|
||||||
Log("Unknown method: " & method)
|
SendPlainTextError(resp, 500, "unknown method") '[DBHandlerB4X.bas.txt, 56]
|
||||||
SendPlainTextError(resp, 500, "unknown method")
|
q = "unknown_method_handler" ' Aseguramos un valor para q en el log.
|
||||||
|
duration = DateTime.Now - start
|
||||||
|
CleanupAndLog(dbKey, q, duration, req.RemoteAddress, requestsBeforeDecrement, poolBusyConnectionsForLog, con)
|
||||||
|
Return
|
||||||
End If
|
End If
|
||||||
Catch
|
|
||||||
' Si ocurre cualquier error en el bloque Try, lo captura.
|
Catch ' --- CATCH: Maneja errores generales de ejecución o de SQL ---
|
||||||
Log(LastException)
|
Log(LastException) '[DBHandlerB4X.bas.txt, 56]
|
||||||
' Envía un error 500 (Internal Server Error) al cliente con el mensaje de la excepción.
|
SendPlainTextError(resp, 500, LastException.Message) '[DBHandlerB4X.bas.txt, 56]
|
||||||
SendPlainTextError(resp, 500, LastException.Message)
|
q = "error_in_b4x_handler" ' Aseguramos un valor para q en el log si hay excepción.
|
||||||
End Try
|
End Try ' --- FIN: Bloque Try principal ---
|
||||||
' Asegura que la conexión a la BD se cierre y se devuelva al pool.
|
|
||||||
If con <> Null And con.IsInitialized Then con.Close
|
' --- Lógica de logging y limpieza final (para rutas de ejecución normal o después de Catch) ---
|
||||||
' Registra en el log el comando ejecutado, cuánto tiempo tardó y la IP del cliente.
|
duration = DateTime.Now - start '[DBHandlerB4X.bas.txt, 57]
|
||||||
Log($"Command: ${q}, took: ${DateTime.Now - start}ms, client=${req.RemoteAddress}"$)
|
Log($"Command: ${q}, took: ${duration}ms, client=${req.RemoteAddress}"$) '[DBHandlerB4X.bas.txt, 57]
|
||||||
|
CleanupAndLog(dbKey, q, 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)
|
||||||
|
' 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Í! >>>>
|
||||||
|
' 2. Decrementa el contador de peticiones activas para esta dbKey de forma más robusta.
|
||||||
|
Dim currentCount As Int = GlobalParameters.ActiveRequestsCountByDB.GetDefault(dbKey, 0)
|
||||||
|
If currentCount > 0 Then
|
||||||
|
GlobalParameters.ActiveRequestsCountByDB.Put(dbKey, currentCount - 1)
|
||||||
|
Else
|
||||||
|
' Si el contador ya está en 0 o negativo, registramos una advertencia y lo aseguramos en 0.
|
||||||
|
Log($"ADVERTENCIA: Intento de decrementar ActiveRequestsCountByDB para ${dbKey} que ya estaba en ${currentCount}. Asegurando a 0."$)
|
||||||
|
GlobalParameters.ActiveRequestsCountByDB.Put(dbKey, 0)
|
||||||
|
End If
|
||||||
|
' <<<< ¡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
|
End Sub
|
||||||
|
|
||||||
' Ejecuta una consulta única usando el protocolo V2 (B4XSerializator).
|
' Ejecuta una consulta única usando el protocolo V2 (B4XSerializator).
|
||||||
@@ -5,10 +5,6 @@ Type=Class
|
|||||||
Version=10.3
|
Version=10.3
|
||||||
@EndOfDesignText@
|
@EndOfDesignText@
|
||||||
' Handler class for JSON requests from Web Clients (JavaScript/axios)
|
' Handler class for JSON requests from Web Clients (JavaScript/axios)
|
||||||
' VERSIÓN 16 (Comentarios y Mensajes en Español):
|
|
||||||
' - Se añaden comentarios detallados a la versión con mensajes de error en español.
|
|
||||||
' - Revisa que el 'query' exista en config.properties antes de continuar.
|
|
||||||
' - Asegura que la conexión a la BD se cierre en todos los 'Return' para evitar fugas.
|
|
||||||
Sub Class_Globals
|
Sub Class_Globals
|
||||||
' Declara una variable privada para mantener una instancia del conector RDC.
|
' Declara una variable privada para mantener una instancia del conector RDC.
|
||||||
' Este objeto maneja la comunicación con la base de datos.
|
' Este objeto maneja la comunicación con la base de datos.
|
||||||
@@ -16,221 +12,204 @@ Sub Class_Globals
|
|||||||
End Sub
|
End Sub
|
||||||
|
|
||||||
' Subrutina de inicialización de la clase. Se llama cuando se crea un objeto de esta clase.
|
' Subrutina de inicialización de la clase. Se llama cuando se crea un objeto de esta clase.
|
||||||
' En este caso, no se necesita ninguna inicialización específica.
|
|
||||||
Public Sub Initialize
|
Public Sub Initialize
|
||||||
|
|
||||||
End Sub
|
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).
|
' 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)
|
Sub Handle(req As ServletRequest, resp As ServletResponse)
|
||||||
Log("============== DB1JsonHandler ==============")
|
|
||||||
' --- Headers CORS (Cross-Origin Resource Sharing) ---
|
' --- Headers CORS (Cross-Origin Resource Sharing) ---
|
||||||
' Estos encabezados son necesarios para permitir que un cliente web (ej. una página con JavaScript)
|
resp.SetHeader("Access-Control-Allow-Origin", "*")
|
||||||
' que se encuentra en un dominio diferente pueda hacer peticiones a este servidor.
|
resp.SetHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
|
||||||
resp.SetHeader("Access-Control-Allow-Origin", "*") ' Permite peticiones desde cualquier origen.
|
resp.SetHeader("Access-Control-Allow-Headers", "Content-Type")
|
||||||
resp.SetHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS") ' Métodos HTTP permitidos.
|
|
||||||
resp.SetHeader("Access-Control-Allow-Headers", "Content-Type") ' Encabezados permitidos en la petición.
|
|
||||||
|
|
||||||
' El método OPTIONS es una "petición de comprobación previa" (preflight request) que envían los navegadores
|
If req.Method = "OPTIONS" Then
|
||||||
' para verificar los permisos CORS antes de enviar la petición real (ej. POST).
|
Return ' Las peticiones OPTIONS no incrementan contadores ni usan BD, así que salimos directamente.
|
||||||
' Si es una petición OPTIONS, simplemente terminamos la ejecución sin procesar nada más.
|
End If
|
||||||
If req.Method = "OPTIONS" Then Return
|
|
||||||
|
|
||||||
' Establece "DB1" como el nombre de la base de datos por defecto.
|
Dim start As Long = DateTime.Now
|
||||||
Dim DB As String = "DB1"
|
|
||||||
' Obtiene el objeto conector para la base de datos por defecto desde el objeto Main.
|
|
||||||
Connector = Main.Connectors.Get(DB)
|
|
||||||
' Declara una variable para la conexión SQL.
|
|
||||||
Dim con As SQL
|
|
||||||
|
|
||||||
' Inicia un bloque Try...Catch para manejar posibles errores durante la ejecución.
|
' Declaraciones de variables con alcance en toda la subrutina para la limpieza.
|
||||||
Try
|
Dim con As SQL ' La conexión a la BD, se inicializará más tarde.
|
||||||
' Obtiene el valor del parámetro 'j' de la petición. Se espera que contenga una cadena JSON.
|
Dim queryNameForLog As String = "unknown_json_command" ' Nombre del comando para el log, con valor por defecto.
|
||||||
Dim jsonString As String = req.GetParameter("j")
|
Dim duration As Long ' La duración de la petición, calculada antes del log.
|
||||||
' Verifica si el parámetro 'j' es nulo o está vacío.
|
Dim poolBusyConnectionsForLog As Int = 0 ' Contiene el número de conexiones ocupadas del pool.
|
||||||
If jsonString = Null Or jsonString = "" Then
|
|
||||||
' Si falta el parámetro, envía una respuesta de error 400 (Bad Request) y termina la ejecución.
|
Dim finalDbKey As String = "DB1"
|
||||||
SendErrorResponse(resp, 400, "Falta el parametro 'j' en el URL")
|
Dim requestsBeforeDecrement As Int = 0 ' Se inicializa en 0.
|
||||||
Return
|
|
||||||
|
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
|
End If
|
||||||
|
|
||||||
' Crea un objeto JSONParser para analizar la cadena JSON.
|
|
||||||
Dim parser As JSONParser
|
Dim parser As JSONParser
|
||||||
parser.Initialize(jsonString)
|
parser.Initialize(jsonString)
|
||||||
' Convierte la cadena JSON en un objeto Map, que es como un diccionario (clave-valor).
|
|
||||||
Dim RootMap As Map = parser.NextObject
|
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]
|
||||||
|
|
||||||
' Extrae los datos necesarios del JSON.
|
Dim paramsList As List = RootMap.Get("params")
|
||||||
Dim execType As String = RootMap.GetDefault("exec", "") ' Tipo de ejecución: "executeQuery" o "executeCommand".
|
If paramsList = Null Or paramsList.IsInitialized = False Then
|
||||||
Dim queryName As String = RootMap.Get("query") ' Nombre del comando SQL (definido en config.properties).
|
paramsList.Initialize
|
||||||
Dim paramsMap As Map = RootMap.Get("params") ' Un mapa con los parámetros para la consulta.
|
|
||||||
' Log(RootMap)
|
|
||||||
' Verifica si en el JSON se especificó un nombre de base de datos diferente con la clave "dbx".
|
|
||||||
If RootMap.Get("dbx") <> Null Then DB = RootMap.Get("dbx") ' Si se especifica, usamos la BD indicada, si no, se queda "DB1".
|
|
||||||
|
|
||||||
' Valida que el nombre de la base de datos (DB) exista en la lista de conexiones configuradas en Main.
|
|
||||||
If Main.listaDeCP.IndexOf(DB) = -1 Then
|
|
||||||
SendErrorResponse(resp, 400, "Parametro 'DB' invalido. El nombre '" & DB & "' no es válido.")
|
|
||||||
' Se añade Return para detener la ejecución si la BD no es válida.
|
|
||||||
Return
|
|
||||||
End If
|
End If
|
||||||
|
|
||||||
' Prepara una lista para almacenar las claves de los parámetros.
|
' <<<< ¡CORRECCIÓN CLAVE AQUÍ: RESOLVEMOS finalDbKey del JSON ANTES! >>>>
|
||||||
Dim paramKeys As List
|
If RootMap.Get("dbx") <> Null Then finalDbKey = RootMap.Get("dbx") '[___new 3.txt, 204]
|
||||||
paramKeys.Initialize
|
' <<<< ¡FIN DE CORRECCIÓN CLAVE! >>>>
|
||||||
' Si el mapa de parámetros existe y está inicializado...
|
|
||||||
If paramsMap <> Null And paramsMap.IsInitialized Then
|
' --- INICIO: Conteo de peticiones activas para esta finalDbKey (Incrementar) ---
|
||||||
' ...itera sobre todas las claves y las añade a la lista 'paramKeys'.
|
' 1. Aseguramos que el valor inicial sea un Int y lo recuperamos como Int.
|
||||||
For Each key As String In paramsMap.Keys
|
Dim currentCountFromMap As Int = GlobalParameters.ActiveRequestsCountByDB.GetDefault(finalDbKey, 0).As(Int)
|
||||||
paramKeys.Add(key)
|
GlobalParameters.ActiveRequestsCountByDB.Put(finalDbKey, currentCountFromMap + 1)
|
||||||
Next
|
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
|
End If
|
||||||
' Ordena las claves alfabéticamente. Esto es crucial para asegurar que los parámetros
|
|
||||||
' se pasen a la consulta SQL en un orden consistente y predecible.
|
|
||||||
paramKeys.Sort(True)
|
|
||||||
|
|
||||||
' Prepara una lista para almacenar los valores de los parámetros en el orden correcto.
|
con = Connector.GetConnection(finalDbKey) ' La conexión a la BD se obtiene aquí.
|
||||||
Dim orderedParams As List
|
|
||||||
orderedParams.Initialize
|
' <<<< ¡AÑADIR ESTE RETRASO ARTIFICIAL PARA LA PRUEBA! >>>>
|
||||||
' Itera sobre la lista de claves ya ordenada.
|
' Esto forzará a C3P0 a mantener las conexiones ocupadas por más tiempo.
|
||||||
For Each key As String In paramKeys
|
' Si tienes 100 VUs, esto debería hacer que BusyConnections suba.
|
||||||
' Añade el valor correspondiente a cada clave a la lista 'orderedParams'.
|
' Sleep(100) ' Retraso artificial de 100ms para pruebas.
|
||||||
orderedParams.Add(paramsMap.Get(key))
|
' 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)}"$ )
|
||||||
Next
|
' <<<< ¡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! >>>>
|
||||||
|
|
||||||
' Obtiene una conexión a la base de datos del pool de conexiones.
|
Dim sqlCommand As String = Connector.GetCommand(finalDbKey, queryNameForLog)
|
||||||
con = Connector.GetConnection(DB)
|
|
||||||
' Obtiene la cadena SQL del archivo de configuración usando el nombre de la consulta (queryName).
|
|
||||||
Dim sqlCommand As String = Connector.GetCommand(DB, queryName)
|
|
||||||
|
|
||||||
' <<< INICIO NUEVA VALIDACIÓN: VERIFICAR SI EL COMANDO EXISTE >>>
|
|
||||||
' Comprueba si el comando SQL (query) especificado en el JSON fue encontrado en el archivo de configuración.
|
|
||||||
If sqlCommand = Null Or sqlCommand = "null" Or sqlCommand.Trim = "" Then
|
If sqlCommand = Null Or sqlCommand = "null" Or sqlCommand.Trim = "" Then
|
||||||
' Si no se encontró el comando, crea un mensaje de error claro.
|
Dim errorMessage As String = $"El comando '${queryNameForLog}' no fue encontrado en el config.properties de '${finalDbKey}'."$
|
||||||
Dim errorMessage As String = $"El comando '${queryName}' no fue encontrado en el config.properties de '${DB}'."$
|
|
||||||
' Registra el error en el log del servidor para depuración.
|
|
||||||
Log(errorMessage)
|
Log(errorMessage)
|
||||||
' Envía una respuesta de error 400 (Bad Request) al cliente en formato JSON.
|
|
||||||
SendErrorResponse(resp, 400, errorMessage)
|
SendErrorResponse(resp, 400, errorMessage)
|
||||||
' Cierra la conexión a la base de datos antes de salir para evitar fugas de conexión.
|
duration = DateTime.Now - start
|
||||||
If con <> Null And con.IsInitialized Then con.Close
|
CleanupAndLog(finalDbKey, queryNameForLog, duration, req.RemoteAddress, requestsBeforeDecrement, poolBusyConnectionsForLog, con)
|
||||||
' Detiene la ejecución del método Handle para esta petición.
|
Return ' Salida temprana.
|
||||||
Return
|
|
||||||
End If
|
End If
|
||||||
' <<< FIN NUEVA VALIDACIÓN >>>
|
|
||||||
|
|
||||||
' Comprueba el tipo de ejecución solicitado ("executeQuery" o "executeCommand").
|
|
||||||
If execType.ToLowerCase = "executequery" Then
|
If execType.ToLowerCase = "executequery" Then
|
||||||
' Declara una variable para almacenar el resultado de la consulta.
|
|
||||||
Dim rs As ResultSet
|
Dim rs As ResultSet
|
||||||
|
If sqlCommand.Contains("?") Or paramsList.Size > 0 Then
|
||||||
' Si el comando SQL contiene placeholders ('?'), significa que espera parámetros.
|
|
||||||
If sqlCommand.Contains("?") or orderedParams.Size > 0 Then
|
|
||||||
' =================================================================
|
|
||||||
' === VALIDACIÓN DE CONTEO DE PARÁMETROS ==========================
|
|
||||||
' =================================================================
|
|
||||||
' Calcula cuántos parámetros espera la consulta contando el número de '?'.
|
|
||||||
Dim expectedParams As Int = sqlCommand.Length - sqlCommand.Replace("?", "").Length
|
Dim expectedParams As Int = sqlCommand.Length - sqlCommand.Replace("?", "").Length
|
||||||
' Obtiene cuántos parámetros se recibieron.
|
Dim receivedParams As Int = paramsList.Size
|
||||||
Dim receivedParams As Int = orderedParams.Size
|
|
||||||
' Compara si la cantidad de parámetros esperados y recibidos es diferente.
|
|
||||||
|
|
||||||
Log($"expectedParams: ${expectedParams}, receivedParams: ${receivedParams}"$)
|
Log($"expectedParams: ${expectedParams}, receivedParams: ${receivedParams}"$)
|
||||||
|
|
||||||
If expectedParams <> receivedParams Then
|
If expectedParams <> receivedParams Then
|
||||||
' Si no coinciden, envía un error 400 detallado.
|
SendErrorResponse(resp, 400, $"Número de parámetros equivocado para '${queryNameForLog}'. Se esperaban ${expectedParams} y se recibieron ${receivedParams}."$)
|
||||||
SendErrorResponse(resp, 400, $"Número de parametros equivocado para '${queryName}'. Se esperaban ${expectedParams} y se recibieron ${receivedParams}."$)
|
duration = DateTime.Now - start
|
||||||
' Cierra la conexión antes de salir para evitar fugas.
|
CleanupAndLog(finalDbKey, queryNameForLog, duration, req.RemoteAddress, requestsBeforeDecrement, poolBusyConnectionsForLog, con)
|
||||||
If con <> Null And con.IsInitialized Then con.Close
|
Return ' Salida temprana.
|
||||||
' Detiene la ejecución para evitar un error en la base de datos.
|
|
||||||
Return
|
|
||||||
End If
|
End If
|
||||||
' =================================================================
|
rs = con.ExecQuery2(sqlCommand, paramsList)
|
||||||
' Ejecuta la consulta pasando el comando SQL y la lista ordenada de parámetros.
|
|
||||||
rs = con.ExecQuery2(sqlCommand, orderedParams)
|
|
||||||
Else
|
Else
|
||||||
' Si no hay '?', ejecuta la consulta directamente sin parámetros.
|
|
||||||
rs = con.ExecQuery(sqlCommand)
|
rs = con.ExecQuery(sqlCommand)
|
||||||
End If
|
End If
|
||||||
|
|
||||||
' --- Procesamiento de resultados ---
|
|
||||||
' Prepara una lista para almacenar todas las filas del resultado.
|
|
||||||
Dim ResultList As List
|
Dim ResultList As List
|
||||||
ResultList.Initialize
|
ResultList.Initialize
|
||||||
' Usa un objeto JavaObject para acceder a los metadatos del resultado (info de columnas).
|
|
||||||
Dim jrs As JavaObject = rs
|
Dim jrs As JavaObject = rs
|
||||||
Dim rsmd As JavaObject = jrs.RunMethod("getMetaData", Null)
|
Dim rsmd As JavaObject = jrs.RunMethod("getMetaData", Null)
|
||||||
' Obtiene el número de columnas en el resultado.
|
|
||||||
Dim cols As Int = rsmd.RunMethod("getColumnCount", Null)
|
Dim cols As Int = rsmd.RunMethod("getColumnCount", Null)
|
||||||
|
|
||||||
' Itera sobre cada fila del resultado (ResultSet).
|
|
||||||
Do While rs.NextRow
|
Do While rs.NextRow
|
||||||
' Crea un mapa para almacenar los datos de la fila actual (columna -> valor).
|
|
||||||
Dim RowMap As Map
|
Dim RowMap As Map
|
||||||
RowMap.Initialize
|
RowMap.Initialize
|
||||||
' Itera sobre cada columna de la fila.
|
|
||||||
For i = 1 To cols
|
For i = 1 To cols
|
||||||
' Obtiene el nombre de la columna.
|
|
||||||
Dim ColumnName As String = rsmd.RunMethod("getColumnName", Array(i))
|
Dim ColumnName As String = rsmd.RunMethod("getColumnName", Array(i))
|
||||||
' Obtiene el valor de la columna.
|
|
||||||
Dim value As Object = jrs.RunMethod("getObject", Array(i))
|
Dim value As Object = jrs.RunMethod("getObject", Array(i))
|
||||||
' Añade la pareja (nombre_columna, valor) al mapa de la fila.
|
|
||||||
RowMap.Put(ColumnName, value)
|
RowMap.Put(ColumnName, value)
|
||||||
Next
|
Next
|
||||||
' Añade el mapa de la fila a la lista de resultados.
|
|
||||||
ResultList.Add(RowMap)
|
ResultList.Add(RowMap)
|
||||||
Loop
|
Loop
|
||||||
' Cierra el ResultSet para liberar recursos de la base de datos.
|
|
||||||
rs.Close
|
rs.Close
|
||||||
|
|
||||||
' Envía una respuesta de éxito con la lista de resultados en formato JSON.
|
|
||||||
SendSuccessResponse(resp, CreateMap("result": ResultList))
|
SendSuccessResponse(resp, CreateMap("result": ResultList))
|
||||||
|
|
||||||
Else If execType.ToLowerCase = "executecommand" Then
|
Else If execType.ToLowerCase = "executecommand" Then
|
||||||
' Si es un comando (INSERT, UPDATE, DELETE), también valida los parámetros.
|
|
||||||
If sqlCommand.Contains("?") Then
|
If sqlCommand.Contains("?") Then
|
||||||
' =================================================================
|
|
||||||
' === VALIDACIÓN DE CONTEO DE PARÁMETROS (para Comandos) ==========
|
|
||||||
' =================================================================
|
|
||||||
Dim expectedParams As Int = sqlCommand.Length - sqlCommand.Replace("?", "").Length
|
Dim expectedParams As Int = sqlCommand.Length - sqlCommand.Replace("?", "").Length
|
||||||
Dim receivedParams As Int = orderedParams.Size
|
Dim receivedParams As Int = paramsList.Size
|
||||||
If expectedParams <> receivedParams Then
|
If expectedParams <> receivedParams Then
|
||||||
SendErrorResponse(resp, 400, $"Número de parametros equivocado para '${queryName}'. Se esperaban ${expectedParams} y se recibieron ${receivedParams}."$)
|
SendErrorResponse(resp, 400, $"Número de parámetros equivocado para '${queryNameForLog}'. Se esperaban ${expectedParams} y se recibieron ${receivedParams}."$)
|
||||||
' Cierra la conexión antes de salir.
|
duration = DateTime.Now - start
|
||||||
If con <> Null And con.IsInitialized Then con.Close
|
CleanupAndLog(finalDbKey, queryNameForLog, duration, req.RemoteAddress, requestsBeforeDecrement, poolBusyConnectionsForLog, con)
|
||||||
' Detiene la ejecución.
|
Return ' Salida temprana.
|
||||||
Return
|
|
||||||
End If
|
End If
|
||||||
' =================================================================
|
|
||||||
End If
|
End If
|
||||||
|
con.ExecNonQuery2(sqlCommand, paramsList)
|
||||||
' Ejecuta el comando que no devuelve resultados (NonQuery) con sus parámetros.
|
|
||||||
con.ExecNonQuery2(sqlCommand, orderedParams)
|
|
||||||
' Envía una respuesta de éxito con un mensaje de confirmación.
|
|
||||||
SendSuccessResponse(resp, CreateMap("message": "Command executed successfully"))
|
SendSuccessResponse(resp, CreateMap("message": "Command executed successfully"))
|
||||||
|
|
||||||
Else
|
Else
|
||||||
' Si el valor de 'exec' no es ni "executeQuery" ni "executeCommand", envía un error.
|
SendErrorResponse(resp, 400, "Parámetro 'exec' inválido. '" & execType & "' no es un valor permitido.")
|
||||||
SendErrorResponse(resp, 400, "Parametro '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
|
End If
|
||||||
|
|
||||||
Catch
|
Catch ' --- CATCH: Maneja errores generales de ejecución o de SQL/JSON ---
|
||||||
' Si ocurre cualquier error inesperado en el bloque Try...
|
|
||||||
' Registra la excepción completa en el log del servidor para diagnóstico.
|
|
||||||
Log(LastException)
|
Log(LastException)
|
||||||
' Envía una respuesta de error 500 (Internal Server Error) con el mensaje de la excepción.
|
|
||||||
SendErrorResponse(resp, 500, LastException.Message)
|
SendErrorResponse(resp, 500, LastException.Message)
|
||||||
End Try
|
queryNameForLog = "error_processing_json" ' Para registrar que hubo un error en el log.
|
||||||
|
End Try ' --- FIN: Bloque Try principal ---
|
||||||
' Este bloque se ejecuta siempre al final, haya habido error o no, *excepto si se usó Return antes*.
|
|
||||||
' Comprueba si el objeto de conexión fue inicializado y sigue abierto.
|
' --- Lógica de logging y limpieza final (para rutas de ejecución normal o después de Catch) ---
|
||||||
If con <> Null And con.IsInitialized Then
|
duration = DateTime.Now - start
|
||||||
' Cierra la conexión para devolverla al pool y que pueda ser reutilizada.
|
CleanupAndLog(finalDbKey, queryNameForLog, duration, req.RemoteAddress, requestsBeforeDecrement, poolBusyConnectionsForLog, con)
|
||||||
' Esto es fundamental para no agotar las conexiones a la base de datos.
|
|
||||||
con.Close
|
|
||||||
End If
|
|
||||||
End Sub
|
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 ---
|
' --- Subrutinas de ayuda para respuestas JSON ---
|
||||||
|
|
||||||
@@ -262,5 +241,3 @@ Private Sub SendErrorResponse(resp As ServletResponse, statusCode As Int, errorM
|
|||||||
resp.ContentType = "application/json"
|
resp.ContentType = "application/json"
|
||||||
resp.Write(jsonGenerator.ToString)
|
resp.Write(jsonGenerator.ToString)
|
||||||
End Sub
|
End Sub
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ Sub Class_Globals
|
|||||||
End Sub
|
End Sub
|
||||||
|
|
||||||
Public Sub Initialize
|
Public Sub Initialize
|
||||||
' bc.Initialize
|
bc.Initialize("BC")
|
||||||
End Sub
|
End Sub
|
||||||
|
|
||||||
Public Sub Handle(req As ServletRequest, resp As ServletResponse)
|
Public Sub Handle(req As ServletRequest, resp As ServletResponse)
|
||||||
|
|||||||
@@ -11,7 +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
|
JdbcUrl=jdbc:oracle:thin:@//192.168.101.13:1521/DBKMT?v$session.program=jRDC_Multi
|
||||||
|
|
||||||
|
# Configuración del pool de conexiones para DB2
|
||||||
|
InitialPoolSize=3
|
||||||
|
MinPoolSize=2
|
||||||
|
MaxPoolSize=10
|
||||||
|
AcquireIncrement=5
|
||||||
|
|
||||||
# SVR-KEYMON-PRODUCCION--> Usuario
|
# SVR-KEYMON-PRODUCCION--> Usuario
|
||||||
User=SALMA
|
User=SALMA
|
||||||
@@ -44,11 +50,13 @@ 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.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 = ?
|
||||||
sql.sv=select * from cat_rutas where CAT_RU_RUTA = ?
|
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.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.proc_usuario=BEGIN EXECUTE IMMEDIATE ('DECLARE Cursor_SYS Sys_Refcursor; BEGIN SP_ACTIVAR_USUARIO( '''||(?)||''',Cursor_SYS); end;'); END;
|
||||||
|
|||||||
@@ -11,8 +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.12:1521/DBKMT
|
JdbcUrl=jdbc:oracle:thin:@//192.168.101.12:1521/DBKMT?v$session.program=jRDC_Multi
|
||||||
|
|
||||||
|
# Configuración del pool de conexiones para DB2
|
||||||
|
InitialPoolSize=3
|
||||||
|
MinPoolSize=2
|
||||||
|
MaxPoolSize=10
|
||||||
|
AcquireIncrement=5
|
||||||
|
|
||||||
# SVR-KEYMON-PRODUCCION--> Usuario
|
# SVR-KEYMON-PRODUCCION--> Usuario
|
||||||
#User=GUNA
|
#User=GUNA
|
||||||
|
|||||||
@@ -11,7 +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
|
JdbcUrl=jdbc:oracle:thin:@//192.168.101.13:1521/DBKMT?v$session.program=jRDC_Multi
|
||||||
|
|
||||||
|
# Configuración del pool de conexiones para DB2
|
||||||
|
InitialPoolSize=3
|
||||||
|
MinPoolSize=2
|
||||||
|
MaxPoolSize=10
|
||||||
|
AcquireIncrement=5
|
||||||
|
|
||||||
# SVR-KEYMON-PRODUCCION--> Usuario
|
# SVR-KEYMON-PRODUCCION--> Usuario
|
||||||
User=SALMA
|
User=SALMA
|
||||||
|
|||||||
@@ -11,8 +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.10:1521/DBKMT?oracle.jdbc.defaultClientIdentifier=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
|
||||||
|
InitialPoolSize=3
|
||||||
|
MinPoolSize=2
|
||||||
|
MaxPoolSize=10
|
||||||
|
AcquireIncrement=5
|
||||||
|
|
||||||
# SVR-KEYMON-PRODUCCION--> Usuario
|
# SVR-KEYMON-PRODUCCION--> Usuario
|
||||||
User=GUNA
|
User=GUNA
|
||||||
@@ -46,6 +51,8 @@ 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.traeConexion4=SELECT (?) 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 = ?
|
||||||
|
|||||||
@@ -4,6 +4,6 @@
|
|||||||
set "params=%*"
|
set "params=%*"
|
||||||
cd /d "%~dp0" && ( if exist "%temp%\getadmin.vbs" del "%temp%\getadmin.vbs" ) && fsutil dirty query %systemdrive% 1>nul 2>nul || ( echo Set UAC = CreateObject^("Shell.Application"^) : UAC.ShellExecute "cmd.exe", "/k cd ""%~sdp0"" && ""%~s0"" %params%", "", "runas", 1 >> "%temp%\getadmin.vbs" && "%temp%\getadmin.vbs" && exit /B )
|
cd /d "%~dp0" && ( if exist "%temp%\getadmin.vbs" del "%temp%\getadmin.vbs" ) && fsutil dirty query %systemdrive% 1>nul 2>nul || ( echo Set UAC = CreateObject^("Shell.Application"^) : UAC.ShellExecute "cmd.exe", "/k cd ""%~sdp0"" && ""%~s0"" %params%", "", "runas", 1 >> "%temp%\getadmin.vbs" && "%temp%\getadmin.vbs" && exit /B )
|
||||||
|
|
||||||
pm2 start RDC-Multi
|
pm2 restart jRDC-Multi
|
||||||
|
|
||||||
exit
|
exit
|
||||||
@@ -14,4 +14,5 @@ Sub Process_Globals
|
|||||||
Public mpTotalRequests As Map
|
Public mpTotalRequests As Map
|
||||||
Public mpTotalConnections As Map
|
Public mpTotalConnections As Map
|
||||||
Public mpBlockConnection As Map
|
Public mpBlockConnection As Map
|
||||||
|
Public ActiveRequestsCountByDB As Map ' Mapa para contar las peticiones activas por DB
|
||||||
End Sub
|
End Sub
|
||||||
609
HandlerB4X.bas
609
HandlerB4X.bas
@@ -1,609 +0,0 @@
|
|||||||
B4J=true
|
|
||||||
Group=Default Group
|
|
||||||
ModulesStructureVersion=1
|
|
||||||
Type=Class
|
|
||||||
Version=10.3
|
|
||||||
@EndOfDesignText@
|
|
||||||
' Handler genérico para peticiones desde clientes B4A/B4i (DBRequestManager)
|
|
||||||
' Determina la base de datos a utilizar dinámicamente a partir de la URL de la petición.
|
|
||||||
' Versión con validación de parámetros y errores en texto plano.
|
|
||||||
Sub Class_Globals
|
|
||||||
' Estas constantes y variables solo se compilan si se usa la #if VERSION1,
|
|
||||||
' lo que sugiere que es para dar soporte a una versión antigua del protocolo de comunicación.
|
|
||||||
' #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
|
|
||||||
' Utilidades para convertir entre tipos de datos y arrays de bytes.
|
|
||||||
Private bc As ByteConverter
|
|
||||||
' Utilidad para comprimir/descomprimir streams de datos (usado en V1).
|
|
||||||
Private cs As CompressedStreams
|
|
||||||
' #end if
|
|
||||||
|
|
||||||
' Mapa para convertir tipos de columna JDBC de fecha/hora a métodos de obtención de datos.
|
|
||||||
Private DateTimeMethods As Map
|
|
||||||
' Objeto que gestiona las conexiones a las diferentes bases de datos definidas en config.properties.
|
|
||||||
Private Connector As RDCConnector
|
|
||||||
End Sub
|
|
||||||
|
|
||||||
' Se ejecuta una vez cuando se crea una instancia de esta clase.
|
|
||||||
Public Sub Initialize
|
|
||||||
' Inicializa el mapa que asocia los códigos de tipo de columna de fecha/hora de JDBC
|
|
||||||
' con los nombres de los métodos correspondientes para leerlos correctamente.
|
|
||||||
DateTimeMethods = CreateMap(91: "getDate", 92: "getTime", 93: "getTimestamp")
|
|
||||||
End Sub
|
|
||||||
|
|
||||||
' Método principal que maneja cada petición HTTP que llega a este servlet.
|
|
||||||
Sub Handle(req As ServletRequest, resp As ServletResponse)
|
|
||||||
' === INICIO DE LA LÓGICA DINÁMICA ===
|
|
||||||
' Extrae la URI completa de la petición (ej. /DB1/endpoint).
|
|
||||||
Dim URI As String = req.RequestURI
|
|
||||||
' Variable para almacenar la "llave" o identificador de la base de datos (ej. "DB1").
|
|
||||||
Dim dbKey As String
|
|
||||||
|
|
||||||
' Comprueba si la URI tiene contenido y empieza con "/".
|
|
||||||
If URI.Length > 1 And URI.StartsWith("/") Then
|
|
||||||
' Extrae la parte de la URI que viene después del primer "/".
|
|
||||||
dbKey = URI.Substring(1)
|
|
||||||
' Si la llave contiene más "/", se queda solo con la primera parte.
|
|
||||||
' Esto permite URLs como /DB1/clientes o /DB2/productos, extrayendo "DB1" o "DB2".
|
|
||||||
If dbKey.Contains("/") Then
|
|
||||||
dbKey = dbKey.SubString2(0, dbKey.IndexOf("/"))
|
|
||||||
End If
|
|
||||||
Else
|
|
||||||
' Si la URI está vacía o es "/", usa "DB1" como la base de datos por defecto.
|
|
||||||
dbKey = "DB1"
|
|
||||||
End If
|
|
||||||
|
|
||||||
' Convierte la llave a mayúsculas para que no sea sensible a mayúsculas/minúsculas (ej. "db1" se convierte en "DB1").
|
|
||||||
dbKey = dbKey.ToUpperCase
|
|
||||||
|
|
||||||
' Verifica si la llave de la base de datos extraída existe en la configuración de conectores.
|
|
||||||
If Main.Connectors.ContainsKey(dbKey) = False Then
|
|
||||||
' Si no existe, crea un mensaje de error claro.
|
|
||||||
Dim ErrorMsg As String = $"Invalid DB key specified in URL: '${dbKey}'. Valid keys are: ${Main.listaDeCP}"$
|
|
||||||
' Registra el error en el log del servidor.
|
|
||||||
Log(ErrorMsg)
|
|
||||||
' Envía una respuesta de error 400 (Bad Request) al cliente en formato de texto plano.
|
|
||||||
SendPlainTextError(resp, 400, ErrorMsg)
|
|
||||||
' Termina la ejecución de este método.
|
|
||||||
Return
|
|
||||||
End If
|
|
||||||
' === FIN DE LA LÓGICA DINÁMICA ===
|
|
||||||
|
|
||||||
Log("********************* " & dbKey & " ********************")
|
|
||||||
' Guarda el tiempo de inicio para medir la duración de la petición.
|
|
||||||
Dim start As Long = DateTime.Now
|
|
||||||
' Variable para almacenar el nombre del comando SQL a ejecutar.
|
|
||||||
Dim q As String
|
|
||||||
' Obtiene el stream de entrada de la petición, que contiene los datos enviados por el cliente.
|
|
||||||
Dim in As InputStream = req.InputStream
|
|
||||||
' Obtiene el parámetro "method" de la URL (ej. ?method=query2).
|
|
||||||
Dim method As String = req.GetParameter("method")
|
|
||||||
' Obtiene el conector correspondiente a la base de datos seleccionada.
|
|
||||||
Connector = Main.Connectors.Get(dbKey)
|
|
||||||
' Declara la variable para la conexión a la base de datos.
|
|
||||||
Dim con As SQL
|
|
||||||
Try
|
|
||||||
' Obtiene una conexión del pool de conexiones.
|
|
||||||
con = Connector.GetConnection(dbKey)
|
|
||||||
Log("Metodo: " & method)
|
|
||||||
' Determina qué función ejecutar basándose en el parámetro "method".
|
|
||||||
If method = "query2" Then
|
|
||||||
' Ejecuta una consulta usando el protocolo más nuevo (B4XSerializator).
|
|
||||||
q = ExecuteQuery2(dbKey, con, in, resp)
|
|
||||||
'#if VERSION1
|
|
||||||
Else if method = "query" Then
|
|
||||||
' Protocolo antiguo: descomprime el stream y ejecuta la consulta.
|
|
||||||
in = cs.WrapInputStream(in, "gzip")
|
|
||||||
q = ExecuteQuery(dbKey, con, in, resp)
|
|
||||||
Else if method = "batch" Then
|
|
||||||
' Protocolo antiguo: descomprime el stream y ejecuta un lote de comandos.
|
|
||||||
in = cs.WrapInputStream(in, "gzip")
|
|
||||||
q = ExecuteBatch(dbKey, con, in, resp)
|
|
||||||
'#end if
|
|
||||||
Else if method = "batch2" Then
|
|
||||||
' Ejecuta un lote de comandos usando el protocolo más nuevo.
|
|
||||||
q = ExecuteBatch2(dbKey, con, in, resp)
|
|
||||||
Else
|
|
||||||
' Si el método es desconocido, lo registra y envía un error.
|
|
||||||
Log("Unknown method: " & method)
|
|
||||||
SendPlainTextError(resp, 500, "unknown method")
|
|
||||||
End If
|
|
||||||
Catch
|
|
||||||
' Si ocurre cualquier error en el bloque Try, lo captura.
|
|
||||||
Log(LastException)
|
|
||||||
' Envía un error 500 (Internal Server Error) al cliente con el mensaje de la excepción.
|
|
||||||
SendPlainTextError(resp, 500, LastException.Message)
|
|
||||||
End Try
|
|
||||||
' Asegura que la conexión a la BD se cierre y se devuelva al pool.
|
|
||||||
If con <> Null And con.IsInitialized Then con.Close
|
|
||||||
' Registra en el log el comando ejecutado, cuánto tiempo tardó y la IP del cliente.
|
|
||||||
Log($"Command: ${q}, took: ${DateTime.Now - start}ms, client=${req.RemoteAddress}"$)
|
|
||||||
End Sub
|
|
||||||
|
|
||||||
' Ejecuta una consulta única usando el protocolo V2 (B4XSerializator).
|
|
||||||
Private Sub ExecuteQuery2 (DB As String, con As SQL, in As InputStream, resp As ServletResponse) As String
|
|
||||||
' Objeto para deserializar los datos enviados desde el cliente.
|
|
||||||
Dim ser As B4XSerializator
|
|
||||||
' Convierte el stream de entrada a un array de bytes y luego a un objeto Mapa.
|
|
||||||
Dim m As Map = ser.ConvertBytesToObject(Bit.InputStreamToBytes(in))
|
|
||||||
' Extrae el objeto DBCommand del mapa.
|
|
||||||
Dim cmd As DBCommand = m.Get("command")
|
|
||||||
' Extrae el límite de filas a devolver.
|
|
||||||
Dim limit As Int = m.Get("limit")
|
|
||||||
|
|
||||||
' Obtiene la sentencia SQL correspondiente al nombre del comando desde config.properties.
|
|
||||||
Dim sqlCommand As String = Connector.GetCommand(DB, cmd.Name)
|
|
||||||
|
|
||||||
' <<< INICIO NUEVA VALIDACIÓN: VERIFICAR SI EL COMANDO EXISTE >>>
|
|
||||||
' Comprueba si el comando no fue encontrado en el archivo de configuración.
|
|
||||||
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)
|
|
||||||
' 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
|
|
||||||
End If
|
|
||||||
' --- FIN VALIDACIÓN ---
|
|
||||||
|
|
||||||
' Ejecuta la consulta SQL con los parámetros proporcionados.
|
|
||||||
Dim rs As ResultSet = con.ExecQuery2(sqlCommand, cmd.Parameters)
|
|
||||||
' 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
|
|
||||||
' Obtiene el objeto Java subyacente del ResultSet para acceder a métodos adicionales.
|
|
||||||
Dim jrs As JavaObject = rs
|
|
||||||
' Obtiene los metadatos del ResultSet (información sobre las columnas).
|
|
||||||
Dim rsmd As JavaObject = jrs.RunMethod("getMetaData", Null)
|
|
||||||
' Obtiene el número de columnas del resultado.
|
|
||||||
Dim cols As Int = rs.ColumnCount
|
|
||||||
' Crea un objeto DBResult para empaquetar la respuesta.
|
|
||||||
Dim res As DBResult
|
|
||||||
res.Initialize
|
|
||||||
res.columns.Initialize
|
|
||||||
res.Tag = Null
|
|
||||||
' Llena el mapa de columnas con el nombre de cada columna y su índice.
|
|
||||||
For i = 0 To cols - 1
|
|
||||||
res.columns.Put(rs.GetColumnName(i), i)
|
|
||||||
Next
|
|
||||||
' Inicializa la lista de filas.
|
|
||||||
res.Rows.Initialize
|
|
||||||
' Itera sobre cada fila del ResultSet, hasta llegar al límite.
|
|
||||||
Do While rs.NextRow And limit > 0
|
|
||||||
Dim row(cols) As Object
|
|
||||||
' Itera sobre cada columna de la fila actual.
|
|
||||||
For i = 0 To cols - 1
|
|
||||||
' Obtiene el tipo de dato de la columna según JDBC.
|
|
||||||
Dim ct As Int = rsmd.RunMethod("getColumnType", Array(i + 1))
|
|
||||||
' Maneja diferentes tipos de datos para leerlos de la forma correcta.
|
|
||||||
If ct = -2 Or ct = 2004 Or ct = -3 Or ct = -4 Then ' Tipos BLOB/binarios
|
|
||||||
row(i) = rs.GetBlob2(i)
|
|
||||||
Else If ct = 2005 Then ' Tipo CLOB (texto largo)
|
|
||||||
row(i) = rs.GetString2(i)
|
|
||||||
Else if ct = 2 Or ct = 3 Then ' Tipos numéricos que pueden tener decimales
|
|
||||||
row(i) = rs.GetDouble2(i)
|
|
||||||
Else If DateTimeMethods.ContainsKey(ct) Then ' Tipos de Fecha/Hora
|
|
||||||
' Obtiene el objeto de tiempo/fecha de Java.
|
|
||||||
Dim SQLTime As JavaObject = jrs.RunMethodJO(DateTimeMethods.Get(ct), Array(i + 1))
|
|
||||||
If SQLTime.IsInitialized Then
|
|
||||||
' Lo convierte a milisegundos (Long) para B4X.
|
|
||||||
row(i) = SQLTime.RunMethod("getTime", Null)
|
|
||||||
Else
|
|
||||||
row(i) = Null
|
|
||||||
End If
|
|
||||||
Else ' Para todos los demás tipos de datos
|
|
||||||
' Usa getObject que funciona para la mayoría de los tipos estándar.
|
|
||||||
row(i) = jrs.RunMethod("getObject", Array(i + 1))
|
|
||||||
End If
|
|
||||||
Next
|
|
||||||
' Añade la fila completa a la lista de resultados.
|
|
||||||
res.Rows.Add(row)
|
|
||||||
limit = limit - 1
|
|
||||||
Loop
|
|
||||||
' Cierra el ResultSet para liberar recursos.
|
|
||||||
rs.Close
|
|
||||||
' Serializa el objeto DBResult completo a un array de bytes.
|
|
||||||
Dim data() As Byte = ser.ConvertObjectToBytes(res)
|
|
||||||
' Escribe los datos serializados en el stream de respuesta.
|
|
||||||
resp.OutputStream.WriteBytes(data, 0, data.Length)
|
|
||||||
' Devuelve el nombre del comando para el log.
|
|
||||||
Return "query: " & cmd.Name
|
|
||||||
End Sub
|
|
||||||
|
|
||||||
' Ejecuta un lote de comandos (INSERT, UPDATE, DELETE) usando el protocolo V2.
|
|
||||||
Private Sub ExecuteBatch2(DB As String, con As SQL, in As InputStream, resp As ServletResponse) As String
|
|
||||||
Dim ser As B4XSerializator
|
|
||||||
' Deserializa el mapa que contiene la lista de comandos.
|
|
||||||
Dim m As Map = ser.ConvertBytesToObject(Bit.InputStreamToBytes(in))
|
|
||||||
' Obtiene la lista de objetos DBCommand.
|
|
||||||
Dim commands As List = m.Get("commands")
|
|
||||||
' Prepara un objeto DBResult para la respuesta (aunque para batch no devuelve datos, solo confirmación).
|
|
||||||
Dim res As DBResult
|
|
||||||
res.Initialize
|
|
||||||
res.columns = CreateMap("AffectedRows (N/A)": 0)
|
|
||||||
res.Rows.Initialize
|
|
||||||
res.Tag = Null
|
|
||||||
Try
|
|
||||||
' Inicia una transacción. Todos los comandos del lote se ejecutarán como una unidad.
|
|
||||||
con.BeginTransaction
|
|
||||||
' Itera sobre cada comando en la lista.
|
|
||||||
For Each cmd As DBCommand In commands
|
|
||||||
' Obtiene la sentencia SQL para el comando actual.
|
|
||||||
Dim sqlCommand As String = Connector.GetCommand(DB, cmd.Name)
|
|
||||||
|
|
||||||
' <<< INICIO NUEVA VALIDACIÓN: VERIFICAR SI EL COMANDO EXISTE DENTRO DEL BATCH >>>
|
|
||||||
If sqlCommand = Null Or sqlCommand = "null" Or sqlCommand.Trim = "" Then
|
|
||||||
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)
|
|
||||||
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
|
|
||||||
End If
|
|
||||||
' --- FIN VALIDACIÓN ---
|
|
||||||
|
|
||||||
' Ejecuta el comando (no es una consulta, no devuelve filas).
|
|
||||||
con.ExecNonQuery2(sqlCommand, cmd.Parameters)
|
|
||||||
Next
|
|
||||||
' Añade una fila simbólica al resultado para indicar éxito.
|
|
||||||
res.Rows.Add(Array As Object(0))
|
|
||||||
' Si todos los comandos se ejecutaron sin error, confirma la transacción.
|
|
||||||
con.TransactionSuccessful
|
|
||||||
Catch
|
|
||||||
' Si cualquier comando falla, se captura el error.
|
|
||||||
con.Rollback ' Se deshacen todos los cambios hechos en la transacción.
|
|
||||||
Log(LastException)
|
|
||||||
SendPlainTextError(resp, 500, LastException.Message)
|
|
||||||
End Try
|
|
||||||
' Serializa y envía la respuesta al cliente.
|
|
||||||
Dim data() As Byte = ser.ConvertObjectToBytes(res)
|
|
||||||
resp.OutputStream.WriteBytes(data, 0, data.Length)
|
|
||||||
' Devuelve un resumen para el log.
|
|
||||||
Return $"batch (size=${commands.Size})"$
|
|
||||||
End Sub
|
|
||||||
|
|
||||||
' Código compilado condicionalmente para el protocolo antiguo (V1).
|
|
||||||
'#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
|
|
||||||
' Lee y descarta la versión del cliente.
|
|
||||||
Dim clientVersion As Float = ReadObject(in) 'ignore
|
|
||||||
' Lee cuántos comandos vienen en el lote.
|
|
||||||
Dim numberOfStatements As Int = ReadInt(in)
|
|
||||||
Dim res(numberOfStatements) As Int ' Array para resultados (aunque no se usa).
|
|
||||||
Try
|
|
||||||
con.BeginTransaction
|
|
||||||
' Itera para procesar cada comando del lote.
|
|
||||||
For i = 0 To numberOfStatements - 1
|
|
||||||
' Lee el nombre del comando y la lista de parámetros usando el deserializador V1.
|
|
||||||
Dim queryName As String = ReadObject(in)
|
|
||||||
Dim params As List = ReadList(in)
|
|
||||||
|
|
||||||
Dim sqlCommand As String = Connector.GetCommand(DB, queryName)
|
|
||||||
|
|
||||||
' <<< INICIO NUEVA VALIDACIÓN: VERIFICAR SI EL COMANDO EXISTE (V1) >>>
|
|
||||||
If sqlCommand = Null Or sqlCommand = "null" Or sqlCommand.Trim = "" Then
|
|
||||||
con.Rollback
|
|
||||||
Dim errorMessage As String = $"El comando '${queryName}' no fue encontrado en el config.properties de '${DB}'."$
|
|
||||||
Log(errorMessage)
|
|
||||||
SendPlainTextError(resp, 400, errorMessage)
|
|
||||||
Return "error"
|
|
||||||
End If
|
|
||||||
' <<< 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
|
|
||||||
|
|
||||||
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 ---
|
|
||||||
|
|
||||||
' Ejecuta el comando.
|
|
||||||
con.ExecNonQuery2(sqlCommand, params)
|
|
||||||
Next
|
|
||||||
' Confirma la transacción.
|
|
||||||
con.TransactionSuccessful
|
|
||||||
|
|
||||||
' Comprime la salida antes de enviarla.
|
|
||||||
Dim out As OutputStream = cs.WrapOutputStream(resp.OutputStream, "gzip")
|
|
||||||
' Escribe la respuesta usando el serializador V1.
|
|
||||||
WriteObject(Main.VERSION, out)
|
|
||||||
WriteObject("batch", out)
|
|
||||||
WriteInt(res.Length, out)
|
|
||||||
For Each r As Int In res
|
|
||||||
WriteInt(r, out)
|
|
||||||
Next
|
|
||||||
out.Close
|
|
||||||
Catch
|
|
||||||
con.Rollback
|
|
||||||
Log(LastException)
|
|
||||||
SendPlainTextError(resp, 500, LastException.Message)
|
|
||||||
End Try
|
|
||||||
Return $"batch (size=${numberOfStatements})"$
|
|
||||||
End Sub
|
|
||||||
|
|
||||||
' Ejecuta una consulta única usando el protocolo V1.
|
|
||||||
Private Sub ExecuteQuery(DB As String, con As SQL, in As InputStream, resp As ServletResponse) As String
|
|
||||||
Log("====================== ExecuteQuery =====================")
|
|
||||||
' Deserializa los datos de la petición usando el protocolo V1.
|
|
||||||
Dim clientVersion As Float = ReadObject(in) 'ignore
|
|
||||||
Dim queryName As String = ReadObject(in)
|
|
||||||
Dim limit As Int = ReadInt(in)
|
|
||||||
Dim params As List = ReadList(in)
|
|
||||||
' Obtiene la sentencia SQL.
|
|
||||||
Dim theSql As String = Connector.GetCommand(DB, queryName)
|
|
||||||
' Log(444 & "|" & theSql)
|
|
||||||
|
|
||||||
' <<< INICIO NUEVA VALIDACIÓN: VERIFICAR SI EL COMANDO EXISTE (V1) >>>
|
|
||||||
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)
|
|
||||||
SendPlainTextError(resp, 400, errorMessage)
|
|
||||||
Return "error"
|
|
||||||
End If
|
|
||||||
' <<< 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
|
|
||||||
|
|
||||||
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 ---
|
|
||||||
|
|
||||||
' Ejecuta la consulta.
|
|
||||||
Dim rs As ResultSet = con.ExecQuery2(theSql, params)
|
|
||||||
If limit <= 0 Then limit = 0x7fffffff 'max int
|
|
||||||
Dim jrs As JavaObject = rs
|
|
||||||
Dim rsmd As JavaObject = jrs.RunMethod("getMetaData", Null)
|
|
||||||
Dim cols As Int = rs.ColumnCount
|
|
||||||
' Comprime el stream de salida.
|
|
||||||
Dim out As OutputStream = cs.WrapOutputStream(resp.OutputStream, "gzip")
|
|
||||||
' Escribe la cabecera de la respuesta V1.
|
|
||||||
WriteObject(Main.VERSION, out)
|
|
||||||
WriteObject("query", out)
|
|
||||||
WriteInt(rs.ColumnCount, out)
|
|
||||||
' Escribe los nombres de las columnas.
|
|
||||||
For i = 0 To cols - 1
|
|
||||||
WriteObject(rs.GetColumnName(i), out)
|
|
||||||
Next
|
|
||||||
|
|
||||||
' Itera sobre las filas del resultado.
|
|
||||||
Do While rs.NextRow And limit > 0
|
|
||||||
' Escribe un byte '1' para indicar que viene una fila.
|
|
||||||
WriteByte(1, out)
|
|
||||||
' Itera sobre las columnas de la fila.
|
|
||||||
For i = 0 To cols - 1
|
|
||||||
Dim ct As Int = rsmd.RunMethod("getColumnType", Array(i + 1))
|
|
||||||
' Maneja los tipos de datos binarios de forma especial.
|
|
||||||
If ct = -2 Or ct = 2004 Or ct = -3 Or ct = -4 Then
|
|
||||||
WriteObject(rs.GetBlob2(i), out)
|
|
||||||
Else
|
|
||||||
' Escribe el valor de la columna.
|
|
||||||
WriteObject(jrs.RunMethod("getObject", Array(i + 1)), out)
|
|
||||||
End If
|
|
||||||
Next
|
|
||||||
limit = limit - 1
|
|
||||||
Loop
|
|
||||||
' Escribe un byte '0' para indicar el fin de las filas.
|
|
||||||
WriteByte(0, out)
|
|
||||||
out.Close
|
|
||||||
rs.Close
|
|
||||||
|
|
||||||
Return "query: " & queryName
|
|
||||||
End Sub
|
|
||||||
|
|
||||||
' Escribe un único byte en el stream de salida.
|
|
||||||
Private Sub WriteByte(value As Byte, out As OutputStream)
|
|
||||||
out.WriteBytes(Array As Byte(value), 0, 1)
|
|
||||||
End Sub
|
|
||||||
|
|
||||||
' Serializador principal para el protocolo V1. Escribe un objeto al stream.
|
|
||||||
Private Sub WriteObject(o As Object, out As OutputStream)
|
|
||||||
Dim data() As Byte
|
|
||||||
' Escribe un byte de tipo seguido de los datos.
|
|
||||||
If o = Null Then
|
|
||||||
out.WriteBytes(Array As Byte(T_NULL), 0, 1)
|
|
||||||
Else If o Is Short Then
|
|
||||||
out.WriteBytes(Array As Byte(T_SHORT), 0, 1)
|
|
||||||
data = bc.ShortsToBytes(Array As Short(o))
|
|
||||||
Else If o Is Int Then
|
|
||||||
out.WriteBytes(Array As Byte(T_INT), 0, 1)
|
|
||||||
data = bc.IntsToBytes(Array As Int(o))
|
|
||||||
Else If o Is Float Then
|
|
||||||
out.WriteBytes(Array As Byte(T_FLOAT), 0, 1)
|
|
||||||
data = bc.FloatsToBytes(Array As Float(o))
|
|
||||||
Else If o Is Double Then
|
|
||||||
out.WriteBytes(Array As Byte(T_DOUBLE), 0, 1)
|
|
||||||
data = bc.DoublesToBytes(Array As Double(o))
|
|
||||||
Else If o Is Long Then
|
|
||||||
out.WriteBytes(Array As Byte(T_LONG), 0, 1)
|
|
||||||
data = bc.LongsToBytes(Array As Long(o))
|
|
||||||
Else If o Is Boolean Then
|
|
||||||
out.WriteBytes(Array As Byte(T_BOOLEAN), 0, 1)
|
|
||||||
Dim b As Boolean = o
|
|
||||||
Dim data(1) As Byte
|
|
||||||
If b Then data(0) = 1 Else data(0) = 0
|
|
||||||
Else If GetType(o) = "[B" Then ' Si el objeto es un array de bytes (BLOB)
|
|
||||||
data = o
|
|
||||||
out.WriteBytes(Array As Byte(T_BLOB), 0, 1)
|
|
||||||
' Escribe la longitud de los datos antes de los datos mismos.
|
|
||||||
WriteInt(data.Length, out)
|
|
||||||
Else ' Trata todo lo demás como un String
|
|
||||||
out.WriteBytes(Array As Byte(T_STRING), 0, 1)
|
|
||||||
data = bc.StringToBytes(o, "UTF8")
|
|
||||||
' Escribe la longitud del string antes del string.
|
|
||||||
WriteInt(data.Length, out)
|
|
||||||
End If
|
|
||||||
' Escribe los bytes del dato.
|
|
||||||
If data.Length > 0 Then out.WriteBytes(data, 0, data.Length)
|
|
||||||
End Sub
|
|
||||||
|
|
||||||
' Deserializador principal para el protocolo V1. Lee un objeto del stream.
|
|
||||||
Private Sub ReadObject(In As InputStream) As Object
|
|
||||||
' Lee el primer byte para determinar el tipo de dato.
|
|
||||||
Dim data(1) As Byte
|
|
||||||
In.ReadBytes(data, 0, 1)
|
|
||||||
Select data(0)
|
|
||||||
Case T_NULL
|
|
||||||
Return Null
|
|
||||||
Case T_SHORT
|
|
||||||
Dim data(2) As Byte
|
|
||||||
Return bc.ShortsFromBytes(ReadBytesFully(In, data, data.Length))(0)
|
|
||||||
Case T_INT
|
|
||||||
Dim data(4) As Byte
|
|
||||||
Return bc.IntsFromBytes(ReadBytesFully(In, data, data.Length))(0)
|
|
||||||
Case T_LONG
|
|
||||||
Dim data(8) As Byte
|
|
||||||
Return bc.LongsFromBytes(ReadBytesFully(In, data, data.Length))(0)
|
|
||||||
Case T_FLOAT
|
|
||||||
Dim data(4) As Byte
|
|
||||||
Return bc.FloatsFromBytes(ReadBytesFully(In, data, data.Length))(0)
|
|
||||||
Case T_DOUBLE
|
|
||||||
Dim data(8) As Byte
|
|
||||||
Return bc.DoublesFromBytes(ReadBytesFully(In, data, data.Length))(0)
|
|
||||||
Case T_BOOLEAN
|
|
||||||
Dim b As Byte = ReadByte(In)
|
|
||||||
Return b = 1
|
|
||||||
Case T_BLOB
|
|
||||||
' Lee la longitud, luego lee esa cantidad de bytes.
|
|
||||||
Dim len As Int = ReadInt(In)
|
|
||||||
Dim data(len) As Byte
|
|
||||||
Return ReadBytesFully(In, data, data.Length)
|
|
||||||
Case Else ' T_STRING
|
|
||||||
' Lee la longitud, luego lee esa cantidad de bytes y los convierte a string.
|
|
||||||
Dim len As Int = ReadInt(In)
|
|
||||||
Dim data(len) As Byte
|
|
||||||
ReadBytesFully(In, data, data.Length)
|
|
||||||
Return BytesToString(data, 0, data.Length, "UTF8")
|
|
||||||
End Select
|
|
||||||
End Sub
|
|
||||||
|
|
||||||
' Se asegura de leer exactamente la cantidad de bytes solicitada del stream.
|
|
||||||
Private Sub ReadBytesFully(In As InputStream, Data() As Byte, Len As Int) As Byte()
|
|
||||||
Dim count = 0, Read As Int
|
|
||||||
' Sigue leyendo en un bucle hasta llenar el buffer, por si los datos llegan en partes.
|
|
||||||
Do While count < Len And Read > -1
|
|
||||||
Read = In.ReadBytes(Data, count, Len - count)
|
|
||||||
count = count + Read
|
|
||||||
Loop
|
|
||||||
Return Data
|
|
||||||
End Sub
|
|
||||||
|
|
||||||
' Escribe un entero (4 bytes) en el stream.
|
|
||||||
Private Sub WriteInt(i As Int, out As OutputStream)
|
|
||||||
Dim data() As Byte
|
|
||||||
data = bc.IntsToBytes(Array As Int(i))
|
|
||||||
out.WriteBytes(data, 0, data.Length)
|
|
||||||
End Sub
|
|
||||||
|
|
||||||
' Lee un entero (4 bytes) del stream.
|
|
||||||
Private Sub ReadInt(In As InputStream) As Int
|
|
||||||
Dim data(4) As Byte
|
|
||||||
Return bc.IntsFromBytes(ReadBytesFully(In, data, data.Length))(0)
|
|
||||||
End Sub
|
|
||||||
|
|
||||||
' Lee un solo byte del stream.
|
|
||||||
Private Sub ReadByte(In As InputStream) As Byte
|
|
||||||
Dim data(1) As Byte
|
|
||||||
In.ReadBytes(data, 0, 1)
|
|
||||||
Return data(0)
|
|
||||||
End Sub
|
|
||||||
|
|
||||||
' Lee una lista de objetos del stream (protocolo V1).
|
|
||||||
Private Sub ReadList(in As InputStream) As List
|
|
||||||
' Primero lee la cantidad de elementos en la lista.
|
|
||||||
Dim len As Int = ReadInt(in)
|
|
||||||
Dim l1 As List
|
|
||||||
l1.Initialize
|
|
||||||
' Luego lee cada objeto uno por uno y lo añade a la lista.
|
|
||||||
For i = 0 To len - 1
|
|
||||||
l1.Add(ReadObject(in))
|
|
||||||
Next
|
|
||||||
Return l1
|
|
||||||
End Sub
|
|
||||||
'#end If
|
|
||||||
|
|
||||||
' 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.
|
|
||||||
' resp: El objeto ServletResponse para enviar la respuesta.
|
|
||||||
' statusCode: El código de estado HTTP (ej. 400 para Bad Request, 500 para Internal Server Error).
|
|
||||||
' errorMessage: El mensaje de error que se enviará al cliente.
|
|
||||||
Private Sub SendPlainTextError(resp As ServletResponse, statusCode As Int, errorMessage As String)
|
|
||||||
Try
|
|
||||||
' Establece el código de estado HTTP (ej. 400, 500).
|
|
||||||
resp.Status = statusCode
|
|
||||||
|
|
||||||
' Define el tipo de contenido como texto plano, con codificación UTF-8 para soportar acentos.
|
|
||||||
resp.ContentType = "text/plain; charset=utf-8"
|
|
||||||
|
|
||||||
' Obtiene el OutputStream de la respuesta para escribir los datos directamente.
|
|
||||||
Dim out As OutputStream = resp.OutputStream
|
|
||||||
|
|
||||||
' Convierte el mensaje de error a un array de bytes usando UTF-8.
|
|
||||||
Dim data() As Byte = errorMessage.GetBytes("UTF8")
|
|
||||||
|
|
||||||
' Escribe los bytes en el stream de salida.
|
|
||||||
out.WriteBytes(data, 0, data.Length)
|
|
||||||
|
|
||||||
' Cierra el stream para asegurar que todos los datos se envíen correctamente.
|
|
||||||
out.Close
|
|
||||||
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)
|
|
||||||
End Try
|
|
||||||
End Sub
|
|
||||||
233
Manager.bas
233
Manager.bas
@@ -28,21 +28,21 @@ Sub Handle(req As ServletRequest, resp As ServletResponse)
|
|||||||
' --- MANEJO ESPECIAL PARA SNAPSHOT ---
|
' --- MANEJO ESPECIAL PARA SNAPSHOT ---
|
||||||
' El comando "snapshot" no devuelve HTML, sino una imagen. Lo manejamos por separado al principio.
|
' El comando "snapshot" no devuelve HTML, sino una imagen. Lo manejamos por separado al principio.
|
||||||
If Command = "snapshot" Then
|
If Command = "snapshot" Then
|
||||||
Try
|
' Try
|
||||||
resp.ContentType = "image/png"
|
' resp.ContentType = "image/png"
|
||||||
Dim robot, toolkit As JavaObject
|
' Dim robot, toolkit As JavaObject
|
||||||
robot.InitializeNewInstance("java.awt.Robot", Null)
|
' robot.InitializeNewInstance("java.awt.Robot", Null)
|
||||||
toolkit.InitializeStatic("java.awt.Toolkit")
|
' toolkit.InitializeStatic("java.awt.Toolkit")
|
||||||
Dim screenRect As JavaObject
|
' Dim screenRect As JavaObject
|
||||||
screenRect.InitializeNewInstance("java.awt.Rectangle", Array As Object( _
|
' screenRect.InitializeNewInstance("java.awt.Rectangle", Array As Object( _
|
||||||
toolkit.RunMethodJO("getDefaultToolkit", Null).RunMethod("getScreenSize", Null)))
|
' toolkit.RunMethodJO("getDefaultToolkit", Null).RunMethod("getScreenSize", Null)))
|
||||||
Dim image As JavaObject = robot.RunMethod("createScreenCapture", Array As Object(screenRect))
|
' Dim image As JavaObject = robot.RunMethod("createScreenCapture", Array As Object(screenRect))
|
||||||
Dim ImageIO As JavaObject
|
' Dim ImageIO As JavaObject
|
||||||
ImageIO.InitializeStatic("javax.imageio.ImageIO").RunMethod("write", Array As Object(image, "png", resp.OutputStream))
|
' ImageIO.InitializeStatic("javax.imageio.ImageIO").RunMethod("write", Array As Object(image, "png", resp.OutputStream))
|
||||||
Catch
|
' Catch
|
||||||
resp.SendError(500, LastException.Message)
|
' resp.SendError(500, LastException.Message)
|
||||||
End Try
|
' End Try
|
||||||
Return ' Detenemos la ejecución aquí para no enviar más HTML.
|
' Return ' Detenemos la ejecución aquí para no enviar más HTML.
|
||||||
End If
|
End If
|
||||||
' --- FIN DE MANEJO ESPECIAL ---
|
' --- FIN DE MANEJO ESPECIAL ---
|
||||||
|
|
||||||
@@ -67,7 +67,8 @@ Sub Handle(req As ServletRequest, resp As ServletResponse)
|
|||||||
' --- Cabecera, Botón y Formulario Oculto (igual que antes) ---
|
' --- Cabecera, Botón y Formulario Oculto (igual que antes) ---
|
||||||
sb.Append("<h1>Panel de Administración jRDC</h1>")
|
sb.Append("<h1>Panel de Administración jRDC</h1>")
|
||||||
sb.Append($"Bienvenido, <b>${req.GetSession.GetAttribute("username")}</b><br>"$)
|
sb.Append($"Bienvenido, <b>${req.GetSession.GetAttribute("username")}</b><br>"$)
|
||||||
sb.Append("<p class='nav'><a href='/manager?command=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></p><hr>")
|
' sb.Append("<p class='nav'><a href='/manager?command=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></p><hr>")
|
||||||
|
sb.Append("<p class='nav'><a href='/manager?command=test'>Test</a> | <a href='/manager?command=ping'>Ping</a> | <a href='/manager?command=reload'>Reload</a> | <a href='/manager?command=slowqueries'>Queries Lentos</a> | <a href='/manager?command=totalcon'>Estadísticas Pool</a> | <a href='/manager?command=rpm2'>Reiniciar (pm2)</a> | <a href='/manager?command=reviveBow'>Revive Bow</a></p><hr>")
|
||||||
' sb.Append("<button onclick='toggleForm()'>Cambiar Contraseña</button>")
|
' sb.Append("<button onclick='toggleForm()'>Cambiar Contraseña</button>")
|
||||||
sb.Append("<div id='changePassForm' style='display:none;'>")
|
sb.Append("<div id='changePassForm' style='display:none;'>")
|
||||||
sb.Append("<h2>Cambiar Contraseña</h2><form action='/changepass' method='post'>")
|
sb.Append("<h2>Cambiar Contraseña</h2><form action='/changepass' method='post'>")
|
||||||
@@ -84,16 +85,142 @@ Sub Handle(req As ServletRequest, resp As ServletResponse)
|
|||||||
' ### INICIO DE TU LÓGICA DE COMANDOS INTEGRADA ###
|
' ### INICIO DE TU LÓGICA DE COMANDOS INTEGRADA ###
|
||||||
' =========================================================================
|
' =========================================================================
|
||||||
If Command = "reload" Then
|
If Command = "reload" Then
|
||||||
Private estaDB As String = ""
|
' Usamos un StringBuilder temporal para acumular los logs de la recarga antes de añadirlos al StringBuilder principal.
|
||||||
For i = 0 To Main.listaDeCP.Size - 1
|
Dim sbTemp As StringBuilder
|
||||||
Main.Connectors.Get(Main.listaDeCP.get(i)).As(RDCConnector).Initialize(Main.listaDeCP.get(i))
|
sbTemp.Initialize
|
||||||
If Main.listaDeCP.get(i) <> "DB1" Then estaDB = "." & Main.listaDeCP.get(i) Else estaDB = ""
|
sbTemp.Append($"Iniciando recarga de configuración (Hot-Swap) ($DateTime{DateTime.Now})"$).Append("<br>" & CRLF)
|
||||||
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/>"$)
|
' 1. Crear un nuevo mapa temporal para almacenar los conectores recién inicializados.
|
||||||
sb.Append($"<b>JdbcUrl:</b> ${Main.Connectors.Get(Main.listaDeCP.get(i)).As(RDCConnector).config.Get("JdbcUrl")}</b><br/>"$)
|
Dim newConnectors As Map
|
||||||
sb.Append($"<b>User:</b> ${Main.Connectors.Get(Main.listaDeCP.get(i)).As(RDCConnector).config.Get("User")}</b><br/>"$)
|
newConnectors.Initialize
|
||||||
sb.Append($"<b>ServerPort:</b> ${Main.srvr.Port}</b><br/><br/>"$)
|
|
||||||
|
' Guardamos una referencia al mapa de conectores actualmente activos.
|
||||||
|
Dim oldConnectors As Map
|
||||||
|
|
||||||
|
Dim reloadSuccessful As Boolean = True
|
||||||
|
|
||||||
|
' *** INICIO DEL BLOQUE CRÍTICO 1: Obtener oldConnectors con ReentrantLock ***
|
||||||
|
Dim lock1Acquired As Boolean = False ' Bandera para saber si el bloqueo fue adquirido.
|
||||||
|
Try
|
||||||
|
Main.MainConnectorsLock.RunMethod("lock", Null) ' Adquirimos el bloqueo.
|
||||||
|
lock1Acquired = True ' Marcamos que el bloqueo fue adquirido.
|
||||||
|
oldConnectors = Main.Connectors ' Obtenemos la referencia al mapa actual de conectores.
|
||||||
|
Catch
|
||||||
|
sbTemp.Append($" -> ERROR CRÍTICO: No se pudo adquirir el bloqueo para leer conectores antiguos: ${LastException.Message}"$).Append("<br>" & CRLF)
|
||||||
|
reloadSuccessful = False ' Si falla aquí, la recarga no puede continuar.
|
||||||
|
End Try
|
||||||
|
If lock1Acquired Then
|
||||||
|
Main.MainConnectorsLock.RunMethod("unlock", Null) ' Liberamos el bloqueo si fue adquirido.
|
||||||
|
End If
|
||||||
|
' *** FIN DEL BLOQUE CRÍTICO 1 ***
|
||||||
|
|
||||||
|
If Not(reloadSuccessful) Then ' Si el primer bloqueo falló o la asignación, salimos temprano.
|
||||||
|
sb.Append(sbTemp.ToString) ' Añadimos los logs acumulados al StringBuilder principal.
|
||||||
|
sb.Append($"¡ERROR: La recarga de configuración falló en la fase de bloqueo inicial! Los conectores antiguos siguen activos."$).Append("<br>" & CRLF)
|
||||||
|
Return ' Salir del Handle ya que ocurrió un error crítico irrecuperable.
|
||||||
|
End If
|
||||||
|
|
||||||
|
' 2. Iterar sobre las bases de datos configuradas y crear *nuevas* instancias de RDCConnector.
|
||||||
|
For Each dbKey As String In Main.listaDeCP
|
||||||
|
Try
|
||||||
|
Dim newRDC As RDCConnector
|
||||||
|
newRDC.Initialize(dbKey) ' Inicializa la nueva instancia con la configuración fresca.
|
||||||
|
newConnectors.Put(dbKey, newRDC)
|
||||||
|
|
||||||
|
Dim newPoolStats As Map = newRDC.GetPoolStats
|
||||||
|
sbTemp.Append($" -> ${dbKey}: Nuevo conector inicializado. Conexiones: ${newPoolStats.Get("TotalConnections")}"$).Append("<br>" & CRLF)
|
||||||
|
|
||||||
|
Catch
|
||||||
|
sbTemp.Append($" -> ERROR CRÍTICO al inicializar nuevo conector para ${dbKey}: ${LastException.Message}"$).Append("<br>" & CRLF)
|
||||||
|
reloadSuccessful = False
|
||||||
|
Exit ' Si uno falla, abortamos la recarga completa para evitar un estado inconsistente.
|
||||||
|
End Try
|
||||||
Next
|
Next
|
||||||
|
|
||||||
|
sb.Append(sbTemp.ToString) ' Añadimos los logs acumulados de la inicialización al StringBuilder principal.
|
||||||
|
|
||||||
|
If reloadSuccessful Then
|
||||||
|
' 3. Si todos los nuevos conectores se inicializaron con éxito,
|
||||||
|
' realizamos el "cambio de cartel" (hot-swap) de forma atómica.
|
||||||
|
' *** INICIO DEL BLOQUE CRÍTICO 2: Reemplazar Main.Connectors con ReentrantLock ***
|
||||||
|
Dim lock2Acquired As Boolean = False ' Bandera para saber si el bloqueo fue adquirido.
|
||||||
|
Try
|
||||||
|
Main.MainConnectorsLock.RunMethod("lock", Null) ' Adquirimos el bloqueo.
|
||||||
|
lock2Acquired = True ' Marcamos que el bloqueo fue adquirido.
|
||||||
|
Main.Connectors = newConnectors ' Reemplazamos el mapa de conectores completo por el nuevo.
|
||||||
|
Catch
|
||||||
|
sb.Append($" -> ERROR CRÍTICO: No se pudo adquirir el bloqueo para reemplazar conectores: ${LastException.Message}"$).Append("<br>" & CRLF)
|
||||||
|
reloadSuccessful = False ' Si falla aquí, la recarga no se completó con éxito.
|
||||||
|
End Try
|
||||||
|
If lock2Acquired Then
|
||||||
|
Main.MainConnectorsLock.RunMethod("unlock", Null) ' Liberamos el bloqueo si fue adquirido.
|
||||||
|
End If
|
||||||
|
' *** FIN DEL BLOQUE CRÍTICO 2 ***
|
||||||
|
|
||||||
|
If reloadSuccessful Then ' Si el segundo bloqueo y swap fue exitoso
|
||||||
|
sb.Append($"¡Recarga de configuración completada con éxito (Hot-Swap)!"$).Append("<br>" & CRLF)
|
||||||
|
sb.Append($"Nuevos conectores activos. Verificando estado final..."$).Append("<br>" & CRLF)
|
||||||
|
|
||||||
|
' Mostrar el estado de los *nuevos* conectores después del swap.
|
||||||
|
If Main.Connectors.IsInitialized Then
|
||||||
|
Dim liveStats As Map
|
||||||
|
liveStats.Initialize
|
||||||
|
For Each dbKey As String In Main.Connectors.Keys
|
||||||
|
Dim currentConnector As RDCConnector = Main.Connectors.Get(dbKey).As(RDCConnector)
|
||||||
|
liveStats.Put(dbKey, currentConnector.GetPoolStats) ' Obtiene las estadísticas en tiempo real.
|
||||||
|
Next
|
||||||
|
j.Initialize(liveStats)
|
||||||
|
sb.Append($"Estado actual de los pools: ${j.ToString}"$).Append(CRLF) ' No <br> para JSON puro
|
||||||
|
End If
|
||||||
|
|
||||||
|
' 4. Cerrar explícitamente los pools de conexión de las instancias antiguas.
|
||||||
|
If oldConnectors.IsInitialized Then
|
||||||
|
sb.Append("Cerrando conectores antiguos...").Append("<br>" & CRLF)
|
||||||
|
For Each dbKey As String In oldConnectors.Keys
|
||||||
|
Dim oldRDC As RDCConnector = oldConnectors.Get(dbKey).As(RDCConnector)
|
||||||
|
If oldRDC <> Null And oldRDC.IsInitialized Then
|
||||||
|
oldRDC.Close ' Llama al método Close que hemos añadido al RDCConnector.
|
||||||
|
sb.Append($" -> Pool antiguo de ${dbKey} cerrado."$).Append("<br>" & CRLF)
|
||||||
|
End If
|
||||||
|
Next
|
||||||
|
End If
|
||||||
|
Else
|
||||||
|
sb.Append($"¡ERROR: La recarga de configuración falló en la fase de reemplazo de conectores! Los conectores antiguos siguen activos."$).Append("<br>" & CRLF)
|
||||||
|
End If
|
||||||
|
Else
|
||||||
|
sb.Append($"¡ERROR: La recarga de configuración falló durante la inicialización de nuevos conectores! Los conectores antiguos siguen activos."$).Append("<br>" & CRLF)
|
||||||
|
' Si la recarga falló, los conectores antiguos (oldConnectors) se mantienen activos
|
||||||
|
' y siguen sirviendo para evitar un paro del servicio.
|
||||||
|
End If
|
||||||
|
Else If Command = "slowqueries" Then ' <<< INICIO: NUEVA Lógica para mostrar las queries lentas
|
||||||
|
sb.Append("<h2>Consultas Lentas Recientes</h2>")
|
||||||
|
Try
|
||||||
|
' Ajusta la consulta SQL para obtener las 20 queries más lentas.
|
||||||
|
' Utilizamos datetime con 'unixepoch' y 'localtime' para una visualización legible del timestamp.
|
||||||
|
Dim rs As ResultSet = Main.SQL1.ExecQuery("SELECT query_name, duration_ms, datetime(timestamp / 1000, 'unixepoch', 'localtime') as timestamp_local, db_key, client_ip, busy_connections, handler_active_requests FROM query_logs ORDER BY duration_ms DESC LIMIT 20")
|
||||||
|
|
||||||
|
sb.Append("<table border='1' style='width:100%; text-align:left; border-collapse: collapse;'>")
|
||||||
|
sb.Append("<thead><tr><th>Query</th><th>Duración (ms)</th><th>Fecha/Hora Local</th><th>DB Key</th><th>Cliente IP</th><th>Conex. Ocupadas</th><th>Peticiones Activas</th></tr></thead>")
|
||||||
|
sb.Append("<tbody>")
|
||||||
|
|
||||||
|
Do While rs.NextRow
|
||||||
|
sb.Append("<tr>")
|
||||||
|
sb.Append($"<td>${rs.GetString("query_name")}</td>"$)
|
||||||
|
sb.Append($"<td>${rs.GetLong("duration_ms")}</td>"$)
|
||||||
|
sb.Append($"<td>${rs.GetString("timestamp_local")}</td>"$)
|
||||||
|
sb.Append($"<td>${rs.GetString("db_key")}</td>"$)
|
||||||
|
sb.Append($"<td>${rs.GetString("client_ip")}</td>"$)
|
||||||
|
sb.Append($"<td>${rs.GetInt("busy_connections")}</td>"$)
|
||||||
|
sb.Append($"<td>${rs.GetInt("handler_active_requests")}</td>"$)
|
||||||
|
sb.Append("</tr>")
|
||||||
|
Loop
|
||||||
|
sb.Append("</tbody>")
|
||||||
|
sb.Append("</table>")
|
||||||
|
rs.Close
|
||||||
|
Catch
|
||||||
|
Log("Error al obtener queries lentas en Manager: " & LastException.Message)
|
||||||
|
sb.Append($"<p style='color:red;'>Error al cargar queries lentas: ${LastException.Message}</p>"$)
|
||||||
|
End Try
|
||||||
Else If Command = "test" Then
|
Else If Command = "test" Then
|
||||||
Try
|
Try
|
||||||
Dim con As SQL = Main.Connectors.Get("DB1").As(RDCConnector).GetConnection("")
|
Dim con As SQL = Main.Connectors.Get("DB1").As(RDCConnector).GetConnection("")
|
||||||
@@ -113,11 +240,17 @@ Sub Handle(req As ServletRequest, resp As ServletResponse)
|
|||||||
Else If Command = "rsx" Then
|
Else If Command = "rsx" Then
|
||||||
Log($"Ejecutamos ${File.DirApp}\start.bat"$)
|
Log($"Ejecutamos ${File.DirApp}\start.bat"$)
|
||||||
sb.Append($"Ejecutamos ${File.DirApp}\start.bat"$)
|
sb.Append($"Ejecutamos ${File.DirApp}\start.bat"$)
|
||||||
' Public shl As Shell...
|
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
|
Else If Command = "rpm2" Then
|
||||||
Log($"Ejecutamos ${File.DirApp}\reiniciaProcesoPM2.bat"$)
|
Log($"Ejecutamos ${File.DirApp}\reiniciaProcesoPM2.bat"$)
|
||||||
sb.Append($"Ejecutamos ${File.DirApp}\reiniciaProcesoPM2.bat"$)
|
sb.Append($"Ejecutamos ${File.DirApp}\reiniciaProcesoPM2.bat"$)
|
||||||
' Public shl As Shell...
|
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
|
Else If Command = "reviveBow" Then
|
||||||
Log($"Ejecutamos ${File.DirApp}\reiniciaProcesoBow.bat"$)
|
Log($"Ejecutamos ${File.DirApp}\reiniciaProcesoBow.bat"$)
|
||||||
sb.Append($"Ejecutamos ${File.DirApp}\reiniciaProcesoBow.bat<br><br>"$)
|
sb.Append($"Ejecutamos ${File.DirApp}\reiniciaProcesoBow.bat<br><br>"$)
|
||||||
@@ -168,13 +301,49 @@ Sub Handle(req As ServletRequest, resp As ServletResponse)
|
|||||||
' j.Initialize(Global.mpBlockConnection)
|
' j.Initialize(Global.mpBlockConnection)
|
||||||
sb.Append(j.ToString)
|
sb.Append(j.ToString)
|
||||||
End If
|
End If
|
||||||
Else If Command = "totalcon" Then
|
|
||||||
If GlobalParameters.mpTotalConnections.IsInitialized Then
|
|
||||||
j.Initialize(GlobalParameters.mpTotalConnections)
|
|
||||||
sb.Append(j.ToString)
|
|
||||||
End If
|
|
||||||
Else If Command = "ping" Then
|
Else If Command = "ping" Then
|
||||||
sb.Append($"Pong ($DateTime{DateTime.Now})"$)
|
sb.Append($"Pong ($DateTime{DateTime.Now})"$)
|
||||||
|
Else If Command = "totalcon" Then ' <<< Modificado: Ahora usa GetPoolStats para cada pool
|
||||||
|
' Verificamos que el mapa global de conexiones esté inicializado.
|
||||||
|
' Aunque no lo poblamos directamente, es un buen chequeo de estado.
|
||||||
|
If GlobalParameters.mpTotalConnections.IsInitialized Then
|
||||||
|
sb.Append("<h2>Estadísticas del Pool de Conexiones por DB:</h2>")
|
||||||
|
|
||||||
|
' Creamos un mapa LOCAL para almacenar las estadísticas de TODOS los pools de conexiones.
|
||||||
|
Dim allPoolStats As Map
|
||||||
|
allPoolStats.Initialize
|
||||||
|
|
||||||
|
' Iteramos sobre cada clave de base de datos que tenemos configurada (DB1, DB2, etc.).
|
||||||
|
For Each dbKey As String In Main.listaDeCP
|
||||||
|
' Obtenemos el conector RDC para la base de datos actual.
|
||||||
|
Dim connector As RDCConnector = Main.Connectors.Get(dbKey).As(RDCConnector)
|
||||||
|
|
||||||
|
' Si el conector no está inicializado (lo cual no debería ocurrir si Main.AppStart funcionó),
|
||||||
|
' registramos un error y pasamos al siguiente.
|
||||||
|
If connector.IsInitialized = False Then
|
||||||
|
Log($"Manager: ADVERTENCIA: El conector para ${dbKey} no está inicializado."$)
|
||||||
|
Dim errorMap As Map = CreateMap("Error": "Conector no inicializado o no cargado correctamente")
|
||||||
|
allPoolStats.Put(dbKey, errorMap)
|
||||||
|
Continue ' Salta a la siguiente iteración del bucle.
|
||||||
|
End If
|
||||||
|
|
||||||
|
' Llamamos al método GetPoolStats del conector para obtener las métricas de su pool.
|
||||||
|
Dim poolStats As Map = connector.GetPoolStats
|
||||||
|
|
||||||
|
' Añadimos las estadísticas de este pool (poolStats) al mapa general (allPoolStats),
|
||||||
|
' usando la clave de la base de datos (dbKey) como su identificador.
|
||||||
|
allPoolStats.Put(dbKey, poolStats)
|
||||||
|
Next
|
||||||
|
|
||||||
|
' Inicializamos el generador JSON con el mapa 'allPoolStats' (que ahora sí debería contener datos).
|
||||||
|
' (La variable 'j' ya está declarada en Class_Globals de Manager.bas, no la declares de nuevo aquí).
|
||||||
|
j.Initialize(allPoolStats)
|
||||||
|
|
||||||
|
' Añadimos la representación JSON de las estadísticas al StringBuilder para la respuesta HTML.
|
||||||
|
sb.Append(j.ToString)
|
||||||
|
Else
|
||||||
|
sb.Append("El mapa de conexiones GlobalParameters.mpTotalConnections no está inicializado.")
|
||||||
|
End If
|
||||||
End If
|
End If
|
||||||
' =========================================================================
|
' =========================================================================
|
||||||
' ### FIN DE TU LÓGICA DE COMANDOS ###
|
' ### FIN DE TU LÓGICA DE COMANDOS ###
|
||||||
|
|||||||
349
RDCConnector.bas
349
RDCConnector.bas
@@ -4,126 +4,293 @@ ModulesStructureVersion=1
|
|||||||
Type=Class
|
Type=Class
|
||||||
Version=4.19
|
Version=4.19
|
||||||
@EndOfDesignText@
|
@EndOfDesignText@
|
||||||
'Class module
|
' Módulo de clase: RDCConnector
|
||||||
|
' Esta clase gestiona el pool de conexiones a una base de datos específica.
|
||||||
|
' Cada instancia de RDCConnector maneja la conexión y los comandos para una base de datos.
|
||||||
|
|
||||||
Sub Class_Globals
|
Sub Class_Globals
|
||||||
Private pool As ConnectionPool
|
Private pool As ConnectionPool ' Objeto principal para gestionar el pool de conexiones de la base de datos (usa C3P0 internamente).
|
||||||
Private DebugQueries As Boolean
|
Private DebugQueries As Boolean ' Bandera para activar/desactivar el modo de depuración de queries.
|
||||||
Dim commands As Map
|
Dim commands As Map ' Almacena los comandos SQL específicos de esta base de datos, cargados de su archivo de configuración.
|
||||||
Public serverPort As Int
|
Public serverPort As Int ' El puerto que el servidor HTTP usará, obtenido del archivo de configuración principal (config.properties).
|
||||||
Public usePool As Boolean = True
|
Public usePool As Boolean = True ' Indica si se debe usar el pool de conexiones (siempre True en este diseño).
|
||||||
Dim config As Map
|
Dim config As Map ' Almacena la configuración completa (JdbcUrl, User, Password, etc.) cargada de su respectivo archivo .properties.
|
||||||
End Sub
|
End Sub
|
||||||
|
|
||||||
'Initializes the object. You can add parameters to this method if needed.
|
' Subrutina de inicialización para el conector de una base de datos específica.
|
||||||
|
' Se llama una vez por cada base de datos (DB1, DB2, DB3, DB4) al iniciar el servidor.
|
||||||
|
' DB: El identificador único de la base de datos (ej. "DB1", "DB2").
|
||||||
Public Sub Initialize(DB As String)
|
Public Sub Initialize(DB As String)
|
||||||
' Log("RDCConnector Initialize")
|
' Si el identificador es "DB1", se usa una cadena vacía para que File.ReadMap cargue "config.properties" (el archivo por defecto).
|
||||||
If DB.EqualsIgnoreCase("DB1") Then DB = "" 'Esto para el config.properties por default
|
If DB.EqualsIgnoreCase("DB1") Then DB = ""
|
||||||
Dim config As Map = LoadConfigMap(DB)
|
|
||||||
Log($"Inicializamos ${DB}, usuario: ${config.Get("User")}"$)
|
' PASO 1: Cargar la configuración desde el archivo .properties correspondiente.
|
||||||
pool.Initialize(config.Get("DriverClass"), config.Get("JdbcUrl"), config.Get("User"), config.Get("Password"))
|
' Es CRUCIAL que se asigne a la variable de CLASE 'config' (sin 'Dim' local)
|
||||||
Dim jo As JavaObject = pool
|
' para que la configuración cargada del archivo sea persistente para esta instancia del conector.
|
||||||
jo.RunMethod("setMaxPoolSize", Array(5)) 'number of concurrent connections
|
config = LoadConfigMap(DB)
|
||||||
|
|
||||||
|
' Bloque Try-Catch para la inicialización y configuración del pool.
|
||||||
|
' Esto capturará cualquier error crítico que impida la conexión inicial a la base de datos.
|
||||||
|
Try
|
||||||
|
' PASO 2: Inicializar el objeto B4X ConnectionPool.
|
||||||
|
' Esto crea la instancia subyacente de com.mchange.v2.c3p0.ComboPooledDataSource (la librería C3P0).
|
||||||
|
' En este punto, C3P0 solo se inicializa como objeto. Aún no intenta hacer conexiones activas.
|
||||||
|
' Se le pasan los parámetros básicos para que C3P0 pueda construirse.
|
||||||
|
pool.Initialize(config.Get("DriverClass"), config.Get("JdbcUrl"), config.Get("User"), config.Get("Password"))
|
||||||
|
|
||||||
|
Dim jo As JavaObject = pool ' Obtener la referencia JavaObject para acceder a métodos de configuración avanzados de C3P0.
|
||||||
|
|
||||||
|
' PASO 3: Aplicar *todas* las propiedades de configuración de C3P0 INMEDIATAMENTE.
|
||||||
|
' Esto debe ocurrir *después* de 'pool.Initialize' pero *antes* de que C3P0 intente realmente adquirir conexiones.
|
||||||
|
' Esto asegura que las configuraciones sean efectivas desde el primer intento de conexión.
|
||||||
|
|
||||||
|
' Lectura de los valores desde el archivo de configuración, con valores por defecto si no se encuentran.
|
||||||
|
Dim initialPoolSize As Int = config.GetDefault("InitialPoolSize", 3)
|
||||||
|
Dim minPoolSize As Int = config.GetDefault("MinPoolSize", 2)
|
||||||
|
Dim maxPoolSize As Int = config.GetDefault("MaxPoolSize", 5)
|
||||||
|
Dim acquireIncrement As Int = config.GetDefault("AcquireIncrement", 5)
|
||||||
|
|
||||||
' com.mchange.v2.c3p0.ComboPooledDataSource [
|
' Configuración de los parámetros del pool de conexiones C3P0:
|
||||||
' acquireIncrement -> 3,
|
jo.RunMethod("setInitialPoolSize", Array(initialPoolSize)) ' Define el número de conexiones que se intentarán crear al iniciar el pool.
|
||||||
' acquireRetryAttempts -> 30,
|
jo.RunMethod("setMinPoolSize", Array(minPoolSize)) ' Fija el número mínimo de conexiones que el pool mantendrá abiertas.
|
||||||
' acquireRetryDelay -> 1000,
|
jo.RunMethod("setMaxPoolSize", Array(maxPoolSize)) ' Define el número máximo de conexiones simultáneas.
|
||||||
' autoCommitOnClose -> False,
|
jo.RunMethod("setAcquireIncrement", Array(acquireIncrement)) ' Cuántas conexiones nuevas se añaden en lote si el pool se queda sin disponibles.
|
||||||
' automaticTestTable -> Null,
|
jo.RunMethod("setMaxIdleTime", Array As Object(config.GetDefault("MaxIdleTime", 300))) ' Tiempo máximo de inactividad de una conexión antes de cerrarse (segundos).
|
||||||
' breakAfterAcquireFailure -> False,
|
jo.RunMethod("setMaxConnectionAge", Array As Object(config.GetDefault("MaxConnectionAge", 900))) ' Tiempo máximo de vida de una conexión (segundos).
|
||||||
' checkoutTimeout -> 20000,
|
jo.RunMethod("setCheckoutTimeout", Array As Object(config.GetDefault("CheckoutTimeout", 60000))) ' Tiempo máximo de espera por una conexión del pool (milisegundos).
|
||||||
' connectionCustomizerClassName -> Null,
|
|
||||||
' connectionTesterClassName -> com.mchange.v2.c3p0.impl.DefaultConnectionTester,
|
' LÍNEAS CRÍTICAS PARA FORZAR UN COMPORTAMIENTO NO SILENCIOSO DE C3P0:
|
||||||
' contextClassLoaderSource -> caller,
|
' Por defecto, C3P0 puede reintentar muchas veces y no lanzar una excepción si las conexiones iniciales fallan.
|
||||||
' dataSourceName -> 2rvxvdb7cyxd8zlw6dyb|63021689,
|
' Estas líneas fuerzan a C3P0 a ser estricto y reportar errores de inmediato.
|
||||||
' debugUnreturnedConnectionStackTraces -> False,
|
jo.RunMethod("setAcquireRetryAttempts", Array As Object(1)) ' Limita los intentos iniciales de adquisición a 1.
|
||||||
' description -> Null,
|
jo.RunMethod("setBreakAfterAcquireFailure", Array As Object(True)) ' ¡Forza a C3P0 a lanzar una excepción si falla al adquirir conexiones!
|
||||||
' driverClass -> oracle.jdbc.driver.OracleDriver,
|
|
||||||
' extensions -> {},
|
' PASO 4: Forzar la creación de conexiones iniciales y verificar el estado.
|
||||||
' factoryClassLocation -> Null,
|
' Este paso es VITAL. Obliga a C3P0 a intentar establecer las conexiones iniciales (InitialPoolSize)
|
||||||
' forceIgnoreUnresolvedTransactions -> False,
|
' *con la configuración ya establecida*. Si hay un problema de conectividad real, la excepción
|
||||||
' forceSynchronousCheckins -> False,
|
' se capturará aquí y se reportará.
|
||||||
' forceUseNamedDriverClass -> False,
|
Dim tempCon As SQL = pool.GetConnection ' Adquiere una conexión para forzar al pool a inicializarse.
|
||||||
' identityToken -> 2rvxvdb7cyxd8zlw6dyb|63021689,
|
If tempCon.IsInitialized Then
|
||||||
' idleConnectionTestPeriod -> 600,
|
tempCon.Close ' Devolvemos la conexión inmediatamente al pool para que esté disponible.
|
||||||
' initialPoolSize -> 3,
|
End If
|
||||||
' jdbcUrl -> jdbc:oracle:thin:@//10.0.0.110:1521/DBKMT,
|
|
||||||
' maxAdministrativeTaskTime -> 0,
|
' com.mchange.v2.c3p0.ComboPooledDataSource [
|
||||||
' maxConnectionAge -> 0,
|
' acquireIncrement -> 3,
|
||||||
' maxIdleTime -> 1800,
|
' acquireRetryAttempts -> 30,
|
||||||
' maxIdleTimeExcessConnections -> 0,
|
' acquireRetryDelay -> 1000,
|
||||||
' maxPoolSize -> 5,
|
' autoCommitOnClose -> False,
|
||||||
' maxStatements -> 150,
|
' automaticTestTable -> Null,
|
||||||
' maxStatementsPerConnection -> 0,
|
' breakAfterAcquireFailure -> False,
|
||||||
' minPoolSize -> 3,
|
' checkoutTimeout -> 20000,
|
||||||
' numHelperThreads -> 3,
|
' connectionCustomizerClassName -> Null,
|
||||||
' preferredTestQuery -> DBMS_SESSION.SET_IDENTIFIER('whatever'),
|
' connectionTesterClassName -> com.mchange.v2.c3p0.impl.DefaultConnectionTester,
|
||||||
' privilegeSpawnedThreads -> False,
|
' contextClassLoaderSource -> caller,
|
||||||
' properties -> {password=******, user=******},
|
' dataSourceName -> 2rvxvdb7cyxd8zlw6dyb|63021689,
|
||||||
' propertyCycle -> 0,
|
' debugUnreturnedConnectionStackTraces -> False,
|
||||||
' statementCacheNumDeferredCloseThreads -> 0,
|
' description -> Null,
|
||||||
' testConnectionOnCheckin -> False,
|
' driverClass -> oracle.jdbc.driver.OracleDriver,
|
||||||
' testConnectionOnCheckout -> True,
|
' extensions -> {},
|
||||||
' unreturnedConnectionTimeout -> 0,
|
' factoryClassLocation -> Null,
|
||||||
' userOverrides -> {},
|
' forceIgnoreUnresolvedTransactions -> False,
|
||||||
|
' forceSynchronousCheckins -> False,
|
||||||
|
' forceUseNamedDriverClass -> False,
|
||||||
|
' identityToken -> 2rvxvdb7cyxd8zlw6dyb|63021689,
|
||||||
|
' idleConnectionTestPeriod -> 600,
|
||||||
|
' initialPoolSize -> 3,
|
||||||
|
' jdbcUrl -> jdbc:oracle:thin:@//10.0.0.110:1521/DBKMT,
|
||||||
|
' maxAdministrativeTaskTime -> 0,
|
||||||
|
' maxConnectionAge -> 0,
|
||||||
|
' maxIdleTime -> 1800,
|
||||||
|
' maxIdleTimeExcessConnections -> 0,
|
||||||
|
' maxPoolSize -> 5,
|
||||||
|
' maxStatements -> 150,
|
||||||
|
' maxStatementsPerConnection -> 0,
|
||||||
|
' minPoolSize -> 3,
|
||||||
|
' numHelperThreads -> 3,
|
||||||
|
' preferredTestQuery -> DBMS_SESSION.SET_IDENTIFIER('whatever'),
|
||||||
|
' privilegeSpawnedThreads -> False,
|
||||||
|
' properties -> {password=******, user=******},
|
||||||
|
' propertyCycle -> 0,
|
||||||
|
' statementCacheNumDeferredCloseThreads -> 0,
|
||||||
|
' testConnectionOnCheckin -> False,
|
||||||
|
' testConnectionOnCheckout -> True,
|
||||||
|
' unreturnedConnectionTimeout -> 0,
|
||||||
|
' userOverrides -> {},
|
||||||
' usesTraditionalReflectiveProxies -> False
|
' usesTraditionalReflectiveProxies -> False
|
||||||
' ]
|
' ]
|
||||||
|
'
|
||||||
|
Catch
|
||||||
|
' 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.
|
||||||
|
Log($"RDCConnector.Initialize para ${DB}: ERROR CRÍTICO al inicializar/forzar conexión: ${LastException.Message}"$)
|
||||||
|
End Try
|
||||||
|
|
||||||
|
' Configuración de depuración de queries. Se activa automáticamente si el proyecto se ejecuta en modo DEBUG.
|
||||||
|
#If DEBUG
|
||||||
|
' DebugQueries = True
|
||||||
|
#Else
|
||||||
|
DebugQueries = False
|
||||||
|
#End If
|
||||||
|
|
||||||
' Dim jo2 As JavaObject = pool
|
' Se obtiene el puerto del servidor HTTP desde la configuración de esta base de datos.
|
||||||
' Log(jo2.GetField("END_TO_END_CLIENTID_INDEX"))
|
' Nota: En el diseño actual, el puerto principal lo define DB1 (config.properties).
|
||||||
|
|
||||||
' jo.RunMethod("setPreferredTestQuery", Array("BEGIN DBMS_SESSION.SET_IDENTIFIER('whatever'); END;"))
|
|
||||||
' jo.RunMethod("setPreferredTestQuery", Array("alter session set current_schema=MYSCHEMA"))
|
|
||||||
' jo2.RunMethod("setClientIdentifier",Array( "MAX")) ' Tiempo máximo de inactividad antes de cerrar una conexión
|
|
||||||
#if DEBUG
|
|
||||||
DebugQueries = True
|
|
||||||
#else
|
|
||||||
DebugQueries = False
|
|
||||||
#end if
|
|
||||||
serverPort = config.Get("ServerPort")
|
serverPort = config.Get("ServerPort")
|
||||||
|
|
||||||
|
' Asegura que el identificador DB no sea una cadena vacía para la carga de comandos.
|
||||||
If DB = "" Then DB = "DB1"
|
If DB = "" Then DB = "DB1"
|
||||||
|
|
||||||
|
' Carga los comandos SQL predefinidos de esta base de datos en el mapa global 'commandsMap'.
|
||||||
LoadSQLCommands(config, DB)
|
LoadSQLCommands(config, DB)
|
||||||
End Sub
|
End Sub
|
||||||
|
|
||||||
|
' Carga el mapa de configuración (JdbcUrl, User, Password, etc.) desde el archivo .properties correspondiente.
|
||||||
|
' DB: El identificador de la base de datos (ej. "DB1", "DB2").
|
||||||
|
' Retorna un Mapa con la configuración leída.
|
||||||
Private Sub LoadConfigMap(DB As String) As Map
|
Private Sub LoadConfigMap(DB As String) As Map
|
||||||
Private DBX As String = ""
|
Private DBX As String = ""
|
||||||
If DB <> "" Then DBX = "." & DB
|
If DB <> "" Then DBX = "." & DB ' Construye el sufijo del nombre de archivo (ej. ".DB2").
|
||||||
Log("===========================================")
|
Log($"Leemos el config${DBX}.properties"$) ' Mantenemos este log para confirmación de carga.
|
||||||
Log($"Leemos el config${DBX}.properties"$)
|
|
||||||
Return File.ReadMap("./", "config" & DBX & ".properties")
|
Return File.ReadMap("./", "config" & DBX & ".properties")
|
||||||
End Sub
|
End Sub
|
||||||
|
|
||||||
|
' Obtiene la sentencia SQL completa para un comando dado desde el mapa de comandos cargado.
|
||||||
|
' DB: El identificador de la base de datos.
|
||||||
|
' Key: El nombre del comando SQL (ej. "select_user").
|
||||||
|
' Retorna la sentencia SQL como String.
|
||||||
Public Sub GetCommand(DB As String, Key As String) As String
|
Public Sub GetCommand(DB As String, Key As String) As String
|
||||||
Log("==== GetCommand ====")
|
commands = Main.commandsMap.get(DB).As(Map) ' Obtiene los comandos de la DB específica del mapa global.
|
||||||
' Log("|" & DB & "|" & Key & "|")
|
|
||||||
commands = Main.commandsMap.get(DB).As(Map)
|
|
||||||
If commands.ContainsKey("sql." & Key) = False Then
|
If commands.ContainsKey("sql." & Key) = False Then
|
||||||
Log("*** Command not found: " & Key)
|
Log("*** Command not found: " & Key) ' Este log es importante mantenerlo si un comando no se encuentra.
|
||||||
End If
|
End If
|
||||||
' Log(commands.ContainsKey("sql." & Key))
|
Return commands.Get("sql." & Key) ' Retorna la sentencia SQL.
|
||||||
Log("========= Traemos """ & Key & """ ==========")
|
|
||||||
Log(">>>>>> " & commands.Get("sql." & Key) & " <<<<<<")
|
|
||||||
Return commands.Get("sql." & Key)
|
|
||||||
End Sub
|
End Sub
|
||||||
|
|
||||||
|
' Obtiene una conexión SQL funcional del pool de conexiones para la base de datos especificada.
|
||||||
|
' DB: El identificador de la base de datos.
|
||||||
|
' Retorna un objeto SQL (la conexión a la base de datos).
|
||||||
|
'Public Sub GetConnection(DB As String) As SQL
|
||||||
|
' If DB.EqualsIgnoreCase("DB1") Then DB = ""
|
||||||
|
' ' En modo de depuración, recarga los comandos SQL en cada petición.
|
||||||
|
' ' Esto permite modificar queries en config.properties sin reiniciar el servidor.
|
||||||
|
' If DebugQueries Then LoadSQLCommands(LoadConfigMap(DB), DB)
|
||||||
|
' Return pool.GetConnection ' Retorna una conexión del pool.
|
||||||
|
'End Sub
|
||||||
|
|
||||||
Public Sub GetConnection(DB As String) As SQL
|
Public Sub GetConnection(DB As String) As SQL
|
||||||
Log("==== GetConnection ==== ")
|
If DB.EqualsIgnoreCase("DB1") Then DB = ""
|
||||||
If DB.EqualsIgnoreCase("DB1") Then DB = "" 'Esto para el config.properties or default
|
|
||||||
If DebugQueries Then LoadSQLCommands(LoadConfigMap(DB), DB)
|
' If DebugQueries Then LoadSQLCommands(LoadConfigMap(DB), DB) ' Esta línea es condicional a DebugQueries
|
||||||
Return pool.GetConnection
|
|
||||||
|
' <<<< ¡ESTOS SON LOS LOGS QUE NECESITAMOS VER! >>>>
|
||||||
|
' Log($"[DEBUG - ${DB}] RDCConnector.GetConnection: Solicitando conexión del pool..."$)
|
||||||
|
Dim conn As SQL = pool.GetConnection
|
||||||
|
' Log($"[DEBUG - ${DB}] RDCConnector.GetConnection: Conexión obtenida. IsInitialized: ${conn.IsInitialized}"$)
|
||||||
|
|
||||||
|
If pool.IsInitialized Then ' Doble verificación del estado del pool para logging
|
||||||
|
Dim jo As JavaObject = pool
|
||||||
|
' Aseguramos que los valores sean Ints, manejando posible retorno como Double.
|
||||||
|
Dim busyCount As Int = jo.RunMethod("getNumBusyConnectionsAllUsers", Null).As(Object).As(Int)
|
||||||
|
Dim totalCount As Int = jo.RunMethod("getNumConnectionsAllUsers", Null).As(Object).As(Int)
|
||||||
|
' Log($"[DEBUG - ${DB}] RDCConnector.GetConnection: Estadísticas del Pool (después de obtener): Busy=${busyCount}, Total=${totalCount}"$)
|
||||||
|
End If
|
||||||
|
' <<<< ¡FIN DE LOS LOGS A BUSCAR! >>>>
|
||||||
|
|
||||||
|
Return conn
|
||||||
End Sub
|
End Sub
|
||||||
|
|
||||||
|
' Carga todos los comandos SQL del mapa de configuración en el mapa global 'commandsMap'.
|
||||||
|
' config2: El mapa de configuración de la DB actual.
|
||||||
|
' DB: El identificador de la base de datos.
|
||||||
Private Sub LoadSQLCommands(config2 As Map, DB As String)
|
Private Sub LoadSQLCommands(config2 As Map, DB As String)
|
||||||
Log("==== LoadSQLCommands ==== ")
|
|
||||||
Log($"Cargamos los comandos desde el config.${DB}.properties"$)
|
|
||||||
Dim newCommands As Map
|
Dim newCommands As Map
|
||||||
newCommands.Initialize
|
newCommands.Initialize
|
||||||
For Each k As String In config2.Keys
|
For Each k As String In config2.Keys
|
||||||
If k.StartsWith("sql.") Then
|
If k.StartsWith("sql.") Then ' Busca claves que comiencen con "sql." (ej. "sql.select_user").
|
||||||
newCommands.Put(k, config2.Get(k))
|
newCommands.Put(k, config2.Get(k)) ' Añade el comando al mapa.
|
||||||
End If
|
End If
|
||||||
Next
|
Next
|
||||||
commands = newCommands
|
commands = newCommands ' Actualiza el mapa de comandos de esta instancia de RDCConnector.
|
||||||
' Log($"Inicializado: ${DB} "$ & Main.commandsMap.IsInitialized)
|
Main.commandsMap.Put(DB, commands) ' Almacena el mapa de comandos en el mapa global 'commandsMap' de Main.
|
||||||
Main.commandsMap.Put(DB, commands)
|
|
||||||
End Sub
|
End Sub
|
||||||
|
|
||||||
|
' Nuevo: Obtiene estadísticas detalladas del pool de conexiones.
|
||||||
|
Public Sub GetPoolStats As Map
|
||||||
|
Dim stats As Map
|
||||||
|
stats.Initialize
|
||||||
|
' Log("--- RDCConnector.GetPoolStats llamado ---") ' Log de inicio
|
||||||
|
|
||||||
|
If pool.IsInitialized Then
|
||||||
|
' Log("RDCConnector.GetPoolStats: Pool está inicializado. Intentando obtener métricas.")
|
||||||
|
Dim jo As JavaObject = pool ' Convertimos el objeto pool a JavaObject para acceder a sus métodos.
|
||||||
|
Try
|
||||||
|
' --- Métricas en tiempo real del pool ---
|
||||||
|
Dim totalConn As Object = jo.RunMethod("getNumConnectionsAllUsers", Null)
|
||||||
|
stats.Put("TotalConnections", totalConn)
|
||||||
|
' Log($"RDCConnector.GetPoolStats: TotalConnections = ${totalConn}"$)
|
||||||
|
|
||||||
|
Dim busyConn As Object = jo.RunMethod("getNumBusyConnectionsAllUsers", Null)
|
||||||
|
stats.Put("BusyConnections", busyConn)
|
||||||
|
' Log($"RDCConnector.GetPoolStats: BusyConnections = ${busyConn}"$)
|
||||||
|
|
||||||
|
Dim idleConn As Object = jo.RunMethod("getNumIdleConnectionsAllUsers", Null)
|
||||||
|
stats.Put("IdleConnections", idleConn)
|
||||||
|
' Log($"RDCConnector.GetPoolStats: IdleConnections = ${idleConn}"$)
|
||||||
|
|
||||||
|
' --- Valores de configuración del pool (para referencia) ---
|
||||||
|
Dim initialSize As Object = jo.RunMethod("getInitialPoolSize", Null)
|
||||||
|
stats.Put("InitialPoolSize", initialSize)
|
||||||
|
' Log($"RDCConnector.GetPoolStats: InitialPoolSize = ${initialSize}"$)
|
||||||
|
|
||||||
|
Dim minSize As Object = jo.RunMethod("getMinPoolSize", Null)
|
||||||
|
stats.Put("MinPoolSize", minSize)
|
||||||
|
' Log($"RDCConnector.GetPoolStats: MinPoolSize = ${minSize}"$)
|
||||||
|
|
||||||
|
Dim maxSize As Object = jo.RunMethod("getMaxPoolSize", Null)
|
||||||
|
stats.Put("MaxPoolSize", maxSize)
|
||||||
|
' Log($"RDCConnector.GetPoolStats: MaxPoolSize = ${maxSize}"$)
|
||||||
|
|
||||||
|
Dim acquireInc As Object = jo.RunMethod("getAcquireIncrement", Null)
|
||||||
|
stats.Put("AcquireIncrement", acquireInc)
|
||||||
|
' Log($"RDCConnector.GetPoolStats: AcquireIncrement = ${acquireInc}"$)
|
||||||
|
|
||||||
|
Dim maxIdle As Object = jo.RunMethod("getMaxIdleTime", Null)
|
||||||
|
stats.Put("MaxIdleTime", maxIdle)
|
||||||
|
' Log($"RDCConnector.GetPoolStats: MaxIdleTime = ${maxIdle}"$)
|
||||||
|
|
||||||
|
Dim maxAge As Object = jo.RunMethod("getMaxConnectionAge", Null)
|
||||||
|
stats.Put("MaxConnectionAge", maxAge)
|
||||||
|
' Log($"RDCConnector.GetPoolStats: MaxConnectionAge = ${maxAge}"$)
|
||||||
|
|
||||||
|
Dim checkoutTime As Object = jo.RunMethod("getCheckoutTimeout", Null)
|
||||||
|
stats.Put("CheckoutTimeout", checkoutTime)
|
||||||
|
' Log($"RDCConnector.GetPoolStats: CheckoutTimeout = ${checkoutTime}"$)
|
||||||
|
|
||||||
|
Catch
|
||||||
|
' Log("RDCConnector.GetPoolStats: ERROR CRÍTICO al obtener estadísticas del pool: " & LastException.Message)
|
||||||
|
stats.Put("Error", LastException.Message)
|
||||||
|
End Try
|
||||||
|
Else
|
||||||
|
' Log("RDCConnector.GetPoolStats: ADVERTENCIA: Pool NO está inicializado. Retornando mapa con error.")
|
||||||
|
stats.Put("Error", "Pool de conexiones no inicializado para esta DB.")
|
||||||
|
End If
|
||||||
|
|
||||||
|
' *** CORRECCIÓN: Usamos JSONGenerator para serializar el mapa a String para el Log ***
|
||||||
|
Dim tempJsonGen As JSONGenerator ' Declaramos un JSONGenerator temporal
|
||||||
|
tempJsonGen.Initialize(stats) ' Lo inicializamos con el mapa 'stats'
|
||||||
|
' Log("--- RDCConnector.GetPoolStats finalizado. Retornando stats: " & tempJsonGen.ToString & " ---") ' Log de fin con JSON
|
||||||
|
|
||||||
|
Return stats
|
||||||
|
End Sub
|
||||||
|
|
||||||
|
' *** NUEVA SUBRUTINA: Cierra el pool de conexiones de forma ordenada usando JavaObject ***
|
||||||
|
' Este método es crucial para liberar los recursos de la base de datos
|
||||||
|
' cuando un conector RDC ya no es necesario o va a ser reemplazado.
|
||||||
|
Public Sub Close
|
||||||
|
If pool <> Null And pool.IsInitialized Then
|
||||||
|
' Log($"RDCConnector: Cerrando pool de conexiones."$)
|
||||||
|
' Convertimos el objeto pool de B4X a un JavaObject para poder llamar a su método 'close()'
|
||||||
|
' que no está expuesto directamente en la envoltura de B4X.
|
||||||
|
Dim joPool As JavaObject = pool
|
||||||
|
joPool.RunMethod("close", Null) ' Llamamos al método 'close()' del objeto Java subyacente de C3P0.
|
||||||
|
End If
|
||||||
|
End Sub
|
||||||
320
RDCHandler.bas
320
RDCHandler.bas
@@ -1,320 +0,0 @@
|
|||||||
B4J=true
|
|
||||||
Group=Default Group
|
|
||||||
ModulesStructureVersion=1
|
|
||||||
Type=Class
|
|
||||||
Version=4.19
|
|
||||||
@EndOfDesignText@
|
|
||||||
'Handler class
|
|
||||||
Sub Class_Globals
|
|
||||||
' #if VERSION1
|
|
||||||
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
|
|
||||||
Private bc As ByteConverter
|
|
||||||
Private cs As CompressedStreams
|
|
||||||
' #end if
|
|
||||||
Private DateTimeMethods As Map
|
|
||||||
End Sub
|
|
||||||
|
|
||||||
Public Sub Initialize
|
|
||||||
DateTimeMethods = CreateMap(91: "getDate", 92: "getTime", 93: "getTimestamp")
|
|
||||||
End Sub
|
|
||||||
|
|
||||||
Sub Handle(req As ServletRequest, resp As ServletResponse)
|
|
||||||
Log("***********************************************")
|
|
||||||
Log(">>>> RDC")
|
|
||||||
Dim start As Long = DateTime.Now
|
|
||||||
Dim q As String
|
|
||||||
Dim in As InputStream = req.InputStream
|
|
||||||
Dim method As String = req.GetParameter("method")
|
|
||||||
Dim con As SQL
|
|
||||||
Try
|
|
||||||
con = Main.rdcConnector0.GetConnection("")
|
|
||||||
If method = "query2" Then
|
|
||||||
q = ExecuteQuery2(con, in, resp)
|
|
||||||
'#if VERSION1
|
|
||||||
Else if method = "query" Then
|
|
||||||
in = cs.WrapInputStream(in, "gzip")
|
|
||||||
q = ExecuteQuery(con, in, resp)
|
|
||||||
Else if method = "batch" Then
|
|
||||||
in = cs.WrapInputStream(in, "gzip")
|
|
||||||
q = ExecuteBatch(con, in, resp)
|
|
||||||
'#end if
|
|
||||||
Else if method = "batch2" Then
|
|
||||||
q = ExecuteBatch2(con, in, resp)
|
|
||||||
Else
|
|
||||||
Log("Unknown method: " & method)
|
|
||||||
resp.SendError(500, "unknown method")
|
|
||||||
End If
|
|
||||||
Catch
|
|
||||||
Log(LastException)
|
|
||||||
resp.SendError(500, LastException.Message)
|
|
||||||
End Try
|
|
||||||
If con <> Null And con.IsInitialized Then con.Close
|
|
||||||
Log($"Command: ${q}, took: ${DateTime.Now - start}ms, client=${req.RemoteAddress}"$)
|
|
||||||
End Sub
|
|
||||||
|
|
||||||
Private Sub ExecuteQuery2 (con As SQL, in As InputStream, resp As ServletResponse) As String
|
|
||||||
Dim ser As B4XSerializator
|
|
||||||
Dim m As Map = ser.ConvertBytesToObject(Bit.InputStreamToBytes(in))
|
|
||||||
Dim cmd As DBCommand = m.Get("command")
|
|
||||||
Dim limit As Int = m.Get("limit")
|
|
||||||
Dim rs As ResultSet = con.ExecQuery2(Main.rdcConnector0.GetCommand(cmd.Name), cmd.Parameters)
|
|
||||||
If limit <= 0 Then limit = 0x7fffffff 'max int
|
|
||||||
Dim jrs As JavaObject = rs
|
|
||||||
Dim rsmd As JavaObject = jrs.RunMethod("getMetaData", Null)
|
|
||||||
Dim cols As Int = rs.ColumnCount
|
|
||||||
Dim res As DBResult
|
|
||||||
res.Initialize
|
|
||||||
res.columns.Initialize
|
|
||||||
res.Tag = Null 'without this the Tag properly will not be serializable.
|
|
||||||
For i = 0 To cols - 1
|
|
||||||
res.columns.Put(rs.GetColumnName(i), i)
|
|
||||||
Next
|
|
||||||
res.Rows.Initialize
|
|
||||||
Do While rs.NextRow And limit > 0
|
|
||||||
Dim row(cols) As Object
|
|
||||||
For i = 0 To cols - 1
|
|
||||||
Dim ct As Int = rsmd.RunMethod("getColumnType", Array(i + 1))
|
|
||||||
'check whether it is a blob field
|
|
||||||
If ct = -2 Or ct = 2004 Or ct = -3 Or ct = -4 Then
|
|
||||||
row(i) = rs.GetBlob2(i)
|
|
||||||
Else If ct = 2005 Then
|
|
||||||
row(i) = rs.GetString2(i)
|
|
||||||
Else if ct = 2 Or ct = 3 Then
|
|
||||||
row(i) = rs.GetDouble2(i)
|
|
||||||
Else If DateTimeMethods.ContainsKey(ct) Then
|
|
||||||
Dim SQLTime As JavaObject = jrs.RunMethodJO(DateTimeMethods.Get(ct), Array(i + 1))
|
|
||||||
If SQLTime.IsInitialized Then
|
|
||||||
row(i) = SQLTime.RunMethod("getTime", Null)
|
|
||||||
Else
|
|
||||||
row(i) = Null
|
|
||||||
End If
|
|
||||||
Else
|
|
||||||
row(i) = jrs.RunMethod("getObject", Array(i + 1))
|
|
||||||
End If
|
|
||||||
Next
|
|
||||||
res.Rows.Add(row)
|
|
||||||
Loop
|
|
||||||
rs.Close
|
|
||||||
Dim data() As Byte = ser.ConvertObjectToBytes(res)
|
|
||||||
resp.OutputStream.WriteBytes(data, 0, data.Length)
|
|
||||||
Return "query: " & cmd.Name
|
|
||||||
End Sub
|
|
||||||
|
|
||||||
Private Sub ExecuteBatch2(con As SQL, in As InputStream, resp As ServletResponse) As String
|
|
||||||
Dim ser As B4XSerializator
|
|
||||||
Dim m As Map = ser.ConvertBytesToObject(Bit.InputStreamToBytes(in))
|
|
||||||
Dim commands As List = m.Get("commands")
|
|
||||||
Dim res As DBResult
|
|
||||||
res.Initialize
|
|
||||||
res.columns = CreateMap("AffectedRows (N/A)": 0)
|
|
||||||
res.Rows.Initialize
|
|
||||||
res.Tag = Null
|
|
||||||
Try
|
|
||||||
con.BeginTransaction
|
|
||||||
For Each cmd As DBCommand In commands
|
|
||||||
con.ExecNonQuery2(Main.rdcConnector0.GetCommand(cmd.Name), _
|
|
||||||
cmd.Parameters)
|
|
||||||
Next
|
|
||||||
res.Rows.Add(Array As Object(0))
|
|
||||||
con.TransactionSuccessful
|
|
||||||
Catch
|
|
||||||
con.Rollback
|
|
||||||
Log(LastException)
|
|
||||||
resp.SendError(500, LastException.Message)
|
|
||||||
End Try
|
|
||||||
Dim data() As Byte = ser.ConvertObjectToBytes(res)
|
|
||||||
resp.OutputStream.WriteBytes(data, 0, data.Length)
|
|
||||||
Return $"batch (size=${commands.Size})"$
|
|
||||||
End Sub
|
|
||||||
|
|
||||||
'#if VERSION1
|
|
||||||
|
|
||||||
Private Sub ExecuteBatch(con As SQL, in As InputStream, resp As ServletResponse) As String
|
|
||||||
Dim clientVersion As Float = ReadObject(in) 'ignore
|
|
||||||
Dim numberOfStatements As Int = ReadInt(in)
|
|
||||||
Dim res(numberOfStatements) As Int
|
|
||||||
Try
|
|
||||||
con.BeginTransaction
|
|
||||||
For i = 0 To numberOfStatements - 1
|
|
||||||
Dim queryName As String = ReadObject(in)
|
|
||||||
Dim params As List = ReadList(in)
|
|
||||||
con.ExecNonQuery2(Main.rdcConnector0.GetCommand(queryName), _
|
|
||||||
params)
|
|
||||||
Next
|
|
||||||
con.TransactionSuccessful
|
|
||||||
|
|
||||||
Dim out As OutputStream = cs.WrapOutputStream(resp.OutputStream, "gzip")
|
|
||||||
WriteObject(Main.VERSION, out)
|
|
||||||
WriteObject("batch", out)
|
|
||||||
WriteInt(res.Length, out)
|
|
||||||
For Each r As Int In res
|
|
||||||
WriteInt(r, out)
|
|
||||||
Next
|
|
||||||
out.Close
|
|
||||||
Catch
|
|
||||||
con.Rollback
|
|
||||||
Log(LastException)
|
|
||||||
resp.SendError(500, LastException.Message)
|
|
||||||
End Try
|
|
||||||
Return $"batch (size=${numberOfStatements})"$
|
|
||||||
End Sub
|
|
||||||
|
|
||||||
Private Sub ExecuteQuery (con As SQL, in As InputStream, resp As ServletResponse) As String
|
|
||||||
' Log("==== ExecuteQuery ==== ")
|
|
||||||
Dim clientVersion As Float = ReadObject(in) 'ignore
|
|
||||||
Dim queryName As String = ReadObject(in)
|
|
||||||
Dim limit As Int = ReadInt(in)
|
|
||||||
Dim params As List = ReadList(in)
|
|
||||||
' Log("EL QUERY: |" & queryName & "|")
|
|
||||||
Private theSql As String = Main.rdcConnector0.GetCommand(queryName)
|
|
||||||
' Log(theSql)
|
|
||||||
' Log(params)
|
|
||||||
' Log(params.Size)
|
|
||||||
Dim rs As ResultSet = con.ExecQuery2(theSql, params)
|
|
||||||
If limit <= 0 Then limit = 0x7fffffff 'max int
|
|
||||||
Dim jrs As JavaObject = rs
|
|
||||||
Dim rsmd As JavaObject = jrs.RunMethod("getMetaData", Null)
|
|
||||||
Dim cols As Int = rs.ColumnCount
|
|
||||||
Dim out As OutputStream = cs.WrapOutputStream(resp.OutputStream, "gzip")
|
|
||||||
WriteObject(Main.VERSION, out)
|
|
||||||
WriteObject("query", out)
|
|
||||||
WriteInt(rs.ColumnCount, out)
|
|
||||||
' Log($"cols: ${cols}"$)
|
|
||||||
For i = 0 To cols - 1
|
|
||||||
WriteObject(rs.GetColumnName(i), out)
|
|
||||||
Next
|
|
||||||
|
|
||||||
Do While rs.NextRow And limit > 0
|
|
||||||
WriteByte(1, out)
|
|
||||||
For i = 0 To cols - 1
|
|
||||||
Dim ct As Int = rsmd.RunMethod("getColumnType", Array(i + 1))
|
|
||||||
'check whether it is a blob field
|
|
||||||
If ct = -2 Or ct = 2004 Or ct = -3 Or ct = -4 Then
|
|
||||||
WriteObject(rs.GetBlob2(i), out)
|
|
||||||
Else
|
|
||||||
WriteObject(jrs.RunMethod("getObject", Array(i + 1)), out)
|
|
||||||
End If
|
|
||||||
Next
|
|
||||||
Loop
|
|
||||||
WriteByte(0, out)
|
|
||||||
out.Close
|
|
||||||
rs.Close
|
|
||||||
|
|
||||||
Return "query: " & queryName
|
|
||||||
End Sub
|
|
||||||
|
|
||||||
Private Sub WriteByte(value As Byte, out As OutputStream)
|
|
||||||
out.WriteBytes(Array As Byte(value), 0, 1)
|
|
||||||
End Sub
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Private Sub WriteObject(o As Object, out As OutputStream)
|
|
||||||
Dim data() As Byte
|
|
||||||
If o = Null Then
|
|
||||||
out.WriteBytes(Array As Byte(T_NULL), 0, 1)
|
|
||||||
Else If o Is Short Then
|
|
||||||
out.WriteBytes(Array As Byte(T_SHORT), 0, 1)
|
|
||||||
data = bc.ShortsToBytes(Array As Short(o))
|
|
||||||
Else If o Is Int Then
|
|
||||||
out.WriteBytes(Array As Byte(T_INT), 0, 1)
|
|
||||||
data = bc.IntsToBytes(Array As Int(o))
|
|
||||||
Else If o Is Float Then
|
|
||||||
out.WriteBytes(Array As Byte(T_FLOAT), 0, 1)
|
|
||||||
data = bc.FloatsToBytes(Array As Float(o))
|
|
||||||
Else If o Is Double Then
|
|
||||||
out.WriteBytes(Array As Byte(T_DOUBLE), 0, 1)
|
|
||||||
data = bc.DoublesToBytes(Array As Double(o))
|
|
||||||
Else If o Is Long Then
|
|
||||||
out.WriteBytes(Array As Byte(T_LONG), 0, 1)
|
|
||||||
data = bc.LongsToBytes(Array As Long(o))
|
|
||||||
Else If o Is Boolean Then
|
|
||||||
out.WriteBytes(Array As Byte(T_BOOLEAN), 0, 1)
|
|
||||||
Dim b As Boolean = o
|
|
||||||
Dim data(1) As Byte
|
|
||||||
If b Then data(0) = 1 Else data(0) = 0
|
|
||||||
Else If GetType(o) = "[B" Then
|
|
||||||
data = o
|
|
||||||
out.WriteBytes(Array As Byte(T_BLOB), 0, 1)
|
|
||||||
WriteInt(data.Length, out)
|
|
||||||
Else 'If o Is String Then (treat all other values as string)
|
|
||||||
out.WriteBytes(Array As Byte(T_STRING), 0, 1)
|
|
||||||
data = bc.StringToBytes(o, "UTF8")
|
|
||||||
WriteInt(data.Length, out)
|
|
||||||
End If
|
|
||||||
If data.Length > 0 Then out.WriteBytes(data, 0, data.Length)
|
|
||||||
End Sub
|
|
||||||
|
|
||||||
Private Sub ReadObject(In As InputStream) As Object
|
|
||||||
Dim data(1) As Byte
|
|
||||||
In.ReadBytes(data, 0, 1)
|
|
||||||
Select data(0)
|
|
||||||
Case T_NULL
|
|
||||||
Return Null
|
|
||||||
Case T_SHORT
|
|
||||||
Dim data(2) As Byte
|
|
||||||
Return bc.ShortsFromBytes(ReadBytesFully(In, data, data.Length))(0)
|
|
||||||
Case T_INT
|
|
||||||
Dim data(4) As Byte
|
|
||||||
Return bc.IntsFromBytes(ReadBytesFully(In, data, data.Length))(0)
|
|
||||||
Case T_LONG
|
|
||||||
Dim data(8) As Byte
|
|
||||||
Return bc.LongsFromBytes(ReadBytesFully(In, data, data.Length))(0)
|
|
||||||
Case T_FLOAT
|
|
||||||
Dim data(4) As Byte
|
|
||||||
Return bc.FloatsFromBytes(ReadBytesFully(In, data, data.Length))(0)
|
|
||||||
Case T_DOUBLE
|
|
||||||
Dim data(8) As Byte
|
|
||||||
Return bc.DoublesFromBytes(ReadBytesFully(In, data, data.Length))(0)
|
|
||||||
Case T_BOOLEAN
|
|
||||||
Dim b As Byte = ReadByte(In)
|
|
||||||
Return b = 1
|
|
||||||
Case T_BLOB
|
|
||||||
Dim len As Int = ReadInt(In)
|
|
||||||
Dim data(len) As Byte
|
|
||||||
Return ReadBytesFully(In, data, data.Length)
|
|
||||||
Case Else
|
|
||||||
Dim len As Int = ReadInt(In)
|
|
||||||
Dim data(len) As Byte
|
|
||||||
ReadBytesFully(In, data, data.Length)
|
|
||||||
Return BytesToString(data, 0, data.Length, "UTF8")
|
|
||||||
End Select
|
|
||||||
End Sub
|
|
||||||
|
|
||||||
Private Sub ReadBytesFully(In As InputStream, Data() As Byte, Len As Int) As Byte()
|
|
||||||
Dim count = 0, Read As Int
|
|
||||||
Do While count < Len And Read > -1
|
|
||||||
Read = In.ReadBytes(Data, count, Len - count)
|
|
||||||
count = count + Read
|
|
||||||
Loop
|
|
||||||
Return Data
|
|
||||||
End Sub
|
|
||||||
|
|
||||||
Private Sub WriteInt(i As Int, out As OutputStream)
|
|
||||||
Dim data() As Byte
|
|
||||||
data = bc.IntsToBytes(Array As Int(i))
|
|
||||||
out.WriteBytes(data, 0, data.Length)
|
|
||||||
End Sub
|
|
||||||
|
|
||||||
Private Sub ReadInt(In As InputStream) As Int
|
|
||||||
Dim data(4) As Byte
|
|
||||||
Return bc.IntsFromBytes(ReadBytesFully(In, data, data.Length))(0)
|
|
||||||
End Sub
|
|
||||||
|
|
||||||
Private Sub ReadByte(In As InputStream) As Byte
|
|
||||||
Dim data(1) As Byte
|
|
||||||
In.ReadBytes(data, 0, 1)
|
|
||||||
Return data(0)
|
|
||||||
End Sub
|
|
||||||
|
|
||||||
Private Sub ReadList(in As InputStream) As List
|
|
||||||
Dim len As Int = ReadInt(in)
|
|
||||||
Dim l1 As List
|
|
||||||
l1.Initialize
|
|
||||||
For i = 0 To len - 1
|
|
||||||
l1.Add(ReadObject(in))
|
|
||||||
Next
|
|
||||||
Return l1
|
|
||||||
End Sub
|
|
||||||
'#end If
|
|
||||||
153
README.md
153
README.md
@@ -1,20 +1,24 @@
|
|||||||
# **Servidor jRDC2-Multi Mod (B4J)**
|
# **Servidor jRDC2-Multi Mod (B4J)**
|
||||||
|
|
||||||
## **1\. Introducción**
|
## **1. Introducción**
|
||||||
|
|
||||||
Este proyecto es una versión modificada del servidor [jRDC2 original](https://www.b4x.com/android/forum/threads/b4x-jrdc2-b4j-implementation-of-rdc-remote-database-connector.61801/#content), diseñada para actuar como un backend robusto y flexible. Su función principal es recibir peticiones HTTP, ejecutar comandos SQL predefinidos contra una base de datos y devolver los resultados en un formato estructurado.
|
Este proyecto es una versión modificada del servidor [jRDC2 original](https://www.b4x.com/android/forum/threads/b4x-jrdc2-b4j-implementation-of-rdc-remote-database-connector.61801/#content), diseñada para actuar como un backend robusto y flexible. Su función principal es recibir peticiones HTTP, ejecutar comandos SQL predefinidos contra una base de datos y devolver los resultados en un formato estructurado.
|
||||||
|
|
||||||
Ha sido adaptado para servir tanto a clientes nativos (`B4A/B4i`) como a clientes web modernos (`JavaScript`, a través de frameworks como `NodeJS, React, Vue, Angular, etc`.).
|
Ha sido adaptado para servir tanto a clientes nativos (`B4A/B4i`) como a clientes web modernos (`JavaScript`, a través de frameworks como `NodeJS, React, Vue, Angular, etc`.).
|
||||||
|
|
||||||
## **2\. Características Principales**
|
-----
|
||||||
|
|
||||||
- **Soporte para Múltiples Bases de Datos**: Puede cargar y gestionar hasta 4 archivos de configuración (`config.properties`) simultáneamente.
|
## **2. Características Principales**
|
||||||
- **Comandos SQL Externalizados**: Las sentencias SQL se definen en los archivos de configuración, permitiendo modificarlas sin recompilar el servidor.
|
|
||||||
- **Doble Handler de Peticiones**: Incluye un handler clásico para clientes B4X y un handler JSON para clientes web.
|
|
||||||
- **Validaciones de Seguridad**: Verifica la existencia de comandos y la correspondencia en el número de parámetros.
|
|
||||||
- **Administración Remota**: Permite verificar el estado, recargar la configuración y reiniciar el servidor a través de URLs específicas.
|
|
||||||
|
|
||||||
## **3\. Configuración**
|
* **Soporte para Múltiples Bases de Datos**: Puede cargar y gestionar hasta 4 archivos de configuración (`config.properties`) simultáneamente.
|
||||||
|
* **Comandos SQL Externalizados**: Las sentencias SQL se definen en los archivos de configuración, permitiendo modificarlas sin recompilar el servidor.
|
||||||
|
* **Doble Handler de Peticiones**: Incluye un handler clásico para clientes B4X y un handler JSON para clientes web.
|
||||||
|
* **Validaciones de Seguridad**: Verifica la existencia de comandos y la correspondencia en el número de parámetros antes de la ejecución.
|
||||||
|
* **Administración Remota**: Permite verificar el estado, recargar la configuración y reiniciar el servidor a través de URLs específicas, con un sistema de autenticación.
|
||||||
|
|
||||||
|
-----
|
||||||
|
|
||||||
|
## **3. Configuración**
|
||||||
|
|
||||||
### **3.1. Archivos de Configuración**
|
### **3.1. Archivos de Configuración**
|
||||||
|
|
||||||
@@ -22,15 +26,15 @@ El sistema está preparado para manejar hasta **cuatro configuraciones de bases
|
|||||||
|
|
||||||
La nomenclatura de los archivos es fundamental:
|
La nomenclatura de los archivos es fundamental:
|
||||||
|
|
||||||
- `config.properties` (para `DB1`)
|
* `config.properties` (para `DB1`)
|
||||||
- `config.DB2.properties`
|
* `config.DB2.properties`
|
||||||
- `config.DB3.properties`
|
* `config.DB3.properties`
|
||||||
- `config.DB4.properties`
|
* `config.DB4.properties`
|
||||||
|
|
||||||
**Notas importantes:**
|
**Notas importantes:**
|
||||||
|
|
||||||
- El **puerto** del servidor se toma **únicamente** del archivo principal `config.properties`, sin importar lo que digan los demás.
|
* El **puerto** del servidor se toma **únicamente** del archivo principal `config.properties`, sin importar lo que digan los demás.
|
||||||
- Los datos de conexión (`JdbcUrl`, `usuario`, `contraseña`) sí se toman del archivo correspondiente a cada base de datos.
|
* Los datos de conexión (`JdbcUrl`, `usuario`, `contraseña`) sí se toman del archivo correspondiente a cada base de datos.
|
||||||
|
|
||||||
### **3.2. Añadir Drivers de Bases de Datos Adicionales**
|
### **3.2. Añadir Drivers de Bases de Datos Adicionales**
|
||||||
|
|
||||||
@@ -43,94 +47,115 @@ Si necesitas conectarte a otros tipos de bases de datos (ej. Oracle), debes agre
|
|||||||
|
|
||||||
Al compilar, el driver se incluirá en el `.jar` final del servidor, por lo que no será necesario copiarlo por separado al directorio de producción.
|
Al compilar, el driver se incluirá en el `.jar` final del servidor, por lo que no será necesario copiarlo por separado al directorio de producción.
|
||||||
|
|
||||||
## **4\. Uso del Handler Clásico (Para Clientes B4X)**
|
-----
|
||||||
|
|
||||||
|
## **4. Validaciones de Seguridad**
|
||||||
|
|
||||||
|
El servidor realiza dos comprobaciones automáticas **en ambos handlers (B4X y JSON)** antes de ejecutar cualquier consulta:
|
||||||
|
|
||||||
|
1. **Verificación de Existencia del Comando**: El servidor comprueba que el nombre del comando SQL solicitado (ej. `"get_user"`) exista como una clave válida en el archivo `.properties` correspondiente. Si no lo encuentra, devolverá un error y no intentará ejecutar nada.
|
||||||
|
|
||||||
|
2. **Conteo de Parámetros**: Si el comando SQL en el archivo de configuración espera parámetros (contiene `?`), el servidor cuenta cuántos son y lo compara con el número de parámetros recibidos en la petición. Si las cantidades no coinciden, devolverá un error específico, evitando una ejecución fallida en la base de datos.
|
||||||
|
|
||||||
|
Estas validaciones aseguran que el desarrollador reciba feedback inmediato y claro si una petición está mal formada.
|
||||||
|
|
||||||
|
-----
|
||||||
|
|
||||||
|
## **5. Uso del Handler Clásico (Para Clientes B4X)**
|
||||||
|
|
||||||
Este handler mantiene la compatibilidad con `DBRequestManager`. La selección de la base de datos se realiza dinámicamente a través de la URL.
|
Este handler mantiene la compatibilidad con `DBRequestManager`. La selección de la base de datos se realiza dinámicamente a través de la URL.
|
||||||
|
|
||||||
- Para `config.properties` \=\> `http://tu-dominio.com:8090`
|
* Para `config.properties` =\> `http://tu-dominio.com:8090`
|
||||||
- Para `config.DB2.properties` \=\> `http://tu-dominio.com:8090/DB2`
|
* Para `config.DB2.properties` =\> `http://tu-dominio.com:8090/DB2`
|
||||||
- Para `config.DB3.properties` \=\> `http://tu-dominio.com:8090/DB3`
|
* Para `config.DB3.properties` =\> `http://tu-dominio.com:8090/DB3`
|
||||||
- Para `config.DB4.properties` \=\> `http://tu-dominio.com:8090/DB4`
|
* Para `config.DB4.properties` =\> `http://tu-dominio.com:8090/DB4`
|
||||||
|
|
||||||
## **5\. Uso del DBHandlerJSON (Para Clientes Web)**
|
-----
|
||||||
|
|
||||||
|
## **6. Uso del DBHandlerJSON (Para Clientes Web)**
|
||||||
|
|
||||||
Este handler está diseñado para clientes que se comunican vía `JSON`, como aplicaciones web JavaScript.
|
Este handler está diseñado para clientes que se comunican vía `JSON`, como aplicaciones web JavaScript.
|
||||||
|
|
||||||
### **5.1. Endpoint y Métodos de Envío**
|
### **6.1. Endpoint y Métodos de Envío**
|
||||||
|
|
||||||
Las peticiones van dirigidas al endpoint `/DBJ`. El handler es flexible y acepta datos de dos maneras:
|
Las peticiones van dirigidas al endpoint `/DBJ`. El handler es flexible y acepta datos de dos maneras:
|
||||||
|
|
||||||
**Método Recomendado: POST con Body JSON**
|
**Método Recomendado: POST con Body JSON**
|
||||||
|
|
||||||
Esta es la forma más limpia y estándar para las APIs modernas.
|
* **Método HTTP**: POST
|
||||||
|
* **URL**: `http://tu-dominio.com:8090/DBJ`
|
||||||
- **Método HTTP**: POST
|
* **Header Requerido**: `Content-Type: application/json`
|
||||||
- **URL**: http://tu-dominio.com:8090/DBJ
|
* **Body (Payload)**: El objeto JSON se envía directamente en el cuerpo de la petición.
|
||||||
- **Header Requerido**: Content-Type: application/json
|
|
||||||
- **Body (Payload)**: El objeto JSON se envía directamente en el cuerpo de la petición.
|
|
||||||
|
|
||||||
**Ejemplo de Body:**
|
**Ejemplo de Body:**
|
||||||
|
|
||||||
```
|
```json
|
||||||
{
|
{
|
||||||
"dbx": "DB2",
|
"dbx": "DB2",
|
||||||
"query": "get\_user",
|
"query": "get_user",
|
||||||
"exec": "executeQuery",
|
"exec": "executeQuery",
|
||||||
"params": {
|
"params": [
|
||||||
"par1": "CDAZA"
|
"CDAZA"
|
||||||
}
|
]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
**Método Legacy: GET con Parámetro `j`**
|
**Método Legacy: GET con Parámetro `j`**
|
||||||
|
|
||||||
Este método se mantiene por retrocompatibilidad.
|
* **Método HTTP**: GET
|
||||||
|
* **URL**: El JSON completo se envía como el valor del parámetro `j` en la URL.
|
||||||
|
|
||||||
- **Método HTTP**: GET (o POST con Content-Type: application/x-www-form-urlencoded)
|
**Ejemplo con GET:**
|
||||||
- **URL**: El JSON completo se envía como el valor del parámetro `j` en la URL.
|
`http://tu-dominio.com:8090/DBJ?j={"dbx":"DB2","query":"get_user","exec":"executeQuery","params":["CDAZA"]}`
|
||||||
|
|
||||||
Ejemplo con GET:
|
### **6.2. Formato del Payload JSON**
|
||||||
http://tu-dominio.com:8090/DBJ?j={"dbx":"DB2","query":"get\_user","exec":"executeQuery","params":{"par1":"CDAZA"}}
|
|
||||||
|
|
||||||
### **5.2. Formato del Payload JSON**
|
|
||||||
|
|
||||||
La estructura del objeto JSON es la misma para ambos métodos:
|
La estructura del objeto JSON es la misma para ambos métodos:
|
||||||
|
|
||||||
```
|
```json
|
||||||
{
|
{
|
||||||
"exec": "executeQuery",
|
"exec": "executeQuery",
|
||||||
"query": "nombre\_del\_comando\_sql",
|
"query": "nombre_del_comando_sql",
|
||||||
"dbx": "DB1",
|
"dbx": "DB1",
|
||||||
"params": {
|
"params": [
|
||||||
"par1": "valor1",
|
"valor1",
|
||||||
"par2": 123
|
123
|
||||||
}
|
]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
- `exec`: `"executeQuery"` (para SELECT) o `"executeCommand"` (para INSERT, UPDATE, DELETE).
|
* `exec`: `"executeQuery"` (para `SELECT`) o `"executeCommand"` (para `INSERT`, `UPDATE`, `DELETE`).
|
||||||
- `query`: Nombre del comando SQL tal como está definido en el archivo de configuración (ej. `select\_user`).
|
* `query`: Nombre del comando SQL tal como está definido en el archivo de configuración.
|
||||||
- `dbx` (opcional): La llave de la base de datos (`DB1`, `DB2`, etc.). Si se omite, se usará **DB1** por defecto.
|
* `dbx` (opcional): La llave de la base de datos (`DB1`, `DB2`, etc.). Si se omite, se usará **DB1**.
|
||||||
- `params` (opcional): Un objeto que contiene los parámetros para la consulta SQL.
|
* `params` (opcional): Un **array** que contiene los parámetros para la consulta SQL, en el orden exacto que se esperan.
|
||||||
|
|
||||||
### **5.3. ¡Importante\! Envío de Parámetros**
|
### **6.3. Respuestas JSON**
|
||||||
|
|
||||||
El servidor ordena las claves de los parámetros alfabéticamente antes de pasarlos a la consulta SQL. Para asegurar que los valores se asignen al `?` correcto, **debes nombrar las claves de los parámetros de forma secuencial**: `"par1"`, `"par2"`, `"par3"`, etc.
|
|
||||||
|
|
||||||
**Nota para más de 9 parámetros**: Si tienes 10 o más parámetros, usa un cero inicial para mantener el orden alfabético correcto (ej. `"par01"`, `"par02"`, ..., `"par10"`).
|
|
||||||
|
|
||||||
### **5.4. Respuestas JSON**
|
|
||||||
|
|
||||||
Las respuestas del servidor siempre son en formato JSON e incluyen un campo booleano `success`.
|
Las respuestas del servidor siempre son en formato JSON e incluyen un campo booleano `success`.
|
||||||
|
|
||||||
- **Si success es true**, los datos se encontrarán en la llave `result`.
|
* **Si `success` es `true`**, los datos se encontrarán en la llave `result`.
|
||||||
- **Si success es false**, el mensaje de error se encontrará en la llave `error`.
|
* **Si `success` es `false`**, el mensaje de error se encontrará en la llave `error`.
|
||||||
|
|
||||||
## **6\. Administración del Servidor**
|
-----
|
||||||
|
|
||||||
Se pueden ejecutar comandos de gestión directamente desde un navegador o una herramienta como cURL.
|
## **7. Administración del Servidor**
|
||||||
|
|
||||||
- **Verificar Estado**: `http://tu-dominio.com:8090/test`
|
Se pueden ejecutar comandos de gestión directamente desde un navegador o una herramienta como `cURL`.
|
||||||
- **Recargar Configuración**: `http://tu-dominio.com:8090/manager?command=reload` (Vuelve a leer todos los archivos `config.\*.properties` sin reiniciar el servidor).
|
|
||||||
- **Reiniciar Servidor (Estándar)**: `http://tu-dominio.com:8090/manager?command=rsx` (Ejecuta los scripts `start.bat`, `start2.bat` y `stop.bat`).
|
### **7.1. Comandos de Administración**
|
||||||
- **Reiniciar Servidor (con PM2)**: `http://tu-dominio.com:8090/manager?command=rpm2` (Ejecuta `reiniciaProcesoPM2.bat` y asume que el nombre del proceso es "RDC-Multi". Modificar el `.bat` si el nombre es diferente).
|
|
||||||
|
#### **Comandos Públicos (sin autenticación)**
|
||||||
|
|
||||||
|
* **Verificar Conectividad**: `http://tu-dominio.com:8090/ping`
|
||||||
|
* Responde con un simple `PONG` y la `hora` para confirmar que el servidor está en línea.
|
||||||
|
* **Verificar Estado Detallado**: `http://tu-dominio.com:8090/test`
|
||||||
|
* Muestra información sobre las conexiones a la base de datos y el estado general.
|
||||||
|
|
||||||
|
#### **Comandos Protegidos (requieren autenticación)**
|
||||||
|
|
||||||
|
* **Recargar Configuración**: `http://tu-dominio.com:8090/manager?command=reload`
|
||||||
|
(Vuelve a leer todos los archivos `config.*.properties` sin reiniciar el servidor).
|
||||||
|
* **Reiniciar Servidor (Estándar)**: `http://tu-dominio.com:8090/manager?command=rsx`
|
||||||
|
(Ejecuta los scripts `start.bat`, `start2.bat` y `stop.bat`).
|
||||||
|
* **Reiniciar Servidor (con PM2)**: `http://tu-dominio.com:8090/manager?command=rpm2`
|
||||||
|
(Ejecuta `reiniciaProcesoPM2.bat` y asume que el nombre del proceso es "RDC-Multi". Modificar el `.bat` si el nombre es diferente).
|
||||||
|
|||||||
48
README0.md
48
README0.md
@@ -1,48 +0,0 @@
|
|||||||
# jRDC-Multi (B4J)
|
|
||||||
Servidor de DBRequest que puede cargar hasta 4 archivos de config.properties al mismo tiempo.
|
|
||||||
|
|
||||||
Los archivos se deben de llamar:
|
|
||||||
|
|
||||||
- config.propierties
|
|
||||||
- config.DB2.properties
|
|
||||||
- config.DB3.properties
|
|
||||||
- config.DB4.properties
|
|
||||||
|
|
||||||
No es necesario que sean 4 archivos, solo toma en cuenta los archivos existentes en el directorio.
|
|
||||||
|
|
||||||
En la aplicacion movil, al URL del servidor se le agrega al final /DB2, /DB3 o /DB4. (Puerto de ejemplo: 1781)
|
|
||||||
|
|
||||||
- Para usar el config.properties => http://keymon.lat:1781
|
|
||||||
- Para usar el config.DB2.properties => http://keymon.lat:1781/DB2
|
|
||||||
- Para usar el config.DB3.properties => http://keymon.lat:1781/DB3
|
|
||||||
- Para usar el config.DB4.properties => http://keymon.lat:1781/DB4
|
|
||||||
|
|
||||||
El puerto es el mismo para todos los archivos, **sin importar** que diga en cada archivo, solo toma el puerto especificado en el **primer** config.properties.
|
|
||||||
|
|
||||||
El usuario, contraseña y JdbcUrl, **si** los toma del archivo correspondiente.
|
|
||||||
|
|
||||||
Se puede revisar el **estatus** del servidor en el URL:
|
|
||||||
|
|
||||||
- http://keymon.lat:1781/test
|
|
||||||
|
|
||||||
Se puede forzar al servidor (**sin reiniciarlo**) a que **recargue** los archivos config.properties en el URL:
|
|
||||||
|
|
||||||
- http://keymon.lat:1781/manager?command=reload
|
|
||||||
|
|
||||||
Se puede reiniciar el servidor con el URL:
|
|
||||||
|
|
||||||
- http://keymon.lat:1781/manager?command=rsx
|
|
||||||
- Este comando utiliza los archivos start.bat, start2.bat y stop.bat
|
|
||||||
|
|
||||||
Si se esta corriendo el servidor con PM2, se puede reinciar con el URL:
|
|
||||||
|
|
||||||
- http://keymon.lat:1781/manager?command=rpm2
|
|
||||||
- Este comando ejecuta el archivo reiniciaProcesoPM2.bat, y **asume** que el nombre del proceso es "RDC-Multi", si no es asi, hay que **modificar** el archivo .bat
|
|
||||||
|
|
||||||
## Agregar drivers de mas bases de datos
|
|
||||||
|
|
||||||
Si se necesitan agregar mas controladores para conectarse a otras bases de datos, hay que agregar una linea a "Main":
|
|
||||||
|
|
||||||
- #AdditionalJar: ojdbc11 <= este es el nombre del archivo .jar, en este caso "C:\Android\AdditionalLibs\B4J\ojdbc11.jar"
|
|
||||||
|
|
||||||
- Al compilar la aplicación, el archivo del controlador se incluye en el archivo .jar del servidor (jRDC-Multi.jar) y no es necesario copiarlo o agregarlo al directorio del servidor en producción.
|
|
||||||
105
Readme1.md
105
Readme1.md
@@ -1,105 +0,0 @@
|
|||||||
# Servidor jRDC2-Multi Modificado (B4J)
|
|
||||||
|
|
||||||
## 1. Introducción
|
|
||||||
|
|
||||||
Este proyecto es una versión modificada del servidor [jRDC2 original](https://www.b4x.com/android/forum/threads/b4x-jrdc2-b4j-implementation-of-rdc-remote-database-connector.61801/#content), diseñada para actuar como un backend robusto y flexible. Su función principal es recibir peticiones HTTP, ejecutar comandos SQL predefinidos contra una base de datos y devolver los resultados en un formato estructurado.
|
|
||||||
|
|
||||||
Ha sido adaptado para servir tanto a clientes nativos (B4A/B4i) como a clientes web modernos (JavaScript, a través de frameworks como React, Vue, Angular, etc.).
|
|
||||||
|
|
||||||
## 2. Características Principales
|
|
||||||
|
|
||||||
* **Soporte para Múltiples Bases de Datos**: Puede cargar y gestionar hasta 4 archivos de configuración (`config.properties`) simultáneamente.
|
|
||||||
* **Comandos SQL Externalizados**: Las sentencias SQL se definen en los archivos de configuración, permitiendo modificarlas sin recompilar el servidor.
|
|
||||||
* **Doble Handler de Peticiones**: Incluye un handler clásico para clientes B4X y un handler JSON para clientes web.
|
|
||||||
* **Validaciones de Seguridad**: Verifica la existencia de comandos y la correspondencia en el número de parámetros.
|
|
||||||
* **Administración Remota**: Permite verificar el estado, recargar la configuración y reiniciar el servidor a través de URLs específicas.
|
|
||||||
|
|
||||||
## 3. Configuración
|
|
||||||
|
|
||||||
### 3.1. Archivos de Configuración
|
|
||||||
|
|
||||||
El sistema está preparado para manejar hasta **cuatro configuraciones de bases de datos** (de `DB1` a `DB4`). No es necesario tener los cuatro archivos; el servidor cargará únicamente los que encuentre.
|
|
||||||
|
|
||||||
La nomenclatura de los archivos es fundamental:
|
|
||||||
|
|
||||||
* `config.properties` (para `DB1`)
|
|
||||||
* `config.DB2.properties`
|
|
||||||
* `config.DB3.properties`
|
|
||||||
* `config.DB4.properties`
|
|
||||||
|
|
||||||
**Notas importantes:**
|
|
||||||
|
|
||||||
* El **puerto** del servidor se toma **únicamente** del archivo principal `config.properties`, sin importar lo que digan los demás.
|
|
||||||
* Los datos de conexión (`JdbcUrl`, usuario, contraseña) sí se toman del archivo correspondiente a cada base de datos.
|
|
||||||
|
|
||||||
### 3.2. Añadir Drivers de Bases de Datos Adicionales
|
|
||||||
|
|
||||||
Si necesitas conectarte a otros tipos de bases de datos (ej. Oracle), debes agregar el archivo del controlador `.jar` al proyecto antes de compilar. En el módulo `Main`, añade una línea como la siguiente:
|
|
||||||
|
|
||||||
```b4x
|
|
||||||
' Este es el nombre del archivo .jar, en este caso "C:\Ruta\Adicional\ojdbc11.jar"
|
|
||||||
#AdditionalJar: ojdbc11
|
|
||||||
````
|
|
||||||
|
|
||||||
Al compilar, el driver se incluirá en el `.jar` final del servidor, por lo que no será necesario copiarlo por separado al directorio de producción.
|
|
||||||
|
|
||||||
## 4\. Uso del Handler Clásico (Para Clientes B4X)
|
|
||||||
|
|
||||||
Este handler mantiene la compatibilidad con `DBRequestManager`. La selección de la base de datos se realiza dinámicamente a través de la URL.
|
|
||||||
|
|
||||||
* Para `config.properties` =\> `http://tu-dominio.com:8090`
|
|
||||||
* Para `config.DB2.properties` =\> `http://tu-dominio.com:8090/DB2`
|
|
||||||
* Para `config.DB3.properties` =\> `http://tu-dominio.com:8090/DB3`
|
|
||||||
* Para `config.DB4.properties` =\> `http://tu-dominio.com:8090/DB4`
|
|
||||||
|
|
||||||
## 5\. Uso del `DB1JsonHandler` (Para Clientes Web)
|
|
||||||
|
|
||||||
Este handler es para clientes que se comunican vía JSON.
|
|
||||||
|
|
||||||
### 5.1. Endpoint y Métodos
|
|
||||||
|
|
||||||
Las peticiones van al endpoint `/DBJ` y deben incluir un parámetro `j` con el JSON. Soporta `GET` y `POST`.
|
|
||||||
|
|
||||||
**Ejemplo con `GET`:**
|
|
||||||
`http://tu-dominio.com:8090/db1json?j={"dbx":"DB2","query":"get_user","exec":"executeQuery","params":{"par1":"CDAZA"}}`
|
|
||||||
|
|
||||||
### 5.2. Formato del Parámetro `j`
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"exec": "executeQuery",
|
|
||||||
"query": "nombre_del_comando_sql",
|
|
||||||
"dbx": "DB1",
|
|
||||||
"params": {
|
|
||||||
"par1": "valor1",
|
|
||||||
"par2": 123
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
* `exec`: `"executeQuery"` (para `SELECT`) o `"executeCommand"` (para `INSERT`, `UPDATE`, etc.).
|
|
||||||
* `query`: Nombre del comando SQL en el archivo de configuración.
|
|
||||||
* `dbx` (opcional): La llave de la BD (`DB1`, `DB2`, etc.). Si se omite, usa `DB1`.
|
|
||||||
* `params` (opcional): Objeto con los parámetros.
|
|
||||||
|
|
||||||
### 5.3. ¡Importante\! Envío de Parámetros
|
|
||||||
|
|
||||||
El servidor ordena las claves de los parámetros alfabéticamente. Para asegurar el orden correcto, **nombra las claves secuencialmente**: `"par1"`, `"par2"`, etc.
|
|
||||||
|
|
||||||
> **Nota para más de 9 parámetros**: Usa un cero inicial para mantener el orden (`"par01"`, `"par02"`, ..., `"par10"`).
|
|
||||||
|
|
||||||
### 5.4. Respuestas JSON
|
|
||||||
|
|
||||||
Las respuestas siempre incluyen `"success": true` o `"success": false`, con los datos en `"result"` o el error en `"error"`.
|
|
||||||
|
|
||||||
## 6\. Administración del Servidor
|
|
||||||
|
|
||||||
Se pueden ejecutar comandos de gestión directamente desde un navegador.
|
|
||||||
|
|
||||||
* **Verificar Estado**: `http://tu-dominio.com:8090/test`
|
|
||||||
* **Recargar Configuración**: `http://tu-dominio.com:8090/manager?command=reload`
|
|
||||||
(Vuelve a leer todos los archivos `config.*.properties` sin reiniciar el servidor).
|
|
||||||
* **Reiniciar Servidor (Estándar)**: `http://tu-dominio.com:8090/manager?command=rsx`
|
|
||||||
(Ejecuta los scripts `start.bat`, `start2.bat` y `stop.bat`).
|
|
||||||
* **Reiniciar Servidor (con PM2)**: `http://tu-dominio.com:8090/manager?command=rpm2`
|
|
||||||
(Ejecuta `reiniciaProcesoPM2.bat` y asume que el nombre del proceso es "RDC-Multi". Modificar el `.bat` si el nombre es diferente).
|
|
||||||
39
faviconHandler.bas
Normal file
39
faviconHandler.bas
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
B4J=true
|
||||||
|
Group=Default Group
|
||||||
|
ModulesStructureVersion=1
|
||||||
|
Type=Class
|
||||||
|
Version=10.3
|
||||||
|
@EndOfDesignText@
|
||||||
|
'Class module: FaviconHandler
|
||||||
|
' Manejador para la petición de /favicon.ico
|
||||||
|
' Simplemente devuelve un estado HTTP 204 (No Content)
|
||||||
|
' para indicar al navegador que no hay un favicon.
|
||||||
|
|
||||||
|
Sub Class_Globals
|
||||||
|
' No se necesitan variables globales para este manejador simple.
|
||||||
|
End Sub
|
||||||
|
|
||||||
|
Public Sub Initialize
|
||||||
|
' No se necesita inicialización específica para este manejador.
|
||||||
|
End Sub
|
||||||
|
|
||||||
|
Public Sub Handle(req As ServletRequest, resp As ServletResponse)
|
||||||
|
' Registra la petición en el Log (opcional, para depuración)
|
||||||
|
' Log("Petición de Favicon recibida: " & req.RequestURI)
|
||||||
|
'
|
||||||
|
' Establece el código de estado HTTP a 204 (No Content).
|
||||||
|
' Esto le dice al navegador que la petición fue exitosa, pero no hay contenido que devolver.
|
||||||
|
resp.Status = 204
|
||||||
|
|
||||||
|
' Es buena práctica cerrar el OutputStream, aunque para 204 no haya contenido explícito.
|
||||||
|
' Algunos servidores web podrían requerir cerrar el stream de respuesta.
|
||||||
|
Try
|
||||||
|
resp.OutputStream.Close
|
||||||
|
Catch
|
||||||
|
Log("Error al cerrar el OutputStream en FaviconHandler: " & LastException)
|
||||||
|
End Try
|
||||||
|
|
||||||
|
' El Return es fundamental para que el manejador termine su ejecución
|
||||||
|
' y no intente procesar la petición con otros manejadores o caiga en el "catch-all".
|
||||||
|
Return
|
||||||
|
End Sub
|
||||||
332
jRDC_Multi.b4j
332
jRDC_Multi.b4j
@@ -30,41 +30,40 @@ Library6=jshell
|
|||||||
Library7=json
|
Library7=json
|
||||||
Library8=jsql
|
Library8=jsql
|
||||||
Library9=bcrypt
|
Library9=bcrypt
|
||||||
Module1=B4AHandler
|
Module1=Cambios
|
||||||
Module10=LoginHandler
|
Module10=Manager
|
||||||
Module11=LogoutHandler
|
Module11=ping
|
||||||
Module12=Manager
|
Module12=RDCConnector
|
||||||
Module13=ping
|
Module13=TestHandler
|
||||||
Module14=RDCConnector
|
|
||||||
Module15=TestHandler
|
|
||||||
Module2=ChangePassHandler
|
Module2=ChangePassHandler
|
||||||
Module3=DB2Handler
|
Module3=DBHandlerB4X
|
||||||
Module4=DB3Handler
|
Module4=DBHandlerJSON
|
||||||
Module5=DB4Handler
|
Module5=DoLoginHandler
|
||||||
Module6=DBHandlerJSON
|
Module6=faviconHandler
|
||||||
Module7=DoLoginHandler
|
Module7=GlobalParameters
|
||||||
Module8=GlobalParameters
|
Module8=LoginHandler
|
||||||
Module9=HandlerB4X
|
Module9=LogoutHandler
|
||||||
NumberOfFiles=10
|
NumberOfFiles=10
|
||||||
NumberOfLibraries=9
|
NumberOfLibraries=9
|
||||||
NumberOfModules=15
|
NumberOfModules=13
|
||||||
Version=10.3
|
Version=10.3
|
||||||
@EndOfDesignText@
|
@EndOfDesignText@
|
||||||
'Non-UI application (console / server application)
|
'Non-UI application (console / server application)
|
||||||
#Region Project Attributes
|
|
||||||
#CommandLineArgs:
|
#Region Project Attributes
|
||||||
#MergeLibraries: True
|
#CommandLineArgs:
|
||||||
' VERSION 5.08.30
|
#MergeLibraries: True
|
||||||
'###########################################################################################################
|
' VERSION 5.09.14
|
||||||
'###################### PULL #############################################################
|
'###########################################################################################################
|
||||||
'Ctrl + click ide://run?file=%WINDIR%\System32\cmd.exe&Args=/c&Args=git&Args=pull
|
'###################### PULL #############################################################
|
||||||
'###########################################################################################################
|
'Ctrl + click ide://run?file=%WINDIR%\System32\cmd.exe&Args=/c&Args=git&Args=pull
|
||||||
'###################### PUSH #############################################################
|
'###########################################################################################################
|
||||||
'Ctrl + click ide://run?file=%WINDIR%\System32\WindowsPowerShell\v1.0\powershell.exe&Args=github&Args=..\..\
|
'###################### PUSH #############################################################
|
||||||
'###########################################################################################################
|
'Ctrl + click ide://run?file=%WINDIR%\System32\WindowsPowerShell\v1.0\powershell.exe&Args=github&Args=..\..\
|
||||||
'###################### PUSH TORTOISE GIT #########################################################
|
'###########################################################################################################
|
||||||
'Ctrl + click ide://run?file=%WINDIR%\System32\WindowsPowerShell\v1.0\powershell.exe&Args=TortoiseGitProc&Args=/command:commit&Args=/path:"../"&Args=/closeonend:2
|
'###################### PUSH TORTOISE GIT #########################################################
|
||||||
'###########################################################################################################
|
'Ctrl + click ide://run?file=%WINDIR%\System32\WindowsPowerShell\v1.0\powershell.exe&Args=TortoiseGitProc&Args=/command:commit&Args=/path:"../"&Args=/closeonend:2
|
||||||
|
'###########################################################################################################
|
||||||
#End Region
|
#End Region
|
||||||
|
|
||||||
'change based on the jdbc jar file
|
'change based on the jdbc jar file
|
||||||
@@ -75,103 +74,244 @@ Version=10.3
|
|||||||
#AdditionalJar: sqlite-jdbc-3.7.2
|
#AdditionalJar: sqlite-jdbc-3.7.2
|
||||||
|
|
||||||
Sub Process_Globals
|
Sub Process_Globals
|
||||||
Public srvr As Server
|
' --- Variables globales accesibles desde cualquier parte del proyecto ---
|
||||||
Public const VERSION As Float = 2.23
|
Public srvr As Server ' El objeto principal del servidor HTTP de B4J.
|
||||||
Type DBCommand (Name As String, Parameters() As Object)
|
Public const VERSION As Float = 2.23 ' La versión actual de este servidor jRDC modificado.
|
||||||
Type DBResult (Tag As Object, Columns As Map, Rows As List)
|
|
||||||
Dim listaDeCP As List
|
' Tipos personalizados para la serialización y deserialización de datos
|
||||||
Dim cpFiles As List
|
' entre el cliente B4X (DBRequestManager) y el servidor jRDC2.
|
||||||
Public Connectors, commandsMap As Map
|
Type DBCommand (Name As String, Parameters() As Object) ' Define un comando SQL.
|
||||||
Public SQL1 As SQL ' Objeto SQL para la base de datos de usuarios
|
Type DBResult (Tag As Object, Columns As Map, Rows As List) ' Define la estructura de un resultado de consulta.
|
||||||
Private bc As BCrypt
|
|
||||||
|
Public listaDeCP As List ' Contiene una lista de los identificadores de bases de datos configuradas (ej. "DB1", "DB2").
|
||||||
|
Private cpFiles As List ' Una lista temporal para almacenar los nombres de archivos encontrados en el directorio.
|
||||||
|
|
||||||
|
' Mapas globales para gestionar los conectores de base de datos y los comandos SQL.
|
||||||
|
Public Connectors, commandsMap As Map ' Connectors: Almacena las instancias de RDCConnector por DB.
|
||||||
|
' commandsMap: Almacena los comandos SQL cargados para cada DB.
|
||||||
|
|
||||||
|
Public SQL1 As SQL ' Objeto SQL para interactuar con la base de datos de usuarios (SQLite).
|
||||||
|
Private bc As BCrypt ' Objeto para realizar operaciones de hashing de contraseñas de forma segura (para autenticación).
|
||||||
|
Public MainConnectorsLock As JavaObject ' Objeto de bloqueo para proteger Main.Connectors
|
||||||
|
Public ActiveRequestsCountByDB As Map
|
||||||
End Sub
|
End Sub
|
||||||
|
|
||||||
Sub AppStart (Args() As String)
|
Sub AppStart (Args() As String)
|
||||||
' --- INICIO DE CAMBIOS ---
|
' --- Subrutina principal que se ejecuta al iniciar la aplicación ---
|
||||||
' Inicializamos la base de datos. Se creará si no existe.
|
|
||||||
|
' 1. Inicializa la base de datos local de usuarios (SQLite).
|
||||||
|
' Esta base de datos se crea automáticamente si no existe y contiene los usuarios para el panel de administración.
|
||||||
InitializeSQLiteDatabase
|
InitializeSQLiteDatabase
|
||||||
' --- FIN DE CAMBIOS ---
|
|
||||||
listaDeCP.Initialize
|
' 2. Inicializa los mapas globales definidos en GlobalParameters.bas.
|
||||||
srvr.Initialize("")
|
' Estos mapas se usan para monitorear el servidor y gestionar configuraciones dinámicas.
|
||||||
Dim con As RDCConnector
|
GlobalParameters.mpLogs.Initialize ' Mapa para almacenar logs de actividad.
|
||||||
Connectors = srvr.CreateThreadSafeMap
|
GlobalParameters.mpTotalRequests.Initialize ' Mapa para contar peticiones por endpoint/DB.
|
||||||
commandsMap.Initialize
|
GlobalParameters.mpTotalConnections.Initialize ' Mapa para almacenar el estado de los pools de conexión por DB.
|
||||||
con.Initialize("DB1") 'Inicializamos el default de config.properties
|
GlobalParameters.mpBlockConnection.Initialize ' Mapa para gestionar IPs bloqueadas (si la funcionalidad está activa).
|
||||||
Connectors.Put("DB1", con)
|
GlobalParameters.ActiveRequestsCountByDB = srvr.CreateThreadSafeMap ' Aseguramos que sea thread-safe para conteo de peticiones activas por DB [___new 3.txt, conversación]
|
||||||
srvr.Port = con.serverPort
|
|
||||||
listaDeCP.Add("DB1")
|
|
||||||
|
' 3. Inicializa las estructuras principales del servidor HTTP.
|
||||||
|
listaDeCP.Initialize ' Inicializa la lista que contendrá los IDs de las bases de datos.
|
||||||
|
srvr.Initialize("") ' Inicializa el objeto servidor HTTP.
|
||||||
|
Connectors = srvr.CreateThreadSafeMap ' Crea un mapa seguro para almacenar instancias de RDCConnector (un conector por DB).
|
||||||
|
commandsMap.Initialize ' Inicializa el mapa que almacenará los comandos SQL cargados de los archivos de configuración.
|
||||||
|
|
||||||
|
' <<<< NUEVA INICIALIZACIÓN: Creamos una instancia de ReentrantLock para proteger Main.Connectors >>>>
|
||||||
|
MainConnectorsLock.InitializeNewInstance("java.util.concurrent.locks.ReentrantLock", Null)
|
||||||
|
' <<<< HASTA AQUÍ LA NUEVA INICIALIZACIÓN >>>>
|
||||||
|
|
||||||
|
' === 4. INICIALIZACIÓN DEL CONECTOR PARA LA BASE DE DATOS PRINCIPAL (DB1) ===
|
||||||
|
' 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.
|
||||||
|
con1.Initialize("DB1") ' Inicializa la instancia del conector para "DB1".
|
||||||
|
Connectors.Put("DB1", con1) ' Asocia el identificador "DB1" con su instancia de RDCConnector.
|
||||||
|
srvr.Port = con1.serverPort ' El puerto del servidor HTTP se obtiene del config.properties de DB1.
|
||||||
|
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}"$)
|
||||||
|
|
||||||
|
' === 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)
|
||||||
|
' en el mismo directorio donde se ejecuta el JAR.
|
||||||
cpFiles = File.ListFiles("./")
|
cpFiles = File.ListFiles("./")
|
||||||
If cpFiles.Size > 0 Then
|
If cpFiles.Size > 0 Then
|
||||||
Log(cpFiles)
|
|
||||||
For i = 0 To cpFiles.Size - 1
|
For i = 0 To cpFiles.Size - 1
|
||||||
If cpFiles.Get(i) = "config.DB2.properties" Then ' Si existe el archivo DB2, lo usamos.
|
' Procesa 'config.DB2.properties'
|
||||||
Dim con As RDCConnector
|
If cpFiles.Get(i) = "config.DB2.properties" Then
|
||||||
con.Initialize("DB2")
|
Dim con2 As RDCConnector ' Declara una variable específica y única para el conector de DB2.
|
||||||
Connectors.Put("DB2", con)
|
con2.Initialize("DB2") ' Inicializa la instancia del conector para "DB2".
|
||||||
listaDeCP.Add("DB2")
|
Connectors.Put("DB2", con2) ' Asocia "DB2" con su instancia de RDCConnector.
|
||||||
|
listaDeCP.Add("DB2") ' Añade "DB2" a la lista de bases de datos.
|
||||||
|
Log("Main.AppStart: Conector 'DB2' inicializado exitosamente.")
|
||||||
End If
|
End If
|
||||||
If cpFiles.Get(i) = "config.DB3.properties" Then ' Si existe el archivo DB3, lo usamos.
|
|
||||||
Dim con As RDCConnector
|
' Procesa 'config.DB3.properties'
|
||||||
con.Initialize("DB3")
|
If cpFiles.Get(i) = "config.DB3.properties" Then
|
||||||
Connectors.Put("DB3", con)
|
Dim con3 As RDCConnector ' Declara una variable específica y única para el conector de DB3.
|
||||||
listaDeCP.Add("DB3")
|
con3.Initialize("DB3") ' Inicializa la instancia del conector para "DB3".
|
||||||
|
Connectors.Put("DB3", con3) ' Asocia "DB3" con su instancia de RDCConnector.
|
||||||
|
listaDeCP.Add("DB3") ' Añade "DB3" a la lista de bases de datos.
|
||||||
|
Log("Main.AppStart: Conector 'DB3' inicializado exitosamente.")
|
||||||
End If
|
End If
|
||||||
If cpFiles.Get(i) = "config.DB4.properties" Then ' Si existe el archivo DB4, lo usamos.
|
|
||||||
con.Initialize("DB4")
|
' Procesa 'config.DB4.properties'
|
||||||
Connectors.Put("DB4", con)
|
If cpFiles.Get(i) = "config.DB4.properties" Then
|
||||||
listaDeCP.Add("DB4")
|
Dim con4 As RDCConnector ' Declara una variable específica y única para el conector de DB4.
|
||||||
|
con4.Initialize("DB4") ' Inicializa la instancia del conector para "DB4".
|
||||||
|
Connectors.Put("DB4", con4) ' Asocia "DB4" con su instancia de RDCConnector.
|
||||||
|
listaDeCP.Add("DB4") ' Añade "DB4" a la lista de bases de datos.
|
||||||
|
Log("Main.AppStart: Conector 'DB4' inicializado exitosamente.")
|
||||||
End If
|
End If
|
||||||
Next
|
Next
|
||||||
|
Else
|
||||||
|
Log("Main.AppStart: No se encontraron archivos de configuración adicionales (config.DBx.properties).")
|
||||||
End If
|
End If
|
||||||
srvr.AddHandler("/ping", "ping", False) ' Agrega un manejador a la ruta "/test", asignando las solicitudes a la clase TestHandler, el último parámetro indica si el manejador debe ejecutar en un nuevo hilo (False en este caso)
|
|
||||||
srvr.AddHandler("/test", "TestHandler", False) ' Agrega un manejador a la ruta "/test", asignando las solicitudes a la clase TestHandler, el último parámetro indica si el manejador debe ejecutar en un nuevo hilo (False en este caso)
|
' Log final de las bases de datos que el servidor está gestionando.
|
||||||
|
Dim sbListaDeCP_Log As StringBuilder
|
||||||
' --- INICIO DE CAMBIOS ---
|
sbListaDeCP_Log.Initialize
|
||||||
' 1. Rutas para el sistema de Login
|
For Each item As String In listaDeCP
|
||||||
srvr.AddHandler("/login", "LoginHandler", False) ' Sirve la página de login
|
sbListaDeCP_Log.Append(item).Append(", ")
|
||||||
srvr.AddHandler("/dologin", "DoLoginHandler", False) ' Procesa el intento de login
|
Next
|
||||||
srvr.AddHandler("/logout", "LogoutHandler", False) ' Cierra la sesión
|
If sbListaDeCP_Log.Length > 0 Then
|
||||||
srvr.AddHandler("/changepass", "ChangePassHandler", False)
|
sbListaDeCP_Log.Remove(sbListaDeCP_Log.Length - 2, sbListaDeCP_Log.Length) ' Elimina la última ", "
|
||||||
' 2. El handler del manager se queda igual, pero ahora estará protegido
|
End If
|
||||||
srvr.AddHandler("/manager", "Manager", False)
|
Log($"Main.AppStart: Bases de datos configuradas y listas: [${sbListaDeCP_Log.ToString}]"$)
|
||||||
' --- FIN DE CAMBIOS ---
|
|
||||||
|
' === 6. REGISTRO DE HANDLERS HTTP PARA EL SERVIDOR ===
|
||||||
srvr.AddHandler("/DBJ", "DBHandlerJSON", False)
|
' Asocia rutas URL específicas con clases que manejarán las peticiones correspondientes.
|
||||||
srvr.AddHandler("/dbrquery", "DBHandlerJSON", False)
|
' El último parámetro (True) indica que el handler se ejecutará en un nuevo hilo,
|
||||||
' srvr.AddHandler("/*", "DB1Handler", False) ' Si no se especifica una base de datos, entonces asignamos la solicitud a la DB1.
|
' lo que es recomendable para la mayoría de los casos para evitar bloqueos.
|
||||||
|
srvr.AddHandler("/ping", "ping", True) ' Endpoint simple para verificar si el servidor está activo.
|
||||||
srvr.AddHandler("/*", "HandlerB4X", False)
|
srvr.AddHandler("/test", "TestHandler", True) ' Endpoint para pruebas de conexión y estado del servidor.
|
||||||
|
srvr.AddHandler("/login", "LoginHandler", True) ' Muestra la página HTML de login.
|
||||||
|
srvr.AddHandler("/dologin", "DoLoginHandler", True) ' Procesa el intento de inicio de sesión.
|
||||||
|
srvr.AddHandler("/logout", "LogoutHandler", True) ' Cierra la sesión del usuario.
|
||||||
|
srvr.AddHandler("/changepass", "ChangePassHandler", True) ' Permite a los usuarios cambiar su contraseña.
|
||||||
|
srvr.AddHandler("/manager", "Manager", True) ' Panel de administración del servidor (requiere autenticación).
|
||||||
|
srvr.AddHandler("/DBJ", "DBHandlerJSON", False) ' Handler para clientes web (ej. JavaScript, Node.js) que usan JSON.
|
||||||
|
srvr.AddHandler("/dbrquery", "DBHandlerJSON", False) ' Un alias para el handler JSON, por si se usa en clientes específicos.
|
||||||
|
srvr.AddHandler("/favicon.ico", "faviconHandler", True) ' Sirve el icono de la página (favicon).
|
||||||
|
srvr.AddHandler("/*", "DBHandlerB4X", False) ' Handler por defecto para clientes B4X (DBRequestManager),
|
||||||
|
' procesa peticiones dinámicamente según la URL.
|
||||||
|
|
||||||
|
' 7. Inicia el servidor HTTP.
|
||||||
srvr.Start
|
srvr.Start
|
||||||
Log("===========================================================")
|
Log("===========================================================")
|
||||||
Log($"-=== jRDC is running on port: ${srvr.port} (version = $1.2{VERSION}) ===-"$)
|
Log($"-=== jRDC está funcionando en el puerto: ${srvr.Port} (versión = $1.2{VERSION}) ===-"$)
|
||||||
Log("===========================================================")
|
Log("===========================================================")
|
||||||
|
|
||||||
|
' 8. Inicia el bucle de mensajes de B4J. Es esencial para que la aplicación
|
||||||
|
' de servidor continúe ejecutándose y procesando eventos.
|
||||||
StartMessageLoop
|
StartMessageLoop
|
||||||
End Sub
|
End Sub
|
||||||
|
|
||||||
' Nueva subrutina para crear y configurar la base de datos de usuarios
|
' --- Subrutina para inicializar la base de datos de usuarios local (SQLite) ---
|
||||||
|
' Esta base de datos se utiliza para almacenar credenciales de usuarios que pueden
|
||||||
|
' acceder al panel de administración del servidor jRDC y los logs de queries.
|
||||||
Sub InitializeSQLiteDatabase
|
Sub InitializeSQLiteDatabase
|
||||||
Dim dbFileName As String = "users.db"
|
Dim dbFileName As String = "users.db" ' Nombre del archivo de la base de datos SQLite.
|
||||||
' Si la base de datos no existe en la carpeta del .jar, la creamos
|
|
||||||
|
' Verifica si el archivo de la base de datos ya existe en el directorio de la aplicación.
|
||||||
If File.Exists(File.DirApp, dbFileName) = False Then
|
If File.Exists(File.DirApp, dbFileName) = False Then
|
||||||
Log("Creando nueva base de datos de usuarios: " & dbFileName)
|
Log("Creando nueva base de datos de usuarios: " & dbFileName)
|
||||||
' Inicializamos la conexión
|
' Inicializa la conexión a la base de datos SQLite, creándola si no existe (último parámetro en True).
|
||||||
SQL1.InitializeSQLite(File.DirApp, dbFileName, True)
|
SQL1.InitializeSQLite(File.DirApp, dbFileName, True)
|
||||||
' Creamos la tabla de usuarios
|
|
||||||
|
' Define y ejecuta la sentencia SQL para crear la tabla 'users'.
|
||||||
Dim createUserTable As String = "CREATE TABLE users (username TEXT PRIMARY KEY, password_hash TEXT NOT NULL)"
|
Dim createUserTable As String = "CREATE TABLE users (username TEXT PRIMARY KEY, password_hash TEXT NOT NULL)"
|
||||||
SQL1.ExecNonQuery(createUserTable)
|
SQL1.ExecNonQuery(createUserTable)
|
||||||
|
|
||||||
' Creamos un usuario por defecto para el primer inicio
|
' >>> INICIO: Creación de la tabla query_logs con las nuevas columnas desde CERO <<<
|
||||||
|
Log("Creando tabla 'query_logs' con columnas de rendimiento.")
|
||||||
|
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)
|
||||||
|
' >>> FIN: Creación de la tabla query_logs <<<
|
||||||
|
|
||||||
|
' Crea un usuario por defecto para facilitar el primer acceso al panel de administración.
|
||||||
Dim defaultUser As String = "admin"
|
Dim defaultUser As String = "admin"
|
||||||
Dim defaultPass As String = "12345"
|
Dim defaultPass As String = "12345"
|
||||||
Dim hashedPass As String = bc.hashpw(defaultPass, bc.gensalt) ' bc.HashPassword(defaultPass)
|
' Genera un hash seguro de la contraseña usando BCrypt, lo cual es crucial para la seguridad.
|
||||||
|
Dim hashedPass As String = bc.hashpw(defaultPass, bc.gensalt)
|
||||||
|
' 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}"$)
|
||||||
Else
|
Else
|
||||||
' Si ya existe, solo la abrimos
|
' 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)
|
||||||
Log("Base de datos de usuarios cargada.")
|
Log("Base de datos de usuarios cargada.")
|
||||||
|
|
||||||
|
' >>> INICIO: Lógica de migración (ALTER TABLE) si la DB ya existía <<<
|
||||||
|
Log("Verificando y migrando tabla 'query_logs' si es necesario.")
|
||||||
|
|
||||||
|
' Primero, verificar si la tabla query_logs existe.
|
||||||
|
If SQL1.ExecQuerySingleResult("SELECT name FROM sqlite_master WHERE type='table' AND name='query_logs'") = Null Then
|
||||||
|
Log("Tabla 'query_logs' no encontrada, creándola con columnas de rendimiento.")
|
||||||
|
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)
|
||||||
|
Else
|
||||||
|
' Si la tabla query_logs ya existe, entonces verificamos y añadimos las columnas faltantes.
|
||||||
|
Dim columnExists As Boolean
|
||||||
|
Dim rs As ResultSet
|
||||||
|
|
||||||
|
' --- VERIFICAR Y AÑADIR busy_connections ---
|
||||||
|
columnExists = False
|
||||||
|
' Ejecutamos PRAGMA sin WHERE y lo filtramos en código.
|
||||||
|
rs = SQL1.ExecQuery("PRAGMA table_info(query_logs)")
|
||||||
|
Do While rs.NextRow
|
||||||
|
If rs.GetString("name").EqualsIgnoreCase("busy_connections") Then
|
||||||
|
columnExists = True
|
||||||
|
Exit ' La columna ya existe, salimos del bucle.
|
||||||
|
End If
|
||||||
|
Loop
|
||||||
|
rs.Close ' ¡Importante cerrar el ResultSet!
|
||||||
|
|
||||||
|
If columnExists = False Then
|
||||||
|
Log("Añadiendo columna 'busy_connections' a query_logs.")
|
||||||
|
SQL1.ExecNonQuery("ALTER TABLE query_logs ADD COLUMN busy_connections INTEGER DEFAULT 0")
|
||||||
|
End If
|
||||||
|
|
||||||
|
' --- VERIFICAR Y AÑADIR handler_active_requests ---
|
||||||
|
columnExists = False
|
||||||
|
rs = SQL1.ExecQuery("PRAGMA table_info(query_logs)") ' Ejecutamos PRAGMA nuevamente para esta columna.
|
||||||
|
Do While rs.NextRow
|
||||||
|
If rs.GetString("name").EqualsIgnoreCase("handler_active_requests") Then
|
||||||
|
columnExists = True
|
||||||
|
Exit ' La columna ya existe, salimos del bucle.
|
||||||
|
End If
|
||||||
|
Loop
|
||||||
|
rs.Close ' ¡Importante cerrar el ResultSet!
|
||||||
|
|
||||||
|
If columnExists = False Then
|
||||||
|
Log("Añadiendo columna 'handler_active_requests' a query_logs.")
|
||||||
|
SQL1.ExecNonQuery("ALTER TABLE query_logs ADD COLUMN handler_active_requests INTEGER DEFAULT 0")
|
||||||
|
End If
|
||||||
|
End If
|
||||||
|
' >>> FIN: Lógica de migración (ALTER TABLE) <<<
|
||||||
End If
|
End If
|
||||||
End Sub
|
End Sub
|
||||||
' --- FIN DE CAMBIOS ---
|
|
||||||
|
' Subrutina para registrar las métricas de rendimiento de las queries
|
||||||
|
Public Sub LogQueryPerformance(QueryName As String, DurationMs As Long, DbKey As String, ClientIp As String, HandlerActiveRequests As Int, PoolBusyConnections As Int)
|
||||||
|
Try
|
||||||
|
' El valor PoolBusyConnections ya se recibe directamente del handler.
|
||||||
|
' Removemos la lógica anterior de obtenerlo del conector.
|
||||||
|
' Dim connector As RDCConnector = Main.Connectors.Get(DbKey).As(RDCConnector)
|
||||||
|
' Dim poolBusyConnections As Int = 0
|
||||||
|
' If connector.IsInitialized Then
|
||||||
|
' Dim poolStats As Map = connector.GetPoolStats
|
||||||
|
' If poolStats.ContainsKey("BusyConnections") Then
|
||||||
|
' poolBusyConnections = poolStats.Get("BusyConnections")
|
||||||
|
' End If
|
||||||
|
' Else
|
||||||
|
' Log($"ADVERTENCIA: Conector RDC para ${DbKey} no inicializado al intentar loguear rendimiento."$)
|
||||||
|
' End If
|
||||||
|
|
||||||
|
' Insertamos los datos en la tabla query_logs de SQLite
|
||||||
|
SQL1.ExecNonQuery2("INSERT INTO query_logs (query_name, duration_ms, timestamp, db_key, client_ip, busy_connections, handler_active_requests) VALUES (?, ?, ?, ?, ?, ?, ?)", _
|
||||||
|
Array As Object(QueryName, DurationMs, DateTime.Now, DbKey, ClientIp, PoolBusyConnections, HandlerActiveRequests))
|
||||||
|
Catch
|
||||||
|
Log("Error al guardar log de query en SQLite (Main.LogQueryPerformance): " & LastException.Message)
|
||||||
|
End Try
|
||||||
|
End Sub
|
||||||
@@ -4,8 +4,6 @@ ModuleBookmarks10=
|
|||||||
ModuleBookmarks11=
|
ModuleBookmarks11=
|
||||||
ModuleBookmarks12=
|
ModuleBookmarks12=
|
||||||
ModuleBookmarks13=
|
ModuleBookmarks13=
|
||||||
ModuleBookmarks14=
|
|
||||||
ModuleBookmarks15=
|
|
||||||
ModuleBookmarks2=
|
ModuleBookmarks2=
|
||||||
ModuleBookmarks3=
|
ModuleBookmarks3=
|
||||||
ModuleBookmarks4=
|
ModuleBookmarks4=
|
||||||
@@ -20,8 +18,6 @@ ModuleBreakpoints10=
|
|||||||
ModuleBreakpoints11=
|
ModuleBreakpoints11=
|
||||||
ModuleBreakpoints12=
|
ModuleBreakpoints12=
|
||||||
ModuleBreakpoints13=
|
ModuleBreakpoints13=
|
||||||
ModuleBreakpoints14=
|
|
||||||
ModuleBreakpoints15=
|
|
||||||
ModuleBreakpoints2=
|
ModuleBreakpoints2=
|
||||||
ModuleBreakpoints3=
|
ModuleBreakpoints3=
|
||||||
ModuleBreakpoints4=
|
ModuleBreakpoints4=
|
||||||
@@ -36,16 +32,14 @@ ModuleClosedNodes10=
|
|||||||
ModuleClosedNodes11=
|
ModuleClosedNodes11=
|
||||||
ModuleClosedNodes12=
|
ModuleClosedNodes12=
|
||||||
ModuleClosedNodes13=
|
ModuleClosedNodes13=
|
||||||
ModuleClosedNodes14=
|
|
||||||
ModuleClosedNodes15=
|
|
||||||
ModuleClosedNodes2=
|
ModuleClosedNodes2=
|
||||||
ModuleClosedNodes3=4,5,6
|
ModuleClosedNodes3=
|
||||||
ModuleClosedNodes4=
|
ModuleClosedNodes4=
|
||||||
ModuleClosedNodes5=
|
ModuleClosedNodes5=
|
||||||
ModuleClosedNodes6=
|
ModuleClosedNodes6=
|
||||||
ModuleClosedNodes7=
|
ModuleClosedNodes7=
|
||||||
ModuleClosedNodes8=
|
ModuleClosedNodes8=
|
||||||
ModuleClosedNodes9=
|
ModuleClosedNodes9=
|
||||||
NavigationStack=DoLoginHandler,Class_Globals,2,0,DoLoginHandler,Initialize,7,0,Manager,Initialize,98,0,ChangePassHandler,Handle,27,0,DoLoginHandler,Handle,9,0,Manager,Handle0,202,6,Main,Process_Globals,24,0,Main,AppStart,97,0,TestHandler,Handle,11,0,Manager,Handle,46,6,LoginHandler,Handle,16,1
|
NavigationStack=RDCConnector,GetCommand,164,0,RDCConnector,GetConnection,187,0,RDCConnector,GetPoolStats,273,0,RDCConnector,Close,283,0,DBHandlerJSON,Handle,94,5,DBHandlerJSON,CleanupAndLog,200,6,DBHandlerJSON,Initialize,10,0,DBHandlerJSON,Class_Globals,6,0,Cambios,Process_Globals,25,3,Main,AppStart,152,1
|
||||||
SelectedBuild=0
|
SelectedBuild=0
|
||||||
VisibleModules=9,6,14,12,15
|
VisibleModules=3,4,12,1,7,2,5
|
||||||
|
|||||||
@@ -4,6 +4,6 @@
|
|||||||
set "params=%*"
|
set "params=%*"
|
||||||
cd /d "%~dp0" && ( if exist "%temp%\getadmin.vbs" del "%temp%\getadmin.vbs" ) && fsutil dirty query %systemdrive% 1>nul 2>nul || ( echo Set UAC = CreateObject^("Shell.Application"^) : UAC.ShellExecute "cmd.exe", "/k cd ""%~sdp0"" && ""%~s0"" %params%", "", "runas", 1 >> "%temp%\getadmin.vbs" && "%temp%\getadmin.vbs" && exit /B )
|
cd /d "%~dp0" && ( if exist "%temp%\getadmin.vbs" del "%temp%\getadmin.vbs" ) && fsutil dirty query %systemdrive% 1>nul 2>nul || ( echo Set UAC = CreateObject^("Shell.Application"^) : UAC.ShellExecute "cmd.exe", "/k cd ""%~sdp0"" && ""%~s0"" %params%", "", "runas", 1 >> "%temp%\getadmin.vbs" && "%temp%\getadmin.vbs" && exit /B )
|
||||||
|
|
||||||
pm2 start RDC-Multi
|
pm2 restart jRDC-Multi
|
||||||
|
|
||||||
exit
|
exit
|
||||||
Reference in New Issue
Block a user