Skip to content

Commit

Permalink
Added WSF standalone_websocket connector, that provides websocket o…
Browse files Browse the repository at this point in the history
…n top of `standalone` connector.
  • Loading branch information
jocelyn committed Jun 21, 2016
1 parent 8ba74e1 commit b49e841
Show file tree
Hide file tree
Showing 11 changed files with 2,033 additions and 0 deletions.
@@ -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 changes: 25 additions & 0 deletions library/server/wsf/connector/standalone_websocket-safe.ecf
@@ -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 comments on commit b49e841

Please sign in to comment.