Added WSF standalone_websocket connector, that provides websocket on top of standalone connector.
This commit is contained in:
@@ -0,0 +1,24 @@
|
|||||||
|
<?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="connector_standalone_websocket" uuid="38E6F413-E001-4774-9B4C-E0C08753D9F7" library_target="connector_standalone_websocket">
|
||||||
|
<target name="connector_standalone_websocket">
|
||||||
|
<root all_classes="true"/>
|
||||||
|
<file_rule>
|
||||||
|
<exclude>/EIFGENs$</exclude>
|
||||||
|
<exclude>/\.git$</exclude>
|
||||||
|
<exclude>/\.svn$</exclude>
|
||||||
|
</file_rule>
|
||||||
|
<option debug="true" warning="true" full_class_checking="true" is_attached_by_default="true" void_safety="all" syntax="transitional">
|
||||||
|
<debug name="dbglog" enabled="true"/>
|
||||||
|
<assertions precondition="true"/>
|
||||||
|
</option>
|
||||||
|
<setting name="concurrency" value="scoop"/>
|
||||||
|
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
|
||||||
|
<library name="encoder" location="..\..\..\..\text\encoder\encoder-safe.ecf"/>
|
||||||
|
<library name="ewsgi" location="..\..\ewsgi-safe.ecf" readonly="false"/>
|
||||||
|
<library name="http" location="..\..\..\..\network\protocol\http\http-safe.ecf"/>
|
||||||
|
<library name="httpd" location="lib\httpd\httpd-safe.ecf" readonly="false"/>
|
||||||
|
<cluster name="src" location=".\src\">
|
||||||
|
<cluster name="implementation" location="$|implementation\" hidden="true"/>
|
||||||
|
</cluster>
|
||||||
|
</target>
|
||||||
|
</system>
|
||||||
25
library/server/wsf/connector/standalone_websocket-safe.ecf
Normal file
25
library/server/wsf/connector/standalone_websocket-safe.ecf
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<?xml version="1.0" encoding="ISO-8859-1"?>
|
||||||
|
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-12-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-12-0 http://www.eiffel.com/developers/xml/configuration-1-12-0.xsd" name="wsf_standalone_websocket" uuid="7C83D4B4-39C9-4D27-941B-0F0AAD45122E" library_target="wsf_standalone_websocket">
|
||||||
|
<target name="wsf_standalone_websocket">
|
||||||
|
<root all_classes="true"/>
|
||||||
|
<file_rule>
|
||||||
|
<exclude>/EIFGENs$</exclude>
|
||||||
|
<exclude>/\.git$</exclude>
|
||||||
|
<exclude>/\.svn$</exclude>
|
||||||
|
</file_rule>
|
||||||
|
<option warning="true" full_class_checking="true" is_attached_by_default="true" void_safety="all" syntax="provisional">
|
||||||
|
</option>
|
||||||
|
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
|
||||||
|
<library name="connector_standalone" location="..\..\ewsgi\connectors\standalone\standalone-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" readonly="false"/>
|
||||||
|
<library name="error" location="..\..\..\utility\general\error\error-safe.ecf"/>
|
||||||
|
<library name="ewsgi" location="..\..\ewsgi\ewsgi-safe.ecf"/>
|
||||||
|
<library name="http" location="..\..\..\network\protocol\http\http-safe.ecf"/>
|
||||||
|
<library name="httpd" location="..\..\ewsgi\connectors\standalone\lib\httpd\httpd-safe.ecf"/>
|
||||||
|
<library name="time" location="$ISE_LIBRARY\library\time\time-safe.ecf"/>
|
||||||
|
<library name="wsf" location="..\wsf-safe.ecf"/>
|
||||||
|
<library name="wsf_standalone" location="standalone-safe.ecf"/>
|
||||||
|
<cluster name="wsf_standalone_websocket" location=".\standalone_websocket\" recursive="true"/>
|
||||||
|
</target>
|
||||||
|
</system>
|
||||||
@@ -0,0 +1,183 @@
|
|||||||
|
note
|
||||||
|
description: "[
|
||||||
|
API to perform actions like opening and closing the connection, sending and receiving messages, and listening
|
||||||
|
for events.
|
||||||
|
]"
|
||||||
|
date: "$Date$"
|
||||||
|
revision: "$Revision$"
|
||||||
|
|
||||||
|
deferred class
|
||||||
|
WEB_SOCKET_EVENT_I
|
||||||
|
|
||||||
|
inherit
|
||||||
|
WEB_SOCKET_CONSTANTS
|
||||||
|
|
||||||
|
REFACTORING_HELPER
|
||||||
|
|
||||||
|
feature -- Web Socket Interface
|
||||||
|
|
||||||
|
on_event (conn: HTTPD_STREAM_SOCKET; a_message: detachable READABLE_STRING_8; a_opcode: INTEGER)
|
||||||
|
-- Called when a frame from the client has been receive
|
||||||
|
require
|
||||||
|
conn_attached: conn /= Void
|
||||||
|
conn_valid: conn.is_open_read and then conn.is_open_write
|
||||||
|
local
|
||||||
|
l_message: READABLE_STRING_8
|
||||||
|
do
|
||||||
|
debug ("ws")
|
||||||
|
print ("%Non_event (conn, a_message, " + opcode_name (a_opcode) + ")%N")
|
||||||
|
end
|
||||||
|
if a_message = Void then
|
||||||
|
create {STRING} l_message.make_empty
|
||||||
|
else
|
||||||
|
l_message := a_message
|
||||||
|
end
|
||||||
|
|
||||||
|
if a_opcode = Binary_frame then
|
||||||
|
on_binary (conn, l_message)
|
||||||
|
elseif a_opcode = Text_frame then
|
||||||
|
on_text (conn, l_message)
|
||||||
|
elseif a_opcode = Pong_frame then
|
||||||
|
on_pong (conn, l_message)
|
||||||
|
elseif a_opcode = Ping_frame then
|
||||||
|
on_ping (conn, l_message)
|
||||||
|
elseif a_opcode = Connection_close_frame then
|
||||||
|
on_connection_close (conn, "")
|
||||||
|
else
|
||||||
|
on_unsupported (conn, l_message, a_opcode)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
on_open (conn: HTTPD_STREAM_SOCKET)
|
||||||
|
-- Called after handshake, indicates that a complete WebSocket connection has been established.
|
||||||
|
require
|
||||||
|
conn_attached: conn /= Void
|
||||||
|
conn_valid: conn.is_open_read and then conn.is_open_write
|
||||||
|
deferred
|
||||||
|
end
|
||||||
|
|
||||||
|
on_binary (conn: HTTPD_STREAM_SOCKET; a_message: READABLE_STRING_8)
|
||||||
|
require
|
||||||
|
conn_attached: conn /= Void
|
||||||
|
conn_valid: conn.is_open_read and then conn.is_open_write
|
||||||
|
deferred
|
||||||
|
end
|
||||||
|
|
||||||
|
on_pong (conn: HTTPD_STREAM_SOCKET; a_message: READABLE_STRING_8)
|
||||||
|
require
|
||||||
|
conn_attached: conn /= Void
|
||||||
|
conn_valid: conn.is_open_read and then conn.is_open_write
|
||||||
|
do
|
||||||
|
-- log ("Its a pong frame")
|
||||||
|
-- at first we ignore pong
|
||||||
|
-- FIXME: provide better explanation
|
||||||
|
end
|
||||||
|
|
||||||
|
on_ping (conn: HTTPD_STREAM_SOCKET; a_message: READABLE_STRING_8)
|
||||||
|
require
|
||||||
|
conn_attached: conn /= Void
|
||||||
|
conn_valid: conn.is_open_read and then conn.is_open_write
|
||||||
|
do
|
||||||
|
send (conn, Pong_frame, a_message)
|
||||||
|
end
|
||||||
|
|
||||||
|
on_text (conn: HTTPD_STREAM_SOCKET; a_message: READABLE_STRING_8)
|
||||||
|
require
|
||||||
|
conn_attached: conn /= Void
|
||||||
|
conn_valid: conn.is_open_read and then conn.is_open_write
|
||||||
|
deferred
|
||||||
|
end
|
||||||
|
|
||||||
|
on_unsupported (conn: HTTPD_STREAM_SOCKET; a_message: READABLE_STRING_8; a_opcode: INTEGER)
|
||||||
|
require
|
||||||
|
conn_attached: conn /= Void
|
||||||
|
conn_valid: conn.is_open_read and then conn.is_open_write
|
||||||
|
do
|
||||||
|
-- do nothing
|
||||||
|
end
|
||||||
|
|
||||||
|
on_connection_close (conn: HTTPD_STREAM_SOCKET; a_message: detachable READABLE_STRING_8)
|
||||||
|
require
|
||||||
|
conn_attached: conn /= Void
|
||||||
|
conn_valid: conn.is_open_read and then conn.is_open_write
|
||||||
|
do
|
||||||
|
send (conn, Connection_close_frame, "")
|
||||||
|
end
|
||||||
|
|
||||||
|
on_close (conn: detachable HTTPD_STREAM_SOCKET)
|
||||||
|
-- Called after the WebSocket connection is closed.
|
||||||
|
deferred
|
||||||
|
end
|
||||||
|
|
||||||
|
feature {NONE} -- Implementation
|
||||||
|
|
||||||
|
send (conn: HTTPD_STREAM_SOCKET; 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
|
||||||
|
do
|
||||||
|
print (">>do_send (..., "+ opcode_name (a_opcode) +", ..)%N")
|
||||||
|
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 ((0 | 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 ((0 | 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 (n.as_natural_32)
|
||||||
|
end
|
||||||
|
conn.put_string (l_header_message)
|
||||||
|
|
||||||
|
|
||||||
|
l_chunk_size := 16_384 -- 16K
|
||||||
|
if l_message_count < l_chunk_size then
|
||||||
|
conn.put_string (a_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 := a_message.substring (i + 1, l_message_count.min (i + l_chunk_size))
|
||||||
|
conn.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
|
||||||
|
|
||||||
|
end
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
note
|
||||||
|
description: "[
|
||||||
|
A web socket message has an opcode specifying the type of the message payload. The
|
||||||
|
opcode consists of the last four bits in the first byte of the frame header.
|
||||||
|
]"
|
||||||
|
date: "$Date$"
|
||||||
|
revision: "$Revision$"
|
||||||
|
EIS: "name=Data Frame", "src=http://tools.ietf.org/html/rfc6455#section-5.6", "protocol=uri"
|
||||||
|
EIS: "name=Control Frame", "src=http://tools.ietf.org/html/rfc6455#section-5.5", "protocol=uri"
|
||||||
|
|
||||||
|
class
|
||||||
|
WEB_SOCKET_MESSAGE_TYPE
|
||||||
|
|
||||||
|
feature -- Data Frames
|
||||||
|
|
||||||
|
Text: INTEGER = 0x1
|
||||||
|
-- The data type of the message is text.
|
||||||
|
|
||||||
|
Binary: INTEGER = 0x2
|
||||||
|
-- The data type of the message is binary.
|
||||||
|
|
||||||
|
feature -- Control Frames
|
||||||
|
|
||||||
|
Close: INTEGER = 0x8
|
||||||
|
-- The client or server is sending a closing
|
||||||
|
-- handshake to the server or client.
|
||||||
|
|
||||||
|
Ping: INTEGER = 0x9
|
||||||
|
-- The client or server sends a ping to the server or client.
|
||||||
|
|
||||||
|
Pong: INTEGER = 0xA
|
||||||
|
-- The client or server sends a pong to the server or client.
|
||||||
|
|
||||||
|
feature -- Reserverd
|
||||||
|
|
||||||
|
-- Opcodes 0x3-0x7 are reserved for further non-control frames yet to be
|
||||||
|
-- defined.
|
||||||
|
|
||||||
|
end
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -0,0 +1,437 @@
|
|||||||
|
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?
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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
|
||||||
|
end
|
||||||
@@ -0,0 +1,754 @@
|
|||||||
|
note
|
||||||
|
description: "Summary description for {WEB_SOCKET}."
|
||||||
|
author: ""
|
||||||
|
date: "$Date$"
|
||||||
|
revision: "$Revision$"
|
||||||
|
|
||||||
|
class
|
||||||
|
WEB_SOCKET
|
||||||
|
|
||||||
|
inherit
|
||||||
|
WGI_STANDALONE_CONNECTOR_ACCESS
|
||||||
|
|
||||||
|
HTTPD_LOGGER_CONSTANTS
|
||||||
|
|
||||||
|
WEB_SOCKET_CONSTANTS
|
||||||
|
|
||||||
|
SHARED_BASE64
|
||||||
|
|
||||||
|
create
|
||||||
|
make
|
||||||
|
|
||||||
|
feature {NONE} -- Initialization
|
||||||
|
|
||||||
|
make (req: WSF_REQUEST; res: WSF_RESPONSE)
|
||||||
|
do
|
||||||
|
request := req
|
||||||
|
response := res
|
||||||
|
is_verbose := True
|
||||||
|
|
||||||
|
if
|
||||||
|
attached {WGI_STANDALONE_INPUT_STREAM} req.input as r_input
|
||||||
|
then
|
||||||
|
socket := r_input.source
|
||||||
|
else
|
||||||
|
create socket.make_empty
|
||||||
|
check has_socket: False end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
feature -- Access
|
||||||
|
|
||||||
|
socket: HTTPD_STREAM_SOCKET
|
||||||
|
|
||||||
|
request: WSF_REQUEST
|
||||||
|
|
||||||
|
response: WSF_RESPONSE
|
||||||
|
|
||||||
|
feature -- Access
|
||||||
|
|
||||||
|
is_websocket: BOOLEAN
|
||||||
|
|
||||||
|
has_error: BOOLEAN
|
||||||
|
|
||||||
|
is_verbose: BOOLEAN
|
||||||
|
|
||||||
|
socket_is_ready_for_reading: BOOLEAN
|
||||||
|
do
|
||||||
|
Result := socket.ready_for_reading
|
||||||
|
end
|
||||||
|
|
||||||
|
feature -- Element change
|
||||||
|
|
||||||
|
log (m: READABLE_STRING_8; lev: INTEGER)
|
||||||
|
do
|
||||||
|
if is_verbose then
|
||||||
|
response.put_error (m)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
feature -- Basic Operation
|
||||||
|
|
||||||
|
open_ws_handshake
|
||||||
|
-- The opening handshake is intended to be compatible with HTTP-based
|
||||||
|
-- server-side software and intermediaries, so that a single port can be
|
||||||
|
-- used by both HTTP clients alking to that server and WebSocket
|
||||||
|
-- clients talking to that server. To this end, the WebSocket client's
|
||||||
|
-- handshake is an HTTP Upgrade request:
|
||||||
|
|
||||||
|
-- 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
|
||||||
|
local
|
||||||
|
l_sha1: SHA1
|
||||||
|
l_key : STRING
|
||||||
|
l_handshake: STRING
|
||||||
|
req: like request
|
||||||
|
res: like response
|
||||||
|
do
|
||||||
|
req := request
|
||||||
|
res := response
|
||||||
|
is_websocket := False
|
||||||
|
has_error := False
|
||||||
|
|
||||||
|
-- Reading client's opening GT
|
||||||
|
|
||||||
|
-- TODO extract to a validator handshake or something like that.
|
||||||
|
if is_verbose then
|
||||||
|
log ("%NReceive <====================", debug_level)
|
||||||
|
if attached req.raw_header_data as rhd then
|
||||||
|
log (rhd, debug_level)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if
|
||||||
|
req.is_get_request_method and then -- MUST be GET request!
|
||||||
|
attached req.meta_string_variable ("HTTP_UPGRADE") as l_upgrade_key and then
|
||||||
|
l_upgrade_key.is_case_insensitive_equal_general ("websocket") -- Upgrade header must be present with value websocket
|
||||||
|
then
|
||||||
|
is_websocket := True
|
||||||
|
-- if
|
||||||
|
-- attached {WGI_STANDALONE_INPUT_STREAM} req.input as r_input and then
|
||||||
|
-- attached r_input.source as l_socket
|
||||||
|
-- then
|
||||||
|
-- l_socket.set_blocking
|
||||||
|
-- end
|
||||||
|
socket.set_blocking
|
||||||
|
if
|
||||||
|
attached req.meta_string_variable ("HTTP_SEC_WEBSOCKET_KEY") as l_ws_key and then -- Sec-websocket-key must be present
|
||||||
|
attached req.meta_string_variable ("HTTP_CONNECTION") as l_connection_key and then -- Connection header must be present with value Upgrade
|
||||||
|
l_connection_key.has_substring ("Upgrade") and then
|
||||||
|
attached req.meta_string_variable ("HTTP_SEC_WEBSOCKET_VERSION") as l_version_key and then -- Version header must be present with value 13
|
||||||
|
l_version_key.is_case_insensitive_equal ("13") and then
|
||||||
|
attached req.http_host -- Host header must be present
|
||||||
|
then
|
||||||
|
if is_verbose then
|
||||||
|
log ("key " + l_ws_key, debug_level)
|
||||||
|
end
|
||||||
|
-- Sending the server's opening handshake
|
||||||
|
create l_sha1.make
|
||||||
|
l_sha1.update_from_string (l_ws_key + magic_guid)
|
||||||
|
l_key := Base64_encoder.encoded_string (digest (l_sha1))
|
||||||
|
-- create l_handshake.make_from_string ("") --HTTP/1.1 101 Switching Protocols%R%N")
|
||||||
|
create l_handshake.make_from_string ("HTTP/1.1 101 Switching Protocols%R%N")
|
||||||
|
l_handshake.append_string ("Upgrade: websocket%R%N")
|
||||||
|
l_handshake.append_string ("Connection: Upgrade%R%N")
|
||||||
|
l_handshake.append_string ("Sec-WebSocket-Accept: ")
|
||||||
|
l_handshake.append_string (l_key)
|
||||||
|
l_handshake.append_string ("%R%N")
|
||||||
|
-- end of header empty line
|
||||||
|
--not with WSF_RESPONSE l_handshake.append_string ("%R%N")
|
||||||
|
l_handshake.append_string ("%R%N")
|
||||||
|
if is_verbose then
|
||||||
|
log ("%N================> Send", debug_level)
|
||||||
|
log (l_handshake, debug_level)
|
||||||
|
end
|
||||||
|
socket.put_string (l_handshake)
|
||||||
|
-- res.set_status_code_with_reason_phrase (101, "Switching Protocols")
|
||||||
|
-- res.put_header_text (l_handshake)
|
||||||
|
else
|
||||||
|
has_error := True
|
||||||
|
if is_verbose then
|
||||||
|
log ("Error (opening_handshake)!!!", debug_level)
|
||||||
|
end
|
||||||
|
-- If we cannot complete the handshake, then the server MUST stop processing the client's handshake and return an HTTP response with an
|
||||||
|
-- appropriate error code (such as 400 Bad Request).
|
||||||
|
res.set_status_code_with_reason_phrase (400, "Bad Request")
|
||||||
|
-- a_socket.put_string ("HTTP/1.1 400 Bad Request%N")
|
||||||
|
end
|
||||||
|
else
|
||||||
|
is_websocket := False
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
feature -- Response!
|
||||||
|
|
||||||
|
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
|
||||||
|
do
|
||||||
|
print (">>do_send (..., "+ opcode_name (a_opcode) +", ..)%N")
|
||||||
|
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 ((0 | 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 ((0 | 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 (n.as_natural_32)
|
||||||
|
end
|
||||||
|
socket.put_string (l_header_message)
|
||||||
|
|
||||||
|
l_chunk_size := 16_384 -- 16K
|
||||||
|
if l_message_count < l_chunk_size then
|
||||||
|
socket.put_string (a_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 := a_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
|
||||||
|
|
||||||
|
next_frame: 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
|
||||||
|
socket_in_blocking_mode: socket.is_blocking
|
||||||
|
local
|
||||||
|
l_socket: like socket
|
||||||
|
l_opcode: INTEGER
|
||||||
|
l_len: INTEGER
|
||||||
|
l_remaining_len: INTEGER
|
||||||
|
l_payload_len: NATURAL_64
|
||||||
|
l_masking_key: detachable READABLE_STRING_8
|
||||||
|
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
|
||||||
|
-- l_input := request.input
|
||||||
|
l_socket := socket
|
||||||
|
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 (l_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 + ")", debug_level)
|
||||||
|
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 + ")", debug_level)
|
||||||
|
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 (l_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 (l_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 (l_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
|
||||||
|
l_masking_key := next_bytes (l_socket, 4) -- 32 bits
|
||||||
|
debug ("ws")
|
||||||
|
print (" Masking key bits=" + string_to_byte_representation (l_masking_key))
|
||||||
|
io.put_new_line
|
||||||
|
end
|
||||||
|
if l_masking_key.count < 4 then
|
||||||
|
debug ("ws")
|
||||||
|
print ("masking-key read stream -> " + l_socket.bytes_read.out + " bits%N")
|
||||||
|
end
|
||||||
|
Result.report_error (Invalid_data, "Incomplete data for Masking-key")
|
||||||
|
l_masking_key := Void
|
||||||
|
end
|
||||||
|
else
|
||||||
|
Result.report_error (protocol_error, "All frames sent from client to server are masked!")
|
||||||
|
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
|
||||||
|
-- l_input.read_string (l_chunk_size)
|
||||||
|
l_socket.read_stream (l_chunk_size)
|
||||||
|
-- l_bytes_read := l_input.last_string.count
|
||||||
|
l_bytes_read := l_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 l_bytes_read > 0 then
|
||||||
|
l_remaining_len := l_remaining_len - l_bytes_read
|
||||||
|
l_chunk := l_socket.last_string
|
||||||
|
if l_masking_key /= Void then
|
||||||
|
-- Masking
|
||||||
|
-- http://tools.ietf.org/html/rfc6455#section-5.3
|
||||||
|
unmask (l_chunk, l_fetch_count + 1, l_masking_key)
|
||||||
|
else
|
||||||
|
check
|
||||||
|
client_frame_should_always_be_encoded: False
|
||||||
|
end
|
||||||
|
end
|
||||||
|
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 <===============", debug_level)
|
||||||
|
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, debug_level)
|
||||||
|
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
|
||||||
|
has_error := Result = Void or else Result.has_error
|
||||||
|
rescue
|
||||||
|
retried := True
|
||||||
|
if Result /= Void then
|
||||||
|
Result.report_error (internal_error, "Internal error")
|
||||||
|
end
|
||||||
|
retry
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
feature -- Encoding
|
||||||
|
|
||||||
|
digest (a_sha1: SHA1): STRING
|
||||||
|
-- Digest of `a_sha1'.
|
||||||
|
-- Should by in SHA1 class
|
||||||
|
local
|
||||||
|
l_digest: SPECIAL [NATURAL_8]
|
||||||
|
index, l_upper: INTEGER
|
||||||
|
do
|
||||||
|
l_digest := a_sha1.digest
|
||||||
|
create Result.make (l_digest.count // 2)
|
||||||
|
from
|
||||||
|
index := l_digest.Lower
|
||||||
|
l_upper := l_digest.upper
|
||||||
|
until
|
||||||
|
index > l_upper
|
||||||
|
loop
|
||||||
|
Result.append_character (l_digest [index].to_character_8)
|
||||||
|
index := index + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
feature {NONE} -- Socket helpers
|
||||||
|
|
||||||
|
next_bytes (a_socket: HTTPD_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
|
||||||
|
|
||||||
|
feature -- Masking Data Client - Server
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
note
|
||||||
|
copyright: "2011-2016, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
|
||||||
|
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
|
||||||
|
source: "[
|
||||||
|
Eiffel Software
|
||||||
|
5949 Hollister Ave., Goleta, CA 93117 USA
|
||||||
|
Telephone 805-685-1006, Fax 805-685-6869
|
||||||
|
Website http://www.eiffel.com
|
||||||
|
Customer support http://support.eiffel.com
|
||||||
|
]"
|
||||||
|
end
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
note
|
||||||
|
description: "Summary description for {WGI_STANDALONE_WEBSOCKET_CONNECTOR}."
|
||||||
|
author: ""
|
||||||
|
date: "$Date$"
|
||||||
|
revision: "$Revision$"
|
||||||
|
|
||||||
|
class
|
||||||
|
WGI_STANDALONE_WEBSOCKET_CONNECTOR [G -> WGI_EXECUTION create make end]
|
||||||
|
|
||||||
|
inherit
|
||||||
|
WGI_STANDALONE_CONNECTOR [G]
|
||||||
|
redefine
|
||||||
|
name, version
|
||||||
|
end
|
||||||
|
|
||||||
|
create
|
||||||
|
make,
|
||||||
|
make_with_base
|
||||||
|
|
||||||
|
feature -- Access
|
||||||
|
|
||||||
|
name: STRING_8
|
||||||
|
-- Name of Current connector
|
||||||
|
once
|
||||||
|
Result := "ws_httpd"
|
||||||
|
end
|
||||||
|
|
||||||
|
version: STRING_8
|
||||||
|
-- Version of Current connector
|
||||||
|
once
|
||||||
|
Result := "1.0"
|
||||||
|
end
|
||||||
|
|
||||||
|
note
|
||||||
|
copyright: "2011-2016, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
|
||||||
|
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
|
||||||
|
source: "[
|
||||||
|
Eiffel Software
|
||||||
|
5949 Hollister Ave., Goleta, CA 93117 USA
|
||||||
|
Telephone 805-685-1006, Fax 805-685-6869
|
||||||
|
Website http://www.eiffel.com
|
||||||
|
Customer support http://support.eiffel.com
|
||||||
|
]"
|
||||||
|
end
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
note
|
||||||
|
description: "[
|
||||||
|
Component to launch the service using the default connector
|
||||||
|
|
||||||
|
Eiffel Web httpd for this class
|
||||||
|
|
||||||
|
|
||||||
|
The httpd default connector support options:
|
||||||
|
port: numeric such as 8099 (or equivalent string as "8099")
|
||||||
|
base: base_url (very specific to standalone server)
|
||||||
|
verbose: to display verbose output, useful for standalone connector
|
||||||
|
force_single_threaded: use only one thread, useful for standalone connector
|
||||||
|
|
||||||
|
check WSF_SERVICE_LAUNCHER for more documentation
|
||||||
|
]"
|
||||||
|
date: "$Date$"
|
||||||
|
revision: "$Revision$"
|
||||||
|
|
||||||
|
class
|
||||||
|
WSF_STANDALONE_WEBSOCKET_SERVICE_LAUNCHER [G -> WSF_WEBSOCKET_EXECUTION create make end]
|
||||||
|
|
||||||
|
inherit
|
||||||
|
WSF_STANDALONE_SERVICE_LAUNCHER [G]
|
||||||
|
redefine
|
||||||
|
connector
|
||||||
|
end
|
||||||
|
|
||||||
|
create
|
||||||
|
make,
|
||||||
|
make_and_launch
|
||||||
|
|
||||||
|
feature -- Status report
|
||||||
|
|
||||||
|
connector: WGI_STANDALONE_WEBSOCKET_CONNECTOR [G]
|
||||||
|
-- Default connector
|
||||||
|
|
||||||
|
;note
|
||||||
|
copyright: "2011-2016, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
|
||||||
|
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
|
||||||
|
source: "[
|
||||||
|
Eiffel Software
|
||||||
|
5949 Hollister Ave., Goleta, CA 93117 USA
|
||||||
|
Telephone 805-685-1006, Fax 805-685-6869
|
||||||
|
Website http://www.eiffel.com
|
||||||
|
Customer support http://support.eiffel.com
|
||||||
|
]"
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
@@ -0,0 +1,240 @@
|
|||||||
|
note
|
||||||
|
description: "[
|
||||||
|
Objects that ...
|
||||||
|
]"
|
||||||
|
author: "$Author$"
|
||||||
|
date: "$Date$"
|
||||||
|
revision: "$Revision$"
|
||||||
|
|
||||||
|
deferred class
|
||||||
|
WSF_WEBSOCKET_EXECUTION
|
||||||
|
|
||||||
|
inherit
|
||||||
|
WSF_EXECUTION
|
||||||
|
rename
|
||||||
|
execute as http_execute
|
||||||
|
end
|
||||||
|
|
||||||
|
WEB_SOCKET_CONSTANTS
|
||||||
|
|
||||||
|
REFACTORING_HELPER
|
||||||
|
|
||||||
|
HTTPD_LOGGER_CONSTANTS
|
||||||
|
|
||||||
|
WGI_STANDALONE_CONNECTOR_ACCESS
|
||||||
|
|
||||||
|
--create
|
||||||
|
-- make
|
||||||
|
|
||||||
|
feature -- Execution
|
||||||
|
|
||||||
|
is_verbose: BOOLEAN
|
||||||
|
|
||||||
|
is_websocket: BOOLEAN
|
||||||
|
|
||||||
|
has_error: BOOLEAN
|
||||||
|
|
||||||
|
log (m: READABLE_STRING_8; lev: INTEGER)
|
||||||
|
do
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
http_execute
|
||||||
|
local
|
||||||
|
ws: WEB_SOCKET
|
||||||
|
do
|
||||||
|
has_error := False
|
||||||
|
is_websocket := False
|
||||||
|
create ws.make (request, response)
|
||||||
|
ws.open_ws_handshake
|
||||||
|
if ws.is_websocket then
|
||||||
|
has_error := ws.has_error
|
||||||
|
is_websocket := True
|
||||||
|
on_open (ws)
|
||||||
|
execute_websocket (ws)
|
||||||
|
else
|
||||||
|
execute
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
execute_websocket (ws: WEB_SOCKET)
|
||||||
|
require
|
||||||
|
is_websocket: is_websocket
|
||||||
|
local
|
||||||
|
exit: BOOLEAN
|
||||||
|
l_frame: detachable WEB_SOCKET_FRAME
|
||||||
|
l_client_message: detachable READABLE_STRING_8
|
||||||
|
l_utf: UTF_CONVERTER
|
||||||
|
do
|
||||||
|
from
|
||||||
|
-- loop until ws is closed or has error.
|
||||||
|
until
|
||||||
|
has_error or else exit
|
||||||
|
loop
|
||||||
|
-- debug ("dbglog")
|
||||||
|
-- dbglog (generator + ".LOOP WS_REQUEST_HANDLER.process_request {...}")
|
||||||
|
-- end
|
||||||
|
if ws.socket_is_ready_for_reading then
|
||||||
|
l_frame := ws.next_frame
|
||||||
|
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.
|
||||||
|
on_event (ws, ic.item.payload_data, ic.item.opcode)
|
||||||
|
exit := True
|
||||||
|
elseif ic.item.is_ping then
|
||||||
|
-- FIXME reply only to the most recent ping ...
|
||||||
|
on_event (ws, ic.item.payload_data, ic.item.opcode)
|
||||||
|
else
|
||||||
|
on_event (ws, ic.item.payload_data, ic.item.opcode)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
l_client_message := l_frame.payload_data
|
||||||
|
if l_client_message = Void then
|
||||||
|
l_client_message := ""
|
||||||
|
end
|
||||||
|
|
||||||
|
-- debug ("ws")
|
||||||
|
if is_verbose then
|
||||||
|
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
|
||||||
|
on_event (ws, l_client_message, l_frame.opcode)
|
||||||
|
exit := True
|
||||||
|
elseif l_frame.is_binary then
|
||||||
|
on_event (ws, l_client_message, l_frame.opcode)
|
||||||
|
elseif l_frame.is_text then
|
||||||
|
check is_valid_utf_8: l_utf.is_valid_utf_8_string_8 (l_client_message) end
|
||||||
|
on_event (ws, l_client_message, l_frame.opcode)
|
||||||
|
else
|
||||||
|
on_event (ws, l_client_message, l_frame.opcode)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
-- debug ("ws")
|
||||||
|
if is_verbose then
|
||||||
|
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
|
||||||
|
on_event (ws, "", connection_close_frame)
|
||||||
|
exit := True
|
||||||
|
end
|
||||||
|
else
|
||||||
|
if is_verbose then
|
||||||
|
log (generator + ".WAITING WS_REQUEST_HANDLER.process_request {..}", debug_level)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
execute
|
||||||
|
-- Execute Current request,
|
||||||
|
-- getting data from `request'
|
||||||
|
-- and response to client via `response'.
|
||||||
|
deferred
|
||||||
|
end
|
||||||
|
|
||||||
|
feature -- Web Socket Interface
|
||||||
|
|
||||||
|
on_event (ws: WEB_SOCKET; a_message: detachable READABLE_STRING_8; a_opcode: INTEGER)
|
||||||
|
-- Called when a frame from the client has been receive
|
||||||
|
local
|
||||||
|
l_message: READABLE_STRING_8
|
||||||
|
do
|
||||||
|
debug ("ws")
|
||||||
|
print ("%Non_event (conn, a_message, " + opcode_name (a_opcode) + ")%N")
|
||||||
|
end
|
||||||
|
if a_message = Void then
|
||||||
|
create {STRING} l_message.make_empty
|
||||||
|
else
|
||||||
|
l_message := a_message
|
||||||
|
end
|
||||||
|
|
||||||
|
if a_opcode = Binary_frame then
|
||||||
|
on_binary (ws, l_message)
|
||||||
|
elseif a_opcode = Text_frame then
|
||||||
|
on_text (ws, l_message)
|
||||||
|
elseif a_opcode = Pong_frame then
|
||||||
|
on_pong (ws, l_message)
|
||||||
|
elseif a_opcode = Ping_frame then
|
||||||
|
on_ping (ws, l_message)
|
||||||
|
elseif a_opcode = Connection_close_frame then
|
||||||
|
on_connection_close (ws, "")
|
||||||
|
else
|
||||||
|
on_unsupported (ws, l_message, a_opcode)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
on_open (ws: WEB_SOCKET)
|
||||||
|
-- Called after handshake, indicates that a complete WebSocket connection has been established.
|
||||||
|
deferred
|
||||||
|
end
|
||||||
|
|
||||||
|
on_binary (ws: WEB_SOCKET; a_message: READABLE_STRING_8)
|
||||||
|
deferred
|
||||||
|
end
|
||||||
|
|
||||||
|
on_pong (ws: WEB_SOCKET; a_message: READABLE_STRING_8)
|
||||||
|
do
|
||||||
|
-- log ("Its a pong frame")
|
||||||
|
-- at first we ignore pong
|
||||||
|
-- FIXME: provide better explanation
|
||||||
|
end
|
||||||
|
|
||||||
|
on_ping (ws: WEB_SOCKET; a_message: READABLE_STRING_8)
|
||||||
|
do
|
||||||
|
ws.send (Pong_frame, a_message)
|
||||||
|
end
|
||||||
|
|
||||||
|
on_text (ws: WEB_SOCKET; a_message: READABLE_STRING_8)
|
||||||
|
deferred
|
||||||
|
end
|
||||||
|
|
||||||
|
on_unsupported (ws: WEB_SOCKET; a_message: READABLE_STRING_8; a_opcode: INTEGER)
|
||||||
|
do
|
||||||
|
-- do nothing
|
||||||
|
fixme ("implement on_unsupported")
|
||||||
|
end
|
||||||
|
|
||||||
|
on_connection_close (ws: WEB_SOCKET; a_message: detachable READABLE_STRING_8)
|
||||||
|
do
|
||||||
|
ws.send (Connection_close_frame, "")
|
||||||
|
end
|
||||||
|
|
||||||
|
on_close (ws: WEB_SOCKET)
|
||||||
|
-- Called after the WebSocket connection is closed.
|
||||||
|
deferred
|
||||||
|
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
|
||||||
Reference in New Issue
Block a user