PageRenderTime 23ms CodeModel.GetById 1ms app.highlight 17ms RepoModel.GetById 1ms app.codeStats 0ms

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