tmbb

tmbb

PhoenixWS - Websockets over Phoenix Channels

PhoenixWS - Websockets over Phoenix Channels

Source code on Github here: https://github.com/tmbb/phoenix_ws

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: https://github.com/tmbb/phwocket_example

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.

Most Liked

josevalim

josevalim

Creator of Elixir

For completeness, Phoenix also supports direct WebSockets usage via implementing your own @behaviour Phoenix.Socket.Transport instead of use Phoenix.Socket. It was added in v1.4. :slight_smile:

13
Post #2
tmbb

tmbb

To answer my own question: Yes, Phoenix.Socket.Transport works 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.

samuelventura

samuelventura

My wish list for oficial almost raw websocket support is so far:

  1. Phoenix JS client to expose raw send/onMessage methods with implicit json serializer. Phoenix js client advantages over native websocket:
  • Included fallback to long polling
  • Params passed on connect are auto encoded to URL
  • Heartbeat and auto-reconnection
  1. Test macros to bootstrap the transport based websocket (mostly to replicate all the config and params passed to connect)
  2. Code generator for a new transport websocket and transport websocket test
  3. Transport websocket with implicit JSON serializer
  4. Some form of assigns
  5. Maybe parametric routing similar to controller

This is useful for:

  • Implementing an API / RPC. An stateful API may look over kill, but imagine the server side process is a proxy to some upstream API whose data flow I need to trim/curate based on some dynamic state.
  • Implementing some form of basic view-less remote shared state (say server side Vue state with mutations over this transport). This is my current goal and seems to be the goal of tmbb/ShareDB as well.

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

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?

acrolink

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.

Where Next?

Popular in Libraries Top

kevinlang
Hey all, We have made an Ecto3 Adapter for SQLite3, ecto_sqlite3! We have successfully on-boarded the full suite of integration tests (...
New
treble37
Just looking for a little feedback on a tiny helper library I built - Sometimes I find the need to convert maps with atom keys to maps...
New
praveenperera
FastRSS Parse RSS feeds very quickly: This is rust NIF built using rustler Uses the RSS rust crate to do the actual RSS parsing Speed...
New
New
riverrun
I’ve just released version 3 of Comeonin, a password hashing library. The following small changes have been made: changes to the NIF c...
New
zorbash
I created Kitto a framework for dashboards inspired by Dashing. [demo] The distributed characteristics of Elixir and the low memory foo...
New
Crowdhailer
Experimenting with this code. OK.try do user &lt;- fetch_user(1) cart &lt;- fetch_cart(1) order = checkout(cart, user) save_or...
New
mtrudel
Bandit is an HTTP server for Plug and WebSock apps. Bandit is written entirely in Elixir and is built atop Thousand Island. It can serve...
New
anshuman23
Hello all, I have been working on my proposed project called Tensorflex as part of Google Summer of Code 2018.. Tensorflex can be used f...
New
ostinelli
Let’s write a database! Well not really, but I think it’s a little sad that there doesn’t seem to be a simple in-memory distributed KV da...
New

Other popular topics Top

sorentwo
Hello! tl;dr Announcing Oban, an Ecto based job processing library with a focus on reliability and historical observability. After spen...
977 41022 311
New
yawaramin
In the Dialyzer docs ( http://erlang.org/doc/man/dialyzer.html#requesting-or-suppressing-warnings-in-source-files ), there is a way to tu...
New
sergio_101
I am VERY much an elixir newbie. I have taken one elixir course and one phoenix course on Udemy. During that course, I saw the instructor...
New
grych
Hi folks, Few months ago I have announced the proof-of-concept of the library to manipulate the browsers DOM objects directly from Elixi...
639 49522 488
New
aadeshere1
I have a another noob question about loop. Since elixir is immutable, while loop is not directly possible. total = 10 while total != 0 ...
New
shahryarjb
Hello, I have map which I want to convert it to string like this: the map: %{last_name: "tavakkoli", name: "shahryar"} the string I ne...
New
chrisalley
ExUnit now has describe blocks which is a welcome addition coming from RSpec. In the docs, it states that nested hierarchies of describe ...
New
beno
I will often find my self writing things similar to: case some_value do nil -&gt; something() "" -&gt; something() _ -&gt; someth...
New
Fl4m3Ph03n1x
About me? ( if you have nothing better to do than reading about some random guy in the internet :stuck_out_tongue: ) Hello all, this is ...
New
josevalim
Hi everyone, One of the features added to Elixir early on to help integration with Erlang code was the idea of overridable function defi...
New

Sub Categories:

We're in Beta

About us Mission Statement