Reuse http_network library.

Reintroduced HTTPD_STREAM_SOCKET for backward compatibility, and ease of usage.
Added websocket libraries (client, and protocol).
This commit is contained in:
2016-10-13 16:25:11 +02:00
parent 981942b2d6
commit 897f64e4fe
54 changed files with 2233 additions and 95 deletions

View File

@@ -0,0 +1,29 @@
note
description: "[
API to perform actions like opening and closing the connection, sending and receiving messages, and listening
for events triggered by the server
]"
date: "$Date$"
revision: "$Revision$"
deferred class
WEB_SOCKET_CLIENT
inherit
WEB_SOCKET_CLIENT_I
feature -- Status report
is_ssl_supported: BOOLEAN = False
feature -- Factory
new_socket (a_port: INTEGER; a_host: STRING): HTTP_STREAM_SOCKET
do
if is_tunneled then
check ssl_supported: False end
end
create {HTTP_STREAM_SOCKET} Result.make_client_by_port (a_port, a_host)
end
end

View File

@@ -0,0 +1,54 @@
note
description: "[
API to perform actions like opening and closing the connection, sending and receiving messages, and listening
for events triggered by the server
]"
date: "$Date$"
revision: "$Revision$"
deferred class
WEB_SOCKET_CLIENT
inherit
WEB_SOCKET_CLIENT_I
feature -- Status report
is_ssl_supported: BOOLEAN = True
feature -- Factory
new_socket (a_port: INTEGER; a_host: STRING): HTTP_STREAM_SOCKET
local
l_ssl: HTTP_STREAM_SSL_SOCKET
do
if is_tunneled then
create l_ssl.make_client_by_port (a_port, a_host)
Result := l_ssl
if attached ssl_protocol as l_prot then
if l_prot.is_case_insensitive_equal ("ssl_2_3") then
l_ssl.set_ssl_protocol_to_ssl_2_or_3
elseif l_prot.is_case_insensitive_equal ("tls_1_0") then
l_ssl.set_ssl_protocol_to_tls_1_0
elseif l_prot.is_case_insensitive_equal ("tls_1_1") then
l_ssl.set_ssl_protocol_to_tls_1_1
elseif l_prot.is_case_insensitive_equal ("tls_1_2") then
l_ssl.set_ssl_protocol_to_tls_1_2
elseif l_prot.is_case_insensitive_equal ("dtls_1_0") then
l_ssl.set_ssl_protocol_to_dtls_1_0
else -- Default
l_ssl.set_ssl_protocol_to_tls_1_2
end
end
if attached ssl_key_file as k then
l_ssl.set_key_file_path (k)
end
if attached ssl_certificate_file as c then
l_ssl.set_certificate_file_path (c)
end
else
create {HTTP_STREAM_SOCKET} Result.make_client_by_port (a_port, a_host)
end
end
end

View File

@@ -0,0 +1,100 @@
note
description: "[
API to perform actions like opening and closing the connection, sending and receiving messages, and listening
for events triggered by the server
]"
author: ""
date: "$Date$"
revision: "$Revision$"
deferred class
WEB_SOCKET
inherit
WEB_SOCKET_CONSTANTS
feature -- Access
uri: READABLE_STRING_GENERAL
-- WebSocket Protocol defines two URI schemes, ws and wss for
-- unencrypted and encrypted traffic between the client and the server.
port: INTEGER
-- Current WebSocket protocol.
ws_port_default: INTEGER = 80
-- WebSocket Protocol uses port 80 for regular WebSocket connections.
wss_port_default: INTEGER = 443
-- WebSocket connections tunneled over Transport Layer Security (TLS) use port 443.
protocols: detachable LIST [STRING]
-- List of protocol names that the client supports.
protocol: STRING
-- Client-Server protocol selected.
-- Has the result fo protocol negotiation between client and the server
-- By default it's an empty string.
is_tunneled: BOOLEAN
-- Is the current connection tunneled over TLS/SSL?
local
l_uri: STRING
do
l_uri := uri.as_lower.as_string_8
Result := l_uri.starts_with ("wss") -- TODO extract to ws_constants.
end
ready_state: WEB_SOCKET_READY_STATE
-- Connection state
feature -- Methods
send (a_message: STRING)
-- Send a message `a_message'
require
is_open: ready_state.is_open
deferred
end
close (a_id: INTEGER)
-- Close a websocket connection with a close id : `a_id'
deferred
ensure
is_closed: ready_state.is_closed
end
close_with_description (a_id: INTEGER; a_description: READABLE_STRING_GENERAL)
-- Close a websocket connection with a close id : `a_id' and a description `a_description'
deferred
ensure
is_closed: ready_state.is_closed
end
feature -- Change Element
set_protocol (a_protocol: STRING)
-- Set `protocol' with `a_protocol'
do
protocol := a_protocol
ensure
protocol_set: protocol = a_protocol
end
set_protocols (a_protocols: detachable ITERABLE [STRING])
local
l_protocols: LIST [STRING]
do
if a_protocols /= Void then
create {ARRAYED_LIST [STRING]} l_protocols.make (0)
across
a_protocols as ic
loop
l_protocols.force (ic.item)
end
protocols := l_protocols
end
end
end

View File

