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:

__sockets__ usage from Github
There are only 2 users of the sockets function

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,
    {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!

View other posts tagged: engineering elixir 28 days of elixir