You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by wi...@apache.org on 2020/01/13 19:31:29 UTC

[couchdb-mochiweb] tag active-passive created (now ecc6f85)

This is an automated email from the ASF dual-hosted git repository.

willholley pushed a change to tag active-passive
in repository https://gitbox.apache.org/repos/asf/couchdb-mochiweb.git.


      at ecc6f85  (commit)
This tag includes the following new commits:

     new e6386a6  * Support modern and legacy websockets spec * Create websockets example * Currently websocket is 'passive', call get_data() for each frame
     new b752a34  use mochiweb_headers when checking websocket headers
     new 101bfc5  refactor websocket init code a little. some style fixes
     new 4146cdf  partial work on active websocket api
     new 26f9c01  begin restructure
     new b0e3897  clean up websocket options parsing, create active and passive examples
     new e724f27  refactor and general tidy-up of websockets detection and examples. origin validation.
     new ecc6f85  make wss:// websockets work over https

The 8 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



[couchdb-mochiweb] 07/08: refactor and general tidy-up of websockets detection and examples. origin validation.

Posted by wi...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

willholley pushed a commit to tag active-passive
in repository https://gitbox.apache.org/repos/asf/couchdb-mochiweb.git

commit e724f2718f880c23f91e7bd48fa088f2d63f5658
Author: Richard Jones <rj...@metabrew.com>
AuthorDate: Sat Nov 20 21:59:41 2010 +0000

    refactor and general tidy-up of websockets detection and examples. origin validation.
---
 examples/websockets/README.txt             |  9 +++
 examples/websockets/websockets_active.erl  | 16 +++--
 examples/websockets/websockets_passive.erl | 29 ++++-----
 src/mochiweb_http.erl                      | 96 +++++++++++++++++++-----------
 src/mochiweb_websocket_delegate.erl        | 23 +++----
 src/mochiweb_wsrequest.erl                 |  6 +-
 6 files changed, 106 insertions(+), 73 deletions(-)

diff --git a/examples/websockets/README.txt b/examples/websockets/README.txt
new file mode 100644
index 0000000..378e274
--- /dev/null
+++ b/examples/websockets/README.txt
@@ -0,0 +1,9 @@
+There are two ways to use websockets, active and passive.
+
+In passive mode, you can call the blocking get_data() to read a frame 
+from the websocket. Passive mode is best suited to a transmit-only
+server, or where you read from the client then send, then read in a loop.
+
+Active mode allows for fully asynchronous sending and receiving. 
+Your process gets sent {frame, Msg} when messages arrive, and you can
+call send at any time.
diff --git a/examples/websockets/websockets_active.erl b/examples/websockets/websockets_active.erl
index 53fa3ed..1ed6ba3 100644
--- a/examples/websockets/websockets_active.erl
+++ b/examples/websockets/websockets_active.erl
@@ -8,9 +8,16 @@ start() -> start([{port, 8080}, {docroot, "."}]).
 start(Options) ->
     {DocRoot, Options1} = get_option(docroot, Options),
     Loop = fun (Req) -> ?MODULE:loop(Req, DocRoot) end,
-    % websockets options:
+    % How we validate origin for cross-domain checks:
+    OriginValidator = fun(Origin) ->
+                           io:format("Origin '~s' -> OK~n", [Origin]),
+                           true
+                      end,
+    % websocket options
     WsOpts = [ {active, true},
+               {origin_validator, OriginValidator},
                {loop,   {?MODULE, wsloop_active}} ],
+    
     mochiweb_http:start([{name, ?MODULE}, 
                          {loop, Loop},
                          {websocket_opts, WsOpts} | Options1]).
@@ -19,7 +26,7 @@ stop() ->
     mochiweb_http:stop(?MODULE).
 
 wsloop_active(Pid) ->
-    mochiweb_websocket_delegate:send(Pid, "WELCOME MSG!"),
+    mochiweb_websocket_delegate:send(Pid, "WELCOME MSG FROM THE SERVER!"),
     wsloop_active0(Pid).
 
 wsloop_active0(Pid) ->
@@ -30,9 +37,8 @@ wsloop_active0(Pid) ->
         {error, Reason} ->
             io:format("client api got error ~p~n", [Reason]),
             ok;
-        % {legacy_frame, M} or {utf8_frame, M}
-        {_, X} ->
-            Msg = io_lib:format("SRVER_GOT: ~p", [X]),
+        {frame, Frame} ->
+            Msg = ["Dear client, thanks for sending us this msg: ", Frame],
             mochiweb_websocket_delegate:send(Pid, Msg),
             wsloop_active0(Pid)
     after 10000 ->
diff --git a/examples/websockets/websockets_passive.erl b/examples/websockets/websockets_passive.erl
index 7632799..4e4151f 100644
--- a/examples/websockets/websockets_passive.erl
+++ b/examples/websockets/websockets_passive.erl
@@ -8,8 +8,14 @@ start() -> start([{port, 8080}, {docroot, "."}]).
 start(Options) ->
     {DocRoot, Options1} = get_option(docroot, Options),
     Loop = fun (Req) -> ?MODULE:loop(Req, DocRoot) end,
