elixir-socket
elixir-socket copied to clipboard
Active Websockets?
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!
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.
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
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.
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 I'm open to anything, but I don't see any other way than having a polling process.
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.
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.
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