He decidido explorar el uso de la tecnología WebSockets para aplicaciones que requieren el envío de mensajes iniciados desde el servidor a clientes web mediante conexiones de persistentes de dos vías.
Estuve explorando diversas opciones. Es interesante que Apache y los sistemas de hospedaje compartido clásicos (tipo CPanel) no incluyan opciones para el uso de esta tecnología.
Explorando encontré que recientemente Amazon agregó WebSockets a su oferta de servicios en la nube. Me pareció particularmente sencillo de aprender, así que acá les comparto un proyecto inicial.
En Resumen
Este proyecto consiste en implementar un sistema de votación tipo poker-planning de forma que el resultado de la votación sea distribuido a todos los participantes en el momento en que todos hayan terminado de votar.
Para esto cada participante debe iniciar una conexión bidireccional con un servidor de WebSockets en un tópico común (partida). Uno de los participantes (dealer) enviará un mensaje de inicio de votación que todos los demás participantes recibirán. Cada uno responderá a dicho mensaje con su voto. Una vez que todos los participantes hayan votado, el sistema enviará un único mensaje a todos los participantes con el resultado de la votación.
El siguiente diagrama ilustra la diferencia entre el modelo convencional HTTP «solicitud/respuesta» y el modelo bi-direccional de WebSocket. Mientras que en el modelo HTTP solamente el cliente inicia enviando mensajes de solicitud al servidor y este responde, en el modelo WebSocket, el cliente realiza una solicitud inicial para una conexión bi-direccional, a la que el servidor responde y a partir de ahí ya sea el cliente o el servidor pueden tomar iniciativa y enviar mensajes a su contraparte.
HTTP:
CLIENT =========== REQUEST ==========> SERVER
<========== RESPONSE ==========
WS:
CLIENT ======== REQUEST UPGRADE ======> SERVER
<======= UPGRADE RESPONSE ======
<========== MESSAGES ==========>
<========== MESSAGES ==========>
<========== MESSAGES ==========>
Los servicios principales a utilizar de parte de AWS son:
- API Gateway (Modo WebSockets)
- Funciones Lambda
- DynamoDB
Nuestro itinerario será:
- Crear Lambda’s base (connect, disconnect, default)
- Crear un Rol para la ejecución de Lambda’s
- Crear un API Gateway de tipo WebSocket
- Utilizaremos
wscat
para interactuar con nuestro WebSocket API - Crear las rutas especializadas para el juego de poker planning
- Crear una partida
- Unirse a una partida
- Crear Votación
- Votar
- Cerrar votación
Lambda’s Base y Rol de Ejecución
Nuestro servidor WebSockets es en realidad una implementación del servicio API Gateway de AWS que utiliza el protocolo para WebSocket.
Cada implementación de dicho protocolo en AWS presenta tres rutas opcionales predeterminadas (connect, disconnect, default) las cuales estarán «alambradas» a una función lambda de AWS cada una que ejecutará la lógica que queramos. Estas lambdas serán accesibles para el API Gateway mediante un rol de acceso que definimos a continuación.
Creando un Rol
Antes que nada vamos a crear un Rol (Role) que defina el acceso a la ejecución de funciones LAMBDA. Este rol lo usaremos posteriormente para dar acceso a la ejecución de lambdas de nuestro sistema.
Para esto vamos a acceder al servicio de Gestión de Acceso e Identidades (IAM), y ahí vamos a «Roles». Se puede acceder fácilmente a este servicio buscando la palabra «IAM» en la casilla de búsqueda.
En la página de gestión de roles, hacemos clic en el botón [Create Role] donde elegimos el tipo «AWS Service» y el servicio «Lambda» para luego dar clic en [Next: Permissions]
En la página del paso 2, adjuntar permisos, usamos la casilla de búsqueda para ubicar el permiso «AWSLambdaExecute», seleccionamos la casilla correspondiente.
El tercer paso permite de forma opcional agregar etiquetas (tags) con llave/valor para describir de forma más precisa el rol y poder administrarlo de mejor manera en un escenario donde haya muchos roles. Para nuestro caso podemos dejar el rol sin etiquetas y hacer clic en [Next: Review] para pasar al cuarto paso.
En este cuarto paso debemos darle un nombre a nuestro rol. Yo ne nombré «pokerface_role» y hacemos clic en [Create Role]
De vuelta en la página de gestión de roles, nuestro nuevo rol aparece en la lista.
Creando las Funciones Lambda predeterminadas
En AWS, el término Funciones Lambda se refiere al servicio que consiste en definir pequeños pedazos de código con un ambiente común que podemos ejecutar de diferentes formas. En nuestro caso las ejecutaremos para cada solicitud a nuestro servicio API Gateway.
Ahora vamos a crear las lambdas predeterminadas que usaremos luego en nuestro servicio API Gateway para las diferentes rutas predeterminadas de WebSockets:
Ruta | Función Lambda |
$connect | pokerface_lfn_connect |
$disconnect | pokerface_lfn_disconnect |
$default | pokerface_lfn_default |
En la consola de administración de AWS, buscamos el servicio «Lambda» y hacemos clic en el botón [Create function]
Seguimos la creación «Author from Scratch» y le damos nombre a nuestro nuevo LAMBDA (ejemplo: «pokerface_lfn_connect»)
Escojemos el tipo de ambiente «Runtime» que puede ser Node.js 12.x (o también Python 3.8.
Hacemos clic en la flecha junto a «Change default execution role» para seleccionar «Use an existing role» donde elegimos nuestro rol «pokerface_role»
Finalmente hacemos clic en [Create Function] abajo para crear un nuevo Lambda.
Dejaremos estas funciones con el código predeterminado. Ejemplo en NodeJS:
exports.handler = async (event) => {
// TODO implement
const response = {
statusCode: 200,
body: JSON.stringify('Hello from Lambda!'),
};
return response;
};
Repetimos este paso para cada una de las 3 funciones que definimos en nuestra tabla (pokerface_lfn_connect, pokerface_lfn_disconnect, pokerface_lfn_default)
Una vez creadas nuestro rol y nuestras funciones Lambda, podremos proceder a la creación de nuestro API Gateway de WebSocket.
Creación de nuestro API Gateway
El servicio API Gateway de AWS permite la implementación de rutas (URL’s) por medio de las cuales un sistema en la nube puede hacer llamados o conexiones a otros servicios definidos en AWS. Por ejemplo, podemos usar la url «https://mi-api-gateway-service.aws.com» para pasar datos a una función LAMBDA que realizará alguna operación de base de datos en nuestro sistema.
Recordemos que el término «API» representa una Interfaz de Programación de Aplicaciones (Application Programming Interface). Es decir definimos una interface para acceder a la programación de nuestra aplicación.
Las instancias API Gateway pueden ser de diversos tipos, por ejemplo HTTP API, WebSocket API, REST API o REST API Privada.
Creando una API Gateway de tipo Websocket
Lo primero que haremos es buscar el servicio API Gateway en nuestra consola de administración de AWS.
Hacemos clic en el botón [Create API]
En el paso para seleccionar un tipo de API, usamos «WebSocket API»
Al hacer clic en [Build] se nos presenta un proceso de 5 pasos para crear nuestro API Gateway. Iniciamos dándole un nombre apropiado como por ejemplo «pokerface_wss» en alusión a nuestro sistema pokerface y que se trata del protocolo web sockets secure. En la casilla de la «expresión de selección de ruta» usamos request.body.action
para indicar que usaremos el cuerpo de la solicitud y una propiedad «action» para definir diferentes rutas. (luego explicaremos esto con más detalle)
Nos saltaremos el proceso automatizado al hacer clic en [Create blank API]
Al hacer clic en [Create blank API] saltamos en el proceso al paso 5 donde revisamos todas las demás etapas (vacías) y hacemos clic en [Create]
Esto nos lleva a la página donde podemos configurar nuestra nueva API
Se nos presentan tes rutas predeterminadas ($connect, $disconnect y $default) que nos servirán para alambrar nuestra API a las lambdas que creamos anteriormente.
Conectando las Rutas con las Lambdas
En este punto vamos a conectar nuestras rutas predeterminadas con nuestras lambdas.
En la página de administración de nuestro API, hacemos clic en la ruta $connect, lo cual nos muestra el tipo de integración que esa ruta tendrá con nuestro ecosistema AWS. Seleccionamos el tipo «Lambda function», y buscamos el lambda correspondiente pokerface_lfn_connect
como se describe a continuación.
En este acto el sistema nos advierte que estamos dándole permiso a nuestra ruta $connect de ejecutar nuestra lambda pokerface_lfn_connect
Repetimos este paso para las rutas $disconnec
y $default
pero, en esta última agregamos una «Integración de respuesta» haciendo clic en el botón [Add Integration Response]. Este último paso permitirá emitir una respuesta desde el Lambda hacia nuestro cliente.
Una vez que tenemos nuestras tres lambdas básicas alambradas a las tres rutas predeterminadas de nuestro API, podemos desplegar ese API para que sea accesible desde la nube.
Para esto hacemos clic en Actions > «Deploy API»
Por ser la primera vez, elegimos un nuevo escenario (New Stage) al que llamaremos «dev» y hacemos clic en el botón [Deploy]
Nótese que además de desplegar el API en el ambiente dev
también configuramos un «throttling» o límite de acceso bastante bajo (10 mensajes por segundo) para nuestras pruebas. Hacemos clic en [Save Changes] y listo.
Los enlaces descritos arriba nos permitirán acceder al websocket API desde la nube.
WSCAT: Probando 123
WSCAT es un módulo utilitario de NodeJS que habilita una especie de terminal para la interacción con WebSockets. En este tutorial asumimos que ya tienes NodeJS instalado en tu computador (puedes ver acá) y que puedes instalar WSCAT usando el comando npm
de la siguiente forma:
npm install wscat -g
Una vez instalado bastará con ejecutar en tu computador el siguiente comando para conectarte a tu API:
wscat -c url_al_api
Donde url_al_api es el URL que obtuviste en el paso de despliegue del API en el campo WebSocket URL.
En mi caso sería así:
wscat -c wss://7dnsj7f3ed.execute-api.us-west-2.amazonaws.com/dev
Esto nos abre un programa interactivo de consola que nos permite escribir mensajes que serán enviados a nuestro API de WebSocket.
Personalizando la respuesta predeterminada
Vimos en el paso anterior que la respuesta fue «Hello from Lambda» lo cual corresponde a la respuesta del código predeterminado de nuestro LAMBDA.
Vayamos a la lambda correspondiente y cambiemos un poco el código:
Una vez que desplegamos este lambda con el botón [Deploy] al enviar un mensaje desde wscat la respuesta cambia de forma significativa.
Generando mensajes desde el servidor
Hasta ahora hemos podido demostrar una conexión solicitud/respuesta desde wscat en nuestro computador a nuestro ecosistema de servicios en la nube en AWS.
Ahora haremos lo que HTTP no puede hacer: enviar mensajes desde el servidor sin que haya una solicitud del cliente de por medio.
Lo primero que haremos es facultar a nuestras lambdas para emitir mensajes a los clientes de WebSocket API’s
Para esto actualizaremos nuestro rol para agregar el permiso de AmazonAPIGatewayInvokeFullAccess
Lambda de Prueba
Para esto crearemos una nueva función Lambda con el siguiente código en python:
import json import boto3 def lambda_handler(event, context): connectionId = "VAMOS A PONER EL CONEXION ID ACÁ" URL = "https://ew16tuqah7.execute-api.us-west-2.amazonaws.com/dev" client = boto3.client("apigatewaymanagementapi", endpoint_url = URL); msg = "HELLO FROM SERVER"; response = client.post_to_connection(ConnectionId=connectionId,Data=json.dumps(msg)) return { 'statusCode': 200, 'body': json.dumps('Hello from Lambda!') }
En esta ocasión usamos Python por facilidad con la librería boto3 que permite contactar de forma muy sencilla a nuestros clientes.
Nótese que en la línea 5 vamos a «hard-codear» una ID de conexión que tomaremos de wscat más tarde.
Vamos al servicio de funciones Lambda y hacemos click en [Create Function]
Le damos un nombre a la función «pokerface_lfn_prueba_servidor» y elegimos el ambiente Python 3.8, elegimos nuestro rol de ejecución como «pokerface_role» y hacemos clic en [Create Function]
Colocamos nuestro código y hacemos clic en [Deploy]
Ahora vamos a ejecutar WSCAT y oibtener nuestro ID de conexión:
Colocamos ese ID en nuestra lambda en la línea 5 y hacemos clic en Deploy.
Ahora de vuelta en nuestra lambda podemos ejecutar una prueba usando el botón TEST.
Rutas Especializadas
En la 2da Parte de nuestro tutorial estaremos discutiendo las rutas especializadas que nos permitirán implementar el juego de Planning Poker.
Referencias
5,285 total views, 3 views today
Comentarios