Merge branch 'master' of https://github.com/EiffelWebFramework/EWF into ewf_ws_compression

# Conflicts:
#	library/server/wsf/connector/standalone_websocket/websocket/web_socket.e
This commit is contained in:
jvelilla
2016-10-17 10:37:44 -03:00
151 changed files with 14370 additions and 781 deletions

View File

@@ -0,0 +1,38 @@
note
description: "ws_client application root class"
date: "$Date$"
revision: "$Revision$"
class
APPLICATION
inherit
SHARED_EXECUTION_ENVIRONMENT
create
make
feature {NONE} -- Initialization
make
-- Run application.
local
ws_client: EXAMPLE_WS_CLIENT
do
-- create ws_client.make_with_port ("wss://echo.websocket.org", 443, Void)
-- if ws_client.is_ssl_supported and ws_client.is_tunneled then
-- ws_client.set_ssl_certificate_file ("ca.crt")
-- ws_client.set_ssl_key_file ("ca.key")
-- else
-- -- Not supported!
-- end
create ws_client.make_with_port ("ws://echo.websocket.org", 80, Void)
-- create ws_client.make_with_port ("ws://127.0.0.1", 9090, Void)
ws_client.launch
ws_client.join_all
execution_environment.sleep (5_000_000)
end
end

View File

@@ -0,0 +1,78 @@
note
description: "Summary description for {EXAMPLE_WS_CLIENT}."
author: ""
date: "$Date$"
revision: "$Revision$"
class
EXAMPLE_WS_CLIENT
inherit
WEB_SOCKET_CLIENT
create
make, make_with_port
feature -- Initialization
make (a_uri: STRING; a_protocols: detachable LIST [STRING])
do
initialize (a_uri, a_protocols)
create implementation.make (create {WEB_SOCKET_NULL_CLIENT}, a_uri)
end
make_with_port (a_uri: STRING; a_port: INTEGER; a_protocols: detachable LIST [STRING])
do
initialize_with_port (a_uri, a_port, a_protocols)
create implementation.make (create {WEB_SOCKET_NULL_CLIENT}, a_uri)
end
feature -- Access
count: INTEGER
feature -- Event
on_open (a_message: STRING)
do
print (a_message)
print ("%NProtocol:" + protocol)
on_text_message (a_message)
end
on_text_message (a_message: STRING)
local
l_message: STRING
do
if count <= 10 then
print ("%NMessage is %"" + a_message + "%".%N")
print ("%NCount:" + count.out)
create l_message.make_empty
l_message.append ("mesg#" + count.out)
send (l_message)
count := count + 1
else -- Send close initiated by the client
close (1001)
end
end
on_binary_message (a_message: STRING)
do
send_binary (a_message)
end
on_close (a_code: INTEGER; a_reason: STRING)
do
ready_state.set_state ({WEB_SOCKET_READY_STATE}.closed)
end
on_error (a_error: STRING)
do
end
connection: like socket
do
Result := socket
end
end

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-15-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-15-0 http://www.eiffel.com/developers/xml/configuration-1-15-0.xsd" name="ws_client" uuid="AF6EDC56-D7B4-4E1F-A62B-40EBED3D93DF">
<target name="common">
<file_rule>
<exclude>/.git$</exclude>
<exclude>/.svn$</exclude>
<exclude>/CVS$</exclude>
<exclude>/EIFGENs$</exclude>
</file_rule>
<option warning="true" is_obsolete_routine_type="true" void_safety="transitional">
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
</option>
<setting name="console_application" value="true"/>
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
<library name="web_socket_client" location="..\..\web_socket_client-safe.ecf" readonly="false"/>
<cluster name="ws_client" location=".\" recursive="true"/>
</target>
<target name="ws_client" extends="common">
<root class="APPLICATION" feature="make"/>
<setting name="concurrency" value="thread"/>
</target>
<target name="ws_client_ssl" extends="ws_client">
<variable name="ssl_enabled" value="true"/>
</target>
</system>

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_secure_connection_supported: BOOLEAN = False
feature -- Factory
new_socket (a_port: INTEGER; a_host: STRING): HTTP_STREAM_SOCKET
do
if is_secure then
check is_secure_connection_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_secure_connection_supported: BOOLEAN = True
feature -- Factory
new_socket (a_port: INTEGER; a_host: STRING): HTTP_STREAM_SOCKET
local
l_secure: HTTP_STREAM_SECURE_SOCKET
do
if is_secure then
create l_secure.make_client_by_port (a_port, a_host)
Result := l_secure
if attached secure_protocol as l_prot then
if l_prot.is_case_insensitive_equal ("ssl_2_3") then
l_secure.set_secure_protocol_to_ssl_2_or_3
elseif l_prot.is_case_insensitive_equal ("tls_1_0") then
l_secure.set_secure_protocol_to_tls_1_0
elseif l_prot.is_case_insensitive_equal ("tls_1_1") then
l_secure.set_secure_protocol_to_tls_1_1
elseif l_prot.is_case_insensitive_equal ("tls_1_2") then
l_secure.set_secure_protocol_to_tls_1_2
elseif l_prot.is_case_insensitive_equal ("dtls_1_0") then
l_secure.set_secure_protocol_to_dtls_1_0
else -- Default
l_secure.set_secure_protocol_to_tls_1_2
end
end
if attached secure_certificate_file as c then
l_secure.set_certificate_file_path (c)
end
if attached secure_certificate_key_file as k then
l_secure.set_key_file_path (k)
end
else
create {HTTP_STREAM_SOCKET} Result.make_client_by_port (a_port, a_host)
end
end
end

View File

