Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also compare across forks.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also compare across forks.
base fork: jbothma/cowboy
base: master
...
head fork: RJ/cowboy
compare: x-webkit-deflate-frame
Checking mergeability… Don’t worry, you can still create the pull request.
  • 2 commits
  • 9 files changed
  • 0 commit comments
  • 1 contributor
View
6 CHANGELOG.md
@@ -1,6 +1,12 @@
CHANGELOG
=========
+x-webkit-deflate-frame branch
+-----------------------------
+
+* Add x-webkit-deflate-frame compression extension for websockets
+ (prefixed version of unadopted deflate-frame spec, in Chrome 22 etc.)
+
next
----
View
5 examples/websockets/README.md
@@ -0,0 +1,5 @@
+Websockets example
+==================
+
+$ rebar compile
+$ ./start.sh
View
15 examples/websockets/src/websockets.app.src
@@ -0,0 +1,15 @@
+%% Feel free to use, reuse and abuse the code in this file.
+
+{application, websockets, [
+ {description, "Cowboy Websockets example."},
+ {vsn, "1"},
+ {modules, []},
+ {registered, []},
+ {applications, [
+ kernel,
+ stdlib,
+ cowboy
+ ]},
+ {mod, {websockets_app, []}},
+ {env, []}
+]}.
View
11 examples/websockets/src/websockets.erl
@@ -0,0 +1,11 @@
+-module(websockets).
+
+%% API.
+-export([start/0]).
+
+%% API.
+
+start() ->
+ ok = application:start(ranch),
+ ok = application:start(cowboy),
+ ok = application:start(websockets).
View
23 examples/websockets/src/websockets_app.erl
@@ -0,0 +1,23 @@
+%% @private
+-module(websockets_app).
+-behaviour(application).
+
+%% API.
+-export([start/2]).
+-export([stop/1]).
+
+%% API.
+
+start(_Type, _Args) ->
+ Dispatch = [
+ {'_', [
+ {[], websockets_handler, []}
+ ]}
+ ],
+ {ok, _} = cowboy:start_http(http, 100, [{port, 8080}], [
+ {dispatch, Dispatch}
+ ]),
+ websockets_sup:start_link().
+
+stop(_State) ->
+ ok.
View
88 examples/websockets/src/websockets_handler.erl
@@ -0,0 +1,88 @@
+-module(websockets_handler).
+-behaviour(cowboy_http_handler).
+-behaviour(cowboy_websocket_handler).
+
+-export([init/3, handle/2, terminate/2]).
+
+-export([websocket_init/3, websocket_handle/3,
+ websocket_info/3, websocket_terminate/3]).
+
+init({_Any, http}, Req, []) ->
+ case cowboy_req:header(<<"upgrade">>, Req) of
+ {undefined, Req2} -> {ok, Req2, undefined};
+ {<<"websocket">>, _Req2} -> {upgrade, protocol, cowboy_websocket}
+ end.
+
+handle(Req, State) ->
+ {ok, Req2} = cowboy_req:reply(200, [{<<"Content-Type">>, <<"text/html">>}],
+%% HTML code taken from misultin's example file.
+<<"<html>
+<head>
+<script type=\"text/javascript\">
+function addStatus(text){
+ var date = new Date();
+ document.getElementById('status').innerHTML
+ = document.getElementById('status').innerHTML
+ + date + \": \" + text + \"<br/>\";
+}
+function ready(){
+ if (\"MozWebSocket\" in window) {
+ WebSocket = MozWebSocket;
+ }
+ if (\"WebSocket\" in window) {
+ // browser supports websockets
+ var ws = new WebSocket( (window.location.protocol == 'http:'
+ ? 'ws://' : 'wss://') +
+ window.location.host + '/');
+ ws.onopen = function() {
+ // websocket is connected
+ addStatus('websocket connected!');
+ // send hello data to server.
+ var msg = 'hello hello hello what\\'s going on \\'ere then?';
+ ws.send(msg);
+ addStatus('sent message to server: ' + msg);
+ };
+ ws.onmessage = function (evt) {
+ var receivedMsg = evt.data;
+ addStatus(\"server sent the following: '\" + receivedMsg + \"'\");
+ };
+ ws.onclose = function() {
+ // websocket was closed
+ addStatus(\"websocket was closed\");
+ };
+ } else {
+ // browser does not support websockets
+ addStatus(\"sorry, your browser does not support websockets.\");
+ }
+}
+</script>
+</head>
+<body onload=\"\">
+Hi!
+<input type=\"button\" onclick=\"ready();\" value=\"click\"/>
+<div id=\"status\"></div>
+</body>
+</html>">>, Req),
+ {ok, Req2, State}.
+
+terminate(_Req, _State) ->
+ ok.
+
+websocket_init(_Any, Req, []) ->
+ timer:send_interval(1000, tick),
+ Req2 = cowboy_req:compact(Req),
+ {ok, Req2, undefined, hibernate}.
+
+websocket_handle({text, Msg}, Req, State) ->
+ {reply, {text, << "You said: ", Msg/binary >>}, Req, State, hibernate};
+websocket_handle(_Any, Req, State) ->
+ {ok, Req, State}.
+
+%% send some repeated text to show compression at work if anyone is counting
+websocket_info(tick, Req, State) ->
+ {reply, {text, <<"badger badger badger badger badger badger badger">>}, Req, State, hibernate};
+websocket_info(_Info, Req, State) ->
+ {ok, Req, State, hibernate}.
+
+websocket_terminate(_Reason, _Req, _State) ->
+ ok.
View
21 examples/websockets/src/websockets_sup.erl
@@ -0,0 +1,21 @@
+%% @private
+-module(websockets_sup).
+-behaviour(supervisor).
+
+%% API.
+-export([start_link/0]).
+
+%% supervisor.
+-export([init/1]).
+
+%% API.
+
+-spec start_link() -> {ok, pid()}.
+start_link() ->
+ supervisor:start_link({local, ?MODULE}, ?MODULE, []).
+
+%% supervisor.
+
+init([]) ->
+ Procs = [],
+ {ok, {{one_for_one, 10, 10}, Procs}}.
View
3  examples/websockets/start.sh
@@ -0,0 +1,3 @@
+#!/bin/sh
+erl -pa ebin ../../ebin ../../deps/*/ebin -boot start_sasl -s websockets \
+ -eval "io:format(\"Point your browser at http://localhost:8080~n\")."
View
264 src/cowboy_websocket.erl
@@ -19,6 +19,9 @@
%% is no need for concern as crypto is already included.
-module(cowboy_websocket).
+%% Should we use x-webkit-deflate-frame extension?
+-define(ENABLE_X_WEBKIT_DEFLATE_FRAME, true).
+
%% API.
-export([upgrade/4]).
@@ -36,6 +39,14 @@
{nofin, opcode(), binary()} | %% first fragment has been unmasked.
{fin, opcode(), binary()}. %% last fragment has been seen.
+%% State and settings for (hybi) websockets extensions, like compression
+-record(extinfo, {
+ 'x-webkit-deflate-frame' :: any(),
+ z_inf :: any(), %% zlib inflate context
+ z_def :: any(), %% zlib deflate context
+ headers = [] :: [ binary() ] %% headers for Sec-WebSocket-Extensions:
+}).
+
-record(state, {
socket = undefined :: inet:socket(),
transport = undefined :: module(),
@@ -49,7 +60,8 @@
hibernate = false :: boolean(),
eop :: undefined | tuple(), %% hixie-76 specific.
origin = undefined :: undefined | binary(), %% hixie-76 specific.
- frag_state = undefined :: frag_state()
+ frag_state = undefined :: frag_state(),
+ extinfo = #extinfo{} :: #extinfo{} %% used by websocket extensions, like compression
}).
%% @doc Upgrade a HTTP request to the WebSocket protocol.
@@ -105,9 +117,40 @@ websocket_upgrade(Version, State, Req)
{Key, Req2} = cowboy_req:header(<<"sec-websocket-key">>, Req),
false = Key =:= undefined,
Challenge = hybi_challenge(Key),
+ {Req3, State2} = check_extensions(Version, Req2, State),
IntVersion = list_to_integer(binary_to_list(Version)),
- {ok, State#state{version=IntVersion, challenge=Challenge},
- cowboy_req:set_meta(websocket_version, IntVersion, Req2)}.
+ {ok, State2#state{version=IntVersion, challenge=Challenge},
+ cowboy_req:set_meta(websocket_version, IntVersion, Req3)}.
+
+
+check_extensions(<<"13">>, Req, State = #state{}) when ?ENABLE_X_WEBKIT_DEFLATE_FRAME ->
+ {H, Req2} = cowboy_req:header(<<"sec-websocket-extensions">>, Req),
+ %% TODO parse this header as per ABNF in the RFC..
+ %% for now: this is the only header seen in the wild:
+ case H of
+ <<"x-webkit-deflate-frame">> ->
+ NewState = init_deflate_frame(State),
+ {Req2, NewState};
+ _ ->
+ {Req2, State}
+ end;
+check_extensions(_Ver, Req, State) ->
+ {Req, State}.
+
+init_deflate_frame(State) ->
+ ExtInfo = State#state.extinfo,
+ Header = <<"x-webkit-deflate-frame">>,
+ Zinf = zlib:open(),
+ Zdef = zlib:open(),
+ ok = zlib:inflateInit(Zinf, -15),
+ ok = zlib:deflateInit(Zdef, best_compression, deflated, -15, 9, default),
+ NewExtInfo = ExtInfo#extinfo{
+ 'x-webkit-deflate-frame' = true,
+ z_inf = Zinf,
+ z_def = Zdef,
+ headers = [ Header | ExtInfo#extinfo.headers ]
+ },
+ State#state{extinfo = NewExtInfo}.
-spec handler_init(#state{}, cowboy_req:req()) -> closed.
handler_init(State=#state{transport=Transport, handler=Handler, opts=Opts},
@@ -179,10 +222,12 @@ websocket_handshake(State=#state{socket=Socket, transport=Transport,
end;
websocket_handshake(State=#state{transport=Transport, challenge=Challenge},
Req, HandlerState) ->
+ ExtHeaders = hybi_extensions_response_headers(State),
{ok, Req2} = cowboy_req:upgrade_reply(
101,
[{<<"Upgrade">>, <<"websocket">>},
- {<<"Sec-Websocket-Accept">>, Challenge}],
+ {<<"Sec-Websocket-Accept">>, Challenge}
+ | ExtHeaders ],
Req),
%% Flush the resp_sent message before moving on.
receive {cowboy_req, resp_sent} -> ok after 0 -> ok end,
@@ -262,30 +307,30 @@ websocket_data(State=#state{version=Version}, Req, HandlerState, Data)
handler_before_loop(State, Req, HandlerState, Data);
%% 7 bit payload length prefix exists
websocket_data(State, Req, HandlerState,
- << Fin:1, Rsv:3, Opcode:4, Mask:1, PayloadLen:7, Rest/bits >>
+ << Fin:1, Rsv:3/binary-unit:1, Opcode:4, Mask:1, PayloadLen:7, Rest/bits >>
= Data) when PayloadLen < 126 ->
websocket_data(State, Req, HandlerState,
Fin, Rsv, Opcode, Mask, PayloadLen, Rest, Data);
%% 7+16 bits payload length prefix exists
websocket_data(State, Req, HandlerState,
- << Fin:1, Rsv:3, Opcode:4, Mask:1, 126:7, PayloadLen:16, Rest/bits >>
+ << Fin:1, Rsv:3/binary-unit:1, Opcode:4, Mask:1, 126:7, PayloadLen:16, Rest/bits >>
= Data) when PayloadLen > 125 ->
websocket_data(State, Req, HandlerState,
Fin, Rsv, Opcode, Mask, PayloadLen, Rest, Data);
%% 7+16 bits payload length prefix missing
websocket_data(State, Req, HandlerState,
- << _Fin:1, _Rsv:3, _Opcode:4, _Mask:1, 126:7, Rest/bits >>
+ << _Fin:1, _Rsv:3/binary-unit:1, _Opcode:4, _Mask:1, 126:7, Rest/bits >>
= Data) when byte_size(Rest) < 2 ->
handler_before_loop(State, Req, HandlerState, Data);
%% 7+64 bits payload length prefix exists
websocket_data(State, Req, HandlerState,
- << Fin:1, Rsv:3, Opcode:4, Mask:1, 127:7, 0:1, PayloadLen:63,
+ << Fin:1, Rsv:3/binary-unit:1, Opcode:4, Mask:1, 127:7, 0:1, PayloadLen:63,
Rest/bits >> = Data) when PayloadLen > 16#FFFF ->
websocket_data(State, Req, HandlerState,
Fin, Rsv, Opcode, Mask, PayloadLen, Rest, Data);
%% 7+64 bits payload length prefix missing
websocket_data(State, Req, HandlerState,
- << _Fin:1, _Rsv:3, _Opcode:4, _Mask:1, 127:7, Rest/bits >>
+ << _Fin:1, _Rsv:3/binary-unit:1, _Opcode:4, _Mask:1, 127:7, Rest/bits >>
= Data) when byte_size(Rest) < 8 ->
handler_before_loop(State, Req, HandlerState, Data);
%% invalid payload length prefix.
@@ -303,44 +348,48 @@ websocket_data(State=#state{frag_state=undefined}, Req, HandlerState,
websocket_data(State, Req, HandlerState, _Fin=0, _Rsv=0, Opcode, _Mask,
_PayloadLen, _Rest, _Buffer) when Opcode >= 8 ->
websocket_close(State, Req, HandlerState, {error, badframe});
+
+
+
+
%% The opcode is only included in the first message fragment.
websocket_data(State=#state{frag_state=undefined}, Req, HandlerState,
- _Fin=0, _Rsv=0, Opcode, Mask, PayloadLen, Rest, Data) ->
+ _Fin=0, Rsv, Opcode, Mask, PayloadLen, Rest, Data) ->
websocket_before_unmask(
State#state{frag_state={nofin, Opcode}}, Req, HandlerState,
- Data, Rest, 0, Mask, PayloadLen);
+ Data, Rest, 0, Mask, Rsv, PayloadLen);
%% non-control opcode when expecting control message or next fragment.
websocket_data(State=#state{frag_state={nofin, _, _}}, Req, HandlerState, _Fin,
- _Rsv=0, Opcode, _Mask, _Ln, _Rest, _Data) when Opcode > 0, Opcode < 8 ->
+ _Rsv, Opcode, _Mask, _Ln, _Rest, _Data) when Opcode > 0, Opcode < 8 ->
websocket_close(State, Req, HandlerState, {error, badframe});
%% If the first message fragment was incomplete, retry unmasking.
websocket_data(State=#state{frag_state={nofin, Opcode}}, Req, HandlerState,
- _Fin=0, _Rsv=0, Opcode, Mask, PayloadLen, Rest, Data) ->
+ _Fin=0, Rsv, Opcode, Mask, PayloadLen, Rest, Data) ->
websocket_before_unmask(
State#state{frag_state={nofin, Opcode}}, Req, HandlerState,
- Data, Rest, 0, Mask, PayloadLen);
+ Data, Rest, 0, Mask, Rsv, PayloadLen);
%% if the opcode is zero and the fin flag is zero, unmask and await next.
websocket_data(State=#state{frag_state={nofin, _Opcode, _Payloads}}, Req,
- HandlerState, _Fin=0, _Rsv=0, _Opcode2=0, Mask, PayloadLen, Rest,
+ HandlerState, _Fin=0, Rsv, _Opcode2=0, Mask, PayloadLen, Rest,
Data) ->
websocket_before_unmask(
- State, Req, HandlerState, Data, Rest, 0, Mask, PayloadLen);
+ State, Req, HandlerState, Data, Rest, 0, Mask, Rsv, PayloadLen);
%% when the last fragment is seen. Update the fragmentation status.
websocket_data(State=#state{frag_state={nofin, Opcode, Payloads}}, Req,
- HandlerState, _Fin=1, _Rsv=0, _Opcode=0, Mask, PayloadLen, Rest,
+ HandlerState, _Fin=1, Rsv, _Opcode=0, Mask, PayloadLen, Rest,
Data) ->
websocket_before_unmask(
State#state{frag_state={fin, Opcode, Payloads}},
- Req, HandlerState, Data, Rest, 0, Mask, PayloadLen);
+ Req, HandlerState, Data, Rest, 0, Mask, Rsv, PayloadLen);
%% control messages MUST NOT use 7+16 bits or 7+64 bits payload length prefixes
websocket_data(State, Req, HandlerState, _Fin, _Rsv, Opcode, _Mask, PayloadLen,
_Rest, _Data) when Opcode >= 8, PayloadLen > 125 ->
websocket_close(State, Req, HandlerState, {error, badframe});
%% unfragmented message. unmask and dispatch the message.
-websocket_data(State=#state{version=Version}, Req, HandlerState, _Fin=1, _Rsv=0,
+websocket_data(State=#state{version=Version}, Req, HandlerState, _Fin=1, Rsv,
Opcode, Mask, PayloadLen, Rest, Data) when Version =/= 0 ->
websocket_before_unmask(
- State, Req, HandlerState, Data, Rest, Opcode, Mask, PayloadLen);
+ State, Req, HandlerState, Data, Rest, Opcode, Mask, Rsv, PayloadLen);
%% Something was wrong with the frame. Close the connection.
websocket_data(State, Req, HandlerState, _Fin, _Rsv, _Opcode, _Mask,
_PayloadLen, _Rest, _Data) ->
@@ -348,99 +397,137 @@ websocket_data(State, Req, HandlerState, _Fin, _Rsv, _Opcode, _Mask,
%% hybi routing depending on whether unmasking is needed.
-spec websocket_before_unmask(#state{}, cowboy_req:req(), any(), binary(),
- binary(), opcode(), 0 | 1, non_neg_integer() | undefined) -> closed.
+ binary(), opcode(), binary(), 0 | 1, non_neg_integer() | undefined) -> closed.
websocket_before_unmask(State, Req, HandlerState, Data,
- Rest, Opcode, Mask, PayloadLen) ->
- case {Mask, PayloadLen} of
- {0, 0} ->
- websocket_dispatch(State, Req, HandlerState, Rest, Opcode, <<>>);
- {1, N} when N + 4 > byte_size(Rest); N =:= undefined ->
+ Rest, Opcode, Mask, Rsv, PayloadLen) ->
+ case {Mask, PayloadLen, Rsv} of
+ {0, 0, << 0:3 >>} ->
+ websocket_dispatch(State, Req, HandlerState, Rest, Opcode, Rsv, <<>>);
+ {1, N, << 0:3 >>} when N + 4 > byte_size(Rest); N =:= undefined ->
%% @todo We probably should allow limiting frame length.
handler_before_loop(State, Req, HandlerState, Data);
- {1, _N} ->
+ {1, _N, Rsv} ->
<< MaskKey:32, Payload:PayloadLen/binary, Rest2/bits >> = Rest,
websocket_unmask(State, Req, HandlerState, Rest2,
- Opcode, Payload, MaskKey)
+ Opcode, Payload, MaskKey, Rsv)
end.
%% hybi unmasking.
-spec websocket_unmask(#state{}, cowboy_req:req(), any(), binary(),
- opcode(), binary(), mask_key()) -> closed.
+ opcode(), binary(), mask_key(), binary()) -> closed.
websocket_unmask(State, Req, HandlerState, RemainingData,
- Opcode, Payload, MaskKey) ->
+ Opcode, Payload, MaskKey, Rsv) ->
websocket_unmask(State, Req, HandlerState, RemainingData,
- Opcode, Payload, MaskKey, <<>>).
+ Opcode, Payload, MaskKey, Rsv, <<>>).
-spec websocket_unmask(#state{}, cowboy_req:req(), any(), binary(),
- opcode(), binary(), mask_key(), binary()) -> closed.
+ opcode(), binary(), mask_key(), binary(), binary()) -> closed.
websocket_unmask(State, Req, HandlerState, RemainingData,
- Opcode, << O:32, Rest/bits >>, MaskKey, Acc) ->
+ Opcode, << O:32, Rest/bits >>, MaskKey, Rsv, Acc) ->
T = O bxor MaskKey,
websocket_unmask(State, Req, HandlerState, RemainingData,
- Opcode, Rest, MaskKey, << Acc/binary, T:32 >>);
+ Opcode, Rest, MaskKey, Rsv, << Acc/binary, T:32 >>);
websocket_unmask(State, Req, HandlerState, RemainingData,
- Opcode, << O:24 >>, MaskKey, Acc) ->
+ Opcode, << O:24 >>, MaskKey, Rsv, Acc) ->
<< MaskKey2:24, _:8 >> = << MaskKey:32 >>,
T = O bxor MaskKey2,
websocket_dispatch(State, Req, HandlerState, RemainingData,
- Opcode, << Acc/binary, T:24 >>);
+ Opcode, Rsv, << Acc/binary, T:24 >>);
websocket_unmask(State, Req, HandlerState, RemainingData,
- Opcode, << O:16 >>, MaskKey, Acc) ->
+ Opcode, << O:16 >>, MaskKey, Rsv, Acc) ->
<< MaskKey2:16, _:16 >> = << MaskKey:32 >>,
T = O bxor MaskKey2,
websocket_dispatch(State, Req, HandlerState, RemainingData,
- Opcode, << Acc/binary, T:16 >>);
+ Opcode, Rsv, << Acc/binary, T:16 >>);
websocket_unmask(State, Req, HandlerState, RemainingData,
- Opcode, << O:8 >>, MaskKey, Acc) ->
+ Opcode, << O:8 >>, MaskKey, Rsv, Acc) ->
<< MaskKey2:8, _:24 >> = << MaskKey:32 >>,
T = O bxor MaskKey2,
websocket_dispatch(State, Req, HandlerState, RemainingData,
- Opcode, << Acc/binary, T:8 >>);
+ Opcode, Rsv, << Acc/binary, T:8 >>);
websocket_unmask(State, Req, HandlerState, RemainingData,
- Opcode, <<>>, _MaskKey, Acc) ->
+ Opcode, <<>>, _MaskKey, Rsv, Acc) ->
websocket_dispatch(State, Req, HandlerState, RemainingData,
- Opcode, Acc).
+ Opcode, Rsv, Acc).
%% hybi dispatching.
-spec websocket_dispatch(#state{}, cowboy_req:req(), any(), binary(),
- opcode(), binary()) -> closed.
+ opcode(), binary(), binary()) -> closed.
%% First frame of a fragmented message unmasked. Expect intermediate or last.
+websocket_dispatch(State=#state{frag_state={nofin, Opcode},
+ extinfo=#extinfo{'x-webkit-deflate-frame'=X,z_inf=Zinf}},
+ Req, HandlerState, RemainingData, 0, << 1:1, 0:2 >>, Payload)
+ when Opcode >= 8, X =/= undefined ->
+ websocket_data(State#state{frag_state={nofin, Opcode, inflate_payload_data(Payload, Zinf)}},
+ Req, HandlerState, RemainingData);
websocket_dispatch(State=#state{frag_state={nofin, Opcode}}, Req, HandlerState,
- RemainingData, 0, Payload) ->
+ RemainingData, 0, << 0:3 >>, Payload) ->
websocket_data(State#state{frag_state={nofin, Opcode, Payload}},
Req, HandlerState, RemainingData);
+
%% Intermediate frame of a fragmented message unmasked. Add payload to buffer.
+websocket_dispatch(State=#state{frag_state={nofin, Opcode, Payloads},
+ extinfo=#extinfo{'x-webkit-deflate-frame'=X,z_inf=Zinf}},
+ Req, HandlerState, RemainingData, 0, << 1:1, 0:2 >>, Payload)
+ when Opcode >= 8, X =/= undefined ->
+ InfPayload = inflate_payload_data(Payload, Zinf),
+ websocket_data(State#state{frag_state={nofin, Opcode,
+ <<Payloads/binary, InfPayload/binary>>}}, Req, HandlerState,
+ RemainingData);
websocket_dispatch(State=#state{frag_state={nofin, Opcode, Payloads}}, Req,
- HandlerState, RemainingData, 0, Payload) ->
+ HandlerState, RemainingData, 0, << 0:3 >>, Payload) ->
websocket_data(State#state{frag_state={nofin, Opcode,
<<Payloads/binary, Payload/binary>>}}, Req, HandlerState,
RemainingData);
+
+
%% Last frame of a fragmented message unmasked. Dispatch to handler.
+websocket_dispatch(State=#state{frag_state={fin, Opcode, Payloads},
+ extinfo=#extinfo{'x-webkit-deflate-frame'=X,z_inf=Zinf}},
+ Req, HandlerState, RemainingData, 0, << 1:1, RsvRest:2 >>, Payload)
+ when Opcode >= 8, X =/= undefined ->
+ InfPayload = inflate_payload_data(Payload, Zinf),
+ %% Set Rsv1 back to 0 since we've inflated this by now:
+ websocket_dispatch(State#state{frag_state=undefined}, Req, HandlerState,
+ RemainingData, Opcode, << 0:1, RsvRest/binary >>, <<Payloads/binary, InfPayload/binary>>);
+
websocket_dispatch(State=#state{frag_state={fin, Opcode, Payloads}}, Req,
- HandlerState, RemainingData, 0, Payload) ->
+ HandlerState, RemainingData, 0, Rsv = << 0:3 >>, Payload) ->
websocket_dispatch(State#state{frag_state=undefined}, Req, HandlerState,
- RemainingData, Opcode, <<Payloads/binary, Payload/binary>>);
+ RemainingData, Opcode, Rsv, <<Payloads/binary, Payload/binary>>);
+
+
%% Text frame.
-websocket_dispatch(State, Req, HandlerState, RemainingData, 1, Payload) ->
+websocket_dispatch(State = #state{extinfo=#extinfo{
+ 'x-webkit-deflate-frame'=X,z_inf=Zinf}}, Req, HandlerState,
+ RemainingData, 1, << 1:1, 0:2 >>, Payload) when X =/= undefined ->
+ handler_call(State, Req, HandlerState, RemainingData,
+ websocket_handle, {text, inflate_payload_data(Payload, Zinf)}, fun websocket_data/4);
+websocket_dispatch(State, Req, HandlerState, RemainingData, 1, << 0:3 >>, Payload) ->
handler_call(State, Req, HandlerState, RemainingData,
websocket_handle, {text, Payload}, fun websocket_data/4);
%% Binary frame.
-websocket_dispatch(State, Req, HandlerState, RemainingData, 2, Payload) ->
+websocket_dispatch(State = #state{extinfo=#extinfo{
+ 'x-webkit-deflate-frame'=X,z_inf=Zinf}}, Req, HandlerState,
+ RemainingData, 2, << 1:1, 0:2 >>, Payload) when X =/= undefined ->
+ handler_call(State, Req, HandlerState, RemainingData,
+ websocket_handle, {binary, inflate_payload_data(Payload, Zinf)}, fun websocket_data/4);
+websocket_dispatch(State, Req, HandlerState, RemainingData, 2, << 0:3 >>, Payload) ->
handler_call(State, Req, HandlerState, RemainingData,
websocket_handle, {binary, Payload}, fun websocket_data/4);
%% Close control frame.
%% @todo Handle the optional Payload.
-websocket_dispatch(State, Req, HandlerState, _RemainingData, 8, _Payload) ->
+websocket_dispatch(State, Req, HandlerState, _RemainingData, 8, _Rsv, _Payload) ->
websocket_close(State, Req, HandlerState, {normal, closed});
%% Ping control frame. Send a pong back and forward the ping to the handler.
websocket_dispatch(State=#state{socket=Socket, transport=Transport},
- Req, HandlerState, RemainingData, 9, Payload) ->
+ Req, HandlerState, RemainingData, 9, _Rsv, Payload) ->
Len = hybi_payload_length(byte_size(Payload)),
Transport:send(Socket, << 1:1, 0:3, 10:4, 0:1, Len/bits, Payload/binary >>),
handler_call(State, Req, HandlerState, RemainingData,
websocket_handle, {ping, Payload}, fun websocket_data/4);
%% Pong control frame.
-websocket_dispatch(State, Req, HandlerState, RemainingData, 10, Payload) ->
+websocket_dispatch(State, Req, HandlerState, RemainingData, 10, _Rsv, Payload) ->
handler_call(State, Req, HandlerState, RemainingData,
websocket_handle, {pong, Payload}, fun websocket_data/4).
@@ -483,17 +570,46 @@ websocket_send({text, Payload}, #state{
%% Ignore all unknown frame types for compatibility with hixie 76.
websocket_send(_Any, #state{version=0}) ->
ignore;
-websocket_send({Type, Payload}, #state{socket=Socket, transport=Transport}) ->
- Opcode = case Type of
- text -> 1;
- binary -> 2;
- ping -> 9;
- pong -> 10
+websocket_send({Type, Payload0}, #state{socket=Socket, transport=Transport,
+ extinfo=#extinfo{
+ 'x-webkit-deflate-frame'=true,
+ z_def=Zdef}}) ->
+ Opcode = hybi_opcode_atom_to_integer(Type),
+ {Payload, Rsv} = case hybi_is_control_opcode(Opcode) of
+ true ->
+ {Payload0, << 0:3 >>}; %% Don't compress control frames
+ false ->
+ Deflated = deflate_payload_data(Payload0, Zdef),
+ %% Rsv bit 1 set when payload deflated
+ {Deflated, << 1:1, 0:2 >>}
end,
Len = hybi_payload_length(iolist_size(Payload)),
+ Transport:send(Socket, [<< 1:1, Rsv/bits, Opcode:4, 0:1, Len/bits >>, Payload]);
+websocket_send({Type, Payload}, #state{socket=Socket, transport=Transport}) ->
+ Opcode = hybi_opcode_atom_to_integer(Type),
+ Len = hybi_payload_length(iolist_size(Payload)),
Transport:send(Socket, [<< 1:1, 0:3, Opcode:4, 0:1, Len/bits >>,
Payload]).
+%% For (x-webkit-)deflate-frame extension:
+deflate_payload_data(Payload, Zdef) when is_list(Payload) ->
+ deflate_payload_data(iolist_to_binary(Payload), Zdef);
+deflate_payload_data(Payload, Zdef) when is_binary(Payload) ->
+ %% Flatten the deflate result so we can strip trailing 0x00,0x00,0xFF,0xFF
+ Deflated = iolist_to_binary(zlib:deflate(Zdef, Payload, full)),
+ LenMinus = erlang:size(Deflated) - 4,
+ case Deflated of
+ <<Main:LenMinus/binary-unit:8, 0:8, 0:8, 255:8, 255:8>> ->
+ Main;
+ _ ->
+ Deflated
+ end.
+
+inflate_payload_data(Data0, Zinf) ->
+ Data = iolist_to_binary(Data0),
+ iolist_to_binary(zlib:inflate(Zinf, << Data/binary, 0:8, 0:8, 255:8, 255:8 >>)).
+
+
-spec websocket_close(#state{}, cowboy_req:req(), any(), {atom(), atom()})
-> closed.
websocket_close(State=#state{socket=Socket, transport=Transport, version=0},
@@ -508,8 +624,9 @@ websocket_close(State=#state{socket=Socket, transport=Transport},
-spec handler_terminate(#state{}, cowboy_req:req(),
any(), atom() | {atom(), atom()}) -> closed.
-handler_terminate(#state{handler=Handler, opts=Opts},
+handler_terminate(State=#state{handler=Handler, opts=Opts},
Req, HandlerState, TerminateReason) ->
+ handle_extensions_terminate(State),
try
Handler:websocket_terminate(TerminateReason, Req, HandlerState)
catch Class:Reason ->
@@ -524,6 +641,16 @@ handler_terminate(#state{handler=Handler, opts=Opts},
end,
closed.
+%% Clean up and resources used by extensions (compression contexts etc)
+-spec handle_extensions_terminate(#state{}) -> ok.
+handle_extensions_terminate(#state{extinfo=#extinfo{z_inf=Zinf, z_def=Zdef}})
+ when Zinf =/= undefined, Zdef =/= undefined ->
+ zlib:close(Zinf),
+ zlib:close(Zdef),
+ ok;
+handle_extensions_terminate(#state{}) ->
+ ok.
+
%% hixie-76 specific.
-spec hixie76_challenge(binary(), binary(), binary()) -> binary().
@@ -553,3 +680,22 @@ hybi_payload_length(N) ->
N when N =< 16#ffff -> << 126:7, N:16 >>;
N when N =< 16#7fffffffffffffff -> << 127:7, N:64 >>
end.
+
+hybi_opcode_atom_to_integer(text) -> 1;
+hybi_opcode_atom_to_integer(binary) -> 2;
+hybi_opcode_atom_to_integer(ping) -> 9;
+hybi_opcode_atom_to_integer(pong) -> 10.
+
+%% Control opcodes have MSB set:
+hybi_is_control_opcode(X) when is_atom(X) ->
+ hybi_is_control_opcode(hybi_opcode_atom_to_integer(X));
+hybi_is_control_opcode(X) when is_integer(X) ->
+ X >= 8.
+
+hybi_extensions_response_headers(#state{extinfo=#extinfo{ headers = [] }}) ->
+ [];
+hybi_extensions_response_headers(#state{extinfo=#extinfo{ headers = [H] }}) ->
+ %% TODO Hlist is a list of headers from each extension
+ %% they need to be joined, with ; between etc as per spec
+ %% this assumes just 1 header for now:
+ [ {<<"Sec-WebSocket-Extensions">>, H} ].

No commit comments for this range

Something went wrong with that request. Please try again.