Specialties: Ruby on Rails Development, Source Control Czar, Ruby Gems Development, Project Manager, Documentation Nag, Jian Shu, Default Linux Sysadmin, Back Flips, C# and Winforms Development, C/C++ HackingView the profile
About the talk
RailsConf 2019 - Unraveling the Cable: How ActionCable works by Christopher Sexton
Rails ships with some handy features, and most of that functionality is there because we have common problems; and those problems need a default solution. You need quick two-way communication between your client and server. Action Cable can solve that quickly, and get you up and productive with out setting up expensive external add-ons and complex integrations.
This magic is wonderful until you need to untangle how websockets, connections, channels, consumers, and clients all fit together. Let's look under the hood and see how everything is woven together.
All right, then we can get started right at 11:40. Hey, I'm Christopher. I'm on Twitter's at Sierra Sexton. I was told it's good to give you a little bit about my about me to make I guess it's more personal connection or something like that to the audience and not just blast people with technical information. Even though I feel like that's showing off and what we're supposed to do is just shoot a lot of technical information at people as much as we possibly can. So I live in DC have a wife and two kids
also have a small dog. I shall make a couple of appearances in this talk on Twitter a service where in I tweet pictures of her in the morning and pretty much post nothing else. I work at a company called radius networks weed to products around proximity and location often with mobile devices and Bluetooth enable things outside of the DC area if that is stuff that's interesting to you or you lie, coyote low-level firmware and Ruby come and talk to me. I'd love to chat also the past couple years. I've been
helping out with a conference call drewbee for good. We have another one coming up here soon in Virginia at the end of July and July 25th to 28 a ruby for good. Org if that's your sort of thing and you like helping Charities and non-profits. Alright the actual talk. So I gave this chocolate at the local DC DC Rock and I wanted things that I failed to do at the beginning was explain like what you should get out of this and what what even action cable is. So I'm not sure the familiar familiar everybody is with
actually cable or websockets, but we're going to step back a little bit and look at it. And then we're going to dig in really deep and then hopefully at the end will kind of kind of come back around to how everything fits together with a little more understanding. So the important thing to know about this is it's not magic. It's not really magic. There's there's tricks to it. And once you learn that you can dig in and figure out how it works. It'll make sense. I remember when I was a kid. I took apart my dad's computer when she was thrilled about
it was an old like 8086 and it had these 7400 series chips all along the side and I member prying them out and looking at it and thinking I will never understand what this does by then went on and got a computer engineering degree and I found out that the 7400 series chips are Justin and agates and Once you understand, what a man gate is you realize it's not that complicated and I thought wait a second. How would I do this in Ruby? And it's not that hard unmanned is
just you know, not and so. Not and then aunt and I just wrote a method that returns it for us and this is basically what those chips do there was only has the whole series of them on the sides of the board. And so digging in and looking under the covers to figure out how things actually work seems more intimidating than it is. And so if this contrived example helps with anything it's to give people the courage to do things like bundle open and dig into the to the source code of rails or whatever ruby gem you're
looking at because it takes a while to trudge through but after you get in there and understand suddenly things can click into place and you'll you'll realize that People wrote this in so as a people you can understand this. All right, like I said before we kind of skipped the background of what is an action cable and I did this once before I'm in that proves to be very confusing for folks. So we use it for things like a digital displays or iot devices getting configuration in sensor data
in and out in and out of different devices. And but when it works out to is everybody makes chat rooms with it. So when I Was preparing for this talk I did what I was supposed to do and I made a chat room. So here is the quintessential example of what action cable is or does? So just run through on a chat room. I opened up two browsers. They're connected to my local real server. And I type in the message and you see as I hit enter the message is kind of pops up. And it works even if I switch over to the
other window and type in the message. They just kind of it just kind of appears. So this is what we're going for. Like how did we get this to work? I don't I didn't want to get into anything fancy or the business the high-level business logic of of what you can do in your app. It's much more of like what the hell is going on under the covers. Where we get to the what let's figure out why we care about how this works. So one of the main reasons you'll hear there's three is we can do a server to client push. So whenever
something happens on the back end or request comes in from somewhere else or somebody has a different browser windows somewhere. If it's not next to you type send a message and hit center and event happens on the server and you can push it down to the other clients. There's this idea of it happening in real time. And I mentioned I was a computer engineering undergrad when I hear this word. It's it makes me a little uneasy there's a lot of connotation that comes along with with using the term real time and there's a specific thing called real-time
Computing and there's that has strict real time constraints that things have to happen within a time frame that is not action cable that is not rails. That's not Ruby even the operating systems that we run these things on a don't follow the real time constraints. That is a whole separate thing. So whenever the The sales team at radius start saying words like real time and it happening instantaneously. I always jump in an in caveat these things but what it does mean is we can get up to the minute or very quick low latency a response times that are faster than something
like pulling every 3 seconds. And then the other thing is overhead and by this I mostly mean bandwidth for a lot of types of traffic something like a websocket connection is much easier on the bandwidth that it consumes. We used it for these iot devices that set up a bunch of sensor data and we ran us all over 3-g modems. And so we were paying the bills for these 3-g modems and realize wait, we're spending a lot of money on bandwidth turns out that something like 80% of the overhead was HTTP headers.
And then the Jason body was you know that last 20% so switching over to something like a websocket made a ton of sense and then saved us a ton of money. All right. How does it work? So we're going to go from the nuts and bolts to the business stuff. We're going to show a lot of dinner in a related Snippets, but I'm always come back to one basic overview. And I know you look at a diagram like this coming in cold and it's a little anxiety inducing there's a lot of stuff going on.
Total side note unrelated turns out that dogs yawn when when they're anxious and uneasy. So this is Hoppers reaction to seeing this diagram. Alright, let's look at our life cycle. So here's our life cycle. We're going to run through this. I'm going to put some code up on on the screen. This is more of reference just to kind of get us an idea. I like putting the concrete code next to the step just so that we can see but we're going to dig in and look at each of these as we go through. So the client connects to a Cable
cable is kind of dis. Generic abstraction as to what is, I kind of the websocket or the controller type thing that it will it will handle this sending information back and forth. If we look at this the one client side, there's a there's a very simple way of saying hey, let's pull in this thing and let's create a consumer that's going to handle this cable. Are the server confirms this connection? It can handle things like making sure that the right person is subscribing to the right things. The
client will subscribed to a specific channels. So the channels are coming in over this connection and it's it's declaring the things that it is interested in. And then the client can send messages up to the server. So this these messages are all posted over that a channel that goes up to the connection that's part of our cable. And finally the server can send the messages back down to the client. So this is the other direction where the server is pushing messages down over the connection on the channel that they're interested in.
So it's part of this. I spent a long time reading and rereading the acting cable guide and one of the things that kind of came out of this was there is all of these different abstractions action cable is handles a lot of things kind of like rails handles the normal like HTTP requests it but puts things into you know, what controllers in views and routes and all of this happens in the action cable World in there is all of the terminology and jargon that that goes into it. I wanted to acknowledge that it's there and we're going to
browser. It doesn't have to be but that's what are default in our chat app that we started with the beginning and we looked I looked into action cabled and Doug to the code and wanted to find out where the web socket was created with a little bit of searching. I found it just calls this website. I could function that native in the browser. But there's some magic going on and so I wanted to figure out what was going on. So I looked in the browser on my trusty Deb tools as web developers like to do. And I noticed that there was an HTTP
request it had headers and responses. And it really look like it was standard HTTP, but it wasn't. It was websockets. And so being from the internet we go and read the RFC. And then we get really confused and not understand with RC is trying to sit understand so that slide down this Rabbit Hole trying to figure out what exactly is going on. So before we can understand how websocket Works, let's step back and just look at just a regular HTTP request. I want kind of go to like a fundamental Atomic level and where and I'm going to treat TCP connections as atomic and we can split those up.
So we're going to think about things that we can do in Ruby and I'll do what we're going to do here as well go down and talk about a socket level in the TCP connections and then kind of build up from there. So the client is going to connect to a server. and establishes a TCP connection and then the client will make a request. The server gets that request. And responds back with the response in Old old-school HTTP one, it would just closed the connection and be done with life. There is a lot of beautiful beautiful
things about this. I do want to make sure that folks understand that when were talking about the socket here. It's like writing to a file when we sent a request. We just wrote some bytes to a file just as if you were writing. Jason R. Markdown or something into a text file on your on your computer and the server kind of did the same thing back to us it wrote a bunch of bites and then through the magic of TCP the operating system gave us those those bites. So that was the abstract how it works with the request. How would we do that in Ruby?
So it's really about a four-line ruby script to do this plus the actual body that we're going to deliver and this body is just a couple of headers for HTTP. The hardest thing to figure out here was that we had to have goofy line endings because of Legacy reasons. And so there was a bunch of carriage returns in addition to our new lines just made my slides kind of ugly. So what it what we did was took this picture code pointed it to my local rails server and watch the logs and once I started out on the bus and got it to work. I
saw 200. Okay what you meant the real server thought. Hey this worked. One of the things that was interesting is I have this one line where I'm going to read the response and I I just ran so I cannot read and put stuff the reply and it's just a bunch of text. So it's just a plain old text file with a headers and empty line and then the HTML body. All right, so it's kind of the basics of how he sees people works. So how do we get from that to a socket? So we go back to our
diagram and we talked about the response at the beginning. But now I want to talk about what's going on on the client as we're upgrading. So before we had this this is what our rubico just did. And before I returned a 200, okay, we need it to return a 101 switch protocol. When we get that response will have this magic conversion of the get response to some sort of bi-directional socket and I thought about this and said, well, that's some magic. So, let's write the Ruby code to make this magic happen. Is the exact same Ruby script however, a different
body that we're going to send over to the server. That's a little more complicated. It has some goofy looking things in here. Like it's not an HTTP protocol at the top. You see a websocket RWS connection and then a few other matters like I want to upgrade to a websocket. But after a little bit of debugging I pointed this at my local real server, and I got a successfully upgraded websocket. The next line was awesome. And then when I went through and I looked at what came out
of the socket. Read I got plain text back and it looks like what it what I needed. We have ran all this and we were to have been able to upgrade it to a socket. And what's going on here is we send some text over and get some text back and then once that's finished and we have read that these headers. We now have a socket connection. That's that's maintained open. So it just doesn't close that connection until the client says we're through. So let's go back to the browser and take a look. And sure enough. What was that coming back with our kind of
simple? Naughty Ruby script we are getting the same thing on the browser and you can see right down here that we got upgrade websocket and the same thing connection was upgraded. So cool. We've got the client to talk to things and upgrade our connection. I we haven't gotten very far. We're just over to the other side of that connection. And so we want to figure out what the server is doing. We went through the real basics of how a client since an HTTP request and so let's go over
quickly how a server would handle that. uses rack most things in the Ruby world use rack to handle http. the nice thing about how this works is you have things like good morning. An environment that gets passed in that has all the context. This will be things that the web server knows and understands it can be things to down from other other parts, like authentication and other environments settings that are being passed in and then practice replies back with us.
Okay. Here's my status code in this case 200 you some headers. I just gave one and then here's the body of of the response and then we run it. So let's let's look at how that actually works. These things are layered on top of each other is kind of middleware, but you can just stack and hook together different rap applications rack layers it in between things in and have the responses go through and what happens is the request comes in from the web server. It makes a call passes in the environment which caused the next thing townhouses in the environment called
the next thing down and then it goes cool and you know, when our case we'd be responding now and then it and then it makes its way back up until eventually the web server gives the response back to the client over the over the network. This is great. But I know we're in here. Does it make sense to have a connection be upgraded? It's very clear you pass parameters in and you and you return but we need something to eat to stop that. So so very clever people came up with this hack called rack. Hijack and it works. It works great
and to go over how this happens. The request comes in it'll call into the rack app. And if it happens to have this thing as part of the environment that's being passed down called a rack. Hijack. It'll it'll invoke that in which case it falls over and establishes a connection effectively throwing away. The rest of what would have happened with the method calls into the different rack. Brock's so How does this work? Define an example of it and threw the code base there was a test
in the thing. There's a lot of stuff going on and most of it doesn't matter. But what is important to know is weight this socket pair returns and I object. Then they sent that to the rack hijack. So then when it calls it later, we now have an aisle object. So as we are going through this request we can just say, oh actually one that connection. We just want to associate these two. I am only two were able to connect the sockets. So one of the things that didn't quite fall into their like stepping through how everything works was was cookies, but it's pretty important. And since
we were talking about how the connection is handed off between HTTP right now. I thought this would this is kind of good timing. So we're going to take a quick detour and look at those. Websockets in action cable doesn't have the same context as the normal HTTP request. However, it has headers and cookies are just HTTP headers if we go and look at our browser we can see the list of the cookies, but we could also go through and see the same cookies, you know, if we were looking at the response body from from our simple Ruby script,
but this is important cuz we need a way to pass information into these connections and we want to be using it in something like this. We're where we are establishing a connection and we need to figure out who is who so in our chat room. We want to make sure that the right people had the right handles so we can do that by passing a cookie in luckily. They should be headers are there while they connection is being established. So we're able to grab them. Topper to remind me to take drink water.
All right, the client subscribe to a channel subscription handshake. We're getting up into a little more the business level logic less of the the fundamental Nuts and Bolts. But we are interested in in our overview is how do we get from the client all the way down to? carretas So what's going to happen is we establish our connection and after it's all done, you know, we've been over all of those types of the upgraded where you got this connection. Those
server says Hey welcome and the client replies with a I want to subscribe to a channel called room Channel. And then the server acknowledges that we confirm your subscription. So I went looked in the browser and under the other tab in Chrome. You can actually click on the bed socket Connection. In this case. It's called cable. You can see all these things happening where you see it as being established the server since down the welcome event. The client demands being subscribed to rooms in the server said sure no problem and then it brings a lot to make sure
their connection stays active. so subscribing to a channel means that we need to get updates for that channel. So that means that we need a way to publish And subscribe things. So would we need to figure out now is how does rails handle that like how they're there isn't anything in the traditional, you know web request that would handle something being updated based off of some sort of event and it solves it by using redis Pub sub. So if we come back to our overview.
We're interested in this part right here. And we want to know how the publish subscribe Cycle Works and this is just a way to centrally the events can go into one one place and it will make sure to notify everything that cares about it about whatever that event happens to me. So the figure out how this works out initially turn to the redice my mind. Which is great. So I've got to terminal windows open. I have one that's going to be my my server when that's going to be my client. They're both just run on my laptop on the server side. I typed in the red a CLA and
then I'm going to type subscribed Channel One. I want to hit enter it comes up and says hey, I'm listening and then just blocks it hangs there. So on the client side we type in redis CLI. And then at the proms we were on publish to channel 1 and we're going to go to a message of hi. As soon as we hit enter. On the server side the terminal updates and says I got a message on Channel 1 said hi. We can just do it again. It happens again and is very Snappy. It happened right away, and it was a great way to kind of see just what's play with redis and figure out how things work
without having to worry about anyting. But being real, if we want to know, how do we do this in Ruby? So two scripts on the left side is the server which will listen and block on. The right side is the client which will which will publish and the way the client publishes is it'll just run once past whatever was passed into it on the command line and then exit. I also tried to make that what kind of look like the Red Sea line? Alright, so we start up our server we were on ribbing.
We're going to subscribe to a channel. We do the same thing. It doesn't actually print out output. So it was just hanging here and now we go over to the client and we're like Ruby and publish High. I'm a server updates right away. I would do the same thing. We say there and then the server updates with their the same thing. It's the message over which channel and the payload that was was given with it. So this is how red is kind of handles these things and it works out to be a really great way for reals to encapsulate a problem, which is
how how can we pull off Pub sub on a web server and have a piece of infrastructure that can just kind of handle all of that and what is his great at that job. And so we're able to just kind of put an instance there and let all of our Ruby code and business logic talk to that and it will handle that like here's the things I need to care about and hear the people that that need to be updated. Are the clients going to send messages to the server? So we're getting even more high-level at this point going back to our diagram. We want to know how stuff is going from the client up
over the socket. So if we want to know how things are going up over the socket, we need to understand the format of how websockets construct their payloads. Pull from the r a c i had a bunch of information. I sat and looked at this ASCII art for a long time and eventually went in kind of broke it down into a much more abstract thing after Consulting a dozen or so different blog post to figure out what was actually happening under the covers. What's nice about this is it's very simple at its core.
It's binary data. And so there is definitely some confusion as to like what exactly is going on and it's harder to poke at and understand but it starts with an opcode and then a length of how long the the date is going to be the client can also send up this mask which isn't all that important. But the thing that was intriguing especially since I was paying been with bills was the overhead per message was like 210 bites that's like a character or two. So when we are comparing that to the HTTP headers that we have had in the past. This is an extremely slim and in lightweight.
She noted that diagram is not to scale. And if we go back to our dub tools in the browser, we can see. Yep. All of these messages are getting getting passed back and forth and the browser even will even let this kind of digging and look at some of the payload values and websocket message construction you can see the timestamps in the sizes in the in addition to all of their kind of the contents of the body. Select the client talking up to the server the
server needs to send things down. I'm so we're going to look real quick at our overview. It's not just from the cable server down. It is also from redis up which is the pub sub but pops up doesn't do anything by itself. So it's the other things telling pubsub to do things. So this could be anything from background jobs finishing and needed needing to announce something or somebody typing in a in a different browser windows somewhere far away, and the message is coming in and being, you know added to a chat room and then sent back out. So we'll look at a
little bit of the high-level code. This is this we're starting to get up to the point where it's in in your rails app itself and we can go through real quick though. The different steps we start with Ash and cable on the back and we can broadcast to a channel in this case. I'm sending two parameters one called message that says hi and then another one called one with the value of one just so we can see that there's multiple parameters coming through that converts at the Jason with an identifier in our in our actual message, Bonnie. And then that Jason gets
We connect to a cable the client talks to the cable server. I'm the server can confirm that connection the client can subscribe to a channel. So we go all the way up to the pub sub component of this. Once were the ones all of that is setup is done. Now. The client is able to send messages up to the server which can then get broadcast out. If they need to be in the server can do the same thing where I can get in other information from anywhere and send it through the pub sub back to the cable server and complete the complete the loop there.
Buy this talk
Access to all the recordings of the event
Buy this video
With ConferenceCast.tv, you get access to our library of the world's best conference talks.