tmbb
PhoenixWS - Websockets over Phoenix Channels
Source code on Github here: GitHub - tmbb/phoenix_ws: Websockets implemented over Phoenix Channels · GitHub
Phoenix channels are a great way of adding real-time features to your web applications. They are implemented over websockets, which are essentially raw TCP sockets with extra steps (like an HTTP request to initialize the connection). Phoenix channels have a lot of advantages over normal websockets, like a reconnection mechanism and authorization features, but they require a specialized client.
Often you would like to use an existing Javascript client which expects a “normal” websocket connection. There is certainly demand for a way of using raw websockets with Phoenix, which seems to be completely impossible. The recommended advice is to drop down to Cowboy, but that is extremely unergonomic and integrates poorly with the rest of your app.
I’ve hit that problem when trying to write a ShareDB backend in Elixir, with communication between server and client happening over Phoenix Channels. The ShareDB client expectes a raw websocket connection or at least a Javascript object that talks like a websocket connection, and Phoenix expects a Phoenix Channel (and by design it can’t handle anything else).
So I’ve decided to implement Websocket-like communication on top of Phoenix Channels (which are themselves implemented on top of websockets). This is a little bit wasteful, in terms of data transmission, but if at your scale this overhead is significant you probably want to reimplement the client yourself.
This library has two parts:
-
A javascript part which defines a Javascript object that implements (most of the?) Websocket API
-
An elixir part which defines a custom Phoenix Channel that talks to said Javascript object
This allows you to do things like this:
// client
import socket from "./socket"
// Import the Javascript class
import PhoenixWS from "./phoenix_ws"
function initializeChat() {
// Build a new PhoenixWS from the phoenix socket
const connection = new PhoenixWS(socket, "room:lobby", {});
// Write the rest of the code as if it were a websocket
}
initializeChat();
Then, define a special channel in your app:
defmodule MyAppWeb.RoomWSChannel do
# Note that this is not a normal Phoenix channel
# but a PhoenixWS.Channel, which can talk to a `PhownixWS` JS object.
use PhoenixWS.Channel
# Join the channel as if it were a Phoenix Channel
def join("room:" <> _, _payload, socket) do
{:ok, socket}
end
# Handle messages from the Client
def phoenix_ws_in(data, socket) do
# This is just an echo server, so we just broadcast the same message
# back into all the clients connected to this topic.
#
# Not that we broadcast with PhoenixWS.broadcast!/2 because
# we are talking to PhoenixWS
PhoenixWS.broadcast!(socket, data)
{:noreply, socket}
end
end
And now you can add this special Channel to your socket:
defmodule MyAppWeb.UserSocket do
use Phoenix.Socket
## Channels
# Add a the (special) channel you've just defined
channel "room:*", MyAppWeb.RoomWSChannel
def connect(_params, socket, _connect_info) do
{:ok, socket}
end
def id(_socket), do: nil
end
You can find a demo here: GitHub - tmbb/phwocket_example: An example using PhoenixWS · GitHub
Because I haven’t published PhoenixWS on hex yet, it’s a little hard to add PhoenixWS’s javascript as a dependency (you can’t get it from /deps/.../phoenix_ws) as phoenix does to get its own javascript. In the project above I’ve just copied the relevant file into the JS directory.
I haven’t published anything on hex yet, because everything is still untested (I’ve only done some basic manual tests). I welcome help on how to setup some integration tests that test compatibility between PhoenixWS and raw Websockets, especially on the client.
Trending in Announcing
Other Trending Topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #library
- #deployment
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #elixirconf
- #channels
- #exunit
- #discussion
- #code-sync
- #podcasts
- #javascript
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixirconf-us
- #blog-post
- #ai
- #elixir-ls
- #phoenix_html
- #iex
- #graphql
- #genstage
- #websockets
- #supervisor
- #advent-of-code
- #distillery
- #processes
- #api
- #forms
- #hex
- #security
- #metaprogramming










Showing Posts 1 to 10- Show Best Posts
- Show All (oldest first)
- Show All (newest first)
josevalim
For completeness, Phoenix also supports direct WebSockets usage via implementing your own
@behaviour Phoenix.Socket.Transportinstead ofuse Phoenix.Socket. It was added in v1.4.tmbb
I think I’ve only lçooked into transports before Phoenix 1.3 (and at that time it looked like it would be nearly impossible to implement my own, becuase everything looked really complex).
Is the “new”
Phoenix.Socket.Transportable to interact with a normal JSWebSocketobject (like a cowboy websocket does)? If so, then my library is useless, right? Even if you want to multiplex messages to several clients connected to the same “channel” (like I do in my example above), I can just usePubSubto broadcast messages to several sockets and implement my custom version of channels without doing silly stuff like implementing websockets on top of channels…PS: it can still be useful if you want things like automatic reconnection, though.
acrolink
@tmbb I have no answer to your questions but even if parts of what your library does can be done using built in components, you have done a great work. I am sure you have gained valuable experience and knowledge in Elixir which would help you to continue to contribute to this great community.
tmbb
To answer my own question: Yes,
Phoenix.Socket.Transportworks perfectly with a normal websocket, with no need for weird custom client-side javascript objects. On the other hand, I’m finding it a bit hard to authenticate and authorize users without being very disruptive on the client. When using websockets on top of Phoenix channels I get a lot for free, like the ability to authenticate the user using tokens in the initial connection and intelligent handling of reconnections.This is very useful for my current project, which is writing a ShareDB backend for Phoenix and Elixir - ShareDB doesn’t seem to provide any tools for authentication, authorization and reconnects. Those things must be handled with Websocket shims (like the one I’ve written that works on top of Phoenix channels), and although foregoing Phoenix channels could allow for a more lightweight solution in terms of data transmission, I’m not sure it would be a net gain. So maybe my library (and the idea behind it) is not so useless after all.
tmbb
There are some serious compatibility errors due to some misunderstandings on my part about how channel replies work. I’ll fix it as soon as possible
samuelventura
I guess what is lacking is official support for a raw websocket approach with docu, samples, and a Phoenix.SocketTest utility. Something with the simplicity of the current controllers for cases where channels is overkill. Testing a custom Phoenix.Socket.Transport has some mismatches with the available Phoenix.ChannelTest.
josevalim
That’s because Phoenix.ChannelTest tests the channel directly (i.e. the test becomes your transport). Once you control the transport itself, you will have to bring your own testing abstraction.
samuelventura
What is the rationale for jumping from standard controllers to pubsub channels and leaving nothing in the middle? Not enough man power? Not enough users requesting such features? Raw websockets are not useful/requested/hype enough? The use case is not clear? Honest question… It looks to me current committers are a few modules away from giving full support to that use case.
samuelventura
My wish list for oficial almost raw websocket support is so far:
This is useful for:
I agree that a couple of use cases may not justify the effort to implement and maintain but it would be nice to have out of the box.
vegabook
I strongly second this request, as I have a need to aggregate REST APIs, do some maths on their data, and then send the results to a websocket client (Node Red), which doesn’t know Phoenix Channels but does know websockets. Happy to pony up some patreon (say 25 bucks a month for a year - I know, I’m a cheapskate but I don’t know Phoenix nor the http protocol stack well enough to do this myself) to get this done. If a few others are open to contributing some dough maybe this gets done quicker?