@@ -0,0 +1,99 @@
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_secure: 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,603 @@
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: secure
is_secure_connection_supported: BOOLEAN
-- Is SSL supported?
deferred
end
secure_protocol: detachable READABLE_STRING_GENERAL
-- SSL protocol , if `is_secure_connection_supported'.
secure_certificate_file: detachable PATH
-- SSL certificate file , if `is_secure_connection_supported'.
secure_certificate_key_file: detachable PATH
-- SSL key file , if `is_secure_connection_supported'.
feature -- Element change
set_secure_protocol (a_prot: like secure_protocol)
do
secure_protocol := a_prot
end
set_secure_certificate_file (p: detachable PATH)
-- Set SSL certificate from file at `p'.
do
secure_certificate_file := p
end
set_secure_certificate_key_file (p: detachable PATH)
-- Set SSL key from file at `p'.
do
secure_certificate_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_secure 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_secure implies port = wss_port_default
port_ws: not is_secure 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_secure implies port = wss_port_default
port_ws: not is_secure 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_secure 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,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="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="thread" location="$ISE_LIBRARY\library\thread\thread.ecf"/>
<library name="uri" location="$ISE_LIBRARY\library\text\uri\uri.ecf"/>
<library name="lib_http_network" location="..\..\http_network\http_network.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>/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,15 @@
<!DOCTYPE html>
<html>
<head>
<title>WebSocket prototype</title>
<script src="js/jquery-1.10.1.js"></script>
<script src="js/main.js"></script>
</head>
<body>
<h1>WebSocket prototype</h1>
<button id="run">Run</button>
<p>Output :</p>
<div id=console>
</div>
</body>
</html>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,37 @@
$(function() {
var wsEchoURI = "ws://localhost:9999/";
var websocket;
function printConsole(text) {
$("#console").append($(document.createElement("p")).html(text));
}
function onWSOpen(evt) {
printConsole("WebSocket open");
if(websocket.readyState == websocket.OPEN) {
printConsole("WebSocket ready");
}
}
function onWSClose(evt) {
printConsole("WebSocket closed");
printConsole(websocket.readyState);
}
function onWSMessage(evt) {
printConsole(evt.data);
}
function onWSError(evt) {
printConsole("An error occured in the WebSocket : " + evt.data);
}
$("#run").click(function buttonRun() {
printConsole("Connection");
websocket = new WebSocket(wsEchoURI);
websocket.onopen = function(evt) { onWSOpen(evt) };
websocket.onclose = function(evt) { onWSClose(evt) };
websocket.onmessage = function(evt) { onWSMessage(evt) };
websocket.onerror = function(evt) { onWSError(evt) };
});
});

View File

@@ -0,0 +1,203 @@
note
description: "Constants for WebSockets"
date: "$Date$"
revision: "$Revision$"
class
WEB_SOCKET_CONSTANTS
feature -- Constants
HTTP_1_1: STRING = "HTTP/1.1 101 WebSocket Protocol Handshake"
Upgrade_ws: STRING = "Upgrade: websocket"
Connection_ws: STRING = "Connection: Upgrade"
Sec_WebSocket_Origin: STRING = "Sec-WebSocket-Origin: "
Sec_WebSocket_Protocol: STRING = "Sec-WebSocket-Protocol: "
Sec_WebSocket_Location: STRING = "Sec-WebSocket-Location: "
Sec_WebSocket_Version: STRING = "Sec-WebSocket-Version: "
Sec_WebSocket_Extensions: STRING = "Sec-WebSocket-Extensions: "
WebSocket_Origin: STRING = "WebSocket-Origin: "
WebSocket_Protocol: STRING = "WebSocket-Protocol: "
WebSocket_Location: STRING = "WebSocket-Location: "
Origin: STRING = "Origin"
Server: STRING = "EWSS"
Sec_WebSocket_Key: STRING = "Sec-WebSocket-Key"
Ws_scheme: STRING = "ws://"
Wss_scheme: STRING = "wss://"
Magic_guid: STRING = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
-- The handshake from the client looks as follows:
-- GET /chat HTTP/1.1
-- Host: server.example.com
-- Upgrade: websocket
-- Connection: Upgrade
-- Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
-- Origin: http://example.com
-- Sec-WebSocket-Protocol: chat, superchat
-- Sec-WebSocket-Version: 13
-- The handshake from the server looks as follows:
-- HTTP/1.1 101 Switching Protocols
-- Upgrade: websocket
-- Connection: Upgrade
-- Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
-- Sec-WebSocket-Protocol: chat
feature -- Opcodes Standard actions
--| Maybe we need an enum STANDARD_ACTIONS_OPCODES?
-- |Opcode | Meaning | Reference |
-- -+--------+-------------------------------------+-----------|
-- | 0 | Continuation Frame | RFC 6455 |
-- -+--------+-------------------------------------+-----------|
-- | 1 | Text Frame | RFC 6455 |
-- -+--------+-------------------------------------+-----------|
-- | 2 | Binary Frame | RFC 6455 |
-- -+--------+-------------------------------------+-----------|
-- | 8 | Connection Close Frame | RFC 6455 |
-- -+--------+-------------------------------------+-----------|
-- | 9 | Ping Frame | RFC 6455 |
-- -+--------+-------------------------------------+-----------|
-- | 10 | Pong Frame | RFC 6455 |
-- -+--------+-------------------------------------+-----------|
Continuation_frame: INTEGER = 0
Text_frame: INTEGER = 1
Binary_frame: INTEGER = 2
Connection_close_frame: INTEGER = 8
Ping_frame: INTEGER = 9
Pong_frame: INTEGER = 10
is_control_frame (a_opcode: INTEGER): BOOLEAN
-- Is `a_opcode' a control frame?
do
inspect a_opcode
when Connection_close_frame, Ping_frame, Pong_frame then
Result := True
else
end
end
opcode_name (a_opcode: INTEGER): STRING
do
inspect a_opcode
when Continuation_frame then Result := "Continuation"
when Text_frame then Result := "Text"
when Binary_frame then Result := "Binary"
when Connection_close_frame then Result := "Connection Close"
when Ping_frame then Result := "Ping"
when Pong_frame then Result := "Pong"
else
Result := "Unknown-Opcode"
end
Result := "0x" + a_opcode.to_hex_string + " " + Result
end
feature -- Close code numbers
-- Maybe an ENUM CLOSE_CODES
-- |Status Code | Meaning | Contact | Reference |
-- -+------------+-----------------+---------------+-----------|
-- | 1000 | Normal Closure | hybi@ietf.org | RFC 6455 |
-- -+------------+-----------------+---------------+-----------|
-- | 1001 | Going Away | hybi@ietf.org | RFC 6455 |
-- -+------------+-----------------+---------------+-----------|
-- | 1002 | Protocol error | hybi@ietf.org | RFC 6455 |
-- -+------------+-----------------+---------------+-----------|
-- | 1003 | Unsupported Data| hybi@ietf.org | RFC 6455 |
-- -+------------+-----------------+---------------+-----------|
-- | 1004 | ---Reserved---- | hybi@ietf.org | RFC 6455 |
-- -+------------+-----------------+---------------+-----------|
-- | 1005 | No Status Rcvd | hybi@ietf.org | RFC 6455 |
-- -+------------+-----------------+---------------+-----------|
-- | 1006 | Abnormal Closure| hybi@ietf.org | RFC 6455 |
-- -+------------+-----------------+---------------+-----------|
-- | 1007 | Invalid frame | hybi@ietf.org | RFC 6455 |
-- | | payload data | | |
-- -+------------+-----------------+---------------+-----------|
-- | 1008 | Policy Violation| hybi@ietf.org | RFC 6455 |
-- -+------------+-----------------+---------------+-----------|
-- | 1009 | Message Too Big | hybi@ietf.org | RFC 6455 |
-- -+------------+-----------------+---------------+-----------|
-- | 1010 | Mandatory Ext. | hybi@ietf.org | RFC 6455 |
-- -+------------+-----------------+---------------+-----------|
-- | 1011 | Internal Server | hybi@ietf.org | RFC 6455 |
-- | | Error | | |
-- -+------------+-----------------+---------------+-----------|
-- | 1015 | TLS handshake | hybi@ietf.org | RFC 6455 |
-- -+------------+-----------------+---------------+-----------|
Normal_closure: INTEGER = 1000
-- Indicates a normal closure, meaning that the purpose for
-- which the connection was established has been fulfilled.
Going_away: INTEGER = 1001
-- Indicates that an endpoint is "going away", such as a server
-- going down or a browser having navigated away from a page.
Protocol_error: INTEGER = 1002
-- Indicates that an endpoint is terminating the connection due
-- to a protocol error.
Unsupported_data: INTEGER = 1003
-- Indicates that an endpoint is terminating the connection
-- because it has received a type of data it cannot accept (e.g., an
-- endpoint that understands only text data MAY send this if it
-- receives a binary message).
Invalid_data: INTEGER = 1007
-- Indicates that an endpoint is terminating the connection
-- because it has received data within a message that was not
-- consistent with the type of the message (e.g., non-UTF-8 [RFC3629]
-- data within a text message).
Policy_violation: INTEGER = 1008
-- Indicates that an endpoint is terminating the connection
-- because it has received a message that violates its policy. This
-- is a generic status code that can be returned when there is no
-- other more suitable status code (e.g., 1003 or 1009) or if there
-- is a need to hide specific details about the policy.
Message_too_large: INTEGER = 1009
-- Indicates that an endpoint is terminating the connection
-- because it has received a message that is too big for it to
-- process.
Extension_required: INTEGER = 1010
-- Indicates that an endpoint (client) is terminating the
-- connection because it has expected the server to negotiate one or
-- more extension, but the server didn't return them in the response
-- message of the WebSocket handshake.
Internal_error: INTEGER = 1011
-- Indicates that a server is terminating the connection because
-- it encountered an unexpected condition that prevented it from
-- fulfilling the request.
end