@@ -0,0 +1,604 @@
note
description: "[
API to perform actions like opening and closing the connection, sending and receiving messages, and listening
for events triggered by the server
]"
date: "$Date$"
revision: "$Revision$"
deferred class
WEB_SOCKET_CLIENT_I
inherit
WEB_SOCKET_SUBSCRIBER
redefine
on_websocket_error,
on_websocket_text_message,
on_websocket_binary_message,
on_websocket_close,
on_websocket_open,
on_websocket_ping,
on_websocket_pong
end
WEB_SOCKET
THREAD
rename
make as thread_make
end
feature -- Initialization
initialize (a_uri: READABLE_STRING_GENERAL; a_protocols: detachable ITERABLE [STRING])
-- Initialize websocket client
require
is_valid_uri: is_valid_uri (a_uri)
do
thread_make
uri := a_uri
set_default_port
create protocol.make_empty
set_protocols (a_protocols)
create ready_state.make
socket := new_socket (port, host)
create server_handshake.make
end
initialize_with_port (a_uri: READABLE_STRING_GENERAL; a_port: INTEGER; a_protocols: detachable ITERABLE [STRING])
-- Initialize websocket client
require
is_valid_uri: is_valid_uri (a_uri)
do
thread_make
uri := a_uri
port := a_port
create protocol.make_empty
set_protocols (a_protocols)
create ready_state.make
socket := new_socket (port, host)
create server_handshake.make
end
initialize_with_host_port_and_path (a_host: READABLE_STRING_GENERAL; a_port: INTEGER; a_path: READABLE_STRING_GENERAL)
require
is_valid_uri: is_valid_uri (a_host)
do
thread_make
uri := a_host + ":" + a_port.out + a_path
port := a_port
create protocol.make_empty
-- set_protocols (a_protocols)
create ready_state.make
socket := new_socket (port, host)
create server_handshake.make
end
feature -- Factory
new_socket (a_port: INTEGER; a_host: STRING): HTTP_STREAM_SOCKET
-- New socket for port `a_port' on host `a_host'.
deferred
end
feature -- Access
socket: HTTP_STREAM_SOCKET
-- Socket
has_error: BOOLEAN
do
Result := implementation.has_error
end
is_server_hanshake_accepted: BOOLEAN
is_valid_uri (a_uri: READABLE_STRING_GENERAL): BOOLEAN
-- Is `a_uri' a valid URI?
local
l_uri: URI
do
create l_uri.make_from_string (a_uri.as_string_8)
Result := l_uri.is_valid
end
server_handshake: WEB_SOCKET_HANDSHAKE_DATA
-- Handshake data received from the server
feature -- Access: ssl
is_ssl_supported: BOOLEAN
-- Is SSL supported?
deferred
end
ssl_protocol: detachable READABLE_STRING_GENERAL
-- SSL protocol , if `is_ssl_supported'.
ssl_certificate_file: detachable PATH
-- SSL certificate file , if `is_ssl_supported'.
ssl_key_file: detachable PATH
-- SSL key file , if `is_ssl_supported'.
feature -- Element change
set_ssl_protocol (a_prot: like ssl_protocol)
do
ssl_protocol := a_prot
end
set_ssl_certificate_file (p: detachable PATH)
-- Set SSL certificate from file at `p'.
do
ssl_certificate_file := p
end
set_ssl_key_file (p: detachable PATH)
-- Set SSL key from file at `p'.
do
ssl_key_file := p
end
feature -- Events API
on_open (a_message: STRING)
deferred
end
on_text_message (a_message: STRING)
deferred
end
on_binary_message (a_message: STRING)
deferred
end
on_close (a_code: INTEGER; a_reason: STRING)
deferred
end
on_error (a_error: STRING)
deferred
end
feature -- Subscriber Events
on_websocket_handshake (a_request: STRING)
-- Send handshake message
do
socket.put_string (a_request)
end
on_websocket_text_message (a_message: STRING)
do
on_text_message (a_message)
end
on_websocket_binary_message (a_message: STRING)
do
on_binary_message (a_message)
end
on_websocket_open (a_message: STRING)
do
on_open (a_message)
end
on_websocket_close (a_message: STRING)
do
close_with_description (1000, a_message)
on_close (1000, a_message)
end
on_websocket_error (a_error: STRING)
do
on_error (a_error)
close_with_description (1002,"")
end
on_websocket_ping (a_message: STRING)
do
do_send (10, a_message)
end
on_websocket_pong (a_message: STRING)
do
-- do_send (9, a_message)
end
feature -- Execute
execute
require else
is_socket_valid: socket.exists
do
set_implementation
socket.connect
check
socket_connected: socket.is_connected
end
send_handshake
receive_handshake
if is_server_hanshake_accepted then
ready_state.set_state ({WEB_SOCKET_READY_STATE}.open)
on_websocket_open ("Open Connection")
from
until
ready_state.is_closed or has_error
loop
receive
end
else
on_websocket_error ("Server Handshake not accepted")
--log(Not connected)
socket.close
end
rescue
on_websocket_close ("")
socket.close
end
feature -- Methods
send (a_message: STRING)
do
do_send (1, a_message)
end
send_binary (a_message: STRING)
do
do_send (2, a_message)
end
close (a_id: INTEGER)
-- Close a websocket connection with a close id : `a_id'
do
do_send (8, "")
ready_state.set_state ({WEB_SOCKET_READY_STATE}.closed)
end
close_with_description (a_id: INTEGER; a_description: READABLE_STRING_GENERAL)
-- Close a websocket connection with a close id : `a_id' and a description `a_description'
do
do_send (8, "")
ready_state.set_state ({WEB_SOCKET_READY_STATE}.closed)
end
feature {NONE} -- Implementation
set_implementation
do
create implementation.make_with_protocols_and_port (Current, host, protocols, port)
end
send_handshake
local
l_uri: URI
l_handshake: STRING
l_random: SALT_XOR_SHIFT_64_GENERATOR
l_secure_protocol: STRING
do
create l_uri.make_from_string (uri.as_string_8)
create l_handshake.make_empty
if l_uri.path.is_empty then
l_handshake.append ("GET / HTTP/1.1")
l_handshake.append (crlf)
elseif l_uri.query = Void then
l_handshake.append ("GET " + l_uri.path + " HTTP/1.1")
l_handshake.append (crlf)
else
if attached l_uri.query as l_query then
l_handshake.append ("GET " + l_uri.path + "?" + l_query + " HTTP/1.1")
l_handshake.append (crlf)
end
end
if attached l_uri.host as l_host then
l_handshake.replace_substring_all ("$host", l_host)
l_handshake.append ("Host: " + l_host + ":" + port.out)
l_handshake.append (crlf)
end
l_handshake.append_string ("Upgrade: websocket")
l_handshake.append (crlf)
l_handshake.append_string ("Connection: Upgrade")
l_handshake.append (crlf)
l_handshake.append_string ("Sec-WebSocket-Key: ")
create l_random.make (16)
l_handshake.append_string (base64_encode_array (l_random.new_sequence))
l_handshake.append (crlf)
if attached protocols as l_protocols then
create l_secure_protocol.make_empty
across
l_protocols as c
loop
l_secure_protocol.append (c.item)
l_secure_protocol.append (" ,")
end
l_secure_protocol.remove_tail (1)
l_handshake.append_string ("Sec-WebSocket-Protocol:" + l_secure_protocol)
l_handshake.append (crlf)
end
l_handshake.append_string ("Sec-WebSocket-Version: 13")
l_handshake.append (crlf)
l_handshake.append (crlf)
implementation.start_handshake (l_handshake)
end
receive_handshake
do
analyze_request_message
if server_handshake.request_header.has_substring ("HTTP/1.1 101") and then attached server_handshake.request_header_map.item ("Upgrade") as l_upgrade_key and then -- Upgrade header must be present with value websocket
l_upgrade_key.is_case_insensitive_equal ("websocket") and then attached server_handshake.request_header_map.item ("Connection") as l_connection_key and then -- Connection header must be present with value Upgrade
l_connection_key.has_substring ("Upgrade")
then
is_server_hanshake_accepted := True
if attached server_handshake.request_header_map.item ("Sec-WebSocet-Protocol") as l_protocol then
set_protocol (l_protocol)
end
end
end
receive
do
implementation.receive
end
set_default_port
do
if is_tunneled then
port := wss_port_default
else
port := ws_port_default
end
end
client_handshake_required_template: STRING = "[
GET $resource HTTP/1.1
Host: $host
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: $key
Sec-WebSocket-Version: 13
]"
base64_encode_array (a_sequence: ARRAY [NATURAL_8]): STRING_8
-- Encode a byte array `a_sequence' into Base64 notation.
local
l_result: STRING
l_base_64: BASE64
do
create l_result.make_empty
across
a_sequence as i
loop
l_result.append_character (i.item.to_character_8)
end
create l_base_64
Result := l_base_64.encoded_string (l_result)
end
host: STRING
local
l_uri: URI
do
create Result.make_empty
create l_uri.make_from_string (uri.as_string_8)
if attached l_uri.host as l_host then
Result := l_host
end
end
feature -- Parse Request line
analyze_request_message
-- Analyze message extracted from `socket' as HTTP request
require
input_readable: socket /= Void and then socket.is_open_read
local
end_of_stream: BOOLEAN
pos, n: INTEGER
line: detachable STRING
k, val: STRING
txt: STRING
do
create txt.make (64)
server_handshake.set_request_header (txt)
if attached next_line as l_request_line and then not l_request_line.is_empty then
txt.append (l_request_line)
txt.append_character ('%N')
else
server_handshake.mark_error
end
-- l_is_verbose := is_verbose
if not server_handshake.has_error then -- or l_is_verbose then
-- if `is_verbose' we can try to print the request, even if it is a bad HTTP request
from
line := next_line
until
line = Void or end_of_stream
loop
n := line.count
-- if l_is_verbose then
-- log (line)
-- end
pos := line.index_of (':', 1)
if pos > 0 then
k := line.substring (1, pos - 1)
if line [pos + 1].is_space then
pos := pos + 1
end
if line [n] = '%R' then
n := n - 1
end
val := line.substring (pos + 1, n)
server_handshake.put_header (k, val)
end
txt.append (line)
txt.append_character ('%N')
if line.is_empty or else line [1] = '%R' then
end_of_stream := True
else
line := next_line
end
end
end
end
next_line: detachable STRING
-- Next line fetched from `socket' is available.
require
is_readable: socket.is_open_read
do
if socket.socket_ok then
socket.read_line_thread_aware
Result := socket.last_string
end
end
feature -- {WEB_SOCKET_CLIENT}
-- do_send (a_opcode: NATURAL_32; a_message: STRING)
-- local
-- l_chunks: INTEGER
-- i: INTEGER
-- l_index: INTEGER
-- l_chunk_size: INTEGER
-- l_key: STRING
-- l_message: STRING
-- l_frame : STRING
-- do
-- print ("%NMessage count:" + a_message.count.out)
-- create l_frame.make_empty
-- create l_message.make_empty
-- if a_message.count > 65535 then
-- --!Improve. this code need to be checked.
-- print("%N Case:1")
-- l_frame.append_code ((0x80 | a_opcode).to_natural_32)
-- l_frame.append_code ((0x80 | 127).to_natural_32)
-- l_frame.append_code (0x0)
-- l_frame.append_code (0x0)
-- l_frame.append_code (0x0)
-- l_frame.append_code (0x0)
-- l_frame.append_code (((a_message.count) |>> 32).to_character_8.code.as_natural_32)
-- l_frame.append_code (((a_message.count) |>> 16).to_character_8.code.as_natural_32)
-- l_frame.append_code (((a_message.count ) |>> 8).to_character_8.code.as_natural_32)
-- l_frame.append_code ((a_message.count).to_character_8.code.as_natural_32)
-- elseif a_message.count > 125 then
-- print("%N Case:2")
-- print ("Message count:" + a_message.count.out)
-- l_frame.append_code ((0x80 | a_opcode).to_natural_32)
-- l_frame.append_code ((0x80 | 126).to_natural_32)
-- l_frame.append_code (((a_message.count ) |>> 8).as_natural_32)
-- l_frame.append_code ((a_message.count).to_character_8.code.as_natural_32)
-- print ("%NHeaderMessage:" + l_frame)
-- else
-- print("%N Case:3")
-- print ("Message count:" + a_message.count.out)
-- l_frame.append_code ((0x80 | a_opcode).to_natural_32)
-- l_frame.append_code ((0x80 | a_message.count).to_natural_32)
-- end
--
-- l_key := new_key
-- l_frame.append (l_key.substring (1, 4))
-- l_message := implementation.unmmask (a_message, l_key.substring (1, 4))
-- l_frame.append (l_message)
-- socket.send_message (l_frame)
-- end
do_send (a_opcode:INTEGER; a_message: READABLE_STRING_8)
local
i: INTEGER
l_chunk_size: INTEGER
l_chunk: READABLE_STRING_8
l_header_message: STRING
l_message_count: INTEGER
n: NATURAL_64
retried: BOOLEAN
l_message : STRING
l_key: STRING
do
if not retried then
create l_header_message.make_empty
l_header_message.append_code ((0x80 | a_opcode).to_natural_32)
l_message_count := a_message.count
n := l_message_count.to_natural_64
if l_message_count > 0xffff then
--! Improve. this code needs to be checked.
l_header_message.append_code ((0x80 | 127).to_natural_32)
l_header_message.append_character ((n |>> 56).to_character_8)
l_header_message.append_character ((n |>> 48).to_character_8)
l_header_message.append_character ((n |>> 40).to_character_8)
l_header_message.append_character ((n |>> 32).to_character_8)
l_header_message.append_character ((n |>> 24).to_character_8)
l_header_message.append_character ((n |>> 16).to_character_8)
l_header_message.append_character ((n |>> 8).to_character_8)
l_header_message.append_character ( n.to_character_8)
elseif l_message_count > 125 then
l_header_message.append_code ((0x80 | 126).to_natural_32)
l_header_message.append_code ((n |>> 8).as_natural_32)
l_header_message.append_character (n.to_character_8)
else
l_header_message.append_code ((0x80 | n).as_natural_32)
end
l_key := new_key
l_header_message.append (l_key.substring (1, 4))
socket.put_string (l_header_message)
l_message := implementation.unmmask (a_message, l_key.substring (1, 4))
l_chunk_size := 16_384 -- 16K
if l_message_count < l_chunk_size then
socket.put_string (l_message)
else
from
i := 0
until
l_chunk_size = 0
loop
debug ("ws")
print ("Sending chunk " + (i + 1).out + " -> " + (i + l_chunk_size).out +" / " + l_message_count.out + "%N")
end
l_chunk := l_message.substring (i + 1, l_message_count.min (i + l_chunk_size))
socket.put_string (l_chunk)
if l_chunk.count < l_chunk_size then
l_chunk_size := 0
end
i := i + l_chunk_size
end
debug ("ws")
print ("Sending chunk done%N")
end
end
else
-- FIXME: what should be done on rescue?
end
rescue
retried := True
io.put_string ("Internal error in " + generator + ".do_send (conn, a_opcode=" + a_opcode.out + ", a_message) !%N")
retry
end
new_key: STRING
local
l_random: SALT_XOR_SHIFT_64_GENERATOR
do
create Result.make_empty
create l_random.make (4)
across
l_random.new_sequence as i
loop
Result.append_integer (i.item)
end
end
implementation: WEB_SOCKET_IMPL
-- Web Socket implementation
crlf: STRING = "%R%N"
end

