A simple service to broadcast messages across all of connected sessions using AWS WebSocket API.
- Install dependencies and deploy.
yarn # Install dependencies
SERVICE_NAME=broadcaster yarn deploy # Deploy it into AWS
- Test using
wscat
.
$ wscat -c "your-endpoint"
connected (press CTRL+C to quit)
> hi there
< {"data":"hi there","_now":1561016329388,"_me":true}
> {"type":"act","payload":"left"}
< {"type":"act","payload":"left","_now":1561016357649,"_me":true}
>
Now, it is time to decide that we build up something special such as a small game or service for proof of concept. There is a very boring job for this. That is a job to add a server to communicate between each clients.
When we need to communicate a very simple message such as a position of character and a small chat message, we should write server codes to bind, accept a connection from a client, manage a list of connections and broadcast a message to all of them. And we should run a new server, deploy it, watch its health and use our energy to make it operate as normal. Moreover, its cost can be high if we didn't shutdown properly.
We don't want it even if we need it. So we prepare a very simple broadcast backend without management.
We build up very simple broadcast backend using AWS WebSocket API and Amazon DynamoDB.
We don't want to concern about this backend in management and costs manner. We want to pay as only we go and keep it simple than a container like Docker.
When we prepare this backend, AWS WebSocketAPI
is the only serverless solution that supports WebSocket properly.
It is built on many of AWS components.
- It uses
API Gateway
andLambda
for WebSocket and broadcasting logic. - It uses
DynamoDB
to manage all of connectionIds. - It uses
CloudWatch
to write all console logs to it. - It uses
CloudFormation
and IAM to deploy this stack and manage proper roles.
For convenience, the system adds _me
and _now
when it forwards the message.
_me
istrue
only if a receiver is a sender._now
is a unix timestamp(milliseconds) that reached at the backend.
If you send a plain text, it will be placed at .data
.
{
"data": "a text you sent",
"_me": true,
"_now": 156101632938
}
If you send a json object, _me
and _now
will be added into that object.
{
// ...yourObject
"_me": true,
"_now": 156101632938
}
- It uses
NodeJS 8.10
andServerless framework
. - It manages some secure variables via environment variable using direnv. Please see
.envrc.example
file. - It is written by
TypeScript
andVisual Studio Code
.
First, please set AWS credentials properly.
And then, set .envrc
file properly using .envrc.example
file. Of course, that file should be sourced by direnv
or your direct command like source .envrc
.
All things are ready. Just type like this.
yarn deploy
If you don't want to use direnv
and .envrc
, you can use SERVICE_NAME
environment variable instead. But this name is referenced in both of the name of CloudFormation stack and the name of DynamoDB Table, so you should set this value very carefully. Both of naming rules are different, for example, CloudFormation allows -
character but DynamoDB doesn't. I recommend this way if you want to use only one word as a service name.
SERVICE_NAME=broadcaster yarn deploy
After deployment, or if you run yarn sls info
command, you can retrieve the information of deployed backend like this.
...omitted...
api keys:
None
endpoints:
wss://0000000000.execute-api.aws-region-code.amazonaws.com/your-stage
functions:
...omitted...
You can test the WebSocket easily using wscat.
npm i -g wscat
wscat -c "wss://0000000000.execute-api.aws-region-code.amazonaws.com/your-stage"
And send a plain text or a json.
$ wscat -c "your-endpoint"
connected (press CTRL+C to quit)
> hi there
< {"data":"hi there","_now":1561016329388,"_me":true}
> {"type":"act","payload":"left"}
< {"type":"act","payload":"left","_now":1561016357649,"_me":true}
>
It is written by NodeJS 8 so please use NodeJS 8.1x runtime. Otherwise, for example, if you use NodeJS 10 runtime on your development environment you can see a stacktrace like below one while developing or deploying.
npm ls -prod -json -depth=1 failed with code 1
internal/modules/cjs/loader.js:584
throw err;
^
Error: Cannot find module '../lib/utils/unsupported.js'
at Function.Module._resolveFilename (internal/modules/cjs/loader.js:582:15)
at Function.Module._load (internal/modules/cjs/loader.js:508:25)
If you give the Administrator privileges to your AWS account, it will be never happened but if not so, you can see some error messages like below due to insufficient AWS privileges.
not authorized to perform: cloudformation:DescribeStacks on resource: arn:aws:cloudformation:aws-region:account-id:stack/your-stack-name/*
not authorized to perform: logs:DescribeLogGroups on resource: arn:aws:logs:aws-region:account-id:log-group::log-stream: (Service: AWSLogs; Status Code: 400; Error Code: AccessDeniedException; Request ID: guid).
Please check your AWS profile has proper privileges for these systems. This IAM role can help you. It is tough but smaller than Administrator.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DeployServerlessWithDynamoDB",
"Effect": "Allow",
"Action": [
"iam:*",
"apigateway:*",
"cloudwatch:*",
"logs:*",
"lambda:*",
"dynamodb:*",
"cloudformation:*"
],
"Resource": "*"
}
]
}
If you fail to deploy the system, CloudFormation performs a rollback to the last deployed system so that the system will function normally. That stage is UPDATE_ROLLBACK_COMPLETE_CLEANUP_IN_PROGRESS
. You have to wait for this step to finish. After that, you can deploy new one normally.
Stack:arn:aws:cloudformation:aws-region:account-id:stack/your-stack-name/event-id is in UPDATE_ROLLBACK_COMPLETE_CLEANUP_IN_PROGRESS state and can not be updated.
This is really no function because it focuses on simplicity. If you like this and you want a bound to broadcast, like topic, please check message-topic
, too.
MIT