satom99

satom99

From the example, adding .Raw to the transport is the only change you need to do in order to get it working - in comparsion to a regular socket setup (with channels) one would have. I assume you have this.

We have discussed changing the syntax here, but for now if you want to send data to the client you may do send socket_pid, {:binary, some_binary} or have this tuple returned on the handle callback - as shown in the example. Have a look here for a reference on op codes.

Would you mind sharing your code as well?

0
Post #11
Licenser

Licenser OP

Sadly that doesn’t solve the problem, I used that and I kept getting the following error:

[error] Ranch protocol #PID<0.514.0> (:cowboy_protocol) of listener CloudUiWeb.Endpoint.HTTP terminated
** (exit) an exception was raised:
** (FunctionClauseError) no function clause matching in :cowboy_websocket.websocket_opcode/1
    (cowboy) /Users/heinz/Projects/fifo/core/cloud_ui/deps/cowboy/src/cowboy_websocket.erl:652: :cowboy_websocket.websocket_opcode(:data)
    (cowboy) /Users/heinz/Projects/fifo/core/cloud_ui/deps/cowboy/src/cowboy_websocket.erl:699: :cowboy_websocket.websocket_send/2
    (cowboy) /Users/heinz/Projects/fifo/core/cloud_ui/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/heinz/Projects/fifo/core/cloud_ui/deps/cowboy/src/cowboy_protocol.erl:442: :cowboy_protocol.execute/4
16:25:51.313 [error] Error in process <0.514.0> with exit value:
{function_clause,[{cowboy_websocket,websocket_opcode,[data],[{file,"/Users/heinz/Projects/fifo/core/cloud_ui/deps/cowboy/src/cowboy_websocket.erl"},{line,652}]},{cowboy_websocket,websocket_send,2,[{file,"/Users/heinz/Projects/fifo/core/cloud_ui/deps/cowboy/src/cowboy_websocket.erl"},{line,699}]},{cowboy_websocket,handler_call,7,[{file,"/Users/heinz/Projects/fifo/core/cloud_ui/deps/cowboy/src/cowboy_websocket.erl"},{line,618}]},{'Elixir.Phoenix.Endpoint.CowboyWebSocket',resume,3,[{file,"lib/phoenix/endpoint/cowboy_websocket.ex"},{line,49}]},{cowboy_protocol,execute,4,[{file,"/Users/heinz/Projects/fifo/core/cloud_ui/deps/cowboy/src/cowboy_protocol.erl"},{line,442}]}]}
16:25:51.313 [error] Ranch listener 'Elixir.CloudUiWeb.Endpoint.HTTP' terminated with reason: no function clause matching cowboy_websocket:websocket_opcode(data) line 652

Since phoenix tries to put the first argument thorugh webxocket_opcode/1

0
Post #12
satom99

satom99

I see that error coming from Cowboy itself, because there’s no function clause matching in websocket_opcode(:data). Which I assume is caused by that atom not being a registered op code (text | binary | ping | pong), I guess :binary should work just fine. :slight_smile:

0
Post #13
Licenser

Licenser OP

it might but that’d require rewriting the library sending {:data, ...} the data and every other applicaiton using it. Not to mention that it’s a horrible hack. I mean it’s not sane that :binary means data send to the socket and :text means data from the socket just because well, because otherwise it explodes.

0
Post #14
satom99

satom99

Do you mean that incoming data (that-is data received from the client) is sent over to the handle function with a :text op code only? And that outgoing data (that-is data sent to the client) is to be set as :binary?

That is not the case. You may receive both :text and :binary, as well as :ping and :pong from the client. Thus you may as well send either of those to the client.

def handle(opcode, data, state) do
  case opcode do
    :text ->
      IO.inspect "text!"
      IO.inspect data
    :binary ->
      IO.inspect "binary!"
      IO.inspect data
    :ping ->
      IO.inspect "ping!"
    :pong ->
      IO.inspect "pong!"
    :closed -> # not an actual opcode, this is a 'somewhat custom' phx_raws event
      IO.inspect "rip"
      IO.inspect data # reason
  end

  send self(), {:text, "Hello user!"}
  send self(), {:binary, ...}
  send self(), {:ping, ...}
  send self(), {:pong, ...}
  send self(), {:close, "Goodbye!"}

  :ok
