Return-Path: X-Original-To: apmail-couchdb-commits-archive@www.apache.org Delivered-To: apmail-couchdb-commits-archive@www.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id 72B2A17C86 for ; Mon, 29 Jun 2015 21:46:10 +0000 (UTC) Received: (qmail 13346 invoked by uid 500); 29 Jun 2015 21:46:08 -0000 Delivered-To: apmail-couchdb-commits-archive@couchdb.apache.org Received: (qmail 13142 invoked by uid 500); 29 Jun 2015 21:46:08 -0000 Mailing-List: contact commits-help@couchdb.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@couchdb.apache.org Delivered-To: mailing list commits@couchdb.apache.org Received: (qmail 12531 invoked by uid 99); 29 Jun 2015 21:46:08 -0000 Received: from git1-us-west.apache.org (HELO git1-us-west.apache.org) (140.211.11.23) by apache.org (qpsmtpd/0.29) with ESMTP; Mon, 29 Jun 2015 21:46:07 +0000 Received: by git1-us-west.apache.org (ASF Mail Server at git1-us-west.apache.org, from userid 33) id E19F0E360C; Mon, 29 Jun 2015 21:46:07 +0000 (UTC) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit From: robertkowalski@apache.org To: commits@couchdb.apache.org Date: Mon, 29 Jun 2015 21:46:26 -0000 Message-Id: In-Reply-To: <41909276fb954bfdb4649bc815a971dd@git.apache.org> References: <41909276fb954bfdb4649bc815a971dd@git.apache.org> X-Mailer: ASF-Git Admin Mailer Subject: [20/50] couch commit: updated refs/heads/COUCHDB-2734-header-date to f3e022c module for websocket and example of usage Project: http://git-wip-us.apache.org/repos/asf/couchdb-couch/repo Commit: http://git-wip-us.apache.org/repos/asf/couchdb-couch/commit/58bc0bee Tree: http://git-wip-us.apache.org/repos/asf/couchdb-couch/tree/58bc0bee Diff: http://git-wip-us.apache.org/repos/asf/couchdb-couch/diff/58bc0bee Branch: refs/heads/COUCHDB-2734-header-date Commit: 58bc0beecd845ea594fdd90a8b4869590bdc25a9 Parents: 31efc60 Author: Łukasz Lalik Authored: Sun Dec 8 12:56:14 2013 +0100 Committer: Łukasz Lalik Committed: Sun Dec 8 12:56:14 2013 +0100 ---------------------------------------------------------------------- examples/websocket/websocket.erl | 49 ++++++ src/mochiweb_websocket.erl | 304 ++++++++++++++++++++++++++++++++++ 2 files changed, 353 insertions(+) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/58bc0bee/examples/websocket/websocket.erl ---------------------------------------------------------------------- diff --git a/examples/websocket/websocket.erl b/examples/websocket/websocket.erl new file mode 100644 index 0000000..14edb64 --- /dev/null +++ b/examples/websocket/websocket.erl @@ -0,0 +1,49 @@ +-module(websocket). + +% The MIT License (MIT) + +% Copyright (c) + +% 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. + +-export([start_link/0, ws_loop/4, loop/1]). + +start_link() -> + Loop = fun (Req) -> + ?MODULE:loop(Req) + end, + + mochiweb_http:start_link([ + {name, client_access}, + {loop, Loop}, + {port, 8080} + ]). + +ws_loop(Socket, Payload, Sid, WsVersion) -> + ReentryWs = mochiweb_websocket:reentry({?MODULE, ws_loop}), + io:format("Received data: ~p~n", [Payload]), + ReentryWs(Socket, Sid, WsVersion). + +loop(Req) -> + ReentryWs = mochiweb_websocket:reentry({?MODULE, ws_loop}), + WsVersion = mochiweb_websocket:upgrade_connection(Req), + + ReplySocket = Req:get(socket), + + ReentryWs(Req:get(socket), [], WsVersion). http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/58bc0bee/src/mochiweb_websocket.erl ---------------------------------------------------------------------- diff --git a/src/mochiweb_websocket.erl b/src/mochiweb_websocket.erl new file mode 100644 index 0000000..d1cc4b4 --- /dev/null +++ b/src/mochiweb_websocket.erl @@ -0,0 +1,304 @@ +-module(mochiweb_websocket). +-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. + +%% @doc Websockets module for Mochiweb. Based on Misultin websockets module. + +-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 + 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); + + (not (Sec1Key == undefined)) and (not (Sec2Key == undefined)) -> + Body = Req:recv(8), + hixie_handshake(Req, Sec1Key, Sec2Key, Body, Origin); + + true -> + mochiweb_socket:close(Req:get(socket)), + exit(normal) + end. + + +hybi_handshake(Req, SecKey) -> + Challenge = handshake_key(SecKey), + Req:respond({101, [{"Connection", "Upgrade"}, + {"Upgrade", "websocket"}, + {"Sec-Websocket-Accept", Challenge}], ""}), + hybi. + +hixie_handshake(Req, 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]), + Blank2 = length([D || D <- Key2, D =:= 32]), + Part1 = erlang:list_to_integer(Ikey1) div Blank1, + Part2 = erlang:list_to_integer(Ikey2) div Blank2, + Ckey = <>, + Challenge = erlang:md5(Ckey), + + Location = lists:concat(["ws://", + Req:get_header_value("Host"), + Req:get(path)]), + + Req:respond({101, [{"Upgrade", "WebSocket"}, + {"Connection", "Upgrade"}, + {"Sec-WebSocket-Origin", Origin}, + {"Sec-WebSocket-Location", Location}], + Challenge}), + hixie. + + + +send(Socket, Payload, hybi) -> + Len = payload_length(iolist_size(Payload)), + Data = <<1:1, 0:3, 1:4, 0:1, Len/bits, Payload/binary>>, + mochiweb_socket:send(Socket, Data); + +send(Socket, Payload, hixie) -> + Data = <<0, Payload/binary, 255>>, + mochiweb_socket:send(Socket, Data). + +request(Socket, Body, State, WsVersion) -> + receive + {tcp_closed, _} -> + mochiweb_socket:close(Socket), + exit(normal); + {ssl_closed, _} -> + mochiweb_socket:close(Socket), + exit(normal); + {tcp_error, _, _} -> + mochiweb_socket:close(Socket), + exit(normal); + + {tcp, _, WsFrames} -> + {M, F} = Body, + case WsVersion of + hybi -> + Reply = fun(close) -> + mochiweb_socket:close(Socket), + exit(normal); + (Payload) -> + M:F(Socket, Payload, State, WsVersion) + end, + + try parse_frames(Socket, WsFrames, []) of + Parsed -> process_frames(Parsed, Reply, []) + catch + _:_ -> Reply(close) + end; + + hixie -> + try handle_frames(WsFrames, []) of + Payload -> M:F(Socket, Payload, State, WsVersion) + catch + _:_ -> + mochiweb_socket:close(Socket), + exit(normal) + end + + end; + + _ -> + handle_invalid_request(Socket) + end. + +process_frames([], Reply, Acc) -> + Reply(lists:reverse(Acc)); + +process_frames([{Opcode, Payload} | Rest], Reply, Acc) -> + case Opcode of + 8 -> + Reply(lists:reverse(Acc)), + Reply(close); + + _ -> + process_frames(Rest, Reply, [Payload | Acc]) + end. + +reentry(Body) -> + fun (Socket, State, WsVersion) -> + ?MODULE:after_response(Body, Socket, State, WsVersion) + end. + +-spec handle_invalid_request(term()) -> no_return(). +handle_invalid_request(Socket) -> + mochiweb_socket:close(Socket), + exit(normal). + +after_response(Body, Socket, State, WsVersion) -> + ?MODULE:loop(Socket, Body, State, WsVersion). + +%% +%% Websockets internal functions for RFC6455 (hybi) +%% + +% RFC6455 Handshake +handshake_key(Key) -> + BinKey = list_to_binary(Key), + Bin = <>, + base64:encode(crypto:hash(sha, Bin)). + +parse_frames(_, <<>>, Acc) -> + lists:reverse(Acc); + +parse_frames(S, <<_Fin:1, + _Rsv:3, + Opcode:4, + _Mask:1, + PayloadLen:7, + MaskKey:4/binary, + Payload:PayloadLen/binary-unit:8, + Rest/binary>>, + Acc) when PayloadLen < 126 -> + + Payload2 = extract_payload(MaskKey, Payload), + parse_frames(S, Rest, [{Opcode, Payload2} | Acc]); + +parse_frames(S, <<_Fin:1, + _Rsv:3, + Opcode:4, + _Mask:1, + 126:7, + PayloadLen:16, + MaskKey:4/binary, + Payload:PayloadLen/binary-unit:8, + Rest/binary>>, + Acc) -> + + Payload2 = extract_payload(MaskKey, Payload), + parse_frames(S, Rest, [{Opcode, Payload2} | Acc]); + +parse_frames(Socket, <<_Fin:1, + _Rsv:3, + _Opcode:4, + _Mask:1, + 126:7, + _PayloadLen:16, + _MaskKey:4/binary, + _/binary-unit:8>> = PartFrame, + Acc) -> + + ok = mochiweb_socket:setopts(Socket, [{packet, 0}, {active, once}]), + receive + {tcp_closed, _} -> + mochiweb_socket:close(Socket), + exit(normal); + {ssl_closed, _} -> + mochiweb_socket:close(Socket), + exit(normal); + {tcp_error, _, _} -> + mochiweb_socket:close(Socket), + exit(normal); + + {tcp, _, Continuation} -> + parse_frames(Socket, <>, Acc); + + _ -> + mochiweb_socket:close(Socket), + exit(normal) + after + 5000 -> + mochiweb_socket:close(Socket), + exit(normal) + end; + +parse_frames(S, <<_Fin:1, + _Rsv:3, + Opcode:4, + _Mask:1, + 127:7, + 0:1, + PayloadLen:63, + MaskKey:4/binary, + Payload:PayloadLen/binary-unit:8, + Rest/binary>>, + Acc) -> + + Payload2 = extract_payload(MaskKey, Payload), + parse_frames(S, Rest, [{Opcode, Payload2} | Acc]). + +extract_payload(MaskKey, Payload) -> + websocket_unmask(Payload, MaskKey, <<>>). + +% Unmasks RFC 6455 message +websocket_unmask(<>, MaskKey, Acc) -> + <> = MaskKey, + T = O bxor MaskKey2, + websocket_unmask(Rest, MaskKey, <>); +websocket_unmask(<>, MaskKey, Acc) -> + <> = MaskKey, + T = O bxor MaskKey2, + <>; +websocket_unmask(<>, MaskKey, Acc) -> + <> = MaskKey, + T = O bxor MaskKey2, + <>; +websocket_unmask(<>, MaskKey, Acc) -> + <> = MaskKey, + T = O bxor MaskKey2, + <>; +websocket_unmask(<<>>, _MaskKey, Acc) -> + Acc. + +payload_length(N) -> + case N of + N when N =< 125 -> << N:7 >>; + N when N =< 16#ffff -> << 126:7, N:16 >>; + N when N =< 16#7fffffffffffffff -> << 127:7, N:64 >> + end. + + +%% +%% Websockets internal functions for hixie-76 websocket version +%% + +handle_frames(<<>>, Frames) -> + lists:reverse(Frames); +handle_frames(<<0, T/binary>>, Frames) -> + {Frame, Rest} = handle_frame(T, <<>>), + handle_frames(Rest, [Frame | Frames]). + +handle_frame(<<255, Rest/binary>>, Buffer) -> + {Buffer, Rest}; +handle_frame(<>, Buffer) -> + handle_frame(T, <>).