Subversion Repositories SE.SVN

Rev

Blame | Last modification | View Log | RSS feed

-module(mochiweb_websocket_tests).

-author('lukasz.lalik@zadane.pl').

%% 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.

-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("ws://", "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() ->
    ?assertEqual([{1, <<"foo">>}],
                 (mochiweb_websocket:parse_hybi_frames(nil,
                                                       <<129, 131, 118, 21, 153,
                                                         58, 16, 122, 246>>,
                                                       []))),
    ?assertEqual([{1, <<"foo">>}, {1, <<"bar">>}],
                 (mochiweb_websocket:parse_hybi_frames(nil,
                                                       <<129, 131, 1, 225, 201,
                                                         42, 103, 142, 166, 129,
                                                         131, 93, 222, 214, 66,
                                                         63, 191, 164>>,
                                                       []))).

hixie_frames_decode_test() ->
    ?assertEqual([],
                 (mochiweb_websocket:parse_hixie_frames(<<>>, []))),
    ?assertEqual([<<"foo">>],
                 (mochiweb_websocket:parse_hixie_frames(<<0, 102, 111,
                                                          111, 255>>,
                                                        []))),
    ?assertEqual([<<"foo">>, <<"bar">>],
                 (mochiweb_websocket:parse_hixie_frames(<<0, 102, 111,
                                                          111, 255, 0, 98, 97,
                                                          114, 255>>,
                                                        []))).

end_to_end_test_factory(ServerTransport) ->
    mochiweb_test_util:with_server(ServerTransport,
                                   fun end_to_end_server/1,
                                   fun (Transport, Port) ->
                                           end_to_end_client(mochiweb_test_util:sock_fun(Transport,
                                                                                         Port))
                                   end).

end_to_end_server(Req) ->
    ?assertEqual("Upgrade",
                 (mochiweb_request:get_header_value("connection", Req))),
    ?assertEqual("websocket",
                 (mochiweb_request:get_header_value("upgrade", Req))),
    {ReentryWs, _ReplyChannel} =
        mochiweb_websocket:upgrade_connection(Req,
                                              fun end_to_end_ws_loop/3),
    ReentryWs(ok).

end_to_end_ws_loop(Payload, State, ReplyChannel) ->
    %% Echo server
    lists:foreach(ReplyChannel, Payload),
    State.

end_to_end_client(S) ->
    %% Key and Accept per https://tools.ietf.org/html/rfc6455
    UpgradeReq = string:join(["GET / HTTP/1.1",
                              "Host: localhost", "Upgrade: websocket",
                              "Connection: Upgrade",
                              "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==", "",
                              ""],
                             "\r\n"),
    ok = S({send, UpgradeReq}),
    {ok, {http_response, {1, 1}, 101, _}} = S(recv),
    read_expected_headers(S,
                          [{'Upgrade', "websocket"}, {'Connection', "Upgrade"},
                           {'Content-Length', "0"},
                           {"Sec-Websocket-Accept",
                            "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="}]),
    %% The first message sent over telegraph :)
    SmallMessage = <<"What hath God wrought?">>,
    ok = S({send,
            <<1:1, %% Fin
              0:1, %% Rsv1
              0:1, %% Rsv2
              0:1, %% Rsv3
              2:4, %% Opcode, 1 = text frame
              1:1, %% Mask on
              (byte_size(SmallMessage)):7, %% Length, <125 case
              0:32, %% Mask (trivial)
              SmallMessage/binary>>}),
    {ok, WsFrames} = S(recv),
    <<1:1, %% Fin
      0:1, %% Rsv1
      0:1, %% Rsv2
      0:1, %% Rsv3
      1:4, %% Opcode, text frame (all mochiweb suports for now)
      MsgSize:8, %% Expecting small size
      SmallMessage/binary>> =
        WsFrames,
    ?assertEqual(MsgSize, (byte_size(SmallMessage))),
    ok.

read_expected_headers(S, D) ->
    Headers = mochiweb_test_util:read_server_headers(S),
    lists:foreach(fun ({K, V}) ->
                          ?assertEqual(V,
                                       (mochiweb_headers:get_value(K, Headers)))
                  end,
                  D).

end_to_end_http_test() ->
    end_to_end_test_factory(plain).

end_to_end_https_test() -> end_to_end_test_factory(ssl).