How do I send data asynchronously over a websocket with Cowboy?
Asked Answered
B

2

5

I have a Cowboy websocket server and I'd like to register a gen_event handler that sends something over the websocket. I also need to be able to reply to regular synchronous requests with websocket_handle/3. I didn't see anything obvious in cowboy_http_websocket_handler.erl and cowboy_http_websocket:websocket_send/3 isn't exported. Am I missing an easy way to send something over an open socket?

Bakki answered 15/11, 2011 at 13:23 Comment(1)
The easiest way to answer this topic is create an issue at cowboy's github and ask author. Loïc Hoguin is very open for discussion.Charla
D
4

@nmichaels answer pointed me in the right direction and I used gen_event successfully in a cowboy app to send internal messages to the websocket_info. But the answer is a bit dated and cowboy has changed a lot so I would like to add to it and provide a solution that works on the latest cowboy version. Hopefully this will help some one new to Erlang and cowboy.

There are three steps needed in order to implement a gen_event in cowboy

  • Start the gen_event and register you handlers

    start(_Type, _Args) ->
    Dispatch = cowboy_router:compile(wrinqle_routes:routes_configuration()),
    {ok, _} = cowboy:start_http(http, 100, [{port, 3000}],
                                [{env, [{dispatch, Dispatch}]}]),
      pg2:start(),
    
      gen_event:start({global,my_events}),
      gen_event:add_handler({global,my_events},my_event_handler,[]).
    

Here I have registered the event called my_events globally (note: you can register the events locally as well) and added handler in the module my_event_handler

  • Create an event handler.

  • Now you can notify your event handler of the events from anywhere in cowboy. As an example the code below raises events from the websocket_handler

    { _,_ }->
    
       gen_event: notify(global:whereis_name(my_events),{event_name,self()}),
        {ok,Req,State};
    

All this code is doing is notifying the event registered under my_events globally of the event. That's it.

Another problem the OP had trouble with was how to send messages to open connections and connections for which pid is not known at the time of initialization. To solve this problem you can make use of pg2 which registers process id under channels. It is a very useful module for manging PIDs. So the above code can be transformed to something like this

  [H|T] = pg2:get_members(Name)
  gen_event: notify(global:whereis_name(my_events),{event_name, H}).

And this way you can send message to a particular pid and by extension to a particular socket.

Draught answered 18/11, 2013 at 16:56 Comment(0)
B
3

In the example websocket handler, websocket_info/3 is used to send stuff like this. Combine gen_event:add_sup_handler/3 in the websocket's init code with websocket_info/3. Keep the pid of the connection in the handler's state and just send a message with the asynchronous event.

Bakki answered 16/11, 2011 at 15:15 Comment(3)
I know you answered this a long time ago but I have a question... Why do you add event handler in websocket's int code and not in the start of your cowboy app?Draught
The websocket's init code gets run when a session is set up. Cowboy is only initialized once. This needs to send messages to open connections, and the pids for future connections aren't known when the app is started. That said, a new version of Cowboy came out recently so this might be obsolete.Bakki
I had a similar problem to yours and I have added the way I solved it. Thank you for pointing me in the right direction. It could be useful for someone else facing a similar problem.Draught

© 2022 - 2024 — McMap. All rights reserved.