Web Sockets! The new kid in town! Joe loves it, maybe you should too?
Web Sockets allow for *real* two-way communication between the browser and Yaws without the overhead and latency that come with polling/long-polling solutions. That should be enough for an introduction. Now... how to use it?
First start by returning:
{websocket, OwnerPid, SocketMode}from the out/1 function. This makes the erlang process within yaws processing that particular page do a protocol upgrade from HTTP to the Web Socket Protocol, after which the OwnerPid can use the socket to interface directly with the Web Sockets client.
When yaws completes the Web Sockets handshake, it sends one of the following messages to OwnerPid:
SocketMode defines how the messages sent by the client are to be delivered to the OwnerPid.
On passive mode (SocketMode=false) it is up to the receiving process to issue a synchronous request to:
yaws_api:websocket_receive(WebSocket)
to grab the incoming messages.
On active mode (SocketMode=true) the incoming Web Socket data frames are delivered to OwnerPid as messages. On this mode the following messages are to be expected:
{tcp, WebSocket, DataFrame}
{tcp_closed, WebSocket}To extract the data from a Web Socket data frame you can use this function:
yaws_api:websocket_unframe_data(DataFrame)
If SocketMode=once then only ONE message will be sent as if in active mode and after that we're back to passive mode.
For switching between the various "receive modes" you can do this:
yaws_api:websocket_setopts(WebSocket, [{active, NewSocketMode}])This function just wraps (gen_tcp|ssl):setops/2 to absctract away from using a regular/secure http connection.
Enough theory for now. Sample echo server follows!
out(A) -> case get_upgrade_header(A#arg.headers) of undefined -> {content, "text/plain", "You're not a web sockets client! Go away!"}; "WebSocket" -> WebSocketOwner = spawn(fun() -> websocket_owner() end), {websocket, WebSocketOwner, passive} end. websocket_owner() -> receive {ok, WebSocket} -> %% This is how we read messages (plural!!) from websockets on passive mode case yaws_api:websocket_receive(WebSocket) of {error,closed} -> io:format("The websocket got disconnected right from the start. " "This wasn't supposed to happen!!~n"); {ok, Messages} -> case Messages of [<<"client-connected">>] -> yaws_api:websocket_setopts(WebSocket, [{active, true}]), echo_server(WebSocket); Other -> io:format("websocket_owner got: ~p. Terminating~n", [Other]) end end; _ -> ok end. echo_server(WebSocket) -> receive {tcp, WebSocket, DataFrame} -> Data = yaws_api:websocket_unframe_data(DataFrame), io:format("Got data from Websocket: ~p~n", [Data]), yaws_api:websocket_send(WebSocket, Data), echo_server(WebSocket); {tcp_closed, WebSocket} -> io:format("Websocket closed. Terminating echo_server...~n"); Any -> io:format("echo_server received msg:~p~n", [Any]), echo_server(WebSocket) end. get_upgrade_header(#headers{other=L}) -> lists:foldl(fun({http_header,_,K0,_,V}, undefined) -> K = case is_atom(K0) of true -> atom_to_list(K0); false -> K0 end, case string:to_lower(K) of "upgrade" -> V; _ -> undefined end; (_, Acc) -> Acc end, undefined, L).
The above code can be executed Here.