28 days - Phoenix websocket HTTP breakdown
This post is coming right on the heels of a websocket post written yesterday. While that post handled what a channel looks like when Phoenix websockets are used, it doesn’t cover much of how Phoenix operates under the hood.
This topic came up as I was trying to figure out how to put a plug in front of the websocket connection, in order to have custom origin validation code. I was surprised that I cannot put a plug in front of it, as it’s not part of the typical Phoenix path.
Websocket HTTP Request
When a client connects to a websocket server, the initial connection is done as http, followed by protocol switching to ws (or https/wss). This web request does the websocket handshake and ends with a connected websocket. It is possible to make this request yourself using curl (right click websocket request in Chrome and “copy as curl”), although you will end up with a 400 bad request.
As this is just a web request, we can be pretty confident that Phoenix is going to handle
it. My first thought was to figure out what the socket
macro in Phoenix.Endpoint
does.
Phoenix Setup
Phoenix.Endpoint
exposes a socket macro that adds the path and module to a module attribute. This
attribute is exposed as __sockets__/0
on the endpoint implementation.
Next up, we can look at where __sockets__
is referenced in the Phoenix codebase:
I checked my version of cowboy in mix.lock to find out that I’m on cowboy 1. So let’s dive into that code.
The cowboy_handler is where the actual cowboy server gets started up. It’s important to pass in the right dispatches, which execute the web logic. We can see that a custom socket based dispatch is created for every socket defined in the endpoint. Here is what my dispatch looked like for the repo in the previous post:
[
{"/socket/websocket", Phoenix.Endpoint.CowboyWebSocket,
{Phoenix.Transports.WebSocket,
{WebsocketDemoWeb.Endpoint, WebsocketDemoWeb.DemoSocket, :websocket}}}
]
Finally, the CowboyWebSocket
handles the actual websocket request, utilizing the Phoenix.Transports.Websocket
module
to do the heavy lifting of deciding if the connection is allowed or not.
The Transports.Websocket
module is what checks the origin and decides whether the websocket is allowed or
not. All of the code up until this point has been entirely within Phoenix’s framework,
without any clear cut place to hook into. Thus, I came to the conclusion that
I can’t just shimmy some new check_origin
logic into the code path. Ah well,
at least there was good learning.
Thanks for reading the 11th post in my 28 days of Elixir. Keep up through the month of February to see if I can stand subjecting myself to 28 days of straight writing. I am looking for new topics to write about, so please reach out if there’s anything you really want to see!