Procesamiento de archivos en tiempo real con Deno y WebSockets
Con el auge de las aplicaciones en tiempo real, los desarrolladores buscan constantemente formas eficientes de manejar flujos de datos en vivo y procesar archivos sobre la marcha. Deno, un entorno de ejecución moderno para JavaScript y TypeScript, ofrece un soporte robusto de WebSocket y funciones de seguridad mejoradas que lo convierten en una excelente opción para crear sistemas seguros de procesamiento de archivos en tiempo real.
En este DevTip, exploraremos cómo aprovechar las capacidades de WebSocket de Deno y su modelo seguro de permisos para implementar un servidor WebSocket en tiempo real. Este patrón puede servir como base para tareas de procesamiento de archivos en tiempo real, por ejemplo, monitorear y procesar datos de archivos, como parte de un ecosistema de aplicaciones más amplio.
¿Por qué Deno?
Deno ofrece un entorno de ejecución seguro que incluye soporte para TypeScript, un sistema de permisos integrado y un entorno aislado (sandbox). Estas características ayudan a garantizar que las operaciones, incluidas las tareas de procesamiento de archivos, se ejecuten de forma segura sin exponer recursos del sistema innecesarios. A diferencia de Node.js, Deno se centra en la seguridad y la simplicidad, y te da más control sobre el acceso a la red y al sistema de archivos.
Configurar un servidor WebSocket
Crea un archivo nuevo llamado server.ts y agrega el siguiente código:
Deno.serve((req) => {
if (req.headers.get('upgrade') !== 'websocket') {
return new Response(null, { status: 501 })
}
const { socket, response } = Deno.upgradeWebSocket(req)
socket.addEventListener('open', () => {
console.log('A client connected!')
})
socket.addEventListener('message', (event) => {
if (event.data === 'ping') {
// In a real application, you might trigger file processing here
socket.send('pong')
}
})
socket.addEventListener('error', (event) => {
console.error('WebSocket error:', event)
})
socket.addEventListener('close', () => {
console.log('A client disconnected')
})
return response
})
Ejecutar el servidor
Para iniciar el servidor, ejecuta:
deno run --allow-net server.ts
Comprender el ciclo de vida de WebSocket
Las conexiones WebSocket pasan por varios estados:
- CONNECTING (0): El estado inicial mientras se establece la conexión.
- OPEN (1): La conexión está activa y lista para transferir datos.
- CLOSING (2): La conexión está en proceso de cierre.
- CLOSED (3): La conexión ha terminado.
Por ejemplo, puedes comprobar si un socket está listo antes de enviar un mensaje:
function isSocketReady(socket: WebSocket): boolean {
return socket.readyState === WebSocket.OPEN
}
socket.onmessage = (event) => {
if (!isSocketReady(socket)) {
return
}
// Handle message
}
Implementación del cliente
A continuación se muestra un ejemplo de cliente simplificado para probar el servidor:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>WebSocket Test</title>
</head>
<body>
<script>
const ws = new WebSocket('ws://localhost:8000')
ws.onopen = () => {
console.log('Connected to the server')
ws.send('ping')
}
ws.onmessage = (event) => {
console.log('Received:', event.data)
}
ws.onerror = (error) => {
console.error('WebSocket error:', error)
}
ws.onclose = () => {
console.log('Disconnected from the server')
}
</script>
</body>
</html>
Mejores prácticas de seguridad
Cuando construyas aplicaciones en tiempo real, especialmente las que implican procesamiento de archivos, considera estas medidas de seguridad:
1. Validación de entradas
Asegúrate de que los datos entrantes tengan el formato correcto para evitar procesar entradas
inesperadas. Estos fragmentos de seguridad ilustran comprobaciones para integrar en el manejador de
mensajes de tu servidor; el servidor de ping/pong anterior no las aplica automáticamente. Si un
mensaje hace referencia a un archivo, acepta un ID de archivo autorizado que esté asignado a una
ruta controlada por el servidor. Nunca pases un nombre de archivo o una ruta no confiables
directamente a las API de archivos de Deno, y limita cualquier permiso --allow-read o --allow-write al directorio requerido.
function validateMessage(data: unknown): boolean {
if (typeof data !== 'string') {
return false
}
try {
const message = JSON.parse(data)
return typeof message === 'object' && message !== null && !Array.isArray(message)
&& typeof message.type === 'string'
} catch {
return false
}
}
2. Limitación de tasa
Integra un limitador de tasa para restringir los mensajes de aplicación aceptados. El limitador en
memoria ilustrativo que aparece a continuación funciona por proceso; en un servicio de larga
duración, haz que expiren las entradas de clientes inactivos, limita el total de conexiones y usa
una identidad autenticada o un ID de conexión asignado por el servidor para clientId.
class RateLimiter {
private requests = new Map<string, number[]>()
private readonly limit: number
private readonly window: number
constructor(limit = 100, window = 60000) {
this.limit = limit
this.window = window
}
isAllowed(clientId: string): boolean {
const now = Date.now()
const timestamps = this.requests.get(clientId) || []
const recent = timestamps.filter((time) => now - time < this.window)
if (recent.length >= this.limit) {
return false
}
recent.push(now)
this.requests.set(clientId, recent)
return true
}
}
3. Límites de tamaño de los mensajes
Comprueba la longitud en bytes UTF-8 de los mensajes de texto aceptados. La longitud de una cadena en JavaScript cuenta unidades de código UTF-16, por lo que no es un conteo de bytes. Esta comprobación se ejecuta después de que WebSocket ha almacenado en búfer y entregado el mensaje, y codificar la cadena reserva otro búfer. Limita el trabajo posterior de la aplicación, no el almacenamiento en búfer de la red ni el pico de memoria. La API de WebSocket carece de contrapresión; usa un servidor o una puerta de enlace con límites explícitos de mensajes de WebSocket, límites de conexiones y límites de tasa para acotar el tráfico no confiable antes del procesamiento en la aplicación.
Coloca este manejador dentro de Deno.serve, donde el socket actualizado está dentro del ámbito:
const MAX_MESSAGE_SIZE = 1024 * 1024 // 1 MiB of UTF-8 text
const encoder = new TextEncoder()
socket.addEventListener('message', (event) => {
if (typeof event.data !== 'string') {
socket.close(1003, 'Only text messages are accepted')
return
}
if (encoder.encode(event.data).byteLength > MAX_MESSAGE_SIZE) {
socket.close(1009, 'Message size exceeds limit')
return
}
// Process message
})
Conclusión
El soporte integrado de WebSocket en Deno y su sólido modelo de seguridad lo convierten en una herramienta potente para desarrollar aplicaciones en tiempo real, incluidas aquellas que procesan archivos de forma dinámica. Al aplicar las mejores prácticas descritas en esta guía, puedes construir sistemas seguros y eficientes capaces de manejar datos en tiempo real y operaciones con archivos.
En Transloadit, reconocemos el valor de la comunicación segura en tiempo real. Explora nuestro servicio de Filtrado de archivos para ver cómo el manejo avanzado de archivos puede complementar tus aplicaciones en tiempo real.
