tests for websocket handshake and frames parsing
Project: http://git-wip-us.apache.org/repos/asf/couchdb-mochiweb/repo Commit: http://git-wip-us.apache.org/repos/asf/couchdb-mochiweb/commit/2eb1c563 Tree: http://git-wip-us.apache.org/repos/asf/couchdb-mochiweb/tree/2eb1c563 Diff: http://git-wip-us.apache.org/repos/asf/couchdb-mochiweb/diff/2eb1c563 Branch: refs/heads/1843-feature-bigcouch Commit: 2eb1c563ca934b53a935c3b96ff5fba135cab9f8 Parents: cef2055 Author: Åukasz Lalik <[email protected]> Authored: Wed Dec 25 01:25:24 2013 +0100 Committer: Åukasz Lalik <[email protected]> Committed: Wed Dec 25 01:25:24 2013 +0100 ---------------------------------------------------------------------- src/mochiweb_websocket.erl | 65 +++++++++++++++++--------------- src/mochiweb_websocket_tests.erl | 70 +++++++++++++++++++++++++++++++++++ 2 files changed, 106 insertions(+), 29 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/couchdb-mochiweb/blob/2eb1c563/src/mochiweb_websocket.erl ---------------------------------------------------------------------- diff --git a/src/mochiweb_websocket.erl b/src/mochiweb_websocket.erl index d1cc4b4..12bf84a 100644 --- a/src/mochiweb_websocket.erl +++ b/src/mochiweb_websocket.erl @@ -28,45 +28,47 @@ -export([loop/4, upgrade_connection/1, request/4]). -export([after_response/4, reentry/1, send/3]). --define(REQUEST_RECV_TIMEOUT, 3000000). %% timeout waiting for request line --define(HEADERS_RECV_TIMEOUT, 30000). %% timeout waiting for headers - --define(MAX_HEADERS, 1000). --define(DEFAULTS, [{name, ?MODULE}, - {port, 8888}]). - loop(Socket, Body, State, WsVersion) -> ok = mochiweb_socket:setopts(Socket, [{packet, 0}, {active, once}]), proc_lib:hibernate(?MODULE, request, [Socket, Body, State, WsVersion]). upgrade_connection(Req) -> - % Headers for HYBI handshake + case make_handshake(Req) of + {Version, Response} -> + Req:respond(Response), + Version; + + _ -> + mochiweb_socket:close(Req:get(socket)), + exit(normal) + end. + +make_handshake(Req) -> SecKey = Req:get_header_value("sec-websocket-key"), Sec1Key = Req:get_header_value("Sec-WebSocket-Key1"), Sec2Key = Req:get_header_value("Sec-WebSocket-Key2"), Origin = Req:get_header_value(origin), - if not (SecKey == undefined) -> - hybi_handshake(Req, SecKey); + hybi_handshake(SecKey); - (not (Sec1Key == undefined)) and (not (Sec2Key == undefined)) -> - Body = Req:recv(8), - hixie_handshake(Req, Sec1Key, Sec2Key, Body, Origin); + (not (Sec1Key == undefined)) and (not (Sec2Key == undefined)) -> + Host = Req:get_header_value("Host"), + Path = Req:get(path), + Body = Req:recv(8), + hixie_handshake(Host, Path, Sec1Key, Sec2Key, Body, Origin); true -> - mochiweb_socket:close(Req:get(socket)), - exit(normal) + error end. - -hybi_handshake(Req, SecKey) -> +hybi_handshake(SecKey) -> Challenge = handshake_key(SecKey), - Req:respond({101, [{"Connection", "Upgrade"}, + Response = {101, [{"Connection", "Upgrade"}, {"Upgrade", "websocket"}, - {"Sec-Websocket-Accept", Challenge}], ""}), - hybi. + {"Sec-Websocket-Accept", Challenge}], ""}, + {hybi, Response}. -hixie_handshake(Req, Key1, Key2, Body, Origin) -> +hixie_handshake(Host, Path, Key1, Key2, Body, Origin) -> Ikey1 = [D || D <- Key1, $0 =< D, D =< $9], Ikey2 = [D || D <- Key2, $0 =< D, D =< $9], Blank1 = length([D || D <- Key1, D =:= 32]), @@ -76,18 +78,14 @@ hixie_handshake(Req, Key1, Key2, Body, Origin) -> Ckey = <<Part1:4/big-unsigned-integer-unit:8, Part2:4/big-unsigned-integer-unit:8, Body/binary>>, Challenge = erlang:md5(Ckey), - Location = lists:concat(["ws://", - Req:get_header_value("Host"), - Req:get(path)]), + Location = lists:concat(["ws://", Host, Path]), - Req:respond({101, [{"Upgrade", "WebSocket"}, + Response = {101, [{"Upgrade", "WebSocket"}, {"Connection", "Upgrade"}, {"Sec-WebSocket-Origin", Origin}, {"Sec-WebSocket-Location", Location}], - Challenge}), - hixie. - - + Challenge}, + {hixie, Response}. send(Socket, Payload, hybi) -> Len = payload_length(iolist_size(Payload)), @@ -121,6 +119,7 @@ request(Socket, Body, State, WsVersion) -> M:F(Socket, Payload, State, WsVersion) end, + io:format("Handle: ~p~n", [WsFrames]), try parse_frames(Socket, WsFrames, []) of Parsed -> process_frames(Parsed, Reply, []) catch @@ -302,3 +301,11 @@ handle_frame(<<255, Rest/binary>>, Buffer) -> {Buffer, Rest}; handle_frame(<<H, T/binary>>, Buffer) -> handle_frame(T, <<Buffer/binary, H>>). + +%% +%% Tests +%% +-ifdef(TEST). +-include_lib("eunit/include/eunit.hrl"). +-compile(export_all). +-endif. http://git-wip-us.apache.org/repos/asf/couchdb-mochiweb/blob/2eb1c563/src/mochiweb_websocket_tests.erl ---------------------------------------------------------------------- diff --git a/src/mochiweb_websocket_tests.erl b/src/mochiweb_websocket_tests.erl new file mode 100644 index 0000000..272b188 --- /dev/null +++ b/src/mochiweb_websocket_tests.erl @@ -0,0 +1,70 @@ +-module(mochiweb_websocket_tests). +-author('[email protected]'). + +%% The MIT License (MIT) + +%% Copyright (c) 2012 Zadane.pl sp. z o.o. + +%% Permission is hereby granted, free of charge, to any person obtaining a copy +%% of this software and associated documentation files (the "Software"), to deal +%% in the Software without restriction, including without limitation the rights +%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +%% copies of the Software, and to permit persons to whom the Software is +%% furnished to do so, subject to the following conditions: + +%% The above copyright notice and this permission notice shall be included in +%% all copies or substantial portions of the Software. + +%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +%% THE SOFTWARE. + +-ifdef(TEST). +-include_lib("eunit/include/eunit.hrl"). + +make_handshake_for_correct_client_test() -> + %% Hybi handshake + Req1 = mochiweb_request:new(nil, 'GET', "/foo", {1, 1}, + mochiweb_headers:make([{"Sec-WebSocket-Key", "Xn3fdKyc3qEXPuj2A3O+ZA=="}])), + + {Version1, {HttpCode1, Headers1, _}} = mochiweb_websocket:make_handshake(Req1), + ?assertEqual(hybi, Version1), + ?assertEqual(101, HttpCode1), + ?assertEqual("Upgrade", proplists:get_value("Connection", Headers1)), + ?assertEqual(<<"BIFTHkJk4r5t8kuud82tZJaQsCE=">>, proplists:get_value("Sec-Websocket-Accept", Headers1)), + + %% Hixie handshake + {Version2, {HttpCode2, Headers2, Body2}} = mochiweb_websocket:hixie_handshake( + "localhost", "/", + "33j284 9 z63 e 9 7", + "TF'3|6D12659H 7 70", + <<175,181,191,215,128,195,144,120>>, + "null"), + ?assertEqual(hixie, Version2), + ?assertEqual(101, HttpCode2), + ?assertEqual("null", proplists:get_value("Sec-WebSocket-Origin", Headers2)), + ?assertEqual("ws://localhost/", proplists:get_value("Sec-WebSocket-Location", Headers2)), + ?assertEqual(<<230,144,237,94,84,214,41,69,244,150,134,167,221,103,239,246>>, Body2). + +hybi_frames_decode_test() -> + Resp1 = mochiweb_websocket:parse_frames(nil, <<129,131,118,21,153,58,16,122,246>>, []), + ?assertEqual([{1, <<"foo">>}], Resp1), + + Resp2 = mochiweb_websocket:parse_frames(nil, <<129,131,1,225,201,42,103,142,166,129,131,93,222,214,66,63,191,164>>, []), + ?assertEqual([{1, <<"foo">>}, {1, <<"bar">>}], Resp2). + +hixie_frames_decode_test() -> + Resp1 = mochiweb_websocket:handle_frames(<<>>, []), + ?assertEqual([], Resp1), + + Resp2 = mochiweb_websocket:handle_frames(<<0,102,111,111,255>>, []), + ?assertEqual([<<"foo">>], Resp2), + + Resp3 = mochiweb_websocket:handle_frames(<<0,102,111,111,255,0,98,97,114,255>>, []), + ?assertEqual([<<"foo">>, <<"bar">>], Resp3). + +-endif.
