Broadcast to socket without channel

That’s the point that it hasn’t an extra “connection” when it is already opened socket there for any data.

Channels do not create an extra network connection. They are multiplexed over the socket. On the server-side channels are quite cheap. So I wouldn’t worry about that.

2 Likes
MyApp.Endpoint.broadcast("users_socket:" <> user_id, "mymessage", %{})

I think it would only work for "disconnect". I remember struggling with it here How to put something in a socket's assigns from another process

1 Like

I don’t worry about that. I just don’t need the channel’s logic

  1. When it goes down for some reason the client has to rejoin to the channel not to the socket
  2. When socket goes down then all the channels have to rejoin
    Like why do the client app has to worry about double connection. So In my case it doesn’t make sense. I agree channels are great when you want to implement observing feature like Youtube comments on the right or event here for new replies

I googled through the forum and was hoping that may be something changed :slight_smile:
According to the code it is just a hardcoded event name. So actually I believe it is a limitation for some reason to give an interface to interact only via channels but then the question is why :thinking:

def ws_info(%Broadcast{event: "disconnect"}, state) do
  {:shutdown, state}
end

disconnect is of course the only hard-coded message, but you can listen for others on the socket (not channel) PID by subscribing it to what it needs.

Sorry didn’t understand the idea. Can you explain it more detailed?

Literally just that, subscribe to a pubsub topic then handle such messages, though I’m unsure how to setup such a receive in a socket (if there is no good API then one really should be PR’d in to handle arbitrary unhandled messages to the socket PID).

You might try running <YourApp>.Endpoint.subscribe("<some_topic>") from within the connect/2 callback. Supposedly, it would run inside the process handling the socket. Then you would be able to broadcast to all socket processes subscribed to "<some_topic>" with <YourApp>.Endpoint.broadcast("<some_topic>", some_event, some_msg) and see where it gets you. If phoenix’s socket is just a handler module for cowboy, you might be able to use cowboy’s callbacks to handle incoming messages, which IIRC is websocket_info, which seems to be forwarded to handler’s ws_info in phoenix v1.3 and to handle_info in phoenix master.

if there is no good API then one really should be PR’d in to handle arbitrary unhandled messages to the socket PID

It seems like handle_info has been added as a possible callback to phoenix.socket in v1.4. But in v1.3 the messages are simply ignored unless you pack them in a {:socket_push, _, _encoded_payload} tuple.

2 Likes

Awesome! Good to know! :slight_smile:

What all @idi527 said looks right though, you subscribe from inside the connect callback. :slight_smile:

1 Like

I tried to subscribe in connect but yeah for some reason it returns :ok but nothing happens.
1.4 is not released yet, so it is better to wait I think before using it in production.

What do you mean by “nothing happens”? Have you tried sending messages to that topic?

Yes, I do. I saw somewhere a comment that connect is a transport layer and it is not possible to automatically subscribe.

:thinking:

I’ve just tried the approach I’ve described above for phoenix v1.3 and it worked.

I saw somewhere a comment that connect is a transport layer and it is not possible to automatically subscribe.

Can you link me to that comment?

1 Like

I believe yes

Can you please push your code somewhere?

The link is in my previous post.

Not sure that it works
I launched it in dev mode and connected from iOS plus added two logs in connect and in join so the second one never called.

join as in a channel join? It wouldn’t be called with this approach, the messages are pushed directly to the websocket bypassing any channels. Check out the test (which passes, so it works :+1:).

Ah, sorry misunderstood this.
:thinking:

[{socket_pid, []}] = Registry.lookup(Registry.Lalala, "lalala")
[{#PID<0.335.0>, []}]
send(socket_pid, {:socket_push, "asdf", "asdf"})

in dev mode gives me right now

[error] Process #PID<0.335.0> raised an exception
** (FunctionClauseError) no function clause matching in :cowboy_websocket.websocket_opcode/1
    (cowboy) /Users/vlad/Documents/elixir/github/socket_push/deps/cowboy/src/cowboy_websocket.erl:652: :cowboy_websocket.websocket_opcode("asdf")
    (cowboy) /Users/vlad/Documents/elixir/github/socket_push/deps/cowboy/src/cowboy_websocket.erl:699: :cowboy_websocket.websocket_send/2
    (cowboy) /Users/vlad/Documents/elixir/github/socket_push/deps/cowboy/src/cowboy_websocket.erl:618: :cowboy_websocket.handler_call/7
    (phoenix) lib/phoenix/endpoint/cowboy_websocket.ex:49: Phoenix.Endpoint.CowboyWebSocket.resume/3
    (cowboy) /Users/vlad/Documents/elixir/github/socket_push/deps/cowboy/src/cowboy_protocol.erl:442: :cowboy_protocol.execute/4
[error] Ranch protocol #PID<0.335.0> of listener SocketBroadcastWeb.Endpoint.HTTP (cowboy_protocol) terminated
** (exit) an exception was raised: 
    ** (FunctionClauseError) no function clause matching in :cowboy_websocket.websocket_opcode/1
        (cowboy) /Users/vlad/Documents/elixir/github/socket_push/deps/cowboy/src/cowboy_websocket.erl:652: :cowboy_websocket.websocket_opcode("asdf")
        (cowboy) /Users/vlad/Documents/elixir/github/socket_push/deps/cowboy/src/cowboy_websocket.erl:699: :cowboy_websocket.websocket_send/2
        (cowboy) /Users/vlad/Documents/elixir/github/socket_push/deps/cowboy/src/cowboy_websocket.erl:618: :cowboy_websocket.handler_call/7
        (phoenix) lib/phoenix/endpoint/cowboy_websocket.ex:49: Phoenix.Endpoint.CowboyWebSocket.resume/3
        (cowboy) /Users/vlad/Documents/elixir/github/socket_push/deps/cowboy/src/cowboy_protocol.erl:442: :cowboy_protocol.execute/4

You need to use proper values instead of "asdf" for what you actually want to send to the client.

For example

send(socket_pid, {:socket_push, :text, Poison.encode!(%{"oh" => "my"})})
2 Likes