I'm trying to create a chatroom with cowboy websocket handler. I want that messages those come from every will be forwarded to other sockets as well, just like a chat group. I don't have any idea how to implement this? And I don't know how to save sockets that are connected to websocket so we can send messages to them. I have this cowboy handler:
-module(chat_conn).
-export([ init/2
, websocket_init/1
, websocket_handle/2
, websocket_info/2
, terminate/3
]).
-include("chat.hrl").
init(Req, [WsConnIdleTimeout]) ->
?LOG_DEBUG("[CHAT-CONN] New HTTP Request on: /api , Pid: ~p", [self()]),
WsOpts = #{idle_timeout => WsConnIdleTimeout},
State = #{counter => 0},
{cowboy_websocket, Req, State, WsOpts}.
websocket_init(State0) ->
?LOG_DEBUG("[FOOZI-CONN] HTTP Upgraded to WebSocketm pid: ~p", [self()]),
NewState = State0,
{ok, NewState}.
websocket_handle({text, PlainRequest}, #{counter := Counter0} = State) ->
?LOG_DEBUG("[HIGGS-CONN] Receive New Message: ~p, Pid: ~p" , [PlainRequest, self()]),
NewCounter = Counter0 + 1,
Reply = list_to_binary("Counter is: " ++ integer_to_list(NewCounter)),
NewState = #{counter => NewCounter},
{reply, {text, Reply}, NewState};
%{ok, State};
websocket_handle(Frame, State) ->
?LOG_INFO("[HIGGS-CONN] Invalid Frame => ~p", [Frame]),
{stop, State}.
websocket_info(Message, State) ->
?LOG_INFO("[CONN-INFO] Unhandled message! => Message: ~p", [Message]),
{reply, {text, list_to_binary(Message)}, State}.
%{ok, State}.
terminate(Reason, _, State) ->
?LOG_INFO("[CONN-TERMINATE] Terminated! => Pid: ~p, Reason: ~p, State: ~p", [self(), Reason, State]),
ok.
I don't know how to save sockets that are connected to websocket so we can send messages to them
In erlang, gen_servers can be used to store state, so you can create a gen_server
that the websocket handlers employ to save the client pids. When a client sends a "start chat" message to your cowboy server using a specific route, the associated websocket handler will be called. Inside that handler, self()
will be the pid of the websocket process for that client. You can save that pid in your gen_server
by calling gen_server:cast(chat_room, {arrive, self()})
, then inside chat_room:handle_cast()
adding the pid to the list of pids stored in the State
variable.
When the cowboy server receives a chat message from a client browser, the client's websocket process will handle the message, and inside the appropriate websocket handler you can query the gen_server
to get a list of the pids for the connected clients. Then you can use !
to send each of them the message. In turn, each client's websocket_info()
handler will handle the message, which it can relay through the websocket back to the client by returning: {reply, {text, Text}, State}
.
For your gen_server
, you will also need to implement a leave()
function that the appropriate websocket handler will call to update the list of clients stored in the gen_server
, e.g. gen_server:cast(chat_room, {leave, self()})
.