View File

@@ -0,0 +1,90 @@
note
description: "Summary description for {WEB_SOCKET_HANDSHAKE_DATA}."
author: ""
date: "$Date$"
revision: "$Revision$"
class
WEB_SOCKET_HANDSHAKE_DATA
create
make
feature {NONE} -- Initialization
make
do
reset
end
reset
do
has_error := False
version := Void
create method.make_empty
create uri.make_empty
create request_header.make_empty
create request_header_map.make (10)
end
feature -- Access
request_header: STRING
-- Header' source
request_header_map: HASH_TABLE [STRING, STRING]
-- Contains key:value of the header
has_error: BOOLEAN
-- Error occurred during `analyze_request_message'
method: STRING
-- http verb
uri: STRING
-- http endpoint
version: detachable STRING
-- http_version
feature -- Change Element
set_method (a_method: STRING)
-- Set `method' with `a_method'
do
method := a_method
ensure
method_set: method = a_method
end
set_version (a_version: STRING)
-- Set `version' with `a_version'
do
version := a_version
ensure
version_set: attached version as l_version implies l_version = a_version
end
mark_error
do
has_error := True
end
set_request_header (a_header: STRING)
-- Set `request_header' with `a_header'
do
request_header := a_header
ensure
request_header_set: request_header = a_header
end
put_header (a_key: STRING; a_value : STRING)
do
request_header_map.put (a_value, a_key)
end
end