View File

@@ -0,0 +1,35 @@
note
description: "Summary description for {WEB_SOCKET_ERROR_FRAME}."
author: ""
date: "$Date$"
revision: "$Revision$"
class
WEB_SOCKET_ERROR_FRAME
create
make
feature {NONE} -- Initialization
make (a_code: INTEGER; a_desc: like description)
do
code := a_code
description := a_desc
end
feature -- Access
code: INTEGER
description: READABLE_STRING_8
feature -- Conversion
string: STRING
do
create Result.make_from_string ("Error(" + code.out + "): " + description)
end
end

View File

@@ -0,0 +1,455 @@
note
description: "[
Summary description for {WEB_SOCKET_FRAME}.
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 ... |
+---------------------------------------------------------------+
Check the `check_utf_8_validity_on_chop' if there is performance issue
with bigger data.
]"
date: "$Date$"
revision: "$Revision$"
EIS: "name=Websocket RFC6455 section-5.2", "protocol=URI", "src=http://tools.ietf.org/html/rfc6455#section-5.2", "tag=rfc"
class
WEB_SOCKET_FRAME
inherit
ANY
WEB_SOCKET_CONSTANTS
create
make,
make_as_injected_control
feature {NONE} -- Initialization
make (a_opcode: INTEGER; flag_is_fin: BOOLEAN)
-- Create current frame with opcode `a_opcode'
-- and `a_fin' to indicate if this is the final fragment.
do
is_incomplete := False
opcode := a_opcode
is_fin := flag_is_fin
inspect opcode
when
Continuation_frame, -- 0
Text_frame, -- 1
Binary_frame -- 2
then
--| Supported opcode
when
Connection_close_frame, -- 8
Ping_frame, -- 9
Pong_frame -- 10
then
--| Supported control opcode
-- All control frames MUST have a payload length of 125 bytes or less
-- and MUST NOT be fragmented.
if flag_is_fin then
-- So far it is valid.
else
report_error (Protocol_error, "Control frames MUST NOT be fragmented.")
end
else
report_error (Protocol_error, "Unknown opcode")
end
end
make_as_injected_control (a_opcode: INTEGER; a_parent: WEB_SOCKET_FRAME)
require
parent_is_not_control_frame: not a_parent.is_control
a_opcode_is_control_frame: is_control_frame (a_opcode)
do
make (a_opcode, True)
parent := a_parent
a_parent.add_injected_control_frame (Current)
end
feature -- Access
opcode: INTEGER
-- CONTINUOUS, TEXT, BINARY, PING, PONG, CLOSING
is_fin: BOOLEAN
-- is the final fragment in a message?
is_rsv1: BOOLEAN
-- is extension negotiation in a message?
fragment_count: INTEGER
payload_length: NATURAL_64
payload_data: detachable STRING_8
-- Maybe we need a buffer here.
uncoded_payload_data: detachable STRING_32
local
utf: UTF_CONVERTER
do
if attached payload_data as d then
Result := utf.utf_8_string_8_to_string_32 (d)
end
end
error: detachable WEB_SOCKET_ERROR_FRAME
-- Describe the type of error
feature -- Access: injected control frames
injected_control_frames: detachable LIST [WEB_SOCKET_FRAME]
parent: detachable WEB_SOCKET_FRAME
-- If Current is injected, `parent' is the related fragmented frame
is_injected_control: BOOLEAN
do
Result := parent /= Void
ensure
Result implies (is_control_frame (opcode))
end
feature -- Operation
update_fin (a_flag_is_fin: BOOLEAN)
do
is_fin := a_flag_is_fin
end
update_rsv1 (a_flag_rsv1: BOOLEAN)
do
is_rsv1 := a_flag_rsv1
end
feature {WEB_SOCKET_FRAME} -- Change: injected control frames
add_injected_control_frame (f: WEB_SOCKET_FRAME)
require
Current_is_not_control: not is_control
f_is_control_frame: f.is_control
parented_to_current: f.parent = Current
local
lst: like injected_control_frames
do
lst := injected_control_frames
if lst = Void then
create {ARRAYED_LIST [WEB_SOCKET_FRAME]} lst.make (1)
injected_control_frames := lst
end
lst.force (f)
ensure
parented_to_current: f.parent = Current
end
remove_injected_control_frame (f: WEB_SOCKET_FRAME)
require
Current_is_not_control: not is_control
f_is_control_frame: f.is_control
parented_to_current: f.parent = Current
local
lst: like injected_control_frames
do
lst := injected_control_frames
if lst /= Void then
lst.prune (f)
if lst.is_empty then
injected_control_frames := Void
end
end
end
feature -- Query
is_binary: BOOLEAN
do
Result := opcode = binary_frame
end
is_text: BOOLEAN
do
Result := opcode = text_frame
end
is_continuation: BOOLEAN
do
Result := opcode = continuation_frame
end
is_connection_close: BOOLEAN
do
Result := opcode = connection_close_frame
end
is_control: BOOLEAN
do
inspect opcode
when connection_close_frame, Ping_frame, Pong_frame then
Result := True
else
end
end
is_ping: BOOLEAN
do
Result := opcode = ping_frame
end
is_pong: BOOLEAN
do
Result := opcode = pong_frame
end
feature -- Status report
is_valid: BOOLEAN
do
Result := not has_error
end
is_incomplete: BOOLEAN
has_error: BOOLEAN
do
Result := error /= Void
end
feature -- Change
increment_fragment_count
do
fragment_count := fragment_count + 1
end
check_utf_8_validity_on_chop: BOOLEAN = False
-- True: check for each chop
-- False: check only for each fragment
--| see autobahntestsuite #6.4.3 and #6.4.4
append_payload_data_chop (a_data: STRING_8; a_len: INTEGER; a_flag_chop_complete: BOOLEAN)
do
if a_flag_chop_complete then
increment_fragment_count
end
if attached payload_data as l_payload_data then
l_payload_data.append (a_data)
else
payload_data := a_data
end
payload_length := payload_length + a_len.to_natural_64
if is_text then
if is_fin and a_flag_chop_complete then
-- Check the whole message is a valid UTF-8 string
if attached payload_data as d then
if not is_valid_utf_8_string (d) then
report_error (invalid_data, "The text message is not a valid UTF-8 text!")
end
else
-- empty payload??
end
elseif check_utf_8_validity_on_chop or else a_flag_chop_complete then
-- Check the payload data as utf-8 stream (may be incomplete at this point)
if not is_valid_text_payload_stream then
report_error (invalid_data, "This is not a valid UTF-8 stream!")
-- is_valid implies the connection will be closed!
end
end
end
end
report_error (a_code: INTEGER; a_description: READABLE_STRING_8)
require
not has_error
do
create error.make (a_code, a_description)
ensure
has_error: has_error
is_not_valid: not is_valid
end
feature {NONE} -- Helper
last_utf_8_stream_validation_position: INTEGER
-- In relation with `is_valid_utf_8 (.., a_is_stream=True)'
is_valid_text_payload_stream: BOOLEAN
require
is_text_frame: is_text
do
if attached payload_data as s then
Result := is_valid_utf_8 (s, not is_fin)
end
end
is_valid_utf_8_string (s: READABLE_STRING_8): BOOLEAN
do
Result := is_valid_utf_8 (s, False)
-- and (create {UTF_CONVERTER}).is_valid_utf_8_string_8 (s)
end
is_valid_utf_8 (s: READABLE_STRING_8; a_is_stream: BOOLEAN): BOOLEAN
-- UTF-8 validity checker.
note
EIS: "name=UTF-8 RFC3629", "protocol=URI", "src=https://tools.ietf.org/html/rfc3629", "tag=rfc"
require
is_text_frame: is_text
local
i: like {STRING_8}.count
n: like {STRING_8}.count
c,c2,c3,c4,w: NATURAL_32
l_is_incomplete_stream: BOOLEAN
do
Result := True
-- Following code also check that codepoint is between 0 and 0x10FFFF
-- (as expected by spec, and tested by autobahn ws testsuite)
from
if a_is_stream then
i := last_utf_8_stream_validation_position -- to avoid recomputing from the beginning each time.
else
i := 0
end
n := s.count
until
i >= n or not Result
loop
i := i + 1
c := s.code (i)
if c <= 0x7F then
-- 0xxxxxxx
w := c
elseif c <= 0xC1 then
-- The octet values C0, C1, F5 to FF never appear.
--| case 0xC0 and 0xC1
Result := False
elseif (c & 0xE0) = 0xC0 then
-- 110xxxxx 10xxxxxx
i := i + 1
if i <= n then
c2 := s.code (i)
if
(c2 & 0xC0) = 0x80
then
w := ((c & 0x1F) |<< 6)
| (c2 & 0x3F)
Result := 0x80 <= w and w <= 0x7FF
else
Result := False
end
else
l_is_incomplete_stream := True
end
elseif (c & 0xF0) = 0xE0 then
-- 1110xxxx 10xxxxxx 10xxxxxx
i := i + 2
if i <= n then
c2 := s.code (i - 1)
c3 := s.code (i)
if
(c2 & 0xC0) = 0x80 and
(c3 & 0xC0) = 0x80
then
w := ((c & 0xF) |<< 12)
| ((c2 & 0x3F) |<< 6)
| (c3 & 0x3F)
if 0x800 <= w and w <= 0xFFFF then
if 0xD800 <= w and w <= 0xDFFF then
-- The definition of UTF-8 prohibits encoding character numbers between U+D800 and U+DFFF
Result := False
end
else
Result := False
end
else
Result := False
end
else
if i - 1 <= n then
Result := (s.code (i - 1) & 0xC0) = 0x80
end
l_is_incomplete_stream := True
end
elseif (c & 0xF8) = 0xF0 then -- 0001 0000-0010 FFFF
-- 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
if 0xF5 <= c and c <= 0xFF then
-- The octet values C0, C1, F5 to FF never appear.
Result := False
else
i := i + 3
if i <= n then
c2 := s.code (i - 2)
c3 := s.code (i - 1)
c4 := s.code (i)
if
(c2 & 0xC0) = 0x80 and
(c3 & 0xC0) = 0x80 and
(c4 & 0xC0) = 0x80
then
w := ((c & 0x7) |<< 18) |
((c2 & 0x3F) |<< 12) |
((c3 & 0x3F) |<< 6) |
(c4 & 0x3F)
Result := 0x1_0000 <= w and w <= 0x10_FFFF
else
Result := False
end
else
if i - 2 <= n then
c2 := s.code (i - 2)
Result := (c2 & 0xC0) = 0x80
if Result then
if c = 0xF4 and c2 >= 0x90 then
--| any byte 10xxxxxx (i.e >= 0x80) that would come after,
-- will result in out of range code point
-- indeed 0xF4 0x90 0x80 0x80 = 0x1100 0000 > 0x10_FFFF
Result := False
elseif i - 1 <= n then
Result := (s.code (i - 1) & 0xC0) = 0x80
end
end
end
l_is_incomplete_stream := True
end
end
else
-- Invalid byte in UTF-8
Result := False
end
if Result then
if l_is_incomplete_stream then
Result := a_is_stream
elseif a_is_stream then
last_utf_8_stream_validation_position := i
end
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,8 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-15-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-15-0 http://www.eiffel.com/developers/xml/configuration-1-15-0.xsd" name="protocol" uuid="97582991-7BF3-4F4D-8944-8141ADE34274" library_target="protocol">
<target name="protocol">
<root all_classes="true"/>
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
<cluster name="src" location=".\" recursive="true"/>
</target>
</system>

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-15-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-15-0 http://www.eiffel.com/developers/xml/configuration-1-15-0.xsd" name="protocol" uuid="97582991-7BF3-4F4D-8944-8141ADE34274" library_target="protocol">
<target name="protocol">
<root all_classes="true"/>
<option void_safety="none">
</option>
<library name="base" location="$ISE_LIBRARY\library\base\base.ecf"/>
<cluster name="src" location=".\" recursive="true"/>
</target>
</system>

View File

@@ -0,0 +1,40 @@
note
description: "Websocket server."
date: "$Date$"
revision: "$Revision$"
class
APPLICATION
inherit
WEB_SOCKET_SERVICE [APPLICATION_EXECUTION]
redefine
set_options
end
create
make_and_launch
feature {NONE} -- Initialization
set_options (opts: WEB_SOCKET_SERVICE_OPTIONS)
do
opts.set_is_verbose (True) -- For debug purpose
opts.set_verbose_level ("debug")
opts.set_is_secure (True) -- If SSL is supported
opts.set_secure_certificate ("ca.crt") -- Change to use your own crt file.
opts.set_secure_certificate_key ("ca.key") -- Change to use your own key file.
opts.set_port (default_port_number)
end
feature -- Access
default_port_number: INTEGER = 9090
note
copyright: "2011-2016, Javier Velilla, Jocelyn Fiat and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -0,0 +1,173 @@
note
description: "[
Implementation of {WEB_SOCKET_EXECUTION} specific for this application.
]"
date: "$Date$"
revision: "$Revision$"
class
APPLICATION_EXECUTION
inherit
WEB_SOCKET_EXECUTION
create
make
feature -- Request processing
is_verbose: BOOLEAN = True
execute
-- Process request ...
local
s: STRING
do
if
request.is_get_request_method and then
request.request_uri.is_case_insensitive_equal_general ("/app")
then
s := websocket_app_html ({APPLICATION}.default_port_number)
else
s := "Open the websocket client example: <a href=%"/app%">click here</a>."
end
response.header.put_content_type_text_html
response.header.put_content_length (s.count)
response.put_string (s)
end
on_open (ws: WEB_SOCKET)
do
if is_verbose then
log ("Connecting", {HTTPD_LOGGER_CONSTANTS}.debug_level)
end
end
on_binary (ws: WEB_SOCKET; a_message: READABLE_STRING_8)
do
ws.send (Binary_frame, a_message)
end
on_text (ws: WEB_SOCKET; a_message: READABLE_STRING_8)
do
-- if is_verbose then
-- log ("On text (message length=" + a_message.count.out + ")", {HTTPD_LOGGER_CONSTANTS}.debug_level)
-- end
ws.send (Text_frame, a_message)
end
on_close (ws: WEB_SOCKET)
-- Called after the WebSocket connection is closed.
do
if is_verbose then
log ("Connection closed", {HTTPD_LOGGER_CONSTANTS}.debug_level)
end
end
log (m: READABLE_STRING_8; lev: INTEGER)
do
response.put_error (m)
end
websocket_app_html (a_port: INTEGER): STRING
do
Result := "[
<!DOCTYPE html>
<html>
<head>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
<script type="text/javascript">
$(document).ready(function() {
var socket;
function connect(){
var host = "ws://127.0.0.1:##PORTNUMBER##";
try{
socket = new WebSocket(host);
message('<p class="event">Socket Status: '+socket.readyState);
socket.onopen = function(){
message('<p class="event">Socket Status: '+socket.readyState+' (open)');
}
socket.onmessage = function(msg){
message('<p class="message">Received: '+msg.data);
}
socket.onclose = function(){
message('<p class="event">Socket Status: '+socket.readyState+' (Closed)');
}
} catch(exception){
message('<p>Error'+exception);
}
}
function send(){
var text = $('#text').val();
if(text==""){
message('<p class="warning">Please enter a message');
return ;
}
try{
socket.send(text);
message('<p class="event">Sent: '+text)
} catch(exception){
message('<p class="warning">');
}
$('#text').val("");
}
function message(msg){
$('#chatLog').append(msg+'</p>');
}//End message()
$('#text').keypress(function(event) {
if (event.keyCode == '13') {
send();
}
});
$('#disconnect').click(function(){
socket.close();
});
if (!("WebSocket" in window)){
$('#chatLog, input, button, #examples').fadeOut("fast");
$('<p>Oh no, you need a browser that supports WebSockets. How about <a href="http://www.google.com/chrome">Google Chrome</a>?</p>').appendTo('#container');
}else{
//The user has WebSockets
connect();
}
});
</script>
<meta charset="utf-8" />
<style type="text/css">
body {font-family:Arial, Helvetica, sans-serif;}
#container { border:5px solid grey; width:800px; margin:0 auto; padding:10px; }
#chatLog { padding:5px; border:1px solid black; }
#chatLog p {margin:0;}
.event {color:#999;}
.warning { font-weight:bold; color:#CCC; }
</style>
<title>WebSockets Client</title>
</head>
<body>
<div id="wrapper">
<div id="container">
<h1>WebSockets Client</h1>
<div id="chatLog"></div>
<input id="text" type="text" />
<button id="disconnect">Disconnect</button>
</div>
</div>
</body>
</html>
]"
Result.replace_substring_all ("##PORTNUMBER##", a_port.out)
end
note
copyright: "2011-2016, Javier Velilla, Jocelyn Fiat and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -0,0 +1,15 @@
-----BEGIN CERTIFICATE-----
MIICWDCCAcGgAwIBAgIJAJnXGtV+PtiYMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV
BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX
aWRnaXRzIFB0eSBMdGQwHhcNMTUwNDAzMjIxNTA0WhcNMTYwNDAyMjIxNTA0WjBF
MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50
ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB
gQDFMK6ojzg+KlklhTossR13c51izMgGc3B0z9ttfHIcx2kxra3HtHcKIl5wSUvn
G8zmSyFAyQTs5LUv65q46FM9qU8tP+vTeFCfNXvjRcIEpouta3J53K0xuUlxz4d4
4D6qvdDWAez/0AkI4y5etW5zXtg7IQorJhsI9TmfGuruzwIDAQABo1AwTjAdBgNV
HQ4EFgQUbWpk2HoHa0YqpEwr7CGEatBFTMkwHwYDVR0jBBgwFoAUbWpk2HoHa0Yq
pEwr7CGEatBFTMkwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOBgQAi+h4/
IgEocWkdRZBKHEcTrRxz5WhEDJMoVo9LhnXvCfn1G/4p6Un6sYv7Xzpi9NuSY8uV
cjfJJXhtF3AtyZ70iTAxWaRWjGaZ03PYOjlledJ5rqJEt6CCn8m+JsfznduZvbxQ
zQ6jCLXfyD/tvemB+yYEI3NntvRKx5/zt6Q26Q==
-----END CERTIFICATE-----

View File

@@ -0,0 +1,15 @@
-----BEGIN RSA PRIVATE KEY-----
MIICXAIBAAKBgQDFMK6ojzg+KlklhTossR13c51izMgGc3B0z9ttfHIcx2kxra3H
tHcKIl5wSUvnG8zmSyFAyQTs5LUv65q46FM9qU8tP+vTeFCfNXvjRcIEpouta3J5
3K0xuUlxz4d44D6qvdDWAez/0AkI4y5etW5zXtg7IQorJhsI9TmfGuruzwIDAQAB
AoGAR5efMg+dieRyLU8rieJcImxVbfOPg9gRsjdtIVkXTR+RL7ow59q7hXBo/Td/
WU8cm1gXoJ/bK+71YYqWyB+BaLRIWvRWb7Gdw203tu4e136Ca5uuY+71qdbVTVcl
NQ7J+T+eAQFP+a+DdT3ZQxu9eze87SMbu6i5YSpIk2kusOECQQDunv/DQ+nc+NgR
DF+Td3sNYUVRT9a1CWi6abAG6reXwp8MS4NobWDf+Ps4JODhEEwlIdq5qL7qqYBZ
Gc1TJJ53AkEA0404Fn6vAzzegBcS4RLlYTK7nMr0m4pMmDMCI6YzAYdMmKHp1e6f
IwxSmQrmwyAgwcT01bc0+A8yipcC2BWQaQJBAJ01QZm635OGmos41KsKF5bsE8gL
SpBBH69Yu/ECqGwie7iU84FUNnO4zIHjwghlPVVlZX3Vz9o4S+fn2N9DC+cCQGyZ
QyCxGdC0r5fbwHJQS/ZQn+UGfvlVzqoXDVMVn3t6ZES6YZrT61eHnOM5qGqklIxE
Old3vDZXPt/MU8Zvk3kCQBOgUx2VxvTrHN37hk9/QIDiM62+RenBm1M3ah8xTosf
1mSeEb6d9Kwb3TgPBmA7YXzJuAQfRIvEPMPxT5SSr6Q=
-----END RSA PRIVATE KEY-----

View File

@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-15-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-15-0 http://www.eiffel.com/developers/xml/configuration-1-15-0.xsd" name="echo_websocket_server" uuid="C9B3DA5F-DF0D-4C0F-924A-130B5C1E6604">
<target name="common">
<file_rule>
<exclude>/.git$</exclude>
<exclude>/.svn$</exclude>
<exclude>/CVS$</exclude>
<exclude>/EIFGENs$</exclude>
</file_rule>
<option debug="true" warning="true" full_class_checking="false" is_attached_by_default="true" is_obsolete_routine_type="true" void_safety="all" syntax="transitional">
<debug name="ws" enabled="true"/>
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
</option>
<setting name="console_application" value="true"/>
<setting name="concurrency" value="scoop"/>
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf">
<option>
<assertions precondition="true"/>
</option>
</library>
<library name="http_network" location="..\..\..\..\http_network\http_network-safe.ecf" readonly="false"/>
<library name="httpd" location="..\..\..\..\..\server\httpd\httpd-safe.ecf" readonly="false"/>
<library name="standalone_websocket_connector" location="..\..\..\..\..\server\wsf\connector\standalone_websocket-safe.ecf" readonly="false"/>
<library name="websocket_server" location="..\..\websocket_server-safe.ecf" readonly="false">
<option debug="true">
<debug name="ws" enabled="true"/>
</option>
</library>
<cluster name="src" location=".\" recursive="true">
</cluster>
</target>
<target name="echo_websocket_server_mt" extends="common">
<root class="APPLICATION" feature="make_and_launch"/>
<setting name="concurrency" value="thread"/>
</target>
<target name="echo_websocket_server_mt_ssl" extends="echo_websocket_server_mt">
<variable name="ssl_enabled" value="true"/>
</target>
<target name="echo_websocket_server_scoop" extends="common">
<root class="APPLICATION" feature="make_and_launch"/>
<setting name="concurrency" value="scoop"/>
</target>
<target name="echo_websocket_server_scoop_ssl" extends="echo_websocket_server_scoop">
<variable name="ssl_enabled" value="true"/>
</target>
</system>

View File

@@ -0,0 +1,4 @@
${NOTE_KEYWORD}
copyright: "2011-${YEAR}, Javier Velilla, Jocelyn Fiat and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"

View File

@@ -0,0 +1,116 @@
<!DOCTYPE html>
<html>
<head>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
<script type="text/javascript">
$(document).ready(function() {
if(!("WebSocket" in window)){
$('#chatLog, input, button, #examples').fadeOut("fast");
$('<p>Oh no, you need a browser that supports WebSockets. How about <a href="http://www.google.com/chrome">Google Chrome</a>?</p>').appendTo('#container');
}else{
//The user has WebSockets
connect();
function connect(){
var socket;
var host = "ws://127.0.0.1:9090";
try{
var socket = new WebSocket(host);
message('<p class="event">Socket Status: '+socket.readyState);
socket.onopen = function(){
message('<p class="event">Socket Status: '+socket.readyState+' (open)');
}
socket.onmessage = function(msg){
message('<p class="message">Received: '+msg.data);
}
socket.onclose = function(){
message('<p class="event">Socket Status: '+socket.readyState+' (Closed)');
}
} catch(exception){
message('<p>Error'+exception);
}
function send(){
var text = $('#text').val();
if(text==""){
message('<p class="warning">Please enter a message');
return ;
}
try{
socket.send(text);
message('<p class="event">Sent: '+text)
} catch(exception){
message('<p class="warning">');
}
$('#text').val("");
}
function message(msg){
$('#chatLog').append(msg+'</p>');
}//End message()
$('#text').keypress(function(event) {
if (event.keyCode == '13') {
send();
}
});
$('#disconnect').click(function(){
socket.close();
});
}
}//End connect()
});
</script>
<meta charset=utf-8 />
<style type="text/css">
body{font-family:Arial, Helvetica, sans-serif;}
#container{
border:5px solid grey;
width:800px;
margin:0 auto;
padding:10px;
}
#chatLog{
padding:5px;
border:1px solid black;
}
#chatLog p{margin:0;}
.event{color:#999;}
.warning{
font-weight:bold;
color:#CCC;
}
</style>
<title>WebSockets Client</title>
</head>
<body>
<div id="wrapper">
<div id="container">
<h1>WebSockets Client</h1>
<div id="chatLog">
</div>
<input id="text" type="text" />
<button id="disconnect">Disconnect</button>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,53 @@
<!DOCTYPE html>
<title>WebSocket Image Drop</title>
<h1>Drop Image Here</h1>
<script>
// Initialize WebSocket connection
var wsUrl = "ws://127.0.0.1:9090";
var ws = new WebSocket(wsUrl);
ws.onopen = function() {
console.log("open");
}
// Handle binary image data received on the WebSocket
ws.onmessage = function(e) {
var blob = e.data;
console.log("message: " + blob.size + " bytes");
// Work with prefixed URL API
if (window.webkitURL) {
URL = webkitURL;
}
var uri = URL.createObjectURL(blob);
var img = document.createElement("img");
img.src = uri;
document.body.appendChild(img);
}
// Handle drop event
document.ondrop = function(e) {
document.body.style.backgroundColor = "#fff";
try {
e.preventDefault();
handleFileDrop(e.dataTransfer.files[0]);
return false;
} catch(err) {
console.log(err);
}
}
// Provide visual feedback for the drop area
document.ondragover = function(e) {
e.preventDefault();
document.body.style.backgroundColor = "#6fff41";
}
document.ondragleave = function() {
document.body.style.backgroundColor = "#fff";
}
// Read binary file contents and send them over WebSocket
function handleFileDrop(file) {
var reader = new FileReader();
reader.readAsArrayBuffer(file);
reader.onload = function() {
console.log("sending: " + file.name);
ws.send(reader.result);
}
}
</script>

View File

@@ -0,0 +1,21 @@
note
description: "Summary description for {WEB_SOCKET_EXECUTION}."
date: "$Date$"
revision: "$Revision$"
deferred class
WEB_SOCKET_EXECUTION
inherit
WSF_WEBSOCKET_EXECUTION
WEB_SOCKET_EVENT_I
feature -- Websocket execution
new_websocket_handler (ws: WEB_SOCKET): WEB_SOCKET_HANDLER
do
create Result.make (ws, Current)
end
end

View File

@@ -0,0 +1,30 @@
note
description: "Summary description for {WEB_SOCKET_SERVICE}."
date: "$Date$"
revision: "$Revision$"
class
WEB_SOCKET_SERVICE [G -> WEB_SOCKET_EXECUTION create make end]
create
make_and_launch
feature {NONE} -- Initialization
make_and_launch
local
l_launcher: WSF_STANDALONE_WEBSOCKET_SERVICE_LAUNCHER [G]
opts: WEB_SOCKET_SERVICE_OPTIONS
do
create opts
set_options (opts)
opts.append_options (create {WSF_SERVICE_LAUNCHER_OPTIONS_FROM_INI}.make_from_file ("ws.ini"))
create l_launcher.make_and_launch (opts)
end
set_options (opts: WEB_SOCKET_SERVICE_OPTIONS)
-- Set values on `opts'.
do
end
end

