Working with WebSockets on AWS

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
- Hi everyone. My name's Ryan H. Lewis, and today we're gonna look at a very interesting tutorial. We're gonna look at how to connect from your client to your cloud in AWS with WebSockets, a fairly new technology, something that has been around for a while, but AWS has released some new things recently, and I've learned some new techniques that make this easier than ever for your front-end applications to connect to AWS over the cloud, so a few years ago, I actually created a real-time chat application at a company I was working for, and to do that with AWS, I had to use a thing called IoT. If you haven't used IoT, it's basically Internet of Things. That's what it stands for, and it's used to connect your cloud to many different devices you may have around, like your alarm system, or your thermostat, or all these little IoT devices, and IoT was a really easy way to connect to those things, but it had an extra kind of ability, and the special thing about it was that it used WebSockets to do that connection. With the chat application we were building, we needed to use WebSockets so we would be able to have real-time communication between an agent, and a call center, and a customer using their phone or on the website, and so WebSockets was what we needed. IoT was really the only managed service that AWS provided at the time to do WebSocket communication. We could have run our own EC2 server that would do the WebSocket communication, but then we'd have to deal with ports, and scaling, and it just, it wasn't gonna be a great option, but a couple of years after I had built that, AWS released a new type of API Gateway. Originally, API Gateway was just REST APIs and that's all they had. AWS realized that a lot of people wanted to use API Gateway and kind of the convenience that it provided, but they wanted to use it in different ways, and so WebSockets API was one of the things that they released, allowing you to connect your API Gateway to a WebSockets connection, and then, in the back end, invoke different endpoints. You could do an HTTP endpoint, which could just be an EC2 server that's listening for HTTP connections, or you could use a Lambda, which is actually what makes it really convenient, and so they also released some other API Gateways, like HTTP API, and I think those are the three, and they still kept the REST API. The WebSockets API is what we're gonna be looking at today, so today we are going to be building a WebSockets API. I've already created a pretty simple front end that allows you to connect to that WebSockets API via just vanilla JavaScript, which is pretty cool, and send messages and receive messages. We are gonna be building the WebSockets API, the Lambda function to support it, and changing around some configuration for that Lambda function so that it can send messages to the API, and we're gonna cover all the bases, so basically, from our tutorial today, you can build out a real application with all of these tools, so let's go ahead and get started. The first thing we're gonna do is create a Lambda function. The reason we do this is because when we set up the API Gateway, it likes to already have a Lambda function to connect the WebSockets endpoints to, so we'll go ahead and do that first. I'm gonna call this WebSocket ws-aws. That's fine. This is gonna be super-bare bones. We're gonna write this all in JavaScript so you can keep Node 12.x there, and then we'll create the function. We will be modifying the role later so that it's able to publish messages to our WebSockets API, so that's why we didn't change any of the defaults down here. All right, now it's created, and we are going to use this built-in editor here. It's kind of the easiest way to do things, so the first thing we're gonna do is I always like to keep a log of the event around. The reason why is 'cause these events are, they're large JSON objects, and a lot of times I forget what a property is, how deep it is, things like that, but we're gonna be getting the event, and so essentially, let me map out what this is gonna look like. The API Gateway is going to be receiving WebSocket connections and maintaining a WebSocket connection with your client. It will then, as new WebSocket kinda messages come in, it will handle those differently. It'll send those to Lambda functions or however you have it configured, so our Lambda function is gonna be being invoked every time a message or any type of thing comes in on that WebSocket, and so what we wanna do is we are gonna do different things based on what's called a route, so the route is going to be on the event, and then it's called the requestContext and a routeKey, so this is going to be a couple of things, so when you connect and you disconnect it's going to be a special route key. Also, you can define your own route keys, and as you're sending things from the client, you can specify that route key, and it will invoke things differently. In our case, we're gonna have a single Lambda function. You can have more and configure that in the API Gateway if you want to, but just for simplicity's sake, I just wanna have a single one here. Another thing that you get is the connectionID. We'll use this later, but for now I can go ahead and get it off of here. What the connectionID is, it's a unique identifier for a client that has connected on that WebSocket connection to your API Gateway, so this is how you can have a single Lambda function but have multiple clients connecting to your single API Gateway, and basically have your Lambda function know how to get back to the one who sent a message, essentially, and then what I wanna do is just, I'll set up a simple switch based on the route, so there's gonna be, we can set up three, we could set up four cases right now, so connect is the connection. It means that a client has connected to the WebSocket, and here's a place where, if you were storing those connectionIDs, connecting it to some kind of user, and you needed to send a push notification to a user, you could take this connect, take the connectionID that you get, and store it in a DynamoDB table with other relevant information that then you could retrieve if you needed to send information to a connected client. Here, we'll just say Connection occurred. We don't really have to even save the connectionID here. It's kind of, it's up to you. I'm including these things, like this disconnect here, so that if you wanna build something off of this, you'll know where to plug in more advanced disconnection and connection functionality, so then we've got our two, now, the reason there's a dollar sign before these is because they're kind of, they're not required, but they're like built-in things that happen. They're like things that happen with the WebSocket as it's going. It's not defined by us. Here, I'm going to define a route, and we'll call this message, and the route message will mean that the client has sent a message down and specified this route. This route could be anything: message, hi, hello, data, whatever you want that route to be called. We're calling it message here, and I'm just gonna say Message received. We're gonna add something else to this later, but I'll put that there, and then we can set it, this is just for safety's sake. You can set a default, sorry, you can set a default switch statement here, and you can say Unknown route hit, and you can specify this route up here, so you'll at least know when people are, when people are sending you things that you don't know what it is, and then, this event here, I put message, but it should be event. Now, this Lambda, there's an important thing about this Lambda. For the WebSocket connection to really work, you need to be responding with a HTTP-type object, and so we'll just return a static 200 here. We'll basically say, kinda no matter what, we're gonna say that everything worked okay. When there are errors and things like that, we can handle that in other ways, but for our purposes, we'll go ahead and say, "Hey, statusCode 200. "Everything's cool," and so that means that when somebody makes a connection, the WebSocket will respond and say, "Yes, that connection is good." If you don't return this, or if you return something like a 502, what that tells the client is that the WebSocket connection was a failure, and then it will never really connect, so we can deploy this now, and let's see, that's really all we need to do here. Now we can move into our API Gateway, so here's the API types that you can create. You can see HTTP API, which I mentioned, REST API. I think it looks like this Private REST API might be a new one as well. I haven't actually messed around with it, but WebSocket API is what we want, so let's go ahead and build that, and we'll call it, we'll just call it the same thing, ws-aws, and here is how you define the route, so this is how you define what you are going to be, remember the custom message route that we defined here? This is going to be sent from the client, and it is going to say action. It's gonna be a property on an object sent by the client. It'll say action, and here, if you want to invoke this route, you'll say message. If you wanna invoke a different route, you'll put that as well, but this allows the client to tell the API basically how it wants to be routed, so we'll just leave that as good, and here's the predefined routes: connect, so this is one that we set up, disconnect, this is when a disconnected WebSocket, or a connection is closed by the client, and then default. I've used default before, but I'm not gonna use it here. Here, let's define a custom route, and we'll call this message to match this case right here that we're gonna be looking at. All right, so here's our integration, so this is where we take those routes, the predefined and the user-defined, and we connect them to some type of integration. In our case, we're gonna connect them to a Lambda function, but you can see that you can connect to different types of connection, so our Lambda, we will pick the Lambda that we just created. This is why we created it first. I'll go through, and I'm actually just gonna point it to all the same Lambda, but this is a case where if you wanted to, you could connect it to different Lambda functions for each one of these routes, and honestly, I would probably say that in a production environment that's probably the best way to do it. Relying on a switch like that is not the cleanest way of doing this. If you've worked with API Gateway, it deploys things in stages, so you could have a dev stage and a production stage. We're gonna just go ahead and have a single stage called production. That's totally fine, and then we can review everything. Probably looks good. We'll test it and see if it works, and if it doesn't work, we'll have to go in and change things, but so we have our Lambda function created. We have our API Gateway created, and now we'll tweak a few things to actually try out everything, so here's our API Gateway. You can see that it has the three routes, and you can see that the integrations are all set up to go to this same Lambda function. If you need to modify the route selection expression, you can do it right here. Ours is gonna be on action, and you can create new route keys if you need to. Stages, we have one stage production, and this is where we can get something that we need, which is the URLs, so I'm gonna copy this WebSocket URL here, and what we're gonna do is we're gonna go into our code. Now, this code's available on my GitHub account. You can see the link in the description below, but basically, our client is gonna be a simple index.html, which includes a JavaScript file, and then some JavaScript that is already all set up. We'll copy, we'll paste this URL, and then I'll walk you through this a little bit, so up here where it defines and creates a new WebSocket, the first argument for that WebSocket is the URL endpoint of the WebSocket you're trying to connect to, and so you'll just paste the URL, starting with the wss protocol, and then it'll be the entire API, so let me walk through, if you haven't worked with JavaScript WebSockets, some of this may be new, but basically, you create a new WebSocket. The creation of that on Line 4 actually, let me actually bump this up just a little bit. The creation of this on Line 4 is actually when the connection happens, and so it's connected, essentially, as of here, but then we add event listeners, so when the connection is open, it will run this function. WebSocket is connected. This is just event listeners, kind of like you're used to on the front end. Then we've got, when our WebSocket is closed, when we encounter an error, all of these things are gonna log things to the console. Then we've got when we receive a message, so a message is when something from the WebSocket sends something to our client, and in this case, we're gonna create a little Magic 8-Ball-type thing where it is going to respond with a answer for our query, so it's gonna be on the event that you get. It'll be data, which is gonna be JSON data in a string, and then we parse that and output the message, and here is our function that I attached to the window called ask, where you can say ask, and then you can put in a question, and there's nothing on the back end. It's just responding, but it's a fun thing anyways. Anyways, the payload that we're gonna send is action, so remember, if we look in our routes, the Route Selection Expression has body.action, so the request.body is gonna be this entire payload. It's what we send to the socket. Action is what it's gonna be looking for here, and we say message, which means it's going to route to whatever is set up on the message integration, which is our Lambda function, and then our switch is looking at that route key to actually route it to the correct handler, and so then we've got, we just send it, so we can say window.ask, and it'll send whatever we put into the function, so we've pasted our URL here. That's really all we need to do. I have a little convenient push command, which will copy things up to a S3 bucket in the cloud. I've already created one. You can't use this one. You won't have permissions, but get your, create your own S3 bucket if you wanna follow completely along with this tutorial, so just paste the bucket title there, and then you can see that it's already set up to set the object ACL, and recursively copy everything in the public folder. This lambda.js is the code for the Lambda, the finished Lambda, if you want to use it, but here, I can just say sh push.sh, and it pushes everything up, including the useless Lambda, so here, I'm going to refresh so that I've got a full connection, and boom, right off the bat, it's connecting to the WebSocket, so this is really exciting, so you'll see, at the beginning of this index file, that it creates that new socket. There's no function around this. That JavaScript code is going to run as soon as it gets loaded, and now, do I have an ask function? I do. Is this working? And I don't know. (chuckles) It didn't blow up. We can see if it's working by going to our Lambda function if you go to monitoring and view logs in CloudWatch. We'll wait for it to load. We've got a log stream here, and right here, this message received. It looks like that at first it, well, it says message received, and then it logs, let's see, the whole event, which we actually already logged here, so here's the event, and you can see that the routeKey is set to message, so that worked correctly. Our connectionID is down here in case we wanna use it for later, which we do, and then the body, it says, action message. That's good, msg, is this working? So the ask function worked. That was pretty smooth. As you can imagine, things don't (laughs) work on the first try, so glad to see that this worked, so let's improve this a little bit. Let's make this look a little bit better, so what we're gonna do is we are going to use the AWS SDK to push a message back to the API Gateway, so what we'll do is when they send a message, we'll record the message. Well, we don't give a, we don't care about the message, so we'll record the connectionID, and we will push a message back to the connectionID, so let's do that, so we'll do const AWS equals require aws-sdk, and we can create the API then, so this is gonna be a new AWS.ApiGatewayManagementApi, so this is kind of an interesting thing on AWS. It's not the regular API Gateway, which you would use to create APIs. This is, API Gateway management API, I don't really know why they chose this, but it basically allows you to push up to a WebSockets API is what it does. Takes an object, and what you want is to get the endpoint of the API that it should be sending things to, and so essentially, if you go back to your API Gateway, we can navigate back to these stages. It just wants this. It doesn't need the protocol. It does need the stage, so let's paste that in there. It's got the URL and the stage. That's the entire endpoint. With this, your code will be able to connect to that API and do things to it, so let's do that, then. We're gonna say we've got a function replyToMessage, and we'll have a response. Let me, let's make this async because we're gonna use a promise. We've got the, we need a, we'll have a response, and we need a connectionID, and so with that, we'll be able to, we basically have to encapsulate the data that we're sending back to it, so I'll say that the message that we'll send back is a response, and this data can be whatever you want it to be. It just needs to be kind of like a contract that your client is working with, so then we'll create our trusty little params object. It does need the connectionID so that the API, so that the, it knows which connection on the API that you're trying to send a connection to, and then the data. This wants a buffer, and so we'll say, we'll create it from a JSON.stringify the data object, and that's all for our data. That's all for our params. Now we're going to be returning a promise, and we're gonna say this is our API object we created above, and the function that we're looking for is postToConnection. We pass in our params object that we already built, and we've got a promise, then, so the postToConnection, we'll use the connectionID and post the data to that connection, meaning our client will be getting that back and be doing things with it. Okay, so we've got our function. Now we need to use it, so first we need to get our available answers, so I'll call these options, and it's just gonna be an array. We'll say yes, no, maybe, probably, I probably don't need this many options, but probably not, and then here, a fun one, are you crazy? (chuckles) It could be are you crazy negative or positive? So what we're gonna be doing, the connect and the disconnector find. In the message is where we'll actually wanna be doing something, and we will be calling this replyToMessage function. The first thing is the response, and I shamelessly stole this off online just because I didn't wanna have to think it, but this is the way that we can get a random option from our array, so we'll do Math.random times the options.length, and then we will, that's our first argument, and then we also need to pass in the connectionID, so this is basically looking in the options array. It's getting an index using Math.random, which gives you a decimal, a double, maybe a float, I don't know, (chuckles) a double or a float, and then applying, multiplying that by the length of the array, and then setting that, getting the floor of that and looking for that option, so that index and the option, so that should get us a random option from that array, so we pass that in as our response, and then we have our connectionID, which should get it back to the right place, so let's go ahead and deploy that. We aren't done, but let's deploy that. Then we need to adjust our permissions, so because, and actually, if you want to refresh this screen, I just noticed something was missing. It was there, but it was just missing visually. We didn't have our API Gateway trigger there, so whenever you publish to a connection, whenever you push to the connection post, sorry, post to a connection for WebSockets API, you do need to give your Lambda permissions for that. We have a role here that we can modify, and when we modify this role, we can give it specific permissions to post to our WebSocket API and API Gateway, so let me, let's do that. We'll add an inline policy 'cause we don't already have one created. We do need to choose a service. I missed this last time around. It's actually not API Gateway. It is the ExecuteAPI service, which I didn't even, like, what is that? If you mouse over, it says Amazon API Gateway, so it does know that it's a part of that. I don't know why they created all this separate stuff. You've got a few things: ManageConnections, Invoke, InvalidateCache. I believe what you need is ManageConnections, but you I might also need Invoke, so I'm just gonna give us blanket permissions for this execute API action. That's fine. Now in our resources, we do wanna restrict this only to the WebSocket API that we've got, and we can use this little builder. You can put in the region that it's a part of, the Api id, which is going to, you'll go to your API Gateway, and if you look up here in the breadcrumbs, you'll see there's an API right here. It's also the first part of your WebSocket URL and connection URL, but it's kind of just like a weird little ID that you'll need. Back in here, we can paste it for the Api id. Then the stage will be production. It'll be whatever stage you set, but we did set production. This is where I'm just gonna tell you what it is. The Method is a post because we're posting to the API, and then the specific path is connections, and then we're actually not done. We need to add one more thing. I don't actually know why it didn't give this as an option. You need to add another slash and an asterisk, and I'm sorry that my, this addressing is blocking it, but you'll have a slash asterisk. You can also kind of add to the Api id production here, and that should work, so we'll do Add. Now you have the ARN and the specific resource that you wanna be working with. We can review the policy. We can create the policy. Oh, we have to give it a name, so I'll say ws-aws-apigwy. Create the policy, and it should, the Lambda should have permissions to respond now via the execute API response, and now we can try this out again. We'll say, "Is it working now?" Maybe, okay? (chuckles) Maybe it's working. I think it's working, so let's try a couple other things, window.ask Is this cool? Probably. It's probably cool. Okay. What about me? Am I cool? 'Course not. Probably. Okay, I'll take it. Okay, so let's recap everything that we've done. We have a simple client. That connects WebSockets to our API Gateway, our WebSocket API Gateway, and then we wrote a Lambda to handle any connections to that WebSocket gateway, and then, for the specific route that we specified, it actually sends back a response kind of like the little 8-Balls. I don't know if you remember 'em. You shake 'em and you get an answer, so that's cool. We've got a live WebSocket connection going on here. It's still available. If you look in network, I'm not sure if... Yeah, yeah, here we go. Here's the WebSocket connection, and I believe, yeah, you're able to see the messages, so you're able to see all of the stuff that's actually happening in this browser, which is so cool. It's so cool, so you can take these, and you can do all sorts of crazy things. You can make multiplayer games. You can make chat applications. Anything requiring real-time connection you can do with this WebSockets API and Lambda stuff, and you can do it completely serverlessly, which is so cool, so anyways, thanks so much for watching today. If you liked this video, please give this a like, and consider subscribing to my channel for more stuff. If you try this out and find some new things that you can do with it, I'd love to hear what you have to say in the comments below, and I'll see you next week. (dreamy electronic music)
Info
Channel: Ryan H. Lewis
Views: 10,073
Rating: undefined out of 5
Keywords:
Id: ZuoZpf3JnY8
Channel Id: undefined
Length: 30min 47sec (1847 seconds)
Published: Tue Jan 05 2021
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.