+    % How we validate origin for cross-domain checks:
+    OriginValidator = fun(Origin) ->
+                           io:format("Origin '~s' -> OK~n", [Origin]),
+                           true
+                      end,
     % websockets options:
     WsOpts = [ {active, false},
+               {origin_validator, OriginValidator},
                {loop,   {?MODULE, wsloop}} ],
     mochiweb_http:start([{name, ?MODULE}, 
                          {loop, Loop},
@@ -19,23 +25,18 @@ stop() ->
     mochiweb_http:stop(?MODULE).
 
 wsloop(Ws) ->
-    io:format("Websocket request, path: ~p~n", [Ws:get(path)]),
+    Ws:send("WELCOME MSG FROM THE SERVER part 1/2"),
+    Ws:send("WELCOME MSG FROM THE SERVER part 2/2"),
+    wsloop0(Ws).
+
+wsloop0(Ws) ->
     case Ws:get_data() of
-        closed ->  ok;
+        closed  ->  ok;
         closing -> ok;
         timeout -> timeout;
-        
-        % older websockets spec which is in the wild, messages are framed with
-        % 0x00...0xFF
-        {legacy_frame, Body} ->
-            Ws:send(["YOU SENT US LEGACY FRAME: ", Body]),
-            wsloop(Ws);
-    
-        % current spec, each message has a 0xFF/<64bit length> header
-        % and must contain utf8 bytestream
-        {utf8_frame, Body} ->
-            Ws:send(["YOU SENT US MODERN FRAME: ", Body]),
-            wsloop(Ws)
+        {frame, Body} ->
+            Ws:send(["Dear client, thanks for sending this message: ", Body]),
+            wsloop0(Ws)
     end.
 
 loop(Req, DocRoot) ->
diff --git a/src/mochiweb_http.erl b/src/mochiweb_http.erl
index 7da890c..55bef18 100644
--- a/src/mochiweb_http.erl
+++ b/src/mochiweb_http.erl
@@ -17,22 +17,32 @@
 -define(DEFAULTS, [{name, ?MODULE},
                    {port, 8888}]).
 
-% client loop holds fun/info on how to hand off request to client code
--record(body, {http_loop, websocket_loop, websocket_active}). 
+%% unless specified, we accept any origin:
+-define(DEFAULT_ORIGIN_VALIDATOR, fun(_Origin) -> true end).
+
+-record(body, {http_loop,                   % normal http handler fun
+               websocket_loop,              % websocket handler fun
+               websocket_active,            % boolean: active or passive api
+               websocket_origin_validator   % fun(Origin) -> true/false
+              }). 
 
 parse_options(Options) ->
     HttpLoop = proplists:get_value(loop, Options),
     case proplists:get_value(websocket_opts, Options) of
         WsProps when is_list(WsProps) ->
             WsLoop   = proplists:get_value(loop, WsProps),
+            WsOrigin = proplists:get_value(origin_validator, WsProps, 
+                                           ?DEFAULT_ORIGIN_VALIDATOR),
             WsActive = proplists:get_value(active, WsProps, false);
         _ ->
             WsLoop   = undefined,
+            WsOrigin = undefined,
             WsActive = undefined
     end,
-    Body = #body{http_loop        = HttpLoop,
-                 websocket_loop   = WsLoop,
-                 websocket_active = WsActive},
+    Body = #body{http_loop                  = HttpLoop,
+                 websocket_loop             = WsLoop,
+                 websocket_origin_validator = WsOrigin,
+                 websocket_active           = WsActive},
     Loop = fun (S) -> ?MODULE:loop(S, Body) end,
     Options1 = [{loop, Loop} | 
                     proplists:delete(loop, 
@@ -135,35 +145,11 @@ headers(Socket, Request, Headers, Body, HeaderCount) ->
     case mochiweb_socket:recv(Socket, 0, ?HEADERS_RECV_TIMEOUT) of
         {ok, http_eoh} ->
             mochiweb_socket:setopts(Socket, [{packet, raw}]),
-            %% Examine headers to decide if this a websocket upgrade request:
             H = mochiweb_headers:make(Headers),
-            HeaderFun = fun(K) -> 
-                            case mochiweb_headers:get_value(K, H) of
-                                undefined -> "";
-                                V         -> string:to_lower(V)
-                            end
-                        end,
-            case {HeaderFun("upgrade"), HeaderFun("connection")} of
-                {"websocket", "upgrade"} ->
-                    io:format("notmal -> ws~n",[]),
-                    {_, {abs_path,Path}, _} = Request,
-                    ok = websocket_init(Socket, Path, H),
-                    case Body#body.websocket_active of
-                        true ->
-                            {ok, WSPid} = mochiweb_websocket_delegate:start_link(Path, H, self()),
-                            mochiweb_websocket_delegate:go(WSPid, Socket),
-                            call_body(Body#body.websocket_loop, WSPid);
-                        false ->
-                            WsReq = mochiweb_wsrequest:new(Socket, Path, H),
-                            call_body(Body#body.websocket_loop, WsReq);
-                        undefined ->
-                            Req = mochiweb:new_request({Socket, Request,
-                                                lists:reverse(Headers)}),
-                            io:format("Websocket upgrade requested, but no websocket handler provided: ~s~n",[Req:get(path)]),
-                            Req:not_found()
-                    end;
-                X -> %% not websocket:
-                    io:format("notmal~p~n",[X]),
+            case is_websocket_upgrade_requested(H) of
+                true ->
+                    headers_ws_upgrade(Socket, Request, Headers, Body, H);
+                false ->
                     Req = mochiweb:new_request({Socket, Request,
                                                 lists:reverse(Headers)}),
                     call_body(Body#body.http_loop, Req),
@@ -179,6 +165,36 @@ headers(Socket, Request, Headers, Body, HeaderCount) ->
             handle_invalid_request(Socket, Request, Headers)
     end.
 
+% checks if these headers are a valid websocket upgrade request
+is_websocket_upgrade_requested(H) ->
+    Hdr = fun(K) -> case mochiweb_headers:get_value(K, H) of
+                        undefined         -> undefined;
+                        V when is_list(V) -> string:to_lower(V)
+                    end
+          end,
+    Hdr("upgrade") == "websocket" andalso Hdr("connection") == "upgrade".
+
+% entered once we've seen valid websocket upgrade headers
+headers_ws_upgrade(Socket, Request, Headers, Body, H) ->
+    {_, {abs_path,Path}, _} = Request,
+    OriginValidator = Body#body.websocket_origin_validator,
+    % websocket_init will exit() if anything looks fishy
+    websocket_init(Socket, Path, H, OriginValidator),
+    case Body#body.websocket_active of
+        true ->
+            {ok, WSPid} = mochiweb_websocket_delegate:start_link(Path,H,self()),
+            mochiweb_websocket_delegate:go(WSPid, Socket),
+            call_body(Body#body.websocket_loop, WSPid);
+        false ->
+            WsReq = mochiweb_wsrequest:new(Socket, Path, H),
+            call_body(Body#body.websocket_loop, WsReq);
+        undefined ->
+            % what is the correct way to respond when a server doesn't
+            % support websockets, but the client requests the upgrade?
+            % use a 400 for now:
+            handle_invalid_request(Socket, Request, Headers)
+    end.
+
 call_body({M, F}, Req) ->
     M:F(Req);
 call_body(Body, Req) ->
@@ -246,9 +262,19 @@ range_skip_length(Spec, Size) ->
 
 %% Respond to the websocket upgrade request with valid signature
 %% or exit() if any of the sec- headers look suspicious.
-websocket_init(Socket, Path, Headers) ->
+websocket_init(Socket, Path, Headers, OriginValidator) ->
+    Origin   = mochiweb_headers:get_value("origin", Headers),
+    %% If origin is invalid, just uncerimoniously close the socket
+    case Origin /= undefiend andalso OriginValidator(Origin) == true of
+        true ->
+            websocket_init_with_origin_validated(Socket, Path, Headers, Origin);
+        false ->
+            mochiweb_socket:close(Socket),
+            exit(websocket_origin_check_failed)
+    end.
+
+websocket_init_with_origin_validated(Socket, Path, Headers, _Origin) ->    
     Host     = mochiweb_headers:get_value("Host", Headers),
-    %Origin  = mochiweb_headers:get_value("origin", Headers), % TODO
     SubProto = mochiweb_headers:get_value("Sec-Websocket-Protocol", Headers),
     Key1     = mochiweb_headers:get_value("Sec-Websocket-Key1", Headers),
     Key2     = mochiweb_headers:get_value("Sec-Websocket-Key2", Headers),
diff --git a/src/mochiweb_websocket_delegate.erl b/src/mochiweb_websocket_delegate.erl
index e77c79e..6682b1b 100644
--- a/src/mochiweb_websocket_delegate.erl
+++ b/src/mochiweb_websocket_delegate.erl
@@ -10,7 +10,9 @@
 %% an older version of the websocket spec, where messages are framed 0x00...0xFF
 %% so the newer protocol with length headers has not been tested with a browser.
 %%
-%% Guarantees that 'closed' will be sent to the client pid once the socket dies
+%% Guarantees that 'closed' will be sent to the client pid once the socket dies,
+%% Messages are:
+%%  closed, {error, Reason}, {frame, Data}
 
 -module(mochiweb_websocket_delegate).
 -behaviour(gen_server).
@@ -40,7 +42,6 @@ go(Pid, Socket) ->
     gen_server:cast(Pid, {go, Socket}).
 
 send(Pid, Msg) ->
-    io:format("send:~s~n",[Msg]),
     gen_server:call(Pid, {send, Msg}).
 
 close(Pid) ->
@@ -87,20 +88,16 @@ handle_cast({go, Socket}, State) ->
     {noreply, State#state{socket=Socket}}.
 
 handle_info({'EXIT', _, _}, State) ->
-    io:format("TRAP EXIT~n",[]),
     State#state.dest ! closed,
     {stop, normal, State};    
 handle_info({tcp_closed, Sock}, State = #state{socket=Sock}) ->
-    io:format("TCP CLOSED~n",[]),
     State#state.dest ! closed,
     {stop, normal, State};
 handle_info({tcp_error, Sock, Reason}, State = #state{socket=Sock}) ->
-    io:format("TCP ERROR~n",[]),
     State#state.dest ! {error, Reason},
     State#state.dest ! closed,
     {stop, normal, State};
 handle_info({tcp, Sock, Data}, State = #state{socket=Sock, buffer=Buffer}) ->
-    %mochiweb_socket:setopts(Sock, [{active, once}]),
     NewState = process_data(State#state{buffer= <<Buffer/binary,Data/binary>>}),
     {noreply, NewState}.
 
@@ -113,38 +110,33 @@ code_change(_OldVsn, State, _Extra) ->
 %%% Internal functions
 
 process_data(State = #state{buffer= <<>>}) -> 
-    %io:format("A 0~n", []),
     State;
 
 process_data(State = #state{buffer= <<FrameType:8,Buffer/binary>>, ft=undefined}) ->
-    %io:format("A 1~n", []),
     process_data(State#state{buffer=Buffer, ft=FrameType, partial= <<>>});
 
 % "Legacy" frames, 0x00...0xFF
 % or modern closing handshake 0x00{8}
 process_data(State = #state{buffer= <<0,0,0,0,0,0,0,0, Buffer/binary>>, ft=0}) ->
-    %io:format("A 2~n", []),
     State#state.dest ! closing_handshake,
     process_data(State#state{buffer=Buffer, ft=undefined});
 
 process_data(State = #state{buffer= <<255, Rest/binary>>, ft=0}) ->
-    %io:format("A 3~n", []),
-    State#state.dest ! {legacy_frame, State#state.partial},
+    % message received in full
+    State#state.dest ! {frame, State#state.partial},
     process_data(State#state{partial= <<>>, ft=undefined, buffer=Rest});
 
 process_data(State = #state{buffer= <<Byte:8, Rest/binary>>, ft=0, partial=Partial}) ->
-    %io:format("A 4, byte=~p state:~p~n", [Byte,State]),
     NewPartial = case Partial of <<>> -> <<Byte>> ; _ -> <<Partial/binary, <<Byte>>/binary>> end,
     NewState = State#state{buffer=Rest, partial=NewPartial},
    process_data(NewState);
 
 % "Modern" frames, starting with 0xFF, followed by 64 bit length
 process_data(State = #state{buffer= <<Len:64/unsigned-integer,Buffer/binary>>, ft=255, flen=undefined}) ->
-    %io:format("A 5~n", []),
     BitsLen = Len*8,
     case Buffer of
         <<Frame:BitsLen/binary, Rest/binary>> ->            
-            State#state.dest ! {utf8_frame, Frame},
+            State#state.dest ! {frame, Frame},
             process_data(State#state{ft=undefined, flen=undefined, buffer=Rest});
 
         _ ->
@@ -152,11 +144,10 @@ process_data(State = #state{buffer= <<Len:64/unsigned-integer,Buffer/binary>>, f
     end;
 
 process_data(State = #state{buffer=Buffer, ft=255, flen=Len}) when is_integer(Len) ->
-    %io:format("A 6~n", []),
     BitsLen = Len*8,
     case Buffer of
         <<Frame:BitsLen/binary, Rest/binary>> ->            
-            State#state.dest ! {utf8_frame, Frame},
+            State#state.dest ! {frame, Frame},
             process_data(State#state{ft=undefined, flen=undefined, buffer=Rest});
 
         _ ->
diff --git a/src/mochiweb_wsrequest.erl b/src/mochiweb_wsrequest.erl
index 1d107c2..9991e3b 100644
--- a/src/mochiweb_wsrequest.erl
+++ b/src/mochiweb_wsrequest.erl
@@ -27,7 +27,7 @@ get_data() ->
                     {ok, <<Len:64/unsigned-integer>>}  = 
                         mochiweb_socket:recv(Socket, 8, ?TIMEOUT),
                     {ok, Frame} = mochiweb_socket:recv(Socket, Len, ?TIMEOUT),
-                    {utf8_frame, Frame};
+                    {frame, Frame};
                 <<0>> ->   % modern close request, or older no-length-frame msg
                     case mochiweb_socket:recv(Socket, 1, ?TIMEOUT) of
                         {ok, <<0>>} ->
@@ -37,12 +37,12 @@ get_data() ->
                         {ok, <<255>>} ->
                             % empty legacy frame.
                             erlang:put(legacy, true), 
-                            {legacy_frame, <<>>};
+                            {frame, <<>>};
                         {ok, Byte2} ->
                             % Read up to the first 0xFF for the body
                             erlang:put(legacy, true),
                             Body = read_until_FF(Socket, Byte2),
-                            {legacy_frame, Body}
+                            {frame, Body}
                     end
             end
     end.


[couchdb-mochiweb] 08/08: make wss:// websockets work over https

Posted by wi...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

willholley pushed a commit to tag active-passive
in repository https://gitbox.apache.org/repos/asf/couchdb-mochiweb.git

commit ecc6f85090c91c59e6628adb393f0c3bd25cc61d
Author: Richard Jones <rj...@metabrew.com>
AuthorDate: Sun Nov 21 13:21:52 2010 +0000

    make wss:// websockets work over https
---
 examples/websockets/index.html             |  2 +-
 examples/websockets/websockets_active.erl  | 17 +++++++++++------
 examples/websockets/websockets_passive.erl |  8 ++++++--
 src/mochiweb_http.erl                      |  6 +++++-
 src/mochiweb_socket.erl                    |  7 ++++++-
 src/mochiweb_websocket_delegate.erl        | 18 ++++++++++++++----
 6 files changed, 43 insertions(+), 15 deletions(-)

diff --git a/examples/websockets/index.html b/examples/websockets/index.html
index 208a602..a5c5217 100644
--- a/examples/websockets/index.html
+++ b/examples/websockets/index.html
@@ -24,7 +24,7 @@
     function $() { return document.getElementById(arguments[0]); }
     function go()
     {
-        ws = new WebSocket("ws://localhost:8080");
+        ws = new WebSocket("wss://localhost:8080/");
         ws.onopen = function(){ $('connstate').innerHTML='CONNECTED'; }
         ws.onclose = function(){ $('connstate').innerHTML='CLOSED'; }
         ws.onmessage = function(e){ $('msgs').innerHTML = $('msgs').innerHTML + "<pre>"+e.data+"</pre>"; }
diff --git a/examples/websockets/websockets_active.erl b/examples/websockets/websockets_active.erl
index 1ed6ba3..9b1e543 100644
--- a/examples/websockets/websockets_active.erl
+++ b/examples/websockets/websockets_active.erl
@@ -14,19 +14,24 @@ start(Options) ->
                            true
                       end,
     % websocket options
-    WsOpts = [ {active, true},
-               {origin_validator, OriginValidator},
-               {loop,   {?MODULE, wsloop_active}} ],
-    
+    WsOpts  = [ {active, true},
+                {origin_validator, OriginValidator},
+                {loop,   {?MODULE, wsloop_active}} ],
+    %
+    Ssl = [ {ssl, true}, {ssl_opts, [ {certfile, "../https/server_cert.pem"},
+                                      {keyfile, "../https/server_key.pem"}]} ],
+    %
     mochiweb_http:start([{name, ?MODULE}, 
                          {loop, Loop},
-                         {websocket_opts, WsOpts} | Options1]).
+                         {websocket_opts, WsOpts} | Options1] ++ Ssl).
 
 stop() ->
     mochiweb_http:stop(?MODULE).
 
 wsloop_active(Pid) ->
-    mochiweb_websocket_delegate:send(Pid, "WELCOME MSG FROM THE SERVER!"),
+    
+    Ret = mochiweb_websocket_delegate:send(Pid, "WELCOME MSG FROM THE SERVER!"),
+    io:format("Sent welcome msg: ~p~n",[Ret]),
     wsloop_active0(Pid).
 
 wsloop_active0(Pid) ->
diff --git a/examples/websockets/websockets_passive.erl b/examples/websockets/websockets_passive.erl
index 4e4151f..ea7a17e 100644
--- a/examples/websockets/websockets_passive.erl
+++ b/examples/websockets/websockets_passive.erl
@@ -17,9 +17,11 @@ start(Options) ->
     WsOpts = [ {active, false},
                {origin_validator, OriginValidator},
                {loop,   {?MODULE, wsloop}} ],
+    Ssl = [ {ssl, true}, {ssl_opts, [ {certfile, "../https/server_cert.pem"},
+                                      {keyfile, "../https/server_key.pem"}]} ],
     mochiweb_http:start([{name, ?MODULE}, 
                          {loop, Loop},
-                         {websocket_opts, WsOpts} | Options1]). 
+                         {websocket_opts, WsOpts} | Options1] ++ Ssl). 
 
 stop() ->
     mochiweb_http:stop(?MODULE).
@@ -30,7 +32,9 @@ wsloop(Ws) ->
     wsloop0(Ws).
 
 wsloop0(Ws) ->
-    case Ws:get_data() of
+    Got = Ws:get_data(),
+    io:format("GOT:~p~n",[Got]),
+    case Got of
         closed  ->  ok;
         closing -> ok;
         timeout -> timeout;
diff --git a/src/mochiweb_http.erl b/src/mochiweb_http.erl
index 55bef18..3a70a3f 100644
--- a/src/mochiweb_http.erl
+++ b/src/mochiweb_http.erl
@@ -295,11 +295,15 @@ websocket_init_with_origin_validated(Socket, Path, Headers, _Origin) ->
                          undefined  -> ""; 
                          P          -> ["Sec-WebSocket-Protocol: ", P, "\r\n"]
                      end,
+    HttpScheme = case mochiweb_socket:type(Socket) of 
+                     plain -> "http"; 
+                     ssl   -> "https" 
+                 end,
     Data = ["HTTP/1.1 101 Web Socket Protocol Handshake\r\n",
             "Upgrade: WebSocket\r\n",
             "Connection: Upgrade\r\n",
             "Sec-WebSocket-Location: ", Proto,Host,Path, "\r\n",
-            "Sec-WebSocket-Origin: http://", Host, "\r\n",
+            "Sec-WebSocket-Origin: ", HttpScheme, "://", Host, "\r\n",
             SubProtoHeader,
             "\r\n",
             <<Sig/binary>>
diff --git a/src/mochiweb_socket.erl b/src/mochiweb_socket.erl
index 76b018c..2948bcd 100644
--- a/src/mochiweb_socket.erl
+++ b/src/mochiweb_socket.erl
@@ -5,7 +5,7 @@
 -module(mochiweb_socket).
 
 -export([listen/4, accept/1, recv/3, send/2, close/1, port/1, peername/1,
-         setopts/2, type/1]).
+         setopts/2, type/1, controlling_process/2]).
 
 -define(ACCEPT_TIMEOUT, 2000).
 
@@ -77,6 +77,11 @@ setopts({ssl, Socket}, Opts) ->
 setopts(Socket, Opts) ->
     inet:setopts(Socket, Opts).
 
+controlling_process({ssl, Socket}, NewOwner) ->
+    ssl:controlling_process(Socket, NewOwner);
+controlling_process(Socket, NewOwner) ->
+    gen_tcp:controlling_process(Socket, NewOwner).
+
 type({ssl, _}) ->
     ssl;
 type(_) ->
diff --git a/src/mochiweb_websocket_delegate.erl b/src/mochiweb_websocket_delegate.erl
index 6682b1b..f1e97bb 100644
--- a/src/mochiweb_websocket_delegate.erl
+++ b/src/mochiweb_websocket_delegate.erl
@@ -38,7 +38,7 @@ start_link(Path, Headers, Destination) ->
     gen_server:start_link(?MODULE, [Path, Headers, Destination], []).
 
 go(Pid, Socket) ->
-    ok = gen_tcp:controlling_process(Socket, Pid),
+    ok = mochiweb_socket:controlling_process(Socket, Pid),
     gen_server:cast(Pid, {go, Socket}).
 
 send(Pid, Msg) ->
@@ -89,16 +89,26 @@ handle_cast({go, Socket}, State) ->
 
 handle_info({'EXIT', _, _}, State) ->
     State#state.dest ! closed,
-    {stop, normal, State};    
-handle_info({tcp_closed, Sock}, State = #state{socket=Sock}) ->
+    {stop, normal, State};
+handle_info({ssl_closed, _Sock}, State) ->
+    State#state.dest ! closed,
+    {stop, normal, State};
+handle_info({tcp_closed, _Sock}, State) ->
     State#state.dest ! closed,
     {stop, normal, State};
-handle_info({tcp_error, Sock, Reason}, State = #state{socket=Sock}) ->
+handle_info({tcp_error, _Sock, Reason}, State) ->
+    State#state.dest ! {error, Reason},
+    State#state.dest ! closed,
+    {stop, normal, State};
+handle_info({ssl_error, _Sock, Reason}, State) ->
     State#state.dest ! {error, Reason},
     State#state.dest ! closed,
     {stop, normal, State};
 handle_info({tcp, Sock, Data}, State = #state{socket=Sock, buffer=Buffer}) ->
     NewState = process_data(State#state{buffer= <<Buffer/binary,Data/binary>>}),
+    {noreply, NewState};
+handle_info({ssl, _Sock, Data}, State = #state{buffer=Buffer}) ->
+    NewState = process_data(State#state{buffer= <<Buffer/binary,Data/binary>>}),
     {noreply, NewState}.
 
 terminate(_Reason, _State) ->


[couchdb-mochiweb] 02/08: use mochiweb_headers when checking websocket headers

Posted by wi...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

willholley pushed a commit to tag active-passive
in repository https://gitbox.apache.org/repos/asf/couchdb-mochiweb.git

commit b752a34b05f8dfbffd0fee799aa9fec864229dae
Author: Richard Jones <rj...@metabrew.com>
AuthorDate: Fri Oct 8 23:58:14 2010 +0100

    use mochiweb_headers when checking websocket headers
---
 src/mochiweb_http.erl      | 40 ++++++++++++++++++++++++++--------------
 src/mochiweb_wsrequest.erl |  5 ++---
 2 files changed, 28 insertions(+), 17 deletions(-)

diff --git a/src/mochiweb_http.erl b/src/mochiweb_http.erl
index e51c33e..8713235 100644
--- a/src/mochiweb_http.erl
+++ b/src/mochiweb_http.erl
@@ -123,15 +123,23 @@ headers(Socket, Request, Headers, {WwwLoop, WsLoop} = Body, HeaderCount) ->
     case mochiweb_socket:recv(Socket, 0, ?HEADERS_RECV_TIMEOUT) of
         {ok, http_eoh} ->
             mochiweb_socket:setopts(Socket, [{packet, raw}]),
-            % is this a websocket upgrade request:
-            case {string:to_lower(proplists:get_value('Upgrade', Headers, "")),
-                  string:to_lower(proplists:get_value('Connection', Headers, ""))} of
+            %% Examine headers to decide if this a websocket upgrade request:
+            H = mochiweb_headers:make(Headers),
+            HeaderFun = fun(K) -> 
+                            case mochiweb_headers:get_value(K, H) of
+                                undefined -> "";
+                                V         -> string:to_lower(V)
+                            end
+                        end,
+            case {HeaderFun("upgrade"), HeaderFun("connection")} of
                 {"websocket", "upgrade"} ->
+                    io:format("notmal -> ws~n",[]),
                     {_, {abs_path,Path}, _} = Request,
-                    ok = websocket_init(Socket, Path, Headers),
-                    WsReq = mochiweb_wsrequest:new(Socket, Path),
+                    ok = websocket_init(Socket, Path, H),
+                    WsReq = mochiweb_wsrequest:new(Socket, Path, H),
                     call_body(WsLoop, WsReq);
-                _ -> % not websocket:
+                X -> %% not websocket:
+                    io:format("notmal~p~n",[X]),
                     Req = mochiweb:new_request({Socket, Request,
                                                 lists:reverse(Headers)}),
                     call_body(WwwLoop, Req),
@@ -213,11 +221,11 @@ range_skip_length(Spec, Size) ->
     end.
 
 websocket_init(Socket, Path, Headers) ->
-    Host    = proplists:get_value('Host', Headers, ""),
-    %Origin  = proplists:get_value("origin", Headers, ""), % TODO
-    SubProto= proplists:get_value("Sec-Websocket-Protocol", Headers, ""),
-    Key1    = proplists:get_value("Sec-Websocket-Key1", Headers, ""),
-    Key2    = proplists:get_value("Sec-Websocket-Key2", Headers, ""),
+    Host     = mochiweb_headers:get_value("Host", Headers),
+    %Origin  = mochiweb_headers:get_value("origin", Headers), % TODO
+    SubProto = mochiweb_headers:get_value("Sec-Websocket-Protocol", Headers),
+    Key1     = mochiweb_headers:get_value("Sec-Websocket-Key1", Headers),
+    Key2     = mochiweb_headers:get_value("Sec-Websocket-Key2", Headers),
     % read the 8 random bytes sent after the client headers for websockets:
     {ok, Key3} = mochiweb_socket:recv(Socket, 8, ?HEADERS_RECV_TIMEOUT),
     %io:format("Key1,2,3: ~p ~p ~p~n", [Key1, Key2, Key3]),
@@ -245,10 +253,14 @@ websocket_init(Socket, Path, Headers) ->
                     Sig = crypto:md5( <<Part1:32/unsigned-integer,
                                         Part2:32/unsigned-integer,
                                         Key3/binary>> ),
-                    Proto = case Socket of {ssl, _} -> "wss://"; _ -> "ws://" end,
+                    Proto = case mochiweb_socket:type(Socket) of 
+                                ssl   -> "wss://"; 
+                                plain -> "ws://" 
+                            end,
                     SubProtoHeader = case SubProto of 
-                                         "" -> ""; 
-                                         P  -> ["Sec-WebSocket-Protocol: ", P, "\r\n"]
+                                         undefined -> ""; 
+                                         P  -> ["Sec-WebSocket-Protocol: ", 
+                                                P, "\r\n"]
                                      end,
                     Data = ["HTTP/1.1 101 Web Socket Protocol Handshake\r\n",
                             "Upgrade: WebSocket\r\n",
diff --git a/src/mochiweb_wsrequest.erl b/src/mochiweb_wsrequest.erl
index aeca063..1d107c2 100644
--- a/src/mochiweb_wsrequest.erl
+++ b/src/mochiweb_wsrequest.erl
@@ -6,15 +6,14 @@
 %% an older version of the websocket spec, where messages are framed 0x00...0xFF
 %% so the newer protocol with length headers has not been tested with a browser.
 
--module(mochiweb_wsrequest, [Socket, Path]).
--define(TIMEOUT, 999999).
+-module(mochiweb_wsrequest, [Socket, Path, Headers]).
+-define(TIMEOUT, 999999). % TODO
 -export([get/1, get_data/0, send/1]).
 
 get(path)   -> Path;
 get(socket) -> Socket.
 
 get_data() ->
-    io:format("get_data...~n",[]),
     % read FrameType byte
     case mochiweb_socket:recv(Socket, 1, ?TIMEOUT) of
         {error, closed} -> 


[couchdb-mochiweb] 03/08: refactor websocket init code a little. some style fixes

Posted by wi...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

willholley pushed a commit to tag active-passive
in repository https://gitbox.apache.org/repos/asf/couchdb-mochiweb.git

commit 101bfc57cb80bfb0f265b322290987df38e3b4c8
Author: Richard Jones <rj...@metabrew.com>
AuthorDate: Sat Oct 9 00:09:45 2010 +0100

    refactor websocket init code a little. some style fixes
---
 src/mochiweb_http.erl | 100 +++++++++++++++++++++++++-------------------------
 1 file changed, 51 insertions(+), 49 deletions(-)

diff --git a/src/mochiweb_http.erl b/src/mochiweb_http.erl
index 8713235..d38c6d5 100644
--- a/src/mochiweb_http.erl
+++ b/src/mochiweb_http.erl
@@ -220,77 +220,79 @@ range_skip_length(Spec, Size) ->
             invalid_range
     end.
 
+%% Respond to the websocket upgrade request with valid signature
+%% or exit() if any of the sec- headers look suspicious.
 websocket_init(Socket, Path, Headers) ->
     Host     = mochiweb_headers:get_value("Host", Headers),
     %Origin  = mochiweb_headers:get_value("origin", Headers), % TODO
     SubProto = mochiweb_headers:get_value("Sec-Websocket-Protocol", Headers),
     Key1     = mochiweb_headers:get_value("Sec-Websocket-Key1", Headers),
     Key2     = mochiweb_headers:get_value("Sec-Websocket-Key2", Headers),
-    % read the 8 random bytes sent after the client headers for websockets:
+    %% read the 8 random bytes sent after the client headers for websockets:
     {ok, Key3} = mochiweb_socket:recv(Socket, 8, ?HEADERS_RECV_TIMEOUT),
-    %io:format("Key1,2,3: ~p ~p ~p~n", [Key1, Key2, Key3]),
     {N1,S1} = parse_seckey(Key1),
     {N2,S2} = parse_seckey(Key2),
-    %io:format("{N1,S1} {N2,S2}: ~p ~p~n",[ {N1,S1}, {N2,S2} ] ),
+    ok = websocket_verify_parsed_sec({N1,S1}, {N2,S2}),
+    Part1 = erlang:round(N1/S1),
+    Part2 = erlang:round(N2/S2),
+    Sig = crypto:md5( <<Part1:32/unsigned-integer, Part2:32/unsigned-integer,
+                        Key3/binary>> ),
+    Proto = case mochiweb_socket:type(Socket) of 
+                ssl   -> "wss://"; 
+                plain -> "ws://" 
+            end,
+    SubProtoHeader = case SubProto of 
+                         undefined  -> ""; 
+                         P          -> ["Sec-WebSocket-Protocol: ", P, "\r\n"]
+                     end,
+    Data = ["HTTP/1.1 101 Web Socket Protocol Handshake\r\n",
+            "Upgrade: WebSocket\r\n",
+            "Connection: Upgrade\r\n",
+            "Sec-WebSocket-Location: ", Proto,Host,Path, "\r\n",
+            "Sec-WebSocket-Origin: http://", Host, "\r\n",
+            SubProtoHeader,
+            "\r\n",
+            <<Sig/binary>>
+           ],
+    mochiweb_socket:send(Socket, Data),
+    ok.
+            
+%% websocket seckey parser:
+%% extract integer by only looking at [0-9]+ in the string
+%% count spaces in the string
+%% returns: {int, numspaces}
+parse_seckey(Str) ->
+    parse_seckey1(Str, {"",0}).
+
+parse_seckey1("", {NumStr,NumSpaces}) ->
+    {list_to_integer(lists:reverse(NumStr)), NumSpaces};
+parse_seckey1([32|T], {Ret,NumSpaces}) -> % ASCII/dec space
+    parse_seckey1(T, {Ret, 1+NumSpaces});
+parse_seckey1([N|T],  {Ret,NumSpaces}) when N >= 48, N =< 57 -> % ASCII/dec 0-9 
+    parse_seckey1(T, {[N|Ret], NumSpaces});
+parse_seckey1([_|T], Acc) -> 
+    parse_seckey1(T, Acc).
+
+%% exit if anything suspicious is detected
+websocket_verify_parsed_sec({N1,S1}, {N2,S2}) ->
     case N1 > 4294967295 orelse 
          N2 > 4294967295 orelse 
          S1 == 0 orelse
          S2 == 0 of
         true ->
-            %  This is a symptom of an attack.
+            %%  This is a symptom of an attack.
             exit(websocket_attack);
         false ->
             case N1 rem S1 /= 0 orelse
                  N2 rem S2 /= 0 of
                 true ->
-                    % This can only happen if the client is not a conforming
-                    % WebSocket client.
-                    exit(dodgy_client);
+                    %% This can only happen if the client is not a conforming
+                    %% WebSocket client.
+                    exit(websocket_client_misspoke);
                 false ->
-                    Part1 = erlang:round(N1/S1),
-                    Part2 = erlang:round(N2/S2),
-                    %io:format("Part1 : ~p  Part2: ~p~n", [Part1, Part2]),
-                    Sig = crypto:md5( <<Part1:32/unsigned-integer,
-                                        Part2:32/unsigned-integer,
-                                        Key3/binary>> ),
-                    Proto = case mochiweb_socket:type(Socket) of 
-                                ssl   -> "wss://"; 
-                                plain -> "ws://" 
-                            end,
-                    SubProtoHeader = case SubProto of 
-                                         undefined -> ""; 
-                                         P  -> ["Sec-WebSocket-Protocol: ", 
-                                                P, "\r\n"]
-                                     end,
-                    Data = ["HTTP/1.1 101 Web Socket Protocol Handshake\r\n",
-                            "Upgrade: WebSocket\r\n",
-                            "Connection: Upgrade\r\n",
-                            "Sec-WebSocket-Location: ", Proto,Host,Path, "\r\n",
-                            "Sec-WebSocket-Origin: http://", Host, "\r\n",
-                            SubProtoHeader,
-                            "\r\n",
-                            <<Sig/binary>>
-                           ],
-                    mochiweb_socket:send(Socket, Data),
-                    ok                    
+                    ok
             end
     end.
-            
-% websocket seckey parser:
-% extract integer by only looking at [0-9]+ in the string
-% count spaces in the string
-% returns: {int, numspaces}
-parse_seckey(Str) ->
-    parse_seckey1(Str, {"",0}).
-
-parse_seckey1("", {NumStr,NumSpaces}) ->
-    {list_to_integer(lists:reverse(NumStr)), NumSpaces};
-parse_seckey1([32|T], {Ret,NumSpaces}) -> % ASCII/dec space
-    parse_seckey1(T, {Ret, 1+NumSpaces});
-parse_seckey1([N|T],  {Ret,NumSpaces}) when N >= 48, N =< 57 -> % ASCII/dec 0-9 
-    parse_seckey1(T, {[N|Ret], NumSpaces});
-parse_seckey1([_|T], Acc) -> 
-    parse_seckey1(T, Acc).
   
 %%
 %% Tests


[couchdb-mochiweb] 04/08: partial work on active websocket api

Posted by wi...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

willholley pushed a commit to tag active-passive
in repository https://gitbox.apache.org/repos/asf/couchdb-mochiweb.git

commit 4146cdfb39318ee5b1e261a6c375fb87c7ab8c3e
Author: Richard Jones <rj...@metabrew.com>
AuthorDate: Mon Oct 11 18:05:03 2010 +0100

    partial work on active websocket api
---
 examples/websockets/websockets.erl  |  24 +++++-
 src/mochiweb_http.erl               |  12 ++-
 src/mochiweb_websocket_delegate.erl | 159 ++++++++++++++++++++++++++++++++++++
 3 files changed, 191 insertions(+), 4 deletions(-)

diff --git a/examples/websockets/websockets.erl b/examples/websockets/websockets.erl
index f50c5b4..ebd8fc6 100644
--- a/examples/websockets/websockets.erl
+++ b/examples/websockets/websockets.erl
@@ -1,7 +1,7 @@
 -module(websockets).
 -author('author <rj...@metabrew.com>').
 
--export([start/0, start/1, stop/0, loop/2, wsloop/1]).
+-export([start/0, start/1, stop/0, loop/2, wsloop_active/1]).
 
 start() -> start([{port, 8080}, {docroot, "."}]).
 
@@ -10,11 +10,31 @@ start(Options) ->
     Loop = fun (Req) -> ?MODULE:loop(Req, DocRoot) end,
     mochiweb_http:start([{name, ?MODULE}, 
                          {loop, Loop}, 
-                         {wsloop, {?MODULE, wsloop}} | Options1]).
+                         {wsloop, {?MODULE, wsloop_active}} | Options1]).
 
 stop() ->
     mochiweb_http:stop(?MODULE).
 
+wsloop_active(Pid) ->
+    mochiweb_websocket_delegate:send(Pid, "WELCOME MSG!"),
+    wsloop_active0(Pid).
+
+wsloop_active0(Pid) ->
+    receive
+        closed ->
+            io:format("client api got closed~n",[]),
+            ok;
+        {error, _Reason} ->
+            ok;
+        % {legacy_frame, M} or {utf8_frame, M}
+        {_, X} ->
+            Msg = io_lib:format("SRVER_GOT: ~p", [X]),
+            mochiweb_websocket_delegate:send(Pid, Msg)
+    after 10000 ->
+            mochiweb_websocket_delegate:send(Pid, "IDLE!")
+    end,
+    wsloop_active0(Pid).
+
 wsloop(Ws) ->
     io:format("Websocket request, path: ~p~n", [Ws:get(path)]),
     case Ws:get_data() of
diff --git a/src/mochiweb_http.erl b/src/mochiweb_http.erl
index d38c6d5..69255d4 100644
--- a/src/mochiweb_http.erl
+++ b/src/mochiweb_http.erl
@@ -136,8 +136,16 @@ headers(Socket, Request, Headers, {WwwLoop, WsLoop} = Body, HeaderCount) ->
                     io:format("notmal -> ws~n",[]),
                     {_, {abs_path,Path}, _} = Request,
                     ok = websocket_init(Socket, Path, H),
-                    WsReq = mochiweb_wsrequest:new(Socket, Path, H),
-                    call_body(WsLoop, WsReq);
+                    Active = true,
+                    case Active of
+                        true ->
+                            {ok, WSPid} = mochiweb_websocket_delegate:start_link(Path, H, self()),
+                            mochiweb_websocket_delegate:go(WSPid, Socket),
+                            call_body(WsLoop, WSPid);
+                        false ->
+                            WsReq = mochiweb_wsrequest:new(Socket, Path, H),
+                            call_body(WsLoop, WsReq)
+                    end;
                 X -> %% not websocket:
                     io:format("notmal~p~n",[X]),
                     Req = mochiweb:new_request({Socket, Request,
diff --git a/src/mochiweb_websocket_delegate.erl b/src/mochiweb_websocket_delegate.erl
new file mode 100644
index 0000000..6c7bd85
--- /dev/null
+++ b/src/mochiweb_websocket_delegate.erl
@@ -0,0 +1,159 @@
+%% @author Richard Jones <rj...@metabrew.com>
+%%
+%% Process for handling send/recv on an established websocket
+%% providing an 'active' api, ala gen_tcp in active mode.
+%%
+%% @see http://www.whatwg.org/specs/web-socket-protocol/
+%% As of August 2010
+%%
+%% However, at time of writing (Oct 8, 2010) Chrome 6 and Firefox 4 implement
+%% an older version of the websocket spec, where messages are framed 0x00...0xFF
+%% so the newer protocol with length headers has not been tested with a browser.
+
+-module(mochiweb_websocket_delegate).
+-behaviour(gen_server).
+
+-record(state, {path, 
+                headers, 
+                legacy,
+                socket, 
+                dest, 
+                buffer, 
+                partial,
+                ft, 
+                flen}).
+
+-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
+         terminate/2, code_change/3]).
+
+-export([start_link/3, go/2, send/2, close/1, headers/1, path/1]).
+
+%%
+
+start_link(Path, Headers, Destination) ->
+    gen_server:start_link(?MODULE, [Path, Headers, Destination], []).
+
+go(Pid, Socket) ->
+    ok = gen_tcp:controlling_process(Socket, Pid),
+    gen_server:cast(Pid, {go, Socket}).
+
+send(Pid, Msg) ->
+    io:format("send:~s~n",[Msg]),
+    gen_server:call(Pid, {send, Msg}).
+
+close(Pid) ->
+    gen_server:call(Pid, close).
+
+headers(Pid) ->
+    gen_server:call(Pid, headers).
+
+path(Pid) ->
+    gen_server:call(Pid, path).
+
+%%
+
+init([Path, Headers, Dest]) ->
+    process_flag(trap_exit, true),
+    {ok, #state{path=Path, 
+                legacy=true,
+                headers=Headers, 
+                dest=Dest, 
+                ft = undefined,
+                buffer = <<>>,
+                partial= <<>>
+               }}.
+
+handle_call(close, _From, State) ->
+    mochiweb_socket:close(State#state.socket),
+    {reply, ok, State};    
+handle_call(headers, _From, State) ->
+    {reply, State#state.headers, State};
+handle_call(path, _From, State) ->
+    {reply, State#state.path, State};
+handle_call({send, Msg}, _From, State = #state{legacy=false, socket=Socket}) ->
+    % header is 0xFF then 64bit big-endian int of the msg length
+    Len = iolist_size(Msg),
+    R = mochiweb_socket:send(Socket, [255, <<Len:64/unsigned-integer>>, Msg]), 
+    {reply, R, State};
+handle_call({send, Msg}, _From, State = #state{legacy=true, socket=Socket}) ->
+    % legacy spec, msgs are framed with 0x00..0xFF
+    R = mochiweb_socket:send(Socket, [0, Msg, 255]),
+    {reply, R, State}.
+
+handle_cast({go, Socket}, State) ->
+    mochiweb_socket:setopts(Socket, [{active, true}]),    
+    {noreply, State#state{socket=Socket}}.
+
+handle_info({'EXIT', _, _}, State) ->
+    io:format("TRAP EXIT~n",[]),
+    State#state.dest ! closed,
+    {stop, normal, State};    
+handle_info({tcp_closed, Sock}, State = #state{socket=Sock}) ->
+    State#state.dest ! closed,
+    {stop, normal, State};
+handle_info({tcp_error, Sock, Reason}, State = #state{socket=Sock}) ->
+    State#state.dest ! {error, Reason},
+    {stop, normal, State};
+handle_info({tcp, Sock, Data}, State = #state{socket=Sock, buffer=Buffer}) ->
+    %mochiweb_socket:setopts(Sock, [{active, once}]),
+    NewState = process_data(State#state{buffer= <<Buffer/binary,Data/binary>>}),
+    {noreply, NewState}.
+
+terminate(_Reason, _State) ->
+    ok.
+
+code_change(_OldVsn, State, _Extra) ->
+    {ok, State}.
+
+%%% Internal functions
+
+process_data(State = #state{buffer= <<>>}) -> 
+    %io:format("A 0~n", []),
+    State;
+
+process_data(State = #state{buffer= <<FrameType:8,Buffer/binary>>, ft=undefined}) ->
+    %io:format("A 1~n", []),
+    process_data(State#state{buffer=Buffer, ft=FrameType, partial= <<>>});
+
+% "Legacy" frames, 0x00...0xFF
+% or modern closing handshake 0x00{8}
+process_data(State = #state{buffer= <<0,0,0,0,0,0,0,0, Buffer/binary>>, ft=0}) ->
+    %io:format("A 2~n", []),
+    State#state.dest ! closing_handshake,
+    process_data(State#state{buffer=Buffer, ft=undefined});
+
+process_data(State = #state{buffer= <<255, Rest/binary>>, ft=0}) ->
+    %io:format("A 3~n", []),
+    State#state.dest ! {legacy_frame, State#state.partial},
+    process_data(State#state{partial= <<>>, ft=undefined, buffer=Rest});
+
+process_data(State = #state{buffer= <<Byte:8, Rest/binary>>, ft=0, partial=Partial}) ->
+    %io:format("A 4, byte=~p state:~p~n", [Byte,State]),
+    NewPartial = case Partial of <<>> -> <<Byte>> ; _ -> <<Partial/binary, <<Byte>>/binary>> end,
+    NewState = State#state{buffer=Rest, partial=NewPartial},
+   process_data(NewState);
+
+% "Modern" frames, starting with 0xFF, followed by 64 bit length
+process_data(State = #state{buffer= <<Len:64/unsigned-integer,Buffer/binary>>, ft=255, flen=undefined}) ->
+    %io:format("A 5~n", []),
+    BitsLen = Len*8,
+    case Buffer of
+        <<Frame:BitsLen/binary, Rest/binary>> ->            
+            State#state.dest ! {utf8_frame, Frame},
+            process_data(State#state{ft=undefined, flen=undefined, buffer=Rest});
+
+        _ ->
+            State#state{flen=Len, buffer=Buffer}
+    end;
+
+process_data(State = #state{buffer=Buffer, ft=255, flen=Len}) when is_integer(Len) ->
+    %io:format("A 6~n", []),
+    BitsLen = Len*8,
+    case Buffer of
+        <<Frame:BitsLen/binary, Rest/binary>> ->            
+            State#state.dest ! {utf8_frame, Frame},
+            process_data(State#state{ft=undefined, flen=undefined, buffer=Rest});
+
+        _ ->
+            State#state{flen=Len, buffer=Buffer}
+    end.


[couchdb-mochiweb] 01/08: * Support modern and legacy websockets spec * Create websockets example * Currently websocket is 'passive', call get_data() for each frame

Posted by wi...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

willholley pushed a commit to tag active-passive
in repository https://gitbox.apache.org/repos/asf/couchdb-mochiweb.git

commit e6386a6651bd7ccf1754c0cbf826618f15f38bbc
Author: Richard Jones <rj...@metabrew.com>
AuthorDate: Fri Oct 8 20:14:56 2010 +0100

    * Support modern and legacy websockets spec
    * Create websockets example
    * Currently websocket is 'passive', call get_data() for each frame
---
 examples/websockets/index.html     | 39 ++++++++++++++++
 examples/websockets/websockets.erl | 58 +++++++++++++++++++++++
 src/mochiweb_http.erl              | 96 ++++++++++++++++++++++++++++++++++----
 src/mochiweb_socket_server.erl     |  3 ++
 src/mochiweb_wsrequest.erl         | 68 +++++++++++++++++++++++++++
 5 files changed, 256 insertions(+), 8 deletions(-)

diff --git a/examples/websockets/index.html b/examples/websockets/index.html
new file mode 100644
index 0000000..208a602
--- /dev/null
+++ b/examples/websockets/index.html
@@ -0,0 +1,39 @@
+<html>
+<head>
+  <title>Websockets With Mochiweb Demo</title>
+</head>
+<body>
+<h1>Mochiweb websocket demo</h1>
+
+  <div id="connect">
+     <button id="btnConn">Connect</button>
+     &nbsp; State: <span id="connstate" style="font-weight:bold;"></span>
+  </div>
+    <br/><i>Protip: open your javascript error console, just in case..</i><br/>
+  <hr/>
+  <div id="connected">
+    <input id='phrase' type='text'/>
+    <input id='btnSend' class='button' type='submit' name='connect' value='Send'/>
+  </div>
+  <hr/>
+  <div id="msgs"></div>
+
+   <script type="text/javascript">
+    var ws;
+    if (!window.WebSocket) alert("WebSocket not supported by this browser");
+    function $() { return document.getElementById(arguments[0]); }
+    function go()
+    {
+        ws = new WebSocket("ws://localhost:8080");
+        ws.onopen = function(){ $('connstate').innerHTML='CONNECTED'; }
+        ws.onclose = function(){ $('connstate').innerHTML='CLOSED'; }
+        ws.onmessage = function(e){ $('msgs').innerHTML = $('msgs').innerHTML + "<pre>"+e.data+"</pre>"; }
+    }
+
+    $('btnConn').onclick = function(event) { go(); return false; };
+    $('btnSend').onclick = function(event) { ws.send($('phrase').value); $('phrase').value=''; return false; };	
+    
+  </script>
+  </body>
+</html>
+
diff --git a/examples/websockets/websockets.erl b/examples/websockets/websockets.erl
new file mode 100644
index 0000000..f50c5b4
--- /dev/null
+++ b/examples/websockets/websockets.erl
@@ -0,0 +1,58 @@
+-module(websockets).
+-author('author <rj...@metabrew.com>').
+
+-export([start/0, start/1, stop/0, loop/2, wsloop/1]).
+
+start() -> start([{port, 8080}, {docroot, "."}]).
+
+start(Options) ->
+    {DocRoot, Options1} = get_option(docroot, Options),
+    Loop = fun (Req) -> ?MODULE:loop(Req, DocRoot) end,
+    mochiweb_http:start([{name, ?MODULE}, 
+                         {loop, Loop}, 
+                         {wsloop, {?MODULE, wsloop}} | Options1]).
+
+stop() ->
+    mochiweb_http:stop(?MODULE).
+
+wsloop(Ws) ->
+    io:format("Websocket request, path: ~p~n", [Ws:get(path)]),
+    case Ws:get_data() of
+        closed ->  ok;
+        closing -> ok;
+        timeout -> timeout;
+        
+        % older websockets spec which is in the wild, messages are framed with
+        % 0x00...0xFF
+        {legacy_frame, Body} ->
+            Ws:send(["YOU SENT US LEGACY FRAME: ", Body]),
+            wsloop(Ws);
+    
+        % current spec, each message has a 0xFF/<64bit length> header
+        % and must contain utf8 bytestream
+        {utf8_frame, Body} ->
+            Ws:send(["YOU SENT US MODERN FRAME: ", Body]),
+            wsloop(Ws)
+    end.
+
+loop(Req, DocRoot) ->
+    "/" ++ Path = Req:get(path),
+    case Req:get(method) of
+        Method when Method =:= 'GET'; Method =:= 'HEAD' ->
+            case Path of
+                _ ->
+                    Req:serve_file(Path, DocRoot)
+            end;
+        'POST' ->
+            case Path of
+                _ ->
+                    Req:not_found()
+            end;
+        _ ->
+            Req:respond({501, [], []})
+    end.
+
+%% Internal API
+
+get_option(Option, Options) ->
+    {proplists:get_value(Option, Options), proplists:delete(Option, Options)}.
diff --git a/src/mochiweb_http.erl b/src/mochiweb_http.erl
index 2414099..e51c33e 100644
--- a/src/mochiweb_http.erl
+++ b/src/mochiweb_http.erl
@@ -18,11 +18,13 @@
                    {port, 8888}]).
 
 parse_options(Options) ->
-    {loop, HttpLoop} = proplists:lookup(loop, Options),
+    HttpLoop = proplists:get_value(loop, Options),
+    WsLoop   = proplists:get_value(wsloop, Options),
     Loop = fun (S) ->
-                   ?MODULE:loop(S, HttpLoop)
+                   ?MODULE:loop(S, {HttpLoop, WsLoop})
            end,
-    Options1 = [{loop, Loop} | proplists:delete(loop, Options)],
+    Options1 = [{loop, Loop}, {wsloop, WsLoop} | 
+                    proplists:delete(loop, proplists:delete(wsloop, Options))],
     mochilists:set_defaults(?DEFAULTS, Options1).
 
 stop() ->
@@ -117,14 +119,24 @@ headers(Socket, Request, Headers, _Body, ?MAX_HEADERS) ->
     %% Too many headers sent, bad request.
     mochiweb_socket:setopts(Socket, [{packet, raw}]),
     handle_invalid_request(Socket, Request, Headers);
-headers(Socket, Request, Headers, Body, HeaderCount) ->
+headers(Socket, Request, Headers, {WwwLoop, WsLoop} = Body, HeaderCount) ->
     case mochiweb_socket:recv(Socket, 0, ?HEADERS_RECV_TIMEOUT) of
         {ok, http_eoh} ->
             mochiweb_socket:setopts(Socket, [{packet, raw}]),
-            Req = mochiweb:new_request({Socket, Request,
-                                        lists:reverse(Headers)}),
-            call_body(Body, Req),
-            ?MODULE:after_response(Body, Req);
+            % is this a websocket upgrade request:
+            case {string:to_lower(proplists:get_value('Upgrade', Headers, "")),
+                  string:to_lower(proplists:get_value('Connection', Headers, ""))} of
+                {"websocket", "upgrade"} ->
+                    {_, {abs_path,Path}, _} = Request,
+                    ok = websocket_init(Socket, Path, Headers),
+                    WsReq = mochiweb_wsrequest:new(Socket, Path),
+                    call_body(WsLoop, WsReq);
+                _ -> % not websocket:
+                    Req = mochiweb:new_request({Socket, Request,
+                                                lists:reverse(Headers)}),
+                    call_body(WwwLoop, Req),
+                    ?MODULE:after_response(Body, Req)
+            end;
         {ok, {http_header, _, Name, _, Value}} ->
             headers(Socket, Request, [{Name, Value} | Headers], Body,
                     1 + HeaderCount);
@@ -200,6 +212,74 @@ range_skip_length(Spec, Size) ->
             invalid_range
     end.
 
+websocket_init(Socket, Path, Headers) ->
+    Host    = proplists:get_value('Host', Headers, ""),
+    %Origin  = proplists:get_value("origin", Headers, ""), % TODO
+    SubProto= proplists:get_value("Sec-Websocket-Protocol", Headers, ""),
+    Key1    = proplists:get_value("Sec-Websocket-Key1", Headers, ""),
+    Key2    = proplists:get_value("Sec-Websocket-Key2", Headers, ""),
+    % read the 8 random bytes sent after the client headers for websockets:
+    {ok, Key3} = mochiweb_socket:recv(Socket, 8, ?HEADERS_RECV_TIMEOUT),
+    %io:format("Key1,2,3: ~p ~p ~p~n", [Key1, Key2, Key3]),
+    {N1,S1} = parse_seckey(Key1),
+    {N2,S2} = parse_seckey(Key2),
+    %io:format("{N1,S1} {N2,S2}: ~p ~p~n",[ {N1,S1}, {N2,S2} ] ),
+    case N1 > 4294967295 orelse 
+         N2 > 4294967295 orelse 
+         S1 == 0 orelse
+         S2 == 0 of
+        true ->
+            %  This is a symptom of an attack.
+            exit(websocket_attack);
+        false ->
+            case N1 rem S1 /= 0 orelse
+                 N2 rem S2 /= 0 of
+                true ->
+                    % This can only happen if the client is not a conforming
+                    % WebSocket client.
+                    exit(dodgy_client);
+                false ->
+                    Part1 = erlang:round(N1/S1),
+                    Part2 = erlang:round(N2/S2),
+                    %io:format("Part1 : ~p  Part2: ~p~n", [Part1, Part2]),
+                    Sig = crypto:md5( <<Part1:32/unsigned-integer,
+                                        Part2:32/unsigned-integer,
+                                        Key3/binary>> ),
+                    Proto = case Socket of {ssl, _} -> "wss://"; _ -> "ws://" end,
+                    SubProtoHeader = case SubProto of 
+                                         "" -> ""; 
+                                         P  -> ["Sec-WebSocket-Protocol: ", P, "\r\n"]
+                                     end,
+                    Data = ["HTTP/1.1 101 Web Socket Protocol Handshake\r\n",
+                            "Upgrade: WebSocket\r\n",
+                            "Connection: Upgrade\r\n",
+                            "Sec-WebSocket-Location: ", Proto,Host,Path, "\r\n",
+                            "Sec-WebSocket-Origin: http://", Host, "\r\n",
+                            SubProtoHeader,
+                            "\r\n",
+                            <<Sig/binary>>
+                           ],
+                    mochiweb_socket:send(Socket, Data),
+                    ok                    
+            end
+    end.
+            
+% websocket seckey parser:
+% extract integer by only looking at [0-9]+ in the string
+% count spaces in the string
+% returns: {int, numspaces}
+parse_seckey(Str) ->
+    parse_seckey1(Str, {"",0}).
+
+parse_seckey1("", {NumStr,NumSpaces}) ->
+    {list_to_integer(lists:reverse(NumStr)), NumSpaces};
+parse_seckey1([32|T], {Ret,NumSpaces}) -> % ASCII/dec space
+    parse_seckey1(T, {Ret, 1+NumSpaces});
+parse_seckey1([N|T],  {Ret,NumSpaces}) when N >= 48, N =< 57 -> % ASCII/dec 0-9 
+    parse_seckey1(T, {[N|Ret], NumSpaces});
+parse_seckey1([_|T], Acc) -> 
+    parse_seckey1(T, Acc).
+  
 %%
 %% Tests
 %%
diff --git a/src/mochiweb_socket_server.erl b/src/mochiweb_socket_server.erl
index 3218195..e229402 100644
--- a/src/mochiweb_socket_server.erl
+++ b/src/mochiweb_socket_server.erl
@@ -17,6 +17,7 @@
 -record(mochiweb_socket_server,
         {port,
          loop,
+         wsloop,
          name=undefined,
          %% NOTE: This is currently ignored.
          max=2048,
@@ -85,6 +86,8 @@ parse_options([{ip, Ip} | Rest], State) ->
     parse_options(Rest, State#mochiweb_socket_server{ip=ParsedIp});
 parse_options([{loop, Loop} | Rest], State) ->
     parse_options(Rest, State#mochiweb_socket_server{loop=Loop});
+parse_options([{wsloop, Loop} | Rest], State) ->
+    parse_options(Rest, State#mochiweb_socket_server{wsloop=Loop});
 parse_options([{backlog, Backlog} | Rest], State) ->
     parse_options(Rest, State#mochiweb_socket_server{backlog=Backlog});
 parse_options([{nodelay, NoDelay} | Rest], State) ->
diff --git a/src/mochiweb_wsrequest.erl b/src/mochiweb_wsrequest.erl
new file mode 100644
index 0000000..aeca063
--- /dev/null
+++ b/src/mochiweb_wsrequest.erl
@@ -0,0 +1,68 @@
+%% @author Richard Jones <rj...@metabrew.com>
+%% @see http://www.whatwg.org/specs/web-socket-protocol/
+%% As of August 2010
+%%
+%% However, at time of writing (Oct 8, 2010) Chrome 6 and Firefox 4 implement
+%% an older version of the websocket spec, where messages are framed 0x00...0xFF
+%% so the newer protocol with length headers has not been tested with a browser.
+
+-module(mochiweb_wsrequest, [Socket, Path]).
+-define(TIMEOUT, 999999).
+-export([get/1, get_data/0, send/1]).
+
+get(path)   -> Path;
+get(socket) -> Socket.
+
+get_data() ->
+    io:format("get_data...~n",[]),
+    % read FrameType byte
+    case mochiweb_socket:recv(Socket, 1, ?TIMEOUT) of
+        {error, closed} -> 
+            closed;
+        {error, timeout} -> 
+            timeout;
+        {ok, FrameType}  ->
+            case FrameType of
+                <<255>> -> % Modern UTF-8 bytestream message with 64bit length
+                    erlang:put(legacy, false), 
+                    {ok, <<Len:64/unsigned-integer>>}  = 
+                        mochiweb_socket:recv(Socket, 8, ?TIMEOUT),
+                    {ok, Frame} = mochiweb_socket:recv(Socket, Len, ?TIMEOUT),
+                    {utf8_frame, Frame};
+                <<0>> ->   % modern close request, or older no-length-frame msg
+                    case mochiweb_socket:recv(Socket, 1, ?TIMEOUT) of
+                        {ok, <<0>>} ->
+                            % invalid for legacy protocol
+                            % probably followed by 7 more 0 in modern
+                            closing;
+                        {ok, <<255>>} ->
+                            % empty legacy frame.
+                            erlang:put(legacy, true), 
+                            {legacy_frame, <<>>};
+                        {ok, Byte2} ->
+                            % Read up to the first 0xFF for the body
+                            erlang:put(legacy, true),
+                            Body = read_until_FF(Socket, Byte2),
+                            {legacy_frame, Body}
+                    end
+            end
+    end.
+
+send(Body)  ->
+    case erlang:get(legacy) of
+        true ->
+            % legacy spec, msgs are framed with 0x00..0xFF
+            mochiweb_socket:send(Socket, [0, Body, 255]);
+        _  -> 
+            % header is 0xFF then 64bit big-endian int of the msg length
+            Len = iolist_size(Body),
+            mochiweb_socket:send(Socket, [255, 
+                                          <<Len:64/unsigned-integer>>,
+                                          Body])
+    end.
+
+read_until_FF(Socket, Acc) when is_binary(Acc) ->
+    case mochiweb_socket:recv(Socket, 1, ?TIMEOUT) of
+        {ok, <<255>>} -> Acc;
+        {ok, B}       -> read_until_FF(Socket, <<Acc/binary, B/binary>>)
+    end.


[couchdb-mochiweb] 06/08: clean up websocket options parsing, create active and passive examples

Posted by wi...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

willholley pushed a commit to tag active-passive
in repository https://gitbox.apache.org/repos/asf/couchdb-mochiweb.git

commit b0e3897938fec0d0626cb32189b2b7080f7a816f
Author: Richard Jones <rj...@metabrew.com>
AuthorDate: Sat Nov 20 18:32:34 2010 +0000

    clean up websocket options parsing, create active and passive examples
---
 examples/websockets/websockets_active.erl  | 16 ++++++++------
 examples/websockets/websockets_passive.erl |  2 +-
 src/mochiweb_http.erl                      | 35 +++++++++++++++---------------
 src/mochiweb_socket_server.erl             |  3 ---
 src/mochiweb_websocket_delegate.erl        |  5 +++++
 5 files changed, 33 insertions(+), 28 deletions(-)

diff --git a/examples/websockets/websockets_active.erl b/examples/websockets/websockets_active.erl
index 48a0ad6..53fa3ed 100644
--- a/examples/websockets/websockets_active.erl
+++ b/examples/websockets/websockets_active.erl
@@ -1,7 +1,7 @@
 -module(websockets_active).
 -author('author <rj...@metabrew.com>').
 
--export([start/0, start/1, stop/0, loop/2, wsloop_active/1, wsloop/1]).
+-export([start/0, start/1, stop/0, loop/2, wsloop_active/1]).
 
 start() -> start([{port, 8080}, {docroot, "."}]).
 
@@ -13,7 +13,7 @@ start(Options) ->
                {loop,   {?MODULE, wsloop_active}} ],
     mochiweb_http:start([{name, ?MODULE}, 
                          {loop, Loop},
-                         {websockets_opts, WsOpts} | Options1]).
+                         {websocket_opts, WsOpts} | Options1]).
 
 stop() ->
     mochiweb_http:stop(?MODULE).
@@ -27,16 +27,18 @@ wsloop_active0(Pid) ->
         closed ->
             io:format("client api got closed~n",[]),
             ok;
-        {error, _Reason} ->
+        {error, Reason} ->
+            io:format("client api got error ~p~n", [Reason]),
             ok;
         % {legacy_frame, M} or {utf8_frame, M}
         {_, X} ->
             Msg = io_lib:format("SRVER_GOT: ~p", [X]),
-            mochiweb_websocket_delegate:send(Pid, Msg)
+            mochiweb_websocket_delegate:send(Pid, Msg),
+            wsloop_active0(Pid)
     after 10000 ->
-            mochiweb_websocket_delegate:send(Pid, "IDLE!")
-    end,
-    wsloop_active0(Pid).
+            mochiweb_websocket_delegate:send(Pid, "IDLE!"),
+            wsloop_active0(Pid)
+    end.
 
 loop(Req, DocRoot) ->
     "/" ++ Path = Req:get(path),
diff --git a/examples/websockets/websockets_passive.erl b/examples/websockets/websockets_passive.erl
index 979ad71..7632799 100644
--- a/examples/websockets/websockets_passive.erl
+++ b/examples/websockets/websockets_passive.erl
@@ -13,7 +13,7 @@ start(Options) ->
                {loop,   {?MODULE, wsloop}} ],
     mochiweb_http:start([{name, ?MODULE}, 
                          {loop, Loop},
-                         {websockets_opts, WsOpts} | Options1]). 
+                         {websocket_opts, WsOpts} | Options1]). 
 
 stop() ->
     mochiweb_http:stop(?MODULE).
diff --git a/src/mochiweb_http.erl b/src/mochiweb_http.erl
index fcec33f..7da890c 100644
--- a/src/mochiweb_http.erl
+++ b/src/mochiweb_http.erl
@@ -18,7 +18,7 @@
                    {port, 8888}]).
 
 % client loop holds fun/info on how to hand off request to client code
--record(clientloop, {http_loop, websocket_loop, websocket_active}). 
+-record(body, {http_loop, websocket_loop, websocket_active}). 
 
 parse_options(Options) ->
     HttpLoop = proplists:get_value(loop, Options),
@@ -30,16 +30,13 @@ parse_options(Options) ->
             WsLoop   = undefined,
             WsActive = undefined
     end,
-    ClientLoop = #clientloop{http_loop        = HttpLoop,
-                             websocket_loop   = WsLoop,
-                             websocket_active = WsActive},
-                
-    WsLoop   = proplists:get_value(wsloop, Options),
-    Loop = fun (S) ->
-                   ?MODULE:loop(S, {HttpLoop, WsLoop})
-           end,
-    Options1 = [{loop, Loop}, {wsloop, WsLoop} | 
-                    proplists:delete(loop, proplists:delete(wsloop, Options))],
+    Body = #body{http_loop        = HttpLoop,
+                 websocket_loop   = WsLoop,
+                 websocket_active = WsActive},
+    Loop = fun (S) -> ?MODULE:loop(S, Body) end,
+    Options1 = [{loop, Loop} | 
+                    proplists:delete(loop, 
+                        proplists:delete(websocket_opts, Options))],
     mochilists:set_defaults(?DEFAULTS, Options1).
 
 stop() ->
@@ -134,7 +131,7 @@ headers(Socket, Request, Headers, _Body, ?MAX_HEADERS) ->
     %% Too many headers sent, bad request.
     mochiweb_socket:setopts(Socket, [{packet, raw}]),
     handle_invalid_request(Socket, Request, Headers);
-headers(Socket, Request, Headers, {WwwLoop, WsLoop} = Body, HeaderCount) ->
+headers(Socket, Request, Headers, Body, HeaderCount) ->
     case mochiweb_socket:recv(Socket, 0, ?HEADERS_RECV_TIMEOUT) of
         {ok, http_eoh} ->
             mochiweb_socket:setopts(Socket, [{packet, raw}]),
@@ -151,21 +148,25 @@ headers(Socket, Request, Headers, {WwwLoop, WsLoop} = Body, HeaderCount) ->
                     io:format("notmal -> ws~n",[]),
                     {_, {abs_path,Path}, _} = Request,
                     ok = websocket_init(Socket, Path, H),
-                    Active = false,
-                    case Active of
+                    case Body#body.websocket_active of
                         true ->
                             {ok, WSPid} = mochiweb_websocket_delegate:start_link(Path, H, self()),
                             mochiweb_websocket_delegate:go(WSPid, Socket),
-                            call_body(WsLoop, WSPid);
+                            call_body(Body#body.websocket_loop, WSPid);
                         false ->
                             WsReq = mochiweb_wsrequest:new(Socket, Path, H),
-                            call_body(WsLoop, WsReq)
+                            call_body(Body#body.websocket_loop, WsReq);
+                        undefined ->
+                            Req = mochiweb:new_request({Socket, Request,
+                                                lists:reverse(Headers)}),
+                            io:format("Websocket upgrade requested, but no websocket handler provided: ~s~n",[Req:get(path)]),
+                            Req:not_found()
                     end;
                 X -> %% not websocket:
                     io:format("notmal~p~n",[X]),
                     Req = mochiweb:new_request({Socket, Request,
                                                 lists:reverse(Headers)}),
-                    call_body(WwwLoop, Req),
+                    call_body(Body#body.http_loop, Req),
                     ?MODULE:after_response(Body, Req)
             end;
         {ok, {http_header, _, Name, _, Value}} ->
diff --git a/src/mochiweb_socket_server.erl b/src/mochiweb_socket_server.erl
index e229402..3218195 100644
--- a/src/mochiweb_socket_server.erl
+++ b/src/mochiweb_socket_server.erl
@@ -17,7 +17,6 @@
 -record(mochiweb_socket_server,
         {port,
          loop,
-         wsloop,
          name=undefined,
          %% NOTE: This is currently ignored.
          max=2048,
@@ -86,8 +85,6 @@ parse_options([{ip, Ip} | Rest], State) ->
     parse_options(Rest, State#mochiweb_socket_server{ip=ParsedIp});
 parse_options([{loop, Loop} | Rest], State) ->
     parse_options(Rest, State#mochiweb_socket_server{loop=Loop});
-parse_options([{wsloop, Loop} | Rest], State) ->
-    parse_options(Rest, State#mochiweb_socket_server{wsloop=Loop});
 parse_options([{backlog, Backlog} | Rest], State) ->
     parse_options(Rest, State#mochiweb_socket_server{backlog=Backlog});
 parse_options([{nodelay, NoDelay} | Rest], State) ->
diff --git a/src/mochiweb_websocket_delegate.erl b/src/mochiweb_websocket_delegate.erl
index 6c7bd85..e77c79e 100644
--- a/src/mochiweb_websocket_delegate.erl
+++ b/src/mochiweb_websocket_delegate.erl
@@ -9,6 +9,8 @@
 %% However, at time of writing (Oct 8, 2010) Chrome 6 and Firefox 4 implement
 %% an older version of the websocket spec, where messages are framed 0x00...0xFF
 %% so the newer protocol with length headers has not been tested with a browser.
+%%
+%% Guarantees that 'closed' will be sent to the client pid once the socket dies
 
 -module(mochiweb_websocket_delegate).
 -behaviour(gen_server).
@@ -89,10 +91,13 @@ handle_info({'EXIT', _, _}, State) ->
     State#state.dest ! closed,
     {stop, normal, State};    
 handle_info({tcp_closed, Sock}, State = #state{socket=Sock}) ->
+    io:format("TCP CLOSED~n",[]),
     State#state.dest ! closed,
     {stop, normal, State};
 handle_info({tcp_error, Sock, Reason}, State = #state{socket=Sock}) ->
+    io:format("TCP ERROR~n",[]),
     State#state.dest ! {error, Reason},
+    State#state.dest ! closed,
     {stop, normal, State};
 handle_info({tcp, Sock, Data}, State = #state{socket=Sock, buffer=Buffer}) ->
     %mochiweb_socket:setopts(Sock, [{active, once}]),


[couchdb-mochiweb] 05/08: begin restructure

Posted by wi...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

willholley pushed a commit to tag active-passive
in repository https://gitbox.apache.org/repos/asf/couchdb-mochiweb.git

commit 26f9c01753411ef2905b272aa43a17bdf9dc64a8
Author: Richard Jones <rj...@metabrew.com>
AuthorDate: Sat Nov 20 15:45:30 2010 +0000

    begin restructure
---
 .../{websockets.erl => websockets_active.erl}      | 31 +++++-----------------
 .../{websockets.erl => websockets_passive.erl}     | 31 +++++-----------------
 src/mochiweb_http.erl                              | 17 +++++++++++-
 3 files changed, 30 insertions(+), 49 deletions(-)

diff --git a/examples/websockets/websockets.erl b/examples/websockets/websockets_active.erl
similarity index 63%
copy from examples/websockets/websockets.erl
copy to examples/websockets/websockets_active.erl
index ebd8fc6..48a0ad6 100644
--- a/examples/websockets/websockets.erl
+++ b/examples/websockets/websockets_active.erl
@@ -1,16 +1,19 @@
--module(websockets).
+-module(websockets_active).
 -author('author <rj...@metabrew.com>').
 
--export([start/0, start/1, stop/0, loop/2, wsloop_active/1]).
+-export([start/0, start/1, stop/0, loop/2, wsloop_active/1, wsloop/1]).
 
 start() -> start([{port, 8080}, {docroot, "."}]).
 
 start(Options) ->
     {DocRoot, Options1} = get_option(docroot, Options),
     Loop = fun (Req) -> ?MODULE:loop(Req, DocRoot) end,
+    % websockets options:
+    WsOpts = [ {active, true},
+               {loop,   {?MODULE, wsloop_active}} ],
     mochiweb_http:start([{name, ?MODULE}, 
-                         {loop, Loop}, 
-                         {wsloop, {?MODULE, wsloop_active}} | Options1]).
+                         {loop, Loop},
+                         {websockets_opts, WsOpts} | Options1]).
 
 stop() ->
     mochiweb_http:stop(?MODULE).
@@ -35,26 +38,6 @@ wsloop_active0(Pid) ->
     end,
     wsloop_active0(Pid).
 
-wsloop(Ws) ->
-    io:format("Websocket request, path: ~p~n", [Ws:get(path)]),
-    case Ws:get_data() of
-        closed ->  ok;
-        closing -> ok;
-        timeout -> timeout;
-        
-        % older websockets spec which is in the wild, messages are framed with
-        % 0x00...0xFF
-        {legacy_frame, Body} ->
-            Ws:send(["YOU SENT US LEGACY FRAME: ", Body]),
-            wsloop(Ws);
-    
-        % current spec, each message has a 0xFF/<64bit length> header
-        % and must contain utf8 bytestream
-        {utf8_frame, Body} ->
-            Ws:send(["YOU SENT US MODERN FRAME: ", Body]),
-            wsloop(Ws)
-    end.
-
 loop(Req, DocRoot) ->
     "/" ++ Path = Req:get(path),
     case Req:get(method) of
diff --git a/examples/websockets/websockets.erl b/examples/websockets/websockets_passive.erl
similarity index 66%
rename from examples/websockets/websockets.erl
rename to examples/websockets/websockets_passive.erl
index ebd8fc6..979ad71 100644
--- a/examples/websockets/websockets.erl
+++ b/examples/websockets/websockets_passive.erl
@@ -1,40 +1,23 @@
--module(websockets).
+-module(websockets_passive).
 -author('author <rj...@metabrew.com>').
 
--export([start/0, start/1, stop/0, loop/2, wsloop_active/1]).
+-export([start/0, start/1, stop/0, loop/2, wsloop/1]).
 
 start() -> start([{port, 8080}, {docroot, "."}]).
 
 start(Options) ->
     {DocRoot, Options1} = get_option(docroot, Options),
     Loop = fun (Req) -> ?MODULE:loop(Req, DocRoot) end,
+    % websockets options:
+    WsOpts = [ {active, false},
+               {loop,   {?MODULE, wsloop}} ],
     mochiweb_http:start([{name, ?MODULE}, 
-                         {loop, Loop}, 
-                         {wsloop, {?MODULE, wsloop_active}} | Options1]).
+                         {loop, Loop},
+                         {websockets_opts, WsOpts} | Options1]). 
 
 stop() ->
     mochiweb_http:stop(?MODULE).
 
-wsloop_active(Pid) ->
-    mochiweb_websocket_delegate:send(Pid, "WELCOME MSG!"),
-    wsloop_active0(Pid).
-
-wsloop_active0(Pid) ->
-    receive
-        closed ->
-            io:format("client api got closed~n",[]),
-            ok;
-        {error, _Reason} ->
-            ok;
-        % {legacy_frame, M} or {utf8_frame, M}
-        {_, X} ->
-            Msg = io_lib:format("SRVER_GOT: ~p", [X]),
-            mochiweb_websocket_delegate:send(Pid, Msg)
-    after 10000 ->
-            mochiweb_websocket_delegate:send(Pid, "IDLE!")
-    end,
-    wsloop_active0(Pid).
-
 wsloop(Ws) ->
     io:format("Websocket request, path: ~p~n", [Ws:get(path)]),
     case Ws:get_data() of
diff --git a/src/mochiweb_http.erl b/src/mochiweb_http.erl
index 69255d4..fcec33f 100644
--- a/src/mochiweb_http.erl
+++ b/src/mochiweb_http.erl
@@ -17,8 +17,23 @@
 -define(DEFAULTS, [{name, ?MODULE},
                    {port, 8888}]).
 
+% client loop holds fun/info on how to hand off request to client code
+-record(clientloop, {http_loop, websocket_loop, websocket_active}). 
+
 parse_options(Options) ->
     HttpLoop = proplists:get_value(loop, Options),
+    case proplists:get_value(websocket_opts, Options) of
+        WsProps when is_list(WsProps) ->
+            WsLoop   = proplists:get_value(loop, WsProps),
+            WsActive = proplists:get_value(active, WsProps, false);
+        _ ->
+            WsLoop   = undefined,
+            WsActive = undefined
+    end,
+    ClientLoop = #clientloop{http_loop        = HttpLoop,
+                             websocket_loop   = WsLoop,
+                             websocket_active = WsActive},
+                
     WsLoop   = proplists:get_value(wsloop, Options),
     Loop = fun (S) ->
                    ?MODULE:loop(S, {HttpLoop, WsLoop})
@@ -136,7 +151,7 @@ headers(Socket, Request, Headers, {WwwLoop, WsLoop} = Body, HeaderCount) ->
                     io:format("notmal -> ws~n",[]),
                     {_, {abs_path,Path}, _} = Request,
                     ok = websocket_init(Socket, Path, H),
-                    Active = true,
+                    Active = false,
                     case Active of
                         true ->
                             {ok, WSPid} = mochiweb_websocket_delegate:start_link(Path, H, self()),