View File

@@ -0,0 +1,739 @@
note
description: "Summary description for {WEB_SOCKET_IMPL}."
author: ""
date: "$Date$"
revision: "$Revision$"
class
WEB_SOCKET_IMPL
inherit
WEB_SOCKET
create
make, make_with_port, make_with_protocols, make_with_protocols_and_port
feature {NONE} -- Initialization
make (a_subscriber: WEB_SOCKET_SUBSCRIBER; a_uri: READABLE_STRING_GENERAL)
-- Create a websocket instante with a default port.
do
reset
subscriber := a_subscriber
uri := a_uri
create protocol.make_empty
set_default_port
create ready_state.make
ensure
uri_set: a_uri = uri
port_wss: is_tunneled implies port = wss_port_default
port_ws: not is_tunneled implies port = ws_port_default
ready_state_set: ready_state.state = {WEB_SOCKET_READY_STATE}.connecting
subscriber_set: subscriber = a_subscriber
protocol_set: protocol.is_empty
end
make_with_port (a_subscriber: WEB_SOCKET_SUBSCRIBER; a_uri: READABLE_STRING_GENERAL; a_port: INTEGER)
-- Create a websocket instance with port `a_port',
do
make (a_subscriber, a_uri)
port := a_port
ensure
uri_set: a_uri = uri
port_set: port = a_port
ready_state_set: ready_state.state = {WEB_SOCKET_READY_STATE}.connecting
subscriber_set: subscriber = a_subscriber
end
make_with_protocols (a_subscriber: WEB_SOCKET_SUBSCRIBER; a_uri: READABLE_STRING_GENERAL; a_protocols: detachable LIST [STRING])
-- Create a web socket instance with a list of protocols `a_protocols' and default port.
do
reset
subscriber := a_subscriber
uri := a_uri
create protocol.make_empty
protocols := a_protocols
set_default_port
create ready_state.make
ensure
uri_set: a_uri = uri
port_wss: is_tunneled implies port = wss_port_default
port_ws: not is_tunneled implies port = ws_port_default
protocols_set: protocols = a_protocols
ready_state_set: ready_state.state = {WEB_SOCKET_READY_STATE}.connecting
subscriber_set: subscriber = a_subscriber
protocol_set: protocol.is_empty
end
make_with_protocols_and_port (a_subscriber: WEB_SOCKET_SUBSCRIBER; a_uri: READABLE_STRING_GENERAL; a_protocols: detachable LIST [STRING]; a_port: INTEGER)
-- Create a web socket instance with a list of protocols `a_protocols' and port `a_port'.
do
make_with_protocols (a_subscriber, a_uri, a_protocols)
port := a_port
ensure
uri_set: a_uri = uri
protocols_set: protocols = a_protocols
port_set: port = a_port
ready_state_set: ready_state.state = {WEB_SOCKET_READY_STATE}.connecting
subscriber_set: subscriber = a_subscriber
end
reset
do
has_error := False
end
feature -- Access
has_error: BOOLEAN
is_verbose: BOOLEAN
feature -- Handshake
start_handshake (a_handshake: STRING)
do
subscriber.on_websocket_handshake (a_handshake)
end
feature -- Receive
receive
local
l_frame: detachable WEB_SOCKET_FRAME
l_client_message: detachable READABLE_STRING_8
do
l_frame := next_frame (subscriber.connection)
if l_frame /= Void and then l_frame.is_valid then
if attached l_frame.injected_control_frames as l_injections then
-- Process injected control frames now.
-- FIXME
across
l_injections as ic
loop
if ic.item.is_connection_close then
-- FIXME: we should probably send this event .. after the `l_frame.parent' frame event.
subscriber.on_websocket_close ("Normal Close")
elseif ic.item.is_ping then
-- FIXME reply only to the most recent ping ...
subscriber.on_websocket_ping (ic.item.payload_data)
elseif ic.item.is_pong then
subscriber.on_websocket_pong (ic.item.payload_data)
else
subscriber.on_websocket_error ("Wrong Opcode")
end
end
end
l_client_message := l_frame.payload_data
if l_client_message = Void then
l_client_message := ""
end
debug ("ws")
print ("%NExecute: %N")
print (" [opcode: " + opcode_name (l_frame.opcode) + "]%N")
if l_frame.is_text then
print (" [client message: %"" + l_client_message + "%"]%N")
elseif l_frame.is_binary then
print (" [client binary message length: %"" + l_client_message.count.out + "%"]%N")
end
print (" [is_control: " + l_frame.is_control.out + "]%N")
print (" [is_binary: " + l_frame.is_binary.out + "]%N")
print (" [is_text: " + l_frame.is_text.out + "]%N")
end
if l_frame.is_connection_close then
subscriber.on_websocket_close ("Normal Close")
elseif l_frame.is_binary then
subscriber.on_websocket_binary_message (l_client_message)
elseif l_frame.is_text then
subscriber.on_websocket_text_message (l_client_message)
elseif l_frame.is_ping then
subscriber.on_websocket_ping (l_client_message)
elseif l_frame.is_pong then
subscriber.on_websocket_pong (l_client_message)
else
subscriber.on_websocket_error ("Wrong Opcode")
end
else
debug ("ws")
print ("%NExecute: %N")
print (" [ERROR: invalid frame]%N")
if l_frame /= Void and then attached l_frame.error as err then
print (" [Code: " + err.code.out + "]%N")
print (" [Description: " + err.description + "]%N")
end
end
subscriber.on_websocket_close ("")
end
-- if l_utf.is_valid_utf_8_string_8 (l_message) then
-- if is_data_frame_ok then
-- if opcode = text_frame then
-- subscriber.on_websocket_text_message (l_message)
-- elseif opcode = binary_frame then
-- subscriber.on_websocket_binary_message (l_message)
-- elseif opcode = ping_frame then
-- subscriber.on_websocket_ping (l_message)
-- elseif opcode = pong_frame then
-- subscriber.on_websocket_pong (l_message)
-- elseif opcode = Connection_close_frame then
-- subscriber.on_websocket_close ("Normal Close")
-- elseif opcode = ping_frame then
-- subscriber.on_websocket_ping (l_message)
-- elseif opcode = pong_frame then
-- subscriber.on_websocket_pong (l_message)
-- else
-- subscriber.on_websocket_error ("Wrong Opcode")
-- end
-- else
-- subscriber.on_websocket_close ("Invalid data frame")
-- end
-- else
-- subscriber.on_websocket_error ("Invalid UTF-8")
-- end
end
feature -- Methods
send (a_message: STRING)
do
end
close (a_id: INTEGER)
-- Close a websocket connection with a close id : `a_id'
do
end
close_with_description (a_id: INTEGER; a_description: READABLE_STRING_GENERAL)
-- Close a websocket connection with a close id : `a_id' and a description `a_description'
do
end
feature {NONE} -- Implementation
set_default_port
do
if is_tunneled then
port := wss_port_default
else
port := ws_port_default
end
end
subscriber: WEB_SOCKET_SUBSCRIBER
next_frame (a_socket: HTTP_STREAM_SOCKET): detachable WEB_SOCKET_FRAME
-- TODO Binary messages
-- Handle error responses in a better way.
-- IDEA:
-- class FRAME
-- is_fin: BOOLEAN
-- opcode: WEB_SOCKET_STATUS_CODE (TEXT, BINARY, CLOSE, CONTINUE,PING, PONG)
-- data/payload
-- status_code: #see Status Codes http://tools.ietf.org/html/rfc6455#section-7.3
-- has_error
--
-- See Base Framing Protocol: http://tools.ietf.org/html/rfc6455#section-5.2
-- 0 1 2 3
-- 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
-- +-+-+-+-+-------+-+-------------+-------------------------------+
-- |F|R|R|R| opcode|M| Payload len | Extended payload length |
-- |I|S|S|S| (4) |A| (7) | (16/64) |
-- |N|V|V|V| |S| | (if payload len==126/127) |
-- | |1|2|3| |K| | |
-- +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
-- | Extended payload length continued, if payload len == 127 |
-- + - - - - - - - - - - - - - - - +-------------------------------+
-- | |Masking-key, if MASK set to 1 |
-- +-------------------------------+-------------------------------+
-- | Masking-key (continued) | Payload Data |
-- +-------------------------------- - - - - - - - - - - - - - - - +
-- : Payload Data continued ... :
-- + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
-- | Payload Data continued ... |
-- +---------------------------------------------------------------+
note
EIS: "name=WebSocket RFC", "protocol=URI", "src=http://tools.ietf.org/html/rfc6455#section-5.2"
require
a_socket_in_blocking_mode: a_socket.is_blocking
local
l_opcode: INTEGER
l_len: INTEGER
l_remaining_len: INTEGER
l_payload_len: NATURAL_64
l_chunk: STRING
l_rsv: BOOLEAN
l_fin: BOOLEAN
l_has_mask: BOOLEAN
l_chunk_size: INTEGER
l_byte: INTEGER
l_fetch_count: INTEGER
l_bytes_read: INTEGER
s: STRING
is_data_frame_ok: BOOLEAN -- Is the last process data framing ok?
retried: BOOLEAN
do
if not retried then
debug ("ws")
print ("next_frame:%N")
end
from
is_data_frame_ok := True
until
l_fin or not is_data_frame_ok
loop
-- multi-frames or continue is only valid for Binary or Text
s := next_bytes (a_socket, 1)
if s.is_empty then
is_data_frame_ok := False
debug ("ws")
print ("[ERROR] incomplete_data!%N")
end
else
l_byte := s [1].code
debug ("ws")
print (" fin,rsv(3),opcode(4)=")
print (to_byte_representation (l_byte))
print ("%N")
end
l_fin := l_byte & (0b10000000) /= 0
l_rsv := l_byte & (0b01110000) = 0
l_opcode := l_byte & 0b00001111
if Result /= Void then
if l_opcode = Result.opcode then
-- should not occur in multi-fragment frame!
create Result.make (l_opcode, l_fin)
Result.report_error (protocol_error, "Unexpected injected frame")
elseif l_opcode = continuation_frame then
-- Expected
Result.update_fin (l_fin)
elseif is_control_frame (l_opcode) then
-- Control frames (see Section 5.5) MAY be injected in the middle of
-- a fragmented message. Control frames themselves MUST NOT be fragmented.
-- if the l_opcode is a control frame then there is an error!!!
-- CLOSE, PING, PONG
create Result.make_as_injected_control (l_opcode, Result)
else
-- should not occur in multi-fragment frame!
create Result.make (l_opcode, l_fin)
Result.report_error (protocol_error, "Unexpected frame")
end
else
create Result.make (l_opcode, l_fin)
if Result.is_continuation then
-- Continuation frame is not expected without parent frame!
Result.report_error (protocol_error, "There is no message to continue!")
end
end
if Result.is_valid then
--| valid frame/fragment
if is_verbose then
log ("+ frame " + opcode_name (l_opcode) + " (fin=" + l_fin.out + ")")
end
-- rsv validation
if not l_rsv then
-- RSV1, RSV2, RSV3: 1 bit each
-- MUST be 0 unless an extension is negotiated that defines meanings
-- for non-zero values. If a nonzero value is received and none of
-- the negotiated extensions defines the meaning of such a nonzero
-- value, the receiving endpoint MUST _Fail the WebSocket
-- Connection_
-- FIXME: add support for extension ?
Result.report_error (protocol_error, "RSV values MUST be 0 unless an extension is negotiated that defines meanings for non-zero values")
end
else
if is_verbose then
log ("+ INVALID frame " + opcode_name (l_opcode) + " (fin=" + l_fin.out + ")")
end
end
-- At the moment only TEXT, (pending Binary)
if Result.is_valid then
if Result.is_text or Result.is_binary or Result.is_control then
-- Reading next byte (mask+payload_len)
s := next_bytes (a_socket, 1)
if s.is_empty then
Result.report_error (invalid_data, "Incomplete data for mask and payload len")
else
l_byte := s [1].code
debug ("ws")
print (" mask,payload_len(7)=")
print (to_byte_representation (l_byte))
io.put_new_line
end
l_has_mask := l_byte & (0b10000000) /= 0 -- MASK
l_len := l_byte & 0b01111111 -- 7bits
debug ("ws")
print (" payload_len=" + l_len.out)
io.put_new_line
end
if Result.is_control and then l_len > 125 then
-- All control frames MUST have a payload length of 125 bytes or less
-- and MUST NOT be fragmented.
Result.report_error (protocol_error, "Control frame MUST have a payload length of 125 bytes or less")
elseif l_len = 127 then -- TODO proof of concept read 8 bytes.
-- the following 8 bytes interpreted as a 64-bit unsigned integer
-- (the most significant bit MUST be 0) are the payload length.
-- Multibyte length quantities are expressed in network byte order.
s := next_bytes (a_socket, 8) -- 64 bits
debug ("ws")
print (" extended payload length=" + string_to_byte_representation (s))
io.put_new_line
end
if s.count < 8 then
Result.report_error (Invalid_data, "Incomplete data for 64 bit Extended payload length")
else
l_payload_len := s [8].natural_32_code.to_natural_64
l_payload_len := l_payload_len | (s [7].natural_32_code.to_natural_64 |<< 8)
l_payload_len := l_payload_len | (s [6].natural_32_code.to_natural_64 |<< 16)
l_payload_len := l_payload_len | (s [5].natural_32_code.to_natural_64 |<< 24)
l_payload_len := l_payload_len | (s [4].natural_32_code.to_natural_64 |<< 32)
l_payload_len := l_payload_len | (s [3].natural_32_code.to_natural_64 |<< 40)
l_payload_len := l_payload_len | (s [2].natural_32_code.to_natural_64 |<< 48)
l_payload_len := l_payload_len | (s [1].natural_32_code.to_natural_64 |<< 56)
end
elseif l_len = 126 then
s := next_bytes (a_socket, 2) -- 16 bits
debug ("ws")
print (" extended payload length bits=" + string_to_byte_representation (s))
io.put_new_line
end
if s.count < 2 then
Result.report_error (Invalid_data, "Incomplete data for 16 bit Extended payload length")
else
l_payload_len := s [2].natural_32_code.to_natural_64
l_payload_len := l_payload_len | (s [1].natural_32_code.to_natural_64 |<< 8)
end
else
l_payload_len := l_len.to_natural_64
end
debug ("ws")
print (" Full payload length=" + l_payload_len.out)
io.put_new_line
end
if Result.is_valid then
if l_has_mask then
Result.report_error (protocol_error, "Server sent a masked frame!")
else
end
if Result.is_valid then
l_chunk_size := 0x4000 -- 16 K
if l_payload_len > {INTEGER_32}.max_value.to_natural_64 then
-- Issue .. to big to store in STRING
-- FIXME !!!
Result.report_error (Message_too_large, "Can not handle payload data (len=" + l_payload_len.out + ")")
else
l_len := l_payload_len.to_integer_32
end
from
l_fetch_count := 0
l_remaining_len := l_len
until
l_fetch_count >= l_len or l_len = 0 or not Result.is_valid
loop
if l_remaining_len < l_chunk_size then
l_chunk_size := l_remaining_len
end
a_socket.read_stream (l_chunk_size)
l_bytes_read := a_socket.bytes_read
debug ("ws")
print ("read chunk size=" + l_chunk_size.out + " fetch_count=" + l_fetch_count.out + " l_len=" + l_len.out + " -> " + l_bytes_read.out + "bytes%N")
end
if a_socket.bytes_read > 0 then
l_remaining_len := l_remaining_len - l_bytes_read
l_chunk := a_socket.last_string
l_fetch_count := l_fetch_count + l_bytes_read
Result.append_payload_data_chop (l_chunk, l_bytes_read, l_remaining_len = 0)
else
Result.report_error (internal_error, "Issue reading payload data...")
end
end
if is_verbose then
log (" Received " + l_fetch_count.out + " out of " + l_len.out + " bytes <===============")
end
debug ("ws")
print (" -> ")
if attached Result.payload_data as l_payload_data then
s := l_payload_data.tail (l_fetch_count)
if s.count > 50 then
print (string_to_byte_hexa_representation (s.head (50) + ".."))
else
print (string_to_byte_hexa_representation (s))
end
print ("%N")
if Result.is_text and Result.is_fin and Result.fragment_count = 0 then
print (" -> ")
if s.count > 50 then
print (s.head (50) + "..")
else
print (s)
end
print ("%N")
end
end
end
end
end
end
end
end
end
if Result /= Void then
if attached Result.error as err then
if is_verbose then
log (" !Invalid frame: " + err.string)
end
end
if Result.is_injected_control then
if attached Result.parent as l_parent then
if not Result.is_valid then
l_parent.report_error (protocol_error, "Invalid injected frame")
end
if Result.is_connection_close then
-- Return this and process the connection close right away!
l_parent.update_fin (True)
l_fin := Result.is_fin
else
Result := l_parent
l_fin := l_parent.is_fin
check
-- This is a control frame but occurs in fragmented frame.
inside_fragmented_frame: not l_fin
end
end
else
check
has_parent: False
end
l_fin := False -- This is a control frame but occurs in fragmented frame.
end
end
if not Result.is_valid then
is_data_frame_ok := False
end
else
is_data_frame_ok := False
end
end
end
rescue
retried := True
if Result /= Void then
Result.report_error (internal_error, "Internal error")
end
retry
end
next_bytes (a_socket: HTTP_STREAM_SOCKET; nb: INTEGER): STRING
require
nb > 0
local
n, l_bytes_read: INTEGER
do
create Result.make (nb)
from
n := nb
until
n = 0
loop
a_socket.read_stream (nb)
l_bytes_read := a_socket.bytes_read
if l_bytes_read > 0 then
Result.append (a_socket.last_string)
n := n - l_bytes_read
else
n := 0
end
end
end
unmask (a_chunk: STRING_8; a_pos: INTEGER; a_key: READABLE_STRING_8)
local
i, n: INTEGER
do
from
i := 1
n := a_chunk.count
until
i > n
loop
a_chunk.put_code (a_chunk.code (i).bit_xor (a_key [((i + (a_pos - 1) - 1) \\ 4) + 1].natural_32_code), i)
i := i + 1
end
end
append_chunk_unmasked (a_chunk: READABLE_STRING_8; a_pos: INTEGER; a_key: READABLE_STRING_8; a_target: STRING)
-- To convert masked data into unmasked data, or vice versa, the following
-- algorithm is applied. The same algorithm applies regardless of the
-- direction of the translation, e.g., the same steps are applied to
-- mask the data as to unmask the data.
-- Octet i of the transformed data ("transformed-octet-i") is the XOR of
-- octet i of the original data ("original-octet-i") with octet at index
-- i modulo 4 of the masking key ("masking-key-octet-j"):
-- j = i MOD 4
-- transformed-octet-i = original-octet-i XOR masking-key-octet-j
-- The payload length, indicated in the framing as frame-payload-length,
-- does NOT include the length of the masking key. It is the length of
-- the "Payload data", e.g., the number of bytes following the masking
-- key.
note
EIS: "name=Masking", "src=http://tools.ietf.org/html/rfc6455#section-5.3", "protocol=uri"
local
i, n: INTEGER
do
-- debug ("ws")
-- print ("append_chunk_unmasked (%"" + string_to_byte_representation (a_chunk) + "%",%N%Ta_pos=" + a_pos.out+ ", a_key, a_target #.count=" + a_target.count.out + ")%N")
-- end
from
i := 1
n := a_chunk.count
until
i > n
loop
a_target.append_code (a_chunk.code (i).bit_xor (a_key [((i + (a_pos - 1) - 1) \\ 4) + 1].natural_32_code))
i := i + 1
end
end
feature {NONE} -- Debug
log (m: STRING)
do
io.put_string (m + "%N")
end
to_byte (a_integer: INTEGER): ARRAY [INTEGER]
require
valid: a_integer >= 0 and then a_integer <= 255
local
l_val: INTEGER
l_index: INTEGER
do
create Result.make_filled (0, 1, 8)
from
l_val := a_integer
l_index := 8
until
l_val < 2
loop
Result.put (l_val \\ 2, l_index)
l_val := l_val // 2
l_index := l_index - 1
end
Result.put (l_val, l_index)
end
feature -- Masking
unmmask (a_frame: READABLE_STRING_8; a_key: READABLE_STRING_8): STRING
-- To convert masked data into unmasked data, or vice versa, the following
-- algorithm is applied. The same algorithm applies regardless of the
-- direction of the translation, e.g., the same steps are applied to
-- mask the data as to unmask the data.
-- Octet i of the transformed data ("transformed-octet-i") is the XOR of
-- octet i of the original data ("original-octet-i") with octet at index
-- i modulo 4 of the masking key ("masking-key-octet-j"):
-- j = i MOD 4
-- transformed-octet-i = original-octet-i XOR masking-key-octet-j
-- The payload length, indicated in the framing as frame-payload-length,
-- does NOT include the length of the masking key. It is the length of
-- the "Payload data", e.g., the number of bytes following the masking
-- key.
note
EIS: "name=Masking", "src=S", "protocol=uri"
local
l_frame: STRING
i: INTEGER
do
l_frame := a_frame.twin
from
i := 1
until
i > l_frame.count
loop
l_frame [i] := (l_frame [i].code.to_integer_8.bit_xor (a_key [((i - 1) \\ 4) + 1].code.to_integer_8)).to_character_8
i := i + 1
end
Result := l_frame
end
to_byte_representation (a_integer: INTEGER): STRING
require
valid: a_integer >= 0 and then a_integer <= 255
local
l_val: INTEGER
do
create Result.make (8)
from
l_val := a_integer
until
l_val < 2
loop
Result.prepend_integer (l_val \\ 2)
l_val := l_val // 2
end
Result.prepend_integer (l_val)
end
string_to_byte_representation (s: STRING): STRING
require
valid: s.count > 0
local
i, n: INTEGER
do
n := s.count
create Result.make (8 * n)
if n > 0 then
from
i := 1
until
i > n
loop
if not Result.is_empty then
Result.append_character (':')
end
Result.append (to_byte_representation (s [i].code))
i := i + 1
end
end
end
string_to_byte_hexa_representation (s: STRING): STRING
local
i, n: INTEGER
c: INTEGER
do
n := s.count
create Result.make (8 * n)
if n > 0 then
from
i := 1
until
i > n
loop
if not Result.is_empty then
Result.append_character (':')
end
c := s [i].code
check
c <= 0xFF
end
Result.append_character (((c |>> 4) & 0xF).to_hex_character)
Result.append_character (((c) & 0xF).to_hex_character)
i := i + 1
end
end
end
end

