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:
jvelilla
2016-10-26 17:35:05 -03:00
parent 406559f1c6
commit 4c912912a6
32 changed files with 1448 additions and 219 deletions

View File

@@ -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"/>

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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