Referencia API de FoxServerCore
Contenido
Introducción a FoxServerCore
FoxServerCore es el componente central del framework FoxServer que permite crear APIs RESTful utilizando Visual FoxPro. Proporciona todas las clases y utilidades necesarias para manejar solicitudes HTTP, procesar datos y generar respuestas en formato JSON.
FoxServerCore actúa como un puente entre Visual FoxPro y el servidor web, permitiendo crear servicios web modernos sin abandonar el ecosistema VFP.
El archivo principal FoxServerCore.prg
contiene las siguientes clases
principales:
- ApiController: Clase base para todos los controladores API
- FoxServerRequest: Encapsula la solicitud HTTP entrante
- FoxServerResponse: Encapsula la respuesta HTTP saliente
- FoxServerLogger: Proporciona capacidades de logging
- Clases auxiliares: Para manejo de datos y utilidades diversas
Clase ApiController
La clase ApiController
es la base para todos los controladores de API en
FoxServer. Hereda de la clase session
de VFP y proporciona una amplia gama de
métodos y propiedades para facilitar el desarrollo de APIs RESTful, incluyendo manejo de
solicitudes, procesamiento de datos, conversión JSON, registro de eventos y utilidades
diversas.
Propiedades principales
Propiedad | Tipo | Descripción |
---|---|---|
cPath |
Character | Ruta base del servicio. Ej: c:\myService\ |
bEnableLog |
Logical | Indica si el registro de logs está habilitado (.T.) o no (.F.) |
Logger |
FoxServerLogger | Instancia del objeto logger para registrar eventos y errores |
cLogCorrelationId |
Character | Identificador de correlación para logs. Se utiliza para rastrear solicitudes específicas a través de múltiples operaciones. |
ApiService |
ApiServiceUtility | Objeto de utilidad para servicios API comunes |
Inicialización y Configuración
Initialize
Inicializa el controlador API. Configura la ruta base, carga servicios auxiliares como JSONFox y establece el entorno de trabajo. Este método es invocado automáticamente por FoxServer al iniciar el servicio.
PROCEDURE Initialize
Implementación:
* Se ejecuta automáticamente al crear una instancia del controlador
procedure Initialize
set deleted on
try
this.ApiService = createobject("ApiServiceUtility")
catch
endtry
try
local lcMacro
if empty(this.cPath)
this.cPath = addbs(fullpath(curdir()))
endif
lcMacro = "set path to [" + this.cPath + "] additive"
&lcMacro
lcMacro = "set default to " + this.cPath
&lcMacro
this.LoadJsonFox()
catch to loEx
local lcError, lcFile
lcError = textmerge("<>: Error in Initialize: <> line: <>")
lcFile = this.cPath + "ApiControllerError.txt"
strtofile(lcError + chr(13) + chr(10), lcFile, 1)
endtry
endproc
GetGlobalResource
Obtiene un recurso global compartido a través del ciclo de vida de la aplicación. Permite acceso a recursos centralizados como conexiones de base de datos, configuraciones globales, etc.
FUNCTION GetGlobalResource(tcResourceName as string) AS Variant
Parámetros:
tcResourceName
(Character): Nombre del recurso a obtener
Retorna:
Variant: El recurso solicitado o NULL si no existe
Ejemplo:
* Obtener una conexión de base de datos global
loConnection = this.GetGlobalResource("DatabaseConnection")
IF !ISNULL(loConnection)
* Usar la conexión...
ENDIF
* Obtener configuración global
loConfig = this.GetGlobalResource("AppConfig")
IF !ISNULL(loConfig)
lcServerUrl = loConfig.ServerUrl
ENDIF
SetWorkingDirectory
Establece el directorio de trabajo para el controlador API y llama a Initialize para configurar el entorno.
PROCEDURE SetWorkingDirectory(tcWorkingDirectory as string)
Parámetros:
tcWorkingDirectory
(Character): Ruta del directorio de trabajo
Ejemplo:
* Configurar directorio de trabajo para el controlador
loController.SetWorkingDirectory("C:\MiAplicacion\api\")
LoadJsonFox
Carga la biblioteca JSONFox para el procesamiento de datos JSON si aún no está disponible.
PROCEDURE LoadJsonFox
Implementación:
procedure LoadJsonFox
try
if type('_screen.json') != 'O' or vartype(_screen.json) == 'X'
local lcJsonApp
lcJsonApp = this.cPath + "JSONFox.app"
if file(lcJsonApp)
do (lcJsonApp)
_screen.json.lShowErrors = .f.
else
this.logerror("JSONfox.app not found: " + lcJsonApp)
endif
endif
catch to loEx
this.LogFromException(loEx)
endtry
endproc
Ciclo de vida de los endpoints
BeforeEndpoint
Método hook que se ejecuta antes de cada endpoint. Puede ser sobrescrito en clases derivadas para implementar lógica común como autenticación, validación o registro.
FUNCTION BeforeEndpoint(req, res) AS BOOLEAN
Parámetros:
req
(FoxServerRequest): Objeto de solicitud HTTPres
(FoxServerResponse): Objeto de respuesta HTTP
Retorna:
Logical: .T. para continuar con el endpoint, .F. para detener el procesamiento
Ejemplo de implementación en clase derivada:
* En una clase que hereda de ApiController
FUNCTION BeforeEndpoint(req, res) AS BOOLEAN
* Verificar autenticación
lcTabla = THIS.cPath + "usuarios.dbf"
IF !FILE(lcTabla)
res.status(500).json('{"error":"La tabla de usuarios no existe"}')
RETURN .F. && Detener procesamiento
ENDIF
* Registrar inicio de solicitud
this.LogInfo("Procesando solicitud: " + req.method + " " + req.uri.AbsolutePath)
RETURN .T. && Continuar con el endpoint
ENDFUNC
AfterEndpoint
Método hook que se ejecuta después de cada endpoint. Puede ser sobrescrito en clases derivadas para implementar lógica común como limpieza de recursos, registro o métricas.
PROCEDURE AfterEndpoint(req, res)
Parámetros:
req
(FoxServerRequest): Objeto de solicitud HTTPres
(FoxServerResponse): Objeto de respuesta HTTP
Ejemplo de implementación en clase derivada:
* En una clase que hereda de ApiController
PROCEDURE AfterEndpoint(req, res)
* Registrar finalización de solicitud
this.LogInfo("Solicitud completada: " + req.method + " " + req.uri.AbsolutePath + " - Status: " + TRANSFORM(res.statusCode))
* Liberar recursos si es necesario
IF USED("tmpData")
USE IN tmpData
ENDIF
ENDPROC
Manejo de Logging
SetLogging / SetLogFile / SetCorrelationId
Métodos para configurar el sistema de registro de eventos y errores.
NOTA: Estos métodos son invocados por FoxServer por lo tanto no hay que intentar modificarlos.PROCEDURE SetLogging(tbEnabled as Boolean)
PROCEDURE SetLogFile(tcLogFile as string)
PROCEDURE SetCorrelationId(tcCorrelationId as string)
Parámetros:
tbEnabled
(Logical): Habilita (.T.) o deshabilita (.F.) el registrotcLogFile
(Character): Ruta completa del archivo de logtcCorrelationId
(Character): Identificador único para rastrear solicitudes
Ejemplo:
* Configurar logging para un controlador
loController.SetLogging(.T.)
loController.SetLogFile("C:\logs\api_" + DTOS(DATE()) + ".log")
loController.SetCorrelationId(SYS(2015))
* En un endpoint
FUNCTION GetUsuario(req, res)
this.LogInfo("Obteniendo usuario con ID: " + req.GetParam("id"))
* Resto del código...
ENDFUNC
LogInfo / LogError / LogFromException
Métodos para registrar diferentes tipos de mensajes en el log.
PROCEDURE LogInfo(tcMessage)
PROCEDURE LogError(tcMessage)
PROCEDURE LogFromException(toException)
Parámetros:
tcMessage
(Character): Mensaje a registrartoException
(Exception): Objeto de excepción para registrar detalles
Ejemplo:
* Registrar información
this.LogInfo("Procesando solicitud para el usuario: " + lcUserId)
* Registrar error
this.LogError("No se pudo conectar a la base de datos")
* Registrar excepción
TRY
* Código que podría fallar
CATCH TO loEx
this.LogFromException(loEx)
res.status(500).json('{"error":"Error interno del servidor"}')
ENDTRY
Manejo de JSON
ToJson
Convierte un objeto VFP a formato JSON utilizando JSONFox.
FUNCTION ToJson(toObj as object) AS memo
Parámetros:
toObj
(Object): Objeto a convertir a JSON
Retorna:
Character (Memo): Cadena JSON
Ejemplo:
* Crear un objeto y convertirlo a JSON
loUsuario = this.NewObject("id,nombre,email,activo")
loUsuario.id = 1
loUsuario.nombre = "Juan Pérez"
loUsuario.email = "juan@example.com"
loUsuario.activo = .T.
* Convertir a JSON
lcJson = this.ToJson(loUsuario)
* Resultado: {"id":1,"nombre":"Juan Pérez","email":"juan@example.com","activo":true}
ParseJson
Convierte una cadena JSON a un objeto VFP utilizando JSONFox.
FUNCTION ParseJson(tcJsonStr as memo) AS object
Parámetros:
tcJsonStr
(Character/Memo): Cadena JSON a convertir
Retorna:
Object: Objeto VFP con las propiedades definidas en el JSON
Ejemplo:
* Procesar JSON recibido en una solicitud
lcJson = req.GetRawBody()
loData = this.ParseJson(lcJson)
IF !ISNULL(loData)
? loData.nombre && "Juan Pérez"
? loData.email && "juan@example.com"
? loData.activo && .T.
ENDIF
NewObject
Crea un nuevo objeto con propiedades específicas.
FUNCTION NewObject(tcSchema as memo) AS object
Parámetros:
tcSchema
(Character/Memo): Lista de propiedades separadas por comas
Retorna:
Object: Objeto con las propiedades especificadas inicializadas a NULL
Ejemplo:
* Crear un objeto para respuesta de API
loRespuesta = this.NewObject("status,data,message,timestamp")
loRespuesta.status = "success"
loRespuesta.data = loResultados
loRespuesta.message = "Operación completada correctamente"
loRespuesta.timestamp = DATETIME()
* Enviar como JSON
res.json(this.ToJson(loRespuesta))
NewObjectFromCursor
Crea un nuevo objeto VFP con la estructura de un cursor, útil para crear plantillas de objetos basadas en estructuras de tabla.
FUNCTION NewObjectFromCursor(tcCursor as string, tnSession as integer) AS Object
Parámetros:
tcCursor
(Character): Nombre del cursor basetnSession
(Integer): ID de sesión de datos donde se encuentra el cursor
Retorna:
Object: Objeto con las propiedades correspondientes a los campos del cursor
Ejemplo:
* Crear un objeto plantilla basado en la estructura de una tabla
USE usuarios IN 0
loTemplate = this.NewObjectFromCursor("usuarios", THIS.DataSessionId)
* El objeto tendrá las propiedades: id, nombre, email, etc.
* Útil para validaciones o crear objetos de respuesta consistentes
Conversión de datos a JSON
TableToJson
Convierte un cursor VFP a formato JSON. Puede convertir todo el cursor o solo el registro actual.
PROCEDURE TableToJson(tcCursor as string, tbCurrentRow as logical) AS memo
Parámetros:
tcCursor
(Character): Nombre del cursor a convertirtbCurrentRow
(Logical): Si es .T., convierte solo el registro actual; si es .F., convierte todos los registros
Retorna:
Character (Memo): Cadena JSON con los datos del cursor
Ejemplo:
* Consultar datos
SELECT id, nombre, email, fecha_registro FROM usuarios WHERE activo = .T. INTO CURSOR curUsuarios
* Convertir todos los registros a JSON (como array)
lcJsonArray = this.TableToJson("curUsuarios", .F.)
* Resultado: [{"id":1,"nombre":"Juan","email":"juan@example.com","fecha_registro":"2023-05-15"},...]
* Convertir solo el registro actual (como objeto)
SELECT curUsuarios
LOCATE FOR id = 5
lcJsonObject = this.TableToJson("curUsuarios", .T.)
* Resultado: {"id":5,"nombre":"Ana","email":"ana@example.com","fecha_registro":"2023-06-20"}
TableToJsonObject
Convierte un cursor VFP a una colección de objetos VFP en lugar de una cadena JSON. Útil cuando necesitas manipular los datos antes de la serialización final.
FUNCTION TableToJsonObject(tcCursor as string, tbCurrentRow as logical, tnSession as integer) AS Collection
Parámetros:
tcCursor
(Character): Nombre del cursor a convertirtbCurrentRow
(Logical): Si es .T., convierte solo el registro actual; si es .F., convierte todos los registrostnSession
(Integer): ID de sesión de datos donde se encuentra el cursor
Retorna:
Collection: Colección VFP con objetos que representan cada registro
Ejemplo:
* Obtener datos como colección de objetos
SELECT id, nombre, email FROM usuarios INTO CURSOR curUsuarios
loCollection = this.TableToJsonObject("curUsuarios", .F., THIS.DataSessionId)
* Manipular los objetos antes de serializar
FOR EACH loUsuario IN loCollection
* Agregar propiedades calculadas
ADDPROPERTY(loUsuario, "display_name", loUsuario.nombre + " (" + loUsuario.email + ")")
ENDFOR
* Serializar la colección modificada
lcJson = this.ToJson(loCollection)
MasterToJSON
Convierte una relación maestro-detalle entre dos cursores a formato JSON. Útil para generar estructuras JSON jerárquicas a partir de datos relacionados.
FUNCTION MasterToJSON(tcMaster as string, tcDetail as string, tcExpr as string, tcDetailAttribute as string) AS memo
Parámetros:
tcMaster
(Character): Nombre del cursor maestrotcDetail
(Character): Nombre del cursor detalletcExpr
(Character): Expresión para relacionar los cursores (ej: "master.id = detail.master_id")tcDetailAttribute
(Character): Nombre del atributo en el JSON donde se incluirán los detalles
Retorna:
Character (Memo): Cadena JSON con la estructura maestro-detalle
Ejemplo:
* Preparar cursores
SELECT id, nombre, fecha FROM facturas INTO CURSOR curFacturas
SELECT id, factura_id, producto, cantidad, precio FROM items INTO CURSOR curItems
* Convertir a JSON con estructura jerárquica
lcJson = this.MasterToJSON("curFacturas", "curItems", "curFacturas.id = curItems.factura_id", "items")
* Resultado:
* [
* {
* "id": 1,
* "nombre": "Factura A-001",
* "fecha": "2023-05-20",
* "items": [
* {"id": 101, "factura_id": 1, "producto": "Teclado", "cantidad": 2, "precio": 25.99},
* {"id": 102, "factura_id": 1, "producto": "Mouse", "cantidad": 1, "precio": 15.50}
* ]
* },
* ...
* ]
Utilidades de Texto y Formato
Format
Formatea una cadena de texto reemplazando marcadores de posición {n} con valores. Similar a String.Format en C# o format() en Python.
FUNCTION Format(tcSource as string, p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10) AS string
Parámetros:
tcSource
(Character): Cadena de formato con marcadores {0}, {1}, etc.p0...p10
(Variant): Valores a insertar en los marcadores
Retorna:
Character: Cadena formateada con los valores reemplazados
Ejemplo:
* Formatear un mensaje con valores
lcMensaje = this.Format("Usuario {0} realizó {1} en {2}", lcNombre, lcAccion, TTOC(DATETIME()))
* Resultado: "Usuario Juan realizó login en 2023-05-20 15:30:45"
* Formatear una URL
lcUrl = this.Format("/api/usuarios/{0}/pedidos/{1}", lcUserId, lcPedidoId)
* Resultado: "/api/usuarios/123/pedidos/456"
Content
Procesa contenido automáticamente según el formato solicitado por el cliente (JSON, XML, CSV). Detecta el formato preferido a través de headers Accept o parámetros de consulta.
FUNCTION Content(req as FoxServerRequest, tvSource as variant) AS memo
Parámetros:
req
(FoxServerRequest): Objeto de solicitud para detectar formato preferidotvSource
(Variant): Datos a procesar (cursor, objeto, cadena)
Retorna:
Memo: Contenido formateado según el tipo solicitado
Ejemplo:
* El cliente puede solicitar diferentes formatos:
* GET /api/usuarios?format=json
* GET /api/usuarios?format=xml
* GET /api/usuarios?format=csv
* O usar headers: Accept: application/xml
FUNCTION GetUsuarios(req, res) HELP "GET: usuarios"
SELECT * FROM usuarios INTO CURSOR curUsuarios
* Content() detecta automáticamente el formato y convierte
lcContent = this.Content(req, "curUsuarios")
res.status(200).send(lcContent)
ENDFUNC
TableToXml / TableToCsv
Métodos especializados para convertir cursores a formatos XML y CSV respectivamente.
FUNCTION TableToXml(tcCursor as string, tbCurrentRow as logical) AS memo
FUNCTION TableToCsv(tcCursor as string, tbCurrentRow as logical) AS memo
Parámetros:
tcCursor
(Character): Nombre del cursor a convertirtbCurrentRow
(Logical): Si es .T., convierte solo el registro actual
Ejemplo:
* Generar XML de datos
SELECT * FROM productos INTO CURSOR curProductos
lcXml = this.TableToXml("curProductos", .F.)
* Generar CSV para exportación
lcCsv = this.TableToCsv("curProductos", .F.)
res.header("Content-Disposition", "attachment; filename=productos.csv").send(lcCsv)
EscapeCharsToJSON / EscapeCharsToHTML
Métodos para escapar caracteres especiales para su uso en JSON o HTML. Útil para manejar correctamente caracteres internacionales y especiales.
FUNCTION EscapeCharsToJSON(tcStream as string) AS string
FUNCTION EscapeCharsToHTML(tcStream as string) AS string
Parámetros:
tcStream
(Character): Texto a escapar
Retorna:
Character: Texto con caracteres especiales escapados
Ejemplo:
* Escapar caracteres para JSON
lcTexto = "Café con leche & más"
lcEscapado = this.EscapeCharsToJSON(lcTexto)
* Resultado: "Caf\u00e9 con leche \u0026 m\u00e1s"
* Escapar caracteres para HTML
lcTexto = "Sección de información"
lcEscapado = this.EscapeCharsToHTML(lcTexto)
* Resultado: "Sección de información"
Utilidades de Fecha y Tiempo
DTOUNX / UNXTOD
Métodos para convertir entre fechas VFP y timestamps UNIX. Útil para interactuar con APIs que utilizan timestamps UNIX.
FUNCTION DTOUNX(tdFecha as datetime, tbUseMiliseconds as logical) AS number
FUNCTION UNXTOD(tnUnixTimestamp as number) AS datetime
Parámetros:
tdFecha
(DateTime): Fecha y hora de VFP a convertirtbUseMiliseconds
(Logical): Si es .T., devuelve milisegundos; si es .F., devuelve segundostnUnixTimestamp
(Number): Timestamp UNIX en segundos o milisegundos
Retorna:
Number/DateTime: Timestamp UNIX o fecha VFP según el método
Ejemplo:
* Convertir fecha VFP a timestamp UNIX
ldFecha = DATETIME()
lnTimestamp = this.DTOUNX(ldFecha, .F.) && En segundos
lnTimestampMs = this.DTOUNX(ldFecha, .T.) && En milisegundos
* Convertir timestamp UNIX a fecha VFP
ldFecha1 = this.UNXTOD(1621500000) && Desde segundos
ldFecha2 = this.UNXTOD(1621500000000) && Desde milisegundos
Utilidades de Codificación
Base64URLEncode / Base64URLDecode
Métodos para codificar y decodificar cadenas en formato Base64URL. Útil para trabajar con tokens JWT y URLs seguras.
FUNCTION Base64URLEncode(tcInput as string) AS string
FUNCTION Base64URLDecode(tcInput as string) AS string
Parámetros:
tcInput
(Character): Texto a codificar o decodificar
Retorna:
Character: Texto codificado o decodificado
Ejemplo:
* Codificar payload JWT
lcPayload = '{"sub":"123","name":"John Doe","role":"admin"}'
lcEncodedPayload = this.Base64URLEncode(lcPayload)
* Resultado: "eyJzdWIiOiIxMjMiLCJuYW1lIjoiSm9obiBEb2UiLCJyb2xlIjoiYWRtaW4ifQ"
* Decodificar token
lcDecodedPayload = this.Base64URLDecode(lcEncodedPayload)
* Resultado: '{"sub":"123","name":"John Doe","role":"admin"}'
NewGuid
Genera un nuevo identificador único global (GUID). Útil para crear identificadores únicos para recursos, correlación de logs, etc.
FUNCTION NewGuid() AS string
Retorna:
Character: GUID en formato de cadena sin llaves
Ejemplo:
* Generar un GUID para un nuevo recurso
lcId = this.NewGuid()
* Resultado: "a1b2c3d4-e5f6-47g8-h9i0-j1k2l3m4n5o6"
* Usar como correlación para logs
this.SetCorrelationId(this.NewGuid())
Gestión de Recursos
CleanUp / Release / Dispose
Métodos para liberar recursos y limpiar el controlador cuando ya no se necesita. Ayudan a prevenir fugas de memoria y asegurar una correcta liberación de recursos.
PROCEDURE CleanUp
PROCEDURE Release
PROCEDURE Dispose
Implementación:
procedure CleanUp
try
if type('this.Logger') == 'O' and vartype(this.Logger) == 'O'
this.Logger = null
endif
catch
endtry
endproc
procedure release
this.CleanUp()
endproc
procedure Dispose
this.CleanUp()
endproc
Ejemplos de Implementación
Ejemplo 1: Creación de un controlador básico
DEFINE CLASS UsuariosController AS ApiController
* Implementación de métodos hooks
FUNCTION BeforeEndpoint(req, res) AS BOOLEAN
* Verificar si la tabla de usuarios existe
LOCAL lcTabla
lcTabla = THIS.cPath + "usuarios.dbf"
IF !FILE(lcTabla)
res.status(500).json('{"error":"La tabla de usuarios no existe"}')
RETURN .F. && Detener procesamiento
ENDIF
* Registrar inicio de solicitud
this.LogInfo(this.Format("Solicitud iniciada: {0} {1}", req.Method, req.Uri.AbsolutePath))
RETURN .T.
ENDFUNC
PROCEDURE AfterEndpoint(req, res)
* Registrar finalización
this.LogInfo(this.Format("Solicitud completada: {0} {1} - Status: {2}", ;
req.GetMethod(), req.Uri.AbsolutePath, TRANSFORM(res.GetStatusCode())))
CLOSE DATABASES ALL
ENDPROC
* Endpoint para obtener lista de usuarios
PROCEDURE GetUsuarios(req, res) HELP "GET: usuarios public"
* Al usar "public" se indica que este endpoint es accesible sin autenticación
LOCAL lcFiltro, lcOrden
* Obtener parámetros de consulta
lcFiltro = req.GetQuery("filtro", "")
lcOrden = req.GetQuery("orden", "nombre")
* Registrar información
this.LogInfo(this.Format("Obteniendo usuarios con filtro: {0}, orden: {1}", lcFiltro, lcOrden))
* Ejecutar consulta
IF !EMPTY(lcFiltro)
SELECT id, nombre, email, activo FROM usuarios ;
WHERE UPPER(nombre) LIKE UPPER('%' + lcFiltro + '%') ;
ORDER BY &lcOrden ;
INTO CURSOR curUsuarios
ELSE
SELECT id, nombre, email, activo FROM usuarios ;
ORDER BY &lcOrden ;
INTO CURSOR curUsuarios
ENDIF
* Convertir a JSON y responder
lcJson = this.TableToJson("curUsuarios", .F.)
res.status(200).json(lcJson)
ENDPROC
* Endpoint para obtener un usuario específico
PROCEDURE GetUsuario(req, res) HELP "GET: usuarios/{id} public"
* Al usar "public" se indica que este endpoint es accesible sin autenticación
LOCAL lcId, lcJson
* Obtener ID del usuario
lcId = req.GetParam("id", "0")
* Validar ID
IF !ISDIGIT(lcId) OR VAL(lcId) <= 0
res.status(400).json('{"error":"ID de usuario inválido"}')
RETURN
ENDIF
* Buscar usuario
SELECT id, nombre, email, telefono, direccion, activo ;
FROM usuarios WHERE id = VAL(lcId) ;
INTO CURSOR curUsuario
* Verificar si existe
IF RECCOUNT("curUsuario") = 0
res.status(404).json('{"error":"Usuario no encontrado"}')
RETURN
ENDIF
* Convertir a JSON y responder
lcJson = this.TableToJson("curUsuario", .T.)
res.status(200).json(lcJson)
ENDPROC
* Endpoint para crear un nuevo usuario
PROCEDURE CreateUsuario(req, res) HELP "POST: usuarios public"
* Al usar "public" se indica que este endpoint es accesible sin autenticación
LOCAL loData, lcNombre, lcEmail, lcJson
* Obtener datos del cuerpo de la solicitud
loData = this.ParseJson(req.GetRawBody())
* Validar datos requeridos
IF ISNULL(loData) OR EMPTY(loData.nombre) OR EMPTY(loData.email)
res.status(400).json('{"error":"Nombre y email son obligatorios"}')
RETURN
ENDIF
* Verificar si el email ya existe
SELECT COUNT(*) as total FROM usuarios WHERE email = loData.email INTO CURSOR curCheck
IF curCheck.total > 0
res.status(409).json('{"error":"El email ya está registrado"}')
RETURN
ENDIF
* Insertar nuevo usuario
lcNombre = loData.email
lcEmail = loData.email
INSERT INTO usuarios (nombre, email, activo, fecha_registro) ;
VALUES (lcNombre, lcEmail, .T., DATETIME())
* Obtener ID generado
lnId = _tally
* Crear objeto de respuesta
loRespuesta = this.NewObject("id,mensaje")
loRespuesta.id = lnId
loRespuesta.mensaje = "Usuario creado correctamente"
* Responder
res.status(201).json(this.ToJson(loRespuesta))
ENDPROC
ENDDEFINE
Consejos de uso
Buenas prácticas
- Organización del código: Divida su API en controladores lógicos por funcionalidad o recurso.
- Validación de entradas: Siempre valide los parámetros de entrada
para evitar errores y vulnerabilidades:
* Validar ID numérico lcId = req.GetParam("id", "0") IF !ISDIGIT(lcId) OR VAL(lcId) <= 0 res.status(400).json('{"error":"ID inválido"}') RETURN ENDIF
- Manejo de errores: Use bloques TRY-CATCH para capturar y manejar
excepciones:
TRY * Código que podría fallar loResultado = this.ProcesarDatos(req.json) res.status(200).json(this.ToJson(loResultado)) EXIT && en los bloques TRY/CATCH no se puede usar RETURN CATCH TO loEx this.LogFromException(loEx) loRespuesta = this.NewObject("status,message") loRespuesta.status = "error" loRespuesta.message = "Error procesando solicitud: " + loEx.Message * Serializamos la respuesta por si loEx.Message contiene caracteres especiales res.status(500).json(this.ToJson(loRespuesta)) ENDTRY
- Uso de hooks: Aproveche los métodos BeforeEndpoint y AfterEndpoint
para implementar lógica común:
* En BeforeEndpoint: validación de autenticación, registro, etc. * En AfterEndpoint: limpieza de recursos, métricas, etc.
- Respuestas consistentes: Mantenga un formato de respuesta
consistente en toda su API:
* Estructura de respuesta estándar loRespuesta = this.NewObject("status,data,message") loRespuesta.status = "success" * o "error" loRespuesta.data = loResultados loRespuesta.message = "Operación completada"
- Logging efectivo: Utilice el sistema de logging para facilitar la
depuración:
* Incluir información relevante en los logs this.LogInfo(this.Format("Usuario {0} solicitó recurso {1}", lcUserId, lcRecursoId))
- Liberación de recursos: Asegúrese de cerrar cursores y liberar
objetos cuando termine:
* En AfterEndpoint o al final del procesamiento IF USED("curDatos") USE IN curDatos ENDIF
Objetos Request y Response
FoxServerRequest
El objeto FoxServerRequest
encapsula la solicitud HTTP entrante y proporciona
acceso a sus datos. Este objeto es pasado a cada manejador de ruta y contiene toda la
información necesaria sobre la petición del cliente.
Propiedad | Tipo | Descripción |
---|---|---|
Method |
Character | Método HTTP utilizado en la solicitud (GET, POST, PUT, DELETE, etc.) |
Uri |
Object | Objeto con información detallada de la URI, incluyendo Scheme, Host, Port, AbsolutePath, PathAndQuery y AbsoluteUri |
Headers |
FoxServerTupleDictionary | Colección de encabezados HTTP recibidos en la solicitud |
Params |
Object | Parámetros extraídos de la ruta URL (p.ej. /usuarios/:id donde :id es un parámetro) |
Query |
Object | Parámetros de consulta extraídos de la URL (p.ej. ?nombre=Juan&edad=30) |
Json |
Object | Cuerpo de la solicitud parseado como JSON (disponible cuando Content-Type es application/json) |
RawBody |
Character (Memo) | Cuerpo de la solicitud sin procesar, útil cuando se necesita acceso al contenido original |
MultipartData |
FoxServerMultipartData | Colección de archivos subidos mediante formularios multipart/form-data |
FormFields |
FoxServerMultipartData | Campos de formulario enviados en solicitudes multipart/form-data o application/x-www-form-urlencoded |
AuthenticatedUser |
Object | Información del usuario autenticado, con propiedades id, name y role |
ContentType |
Character | Tipo de contenido de la solicitud (p.ej. application/json, multipart/form-data) |
cPath |
Character | Ruta base del servicio (p.ej. c:\myService\) |
Logger |
FoxServerLogger | Objeto para registrar eventos y errores durante el procesamiento de la solicitud |
Métodos para acceder a datos de la solicitud
GetHeader
Obtiene el valor de un encabezado HTTP específico de la solicitud. Si el encabezado no existe, retorna un valor por defecto.
FUNCTION GetHeader(tcKey as string, tvDefault as variant) AS variant
Parámetros:
tcKey
(Character): Nombre del encabezado a obtenertvDefault
(Variant, opcional): Valor por defecto si el encabezado no existe (por defecto: '')
Ejemplo:
* Obtener token de autorización
lcToken = req.GetHeader("Authorization", "")
IF !EMPTY(lcToken)
* Procesar token...
ENDIF
* Verificar tipo de contenido
lcContentType = req.GetHeader("Content-Type", "")
IF "application/json" $ lcContentType
* Procesar como JSON...
ENDIF
GetParam / GetQuery
Obtienen valores de los parámetros de ruta o de consulta respectivamente.
FUNCTION GetParam(tcProp as string, tvDefault as variant) AS variant
FUNCTION GetQuery(tcProp as string, tvDefault as variant) AS variant
Parámetros:
tcProp
(Character): Nombre del parámetro a obtenertvDefault
(Variant, opcional): Valor por defecto si el parámetro no existe (por defecto: '')
Ejemplo:
* Para una ruta como /usuarios/:id
lcUserId = req.GetParam("id", "0")
* Para una URL como /productos?categoria=electronica&orden=precio
lcCategoria = req.GetQuery("categoria", "todas")
lcOrden = req.GetQuery("orden", "nombre")
GetQueryParams
Obtiene todos los parámetros de consulta como un objeto FoxServerTupleDictionary. Útil para iterar sobre todos los parámetros de consulta.
FUNCTION GetQueryParams() AS FoxServerTupleDictionary
Retorna:
FoxServerTupleDictionary: Diccionario con todos los parámetros de consulta
Ejemplo:
* Iterar sobre todos los parámetros de consulta
loParams = req.GetQueryParams()
FOR lnI = 1 TO loParams.Count
lcKey = loParams.GetKey(lnI)
lcValue = loParams.GetValue(lnI)
THIS.LogInfo("Parámetro: {0} = {1}", lcKey, lcValue)
ENDFOR
HasJsonBody
Verifica si la solicitud contiene un cuerpo JSON válido y procesado.
FUNCTION HasJsonBody() AS Boolean
Retorna:
Logical: .T. si hay un cuerpo JSON válido, .F. en caso contrario
Ejemplo:
* Validar que hay datos JSON antes de procesarlos
IF req.HasJsonBody()
lcNombre = req.json.nombre
lcEmail = req.json.email
ELSE
res.status(400).json('{"error":"Se requiere un cuerpo JSON válido"}')
RETURN
ENDIF
GetFormField / HasFormField
Obtiene el valor de un campo de formulario o verifica su existencia.
FUNCTION GetFormField(tcKey as string, tvDefault as variant) AS variant
FUNCTION HasFormField(tcKey as string) AS Boolean
Parámetros:
tcKey
(Character): Nombre del campo de formulariotvDefault
(Variant, opcional): Valor por defecto si el campo no existe (por defecto: '')
Ejemplo:
* Procesar un formulario de registro
IF req.HasFormField("email") AND req.HasFormField("password")
lcEmail = req.GetFormField("email")
lcPassword = req.GetFormField("password")
lcNombre = req.GetFormField("nombre", "Usuario")
* Registrar usuario...
ELSE
* Campos requeridos faltantes...
ENDIF
File / Files (GetFile / GetFiles)
Obtiene uno o varios archivos cargados desde un formulario multipart. Estos métodos son alias simplificados de GetMultipartFile y GetMultipartFiles.
* Métodos simplificados (recomendados)
FUNCTION File(tcKey as string) AS Object
FUNCTION Files(tcKey as string) AS Object
* Métodos tradicionales
FUNCTION GetFile(tcKey as string) AS Object
FUNCTION GetFiles(tcKey as string) AS Object
* Métodos originales
FUNCTION GetMultipartFile(tcKey as string, tvDefault as variant) AS variant
FUNCTION GetMultipartFiles(tcKey as string, tvDefault as variant) AS variant
Parámetros:
tcKey
(Character): Nombre del campo de archivo en el formulariotvDefault
(Variant, opcional): Valor por defecto si no hay archivos (por defecto: NULL)
Retorna:
Object: Un objeto de archivo individual (File/GetFile) o una colección de objetos de archivo (Files/GetFiles)
Ejemplos:
* Obtener un solo archivo
loFile = req.File("avatar")
IF !ISNULL(loFile)
lcDestino = req.cPath + "uploads\" + loFile.FileName
loFile.SaveToFile(lcDestino)
This.LogInfo("Archivo guardado en:{0} ", lcDestino)
ENDIF
* Obtener múltiples archivos
loFiles = req.Files("documentos")
IF !ISNULL(loFiles)
FOR EACH loKey IN loFiles.Keys
loFile = loFiles.GetValue(loKey)
lcDestino = req.cPath + "uploads\" + loFile.FileName
loFile.SaveToFile(lcDestino)
this.LogInfo("Archivo guardado: {0}", loFile.FileName)
ENDFOR
ENDIF
GetRawBody
Obtiene el cuerpo de la solicitud sin procesar como una cadena de texto.
FUNCTION GetRawBody() AS string
Ejemplo:
* Acceder al cuerpo raw para procesamiento personalizado
lcBody = req.GetRawBody()
IF !EMPTY(lcBody)
* Procesar el cuerpo según sea necesario
IF req.ContentType == "application/xml"
* Procesar XML...
ENDIF
ENDIF
SetAuthenticatedUser
Establece la información del usuario autenticado para la solicitud actual. Útil en middleware de autenticación.
PROCEDURE SetAuthenticatedUser(tcId as string, tcName as string, tcRole as string) AS Void
Parámetros:
tcId
(Character): Identificador único del usuariotcName
(Character): Nombre del usuariotcRole
(Character): Rol o nivel de acceso del usuario
SetLogging / SetLogFile / SetCorrelationId
Métodos para configurar el sistema de registro de la solicitud.
PROCEDURE SetLogging(tbEnabled as Boolean)
PROCEDURE SetLogFile(tcLogFile as string)
PROCEDURE SetCorrelationId(tcCorrelationId as string)
FoxServerResponse
El objeto FoxServerResponse
encapsula la respuesta HTTP saliente y proporciona
métodos para configurarla. Este objeto permite establecer el código de estado, encabezados y
el cuerpo de la respuesta, así como enviar diferentes tipos de contenido al cliente.
Propiedad | Tipo | Descripción |
---|---|---|
StatusCode |
Integer | Código de estado HTTP de la respuesta (por defecto: 200) |
Headers |
FoxServerTupleDictionary | Colección de encabezados HTTP a enviar en la respuesta |
Body |
Character (Memo) | Contenido del cuerpo de la respuesta |
ContentType |
Character | Tipo de contenido de la respuesta (p.ej. application/json, text/html) |
ContentDisposition |
Character | Disposición del contenido para descargas de archivos |
ResponseLocation |
Character | URL para redirecciones (encabezado Location) |
FilePath |
Character | Ruta del archivo a enviar (cuando IsFile es .T.) |
IsFile |
Logical | Indica si la respuesta es un archivo (.T.) o contenido en memoria (.F.) |
Logger |
FoxServerLogger | Objeto para registrar eventos y errores durante la generación de la respuesta |
Status
Establece el código de estado HTTP de la respuesta.
FUNCTION Status(tnStatusCode as integer) AS FoxServerResponse
Parámetros:
tnStatusCode
(Integer): Código de estado HTTP (p.ej. 200, 201, 400, 404, 500)
Retorna:
Object: La instancia de FoxServerResponse (para encadenamiento de métodos)
Ejemplos:
* Respuesta exitosa
res.status(200).json('{"mensaje":"Operación completada"}')
* Recurso creado
res.status(201).json('{"id":123,"mensaje":"Recurso creado"}')
* Error de validación
res.status(400).json('{"error":"Datos inválidos"}')
* Recurso no encontrado
res.status(404).json('{"error":"El recurso solicitado no existe"}')
* Error interno
res.status(500).json('{"error":"Error interno del servidor"}')
Header
Establece un encabezado HTTP en la respuesta.
FUNCTION Header(tcKey as string, tvValue as variant) AS FoxServerResponse
Parámetros:
tcKey
(Character): Nombre del encabezadotvValue
(Variant): Valor del encabezado
Retorna:
Object: La instancia de FoxServerResponse (para encadenamiento)
Ejemplos:
* Establecer encabezados personalizados
res.header("Content-Type", "application/json");
.header("X-API-Version", "1.0");
.header("Cache-Control", "no-cache");
.json('{"data":"ejemplo"}')
Json
Establece el cuerpo de la respuesta como JSON y configura el Content-Type adecuado.
FUNCTION Json(tcContent as memo) AS FoxServerResponse
Parámetros:
tcContent
(Character/Memo): Cadena JSON a enviar como respuesta
Retorna:
Object: La instancia de FoxServerResponse (para encadenamiento)
Ejemplos:
* Respuesta JSON simple
res.status(200).json('{"status":"success","message":"Operación completada"}')
* Respuesta JSON con datos complejos
LOCAL loRes, loData
loData = THIS.NewObject("id,nombre,email")
loData.id = 123
loData.nombre = "Juan Pérez"
loData.email = "juanperez@gmail.com"
loRes = THIS.NewObject("status,data,message,timestamp")
loRes.status = "success"
loRes.data = loData
loRes.message = "Datos obtenidos correctamente"
loRes.timestamp = DATETIME()
res.status(200).json(THIS.toJson(loRes))
Send
Envía una respuesta con el contenido especificado, detectando automáticamente el tipo de contenido.
FUNCTION Send(tcContent as memo, tcDisposition as string) AS FoxServerResponse
Parámetros:
tcContent
(Character/Memo): Contenido a enviar o ruta de archivotcDisposition
(Character, opcional): Tipo de disposición para archivos ("inline" o "attachment")
Retorna:
Object: La instancia de FoxServerResponse (para encadenamiento)
Ejemplos:
* Enviar texto plano
res.status(200).send("Operación completada correctamente")
* Enviar HTML
lcHtml = '<!DOCTYPE html><html><body><h1>Hola Mundo</h1></body></html>'
res.status(200).send(lcHtml)
* Enviar XML
lcXml = 'ok '
res.status(200).send(lcXml)
* Enviar archivo (detecta automáticamente si es una ruta de archivo)
res.status(200).send("C:\archivos\documento.pdf", "attachment")
SendFile
Envía un archivo como respuesta, configurando los encabezados adecuados para la descarga.
FUNCTION SendFile(tcFilePath as string, tcDisposition as string) AS FoxServerResponse
Parámetros:
tcFilePath
(Character): Ruta completa del archivo a enviartcDisposition
(Character): Tipo de disposición ("inline" para mostrar en navegador, "attachment" para descargar)
Retorna:
Object: La instancia de FoxServerResponse (para encadenamiento)
Ejemplos:
* Enviar un PDF para visualizar en el navegador
res.sendFile("C:\informes\reporte.pdf", "inline")
* Enviar un archivo Excel para descargar
res.sendFile("C:\datos\exportacion.xlsx", "attachment")
* Enviar una imagen
res.sendFile("C:\imagenes\foto.jpg", "inline")
* Enviar un archivo con estado personalizado
res.status(201).sendFile("C:\archivos\nuevo_documento.docx", "attachment")
Location
Establece el encabezado de redirección Location, útil para redirecciones HTTP.
FUNCTION Location(tvLocation as variant) AS FoxServerResponse
Parámetros:
tvLocation
(Variant): URL de destino para la redirección
Retorna:
Object: La instancia de FoxServerResponse (para encadenamiento)
Ejemplos:
* Redirección temporal (302)
res.status(302).location("/login").send("")
* Redirección permanente (301)
res.status(301).location("https://nuevositio.com").send("")
* Redirección después de crear un recurso
res.status(303).location("/api/recursos/" + lcId).send("")
* Redirección con mensaje
res.status(302).location("/dashboard").send('{"message":"Redirigiendo al panel de control..."}')
HasHeader / GetHeader
Verifica la existencia de un encabezado o obtiene su valor para procesamiento interno.
FUNCTION HasHeader(tcHeader as string) AS Boolean
FUNCTION GetHeader() AS string
Ejemplo:
* Verificar si ya se ha establecido un encabezado
IF !res.hasHeader("Content-Type")
res.header("Content-Type", "application/json")
ENDIF
* Iterar sobre todos los encabezados
res.nHeaderIndex = 1
DO WHILE res.HasNextHeader()
lcHeader = res.GetHeader()
? lcHeader
ENDDO
Consejos de uso
Buenas prácticas
- Encadenamiento de métodos: Aproveche el encadenamiento de métodos
en FoxServerResponse para escribir código más conciso:
res.status(200).header("X-Custom", "Value").json('{"status":"success"}')
- Validación de entradas: Siempre valide los parámetros de entrada
para evitar errores:
lcId = req.getParam("id", "0") IF !ISDIGIT(lcId) OR VAL(lcId) <= 0 res.status(400).json('{"error":"ID inválido"}') RETURN ENDIF
- Manejo de errores: Use bloques TRY-CATCH para capturar y manejar
excepciones:
TRY * Código que podría fallar loResultado = this.ProcesarDatos(req.json) res.status(200).json(_screen.json.stringify(loResultado)) EXIT && para salir del bloque TRY usamos EXIT en lugar de RETURN CATCH TO loEx req.LogFromException(loEx) res.status(500).json('{"error":"Error interno: ' + loEx.Message + '"}') ENDTRY
- Logging: Utilice el sistema de logging para facilitar la
depuración:
req.LogInfo("Procesando solicitud para el usuario: " + req.AuthenticatedUser.id) * Código de procesamiento req.LogInfo("Solicitud procesada correctamente")
Clase Auth
La clase Auth
es un componente especializado que hereda de
ApiController
y proporciona funcionalidades para la autenticación de usuarios
en FoxServer. Esta clase se encarga de validar las credenciales del usuario y comunicar el
resultado al middleware de autenticación mediante encabezados HTTP específicos.
Importante: La clase Auth se crea automáticamente cuando se activa la
opción Autenticar con JWT al crear un nuevo proyecto FoxServer con WinFx. El
encabezado X-Auth-Status
es crítico para el correcto funcionamiento de la
autenticación.
Estructura básica
define class Auth as ApiController olepublic
function ValidateUser(req, res) help "POST: auth/login public"
* implementa aquí el proceso de autenticación con tus datos.
* implement here the authentication process with your data.
endfunc
enddefine
Métodos principales
Método | Parámetros | Descripción |
---|---|---|
ValidateUser() |
req, res |
Método principal para la autenticación de usuarios. Recibe las credenciales,
valida al usuario y comunica el resultado al middleware mediante el
encabezado X-Auth-Status .
Ruta: POST: auth/login Acceso: público |
Flujo de autenticación
El proceso de autenticación en FoxServer sigue estos pasos:
- El cliente envía una solicitud POST a
/auth/login
con las credenciales del usuario. - El método
ValidateUser
en FoxPro valida estas credenciales contra la base de datos. - Dependiendo del resultado, se establece el encabezado
X-Auth-Status
atrue
ofalse
. - El middleware de autenticación en el servidor X# detecta este encabezado y, si es
true
, genera el token JWT. - El token JWT se incluye en la respuesta final enviada al cliente.
Nota: El token JWT no se genera en el código FoxPro, sino en el middleware de autenticación del servidor X#.
Encabezado X-Auth-Status
Este encabezado es crucial para el funcionamiento de la autenticación:
- X-Auth-Status: true - Indica que la autenticación fue exitosa y el middleware debe generar un token JWT.
- X-Auth-Status: false - Indica que la autenticación falló y no debe generarse un token.
Si no se incluye el encabezado X-Auth-Status
en la respuesta, el middleware
devolverá un error 406 (Not Acceptable).
Formato de respuesta requerido
Para que el middleware pueda generar correctamente el token JWT, la respuesta JSON debe incluir los siguientes campos obligatorios:
{
"status": "ok",
"data": {
"id": "123", // Obligatorio: ID del usuario
"name": "usuario", // Obligatorio: Nombre del usuario
"role": "admin" // Obligatorio: Rol del usuario
},
"message": "autenticación exitosa"
}
Implementación recomendada
A continuación se muestra una implementación completa del método ValidateUser:
procedure ValidateUser(req, res) help "POST: /auth/login public"
local loRes, loData
loRes = this.newObject('status,data,message')
loData = this.newObject('id,name,role')
try
select users
if seek(req.json.userName + req.json.password, 'users', 'login')
loData.id = users.id
loData.name = req.json.userName
loData.Role = 'server'
loRes.status = 'ok'
loRes.data = loData
loRes.message = 'autenticación exitosa'
res.AddHeader('X-Auth-Status', 'true')
res.status(200).json(this.toJson(loRes))
else
loRes.status = 'error'
loRes.data = null
loRes.message = 'el usuario y/o la contraseña no son válidos'
res.AddHeader('X-Auth-Status', 'false')
res.status(404).json(this.toJson(loRes))
endif
catch to loEx
local lcMsg
lcMsg = "Error en ValidateUser: " + loEx.message
this.Logger.logerror(lcMsg)
loRes.status = 'error'
loRes.data = null
loRes.message = lcMsg
res.addHeader('X-Auth-Status', 'false')
res.status(500).json(this.toJson(loRes))
endtry
endproc
Respuesta del middleware
Si la autenticación es exitosa, el middleware de X# generará un token JWT y modificará la respuesta para incluirlo:
{
"status": "ok",
"data": {
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"expiresIn": 3600
},
"message": "token generated successfully"
}
Protección de rutas
Cuando un proyecto FoxServer utiliza autenticación JWT, todos los endpoints requieren
autenticación por defecto. Para indicar que una ruta debe ser accesible
públicamente (sin token JWT), utilice la palabra clave public
en el comentario
de ayuda del método:
function ValidateUser(req, res) help "POST: auth/login public"
* Esta función es accesible sin token JWT (pública)
* ...
endfunc
function GetUserData(req, res) help "GET: users/:id"
* Esta función requiere autenticación con token JWT (protegida por defecto)
* ...
endfunc
Nota: No es necesario añadir ninguna palabra clave especial para rutas protegidas, ya que es el comportamiento predeterminado cuando se activa la autenticación JWT.
Sistema de Logging
FoxServerCore incluye un potente sistema de logging a través de la clase
FoxServerLogger
, que permite registrar eventos, información, advertencias y
errores durante la ejecución de la aplicación. Este sistema facilita la depuración y el
seguimiento de la actividad del servidor.
Clase FoxServerLogger
La clase FoxServerLogger
es el componente central del sistema de logging,
proporcionando métodos para registrar diferentes tipos de mensajes y configurar el
comportamiento del logger.
Propiedades principales
Propiedad | Tipo | Descripción |
---|---|---|
cLogFile |
Character | Ruta completa del archivo de log donde se escribirán los mensajes |
cPath |
Character | Directorio donde se almacena el archivo de log |
cCorrelationId |
Character | Identificador único para correlacionar mensajes relacionados con una misma operación o solicitud |
bCanWriteLog |
Logical | Indica si el logging está habilitado (.T.) o deshabilitado (.F.) |
apiService |
Object | Referencia al servicio de API para utilizar funciones de formato |
Configuración del Logger
SetLogFile
Establece el archivo donde se escribirán los mensajes de log. Si el directorio especificado no existe, lo crea automáticamente.
PROCEDURE SetLogFile(tcLogFile as string)
Parámetros:
tcLogFile
(Character): Ruta completa del archivo de log
SetLogging
Habilita o deshabilita la escritura de logs. Útil para controlar dinámicamente el logging durante la ejecución.
PROCEDURE SetLogging(tbEnabled as Boolean)
Parámetros:
tbEnabled
(Logical): .T. para habilitar, .F. para deshabilitar
SetCorrelationId
Establece un identificador de correlación que se incluirá en todos los mensajes de log. Facilita el seguimiento de operaciones específicas a través de múltiples entradas de log.
PROCEDURE SetCorrelationId(tcCorrelationId as string)
Parámetros:
tcCorrelationId
(Character): Identificador único para correlacionar mensajes
SetApiService
Establece una referencia al servicio de API para utilizar sus funciones de formato en los mensajes de log.
PROCEDURE SetApiService(toApiService as ApiServiceUtility)
Parámetros:
toApiService
(ApiServiceUtility): Instancia del servicio de API
Métodos de Logging
LogInfo
Registra un mensaje de nivel informativo. Útil para eventos normales del sistema que no requieren atención especial. Soporta formato de cadenas con múltiples parámetros.
PROCEDURE LogInfo(tcMessage, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11)
Parámetros:
tcMessage
(Character): Mensaje informativo a registrar, puede contener marcadores de posición para formatop2...p11
(Varios): Parámetros opcionales para formatear el mensaje
Ejemplo:
* Registrar mensaje simple
loLogger.LogInfo("Iniciando procesamiento de datos")
* Registrar mensaje con formato
loLogger.LogInfo("Cliente ID: {0} procesado con {1} registros", lcClienteId, lnRegistros)
LogWarning
Registra un mensaje de advertencia. Indica situaciones que no son errores pero que podrían requerir atención. Soporta formato de cadenas con múltiples parámetros.
PROCEDURE LogWarning(tcMessage, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11)
Parámetros:
tcMessage
(Character): Mensaje de advertencia a registrar, puede contener marcadores de posición para formatop2...p11
(Varios): Parámetros opcionales para formatear el mensaje
Ejemplo:
* Advertir sobre datos incompletos
loLogger.LogWarning("Usuario ID {0} no tiene email configurado", lcUserId)
* Advertir sobre recursos bajos
loLogger.LogWarning("Memoria disponible baja: {0} bytes", lnMemoryAvailable)
LogError
Registra un mensaje de error. Indica problemas que requieren atención y podrían afectar el funcionamiento del sistema. Soporta formato de cadenas con múltiples parámetros.
PROCEDURE LogError(tcMessage, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11)
Parámetros:
tcMessage
(Character): Mensaje de error a registrar, puede contener marcadores de posición para formatop2...p11
(Varios): Parámetros opcionales para formatear el mensaje
Ejemplo:
* Registrar error de conexión
loLogger.LogError("No se pudo conectar a la base de datos: {0}", lcServer)
* Registrar error con múltiples parámetros
loLogger.LogError("Error al procesar el archivo: {0}. Código: {1}, Tipo: {2}", lcFileName, lnErrorCode, lcErrorType)
LogFromException
Registra información detallada de una excepción VFP. Extrae automáticamente el número de error, línea, mensaje y procedimiento.
PROCEDURE LogFromException(toEx as exception)
Parámetros:
toEx
(Exception): Objeto de excepción capturado en un bloque TRY-CATCH
Ejemplo:
* Capturar y registrar una excepción
TRY
* Código que puede generar error
USE (lcDatabase) SHARED
* Más operaciones...
CATCH TO loException
loLogger.LogFromException(loException)
* Manejar el error...
ENDTRY
Log
Método interno que implementa la funcionalidad de logging. Puede ser llamado directamente para especificar el tipo de log mediante un código numérico. Soporta formato de cadenas con múltiples parámetros.
PROCEDURE Log(tnType as integer, tcMessage, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11)
Parámetros:
tnType
(Integer): Tipo de log (16=ERROR, 48=WARNING, 64=INFO)tcMessage
(Character): Mensaje a registrar, puede contener marcadores de posición para formatop2...p11
(Varios): Parámetros opcionales para formatear el mensaje
Nota:
Normalmente no es necesario llamar a este método directamente. Use los métodos específicos LogInfo, LogWarning y LogError en su lugar.
Formato de mensajes con múltiples parámetros
La clase FoxServerLogger ahora soporta el formato de mensajes con múltiples parámetros, similar a las funciones printf en otros lenguajes. Esto se logra a través del servicio ApiServiceUtility que proporciona la función format.
Ejemplo de formato:
* Mensaje con formato y múltiples parámetros
loLogger.LogInfo("Usuario {0} realizó {1} operaciones en {2}", lcUserName, lnOperations, lcModule)
Formato de los mensajes de log
Los mensajes de log se escriben en el archivo con el siguiente formato:
FECHA_HORA | TIPO_LOG | [CORRELATION_ID] | MENSAJE
Ejemplo:
31/05/2025 11:45:23 | INFO | [a1b2c3d4-e5f6-g7h8] | Iniciando procesamiento de solicitud GET /api/usuarios
31/05/2025 11:45:24 | WARNING | [a1b2c3d4-e5f6-g7h8] | Parámetro 'filtro' no especificado, usando valores predeterminados
31/05/2025 11:45:25 | ERROR | [a1b2c3d4-e5f6-g7h8] | Error 1526 at line 45: File 'datos.dbf' does not exist in program PROCESAR
Integración con ApiController
La clase ApiController
integra automáticamente el sistema de logging,
proporcionando métodos para acceder al logger desde los controladores:
* En un controlador que hereda de ApiController
FUNCTION GetUsuarios(req, res) HELP "GET: /api/usuarios"
this.LogInfo("Obteniendo lista de usuarios")
TRY
* Código para obtener usuarios...
CATCH TO loEx
this.LogFromException(loEx)
res.status(500).json('{"error":"Error interno del servidor"}')
ENDTRY
this.LogInfo("Se encontraron {0} usuarios", lnUsuarios)
res.status(200).json(lcJson)
ENDFUNC
Buenas prácticas de logging
Recomendaciones para un logging efectivo
- Niveles apropiados: Use el nivel adecuado para cada mensaje (INFO, WARNING, ERROR).
- Información contextual: Incluya información relevante como IDs, valores clave y estados.
- Formato estructurado: Aproveche la capacidad de formato para crear mensajes claros y consistentes.
- Correlation IDs: Use IDs de correlación para seguir operaciones a través de múltiples logs.
- Rotación de logs: Implemente una estrategia para rotar archivos de log (por día, tamaño, etc.).
- Datos sensibles: Evite registrar información sensible como contraseñas o datos personales.
- Rendimiento: Considere deshabilitar logs detallados en producción para mejorar el rendimiento.
Manejo de Datos Multipart
FoxServerCore proporciona un sistema robusto para manejar datos multipart/form-data,
permitiendo procesar formularios con archivos adjuntos y campos de texto. Este sistema se
compone de dos clases principales: FoxServerMultipartData
y
FoxServerMultipartFile
.
FoxServerMultipartData
La clase FoxServerMultipartData
extiende FoxServerTupleDictionary
y
proporciona métodos para gestionar colecciones de archivos enviados en formularios
multipart.
AddFile
Agrega un archivo a la colección de datos multipart. Si ya existen archivos con el mismo nombre de campo, los organiza como una colección.
PROCEDURE AddFile(tcFieldName as string, tcFileName as string, tcContentType as string, tvContent as Variant) as Void
Parámetros:
tcFieldName
(Character): Nombre del campo del formulariotcFileName
(Character): Nombre original del archivotcContentType
(Character): Tipo MIME del archivotvContent
(Variant): Contenido binario del archivo
Ejemplo:
* Agregar un archivo a la colección
loMultipartData = CREATEOBJECT("FoxServerMultipartData")
loMultipartData.AddFile("avatar", "profile.jpg", "image/jpeg", lcFileContent)
* Agregar múltiples archivos con el mismo nombre de campo
loMultipartData.AddFile("documentos", "contrato.pdf", "application/pdf", lcPdfContent)
loMultipartData.AddFile("documentos", "anexo.docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document", lcDocxContent)
GetFiles
Obtiene todos los archivos asociados a un nombre de campo específico como un objeto
FoxServerTupleDictionary
.
FUNCTION GetFiles(tcKey as string) as Variant
Parámetros:
tcKey
(Character): Nombre del campo del formulario
Retorna:
Variant: Objeto FoxServerTupleDictionary
con los archivos o
.NULL.
si no existen
Ejemplo:
* Obtener todos los archivos de un campo y procesarlos
loFiles = loMultipartData.GetFiles("documentos")
IF !ISNULL(loFiles)
* Iterar a través de todos los archivos
FOR EACH loKey IN loFiles.Keys
loFile = loFiles.GetValue(loKey)
THIS.LogInfo("Procesando archivo: {0} Tamaño: {1}", loFile.FileName, loFile.GetSize())
* Hacer algo con cada archivo...
ENDFOR
ENDIF
GetFile
Obtiene el primer archivo asociado a un nombre de campo específico.
FUNCTION GetFile(tcKey as string) as Variant
Parámetros:
tcKey
(Character): Nombre del campo del formulario
Retorna:
Variant: Objeto FoxServerMultipartFile
o .NULL.
si no
existe
Ejemplo:
* Obtener un único archivo y procesarlo
loFile = loMultipartData.GetFile("avatar")
IF !ISNULL(loFile)
* Verificar si es una imagen
IF loFile.IsImage()
* Guardar la imagen en un directorio
lcPath = "C:\uploads\avatars\" + SYS(2015) + "." + loFile.GetBestExtension()
IF loFile.SaveToFile(lcPath)
THIS.LogInfo("Imagen guardada en: {0}", lcPath)
ELSE
THIS.LogError("Error al guardar la imagen")
ENDIF
ENDIF
ENDIF
FoxServerMultipartFile
La clase FoxServerMultipartFile
representa un archivo individual enviado en un
formulario multipart y proporciona métodos para manipular y validar el archivo.
Propiedades
Propiedad | Tipo | Descripción |
---|---|---|
FieldName |
Character | Nombre del campo del formulario asociado al archivo |
FileName |
Character | Nombre original del archivo |
ContentType |
Character | Tipo MIME del archivo (ej: "image/jpeg", "application/pdf") |
Content |
Variant | Contenido binario del archivo |
Métodos de configuración
SetFieldName / SetFileName / SetContentType / SetContent
Métodos para establecer las propiedades del archivo.
PROCEDURE SetFieldName(tcFieldName as string) as Void
PROCEDURE SetFileName(tcFileName as string) as Void
PROCEDURE SetContentType(tcContentType as string) as Void
PROCEDURE SetContent(tvContent as Variant) as Void
Métodos de extensión de archivo
GetFileExtension
Determina la extensión de archivo basada en el tipo MIME.
FUNCTION GetFileExtension() as string
Retorna:
Character: Extensión de archivo sin el punto inicial (ej: "jpg", "pdf")
Ejemplo:
* Obtener extensión basada en el tipo MIME
lcExt = loFile.GetFileExtension()
? lcExt && "pdf" para un archivo con ContentType = "application/pdf"
GetOriginalExtension
Obtiene la extensión original del nombre de archivo.
FUNCTION GetOriginalExtension() as string
Retorna:
Character: Extensión original del archivo sin el punto inicial
Ejemplo:
* Obtener extensión del nombre original
lcExt = loFile.GetOriginalExtension()
? lcExt && "pdf" para un archivo con FileName = "documento.pdf"
GetBestExtension
Obtiene la mejor extensión disponible, usando la extensión original si existe, o la derivada del tipo MIME en caso contrario.
FUNCTION GetBestExtension() as string
Retorna:
Character: La mejor extensión disponible sin el punto inicial
Ejemplo:
* Obtener la mejor extensión para guardar el archivo
lcExt = loFile.GetBestExtension()
lcPath = "C:\uploads\" + SYS(2015) + "." + lcExt
Métodos de manejo de archivos
SaveToFile
Guarda el contenido del archivo en el sistema de archivos. Crea el directorio si no existe y agrega la extensión adecuada si es necesario.
FUNCTION SaveToFile(tcTargetPath as string) as Boolean
Parámetros:
tcTargetPath
(Character): Ruta donde guardar el archivo
Retorna:
Logical: .T. si se guardó correctamente, .F. en caso de error
Ejemplo:
* Guardar archivo con nombre único
lcPath = "C:\uploads\" + SYS(2015)
IF loFile.SaveToFile(lcPath)
THIS.Logger("Archivo guardado como: {0}", lcPath + "." + loFile.GetBestExtension())
ELSE
THIS.Logger("Error al guardar el archivo")
ENDIF
* Guardar con ruta y nombre específicos
IF loFile.SaveToFile("C:\uploads\documentos\informe_final.pdf")
? "Archivo guardado correctamente"
ENDIF
GetSize
Obtiene el tamaño del archivo en bytes.
FUNCTION GetSize() as integer
Retorna:
Integer: Tamaño del archivo en bytes
Ejemplo:
* Verificar tamaño antes de procesar
lnSize = loFile.GetSize()
THIS.Logger("Tamaño del archivo: {0} bytes", lnSize)
* Verificar si excede un límite
IF lnSize > 5000000 && 5MB
THIS.LogError("El archivo es demasiado grande")
ENDIF
IsEmpty
Verifica si el archivo está vacío (sin contenido).
FUNCTION IsEmpty() as Boolean
Retorna:
Logical: .T. si el archivo está vacío, .F. en caso contrario
Ejemplo:
* Verificar si el archivo tiene contenido
IF loFile.IsEmpty()
THIS.LogError("El archivo está vacío")
ELSE
THIS.LogInfo("El archivo contiene {0} bytes.", loFile.GetSize())
ENDIF
Métodos de validación de tipo
IsContentType
Verifica si el archivo es de un tipo MIME específico.
FUNCTION IsContentType(tcPattern as string) as Boolean
Parámetros:
tcPattern
(Character): Tipo MIME a verificar
Retorna:
Logical: .T. si coincide con el tipo especificado, .F. en caso contrario
Ejemplo:
* Verificar tipo específico
IF loFile.IsContentType("application/pdf")
THIS.LogInfo("Es un archivo PDF")
ENDIF
* Verificar tipo de imagen específico
IF loFile.IsContentType("image/jpeg")
THIS.LogInfo("Es una imagen JPEG")
ENDIF
IsImage
Verifica si el archivo es una imagen (cualquier tipo de imagen).
FUNCTION IsImage() as Boolean
Retorna:
Logical: .T. si es una imagen, .F. en caso contrario
Ejemplo:
* Verificar si es una imagen
IF loFile.IsImage()
THIS.LogInfo("Es una imagen de tipo: {0}", loFile.ContentType)
* Procesar solo imágenes
lcImagePath = "C:\uploads\images\" + SYS(2015) + "." + loFile.GetBestExtension()
loFile.SaveToFile(lcImagePath)
ELSE
? "No es una imagen"
ENDIF
IsPDF
Verifica si el archivo es un PDF.
FUNCTION IsPDF() as Boolean
Retorna:
Logical: .T. si es un PDF, .F. en caso contrario
Ejemplo:
* Verificar si es un PDF
IF loFile.IsPDF()
THIS.LogInfo("Es un archivo PDF")
* Procesar solo PDFs
lcPdfPath = "C:\uploads\documents\" + SYS(2015) + ".pdf"
loFile.SaveToFile(lcPdfPath)
ELSE
THIS.LogInfo("No es un archivo PDF")
ENDIF
IsAllowedType
Verifica si el archivo tiene una extensión permitida según una lista de extensiones.
FUNCTION IsAllowedType(tcAllowedTypes as string) as Boolean
Parámetros:
tcAllowedTypes
(Character): Lista de extensiones permitidas separadas por comas (ej: "pdf,jpg,png" o ".pdf,.jpg,.png")
Retorna:
Logical: .T. si la extensión está permitida, .F. en caso contrario
Ejemplo:
* Verificar si es un tipo permitido
IF loFile.IsAllowedType("pdf,docx,xlsx")
THIS.LogInfo("Tipo de archivo permitido")
ELSE
THIS.LogInfo("Tipo de archivo no permitido. Solo se aceptan PDF, DOCX y XLSX")
ENDIF
* Verificar tipos de imagen permitidos
IF loFile.IsImage() AND loFile.IsAllowedType("jpg,png,gif")
THIS.LogInfo("Formato de imagen permitido")
ELSE
THIS.LogInfo("Solo se permiten imágenes JPG, PNG y GIF")
ENDIF
Integración con ApiController
El sistema de manejo multipart se integra perfectamente con ApiController
para
procesar formularios con archivos:
Ejemplo: Procesamiento de formulario con archivos
DEFINE CLASS UploadController AS ApiController
* Endpoint para subir archivos
FUNCTION Upload(req, res) HELP "POST: /api/upload"
LOCAL loFiles, loFile, lcUploadPath, lcSavedPath, loResponse
* Verificar si hay archivos
IF !req.HasFiles()
res.status(400).json('{"error":"No se encontraron archivos"}')
RETURN
ENDIF
* Configurar directorio de carga
lcUploadPath = ADDBS(this.GetConfig("upload_path", "C:\uploads"))
* Verificar y crear directorio si no existe
IF !DIRECTORY(lcUploadPath)
MD (lcUploadPath)
ENDIF
* Procesar archivo de avatar (uno solo)
loFile = req.GetFile("avatar")
IF !ISNULL(loFile)
* Validar que sea una imagen
IF !loFile.IsImage()
res.status(400).json('{"error":"El avatar debe ser una imagen"}')
RETURN
ENDIF
* Validar tamaño máximo (1MB)
IF loFile.GetSize() > 1048576
res.status(400).json('{"error":"El avatar no debe superar 1MB"}')
RETURN
ENDIF
* Guardar con nombre único
lcSavedPath = ADDBS(lcUploadPath) + "avatar_" + req.GetParam("user_id") + "." + loFile.GetBestExtension()
IF !loFile.SaveToFile(lcSavedPath)
res.status(500).json('{"error":"Error al guardar el avatar"}')
RETURN
ENDIF
this.LogInfo("Avatar guardado: {0}", lcSavedPath)
ENDIF
* Procesar múltiples documentos
loDocumentos = req.GetFiles("documentos")
IF !ISNULL(loDocumentos)
LOCAL laRutas[1], lnCount, loDocumento
lnCount = 0
DIMENSION laRutas[1]
* Crear directorio específico para documentos
lcDocPath = ADDBS(lcUploadPath) + "docs_" + SYS(2015)
MD (lcDocPath)
* Procesar cada documento
FOR EACH loKey IN loDocumentos.Keys
loDocumento = loDocumentos.GetValue(loKey)
* Validar tipo permitido
IF !loDocumento.IsAllowedType("pdf,docx,xlsx")
this.LogWarning("Tipo de documento no permitido: {0}", loDocumento.FileName)
LOOP
ENDIF
* Guardar documento
lcDocFile = ADDBS(lcDocPath) + loDocumento.FileName
IF loDocumento.SaveToFile(lcDocFile)
lnCount = lnCount + 1
DIMENSION laRutas[lnCount]
laRutas[lnCount] = lcDocFile
this.LogInfo("Documento guardado: {0}", lcDocFile)
ENDIF
ENDFOR
* Crear respuesta
loResponse = this.NewObject("success,message,avatar,documentos")
loResponse.success = .T.
loResponse.message = "Archivos procesados correctamente"
loResponse.avatar = lcSavedPath
loResponse.documentos = laRutas
res.status(200).json(this.ToJson(loResponse))
ENDIF
res.status(200).json('{"success":true,"message":"Avatar procesado correctamente"}')
ENDFUNC
ENDDEFINE
Buenas prácticas
Recomendaciones para el manejo de archivos
- Validación de tipos: Siempre valide los tipos de archivo permitidos para evitar vulnerabilidades.
- Límites de tamaño: Establezca límites de tamaño para los archivos subidos.
- Nombres seguros: Genere nombres únicos para los archivos guardados para evitar colisiones.
- Estructura de directorios: Organice los archivos en directorios por tipo o fecha.
- Permisos: Asegúrese de que los directorios tengan los permisos adecuados.
- Verificación de contenido: Para mayor seguridad, verifique que el contenido coincida con el tipo declarado.
Colecciones y Diccionarios
FoxServerCore proporciona un sistema flexible para manejar colecciones de pares clave-valor a
través de las clases FoxServerTuple
y FoxServerTupleDictionary
.
Estas clases forman la base de muchas estructuras de datos utilizadas en el framework.
FoxServerTuple
La clase FoxServerTuple
representa un par clave-valor simple, donde la clave es
una cadena y el valor puede ser de cualquier tipo. Esta estructura básica se utiliza como
componente fundamental para construir colecciones más complejas.
Propiedades
Propiedad | Tipo | Descripción |
---|---|---|
key |
Character | Clave que identifica al elemento |
value |
Variant | Valor asociado a la clave (puede ser de cualquier tipo) |
Constructor (init)
Inicializa una nueva instancia de FoxServerTuple
con una clave y un valor
opcionales.
PROCEDURE init(tcKey as string, tvValue as Variant)
Parámetros:
tcKey
(Character): Clave del par (opcional)tvValue
(Variant): Valor asociado a la clave (opcional)
SetKey / GetKey
Establece o recupera la clave del par.
PROCEDURE SetKey(tcKey as string) as Void
FUNCTION GetKey() as string
Parámetros (SetKey):
tcKey
(Character): Clave a establecer
Retorna (GetKey):
Character: La clave actual del par
SetValue / GetValue
Establece o recupera el valor asociado a la clave.
PROCEDURE SetValue(tvValue as Variant) as Void
FUNCTION GetValue() as Variant
Parámetros (SetValue):
tvValue
(Variant): Valor a establecer
Retorna (GetValue):
Variant: El valor actual asociado a la clave
ToString
Devuelve una representación en cadena del par clave-valor, formateando el valor según su tipo.
FUNCTION ToString() as string
Retorna:
Character: Representación en cadena del par clave-valor
FoxServerTupleDictionary
La clase FoxServerTupleDictionary
extiende la clase nativa
Collection
de VFP para proporcionar funcionalidad de diccionario (mapa
clave-valor). Permite almacenar y recuperar valores utilizando claves de cadena, manteniendo
un acceso eficiente.
Propiedades
Propiedad | Tipo | Descripción |
---|---|---|
akeys |
Array | Array interno que almacena las claves para acceso rápido |
Exists
Verifica si una clave específica existe en el diccionario.
FUNCTION Exists(tcKey as string) as Boolean
Parámetros:
tcKey
(Character): Clave a verificar
Retorna:
Logical: .T. si la clave existe, .F. en caso contrario
AddPair
Agrega un nuevo par clave-valor al diccionario. Si la clave ya existe, actualiza su valor.
PROCEDURE AddPair(tcKey as string, tvValue as Variant)
Parámetros:
tcKey
(Character): Clave del partvValue
(Variant): Valor a asociar con la clave
AddEntry
Agrega un objeto FoxServerTuple
al diccionario. Si la clave ya existe,
actualiza su valor.
FUNCTION AddEntry(toEntry as FoxServerTuple) as Void
Parámetros:
toEntry
(FoxServerTuple): Objeto tuple a agregar
GetIndex
Obtiene el índice numérico de una clave en el diccionario.
FUNCTION GetIndex(tcKey as string) as integer
Parámetros:
tcKey
(Character): Clave a buscar
Retorna:
Integer: Índice de la clave en el diccionario (0 si no existe)
Get
Obtiene un objeto FoxServerTuple
por su índice o clave.
FUNCTION Get(tvIndexOrKey as Variant) as Variant
Parámetros:
tvIndexOrKey
(Variant): Índice numérico o clave de cadena
Retorna:
Variant: Objeto FoxServerTuple
o .NULL.
si no existe
GetValue
Obtiene el valor asociado a una clave o índice, con la opción de especificar un valor predeterminado si la clave no existe.
FUNCTION GetValue(tvIndexOrKey as Variant, tvDefault as Variant) as Variant
Parámetros:
tvIndexOrKey
(Variant): Índice numérico o clave de cadenatvDefault
(Variant): Valor predeterminado a devolver si la clave no existe (opcional)
Retorna:
Variant: Valor asociado a la clave o el valor predeterminado
CleanUp / Release / Dispose
Métodos para limpiar y liberar recursos del diccionario.
PROCEDURE CleanUp() as Void
PROCEDURE Release() as Void
PROCEDURE Dispose() as Void
Utilidades de Servicio API
La clase ApiServiceUtility
proporciona un conjunto de herramientas y funciones
auxiliares comúnmente utilizadas en el desarrollo de APIs y servicios web. Estas utilidades
facilitan tareas como la determinación de tipos MIME, validación de formatos, generación de
identificadores únicos, manipulación de JSON y más.
ApiServiceUtility
Esta clase contiene métodos que pueden ser utilizados en cualquier parte de la aplicación para realizar operaciones comunes.
Propiedades principales
Propiedad | Tipo | Descripción |
---|---|---|
Logger |
Object | Referencia al objeto FoxServerLogger para registrar eventos |
cPath |
Character | Ruta base utilizada para operaciones de archivo |
Métodos de configuración
SetPath
Establece la ruta base utilizada por la clase para operaciones de archivo.
PROCEDURE SetPath(tcPath as String)
Parámetros:
tcPath
(Character): Ruta base para operaciones de archivo
SetLogger
Establece el objeto logger utilizado para registrar eventos y errores.
PROCEDURE SetLogger(toLogger as FoxServerLogger)
Parámetros:
toLogger
(FoxServerLogger): Instancia del logger para registrar eventos
Utilidades básicas
GetMimeType
Determina el tipo MIME correspondiente a una extensión de archivo. Este método es útil para establecer cabeceras Content-Type adecuadas al servir archivos estáticos o descargas.
FUNCTION GetMimeType(tcExtension as string) as string
Parámetros:
tcExtension
(Character): Extensión del archivo sin el punto inicial (ej: "pdf", "jpg")
Retorna:
Character: Tipo MIME correspondiente a la extensión
Ejemplo:
* Obtener tipos MIME para diferentes extensiones
? loUtils.GetMimeType("pdf") && "application/pdf"
? loUtils.GetMimeType("jpg") && "image/jpeg"
? loUtils.GetMimeType("docx") && "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
? loUtils.GetMimeType("json") && "application/json"
IsValidEmail
Valida si una cadena tiene formato de dirección de correo electrónico válido utilizando expresiones regulares.
FUNCTION IsValidEmail(tcEmail as string) as Boolean
Parámetros:
tcEmail
(Character): Dirección de correo electrónico a validar
Retorna:
Logical: .T. si el formato es válido, .F. en caso contrario
Ejemplo:
* Validar direcciones de correo electrónico
? loUtils.IsValidEmail("usuario@dominio.com") && .T.
? loUtils.IsValidEmail("nombre.apellido@empresa.co.uk") && .T.
? loUtils.IsValidEmail("usuario@dominio") && .F.
GenerateUUID
Genera un identificador único universal (UUID/GUID) utilizando preferentemente el
componente COM scriptlet.typelib
o, como respaldo, una implementación
manual.
FUNCTION GenerateUUID() as string
Retorna:
Character: Cadena UUID en formato estándar (ej: "550e8400-e29b-41d4-a716-446655440000")
Ejemplo:
* Generar identificadores únicos
lcUUID = loUtils.GenerateUUID()
? lcUUID && Por ejemplo: "550e8400-e29b-41d4-a716-446655440000"
GenerateUUIDManual
Implementación manual para generar un UUID cuando el componente COM no está disponible.
Este método es utilizado internamente por GenerateUUID
como respaldo.
FUNCTION GenerateUUIDManual() as string
Retorna:
Character: Cadena UUID en formato estándar
Utilidades de formato y texto
format
Formatea una cadena de texto reemplazando marcadores de posición con valores proporcionados. Similar a la función printf en otros lenguajes.
FUNCTION format(tcSource, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11) as string
Parámetros:
tcSource
(Character): Cadena de formato con marcadores de posición {0}, {1}, etc.p2...p11
(Varios): Valores para reemplazar los marcadores de posición
Retorna:
Character: Cadena formateada con los valores reemplazados
Ejemplo:
* Formatear una cadena con valores
lcNombre = "Juan"
lnEdad = 30
lcCiudad = "Madrid"
lcMensaje = loUtils.format("Hola, mi nombre es {0}, tengo {1} años y vivo en {2}.", lcNombre, lnEdad, lcCiudad)
? lcMensaje && "Hola, mi nombre es Juan, tengo 30 años y vivo en Madrid."
EscapeCharsToJSON
Convierte caracteres especiales y acentuados a su representación Unicode para uso en cadenas JSON.
FUNCTION EscapeCharsToJSON(tcStream) as string
Parámetros:
tcStream
(Character): Cadena con caracteres que necesitan ser escapados
Retorna:
Character: Cadena con caracteres escapados para JSON
Ejemplo:
* Escapar caracteres especiales para JSON
lcTexto = "José Martínez & Cía."
lcEscapado = loUtils.EscapeCharsToJSON(lcTexto)
? lcEscapado && "Jos\u00e9 Mart\u00ednez \u0026 C\u00eda."
EscapeCharsToHTML
Convierte caracteres acentuados a su representación en entidades HTML.
FUNCTION EscapeCharsToHTML(tcStream) as string
Parámetros:
tcStream
(Character): Cadena con caracteres que necesitan ser convertidos
Retorna:
Character: Cadena con caracteres convertidos a entidades HTML
Ejemplo:
* Convertir caracteres a entidades HTML
lcTexto = "José María"
lcHtml = loUtils.EscapeCharsToHTML(lcTexto)
? lcHtml && "José María"
Utilidades de fecha y hora
DTOUNX
Convierte una fecha/hora de VFP a un timestamp UNIX (segundos o milisegundos desde el 1 de enero de 1970).
FUNCTION DTOUNX(tdFecha as datetime, tbUseMiliseconds as logical) as number
Parámetros:
tdFecha
(DateTime): Fecha y hora a convertirtbUseMiliseconds
(Logical): Si es .T., retorna milisegundos; si es .F., retorna segundos
Retorna:
Number: Timestamp UNIX en segundos o milisegundos
Ejemplo:
* Convertir fecha a timestamp UNIX
ldFecha = {^2025-06-01 12:30:00}
* En segundos
lnUnixSecs = loUtils.DTOUNX(ldFecha, .F.)
? lnUnixSecs && 1748905800
* En milisegundos
lnUnixMs = loUtils.DTOUNX(ldFecha, .T.)
? lnUnixMs && 1748905800000
UNXTOD
Convierte un timestamp UNIX a una fecha/hora de VFP.
FUNCTION UNXTOD(tnUnixTimestamp as number) as datetime
Parámetros:
tnUnixTimestamp
(Number): Timestamp UNIX en segundos (10 dígitos) o milisegundos (13 dígitos)
Retorna:
DateTime: Fecha y hora correspondiente al timestamp
Ejemplo:
* Convertir timestamp UNIX a fecha
* Timestamp en segundos
ldFecha1 = loUtils.UNXTOD(1748905800)
? ldFecha1 && 01/06/2025 12:30:00
* Timestamp en milisegundos
ldFecha2 = loUtils.UNXTOD(1748905800000)
? ldFecha2 && 01/06/2025 12:30:00
Utilidades de codificación
Base64URLEncode
Codifica una cadena en formato Base64URL (variante de Base64 segura para URLs).
FUNCTION Base64URLEncode(tcInput as string) as string
Parámetros:
tcInput
(Character): Cadena a codificar
Retorna:
Character: Cadena codificada en Base64URL
Ejemplo:
* Codificar en Base64URL
lcTexto = "Esto es un texto de prueba"
lcCodificado = loUtils.Base64URLEncode(lcTexto)
? lcCodificado && "RXN0byBlcyB1biB0ZXh0byBkZSBwcnVlYmE"
Base64URLDecode
Decodifica una cadena en formato Base64URL a su valor original.
FUNCTION Base64URLDecode(tcInput as string) as string
Parámetros:
tcInput
(Character): Cadena codificada en Base64URL
Retorna:
Character: Cadena decodificada
Ejemplo:
* Decodificar desde Base64URL
lcCodificado = "RXN0byBlcyB1biB0ZXh0byBkZSBwcnVlYmE"
lcDecodificado = loUtils.Base64URLDecode(lcCodificado)
? lcDecodificado && "Esto es un texto de prueba"
Utilidades JSON
LoadJsonFox
Carga la biblioteca JSONFox para procesamiento de JSON si aún no está cargada.
PROCEDURE LoadJsonFox()
Notas:
Este método verifica si JSONFox ya está cargado en _screen.json y, si no, intenta cargarlo desde la ruta configurada en cPath.
ParseJson
Analiza una cadena JSON y la convierte en un objeto VFP.
FUNCTION ParseJson(tcJsonStr as memo) as object
Parámetros:
tcJsonStr
(Memo): Cadena JSON a analizar
Retorna:
Object: Objeto VFP con la estructura del JSON, o .NULL. en caso de error
Ejemplo:
* Analizar una cadena JSON
lcJson = '{"nombre":"Juan","edad":30,"ciudad":"Madrid"}'
loObj = loUtils.ParseJson(lcJson)
? loObj.nombre && "Juan"
? loObj.edad && 30
? loObj.ciudad && "Madrid"
TableToJson
Convierte un cursor o tabla VFP a formato JSON.
FUNCTION TableToJson(tcCursor as string, tbCurrentRow as logical, tnSessionId as integer) as memo
Parámetros:
tcCursor
(Character): Nombre del cursor a convertirtbCurrentRow
(Logical): Si es .T., solo convierte el registro actual; si es .F., convierte todos los registrostnSessionId
(Integer): ID de sesión de datos donde se encuentra el cursor
Retorna:
Memo: Cadena JSON con los datos del cursor
Ejemplo:
* Crear un cursor de ejemplo
CREATE CURSOR clientes (id i, nombre c(30), email c(50))
INSERT INTO clientes VALUES (1, 'Juan Pérez', 'juan@ejemplo.com')
INSERT INTO clientes VALUES (2, 'María López', 'maria@ejemplo.com')
* Convertir todo el cursor a JSON
lcJson = loUtils.TableToJson('clientes', .F., THIS.DataSessionId)
? lcJson
&& [{"id":1,"nombre":"Juan Pérez","email":"juan@ejemplo.com"},
&& {"id":2,"nombre":"María López","email":"maria@ejemplo.com"}]
* Convertir solo el registro actual
GO 2
lcJsonRow = loUtils.TableToJson('clientes', .T., THIS.DataSessionId)
? lcJsonRow
&& {"id":2,"nombre":"María López","email":"maria@ejemplo.com"}
MasterToJSON
Convierte una relación maestro-detalle entre dos cursores a formato JSON.
FUNCTION MasterToJSON(tcMaster as string, tcDetail as string, tcExpr as string, tcDetailAttribute as string, tnSessionID as integer) as memo
Parámetros:
tcMaster
(Character): Nombre del cursor maestrotcDetail
(Character): Nombre del cursor detalletcExpr
(Character): Expresión de relación entre maestro y detalletcDetailAttribute
(Character): Nombre del atributo que contendrá los detalles en el JSONtnSessionID
(Integer): ID de sesión de datos
Retorna:
Memo: Cadena JSON con la estructura maestro-detalle
Ejemplo:
* Crear cursores maestro-detalle
CREATE CURSOR facturas (id i, fecha d, cliente c(30))
CREATE CURSOR items (factura_id i, producto c(30), cantidad i, precio n(10,2))
* Insertar datos de ejemplo
INSERT INTO facturas VALUES (1, DATE(), 'Cliente A')
INSERT INTO facturas VALUES (2, DATE(), 'Cliente B')
INSERT INTO items VALUES (1, 'Producto 1', 2, 100.00)
INSERT INTO items VALUES (1, 'Producto 2', 1, 50.00)
INSERT INTO items VALUES (2, 'Producto 3', 3, 75.00)
* Convertir a JSON con relación maestro-detalle
lcJson = loUtils.MasterToJSON('facturas', 'items', 'facturas.id = items.factura_id', 'items', _VFP.DataSessionId)
* El resultado será un JSON con cada factura y sus items relacionados
Métodos de logging
ApiServiceUtility incluye métodos para registrar eventos a través del objeto Logger configurado. Estos métodos son wrappers que verifican la existencia del logger antes de intentar registrar mensajes.
LogInfo
Registra un mensaje informativo si hay un logger configurado.
PROCEDURE LogInfo(tcMessage, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11)
Parámetros:
tcMessage
(Character): Mensaje a registrar, puede incluir marcadores de formatop2...p11
(Varios): Parámetros opcionales para formatear el mensaje
LogError
Registra un mensaje de error si hay un logger configurado.
PROCEDURE LogError(tcMessage, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11)
Parámetros:
tcMessage
(Character): Mensaje a registrar, puede incluir marcadores de formatop2...p11
(Varios): Parámetros opcionales para formatear el mensaje
LogWarning
Registra un mensaje de advertencia si hay un logger configurado.
PROCEDURE LogWarning(tcMessage, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11)
Parámetros:
tcMessage
(Character): Mensaje a registrar, puede incluir marcadores de formatop2...p11
(Varios): Parámetros opcionales para formatear el mensaje
LogFromException
Registra información de una excepción si hay un logger configurado.
PROCEDURE LogFromException(toException as exception)
Parámetros:
toException
(Exception): Objeto de excepción a registrar
Tabla de tipos MIME soportados
Extensión | Tipo MIME | Categoría |
---|---|---|
application/pdf | Documento | |
xlsx, xls | application/vnd.openxmlformats-officedocument.spreadsheetml.sheet | Hoja de cálculo |
docx, doc | application/vnd.openxmlformats-officedocument.wordprocessingml.document | Documento |
txt | text/plain | Texto |
csv | text/csv | Datos |
zip | application/zip | Archivo comprimido |
jpg, jpeg | image/jpeg | Imagen |
png | image/png | Imagen |
gif | image/gif | Imagen |
svg | image/svg+xml | Imagen |
html, htm | text/html | Documento web |
css | text/css | Estilo web |
js | application/javascript | Script |
json | application/json | Datos |
xml | application/xml | Datos |
mp3 | audio/mpeg | Audio |
mp4 | video/mp4 | Video |
wav | audio/wav | Audio |
avi | video/x-msvideo | Video |
mov | video/quicktime | Video |
rtf | application/rtf | Documento |
ppt, pptx | application/vnd.openxmlformats-officedocument.presentationml.presentation | Presentación |
otros | application/octet-stream | Genérico |
Gestión del Ciclo de Vida de la Aplicación
La clase ApplicationLifecycle
proporciona un mecanismo para gestionar recursos
globales y eventos del ciclo de vida de la aplicación FoxServer. Esta clase hereda de
ApiController
y permite centralizar la inicialización y limpieza de recursos
compartidos.
ApplicationLifecycle
Esta clase actúa como un contenedor para recursos globales y maneja los eventos de inicio y
parada de la aplicación. Utiliza una colección interna (oResources
) para almacenar
y gestionar todos los recursos globales de forma centralizada.
Init
Constructor de la clase que inicializa la colección de recursos globales.
PROCEDURE Init()
Implementación:
PROCEDURE Init()
THIS.oResources = CREATEOBJECT("Collection")
ENDPROC
OnApplicationStarting
Método hook que se ejecuta al iniciar la aplicación. Debe ser implementado en clases derivadas para inicializar recursos globales.
PROCEDURE OnApplicationStarting()
Ejemplo de implementación:
DEFINE CLASS MyAppLifecycle AS ApplicationLifecycle OLEPUBLIC
PROCEDURE OnApplicationStarting()
* Inicializar conexión de base de datos global
LOCAL loConnection
loConnection = CREATEOBJECT("ADODB.Connection")
loConnection.Open("Provider=SQLOLEDB;Server=localhost;Database=MyApp;Trusted_Connection=yes")
* Almacenar como recurso global
THIS.SetResource("DatabaseConnection", loConnection)
* Cargar configuración global
LOCAL loConfig
loConfig = THIS.LoadConfiguration()
THIS.SetResource("AppConfig", loConfig)
* Inicializar cache global
LOCAL loCache
loCache = CREATEOBJECT("ApplicationCache")
THIS.SetResource("Cache", loCache)
THIS.LogInfo("Aplicación iniciada correctamente")
ENDPROC
ENDDEFINE
OnApplicationStopping
Método hook que se ejecuta al detener la aplicación. Debe ser implementado en clases
derivadas para limpiar recursos globales de forma manual o si lo prefiere puede llamar al
método CleanUp
para liberar todos los recursos automáticamente.
PROCEDURE OnApplicationStopping()
Ejemplo de implementación:
PROCEDURE OnApplicationStopping()
* Cerrar conexión de base de datos
LOCAL loConnection
loConnection = THIS.GetResource("DatabaseConnection")
IF !ISNULL(loConnection)
loConnection.Close()
THIS.LogInfo("Conexión de base de datos cerrada")
ENDIF
* Limpiar cache
LOCAL loCache
loCache = THIS.GetResource("Cache")
IF !ISNULL(loCache) AND PEMSTATUS(loCache, "Clear", 5)
loCache.Clear()
THIS.LogInfo("Cache limpiado")
ENDIF
THIS.LogInfo("Aplicación detenida correctamente")
ENDPROC
SetResource
Método protegido para almacenar un recurso global. Reemplaza automáticamente recursos existentes con el mismo nombre, garantizando que siempre se mantenga la versión más reciente.
PROTECTED PROCEDURE SetResource(tcResourceName as string, toReference as variant)
Parámetros:
tcResourceName
(Character): Nombre único del recursotoReference
(Variant): Referencia al objeto o valor a almacenar
Ejemplo:
* En OnApplicationStarting()
LOCAL loConnection
loConnection = CREATEOBJECT("ADODB.Connection")
loConnection.Open("connection_string")
THIS.SetResource("DatabaseConnection", loConnection)
* Actualizar recurso existente
LOCAL loNewConfig
loNewConfig = THIS.LoadUpdatedConfiguration()
THIS.SetResource("AppConfig", loNewConfig) && Reemplaza el anterior
* Almacenar diferentes tipos de recursos
THIS.SetResource("ServerStartTime", DATETIME())
THIS.SetResource("MaxConnections", 100)
THIS.SetResource("DebugMode", .T.)
Comportamiento del SetResource:
- Si el recurso no existe, lo crea nuevo
- Si el recurso ya existe, lo reemplaza completamente
- Acepta cualquier tipo de dato: objetos, valores, referencias
- Es thread-safe para operaciones de escritura
GetResource
Obtiene un recurso global por su nombre. Proporciona acceso seguro a recursos compartidos con validación automática de la colección.
FUNCTION GetResource(tcResourceName as string) AS Variant
Parámetros:
tcResourceName
(Character): Nombre del recurso a obtener
Retorna:
Variant: El recurso solicitado o NULL si no existe o la colección no es válida
Ejemplo de uso:
* Obtener recursos de diferentes tipos
LOCAL loConnection, loConfig, lnMaxConn, llDebug
loConnection = THIS.GetResource("DatabaseConnection")
loConfig = THIS.GetResource("AppConfig")
lnMaxConn = THIS.GetResource("MaxConnections")
llDebug = THIS.GetResource("DebugMode")
* Siempre validar antes de usar
IF !ISNULL(loConnection)
* Usar la conexión...
ELSE
THIS.LogError("Conexión de base de datos no disponible")
ENDIF
* Obtener con valores por defecto
LOCAL lcServerUrl
lcServerUrl = IIF(ISNULL(THIS.GetResource("ServerUrl")), "localhost", THIS.GetResource("ServerUrl"))
IsValidResourceCollection
Método helper protegido que valida si la colección de recursos está correctamente inicializada.
PROTECTED FUNCTION IsValidResourceCollection() AS Boolean
Retorna:
Logical: .T. si la colección es válida, .F. en caso contrario
Uso interno:
FUNCTION GetResource(tcResourceName)
IF !THIS.IsValidResourceCollection()
RETURN NULL
ENDIF
LOCAL i
i = THIS.oResources.GetKey(tcResourceName)
IF i > 0
RETURN THIS.oResources.Item(i)
ENDIF
RETURN NULL
ENDFUNC
GetResourceNames
Obtiene una lista de todos los nombres de recursos almacenados. Útil para debugging y monitoreo del estado de la aplicación.
FUNCTION GetResourceNames() AS String
Retorna:
String: Lista de nombres de recursos separados por comas, o cadena vacía si no hay recursos
Ejemplo:
* Listar todos los recursos disponibles
LOCAL lcResourceList
lcResourceList = THIS.GetResourceNames()
THIS.LogInfo("Recursos disponibles: {0}", lcResourceList)
* Resultado ejemplo: "DatabaseConnection, AppConfig, Cache, ServerStartTime"
* Usar para validaciones de estado
IF EMPTY(lcResourceList)
THIS.LogWarning("No hay recursos globales inicializados")
ENDIF
CleanUp
Limpia todos los recursos almacenados y llama al CleanUp del ApiController padre. No se ejecuta automáticamente al detener la aplicación, debe ser llamado explícitamente a menos que se implemente un manejo personalizado en el método OnApplicationStopping.
PROCEDURE CleanUp()
Implementación completa:
PROCEDURE CleanUp()
DODEFAULT() && Llama al CleanUp del ApiController padre
TRY
IF THIS.IsValidResourceCollection()
* Limpiar recursos individuales si tienen método CleanUp
LOCAL i, loResource
FOR i = 1 TO THIS.oResources.Count
loResource = THIS.oResources.Item(i)
IF VARTYPE(loResource) == 'O' AND !ISNULL(loResource)
IF PEMSTATUS(loResource, "CleanUp", 5)
loResource.CleanUp()
ENDIF
ENDIF
ENDFOR
* Limpiar toda la colección
THIS.oResources.Remove(-1)
THIS.oResources = NULL
ENDIF
CATCH TO loEx
* No propagar errores en cleanup
ENDTRY
ENDPROC
Nota importante:
Este método no se ejecuta automáticamente cuando se libera la instancia. Debe ser llamado explícitamente a menos que se implemente un manejo personalizado en el método OnApplicationStopping.
Integración con ApiController
Los controladores API pueden acceder a los recursos globales a través del método
GetGlobalResource
que está disponible en todos los controladores que hereden de ApiController
:
Ejemplo: Uso de recursos globales en controladores
DEFINE CLASS UsuariosController AS ApiController
FUNCTION GetUsuarios(req, res) HELP "GET: usuarios"
* Obtener conexión global de base de datos
LOCAL loConnection
loConnection = THIS.GetGlobalResource("DatabaseConnection")
IF ISNULL(loConnection)
res.Status(500).Json('{"error":"Base de datos no disponible"}')
RETURN
ENDIF
* Obtener configuración global
LOCAL loConfig
loConfig = THIS.GetGlobalResource("AppConfig")
LOCAL lnPageSize
lnPageSize = IIF(ISNULL(loConfig), 10, loConfig.DefaultPageSize)
* Usar la conexión para consultar datos
LOCAL lcSql, loRecordset
lcSql = "SELECT TOP " + ALLTRIM(STR(lnPageSize)) + " * FROM usuarios"
loRecordset = loConnection.Execute(lcSql)
* Procesar resultados...
LOCAL lcJsonData
lcJsonData = THIS.RecordsetToJson(loRecordset)
res.Status(200).Json(lcJsonData)
ENDFUNC
FUNCTION CreateUsuario(req, res) HELP "POST: usuarios"
* Validar que hay datos JSON
IF !req.HasJsonBody()
res.Status(400).Json('{"error":"Se requiere un cuerpo JSON válido"}')
RETURN
ENDIF
* Obtener recursos globales
LOCAL loConnection, loCache
loConnection = THIS.GetGlobalResource("DatabaseConnection")
loCache = THIS.GetGlobalResource("Cache")
IF ISNULL(loConnection)
res.Status(500).Json('{"error":"Servicio no disponible"}')
RETURN
ENDIF
* Crear usuario...
LOCAL lcSql
lcSql = "INSERT INTO usuarios (nombre, email) VALUES (?, ?)"
loConnection.Execute(lcSql, req.json.nombre, req.json.email)
* Invalidar cache si existe
IF !ISNULL(loCache) AND PEMSTATUS(loCache, "InvalidatePattern", 5)
loCache.InvalidatePattern("usuarios*")
ENDIF
res.Status(201).Json('{"message":"Usuario creado correctamente"}')
ENDFUNC
ENDDEFINE
Ejemplo: Implementación completa de ApplicationLifecycle
DEFINE CLASS MyAppLifecycle AS ApplicationLifecycle OLEPUBLIC
PROCEDURE OnApplicationStarting()
THIS.LogInfo("Iniciando aplicación...")
TRY
* 1. Inicializar conexión de base de datos
THIS.InitializeDatabase()
* 2. Cargar configuración
THIS.LoadApplicationConfig()
* 3. Inicializar cache
THIS.InitializeCache()
* 4. Configurar servicios externos
THIS.InitializeExternalServices()
THIS.LogInfo("Aplicación iniciada correctamente. Recursos: {0}", THIS.GetResourceNames())
CATCH TO loEx
THIS.LogError("Error al iniciar aplicación: {0}", loEx.Message)
THROW loEx
ENDTRY
ENDPROC
PROCEDURE OnApplicationStopping()
THIS.LogInfo("Deteniendo aplicación...")
* Cerrar conexiones de base de datos
LOCAL loConnection
loConnection = THIS.GetResource("DatabaseConnection")
IF !ISNULL(loConnection)
loConnection.Close()
THIS.LogInfo("Conexión de base de datos cerrada")
ENDIF
* Limpiar cache
LOCAL loCache
loCache = THIS.GetResource("Cache")
IF !ISNULL(loCache) AND PEMSTATUS(loCache, "SaveToDisk", 5)
loCache.SaveToDisk()
THIS.LogInfo("Cache guardado en disco")
ENDIF
THIS.LogInfo("Aplicación detenida correctamente")
ENDPROC
PROTECTED PROCEDURE InitializeDatabase()
LOCAL loConnection
loConnection = CREATEOBJECT("ADODB.Connection")
loConnection.Open("Provider=SQLOLEDB;Server=localhost;Database=MyApp;Trusted_Connection=yes")
THIS.SetResource("DatabaseConnection", loConnection)
THIS.LogInfo("Conexión de base de datos inicializada")
ENDPROC
PROTECTED PROCEDURE LoadApplicationConfig()
LOCAL loConfig
loConfig = CREATEOBJECT("Empty")
ADDPROPERTY(loConfig, "DefaultPageSize", 20)
ADDPROPERTY(loConfig, "MaxUploadSize", 10485760) && 10MB
ADDPROPERTY(loConfig, "EnableCache", .T.)
THIS.SetResource("AppConfig", loConfig)
THIS.LogInfo("Configuración de aplicación cargada")
ENDPROC
PROTECTED PROCEDURE InitializeCache()
LOCAL loCache
loCache = CREATEOBJECT("ApplicationCache") && Clase personalizada
THIS.SetResource("Cache", loCache)
THIS.LogInfo("Cache de aplicación inicializado")
ENDPROC
PROTECTED PROCEDURE InitializeExternalServices()
* Configurar servicios externos, APIs, etc.
LOCAL loEmailService
loEmailService = CREATEOBJECT("EmailService")
loEmailService.Configure("smtp.gmail.com", 587)
THIS.SetResource("EmailService", loEmailService)
THIS.LogInfo("Servicios externos configurados")
ENDPROC
ENDDEFINE
Buenas prácticas para ApplicationLifecycle
- Recursos thread-safe: Asegúrese de que los recursos globales sean seguros para acceso concurrente.
- Gestión de errores: Implemente manejo robusto de errores en la inicialización y limpieza.
- Logging detallado: Registre eventos importantes del ciclo de vida para facilitar la depuración y monitoreo.
- Configuración externa: Use archivos de configuración externos para parámetros de conexión y configuraciones.
- Cleanup seguro: Asegúrese de que la limpieza de recursos no genere excepciones que puedan afectar el cierre de la aplicación.
- Inicialización ordenada: Inicialice recursos en orden de dependencia (primero la base de datos, luego servicios que la usen).
- Validación de recursos: Siempre valide que los recursos existan antes de usarlos en los controladores.
- Monitoreo de estado: Use
GetResourceNames()
para monitorear qué recursos están disponibles.