View File

@@ -0,0 +1,30 @@
note
description: "[
Objects that is used to create dummy web socket client object
used for void-safety
]"
author: "$Author$"
date: "$Date$"
revision: "$Revision$"
class
WEB_SOCKET_NULL_CLIENT
inherit
WEB_SOCKET_SUBSCRIBER
feature -- Handshake
on_websocket_handshake (request: STRING)
-- Default do nothing
do
end
feature -- TCP connection
connection: HTTP_STREAM_SOCKET
do
create Result.make_client_by_port (0, "null")
end
end

View File

@@ -0,0 +1,120 @@
note
description: "Report the state of the connection: [CONNECTING, OPEN, CLOSING, CLOSED]"
date: "$Date$"
revision: "$Revision$"
class
WEB_SOCKET_READY_STATE
create
make
feature -- Initialization
make
do
set_state (Connecting)
ensure
state_connecting: state = Connecting
end
feature -- Access
state: INTEGER
-- Current ready state
feature -- Status Report
is_valid_state (a_state: INTEGER): BOOLEAN
-- Is `a_state' a valid ready state?
do
Result := Connecting = a_state or else Open = a_state or else Closing = a_state or else Closed = a_state
end
is_connecting: BOOLEAN
-- Is current `state' connecting?
do
Result := state = Connecting
end
is_open: BOOLEAN
-- Is current `state' open?
do
Result := state = Open
end
is_closing: BOOLEAN
-- Is current `state' closing?
do
Result := state = Closing
end
is_closed: BOOLEAN
-- Is current `state' closed?
do
Result := state = Closed
end
feature -- Change Element
set_state (a_state: INTEGER)
-- Set `state' to `a_state'
require
valid_state: is_valid_state (a_state)
do
state := a_state
ensure
state_set: state = a_state
end
mark_connecting
--Set `state' to `connecting'
do
set_state (Connecting)
ensure
connecting_state: state = Connecting
end
mark_open
--Set `state' to `open'
do
set_state (Open)
ensure
open_state: state = Open
end
mark_closing
--Set `state' to `closing'
do
set_state (Closing)
ensure
open_state: state = Closing
end
mark_closed
--Set `state' to `closed'
do
set_state (Closed)
ensure
open_state: state = Closed
end
feature --State connection
Connecting: INTEGER = 0
-- The connection is in progress but has not been established.
Open: INTEGER = 1
-- The connection has been stablished. Messages can flow between client and server.
Closing: INTEGER = 2
-- The connection is going through the closing handshake
Closed: INTEGER = 3
-- The connection has been closed or could not be opened.
invariant
is_valid_sate: is_valid_state (state)
end

