/modules/mod_base/support/z_websocket_hixie75.erl

https://code.google.com/p/zotonic/ · Erlang · 166 lines · 93 code · 25 blank · 48 comment · 0 complexity · 40d80e1e2e4c14189d0dbb67d1f29e4a MD5 · raw file

  1. %% @author Marc Worrell <marc@worrell.nl>
  2. %% @copyright 2010-2011 Marc Worrell
  3. %% @doc WebSocket draft-hixie-thewebsocketprotocol-75 (Chrome 4; Safari 5.0.0)
  4. %% Copyright 2010-2011 Marc Worrell
  5. %%
  6. %% Licensed under the Apache License, Version 2.0 (the "License");
  7. %% you may not use this file except in compliance with the License.
  8. %% You may obtain a copy of the License at
  9. %%
  10. %% http://www.apache.org/licenses/LICENSE-2.0
  11. %%
  12. %% Unless required by applicable law or agreed to in writing, software
  13. %% distributed under the License is distributed on an "AS IS" BASIS,
  14. %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15. %% See the License for the specific language governing permissions and
  16. %% limitations under the License.
  17. -module(z_websocket_hixie75).
  18. -author("Marc Worrell <marc@worrell.nl>").
  19. -export([
  20. start/2,
  21. receive_loop/4,
  22. start_send_loop/2,
  23. send_loop/2,
  24. send/2
  25. ]).
  26. % Hixie-75 (Chrome 4; Safari 5.0.0)
  27. % First draft protocol version, this code should be removed in due time.
  28. start(ReqData, Context1) ->
  29. Hostname = m_site:get(hostname, Context1),
  30. WebSocketPath = z_dispatcher:url_for(websocket, [{z_pageid, z_context:get_q("z_pageid", Context1)}],Context1),
  31. Protocol = case wrq:is_ssl(ReqData) of true -> "https"; _ -> "http" end,
  32. Socket = webmachine_request:socket(ReqData),
  33. Data = ["HTTP/1.1 101 Web Socket Protocol Handshake", 13, 10,
  34. "Upgrade: WebSocket", 13, 10,
  35. "Connection: Upgrade", 13, 10,
  36. "WebSocket-Origin: ",Protocol,"://", Hostname, 13, 10,
  37. "WebSocket-Location: ws://", Hostname, WebSocketPath, 13, 10,
  38. 13, 10
  39. ],
  40. ok = send(Socket, Data),
  41. spawn_link(fun() -> z_websocket_hixie75:start_send_loop(Socket, Context1) end),
  42. z_websocket_hixie75:receive_loop(none, nolength, Socket, Context1).
  43. %% ============================== RECEIVE DATA =====================================
  44. %% @doc Start receiving messages from the websocket
  45. receive_loop(Buff, Length, Socket, Context) ->
  46. case mochiweb_socket:recv(Socket, 0, infinity) of
  47. {ok, Received} ->
  48. handle_data(Buff, Length, Received, Socket, Context);
  49. {error, Reason} ->
  50. {error, Reason}
  51. end.
  52. %% @doc Upack any data frames, send them to the handling functions.
  53. handle_data(none, nolength, <<0,T/binary>>, Socket, Context) ->
  54. <<Type, _/binary>> = T,
  55. case Type =< 127 of
  56. true ->
  57. handle_data(<<>>, nolength, T, Socket, Context);
  58. false ->
  59. {Length, LenBytes} = unpack_length(T),
  60. <<_:LenBytes/bytes, Rest:Length/bytes>> = T,
  61. handle_data(<<>>, Length, Rest, Socket, Context)
  62. end;
  63. %% Extract frame ending with 255
  64. handle_data(none, nolength, <<>>, Socket, Context) ->
  65. z_websocket_hixie75:receive_loop(none, nolength, Socket, Context);
  66. handle_data(<<>>, nolength, <<255,_T/binary>>, _Socket, _Context) ->
  67. % A packet of <<0,255>> signifies that the ua wants to close the connection
  68. ua_close_request;
  69. handle_data(Msg, nolength, <<255,T/binary>>, Socket, Context) ->
  70. resource_websocket:handle_message(Msg, Context),
  71. handle_data(none, nolength, T, Socket, Context);
  72. handle_data(Msg, nolength, <<H,T/binary>>, Socket, Context) ->
  73. handle_data(<<Msg/binary, H>>, nolength, T, Socket, Context);
  74. %% Extract frame with length bytes
  75. handle_data(Msg, 0, T, Socket, Context) ->
  76. resource_websocket:handle_message(Msg, Context),
  77. handle_data(none, nolength, T, Socket, Context);
  78. handle_data(Msg, Length, <<H,T/binary>>, Socket, Context) when is_integer(Length) and Length > 0 ->
  79. handle_data(<<Msg/binary, H>>, Length-1, T, Socket, Context);
  80. %% Data ended before the end of the frame, loop to fetch more
  81. handle_data(Msg, Length, <<>>, Socket, Context) ->
  82. z_websocket_hixie75:receive_loop(Msg, Length, Socket, Context).
  83. %% @doc Unpack the length bytes
  84. %% author: Davide Marqu??s (From yaws_websockets.erl)
  85. unpack_length(Binary) ->
  86. unpack_length(Binary, 0, 0).
  87. unpack_length(Binary, LenBytes, Length) ->
  88. <<_:LenBytes/bytes, B, _/bitstring>> = Binary,
  89. B_v = B band 16#7F,
  90. NewLength = (Length * 128) + B_v,
  91. case B band 16#80 of
  92. 16#80 ->
  93. unpack_length(Binary, LenBytes + 1, NewLength);
  94. 0 ->
  95. {NewLength, LenBytes + 1}
  96. end.
  97. % %% @doc Pack the length in 7 bits bytes
  98. % pack_length(N) ->
  99. % pack_length(N, []).
  100. %
  101. % pack_length(N, Acc) ->
  102. % N1 = N div 128,
  103. % B = N rem 128,
  104. % case Acc of
  105. % [] ->
  106. % pack_length(N1, [B|Acc]);
  107. % _ ->
  108. % case N1 of
  109. % 0 -> [B+128|Acc];
  110. % _ -> pack_length(N1, [B+128|Acc])
  111. % end
  112. % end.
  113. %% ============================== SEND DATA =====================================
  114. %% @doc Start the loop passing data (scripts) from the page to the browser
  115. start_send_loop(Socket, Context) ->
  116. % We want to receive any exit signal (including 'normal') from the socket's process.
  117. process_flag(trap_exit, true),
  118. z_session_page:websocket_attach(self(), Context),
  119. send_loop(Socket, Context).
  120. send_loop(Socket, Context) ->
  121. receive
  122. {send_data, Data} ->
  123. case send(Socket, [0, Data, 255]) of
  124. ok -> z_websocket_hixie75:send_loop(Socket, Context);
  125. closed -> closed
  126. end;
  127. {'EXIT', _FromPid, _Reason} ->
  128. % Exit of the socket's process, stop sending data.
  129. exit;
  130. _ ->
  131. z_websocket_hixie75:send_loop(Socket, Context)
  132. end.
  133. %% @doc Send data to the user agent
  134. send(undefined, _Data) ->
  135. ok;
  136. send(Socket, Data) ->
  137. case mochiweb_socket:send(Socket, iolist_to_binary(Data)) of
  138. ok -> ok;
  139. {error, closed} -> closed;
  140. _ -> exit(normal)
  141. end.