View File

@@ -0,0 +1,12 @@
note
description: "Summary description for {WEB_SOCKET_SERVICE_OPTIONS}."
date: "$Date$"
revision: "$Revision$"
class
WEB_SOCKET_SERVICE_OPTIONS
inherit
WSF_STANDALONE_WEBSOCKET_SERVICE_OPTIONS
end

View File

@@ -0,0 +1,30 @@
<?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="websocket_server" uuid="FDD2276B-7149-4490-9E09-46AFEA122D1E" library_target="websocket_server">
<target name="websocket_server">
<root all_classes="true"/>
<file_rule>
<exclude>/.git$</exclude>
<exclude>/EIFGENs$</exclude>
<exclude>/CVS$</exclude>
<exclude>/.svn$</exclude>
</file_rule>
<option warning="true" is_attached_by_default="true" void_safety="all" syntax="standard">
<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="..\..\..\text\encoder\encoder-safe.ecf"/>
<library name="standalone_websocket_connector" location="..\..\..\server\wsf\connector\standalone_websocket-safe.ecf"/>
<library name="wsf" location="..\..\..\server\wsf\wsf-safe.ecf"/>
<library name="net" location="$ISE_LIBRARY\library\net\net-safe.ecf"/>
<library name="thread" location="$ISE_LIBRARY\library\thread\thread-safe.ecf">
<condition>
<concurrency excluded_value="none"/>
</condition>
</library>
<library name="time" location="$ISE_LIBRARY\library\time\time-safe.ecf"/>
<cluster name="websocket_server" location=".\src\" recursive="true"/>
</target>
</system>

View File

@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-11-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-11-0 http://www.eiffel.com/developers/xml/configuration-1-11-0.xsd" name="websocket_server" uuid="FDD2276B-7149-4490-9E09-46AFEA122D1E" library_target="websocket_server">
<target name="websocket_server">
<root all_classes="true"/>
<file_rule>
<exclude>/.git$</exclude>
<exclude>/EIFGENs$</exclude>
<exclude>/CVS$</exclude>
<exclude>/.svn$</exclude>
</file_rule>
<option warning="true" is_attached_by_default="false" void_safety="none" syntax="standard">
<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="standalone_websocket_connector" location="..\..\..\server\wsf\connector\standalone_websocket.ecf"/>
<library name="wsf" location="..\..\..\server\wsf\wsf.ecf"/>
<library name="net" location="$ISE_LIBRARY\library\net\net.ecf"/>
<library name="thread" location="$ISE_LIBRARY\library\thread\thread.ecf">
<condition>
<concurrency excluded_value="none"/>
</condition>
</library>
<library name="time" location="$ISE_LIBRARY\library\time\time.ecf"/>
<cluster name="websocket_server" location=".\src\" recursive="true"/>
</target>
</system>