View File

@@ -0,0 +1,61 @@
note
description: "Summary description for {WEB_SOCKET_SUBSCRIBER}."
author: ""
date: "$Date$"
revision: "$Revision$"
deferred class
WEB_SOCKET_SUBSCRIBER
feature -- Events
on_websocket_ping (a_message: detachable STRING)
-- Send a ping in response to a received ping.
do
end
on_websocket_pong (a_message: detachable STRING)
-- Default implementation do nothing.
do
end
on_websocket_text_message (a_message: detachable STRING)
-- Default implementation do nothing.
do
end
on_websocket_binary_message (a_message: detachable STRING)
-- Default implementation do nothing.
do
end
on_websocket_open (a_message: detachable STRING)
do
end
on_websocket_close (a_message: detachable STRING)
do
end
on_websocket_error (a_error: detachable STRING)
do
end
feature -- Handshake
on_websocket_handshake (request: STRING)
-- Default do nothing
deferred
end
feature -- TCP connection
connection: HTTP_STREAM_SOCKET
deferred
end
end

View File

@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-13-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-13-0 http://www.eiffel.com/developers/xml/configuration-1-13-0.xsd" name="web_socket_client" uuid="EE010507-597F-4FAD-8EFA-B7251E800911" library_target="web_socket_client">
<target name="web_socket_client">
<root all_classes="true"/>
<file_rule>
<exclude>/.git$</exclude>
<exclude>/EIFGENs$</exclude>
<exclude>/CVS$</exclude>
<exclude>/.svn$</exclude>
</file_rule>
<option warning="true" void_safety="all">
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
</option>
<setting name="concurrency" value="thread"/>
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
<library name="crypto" location="$ISE_LIBRARY\unstable\library\text\encryption\crypto\crypto-safe.ecf"/>
<library name="encoder" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\text\encoder\encoder-safe.ecf"/>
<library name="net" location="$ISE_LIBRARY\library\net\net-safe.ecf"/>
<library name="thread" location="$ISE_LIBRARY\library\thread\thread-safe.ecf"/>
<library name="uri" location="$ISE_LIBRARY\library\text\uri\uri-safe.ecf"/>
<library name="lib_http_network" location="..\..\http_network\http_network-safe.ecf"/>
<library name="lib_web_socket_protocol" location="..\protocol\web_socket_protocol-safe.ecf"/>
<cluster name="web_socket_client" location=".\src\" recursive="true">
<file_rule>
<exclude>/no_ssl$</exclude>
<exclude>/ssl$</exclude>
<exclude>/spec$</exclude>
</file_rule>
<cluster name="ssl" location="$|ssl\" recursive="true">
<condition>
<custom name="ssl_enabled" value="true"/>
</condition>
<condition>
<custom name="net_ssl_enabled" value="true"/>
</condition>
</cluster>
<cluster name="no_ssl" location="$|no_ssl\" recursive="true">
<condition>
<custom name="ssl_enabled" excluded_value="true"/>
<custom name="net_ssl_enabled" excluded_value="true"/>
</condition>
</cluster>
</cluster>
</target>
</system>

