cowboy_http_websocket.erl 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. %% Copyright (c) 2011, Loïc Hoguin <essen@dev-extend.eu>
  2. %%
  3. %% Permission to use, copy, modify, and/or distribute this software for any
  4. %% purpose with or without fee is hereby granted, provided that the above
  5. %% copyright notice and this permission notice appear in all copies.
  6. %%
  7. %% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
  8. %% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
  9. %% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
  10. %% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
  11. %% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
  12. %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  13. %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  14. -module(cowboy_http_websocket).
  15. -export([upgrade/3]).
  16. -include("include/http.hrl").
  17. -record(state, {
  18. handler :: module(),
  19. opts :: any(),
  20. origin = undefined :: undefined | binary(),
  21. challenge = undefined :: undefined | binary(),
  22. timeout = infinity :: timeout(),
  23. messages = undefined :: undefined | {atom(), atom(), atom()}
  24. }).
  25. -spec upgrade(module(), any(), #http_req{}) -> ok.
  26. upgrade(Handler, Opts, Req) ->
  27. case catch websocket_upgrade(#state{handler=Handler, opts=Opts}, Req) of
  28. {ok, State, Req2} -> handler_init(State, Req2);
  29. {'EXIT', _Reason} -> upgrade_error(Req)
  30. end.
  31. -spec websocket_upgrade(#state{}, #http_req{}) -> {ok, #state{}, #http_req{}}.
  32. websocket_upgrade(State, Req) ->
  33. {<<"Upgrade">>, Req2} = cowboy_http_req:header('Connection', Req),
  34. {<<"WebSocket">>, Req3} = cowboy_http_req:header('Upgrade', Req2),
  35. {Origin, Req4} = cowboy_http_req:header(<<"Origin">>, Req3),
  36. {Key1, Req5} = cowboy_http_req:header(<<"Sec-Websocket-Key1">>, Req4),
  37. {Key2, Req6} = cowboy_http_req:header(<<"Sec-Websocket-Key2">>, Req5),
  38. false = lists:member(undefined, [Origin, Key1, Key2]),
  39. {ok, Key3, Req7} = cowboy_http_req:body(8, Req6),
  40. Challenge = challenge(Key1, Key2, Key3),
  41. {ok, State#state{origin=Origin, challenge=Challenge}, Req7}.
  42. -spec challenge(binary(), binary(), binary()) -> binary().
  43. challenge(Key1, Key2, Key3) ->
  44. IntKey1 = key_to_integer(Key1),
  45. IntKey2 = key_to_integer(Key2),
  46. erlang:md5(<< IntKey1:32, IntKey2:32, Key3/binary >>).
  47. -spec key_to_integer(binary()) -> integer().
  48. key_to_integer(Key) ->
  49. Number = list_to_integer([C || << C >> <= Key, C >= $0, C =< $9]),
  50. Spaces = length([C || << C >> <= Key, C =:= 32]),
  51. Number div Spaces.
  52. -spec handler_init(#state{}, #http_req{}) -> ok.
  53. handler_init(State=#state{handler=Handler, opts=Opts},
  54. Req=#http_req{transport=Transport}) ->
  55. case catch Handler:websocket_init(Transport:name(), Req, Opts) of
  56. {ok, Req2, HandlerState} ->
  57. websocket_handshake(State, Req2, HandlerState);
  58. {ok, Req2, HandlerState, Timeout} ->
  59. websocket_handshake(State#state{timeout=Timeout},
  60. Req2, HandlerState);
  61. {'EXIT', _Reason} ->
  62. upgrade_error(Req)
  63. end.
  64. -spec upgrade_error(#http_req{}) -> ok.
  65. upgrade_error(Req=#http_req{socket=Socket, transport=Transport}) ->
  66. {ok, _Req} = cowboy_http_req:reply(400, [], [],
  67. Req#http_req{resp_state=waiting}),
  68. Transport:close(Socket).
  69. -spec websocket_handshake(#state{}, #http_req{}, any()) -> ok.
  70. websocket_handshake(State=#state{origin=Origin, challenge=Challenge},
  71. Req=#http_req{transport=Transport, raw_host=Host, port=Port,
  72. raw_path=Path}, HandlerState) ->
  73. Location = websocket_location(Transport:name(), Host, Port, Path),
  74. {ok, Req2} = cowboy_http_req:reply(
  75. <<"101 WebSocket Protocol Handshake">>,
  76. [{<<"Connection">>, <<"Upgrade">>},
  77. {<<"Upgrade">>, <<"WebSocket">>},
  78. {<<"Sec-WebSocket-Location">>, Location},
  79. {<<"Sec-WebSocket-Origin">>, Origin}],
  80. Challenge, Req#http_req{resp_state=waiting}),
  81. handler_loop(State#state{messages=Transport:messages()},
  82. Req2, HandlerState, <<>>).
  83. -spec websocket_location(atom(), binary(), inet:ip_port(), binary())
  84. -> binary().
  85. websocket_location(ssl, Host, Port, Path) ->
  86. << "wss://", Host/binary, ":",
  87. (list_to_binary(integer_to_list(Port)))/binary, Path/binary >>;
  88. websocket_location(_Any, Host, Port, Path) ->
  89. << "ws://", Host/binary, ":",
  90. (list_to_binary(integer_to_list(Port)))/binary, Path/binary >>.
  91. -spec handler_loop(#state{}, #http_req{}, any(), binary()) -> ok.
  92. handler_loop(State=#state{messages={OK, Closed, Error}, timeout=Timeout},
  93. Req=#http_req{socket=Socket, transport=Transport},
  94. HandlerState, SoFar) ->
  95. Transport:setopts(Socket, [{active, once}]),
  96. receive
  97. {OK, Socket, Data} ->
  98. websocket_data(State, Req, HandlerState,
  99. << SoFar/binary, Data/binary >>);
  100. {Closed, Socket} ->
  101. handler_terminate(State, Req, HandlerState, {error, closed});
  102. {Error, Socket, Reason} ->
  103. handler_terminate(State, Req, HandlerState, {error, Reason});
  104. Message ->
  105. handler_call(State, Req, HandlerState,
  106. SoFar, Message, fun handler_loop/4)
  107. after Timeout ->
  108. websocket_close(State, Req, HandlerState, {normal, timeout})
  109. end.
  110. -spec websocket_data(#state{}, #http_req{}, any(), binary()) -> ok.
  111. websocket_data(State, Req, HandlerState, << 255, 0, _Rest/bits >>) ->
  112. websocket_close(State, Req, HandlerState, {normal, closed});
  113. websocket_data(State, Req, HandlerState, Data) when byte_size(Data) < 3 ->
  114. handler_loop(State, Req, HandlerState, Data);
  115. websocket_data(State, Req, HandlerState, Data) ->
  116. websocket_frame(State, Req, HandlerState, Data, binary:first(Data)).
  117. %% We do not support any frame type other than 0 yet. Just like the specs.
  118. -spec websocket_frame(#state{}, #http_req{}, any(), binary(), byte()) -> ok.
  119. websocket_frame(State, Req, HandlerState, Data, 0) ->
  120. case binary:match(Data, << 255 >>) of
  121. {Pos, 1} ->
  122. Pos2 = Pos - 1,
  123. << 0, Frame:Pos2/binary, 255, Rest/bits >> = Data,
  124. handler_call(State, Req, HandlerState,
  125. Rest, {websocket, Frame}, fun websocket_data/4);
  126. nomatch ->
  127. %% @todo We probably should allow limiting frame length.
  128. handler_loop(State, Req, HandlerState, Data)
  129. end;
  130. websocket_frame(State, Req, HandlerState, _Data, _FrameType) ->
  131. websocket_close(State, Req, HandlerState, {error, badframe}).
  132. -spec handler_call(#state{}, #http_req{}, any(), binary(), any(), fun()) -> ok.
  133. handler_call(State=#state{handler=Handler}, Req, HandlerState,
  134. RemainingData, Message, NextState) ->
  135. case catch Handler:websocket_handle(Message, Req, HandlerState) of
  136. {ok, Req2, HandlerState2} ->
  137. NextState(State, Req2, HandlerState2, RemainingData);
  138. {reply, Data, Req2, HandlerState2} ->
  139. websocket_send(Data, Req2),
  140. NextState(State, Req2, HandlerState2, RemainingData);
  141. {shutdown, Req2, HandlerState2} ->
  142. websocket_close(State, Req2, HandlerState2, {normal, shutdown});
  143. {'EXIT', _Reason} ->
  144. websocket_close(State, Req, HandlerState, {error, handler})
  145. end.
  146. -spec websocket_send(binary(), #http_req{}) -> ok.
  147. websocket_send(Data, #http_req{socket=Socket, transport=Transport}) ->
  148. Transport:send(Socket, << 0, Data/binary, 255 >>).
  149. -spec websocket_close(#state{}, #http_req{}, any(), {atom(), atom()}) -> ok.
  150. websocket_close(State, Req=#http_req{socket=Socket, transport=Transport},
  151. HandlerState, Reason) ->
  152. Transport:send(Socket, << 255, 0 >>),
  153. Transport:close(Socket),
  154. handler_terminate(State, Req, HandlerState, Reason).
  155. -spec handler_terminate(#state{}, #http_req{},
  156. any(), atom() | {atom(), atom()}) -> ok.
  157. handler_terminate(#state{handler=Handler}, Req, HandlerState, Reason) ->
  158. Handler:websocket_terminate(Reason, Req, HandlerState).