elixir-socket icon indicating copy to clipboard operation
elixir-socket copied to clipboard

Active Websockets?

Open jtmoulia opened this issue 11 years ago • 8 comments

Is there a way to have an active websocket client? I can set the underlying socket to be active, but then I believe the messages I get are the raw over the socket messages.

Would it make sense for Socket.Web to implement Socket.Protocol?

Thanks!

jtmoulia avatar Sep 17 '14 22:09 jtmoulia

I thought about making Socket.Web active as well, but you'd end up with two processes, just to handle a connection, which might be a problem.

It would also make owning a process a bit more complex, which you need when the socket is active.

About implementing Socket.Protocol and friends, you could end up breaking the websocket itself if you change something you shouldn't, like the packet type or the process owner, or the passive/active state of the socket.

meh avatar Sep 17 '14 22:09 meh

Makes sense as to why it's not built in -- sounds like it's tricky to do right. If this is something that's not on the roadmap feel free to close.

Well, here's my hacky two process-er :)

defmodule Mainframex.Listener do
  @moduledoc """
  This is a hack to provide active-ish websockets by wrapping them in a
  process.
  """

  @doc """
  Spawn link a process to pull data from the websocket and send it to the
  calling process
  """
  @spec listen(Socket.Web.t) :: {:ok, pid}
  def listen(socket) do
    me = self()
    {:ok, fn -> init(socket, me) end |> spawn_link}
  end

  defp init(%{socket: underlying} = socket, pid) do
    true = Process.link(underlying)
    loop(socket, pid)
  end

  defp loop(socket, pid) do
    msg = Socket.Web.recv!(socket)
    send(pid, msg)
    loop(socket, pid)
  end
end

jtmoulia avatar Sep 17 '14 22:09 jtmoulia

Nah, leave it open, it's something I want to do but, as you said, is tricky to do properly.

Just have to figure out the best way to go.

meh avatar Sep 17 '14 22:09 meh

Currently, I had to implement a bridge process to imitate an active websocket, but this is far from ideal and counter-intuitive. Maybe we can brainstorm a good solution for this issue?

yrashk avatar Jun 07 '15 22:06 yrashk

@yrashk I'm open to anything, but I don't see any other way than having a polling process.

meh avatar Jun 08 '15 03:06 meh

I handled this a bit different. I made my loop function take an on_data callback and then just call that &on_data/1 before recursing again. It could be called in a Task as well so you never block.

Each use case might be different, so I agree it might be hard to implement here.

dustinsmith1024 avatar Dec 15 '16 19:12 dustinsmith1024

Would really appreciate an example for how to build an active connection! I'm trying to create a websocket that can accept messages to send over the wire while also accepting responses (which can be 0 or multiple).

Is there a way to block on multiple conditions? Ideally I would have a loop function block on receiving a message in the process's inbox OR over the wire, whichever comes first, then call itself.

pikeas avatar Feb 13 '17 08:02 pikeas

Here's my way.

  @sleep_time_on_error 100
  defp spawn_recv_loop(socket) do
    pid = self()
    spawn(fn ->
      for _ <- Stream.cycle([:ok]) do
        case WebSocket.recv(socket) do
          {:ok, message} ->
            send pid, {:message, message}
          {:error, error} ->
            send pid, {:error, error}
            :timer.sleep(@sleep_time_on_error)
        end
      end
    end)
  end

https://github.com/ryo33/phoenix-channel-client/blob/c3961ac491b920602a559c8b79d5731cf6a8b68e/lib/phoenix_channel_client.ex#L280-L298

ryo33 avatar Apr 25 '17 08:04 ryo33