View File

@@ -0,0 +1,64 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-13-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-13-0 http://www.eiffel.com/developers/xml/configuration-1-13-0.xsd" name="web_socket_client" uuid="934F36F1-D417-4695-A5A9-2D005B35BB1B" library_target="web_socket_client">
<target name="web_socket_client">
<root all_classes="true"/>
<file_rule>
<exclude>/.git$</exclude>
<exclude>/EIFGENs$</exclude>
<exclude>/CVS$</exclude>
<exclude>/.svn$</exclude>
</file_rule>
<option warning="true" void_safety="none">
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
</option>
<setting name="concurrency" value="thread"/>
<library name="base" location="$ISE_LIBRARY\library\base\base.ecf"/>
<library name="crypto" location="$ISE_LIBRARY\unstable\library\text\encryption\crypto\crypto.ecf"/>
<library name="encoder" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\text\encoder\encoder.ecf"/>
<library name="net" location="$ISE_LIBRARY\library\net\net.ecf"/>
<library name="net_ssl" location="$ISE_LIBRARY\unstable\library\network\socket\netssl\net_ssl.ecf">
<condition>
<custom name="client_ssl_disabled" excluded_value="true"/>
</condition>
</library>
<library name="thread" location="$ISE_LIBRARY\library\thread\thread.ecf"/>
<library name="uri" location="$ISE_LIBRARY\library\text\uri\uri.ecf"/>
<library name="lib_web_socket_protocol" location="..\protocol\web_socket_protocol.ecf"/>
<cluster name="web_socket_client" location=".\src\" recursive="true">
<file_rule>
<exclude>/socket$</exclude>
<exclude>/no_ssl$</exclude>
<exclude>/ssl$</exclude>
<exclude>/spec$</exclude>
</file_rule>
<cluster name="ssl" location="$|ssl\" recursive="true">
<condition>
<custom name="client_ssl_disabled" excluded_value="true"/>
</condition>
</cluster>
<cluster name="no_ssl" location="$|no_ssl\" recursive="true">
<condition>
<custom name="client_ssl_disabled" value="true"/>
</condition>
</cluster>
<cluster name="socket" location="$|socket\">
<file_rule>
<exclude>/tcp_stream_socket.e$</exclude>
<condition>
<version type="compiler" max="15.2.0.0"/>
</condition>
</file_rule>
<cluster name="socket_ssl" location="$|ssl\" recursive="true" hidden="true">
<condition>
<custom name="client_ssl_disabled" excluded_value="true"/>
</condition>
</cluster>
</cluster>
<cluster name="spec_before_15_01" location="$|spec\before_15_01\" recursive="true">
<condition>
<version type="compiler" max="15.2.0.0"/>
</condition>
</cluster>
</cluster>
</target>
</system>