diff --git a/www/TAB.inc b/www/TAB.inc index 0d9534760..3f1368bc2 100644 --- a/www/TAB.inc +++ b/www/TAB.inc @@ -55,6 +55,7 @@ PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
Simple
SOAP with Yaws
Streaming data
+
Web Sockets
Tiny shopping cart
Yaws applications
diff --git a/www/websockets.yaws b/www/websockets.yaws new file mode 100644 index 000000000..b6e9edbfd --- /dev/null +++ b/www/websockets.yaws @@ -0,0 +1,86 @@ + + + + +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",[],[]} + ]. + + + + + + diff --git a/www/websockets_example.yaws b/www/websockets_example.yaws index b92a1db26..cee0233fd 100644 --- a/www/websockets_example.yaws +++ b/www/websockets_example.yaws @@ -1,74 +1,11 @@ - -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}. diff --git a/www/websockets_example_endpoint.yaws b/www/websockets_example_endpoint.yaws new file mode 100644 index 000000000..a9ad7b678 --- /dev/null +++ b/www/websockets_example_endpoint.yaws @@ -0,0 +1,63 @@ + +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). + +