Reconnecting websockets on AWS
Tags: aws, serverless, dynamodb, lambda
Published: 2023-10-09 01:11:32

AWS API Gateway supports websockets.

Unfortunately, their service does not provide the ability to persist connection data and reconnect on flaky internet sessions. Nor could I find any example projects with those features. In this article, I will explore a [potential solution using Lambda and DynamoDB](https://github.com/MengLinMaker/websocket-lambda).





---
# Firstly, how do API Gateway websockets work?

![AWS websocket chat application example](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3vy6avivo5z7l2q7ho3h.png)

AWS uses routes to execute different actions. Consider a chat application workflow, assuming Lambda is used for compute:

1. Client connects to websocket, firing the `$connect` route and associated Lambda.

2. Client sends JSON payload `{action: 'sendmessage'}`, firing the `sendmessage` route.

3. Server can send data to client by specifying a `socketUrl` with `ConnectionId`.

4. If client sends JSON payload without action, the `$default` route is fired.

5. Client disconnects, firing the `$disconnect` route.



### Limitations with API Gateway websocket:

- Every time the `$connect` route is called, a new `ConnectionId` is created. To persist connection on flaky internet, the client must store an ID.

- Lambda is ephemeral, so a database like DynamoDB is required to persist connection data — connection data shouldn’t be stored in memory in case of failure.

- The 10 minute connection timeout can be avoided with a ping/pong request.

- Max websocket duration of 2 hours, which would require a new connection session.






---
# A solution to reconnecting websockets on AWS

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ygffam779qvrlj5uuag5.png)

This solution uses custom actions instead of `$connect` and `$disconnect` to manage connections, which shifts connection management to the client instead of AWS. DynamoDB will persist connection data and provide an event-driven architecture for returning messages as well as interfacing with external services.



### Case 1: Ideal connection

1. After opening a websocket connection, the client will send an empty socketId to the `open` route, which generates a new socketId for the client. The socketUrl, ID and currentId are stored in DynamoDB.

2. Client calls external service “ping”, providing `socketUrl` and socketId. These details are used to update the DynamoDB table.

3. Updating DynamoDB will trigger the `message` Lambda to post any stored messages in the updated row to the client.

4. Client is responsible for triggering the `close` route, which will delete the associated row in DynamoDB.

5. If client fails to close the connection, a [TTL (time to live)](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/howitworks-ttl.html) data deletion timer can be specified for Dynamo
DB.



### Case 2: Flaky connection

If the internet connection is poor, the websocket connection may close on the client side.

1. When the client is offline, new messages will be stored in DynamoDB.

2. A “back online” event listener on the client will create a new websocket connection.

3. Client will send an existing socketId to the `open` route, updating DynamoDB with the new `ConnectionID`. This triggers an event to send the last message back to the client.



### Case 3: Long connection
An under 10 minute disconnect and connect interval will solve this issue.





---
# Limitations to consider

- DynamoDB stream adds latency for message updates to client.
- Connection data will not persist if client is refreshed. Also, storing socketId in local storage is not ideal when multiple tabs are used.