Cowboy websocket Global processing - erlang

I have a cowboy websocket server. Many clients send message over the websocket. I need to do processing on the message. I can do that in websocket_handle, However as it's realtime I would like to avoid it instead I want to send the message to a Global Process where all the processing can be done.
As Each cowboy has it's own process How to run a process where every user can send message and processing can be done in that process.

Just to clarify, each websocket connection will have its own erlang process in Cowboy, so messages from different websocket clients will be processed in different processes.
If you need to move the processing from the websocket you can simply start a new handler/server process when your app starts (e.g. when you start Cowboy) that listens for process commands and data. Sample processing code:
-module(my_processor).
-export([start/0]).
start() ->
spawn(fun process_loop/0).
process_loop() ->
receive
{process_cmd, Data} ->
process(Data)
end,
process_loop().
When you start it, also register the process with a global name. That way we can reference it from the websocket handlers later.
Pid=my_processor:start().
register(processor, Pid).
Now you can send the data from Cowboy's websocket_handle/3 function to the handling process:
websocket_handle(Data, Req, State) ->
...,
processor ! {process_cmd, Data},
...,
{ok,Req,State}.
Note that the my_processor process will handle the processing requests from all connections. If you want to have a separate process for each websocket connection you could start my_processor in Cowboy's websocket_init/3 function, store the Pid of the my_processorprocess in the State parameter returned from websocket_init and use that pid instead of the processor global name.

Related

Starting ibrowse to support unlimited requests to the server

I am using ibrowse to make POST and GET requests to the external server but I have issues when it comes to supporting frequent and simultaneous requests.
I start ibrowse with default settings:
application:start(ibrowse).
Then I use ibrowse to carry out POST requests:
ibrowse:send_req("http://somelink.com/api/capi/send.php?" ++ Data,[],get,[]).
or:
ibrowse:send_req("http://somelink.com/cgi-bin/send",[{"Content-Type", "application/x-www-form-urlencoded"}],post,Data).
When I call that function, it succeeds on the first try with this result:
[{http_code,"202"},
{http_headers,[{"Server","CCSRouter/0.1"},
{"Content-Length","34"},
{"Content-type","text/html"},
{"Pragma","no-cache"},
{"Cache-Control","no-cache"}]},
{result,"status=1"}],
But when I call the function repeatedly, it fails returning the following error:
{error,{conn_failed,{error,econnrefused}}}
How can I ensure that whenever I make high frequency connections with ibrowse, it succeeds?

Cowboy: How can a cowboy handler reply back to client without request

Now that Im using cowboy to serve as a websocket server, I understand that the handler implements a behavior called cowboy_websocket_handler and that the websocket_handle/3 function is called every time we receive a request and that to reply back to the request, we reply using {reply, X, _}. However since WebSocket is a bi-directional protocol and that server can reach to a client without a request, how do I send some data to the client, not in the web_socket_handle.
I am expecting something in the handler along the lines of
send(Client, Data). Am I thinking in the right direction? ? Thanks!
If yes, does cowboy provide some API to do so?
To quote the docs:
Cowboy will call websocket_info/2 whenever an Erlang message arrives.
The handler can handle or ignore the messages. It can also send frames
to the client or stop the connection.
The following snippet forwards log messages to the client and ignores
all others:
websocket_info({log, Text}, State) ->
{reply, {text, Text}, State};
websocket_info(_Info, State) ->
{ok, State}.
So all you have to do is send a message to your handler from another process (or from itself if you wish), and implement websocket_info as above to send a frame to the client.

Erlang : Tcp Server

Hi I am a new bee in Erlang but managed to create a simple TCP server which accepts client in passive mode and displays message.
I spawn a new process every time new client connects to the server. Is there a way I could send message to the client using the process which gets spawned when client connects.
Here is code.
-module(test).
-export([startserver/0]).
startserver()->
{ok, ListenSocket}=gen_tcp:listen(1235,[binary,{active, false}]),
connect(ListenSocket).
connect(ListenSocket)->
{ok, UserSocket}=gen_tcp:accept(ListenSocket),
Pid=spawn(? MODULE, user,[UserSocket]),
gen_tcp:controlling_process(UserSocket, Pid),
connect(ListenSocket).
user(UserSocket)->
case gen_tcp:recv(UserSocket, 0) of.
{ok, Binary}->% Send basic message.
{error, closed}->% operation on close.
end.
Can I have some thing like if I do.
Pid!{"Some Message"}. And the message is send to the socket associated with the process with non blocking io,
You could try this tutorial for writing a TCP server using OTP principles: http://learnyousomeerlang.com/buckets-of-sockets#sockserv-revisited
If you use a gen_server instead of your connect loop, you can store the Pids in the state. Then you can use gen_server:cast/2 to send a message to one of the Pids.
The function you want to send a message to the client from the controlling process is gen_tcp:send(Socket, Message), so for example if you wanted to send a one off message on connection you could do this:
user(UserSocket)->
gen_tcp:send(UserSocket, "hello"),
case gen_tcp:recv(UserSocket, 0) of
{ok, Binary}->% Send basic message.
gen_tcp:send(UserSocket, "basic message"),
{error, closed}->% operation on close.
gen_tcp:send(UserSocket, "this socket is closing now"),
end.

