The Back-Story
I’ve been writing Phoenix applications for about four months now and really enjoy it so far; however, I’ve been stuck working mostly on boring web APIs and haven’t had a chance to build anything that is more rich and interactive with a specific user application in mind. That’s all changed though as I decided to beef up on my front-end skills a bit and work on a pet project I’ve had cooking in the noodle for awhile now.
What are Channels?
Chances are, if you’ve heard about Phoenix you’ve also heard people brag up the “Channel” system that ships with it. It gives you a great way to send real-time updates to the browser and doesn’t require a crazy amount of hardware to do it either! If you’re familiar with MVC then you can think of a channel as being a controller that maintains persistence and a constant socket open with the browser.
What does that mean though? Ask any web developer and they’ll be able to tell you about the life-cycle of a web request, which at it’s heart is stateless. This means every request you make to a web server requires it to build up state every time you hop to a new page. The overhead to build that up can be pretty incredible. With channels you’re able to store the state and keep it around for any requests that happen.
Show Me Some Code Already
Let’s peel back what is going on with these channels by snooping through some of
the code from a fresh install. First you’ll want to direct your attention to
lib/project_name/endpoint.ex
. This is the starting code base for a request,
and right away one of the things we find is this (assuming your application is
named MyApp):
|
If you’re worked with Plug
routing this should should feel pretty similar.
What’s going on here is any requests to /socket
are being handled by the
MyApp.UserSocket
module. Let’s crack that open next and take a peek!
|
Quite a bit is going on here, luckily a lot of it is being handled for us with
the use Phoenix.Socket
statement. This is where your web socket connection
becomes a channel. Think of this module as being a initial starting point and
router for which the channel specific protocol takes shape. The channel
macro
wires up “topics” to more specific modules. I like to think of topics as web
routes. The *
in the channel does what you would expect and allows anything
to match at that location.
Next up is the transport, I don’t know much about this; but my assumption is
this specifies the underpinnings of how to actually talk with the web client.
Based on the commented out option of :longpoll
it looks like this would
support older clients that don’t have web socket support. There are some
libraries out there that use a long polling ajax request to simulate websockets.
The comments do a pretty great job of explaining the rest of what’s going on here! But what would a channel look like? Here is the code for a channel that I’m working on at the moment:
|
If you’ve worked with GenServer
some of this is going to look eerily the same.
This is as much experience as I’ve had so far so I’ll let the code do most of
the talking for now… This post has gone on a bit long so we’ll wrap up here
for now and dive in again in a second post with how to test this stuff, stay
tuned!