Permalink
Browse files

Added documentation for the HTML5 Web Sockets implementation.

  • Loading branch information...
1 parent da8ebc6 commit 785b7c60ca164bc1fcee57877ad960a4ab329e7e @davide davide committed Dec 18, 2009
Showing with 154 additions and 67 deletions.
  1. +1 −0 www/TAB.inc
  2. +86 −0 www/websockets.yaws
  3. +4 −67 www/websockets_example.yaws
  4. +63 −0 www/websockets_example_endpoint.yaws
View
@@ -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>
View
@@ -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>
@@ -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}.
@@ -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.