From 8164b504534e932df24adb65c5e15ed8d8a9feea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Hoguin?= Date: Mon, 12 Nov 2018 18:12:44 +0100 Subject: Add deflate options for Websocket compression They allow the server to configure what it is willing to accept for both the negotiated configuration (takeover and window bits) and the other zlib options (level, mem_level and strategy). This can be used to reduce the memory and/or CPU footprint of the compressed data, which comes with a cost in compression ratio. --- test/handlers/ws_deflate_opts_h.erl | 36 +++++++++++ test/ws_SUITE.erl | 120 +++++++++++++++++++++++++++++++++++- 2 files changed, 155 insertions(+), 1 deletion(-) create mode 100644 test/handlers/ws_deflate_opts_h.erl (limited to 'test') diff --git a/test/handlers/ws_deflate_opts_h.erl b/test/handlers/ws_deflate_opts_h.erl new file mode 100644 index 0000000..1c15efe --- /dev/null +++ b/test/handlers/ws_deflate_opts_h.erl @@ -0,0 +1,36 @@ +%% This module enables compression and returns deflate +%% options depending on the query string. + +-module(ws_deflate_opts_h). +-behavior(cowboy_websocket). + +-export([init/2]). +-export([websocket_handle/2]). +-export([websocket_info/2]). + +init(Req=#{qs := Qs}, State) -> + {Name, Value} = case Qs of + <<"server_context_takeover">> -> {server_context_takeover, takeover}; + <<"server_no_context_takeover">> -> {server_context_takeover, no_takeover}; + <<"client_context_takeover">> -> {client_context_takeover, takeover}; + <<"client_no_context_takeover">> -> {client_context_takeover, no_takeover}; + <<"server_max_window_bits">> -> {server_max_window_bits, 9}; + <<"client_max_window_bits">> -> {client_max_window_bits, 9}; + <<"level">> -> {level, best_speed}; + <<"mem_level">> -> {mem_level, 1}; + <<"strategy">> -> {strategy, rle} + end, + {cowboy_websocket, Req, State, #{ + compress => true, + deflate_opts => #{Name => Value} + }}. + +websocket_handle({text, Data}, State) -> + {reply, {text, Data}, State}; +websocket_handle({binary, Data}, State) -> + {reply, {binary, Data}, State}; +websocket_handle(_, State) -> + {ok, State}. + +websocket_info(_, State) -> + {ok, State}. diff --git a/test/ws_SUITE.erl b/test/ws_SUITE.erl index 284d571..af1be05 100644 --- a/test/ws_SUITE.erl +++ b/test/ws_SUITE.erl @@ -83,7 +83,8 @@ init_dispatch() -> {"/terminate", ws_terminate_h, []}, {"/ws_timeout_hibernate", ws_timeout_hibernate, []}, {"/ws_timeout_cancel", ws_timeout_cancel, []}, - {"/ws_max_frame_size", ws_max_frame_size, []} + {"/ws_max_frame_size", ws_max_frame_size, []}, + {"/ws_deflate_opts", ws_deflate_opts_h, []} ]} ]). @@ -231,6 +232,123 @@ do_ws_version(Socket) -> {error, closed} = gen_tcp:recv(Socket, 0, 6000), ok. +ws_deflate_opts_client_context_takeover(Config) -> + doc("Handler is configured with client context takeover enabled."), + {ok, _, Headers1} = do_handshake("/ws_deflate_opts?client_context_takeover", + "Sec-WebSocket-Extensions: permessage-deflate\r\n", Config), + {_, "permessage-deflate"} + = lists:keyfind("sec-websocket-extensions", 1, Headers1), + {ok, _, Headers2} = do_handshake("/ws_deflate_opts?client_context_takeover", + "Sec-WebSocket-Extensions: permessage-deflate; client_no_context_takeover\r\n", Config), + {_, "permessage-deflate; client_no_context_takeover"} + = lists:keyfind("sec-websocket-extensions", 1, Headers2), + ok. + +ws_deflate_opts_client_no_context_takeover(Config) -> + doc("Handler is configured with client context takeover disabled."), + {ok, _, Headers1} = do_handshake("/ws_deflate_opts?client_no_context_takeover", + "Sec-WebSocket-Extensions: permessage-deflate\r\n", Config), + {_, "permessage-deflate; client_no_context_takeover"} + = lists:keyfind("sec-websocket-extensions", 1, Headers1), + {ok, _, Headers2} = do_handshake("/ws_deflate_opts?client_no_context_takeover", + "Sec-WebSocket-Extensions: permessage-deflate; client_no_context_takeover\r\n", Config), + {_, "permessage-deflate; client_no_context_takeover"} + = lists:keyfind("sec-websocket-extensions", 1, Headers2), + ok. + +%% We must send client_max_window_bits to indicate we support it. +ws_deflate_opts_client_max_window_bits(Config) -> + doc("Handler is configured with client max window bits."), + {ok, _, Headers} = do_handshake("/ws_deflate_opts?client_max_window_bits", + "Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits\r\n", Config), + {_, "permessage-deflate; client_max_window_bits=9"} + = lists:keyfind("sec-websocket-extensions", 1, Headers), + ok. + +ws_deflate_opts_client_max_window_bits_override(Config) -> + doc("Handler is configured with client max window bits."), + {ok, _, Headers1} = do_handshake("/ws_deflate_opts?client_max_window_bits", + "Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits=8\r\n", Config), + {_, "permessage-deflate; client_max_window_bits=8"} + = lists:keyfind("sec-websocket-extensions", 1, Headers1), + {ok, _, Headers2} = do_handshake("/ws_deflate_opts?client_max_window_bits", + "Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits=12\r\n", Config), + {_, "permessage-deflate; client_max_window_bits=9"} + = lists:keyfind("sec-websocket-extensions", 1, Headers2), + ok. + +ws_deflate_opts_server_context_takeover(Config) -> + doc("Handler is configured with server context takeover enabled."), + {ok, _, Headers1} = do_handshake("/ws_deflate_opts?server_context_takeover", + "Sec-WebSocket-Extensions: permessage-deflate\r\n", Config), + {_, "permessage-deflate"} + = lists:keyfind("sec-websocket-extensions", 1, Headers1), + {ok, _, Headers2} = do_handshake("/ws_deflate_opts?server_context_takeover", + "Sec-WebSocket-Extensions: permessage-deflate; server_no_context_takeover\r\n", Config), + {_, "permessage-deflate; server_no_context_takeover"} + = lists:keyfind("sec-websocket-extensions", 1, Headers2), + ok. + +ws_deflate_opts_server_no_context_takeover(Config) -> + doc("Handler is configured with server context takeover disabled."), + {ok, _, Headers1} = do_handshake("/ws_deflate_opts?server_no_context_takeover", + "Sec-WebSocket-Extensions: permessage-deflate\r\n", Config), + {_, "permessage-deflate; server_no_context_takeover"} + = lists:keyfind("sec-websocket-extensions", 1, Headers1), + {ok, _, Headers2} = do_handshake("/ws_deflate_opts?server_no_context_takeover", + "Sec-WebSocket-Extensions: permessage-deflate; server_no_context_takeover\r\n", Config), + {_, "permessage-deflate; server_no_context_takeover"} + = lists:keyfind("sec-websocket-extensions", 1, Headers2), + ok. + +ws_deflate_opts_server_max_window_bits(Config) -> + doc("Handler is configured with server max window bits."), + {ok, _, Headers} = do_handshake("/ws_deflate_opts?server_max_window_bits", + "Sec-WebSocket-Extensions: permessage-deflate\r\n", Config), + {_, "permessage-deflate; server_max_window_bits=9"} + = lists:keyfind("sec-websocket-extensions", 1, Headers), + ok. + +ws_deflate_opts_server_max_window_bits_override(Config) -> + doc("Handler is configured with server max window bits."), + {ok, _, Headers1} = do_handshake("/ws_deflate_opts?server_max_window_bits", + "Sec-WebSocket-Extensions: permessage-deflate; server_max_window_bits=8\r\n", Config), + {_, "permessage-deflate; server_max_window_bits=8"} + = lists:keyfind("sec-websocket-extensions", 1, Headers1), + {ok, _, Headers2} = do_handshake("/ws_deflate_opts?server_max_window_bits", + "Sec-WebSocket-Extensions: permessage-deflate; server_max_window_bits=12\r\n", Config), + {_, "permessage-deflate; server_max_window_bits=9"} + = lists:keyfind("sec-websocket-extensions", 1, Headers2), + ok. + +ws_deflate_opts_zlevel(Config) -> + doc("Handler is configured with zlib level."), + do_ws_deflate_opts_z("/ws_deflate_opts?level", Config). + +ws_deflate_opts_zmemlevel(Config) -> + doc("Handler is configured with zlib mem_level."), + do_ws_deflate_opts_z("/ws_deflate_opts?mem_level", Config). + +ws_deflate_opts_zstrategy(Config) -> + doc("Handler is configured with zlib strategy."), + do_ws_deflate_opts_z("/ws_deflate_opts?strategy", Config). + +do_ws_deflate_opts_z(Path, Config) -> + {ok, Socket, Headers} = do_handshake(Path, + "Sec-WebSocket-Extensions: permessage-deflate\r\n", Config), + {_, "permessage-deflate"} = lists:keyfind("sec-websocket-extensions", 1, Headers), + %% Send and receive a compressed "Hello" frame. + Mask = 16#11223344, + CompressedHello = << 242, 72, 205, 201, 201, 7, 0 >>, + MaskedHello = do_mask(CompressedHello, Mask, <<>>), + ok = gen_tcp:send(Socket, << 1:1, 1:1, 0:2, 1:4, 1:1, 7:7, Mask:32, MaskedHello/binary >>), + {ok, << 1:1, 1:1, 0:2, 1:4, 0:1, 7:7, CompressedHello/binary >>} = gen_tcp:recv(Socket, 0, 6000), + %% Client-initiated close. + ok = gen_tcp:send(Socket, << 1:1, 0:3, 8:4, 1:1, 0:7, 0:32 >>), + {ok, << 1:1, 0:3, 8:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000), + {error, closed} = gen_tcp:recv(Socket, 0, 6000), + ok. + ws_init_return_ok(Config) -> doc("Handler does nothing."), {ok, Socket, _} = do_handshake("/ws_init?ok", Config), -- cgit v1.2.3