how to ensure message sending with ssl

I am implementing as exercise a gen_server which behaves as interface towards an ssl authentication server. The ssl server severes the connection if a packet received is wrong (e.g. wrong username and password). The connection must be persistent.
In my gen_server, I open the ssl connection towards the server with an handle_cast/2:
handle_cast(connect, State) ->
......
case ssl:connect(Address, Port, Options, Timeout) of
{ok, NewSocket} ->
{noreply, State#state{socket=NewSocket}};
{error, Reason} ->
gen_server:cast(?SERVER, connect),
{noreply, State#state{socket=undefined}};
and then I wait for other other messages in handle_cast/2 which can be sent for example using:
gen_server:cast(Pid, {authenticate, User, Password}).
Whenever I receive such a cast message I spawn a new function which recovers the SSL socket from the server state using a gen_server:call/3 and sends the authentication message to the SSL server. If the sending part returns an error I try to reconnect, otherwise I read for a while on the socket, to be sure that the socket does not go down, and if it does I reconnect.
send_auth(_, _, 0) ->
{error, max_num_reached};
send_auth(User, Password, Num) ->
Socket = gen_server:call(?SERVER, socket),
%% also a check that socket is not 'undefined'
case ssl:send(Socket, AuthMessage) of
ok ->
case ssl:recv(Socket, 0, 2000) of
{error, timeout} ->
ok;
_ ->
gen_server:cast(?SERVER, connect),
send_auth(User, Password, Num-1)
end,
{error, closed} ->
gen_server:cast(?SERVER, connect),
send_auth(User, Password, Num-1)
end.
I made many tests, but every time, if one message (not the last) is wrong, none of the following messages is actually delivered.
How can I grant that all valid authentication messages are delivered to the authentication server? Moreover how can be sure that the server will connect only if it is not already trying to do so? Otherwise that would be like DOS attack!
As I can understand there's only one authentication server in the system so why can't you connect to it right in the gen_server's init? If the connection is somewhat reliable, you can just kill gen_server if there are any issues with connection and let supervisor re-launch it. It probably will solve your DOS concern.
When you spawn additional processes with send_auth you can just pass socket connection in arguments, instead of doing gen_server call.
To guarantee the delivery you'll add some kind of acknowledgement into your protocol. Auth server should reply to send_auth with something confirming that it got it. And send_auth should retry until it receives that acknowledgement or have some other fallback behavior in case it never does.

How do I handle a WebSocket close from the client in Yaws?

I have implemented a simple appmod that handle WebSockets and echo back the messages. But how do I handle an ws.close(); from the JavaScript client? I have tried with the code below, but handle_message({close, Reason}) is never called and ws.onclose = function(evt) {} is never executed on the JavaScript client.
When I use the same JavaScript client code interacting with a node.js websocket, the client receives an onclose event immediately after ws.close();.
Here is the code for my simple appmod:
-module(mywebsocket).
-export([handle_message/1]).
handle_message({text, Message}) ->
{reply, {text, <<Message/binary>>}};
handle_message({close, Reason}) ->
io:format("User closed websocket.~n", []),
{close, normal}.
Updated answer:
As of github commit 16834c, which will eventually be part of Yaws 1.93, Yaws passes a new callback to your WebSockets callback module when the client sends a close message. The callback is:
{close, Status, Reason}
where Status is either the close status sent by the client, or the numerical value 1000 (specified by RFC 6455 for a normal close) if the client didn't include a status value. Reason is a binary holding any optional reason string passed from the client; it will be an empty binary if the client sent no reason.
Your callback handler for a close message MUST return {close, CloseReason} where CloseReason is either the atom normal for a normal close (which results in the status code 1000 being returned to the client) or another legal numerical status code allowed by RFC 6455. Note that CloseReason is unrelated to any Reason value passed by the client. Technically CloseReason can also be any other Erlang term, in which case Yaws returns status 1000 and passes the term to erlang:exit/1 to exit the Erlang process handling the web socket, but based on RFC 6455 we suggest simply returning the atom normal for CloseReason in all cases.
Original answer, obsoleted by Yaws github commit 16834c:
Yaws never passes a {close, Reason} message to your callback module. Rather, {close, Reason} is a valid return value from handle_message/1 should your callback module decide it wants to close the ws socket.
I modified the websockets_example.yaws file shipped with Yaws (version 1.92) to call this._ws.close() in the client if the user enters the "bye" message on the web page, and added an alert to the _onclose function to show that the onclose event is triggered. In this case the alert occurred, I believe because the "bye" message causes the server to close the ws socket explicitly. But I then modified the example to call this._ws.close() in the client no matter what message the user enters, and in that case no alert for onclose occurred. In this case, a check with lsof showed the ws connection from the browser to Yaws was still present.
So, for now I believe you've hit a bug where the Yaws websockets support isn't detecting the client close and closing its end. I'll see if I can fix it.

Resources