cowboy_websocket:websocket_send argument problem

110 Views Asked by At

I am trying to use cowboy to send a notification to multiple clients using the socket connected to them. The problem is that I cannot find anything in the documentation about the argument to be passed to the function, the one I used in the code seems to be incorrect.

The socket is saved in a variable called Req that is given when a new client connects in the init function:

% Called to know how to dispatch a new connection.
init(Req, _Opts) ->
  ?LOG_INFO("New client"),
  ?LOG_DEBUG("Request: ~p", [Req]),
% "upgrade" every request to websocket,
% we're not interested in serving any other content.
  Req2=Req,
  {cowboy_websocket, Req, #state{socket = Req2}}.

The sockets are used in this way

send_message_to_sockets([Socket | Sockets], Msg) ->
  cowboy_websocket:websocket_send(Socket, {text, Msg}),
  send_message_to_sockets(Sockets, Msg).

This is the error: Error in process <0.202.0> with exit value: {undef,[{cowboy_websocket,websocket_send, [#{bindings => #{},...}

I have tried different argument to be passed to the function websocket_send but nothing worked.

Here is the code of the websocket_send:

transport_send(#state{socket=Stream={Pid, _}, transport=undefined}, IsFin, Data) ->
 Pid ! {Stream, {data, IsFin, Data}},
 ok;
transport_send(#state{socket=Socket, transport=Transport}, _, Data) ->
 Transport:send(Socket, Data).
-spec websocket_send(cow_ws:frame(), #state{}) -> ok | stop | {error, atom()}.
websocket_send(Frames, State) when is_list(Frames) ->
 websocket_send_many(Frames, State, []);
websocket_send(Frame, State) ->
 Data = frame(Frame, State),
 case is_close_frame(Frame) of
  true ->
   _ = transport_send(State, fin, Data),
   stop;
  false ->
   transport_send(State, nofin, Data)
 end.
websocket_send_many([], State, Acc) ->
 transport_send(State, nofin, lists:reverse(Acc));
websocket_send_many([Frame|Tail], State, Acc0) ->
 Acc = [frame(Frame, State)|Acc0],
 case is_close_frame(Frame) of
  true ->
   _ = transport_send(State, fin, lists:reverse(Acc)),
   stop;
  false ->
   websocket_send_many(Tail, State, Acc)
 end.
1

There are 1 best solutions below

0
7stud On

The socket is saved in a variable called Req that is given when a new client connects in the init function:

init(Req, _Opts) ->
  ?LOG_INFO("New client"),
  ?LOG_DEBUG("Request: ~p", [Req]),
% "upgrade" every request to websocket,
% we're not interested in serving any other content.
  Req2=Req,
  {cowboy_websocket, Req, #state{socket = Req2}}.

I don't think that is true. In Nine Nine's Cowboy User Guide, there is a section titled Getting Started, which shows this code:

Handling requests
...
...

    init(Req0, State) ->
        Req = cowboy_req:reply(200,
            #{<<"content-type">> => <<"text/plain">>},
            <<"Hello Erlang!">>,
            Req0),
    {ok, Req, State}.

In that code, init() is passed a map that is assigned/bound to the Req0 variable. You can read about the Request map here:

https://ninenines.eu/docs/en/cowboy/2.9/guide/req/

The map contains the usual HTTP Request information, e.g. the HTTP Request method, the HTTP version number, scheme, host, port, path, etc. Then the docs say:

Any other field is internal and should not be accessed.

Generally, a socket is defined like this:

A socket is one endpoint of a two-way communication link between two programs running on the network.

And:

An endpoint is a combination of an IP address and a port number.

https://docs.oracle.com/javase/tutorial/networking/sockets/definition.html

In this line:

cowboy_websocket:websocket_send(Socket, {text, Msg}),

websocket_send() is defined to take a cow_ws:frame() type as the first argument:

-spec websocket_send(cow_ws:frame(), #state{}) -> ok | stop | {error, atom()}.
websocket_send(Frames, State) when is_list(Frames) ->

In the cow_ws module, the frame() type is defined like this:

frame() :: {text, iodata()}
           | {binary, iodata()}
           | ping | {ping, iodata()}
           | pong | {pong, iodata()}
           | close | {close, iodata()} | {close, close_code(), iodata()}
  

...which is a tuple with the first element being an atom and possibly a second element being an iodata() type, which is a built in erlang type that is a binary or a list (containing integers, binaries and other lists), and it is defined here:

enter image description here

I'm not sure how you go from a Request map to a two-tuple.