Skip to content

Commit

Permalink
Added documentation for the HTML5 Web Sockets implementation.
Browse files Browse the repository at this point in the history
  • Loading branch information
davide committed Dec 18, 2009
1 parent da8ebc6 commit 785b7c6
Show file tree
Hide file tree
Showing 4 changed files with 154 additions and 67 deletions.
1 change: 1 addition & 0 deletions www/TAB.inc
Expand Up @@ -55,6 +55,7 @@ PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
<div class="%%simple%%"> <a href="/simple.yaws">Simple</a> </div>
<div class="%%soap_intro%%"> <a href="/soap_intro.yaws">SOAP with Yaws</a></div>
<div class="%%stream%%"> <a href="/stream.yaws">Streaming data</a> </div>
<div class="%%websockets%%"> <a href="/websockets.yaws">Web Sockets</a> </div>
<a href="/shoppingcart/index.yaws">Tiny shopping cart</a>
<div class="%%yapp_intro%%"> <a href="/yapp_intro.yaws">Yaws applications</a></div>

Expand Down
86 changes: 86 additions & 0 deletions www/websockets.yaws
@@ -0,0 +1,86 @@

<erl>


box(Str) ->
{'div',[{class,"box"}],
{pre,[],yaws_api:htmlize(Str)}}.

tbox(T) ->
box(lists:flatten(io_lib:format("~p",[T]))).


ssi(File) ->
{'div',[{class,"box"}],
{pre,[],
{ssi, File,[],[]}}}.


out(A) ->
[{ssi, "TAB.inc", "%%",[{"websockets", "choosen"}]},
{ehtml,
{'div',[{id, "entry"}],
[{h1, [], "Web Sockets in Yaws"},

{p, [], ["Web Sockets! The new kid in town! Joe ", {a, [{href, "http://armstrongonsoftware.blogspot.com/2009/12/comet-is-dead-long-live-websockets.html"}], "loves it"}, ", maybe you should too?"]},

{p, [], "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?"},

{p, [], "First start by returning:"},

box(" {websocket, OwnerPid, SocketMode}"),

{p,[], "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."},

{p,[], "When yaws completes the Web Sockets handshake, it sends one of the following messages to OwnerPid:"},

{ul,[],
[{li,[]," {ok, WebSocket} indicates to OwnerPid that the handshake completed successfully and that the WebSocket is set up and can be used to communicate with the web sockets client."},
{li,[]," discard indicates to OwnerPid that the handshake failed and no web sockets connection is available."}]},

{p,[], "SocketMode defines how the messages sent by the client are to be delivered to the OwnerPid."},

{ul, [], [
{li, [], [
{p,[], "On passive mode (SocketMode=false) it is up to the receiving process to issue a synchronous request to: "},
box("yaws_api:websocket_receive(WebSocket)"),
{p, [], " to grab the incoming messages."}
]},
{li, [], [
{p, [], "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:"},
box("{tcp, WebSocket, DataFrame}
{tcp_closed, WebSocket}"),
{p, [], "To extract the data from a Web Socket data frame you can use this function: "},
box("yaws_api:websocket_unframe_data(DataFrame)")
]},
{li, [], [
{p, [], "If SocketMode=once then only ONE message will be sent as if in active mode and after that we're back to passive mode."}
]}
]},

{p,[], "For switching between the various \"receive modes\" you can do this:"},

box("yaws_api:websocket_setopts(WebSocket, [{active, NewSocketMode}])"),

{p,[],
"This function just wraps (gen_tcp|ssl):setops/2 to absctract away from using a regular/secure http connection."},

{p, [], "Enough theory for now. Sample echo server follows!"},

ssi("websockets_example_endpoint.yaws"),

{p, [],
["The above code can be executed ",
{a, [{href, "websockets_example.yaws"}], "Here"},
"."]}

]}},
{ssi, "END2",[],[]}
].


</erl>


</html>
71 changes: 4 additions & 67 deletions www/websockets_example.yaws
@@ -1,74 +1,11 @@

<erl>

out(A) ->
case get_upgrade_header(A#arg.headers) of
undefined ->
serve_html_page(A);
"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).

serve_html_page(A) ->
out(A) ->
Host = (A#arg.headers)#headers.host,
{abs_path, Path} = (A#arg.req)#http_request.path,
WebSocketLocation = Host ++ Path,
io:format("WebSocketLocation: ~p ~n", [WebSocketLocation]),
EndpointPath = filename:dirname(Path)
++ "websockets_example_endpoint.yaws",
WebSocketLocation = Host ++ EndpointPath,
Body = html_body(WebSocketLocation),
{content, "text/html", Body}.

Expand Down
63 changes: 63 additions & 0 deletions www/websockets_example_endpoint.yaws
@@ -0,0 +1,63 @@
<erl>
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).

</erl>

0 comments on commit 785b7c6

Please sign in to comment.