end

For instance, the readme example is an echo server. Upon connection, a Welcome! text message is sent to the client. And whenever a text message is received from the client, the exact same message is sent back.

0
Post #15
Licenser

Licenser OP

Nono, I understand how opcode work, I mean that Socket interprets every message the process gets as an opcode.

I think I’m really bad at explaining it, let me try with some examples:

We got:

B - A browser (web socket client)

S - The Phoenix.Socket process using transport :websocket, Phoenix.Transports.WebSocket.Raw

E - An endpoint process that somewhere else.

What I try to build is an S that sends all data it gets form E to B and all data it gets form B to E. So basically:

defmodule S do
  use Phoenix.Socket
  transport :websocket, Phoenix.Transports.WebSocket.Raw
  # data coming from B gets send to E
  def handle(:text, message, %{endpoint: endpoint}) do
    FancyLib.send_data(producer, message)
    :ok
  end

  # data coming from E gets send to B
  def handle(:data, message, _state) do
    {:text, message}
  end
end

(semi pseudocodeish)

The problem here is that I can’t find a sane way to send data to S, as all messages that go there get forced through Phoenix.Socket and are interpreted as opcode messages.

0
Post #16
satom99

satom99

If I understood you right, this is what we have:

  • A client C sends a message to the server S
  • The server forwards this message to a process P
  • This process does something with the client’s message and sends a reply to the client
    Client > Server > Process > Client

What is FancyLib? Why are you defining handle(:data, ...) - there is no such opcode?

Let me introduce an example.

defmodule Socket do
  use Phoenix.Socket

  transport :websocket, Phoenix.Transports.Websocket.Raw

  def handle(:text, message, state) do
    Something.dothing(message)

    :ok        
  end
end

defmodule Something do
  use GenServer

  def start_link do
    # Start a GenServer on this module and give it a name
    GenServer.start_link __MODULE__, [], name: PotatoServer
  end

  def dothing(message) do
    GenServer.call PotatoServer, {:in, message}
  end

  def handle_call({:in, message}, from, state) do
    # message = message received from the client
    # from = socket PID, the process that called the GenServer
    
    IO.inspect message

    send from, {:text, "I received this: #{message}"}

    {:noreply, state}
  end
end

Say you have a GenServer running named PotatoServer. Following the scheme, we would have a client sending a message to the server, this message forwarded to the GenServer process, and finally this GenServer would send a message back to the client. Client > Server > PotatoServer > Client.

Essentially, by name:'ing a GenServer we are able to refer to it by name instead of using its PID, which is what we are doing when .call’ing.

0
Post #17
sasajuric

sasajuric

Author of Elixir In Action

You need to have something like this in you endpoint config:

http: [
    dispatch: [
      {:_, [
        {"/raw_socket", MyAppWeb.RawSocket, []},
        {"/phoenix/live_reload/socket/websocket", Phoenix.Endpoint.CowboyWebSocket,
          {Phoenix.Transports.WebSocket, {MyAppWeb.Endpoint, Phoenix.LiveReloader.Socket, :websocket}}
        },
        {:_, Plug.Adapters.Cowboy.Handler, {MyAppWeb.Endpoint, []}}
      ]}
    ]

Notes:

  • you should only include the second entry (live reload socket route) in :dev environment
  • you might need to include https config as well/instead

Now, you can use the standard cowboy handler, for example:

defmodule CustomDispatchWeb.RawSocket do
  @behaviour :cowboy_websocket_handler

  def init({_transport, :http}, _req, _opts), do:
    {:upgrade, :protocol, :cowboy_websocket}

  def websocket_init(_transport, req, _opts), do:
    {:ok, req, nil}

  def websocket_handle(_msg, req, state), do:
    {:ok, req, state}

  def websocket_info(_msg, req, state), do:
    {:ok, req, state}

  def websocket_terminate(_reason, _req, _state), do:
    :ok
end
8
Post #18
Licenser

Licenser OP

Thank you :smiley: that does the trick!

2
Post #19
greentornado

greentornado

how to use pg2 pubsub in raw websocket like phoenix channel please ?

0
Post #20