Closed the-mikedavis closed 3 years ago
While I don't have a quote from the official docs to back me
up, I'm relatively sure that clients are not allowed to reply to
messages. A quick survey of other client implementations only brings
up a reply feature which defdelegate/2
s to GenServer.reply/2
(see
here
and associated source code
here)
It seems like this is an limitation on the Phoenix Channel communication
protocol caused by the lack of ref
in messages from the server. E.g. open
up a websocket inspector (network tab in your browser's dev-tools)
on http://phoenixchat.herokuapp.com/. I have added message numbers and
directions to the packets, where >
is a message from client (browser)
to server and <
vice versa.
1 > {"topic":"rooms:lobby","event":"phx_join","payload":{},"ref":1}
2 < {"topic":"rooms:lobby","event":"phx_reply","payload":{"status":"ok","response":{}},"ref":1}
3 < {"topic":"rooms:lobby","event":"join","payload":{"status":"connected"},"ref":null}
4 < {"topic":"rooms:lobby","event":"user:entered","payload":{"user":null},"ref":null}
5 < {"topic":"rooms:lobby","event":"new:msg","payload":{"user":"SYSTEM","body":"ping"},"ref":null}
6 < {"topic":"rooms:lobby","event":"new:msg","payload":{"user":"SYSTEM","body":"ping"},"ref":null}
7 > {"topic":"rooms:lobby","event":"new:msg","payload":{"user":"asdf","body":"asdf"},"ref":"2"}
8 < {"topic":"rooms:lobby","event":"phx_reply","payload":{"status":"ok","response":{"msg":"asdf"}},"ref":"2"}
9 < {"topic":"rooms:lobby","event":"new:msg","payload":{"user":"SYSTEM","body":"ping"},"ref":null}
10 < {"topic":"rooms:lobby","event":"new:msg","payload":{"user":"SYSTEM","body":"ping"},"ref":null}
Also see the source code.
Messages one and two are the join and successful join-reply messages. Joins
requests have refs, of course, because the phoenix server needs to reply to
those join requests. Message three is sent from a Phoenix.Channel.push/3
,
and message four is sent from a Phoenix.Channel.broadcast!/3
. Notice how
neither have ref
s (well.. they do, but they're null
). This is notable
because a message must have a ref to be replied-to. A message with an event
of phx_reply
must have a ref, and they're not sending us one, so we just can't
reply.
But I want a reply from the client! Never fear, you can do a similar system to
Slipstream
's push+reply feature by simply sending a reference yourself in
the higher-level (would be in the payload
key of one of those above messages)
payload of a push. The code might look like...
defmodule MyServerSideChannel do
use MyAppWeb, :channel
def join("foo", _params, socket) do
{:ok, socket}
end
def handle_info(:send_and_receive, socket) do
ref = "#{make_ref()}"
push socket, "bar", %{ref: ref}
{:noreply, assign(socket, :ref, ref)}
end
def handle_in("reply", %{"ref" => ref}, %{assigns: %{ref: ref}} = socket) do
IO.inspect(ref, label: "I got a reply!")
{:noreply, socket}
end
end
and keep in mind that this is server-side code (a Phoenix.Channel
). Replies
to pushes are aleady implemented in the client-side (see
t:Slipstream.push_reference/0
). But to work with this server-side reply, a
Slipstream client that would work could be:
defmodule MyClient do
use Slipstream
# .. connect and all that
@impl Slipstream
def handle_message("foo", "bar", %{"ref" => ref}, socket) do
push(socket, "foo", "reply", %{ref: ref, fizz: "buzz"})
{:noreply, socket}
end
end
If Phoenix wanted to implement this, they could do it with a few changes:
Phoenix.Channel.push/3
returns a referencehandle_reply/3
function and/or an await_reply/1
function
that would block until a reply is receivedBut I suspect that they do not want to implement it, because of that breaking change, because you can already do this with the above work-around, and because it's not a very pretty workflow (synchronously waiting for things isn't a very Elixir thing to do).
This isn't a real issue/feature-request. Just wanted to jot down some of the notes in my head and write it out publicly so if anyone else has this question, they can follow my logic.
if a
Phoenix.Channel
can reply with a reply signature ({:reply, reply, socket}
) orPhoenix.Channel.reply/2
, surely we the client writers should be able to reply to server pushes?