WebSockets con Api Gateway
Tags: aws, websocket, apigateway, cdk
Published: 2024-04-19 07:26:21


¿Alguna vez has querido que tu app hable con tus usuarios en tiempo real, como por arte de magia? Pues bien, ¡hoy es tu día de suerte! En este post vamos a meternos de lleno en cómo darle súper poderes a tus proyectos usando WebSockets con AWS. Vamos a usar dos herramientas muy conocidas dentro del Cloud de AWS: API Gateway y Lambda. Te voy a llevar paso a paso para que crees tu propio websocket en menos de 10 minutos. 

## Crear el proyecto con cdk

Vamos a crear el proyecto con cdk y vamos a implementarlo con python, para ello vamos a crear nuestro directorio del proyecto y crearemos el proyecto con cdk

``` shell
mkdir websocket-poc
cd websocket-poc
cdk init app --language python
```
Ahora activamos el entorno virtual

``` shell
source .venv/bin/activate
```

Y por último instalamos las dependencias necesarias:

``` shell
pip install -r requirements.txt
```

Este sería el contenido de nuestro requirements.txt

```
aws-cdk-lib==2.114.1
constructs>=10.0.0,<11.0.0
```

## Creación de lambda

Usaremos una lambda para enviar un mensaje a través de nuestro websocket, para ello en nuestro proyecto cdk, y dentro de nuestro stack creamos la lambda con el siguiente código:

``` python
lambda_function = lambda_.Function(
            self, 'WebSocketHandler',
            runtime=lambda_.Runtime.PYTHON_3_8,
            handler='ws_function.handler',
            code=lambda_.Code.from_asset('./lambda'),
            timeout=core.Duration.seconds(10)
        )

lambda_function.add_to_role_policy(iam.PolicyStatement(
            actions=["execute-api:ManageConnections"],
            resources=["arn:aws:execute-api:REGION:ACCOUNT-ID:API-ID/STAGE-NAME/*"],
            effect=iam.Effect.ALLOW
        ))
```

**execute-api:ManageConnections** permite a funciones autorizadas gestionar conexiones de clientes en API Gateway WebSockets.


Y este sería el código de la lambda, bastante sencillo, simplemente recibimos los parámetros de conexión y la url del websocket, esto podría ser fijo, pero en caso que tengamos mas de un websocket podríamos re-utilzar el código de la lambda, y por último, enviamos el mensaje "**Hello, WebSocket!**"


``` python
import boto3

def handler(event, context):
    connection_id = event['requestContext']['connectionId']
    gatewayapi = boto3.client("apigatewaymanagementapi",
                              endpoint_url = "https://" + event["requestContext"]["domainName"] + "/" + event["requestContext"]["stage"])

    post_data = "Hello, WebSocket!"

    gatewayapi.post_to_connection(ConnectionId=connection_id, Data=post_data.encode('utf-8'))

    return {
        'statusCode': 200,
        'body': 'Message sent'
    }

```


## Creación de la API

Ahora vamos a crear nuestra API que permitirá el envío de mensajes por websocket.

``` python
api = apigw.WebSocketApi(self, "WebsocketApi")
```

Luego creamos un stage dentro de nuestra api:

``` python
stage = apigw.WebSocketStage(
            self, 'DevStage',
            web_socket_api=api,
            stage_name='dev',
            auto_deploy=True
        )
```

Y por último añadimos una ruta específica (sendMessage" al websocket de la api y lo vinculamos a la función lambda que hemos creado previamente.

``` python
api.add_route("sendMessage",
  integration=WebSocketLambdaIntegration("SendMessageIntegration", lambda_function)
)
```

## Outputs

Necesitamos conocer la url de nuestro websocket para poder establecer una comunicación con el, por lo que hacemos que nos la muestre en el terminal cuando complete el despliegue de la api

```python
core.CfnOutput(self, "WebSocketURL", value=stage.url)
```

## WebScocket client

Ahora vamos a usar una herramienta para simular nuestro cliente de websocket, podríamos crear una web o una app para probarlo, pero esta herramienta nos permite probar nuestro websocket mucho mas rápido y de una forma mas sencilla.

La herramienta es **websocketking** (https://websocketking.com/) y es muy fácil de usar.

Lo primero que debemos hacer es añadir la url de nuestro websocket que nos ha devuelto el deploy de nuestro proyecto cdk. En nuestro caso sería **wss://kiyi70kcll.execute-api.eu-west-1.amazonaws.com/dev** 
y le damos a conectar

![Conectar websocket](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sr0gxtjuhv83jem9c257.png)

una vez conectados enviaremos un mensaje (puede estar vacio),simplemente es la forma mas sencilla de conocer nuestro connectionId que lo necesitaremos luego en el test de la lambda para poder enviar un mensaje a través del websocket.


![Enviar mensaje al ws](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mwycuekv4gztl1yico0q.png)

```json
{
  "message": "Forbidden",
  "connectionId": "WbqM9csoDoECF9w=",
  "requestId": "WbqUVE-OjoEFwjw="
}
```
ahora copiamos nuestro connectionId y creamos un test en la lambda

y ahora enviaremos un mensaje a nuestro websocket con este JSON

```json
{
    "requestContext": {
        "connectionId": "WbqM9csoDoECF9w=",
        "domainName": "kiyi70kcll.execute-api.eu-west-1.amazonaws.com",
        "stage": "dev"
    }
}

```
donde tenemos:

- **connectionId**: obtenido previamente

- **domainName**: url de nuestro websocket

- **stage**: stage de nuestra api


![Lambda test](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zs6tc4e7hmnrr00dctho.png)

y realizamos nuestro test ....

comprobamos que nuestra lambda se ha ejecutado correctamente


![Lambda response](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ygnc3cx92skdzyd1cc59.png)

y comprobamos nuestra aplicación cliente de websocket


![WebSocket output](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rwoa4xx3wiihosop3yx0.png)


Como se puede comprobar en la imagen, nuestra comunicación a través de websocket a funcionado perfectamente :) 

## Precios

Ahora viene el dato delicado, si, muy bonito, pero cuanto me va a costar. Ya sabemos que el coste en AWS (y en el resto de Clouds) es de pago por uso, por lo que antes de implementar te aconsejo que vayas a la web de AWS para consultar los precios:

[AWS Api Gateway Pricing](https://aws.amazon.com/es/api-gateway/pricing/)

O bien también puedes ir a la calculadora de AWS y calcularlo en función de tus métricas

[AWS Princing Calculator](https://calculator.aws/#/)

Te pongo un ejemplo de costes calculados con la calculadora:

- 800 Messages/minutes
- 80 (kb) Average message size
- 20 (seconds) Average connection duration)
- 300 Average connection rate / minute

Y esto hace un total de **121,11 UDS / Mes**

Cabe destacar que existe una capa gratuita los 12 primeros meses:

> _En el caso de las API WebSocket, la capa gratuita de API Gateway incluye un millón de mensajes (enviados o recibidos) y 750 000 minutos de conexión durante un máximo de doce meses._

Dependiendo de cada proyecto puede compensar o no pagar este tipo de soluciones frente a otras que necesiten mas desarrollo. 

## Github

El proyecto lo podéis encontrar en mi repositorio de Github

https://github.com/fjaviermoradiaz/aws-blog-websocket