Welcome to the Websocket-Lab. The purpose of this lab is to introduce yourself to the AWS Websocket on Amazon API Gateway, released on December 18, 2018. (To read more about the release please visit here.
Note: If this is being viewed in a .md file, opening a new tab on click is not supported with target="_blank"
, when clicking a link please do Command + Click
to open a link in a new tab
Please refer below for a short description of the services used in this lab:
-
IAM Roles and Policies: AWS offers users the AWS Identity and Access Management (IAM) service to users as a way to enhance security among the products that they use. The IAM roles and policies are used in this lab to help limit the amount of access some of our products can have to other products. To read more please visit: <a href="https://aws.amazon.com/iam/"target="_blank">here.
-
AWS Lambda: The cornerstone product in AWS offerings of server-less products. This product is the backbone of our computing and business logic for our application. Lambda functions, are triggered by an event and when invoked execute a block of code. In this lab Lambda is used as a receiver for when we send messages to our Websocket, and then handle those messages accordingly. To read more please visit: here.
-
Amazon DynamoDB: AWS' trademark NoSQL database. DynamoDB is used in this lab to keep track of session information as users interact with the Chat room. To read more please visit: here.
-
Amazon APIGateway: APIGateway is used in this lab as the supplier of a websocket for our application. The APIGatway websocket configuration is setup as routes. Routes are pathways for messages to travel. For example if there is a route called "message" when a message is sent to the APIGatway that containts the action as "message" that route will be taken. It is important to note that APIGateway websocket has three defualt routes: $connect, $disconnect, and $default. These routes are triggered when a socket is opened, closed, and have no other matching routes respectively. To read more please visit: here.
-
Cloud9: Cloud9 it will be your "development environment". To know more about Cloud9, including pricing, click here. Cloud9 provides free tier.
-
On the programming side: We are using raw unadultarated javascript and html for the browser and User Interface. And for the lambda function we are using node.js version 10.
In order to complete this lab you are required to have access to the following:
- IAM
- DynamoDB
- Lambda
- API Gateway
In this lab we are going to create 3 microservices. These microservices manage and operate a functional chat room.
- There is the client-records table stored in DynamoDB. This stores the connection info for all of the users.
- There are a few lambda functions. Each have their own respective action.
- Connect: this function is used to store the connection ID in DynamoDB
- Send Message: This function is used to send messages to everyone in the chat room.
- Disconnect This function cleans up any session data that was related to the user that disconnected.
There are three lambda Funtions for this lab to show that once orientated with the WebSocket API and the microservice pattern, you can then easily deploy new services with ease.
- Login to your account
- Select a region (take note of the region) - We recommend us-east-1 (Virginia) or us-east-2 (Ohio)
Note: Be sure that you have permissions to create resources on your account. For the purpose of this workshop, having administrative privileges is the best option.
- On the AWS console, go to Cloud9.
- Go to the Cloud9 section of the console
- Select Create environment
- Give a name to your environment. Important: If you are sharing the same account and region with a colleague, be sure to take note of the identification of your environment, and be careful to not to destroy your colleague environment.
- For the "environment settings":
- For "Environment type" choose
Create a new instance for environment (EC2)
- For "Instance type" choose
t2.micro (1 GiB RAM + 1 vCPU)*
- Leave the other configuration settings at their default values and click Next step, and then Create environment
- For "Environment type" choose
In a few seconds your environment will be available. You can close the Welcome tab.
Down on your Cloud9 console, a terminal is available. Go to the terminal and clone this repository. This repository contains the source code.
$ git clone https://github.com/cjhillbrand/websocket-sandbox.git
Before we begin with tasks: note that from this point forward any and all resources will be referenced as [Prefix][Resource]
- Prefix: We include this prefix so incase there are multiple people working on one account there won't be any collisions. This will be a unique string of characters that you choose. A simple and short prefix is the best choice. For example,
ENV01
,R2D2
,C3PO
are all appropiate choices of prefixes, it is the easiest and simplest to have your prefix be all UPPERCASE. - Resource: This is the given resource name that the documentation gives it.
- Navigate to the
/resources
directory:
$ cd websocket-sandbox/resources
- Run the shell script
deployLambdas.sh
$ source deployLambdas.sh [Prefix]
If everything went well you should see a message like the following:
Successfully created/updated stack - [Prefix]-PreLab-WebSocket-Stack
Here are a few notes about this shell script:
- The [Prefix] is the name we chose right after we cloned the repository in the previous task.
- The Shell script uses the Serverless Application Model (or SAM for short) to deploy some preliminary resources:
- A dynamoDB table named
[Prefix]client-records
this stores connection info for our Chat Room. - Three lambda functions that take care of connecting to the WebSocket, sending messages through the WebSocket, and disconnecting from the WebSocket. We will use these functions when we create our WebSocket
- If you want to learn how to launch these manually, the extended lab covers everything we automated here.
- The script also created a S3 bucket that stores the code for the lambda functions.
- A dynamoDB table named
Note: To skip this task refer to the FastFix at the bottom of this Task
- Navigate to the API Gateway console here.
- Depending on the state of your account you can find a Create API or Get Started Button. Click on the one that you see and you are going to be taken to a create API page.
- Press the WebSocket radio button for Choose the Protocol.
- For API name put,
[Prefix]Chatroom-WebSocket
(Take note of Casing) - For Route Selection Expression enter
$request.body.action
Note: If you want to learn more about routes and what they do click on the Learn More button next to the input box - For Description enter, WebSocket for a Chatroom web page.
- Click Create API
- Go to the IAM dashboard here.
- Click on Roles
- Click on Create Role
- Choose API Gateway as the resource and then scroll to the bottom and press next.
- Click on Next: Tags and then Next: Review
- For role name enter,
[Prefix]WebSocketAPIRole
, then click Create. (Make sure the Prefix is all caps) - Confirm the Role was made by clicking on it.
- Now we need to give this role permission to Invoke our lambda functions
- Click Attach Policies
- Search
AWSLambdaRole
and select it. - Click, Attach Policy
- Copy the Role ARN or keep this info handy. We will be using it very shortly.
- Head back to the Dashboard of your WebSocket
- If you are not already, navigate to the routes page.
- In the box New Route Key enter,
dispatch
, and click the checkmark to the right of the box.
While still on the page thats titled Provide information about the target backend that this route will call and whether the incoming payload should be modified. Do the following:
- Make sure that the Lambda Function radio is pressed and Use Lambda Proxy Integration is pressed.
- For Lambda function enter
[Prefix]SendMessage
. This field should suggest a lambda function you have already made. - Enter in the API Gateway ARN Role we made two steps ago.
- Cick Use Default Timeout
- Press Save and click Ok for any pop-ups.
- Repeat Step 4 for the $connect and $disconnect, but with their respective lambda functions.
- Click on the Actions dropdown, and select Deploy API
- For the Deployment Stage enter click on [New Stage]
- For Stage Name enter, production
- You can leave the descriptions blank, or enter what you like.
- Press Deploy
- Keep track of the WebSocket URL this is used in our local code.
- Create the Role for the API. (Task 2; Step 2) We need to do this because Cloud9 does not have the permission to manipulate IAM roles from the CLI.
- Make sure that you have ran
deployLambdas.sh
and you have finished the previous step, then you can run:
source FastFixSimpleLab.sh [Prefix]
Note: You will still have to complete Task 3 and Task 4 since that is out of the scope of the CLI
-
Navigate to the IAM dashboard here.
-
Click on Roles
-
Find the Role name
[Prefix]-PreLab-WebSocket-Stack-DispatchRole
-
Click on it
- Click Add inline policy
- Click JSON
- Copy and paste the file
role.json
which can be foundwebsocket-sandbox/resources/aws-utils/DispatchLambda/role.json
- Replace
<Your WebSocket ARN/*>
with your WebSocket ARNClick me to find out how to find your WebSocket ARN
0. If you did the Fast Fix for Task 2, the ARN is located in your terminal.- Navigate to your WebSocket that you created in the previous task.
- Click on the $connect route.
- Under Route Request you should see a Field labeled ARN copy and paste this so it takes the following form:
arn:aws:execute-api:{region}:{account ID}:{API ID}/*
- press Review Policy
Note: you may receive an error when reviewing the polciy. You can ignore this and move forward
- For name enter, Custom-Inline-Policy
- Press Create Policy
Note: This task is completed using a Google Chrome Browser, and the lab has only been tested using Google Chrome and Firefox please be aware that some performance issues may arise if using other browsers than these.
- On your Cloud9 Environment navigate to: websocket-sandbox/chatroom/awsconfig.js
- Under the AWS_CONFIG for the key websocket enter the Websocket URL. This can be found by going to our WebSocket on API Gateway and clicking Stages and then development (If you did the FastFix in Task 2, the URL will be in the terminal)
-
On a browser on your Cloud9 Environment open
websocket-sandbox/chatroom/index.html
-
Preview this file in Cloud9 by pressing the Preview button located on a toolbar next to Run.
-
Click the icon located on the top right of the preview of the web page. If you drag your mouse over it a message will read Pop out into new window click this.
-
You can copy the URL in the browser and open multiple tabs to simulate multiple people in the ChatRoom.
-
The moment we have been waiting for, sending and receiving messages.
- Since this is the basic chatroom, we did not introduce the ability for custom names, or the creation of joining chatrooms. If you are interested in doing this take a look at the Extended Lab which walks through how to do this. To make sure that our UI knows we are in a Simple Mode:
- enter no-rooms in the Please Enter a Name text box.
- Click Register Note: if you have multiple windows open, do this step and the previous for each window before moving forward
- The Submit button under the message text box should now be enabled. If not refresh the page, enter no-rooms and try again.
- Enter any message in the text box labeled Type a Message here
- Press Submit under the text box.
- We should receive the message we just sent. If you have multiple windows open check to see if the message in one window was sent to all other windows.
- Look at the code in the SendMessage lambda and see where our message traveled and how it got transformed. Look ecspecially at this snippet of code in the SendMessage Lambda:
for (let connectionId of connectionData) { try { await apigwManagementApi.postToConnection({ ConnectionId: connectionId, Data: message}).promise(); } catch (e) { if (e.statusCode == 410) { await db.delete({TableName: TABLE_CR, Key: { ID: connectionId }}).promise(); } } }
Note: the
e.statusCode == 410
what does the code 410 mean? I recommend looking at the documentation and doing a deeper dive and do some investigating! - Since this is the basic chatroom, we did not introduce the ability for custom names, or the creation of joining chatrooms. If you are interested in doing this take a look at the Extended Lab which walks through how to do this. To make sure that our UI knows we are in a Simple Mode:
-
If this lab is being completed in conjunction with other individuals and you want to try and send messages to eachother all you have to do is change the awsconfig.js. Change it to:
const AWS_CONFIG = {
"websocket": "<websocket of your partner>",
}
now everyone should have the same websocket URL. Congratulations you are done with this lab! Have fun and send messages to eachother!
The purpose of this extended portion is:
- introduce more functionality into our chatroom.
- Demonstrate the ease at which you can build more microservice on top of the WebSocket.
Note: This lab must be done once the first lab is done
-
Visit the DynamoDB console
-
Click on the Create Table button.
-
At this stage:
- Enter
[Prefix]room-messages-users
for table name. - Enter room for the primary key.
- Leave the rest as the default.
- Press Create
- Enter
This is going to start spinning up our table.
Function Name | Location of Source | Location of Role | Permissions | Route |
---|---|---|---|---|
[Prefix]RegisterUser |
Source Link | Link to Role | DynamoDB | register |
[Prefix]CreateRoom |
Source Link | Link to Role | DynamoDB WebSocket | new-room |
[Prefix]JoinRoom |
Source Link | Link to Role | DynamoDB WebSocket | join-room |
[Prefix]LeaveRoom |
Source Link | Link to Role | DynamoDB WebSocket | leave-room |
Navigate to the Lambda Dashboard
For each Lambda Function above:
-
Click on the button create a function If the button is not visible, check on the console for a menu on the left, with the label functions, and then hit on the Create Function button.
-
Select Author From Scratch
-
Under the section Basic Information:
- For Function name put the function name listed in the table above.
- For Runtime, select the latest supported version for Node.js
- For Permissions, we need to give the lambda function the required permissions we have listed above.
- Click on Choose or create an execution role. This unfolds a section where we can trigger the creation of the role for our lambda function.
- For execution Role, select Create new role from AWS policy templates. This will expand two fields below the section:
- For the Role Name input the [Prefix] + function-name + Role, so for example the Connect function's role would be [Prefix]ConnectRole.
- Do not select anything for the Policy template field.
- Click the Create Function button on the bottom of the right hand side of the page. You should now be taken to the dashboard of that lambda function.
-
Scroll down to where you see the function code. It looks like a code editor and should have one open tab with the file
index.js
-
Copy and paste the code for each function (located in the table above) into the editor.
-
Set up your environment variable:
- Scroll past the code editor to the Environment Variables panel.
- For Key, put
TABLE_CR
- For Value, put
[Prefix]Client-records
- Add another Environment Variable.
- For Key, put
TABLE_RMU
- For Value, put
[Prefix]room-messages-users
-
Press Save
Take some time and read over the code for each function. They either perform some action to DynamoDB or send a message via the websocket. Moving forward this fundamental understanding of the code helps understand how our chatroom deals with the information given to it.
Note: For Lambda functions that need permissions to our WebSocket, we need to first receive the ARN for our WebSocket before attaching the permission. With this in mind the one function that needs this permission requires us to complete this step after we have created the Websocket
FOR EACH ONE OF YOUR LAMBDA FUNCTIONS DO THE FOLLOWING
-
Scroll down on the page of your lambda function and find the section Execution Role
-
Check to see if there is a link to the role that you created previously. Open that link in another tab of your browser.
-
We are now at the IAM Access and Policy page for our Role. If we have done everything correctly thus far regarding the permissions there should be one policy, AWSLambdaBasicExecutionRole.
-
Click on the + Add inline policy button located to the right and midway down the page.
-
Click on the JSON Tab. This is where we can add in our custom permissions to our role.
-
Paste the policy (role.json located in the table above) in each respective functions folder to the editor.
-
Replace the <ARN> with the ARN for the designated resource.
Finding DynamoDB ARN
- Go to the DynamoDB console
- Click on Tables and choose [Prefix]client-records
- In the Overview section the very last Key will be the Amazon Resource Name (ARN).
Finding API Gateway WebSocket ARN
- Got to the API Gateway Console
- Click on your WebSocket.
- Select any route.
- Under the box labeled Route Request there will be an ARN, but it will only be SPECIFIC TO THAT ROUTE.
- To make the ARN general enough copy and paste up until, but not including the route name. An example of this ARN would look like this:
arn:aws:execute-api:{region}:{account ID}:{API ID}/*
-
For the name put, Custom-Inline-Policy (since these are inline policies and only in the scope of each respective role there will not be any naming overlap)
- For the lambda function
[Prefix]Disconnect
and[Prefx]Dispatch
navigate back to the lambda functions' dashboard. - Scroll down to the Environment Variables section.
- Add another Environment variable:
- For Key put, TABLE_RMU
- For Value put,
[Prefix]room-messages-users
- Press Save in the top right corner.
- Navigate to the API Gateway Dashboard and click on your websocket, chatroom-websocket.
- Click on the routes tab on the sidebar. Do the following for each new route: 3. Create a new route with the route name in the table above 4. When you're done creating the route make sure to click the Add Integration Response to each one. This allows us to return a JSON object from our function back to the local client.
- When you have created 4 new routes and configured their lambda response correctly, click on Actions
- Finally click Deploy API
- For stage name, choose Development
- For description choose whatever you like.
- Click Deploy
- Run (Make sure [Prefix] is all caps):
source FastFixExtended.sh [Prefix]
That's it! Now your chatroom has full functional chatrooms and user names!
- Delete the role associated with the Send Message Role.
- Delete the WebSocket deployed on API Gateway.
- Go to Cloud formation and delete any stack associated with this lab.
- Delete your Cloud9 environment
Note: The below instructions only deploy the Simple stack, comparable to the infrastructure set up in the Simple Lab. If you want to deploy the full stack replace the file template-simple.yml to template-full.yml in STEP 4
Note: the Deploying with SAM CLI section is designed to be done independently from the Simple/Extended Lab you will replicate resources if you execute this
- Make sure you have the aws sam cli downloaded, if you are using Cloud9 SAM CLI is already available to use.
- Navigate, in your terminal to the directory
websocket-sandbox/resources/
- create an s3 bucket by running the command
aws s3 mb s3://<bucketName>
- Run the following commmand: (Replace template-simple.yml to template-full.yml to deploy the full stack)
sam package --template-file template-simple.yml --output-template-file packaged.yaml --s3-bucket <bucketName>
Note: Make sure the bucket names in the above two commands are the exact same
- Finally run:
sam deploy --template-file ./packaged.yaml --stack-name <custom stack name> --capabilities CAPABILITY_IAM
-
Navigate to the CloudFormation dashboard here.
-
Click on the Outputs tab.
-
Copy the Websocket URL
-
On Cloud 9 Environment:
- Go to awsconfig.js
- Paste the URL from cloudformation into the field websocket
-
Open index.html in your browser.
Note: If you deployed both template files there will be two distinct CloudFormation stacks that must be deleted!!!
- Run the command:
aws cloudformation delete-stack --stack-name <Your Stack Name>
- Delete the S3 bucket that you created:
Note the
--force
command will delete the bucket even if there is versioning, or there are objects in the bucket. DO NOT RUN THE COMMAND if you use this bucket for anything outside of this Lab
aws s3 rb s3://<bucketName> --force
Q. How can we federate who accesses the websocket?
A. Using a combination of Cognito and IAM Permissions/Groups we can federate who can access the websocket URL and we can actually give different access to different users! If we wanted one person to have access to send a message and another group not to, this can all be done with Cognito and IAM. Read more about Cognito here.
Q. I understand that I accessed my chatroom file on my local machine, but I want anyone on the internet to access it, how can I do this?
A. There are definitely plenty of ways! In the spirit of the lab there is a serverless approach that uses several different services:
- Route 53: This is AWS' DNS resolver, and also domain registrar.
- CloudFront: This is AWS' CDN, utilizing edge capabilities we can deliver our chatroom to users accross different regions and still give them a reliable performance (or even better...?).
- S3: Place all of our static content here! This is a cheap, easy, and secure way to store our files. S3 also has website hosting capabilities.
This lab was inspired by the release notes done by Chris Munns. To visit the page visit here