Initial import WebSocket Compression Protocol extension Permessage-deflate.
Added test cases for Permessage-delate valid parameters Added a simple websocket example with compression.
This commit is contained in:
@@ -18,6 +18,7 @@
|
||||
<library name="http" location="..\..\..\network\protocol\http\http-safe.ecf"/>
|
||||
<library name="httpd" location="..\..\httpd\httpd-safe.ecf"/>
|
||||
<library name="time" location="$ISE_LIBRARY\library\time\time-safe.ecf"/>
|
||||
<library name="zlib" location="$ISE_LIBRARY\unstable\library\compression\zlib\zlib-safe.ecf" readonly="false"/>
|
||||
<library name="wsf" location="..\wsf-safe.ecf"/>
|
||||
<library name="wsf_standalone" location="standalone-safe.ecf"/>
|
||||
<library name="web_socket_protocol" location="..\..\..\network\websocket\protocol\web_socket_protocol-safe.ecf"/>
|
||||
|
||||
@@ -1,111 +0,0 @@
|
||||
note
|
||||
description: "{COMPRESSION_EXTENSIONS_PARSER} Parse the SEC_WEBSOCKET_EXTENSION header as par of websocket opening handshake."
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
EIS: "name=Compression Extension for WebSocket"
|
||||
|
||||
class
|
||||
COMPRESSION_EXTENSIONS_PARSER
|
||||
|
||||
create
|
||||
make
|
||||
|
||||
feature {NONE} -- Initialize
|
||||
|
||||
make (a_header: STRING_32)
|
||||
do
|
||||
header := a_header
|
||||
create {ARRAYED_LIST [WEBSOCKET_PCME]} last_offers.make (0)
|
||||
ensure
|
||||
header_set: header = a_header
|
||||
end
|
||||
|
||||
feature -- Access
|
||||
|
||||
header: STRING_32
|
||||
-- Content raw header `Sec-Websocket-Extensions'.
|
||||
|
||||
last_offers: LIST [WEBSOCKET_PCME]
|
||||
-- List of potential offered PMCE
|
||||
--| From Sec-Websocket-Extensions header.
|
||||
--| The order of elements is important as it specifies the client's preferences.
|
||||
|
||||
feature -- Parse
|
||||
|
||||
parse
|
||||
-- Parse `SEC-WEBSOCKET-EXTENSIONS' header.
|
||||
-- The result is available in `last_offer'.
|
||||
local
|
||||
l_offers: ARRAYED_LIST [WEBSOCKET_PCME]
|
||||
l_pcme: WEBSOCKET_PCME
|
||||
do
|
||||
create l_offers.make (1)
|
||||
if attached header.split (',') as l_list then
|
||||
-- Multiple offers separated by ',', if l_list.count > 1
|
||||
across l_list as ic loop
|
||||
-- Shared code extract to an external feature.
|
||||
l_offers.force (parse_parameters (ic.item))
|
||||
end
|
||||
else
|
||||
-- we should raise an Issue.
|
||||
end
|
||||
last_offers := l_offers
|
||||
end
|
||||
|
||||
feature {NONE}-- Parse Compression Extension.
|
||||
|
||||
parse_parameters (a_string: STRING_32): WEBSOCKET_PCME
|
||||
local
|
||||
l_first: BOOLEAN
|
||||
l_str: STRING_32
|
||||
l_key: STRING_32
|
||||
l_value: STRING_32
|
||||
do
|
||||
if attached a_string.split (';') as l_parameters then
|
||||
-- parameters for the current offer.
|
||||
create Result
|
||||
across
|
||||
l_parameters as ip
|
||||
from
|
||||
l_first := True
|
||||
loop
|
||||
if l_first then
|
||||
l_str := ip.item
|
||||
l_str.adjust
|
||||
Result.set_name (l_str)
|
||||
l_first := False
|
||||
else
|
||||
l_str := ip.item
|
||||
l_str.adjust
|
||||
if l_str.has ('=') then
|
||||
-- The parameter has a value
|
||||
-- server_max_window_bits = 10
|
||||
l_key := l_str.substring (1, l_str.index_of ('=', 1) - 1) -- Exclude =
|
||||
l_value := l_str.substring (l_str.index_of ('=', 1) + 1, l_str.count) -- Exclude =
|
||||
Result.force (l_value, l_key)
|
||||
else
|
||||
Result.force (Void, l_str)
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
-- Compression Extension simple
|
||||
--| like
|
||||
--| permessage-deflate
|
||||
l_str := a_string
|
||||
l_str.adjust
|
||||
create Result
|
||||
Result.set_name (l_str)
|
||||
end
|
||||
end
|
||||
note
|
||||
copyright: "2011-2016, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
|
||||
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
|
||||
source: "[
|
||||
Eiffel Software
|
||||
5949 Hollister Ave., Goleta, CA 93117 USA
|
||||
Telephone 805-685-1006, Fax 805-685-6869
|
||||
Website http://www.eiffel.com
|
||||
Customer support http://support.eiffel.com
|
||||
]"
|
||||
end
|
||||
@@ -0,0 +1,163 @@
|
||||
note
|
||||
description: "{WEB_SOCKET_COMPRESSION_EXTENSIONS_PARSER} Parse the SEC_WEBSOCKET_EXTENSION header as par of websocket opening handshake."
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
EIS: "name=Compression Extension for WebSocket"
|
||||
|
||||
class
|
||||
WEB_SOCKET_COMPRESSION_EXTENSIONS_PARSER
|
||||
|
||||
create
|
||||
make
|
||||
|
||||
feature {NONE} -- Initialize
|
||||
|
||||
make (a_header: STRING_32)
|
||||
do
|
||||
header := a_header
|
||||
create {ARRAYED_LIST [WEB_SOCKET_PMCE]} last_offers.make (0)
|
||||
ensure
|
||||
header_set: header = a_header
|
||||
no_error: not has_error
|
||||
end
|
||||
|
||||
feature -- Access
|
||||
|
||||
header: STRING_32
|
||||
-- Content raw header `Sec-Websocket-Extensions'.
|
||||
|
||||
last_offers: LIST [WEB_SOCKET_PMCE]
|
||||
-- List of potential offered PMCE
|
||||
--| From Sec-Websocket-Extensions header.
|
||||
--| The order of elements is important as it specifies the client's preferences.
|
||||
|
||||
error_message: detachable STRING
|
||||
-- last error message if any?
|
||||
|
||||
has_error: BOOLEAN
|
||||
-- Has the extension header errors?
|
||||
do
|
||||
Result := attached error_message
|
||||
end
|
||||
|
||||
|
||||
feature -- Parse
|
||||
|
||||
parse
|
||||
-- Parse `SEC-WEBSOCKET-EXTENSIONS' header.
|
||||
-- The result is available in `last_offer'.
|
||||
local
|
||||
l_offers: ARRAYED_LIST [WEB_SOCKET_PMCE]
|
||||
l_pcme: WEB_SOCKET_PMCE
|
||||
do
|
||||
create l_offers.make (1)
|
||||
if attached header.split (',') as l_list then
|
||||
-- Multiple offers separated by ',', if l_list.count > 1
|
||||
across l_list as ic until has_error loop
|
||||
-- Shared code extract to an external feature.
|
||||
l_offers.force (parse_parameters (ic.item))
|
||||
end
|
||||
else
|
||||
-- we should raise an Issue.
|
||||
end
|
||||
if not has_error then
|
||||
last_offers := l_offers
|
||||
end
|
||||
check
|
||||
has_errors: has_error implies last_offers.is_empty
|
||||
end
|
||||
end
|
||||
|
||||
feature {NONE}-- Parse Compression Extension.
|
||||
|
||||
parse_parameters (a_string: STRING_32): WEB_SOCKET_PMCE
|
||||
local
|
||||
l_validator: WEB_SOCKET_PMCE_DEFLATE_VALIDATOR
|
||||
l_first: BOOLEAN
|
||||
l_str: STRING_32
|
||||
l_key: STRING_32
|
||||
l_value: STRING_32
|
||||
do
|
||||
create l_validator
|
||||
if attached a_string.split (';') as l_parameters then
|
||||
-- parameters for the current offer.
|
||||
create Result
|
||||
across
|
||||
l_parameters as ip
|
||||
from
|
||||
l_first := True
|
||||
until
|
||||
has_error
|
||||
loop
|
||||
if l_first then
|
||||
l_str := ip.item
|
||||
l_str.adjust
|
||||
if not l_validator.name.same_string (l_str) then
|
||||
create error_message.make_from_string ("Invalid PCME name: expected: `permessage-deflate` got `" + l_str + "`")
|
||||
end
|
||||
Result.set_name (l_str)
|
||||
l_first := False
|
||||
else
|
||||
l_str := ip.item
|
||||
l_str.adjust
|
||||
-- valid parameter
|
||||
if l_str.has ('=') then
|
||||
-- The parameter has a value
|
||||
-- server_max_window_bits = 10
|
||||
l_key := l_str.substring (1, l_str.index_of ('=', 1) - 1) -- Exclude =
|
||||
if l_validator.parameters.has (l_key) and then l_validator.parameters.at (l_key) then
|
||||
l_value := l_str.substring (l_str.index_of ('=', 1) + 1, l_str.count) -- Exclude =
|
||||
if Result.has (l_key) then
|
||||
-- Unexpected value for parameter name
|
||||
create error_message.make_from_string ("Invalid PCME value multiple occurences of parameter `" + l_str + "`")
|
||||
else
|
||||
if l_validator.sliding_windows_size.has (l_value) then
|
||||
Result.force (l_value, l_key)
|
||||
else
|
||||
-- Unexpected value sliding window, value must be 8..15
|
||||
create error_message.make_from_string ("Invalid PCME value for parameters with windows bits: expected a value between:`8 .. 15 ` got `" + l_value + "`")
|
||||
end
|
||||
end
|
||||
else
|
||||
-- Unexpected value for parameter name
|
||||
create error_message.make_from_string ("Invalid PCME value for parameters: expected: `server_max_window_bits, client_max_window_bits ` got `" + l_str + "`")
|
||||
end
|
||||
else
|
||||
if l_validator.parameters.has (l_str) then
|
||||
if Result.has (l_str) then
|
||||
-- Unexpected value for parameter name
|
||||
create error_message.make_from_string ("Invalid PCME value multiple occurences of parameter `" + l_str + "`")
|
||||
else
|
||||
Result.force (Void, l_str)
|
||||
end
|
||||
else
|
||||
-- Unexpected parameter name
|
||||
create error_message.make_from_string ("Invalid PCME parameters: expected: `server_no_context_takeover, client_no_context_takeover, server_max_window_bits, client_max_window_bits ` got `" + l_str + "`")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
-- Compression Extension simple
|
||||
--| like
|
||||
--| permessage-deflate
|
||||
l_str := a_string
|
||||
l_str.adjust
|
||||
if not l_validator.name.same_string (l_str) then
|
||||
create error_message.make_from_string ("Invalid PCME name: expected: `permessage-deflate` got `" + l_str + "`")
|
||||
end
|
||||
create Result
|
||||
Result.set_name (l_str)
|
||||
end
|
||||
end
|
||||
note
|
||||
copyright: "2011-2016, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
|
||||
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
|
||||
source: "[
|
||||
Eiffel Software
|
||||
5949 Hollister Ave., Goleta, CA 93117 USA
|
||||
Telephone 805-685-1006, Fax 805-685-6869
|
||||
Website http://www.eiffel.com
|
||||
Customer support http://support.eiffel.com
|
||||
]"
|
||||
end
|
||||
@@ -4,7 +4,7 @@ note
|
||||
revision: "$Revision$"
|
||||
|
||||
class
|
||||
WEBSOCKET_PCME
|
||||
WEB_SOCKET_PMCE
|
||||
|
||||
feature -- Access
|
||||
|
||||
@@ -14,6 +14,16 @@ feature -- Access
|
||||
parameters: detachable STRING_TABLE [detachable STRING_32]
|
||||
-- Compression extensions parameter.
|
||||
|
||||
feature -- Status Report
|
||||
|
||||
has (a_key: STRING_32): BOOLEAN
|
||||
-- Is there an item in the table with key `a_key'?
|
||||
do
|
||||
if attached parameters as l_parameters then
|
||||
Result := l_parameters.has (a_key)
|
||||
end
|
||||
end
|
||||
|
||||
feature -- Change Element
|
||||
|
||||
set_name (a_name: STRING_32)
|
||||
@@ -0,0 +1,34 @@
|
||||
note
|
||||
description: "Summary description for {WEB_SOCKET_PMCE_CONSTANTS_2}."
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
class
|
||||
WEB_SOCKET_PMCE_CONSTANTS
|
||||
|
||||
|
||||
feature -- Extension Parameters
|
||||
|
||||
Permessage_deflate: STRING = "permessage-deflate"
|
||||
-- PCME permessage deflate name.
|
||||
|
||||
Default_window_size: INTEGER = 15
|
||||
-- Default value for windows size.
|
||||
|
||||
Default_value_memory: INTEGER = 8
|
||||
-- Default value for memory level.
|
||||
|
||||
Default_chunk_size: INTEGER = 32768
|
||||
-- Default chunk size 2^15
|
||||
|
||||
note
|
||||
copyright: "2011-2016, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
|
||||
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
|
||||
source: "[
|
||||
Eiffel Software
|
||||
5949 Hollister Ave., Goleta, CA 93117 USA
|
||||
Telephone 805-685-1006, Fax 805-685-6869
|
||||
Website http://www.eiffel.com
|
||||
Customer support http://support.eiffel.com
|
||||
]"
|
||||
end
|
||||
@@ -0,0 +1,89 @@
|
||||
note
|
||||
description: "Object representing compression parameter client offer accepts by the server."
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
class
|
||||
WEB_SOCKET_PMCE_DEFLATE_ACCEPT
|
||||
|
||||
create
|
||||
make
|
||||
|
||||
feature {NONE} -- Initialization
|
||||
|
||||
make (a_server_ctx: BOOLEAN; a_accept_server_window: BOOLEAN; a_server_window: INTEGER; a_client_ctx: BOOLEAN; a_accept_client_window: BOOLEAN; a_client_window: INTEGER; )
|
||||
-- Create accepted offer.
|
||||
do
|
||||
accept_server_no_context_takeover := a_server_ctx
|
||||
accept_server_max_window_bits := a_accept_server_window
|
||||
server_max_window_bits := a_server_window
|
||||
|
||||
accept_client_no_context_takeover := a_client_ctx
|
||||
accept_client_max_window_bits := a_accept_client_window
|
||||
client_max_window_bits := a_client_window
|
||||
end
|
||||
|
||||
accept_server_no_context_takeover: BOOLEAN
|
||||
-- Does the response include this parameter?
|
||||
|
||||
accept_server_max_window_bits: BOOLEAN
|
||||
-- Does the response include this parameter?
|
||||
|
||||
server_max_window_bits: INTEGER
|
||||
-- Server sliding window.
|
||||
|
||||
accept_client_no_context_takeover: BOOLEAN
|
||||
-- Does the response include this parameter?
|
||||
|
||||
accept_client_max_window_bits: BOOLEAN
|
||||
-- Does the response include this parameter?
|
||||
|
||||
client_max_window_bits: INTEGER
|
||||
-- Client sliding window.
|
||||
|
||||
|
||||
feature -- Access
|
||||
|
||||
extension_response: STRING
|
||||
-- Generate response
|
||||
do
|
||||
create Result.make_from_string ({WEB_SOCKET_PMCE_CONSTANTS}.Permessage_deflate)
|
||||
if accept_server_no_context_takeover then
|
||||
Result.append_character (';')
|
||||
Result.append ("server_no_context_takeover")
|
||||
end
|
||||
|
||||
if accept_client_no_context_takeover then
|
||||
Result.append_character (';')
|
||||
Result.append ("client_no_context_takeover")
|
||||
end
|
||||
|
||||
if accept_server_max_window_bits then
|
||||
Result.append_character (';')
|
||||
Result.append ("server_max_window_bits")
|
||||
if server_max_window_bits > 0 then
|
||||
Result.append_character ('=')
|
||||
Result.append_integer (server_max_window_bits)
|
||||
end
|
||||
end
|
||||
|
||||
if accept_client_max_window_bits then
|
||||
Result.append_character (';')
|
||||
Result.append ("client_max_window_bits")
|
||||
if client_max_window_bits > 0 then
|
||||
Result.append_character ('=')
|
||||
Result.append_integer (client_max_window_bits)
|
||||
end
|
||||
end
|
||||
end
|
||||
note
|
||||
copyright: "2011-2016, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
|
||||
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
|
||||
source: "[
|
||||
Eiffel Software
|
||||
5949 Hollister Ave., Goleta, CA 93117 USA
|
||||
Telephone 805-685-1006, Fax 805-685-6869
|
||||
Website http://www.eiffel.com
|
||||
Customer support http://support.eiffel.com
|
||||
]"
|
||||
end
|
||||
@@ -0,0 +1,58 @@
|
||||
note
|
||||
description: "Object representing the accept offer from client to server"
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
class
|
||||
WEB_SOCKET_PMCE_DEFLATE_ACCEPT_FACTORY
|
||||
|
||||
|
||||
feature -- Factory
|
||||
|
||||
accept_offer (a_server_conf: WEB_SOCKET_PMCE_DEFLATE_SERVER_SUPPORT; a_client: WEB_SOCKET_PMCE_DEFLATE_OFFER): detachable WEB_SOCKET_PMCE_DEFLATE_ACCEPT
|
||||
-- Does the server accept the client offer?
|
||||
-- Return the accepted offer or void in other case.
|
||||
local
|
||||
l_request_window: BOOLEAN
|
||||
l_request_context: BOOLEAN
|
||||
l_invalid: BOOLEAN
|
||||
l_server_max_window_bits: INTEGER
|
||||
l_client_max_window_bits: INTEGER
|
||||
l_client_context: BOOLEAN
|
||||
do
|
||||
-- server_max_windows_bits
|
||||
-- client request max_windows and server accept
|
||||
if a_client.request_max_window_bits > 0 and then a_server_conf.accept_max_window_bits then
|
||||
l_request_window := True
|
||||
l_server_max_window_bits := a_client.request_max_window_bits
|
||||
elseif a_client.request_max_window_bits = 0 then
|
||||
l_request_window := False
|
||||
else
|
||||
-- Server does not support client offer
|
||||
l_invalid := True
|
||||
end
|
||||
|
||||
-- server_no_content_takeover
|
||||
if a_client.request_no_context_take_over and then a_server_conf.accept_no_context_take_over then
|
||||
l_request_context := True
|
||||
elseif not a_client.request_no_context_take_over and then not a_server_conf.accept_no_context_take_over then
|
||||
l_request_context := False
|
||||
else
|
||||
l_invalid := True
|
||||
end
|
||||
if not l_invalid then
|
||||
create Result.make (l_request_context, l_request_window, l_server_max_window_bits, a_client.accept_no_context_take_over, a_client.accept_max_window_bits, a_server_conf.request_max_window_bits )
|
||||
end
|
||||
end
|
||||
|
||||
note
|
||||
copyright: "2011-2016, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
|
||||
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
|
||||
source: "[
|
||||
Eiffel Software
|
||||
5949 Hollister Ave., Goleta, CA 93117 USA
|
||||
Telephone 805-685-1006, Fax 805-685-6869
|
||||
Website http://www.eiffel.com
|
||||
Customer support http://support.eiffel.com
|
||||
]"
|
||||
end
|
||||
@@ -0,0 +1,140 @@
|
||||
note
|
||||
description: "Object representing a permessage-deflate` WebSocket extension offered by a client to a server."
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
class
|
||||
WEB_SOCKET_PMCE_DEFLATE_OFFER
|
||||
|
||||
|
||||
create
|
||||
make
|
||||
|
||||
feature {NONE} -- Initialization
|
||||
|
||||
make
|
||||
-- create an object with default settings.
|
||||
do
|
||||
mark_accept_no_context_take_over
|
||||
mark_accept_max_window_bits
|
||||
mark_request_no_context_take_over
|
||||
set_request_max_window_bits (0)
|
||||
end
|
||||
|
||||
feature -- Access
|
||||
|
||||
accept_no_context_take_over: BOOLEAN
|
||||
-- Does client accepts `no context takeover` feature?
|
||||
-- Default value: True
|
||||
|
||||
accept_max_window_bits: BOOLEAN
|
||||
-- Does client accepts setting `max window size`?
|
||||
-- Default value: True.
|
||||
|
||||
request_no_context_take_over: BOOLEAN
|
||||
-- Does client request `no context takeover` feature?
|
||||
|
||||
request_max_window_bits: INTEGER
|
||||
-- Does client requests given `max window size?`
|
||||
-- Valid value must be 8-15
|
||||
-- Default 0.
|
||||
|
||||
feature -- Parse Params
|
||||
|
||||
configure (a_pcme: WEB_SOCKET_PMCE)
|
||||
-- Configure websocket extension parameters an returns the provided offer by a client.
|
||||
do
|
||||
-- extension paramet defaults
|
||||
accept_max_window_bits := False
|
||||
accept_no_context_take_over := False
|
||||
request_max_window_bits := 0
|
||||
request_no_context_take_over := False
|
||||
|
||||
if attached a_pcme.parameters as l_parameters then
|
||||
across l_parameters as ic loop
|
||||
if ic.key.same_string ("client_max_window_bits") then
|
||||
mark_accept_max_window_bits
|
||||
elseif ic.key.same_string ("client_no_context_takeover") then
|
||||
mark_accept_no_context_take_over
|
||||
elseif ic.key.same_string ("server_max_window_bits") then
|
||||
if attached ic.item as l_value then
|
||||
set_request_max_window_bits (l_value.to_integer)
|
||||
end
|
||||
elseif ic.key.same_string ("server_no_context_takeover") then
|
||||
mark_request_no_context_take_over
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
feature -- Element Change
|
||||
|
||||
mark_accept_no_context_take_over
|
||||
-- Set `accept_no_context_take_over` to True.
|
||||
do
|
||||
accept_no_context_take_over := True
|
||||
ensure
|
||||
accept_no_context_take_over_true: accept_no_context_take_over
|
||||
end
|
||||
|
||||
unmark_accept_no_context_take_over
|
||||
-- Set `accept_no_context_take_over` to False
|
||||
do
|
||||
accept_no_context_take_over := False
|
||||
ensure
|
||||
accept_no_context_take_over_false: not accept_no_context_take_over
|
||||
end
|
||||
|
||||
mark_accept_max_window_bits
|
||||
-- Set `accept_max_window_bits` to True
|
||||
do
|
||||
accept_max_window_bits := True
|
||||
ensure
|
||||
accept_max_window_bits_true: accept_max_window_bits
|
||||
end
|
||||
|
||||
unmark_accept_max_window_bits
|
||||
-- Set `accept_max_window_bits` to False
|
||||
do
|
||||
accept_max_window_bits := False
|
||||
ensure
|
||||
accept_max_window_bits_false: not accept_max_window_bits
|
||||
end
|
||||
|
||||
mark_request_no_context_take_over
|
||||
-- Set `request_no_context_take_over` to True.
|
||||
do
|
||||
request_no_context_take_over := True
|
||||
ensure
|
||||
request_no_context_take_over_true: request_no_context_take_over
|
||||
end
|
||||
|
||||
unmark_request_no_context_take_over
|
||||
-- Set `request_no_context_take_over` to False.
|
||||
do
|
||||
request_no_context_take_over := False
|
||||
ensure
|
||||
request_no_context_take_over_false: request_no_context_take_over
|
||||
end
|
||||
|
||||
set_request_max_window_bits (a_bits: INTEGER)
|
||||
-- Set `request_max_window_bits` to `a_bits`.
|
||||
require
|
||||
valid_range: (a_bits >= 8 and then a_bits <= 15) or else (a_bits = 0)
|
||||
do
|
||||
request_max_window_bits := a_bits
|
||||
ensure
|
||||
request_max_window_bits_set: request_max_window_bits = a_bits
|
||||
end
|
||||
|
||||
;note
|
||||
copyright: "2011-2016, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
|
||||
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
|
||||
source: "[
|
||||
Eiffel Software
|
||||
5949 Hollister Ave., Goleta, CA 93117 USA
|
||||
Telephone 805-685-1006, Fax 805-685-6869
|
||||
Website http://www.eiffel.com
|
||||
Customer support http://support.eiffel.com
|
||||
]"
|
||||
end
|
||||
@@ -0,0 +1,113 @@
|
||||
note
|
||||
description: "Object representing serever support"
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
class
|
||||
WEB_SOCKET_PMCE_DEFLATE_SERVER_SUPPORT
|
||||
|
||||
create
|
||||
make
|
||||
|
||||
feature {NONE} -- Initialization
|
||||
|
||||
feature {NONE} -- Initialization
|
||||
|
||||
make
|
||||
-- create an object with default settings.
|
||||
do
|
||||
mark_accept_no_context_take_over
|
||||
mark_accept_max_window_bits
|
||||
mark_request_no_context_take_over
|
||||
set_request_max_window_bits (0)
|
||||
end
|
||||
|
||||
feature -- Access
|
||||
|
||||
accept_no_context_take_over: BOOLEAN
|
||||
-- Does server accepts `no context takeover` feature?
|
||||
-- Default value: True
|
||||
|
||||
accept_max_window_bits: BOOLEAN
|
||||
-- Does server accepts setting `max window size`?
|
||||
-- Default value: True.
|
||||
|
||||
request_no_context_take_over: BOOLEAN
|
||||
-- Does server request `no context takeover` feature?
|
||||
|
||||
request_max_window_bits: INTEGER
|
||||
-- Does server requests given `max window size?`
|
||||
-- Valid value must be 8-15
|
||||
-- Default 0.
|
||||
|
||||
feature -- Element Change
|
||||
|
||||
mark_accept_no_context_take_over
|
||||
-- Set `accept_no_context_take_over` to True.
|
||||
do
|
||||
accept_no_context_take_over := True
|
||||
ensure
|
||||
accept_no_context_take_over_true: accept_no_context_take_over
|
||||
end
|
||||
|
||||
unmark_accept_no_context_take_over
|
||||
-- Set `accept_no_context_take_over` to False
|
||||
do
|
||||
accept_no_context_take_over := False
|
||||
ensure
|
||||
accept_no_context_take_over_false: not accept_no_context_take_over
|
||||
end
|
||||
|
||||
mark_accept_max_window_bits
|
||||
-- Set `accept_max_window_bits` to True
|
||||
do
|
||||
accept_max_window_bits := True
|
||||
ensure
|
||||
accept_max_window_bits_true: accept_max_window_bits
|
||||
end
|
||||
|
||||
unmark_accept_max_window_bits
|
||||
-- Set `accept_max_window_bits` to False
|
||||
do
|
||||
accept_max_window_bits := False
|
||||
ensure
|
||||
accept_max_window_bits_false: not accept_max_window_bits
|
||||
end
|
||||
|
||||
mark_request_no_context_take_over
|
||||
-- Set `request_no_context_take_over` to True.
|
||||
do
|
||||
request_no_context_take_over := True
|
||||
ensure
|
||||
request_no_context_take_over_true: request_no_context_take_over
|
||||
end
|
||||
|
||||
unmark_request_no_context_take_over
|
||||
-- Set `request_no_context_take_over` to False.
|
||||
do
|
||||
request_no_context_take_over := False
|
||||
ensure
|
||||
request_no_context_take_over_false: request_no_context_take_over
|
||||
end
|
||||
|
||||
set_request_max_window_bits (a_bits: INTEGER)
|
||||
-- Set `request_max_window_bits` to `a_bits`.
|
||||
require
|
||||
valid_range: (a_bits >= 8 and then a_bits <= 15) or else (a_bits = 0)
|
||||
do
|
||||
request_max_window_bits := a_bits
|
||||
ensure
|
||||
request_max_window_bits_set: request_max_window_bits = a_bits
|
||||
end
|
||||
|
||||
note
|
||||
copyright: "2011-2016, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
|
||||
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
|
||||
source: "[
|
||||
Eiffel Software
|
||||
5949 Hollister Ave., Goleta, CA 93117 USA
|
||||
Telephone 805-685-1006, Fax 805-685-6869
|
||||
Website http://www.eiffel.com
|
||||
Customer support http://support.eiffel.com
|
||||
]"
|
||||
end
|
||||
@@ -0,0 +1,37 @@
|
||||
note
|
||||
description: "Summary description for {WEB_SOCKET_PCME_DEFLATE_SERVER_SUPPORT_FACTORY}."
|
||||
author: ""
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
class
|
||||
WEB_SOCKET_PMCE_DEFLATE_SERVER_SUPPORT_FACTORY
|
||||
|
||||
|
||||
feature -- Factory
|
||||
|
||||
basic_support: WEB_SOCKET_PMCE_DEFLATE_SERVER_SUPPORT
|
||||
-- client_max_window_bits: True
|
||||
-- server_max_window_bits: False
|
||||
-- client_content_take_over: False
|
||||
-- server_content_take_over: False
|
||||
do
|
||||
create Result.make
|
||||
Result.mark_accept_max_window_bits
|
||||
Result.unmark_accept_no_context_take_over
|
||||
Result.unmark_request_no_context_take_over
|
||||
Result.set_request_max_window_bits ({WEB_SOCKET_PMCE_CONSTANTS}.default_window_size)
|
||||
end
|
||||
|
||||
|
||||
note
|
||||
copyright: "2011-2016, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
|
||||
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
|
||||
source: "[
|
||||
Eiffel Software
|
||||
5949 Hollister Ave., Goleta, CA 93117 USA
|
||||
Telephone 805-685-1006, Fax 805-685-6869
|
||||
Website http://www.eiffel.com
|
||||
Customer support http://support.eiffel.com
|
||||
]"
|
||||
end
|
||||
@@ -1,7 +1,8 @@
|
||||
note
|
||||
description: "[Object that validate a PMCE permessage defalate extension,
|
||||
description: "[
|
||||
Object that validate a PMCE permessage defalate extension,
|
||||
using the DEFLATE algorithm
|
||||
]"
|
||||
]"
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
@@ -9,10 +10,6 @@ class
|
||||
WEB_SOCKET_PMCE_DEFLATE_VALIDATOR
|
||||
|
||||
|
||||
feature -- Validate
|
||||
|
||||
|
||||
|
||||
feature -- Access
|
||||
|
||||
name: STRING = "permessage-deflate"
|
||||
@@ -27,8 +24,8 @@ feature -- Access
|
||||
create Result.make_caseless (4)
|
||||
Result.force (False, "server_no_context_takeover")
|
||||
Result.force (False, "client_no_context_takeover")
|
||||
Result.force (True, "server_max_windows_bits")
|
||||
Result.force (True, "client_max_windows_bits")
|
||||
Result.force (True, "server_max_window_bits")
|
||||
Result.force (True, "client_max_window_bits")
|
||||
end
|
||||
|
||||
|
||||
|
||||
@@ -44,11 +44,6 @@ feature {NONE} -- Initialization
|
||||
end
|
||||
end
|
||||
|
||||
set_pcme_deflate
|
||||
do
|
||||
|
||||
end
|
||||
|
||||
feature -- Access
|
||||
|
||||
socket: HTTPD_STREAM_SOCKET
|
||||
@@ -119,14 +114,20 @@ feature -- Element change
|
||||
verbose_level := lev
|
||||
end
|
||||
|
||||
mark_pcme_supported
|
||||
-- Set the websocket to handle pcme.
|
||||
configure_pcme (a_pcme_server: WEB_SOCKET_PMCE_DEFLATE_SERVER_SUPPORT)
|
||||
-- Set `pcme_server_support` with `a_pcme_server`.
|
||||
do
|
||||
is_pcme_supported := True
|
||||
pmce_server_support := a_pcme_server
|
||||
ensure
|
||||
pmce_supported_true: is_pcme_supported
|
||||
pcme_server_support_set: pmce_server_support = a_pcme_server
|
||||
end
|
||||
|
||||
|
||||
feature -- PMCE Compression
|
||||
|
||||
pmce_server_support : detachable WEB_SOCKET_PMCE_DEFLATE_SERVER_SUPPORT
|
||||
-- Compression extension supported by the server.
|
||||
|
||||
feature -- Basic operation
|
||||
|
||||
put_error (a_message: READABLE_STRING_8)
|
||||
@@ -202,7 +203,7 @@ feature -- Basic Operation
|
||||
attached req.http_host -- Host header must be present
|
||||
then
|
||||
-- here we can check for Sec-WebSocket-Extensions, it could be a collection of extensions.
|
||||
if is_pcme_supported and then attached req.meta_string_variable ("HTTP_SEC_WEBSOCKET_EXTENSIONS") as l_ws_extension then
|
||||
if attached pmce_server_support and then attached req.meta_string_variable ("HTTP_SEC_WEBSOCKET_EXTENSIONS") as l_ws_extension then
|
||||
-- at the moment we only handle web socket compression extension (PMCE permessage-deflate).
|
||||
--| We need a way to define which compression algorithm the server support.
|
||||
--|
|
||||
@@ -224,10 +225,9 @@ feature -- Basic Operation
|
||||
res.header.add_header_key_value ("Sec-WebSocket-Accept", l_key)
|
||||
|
||||
-- Sec-WebSocket-Extensions
|
||||
if is_pcme_supported and then attached accepted_offer as l_offer
|
||||
and then attached extension_response(l_offer) as l_extension_response
|
||||
if attached pmce_server_support and then attached accepted_offer as l_offer
|
||||
then
|
||||
res.header.add_header_key_value ("Sec-WebSocket-Extensions", l_extension_response)
|
||||
res.header.add_header_key_value ("Sec-WebSocket-Extensions", l_offer.extension_response)
|
||||
end
|
||||
|
||||
if is_verbose then
|
||||
@@ -267,20 +267,20 @@ feature -- Response!
|
||||
l_opcode: NATURAL_32
|
||||
do
|
||||
l_message := a_message
|
||||
-- if attached accepted_offer and then not on_handshake then
|
||||
-- l_message := compress_string (l_message)
|
||||
-- end
|
||||
if attached accepted_offer and then not on_handshake then
|
||||
l_message := compress_string (l_message)
|
||||
end
|
||||
debug ("ws")
|
||||
print (">>do_send (..., "+ opcode_name (a_opcode) +", ..)%N")
|
||||
end
|
||||
if not retried then
|
||||
create l_header_message.make_empty
|
||||
-- if attached accepted_offer and then not on_handshake then
|
||||
-- l_opcode := (0x80 | a_opcode).to_natural_32
|
||||
-- l_header_message.append_code ((l_opcode.bit_xor (0b1000000)))
|
||||
-- else
|
||||
if attached accepted_offer and then not on_handshake then
|
||||
l_opcode := (0x80 | a_opcode).to_natural_32
|
||||
l_header_message.append_code ((l_opcode.bit_xor (0b1000000)))
|
||||
else
|
||||
l_header_message.append_code ((0x80 | a_opcode).to_natural_32)
|
||||
-- end
|
||||
end
|
||||
l_message_count := l_message.count
|
||||
n := l_message_count.to_natural_64
|
||||
if l_message_count > 0xffff then
|
||||
@@ -305,7 +305,7 @@ feature -- Response!
|
||||
if not socket.was_error then
|
||||
l_chunk_size := 16_384 -- 16K TODO: see if we should make it customizable.
|
||||
if l_message_count < l_chunk_size then
|
||||
socket.put_string_8_noexception (a_message)
|
||||
socket.put_string_8_noexception (l_message)
|
||||
else
|
||||
from
|
||||
i := 0
|
||||
@@ -390,6 +390,7 @@ feature -- Response!
|
||||
is_data_frame_ok: BOOLEAN -- Is the last process data framing ok?
|
||||
retried: BOOLEAN
|
||||
l_frame_rsv: INTEGER
|
||||
-- l_ps: PROFILING_SETTING
|
||||
do
|
||||
if not retried then
|
||||
l_socket := socket
|
||||
@@ -600,7 +601,10 @@ feature -- Response!
|
||||
|
||||
if attached accepted_offer then
|
||||
-- Uncompress data.
|
||||
-- Result.append_payload_data_chop (uncompress_string (l_chunk), l_bytes_read, l_remaining_len = 0)
|
||||
Result.append_raw_data_chop (l_chunk, l_bytes_read, l_remaining_len = 0)
|
||||
if Result.is_fin and then attached Result.raw_data as l_raw_data then
|
||||
Result.append_payload_data_chop (uncompress_string (l_raw_data), Result.raw_data_length.to_integer_32, l_remaining_len = 0)
|
||||
end
|
||||
else
|
||||
Result.append_payload_data_chop (l_chunk, l_bytes_read, l_remaining_len = 0)
|
||||
end
|
||||
@@ -859,56 +863,110 @@ feature {NONE} -- Debug
|
||||
|
||||
feature -- PCME
|
||||
|
||||
-- uncompress_string (a_string: STRING): STRING
|
||||
-- local
|
||||
-- di: ZLIB_STRING_UNCOMPRESS
|
||||
-- l_string: STRING
|
||||
-- l_array: ARRAY [NATURAL_8]
|
||||
-- l_byte: SPECIAL [INTEGER_8]
|
||||
-- do
|
||||
-- create l_string.make_from_string (a_string)
|
||||
-- --Prepend 0x78 and 09c
|
||||
-- l_string.prepend_character ((156).to_character_8)
|
||||
-- l_string.prepend_character ((120).to_character_8)
|
||||
uncompress_string (a_string: STRING): STRING
|
||||
local
|
||||
di: ZLIB_STRING_UNCOMPRESS
|
||||
l_string: STRING
|
||||
l_array: ARRAY [NATURAL_8]
|
||||
l_byte: SPECIAL [INTEGER_8]
|
||||
do
|
||||
create l_string.make_from_string (a_string)
|
||||
|
||||
-- -- Append 4 octects 0x00 0x00 0xff 0xff to the tail of the paiload message
|
||||
---- l_array := string_to_array (l_string)
|
||||
---- l_byte := byte_array (l_array)
|
||||
|
||||
-- -- TODO add logic to compute window size based on extension negotiation.
|
||||
|
||||
-- create di.string_stream_with_size (l_string, {WEB_SOCKET_PCME_CONSTANTS}.default_chunk_size)
|
||||
|
||||
-- -- Append 4 octects 0x00 0x00 0xff 0xff to the tail of the paiload message
|
||||
-- l_string.append_character ((0x00).to_character_8)
|
||||
-- l_string.append_character ((0x00).to_character_8)
|
||||
-- l_string.append_character ((0xff).to_character_8)
|
||||
-- l_string.append_character ((0xff).to_character_8)
|
||||
-- Result := di.to_string_with_options (-{WEB_SOCKET_PCME_CONSTANTS}.default_window_size)
|
||||
|
||||
-- l_array := string_to_array (l_string)
|
||||
-- l_byte := byte_array (l_array)
|
||||
|
||||
|
||||
|
||||
|
||||
-- create di.string_stream (l_string)
|
||||
-- Result := di.to_string
|
||||
-- debug ("ws")
|
||||
-- print ("%NBytes uncompresses:" + di.total_bytes_uncompressed.out)
|
||||
-- print ("%NUncompress message:" + Result)
|
||||
-- end
|
||||
-- end
|
||||
Result := do_uncompress (a_string)
|
||||
end
|
||||
|
||||
-- compress_string (a_string: STRING): STRING
|
||||
-- local
|
||||
-- dc: ZLIB_STRING_COMPRESS
|
||||
-- l_string: STRING
|
||||
-- do
|
||||
|
||||
do_uncompress (a_string: STRING): STRING
|
||||
local
|
||||
di: ZLIB_STRING_UNCOMPRESS
|
||||
l_string: STRING
|
||||
do
|
||||
create l_string.make_from_string (a_string)
|
||||
di := string_uncompress
|
||||
if attached di then
|
||||
-- create di.string_stream_with_size (l_string, {WEB_SOCKET_PCME_CONSTANTS}.default_chunk_size)
|
||||
di.set_string (l_string)
|
||||
|
||||
-- Append 4 octects 0x00 0x00 0xff 0xff to the tail of the paiload message
|
||||
l_string.append_character ((0x00).to_character_8)
|
||||
l_string.append_character ((0x00).to_character_8)
|
||||
l_string.append_character ((0xff).to_character_8)
|
||||
l_string.append_character ((0xff).to_character_8)
|
||||
Result := di.to_string_with_options (-{WEB_SOCKET_PMCE_CONSTANTS}.default_window_size)
|
||||
else
|
||||
create di.string_stream_with_size(l_string, {WEB_SOCKET_PMCE_CONSTANTS}.default_chunk_size)
|
||||
|
||||
-- Append 4 octects 0x00 0x00 0xff 0xff to the tail of the paiload message
|
||||
l_string.append_character ((0x00).to_character_8)
|
||||
l_string.append_character ((0x00).to_character_8)
|
||||
l_string.append_character ((0xff).to_character_8)
|
||||
l_string.append_character ((0xff).to_character_8)
|
||||
Result := di.to_string_with_options (-{WEB_SOCKET_PMCE_CONSTANTS}.default_window_size)
|
||||
end
|
||||
end
|
||||
|
||||
string_uncompress: detachable ZLIB_STRING_UNCOMPRESS
|
||||
|
||||
compress_string (a_string: STRING): STRING
|
||||
local
|
||||
dc: ZLIB_STRING_COMPRESS
|
||||
l_string: STRING
|
||||
do
|
||||
|
||||
debug ("ws")
|
||||
print ("%NBegin compresses:" + a_string.count.out)
|
||||
end
|
||||
-- -- TODO add logic to compute window size based on extension negotiation.
|
||||
-- create Result.make_empty
|
||||
-- create dc.string_stream (Result)
|
||||
-- create dc.string_stream_with_size (Result, {WEB_SOCKET_PCME_CONSTANTS}.default_chunk_size)
|
||||
-- dc.mark_sync_flush
|
||||
-- dc.put_string (a_string)
|
||||
-- dc.put_string_with_options (a_string, {ZLIB_CONSTANTS}.Z_default_compression, -{WEB_SOCKET_PCME_CONSTANTS}.default_window_size, {WEB_SOCKET_PCME_CONSTANTS}.default_value_memory, {ZLIB_CONSTANTS}.z_default_strategy.to_integer_32)
|
||||
-- Result := Result.substring (1, Result.count - 4)
|
||||
|
||||
-- Result := Result.substring (3, Result.count - 4)
|
||||
Result := do_compress (a_string)
|
||||
|
||||
-- debug ("ws")
|
||||
-- print ("%NBytes uncompresses:" + dc.total_bytes_compressed.out )
|
||||
-- end
|
||||
-- end
|
||||
end
|
||||
|
||||
|
||||
do_compress (a_string: STRING): STRING
|
||||
local
|
||||
dc: ZLIB_STRING_COMPRESS
|
||||
do
|
||||
create Result.make_empty
|
||||
dc := string_compress
|
||||
|
||||
if attached dc then
|
||||
dc.set_string (Result)
|
||||
dc.mark_sync_flush
|
||||
dc.put_string_with_options (a_string, {ZLIB_CONSTANTS}.Z_default_compression, -{WEB_SOCKET_PMCE_CONSTANTS}.default_window_size, 9, {ZLIB_CONSTANTS}.z_default_strategy.to_integer_32)
|
||||
Result := Result.substring (1, Result.count - 4)
|
||||
else
|
||||
create dc.string_stream_with_size(Result, {WEB_SOCKET_PMCE_CONSTANTS}.default_chunk_size)
|
||||
dc.mark_sync_flush
|
||||
dc.put_string_with_options (a_string, {ZLIB_CONSTANTS}.Z_default_compression, -{WEB_SOCKET_PMCE_CONSTANTS}.default_window_size, 9, {ZLIB_CONSTANTS}.z_default_strategy.to_integer_32)
|
||||
Result := Result.substring (1, Result.count - 4)
|
||||
end
|
||||
end
|
||||
|
||||
string_compress: detachable ZLIB_STRING_COMPRESS
|
||||
|
||||
byte_array (a_bytes: SPECIAL [NATURAL_8]) : SPECIAL [INTEGER_8]
|
||||
local
|
||||
i: INTEGER
|
||||
@@ -963,28 +1021,17 @@ feature -- PCME
|
||||
|
||||
feature {NONE} -- Extensions
|
||||
|
||||
is_pcme_supported: BOOLEAN
|
||||
--| Temporary hack to test websocket compression
|
||||
|
||||
on_handshake: BOOLEAN
|
||||
|
||||
permessage_compression: STRING = "permessage-deflate"
|
||||
--| Temporary hack to test websocket compression
|
||||
|
||||
extension_response (a_offer: WEBSOCKET_PCME): detachable STRING_8
|
||||
do
|
||||
if attached a_offer.name as l_name then
|
||||
create Result.make_from_string (l_name)
|
||||
end
|
||||
end
|
||||
|
||||
handle_extensions (a_extension: READABLE_STRING_32)
|
||||
-- handle WebSocket extensions.
|
||||
local
|
||||
l_parse: COMPRESSION_EXTENSIONS_PARSER
|
||||
l_offers: LIST [WEBSOCKET_PCME]
|
||||
l_parse: WEB_SOCKET_COMPRESSION_EXTENSIONS_PARSER
|
||||
l_offers: LIST [WEB_SOCKET_PMCE]
|
||||
l_accepted: BOOLEAN
|
||||
l_offer: WEBSOCKET_PCME
|
||||
l_offer:WEB_SOCKET_PMCE_DEFLATE_OFFER
|
||||
l_client_offer: WEB_SOCKET_PMCE_DEFLATE_OFFER
|
||||
l_accept_offer_accept: WEB_SOCKET_PMCE_DEFLATE_ACCEPT_FACTORY
|
||||
do
|
||||
-- TODO improve handle
|
||||
-- at the moment only check we have permessage_compression
|
||||
@@ -994,26 +1041,22 @@ feature {NONE} -- Extensions
|
||||
l_parse.parse
|
||||
l_offers := l_parse.last_offers
|
||||
if not l_offers.is_empty then
|
||||
-- filter by permessage-deflate.
|
||||
--| TODO: validate if it's a valid extension.
|
||||
--| validate params.
|
||||
create l_accept_offer_accept
|
||||
across l_offers as ic
|
||||
until
|
||||
l_accepted
|
||||
loop
|
||||
if attached {STRING_32} ic.item.name as l_name and then
|
||||
l_name.is_case_insensitive_equal_general (permessage_compression)
|
||||
then
|
||||
l_accepted := True
|
||||
create l_offer
|
||||
l_offer.set_name (permessage_compression)
|
||||
create l_client_offer.make
|
||||
l_client_offer.configure (ic.item)
|
||||
if attached pmce_server_support as l_server_suppor then
|
||||
accepted_offer := l_accept_offer_accept.accept_offer (l_server_suppor, l_client_offer)
|
||||
l_accepted := attached accepted_offer
|
||||
end
|
||||
end
|
||||
accepted_offer := l_offer
|
||||
end
|
||||
end
|
||||
|
||||
accepted_offer: detachable WEBSOCKET_PCME
|
||||
accepted_offer: detachable WEB_SOCKET_PMCE_DEFLATE_ACCEPT
|
||||
-- Accepted compression extension.
|
||||
|
||||
;note
|
||||
|
||||
Reference in New Issue
Block a user