diff --git a/examples/websocket/application.e b/examples/websocket/application.e
new file mode 100644
index 00000000..72f4f627
--- /dev/null
+++ b/examples/websocket/application.e
@@ -0,0 +1,29 @@
+note
+ description : "simple application root class"
+ date : "$Date$"
+ revision : "$Revision$"
+
+class
+ APPLICATION
+
+create
+ make_and_launch
+
+feature {NONE} -- Initialization
+
+ make_and_launch
+ local
+ l_launcher: WSF_STANDALONE_WEBSOCKET_SERVICE_LAUNCHER [APPLICATION_EXECUTION]
+ opts: WSF_SERVICE_LAUNCHER_OPTIONS
+ do
+ create {WSF_SERVICE_LAUNCHER_OPTIONS_FROM_INI} opts.make_from_file ("ws.ini")
+ create l_launcher.make_and_launch (options)
+ end
+
+ options: WSF_SERVICE_LAUNCHER_OPTIONS
+ -- Initialize current service.
+ do
+ create {WSF_SERVICE_LAUNCHER_OPTIONS_FROM_INI} Result.make_from_file ("ws.ini")
+ end
+
+end
diff --git a/examples/websocket/application_execution.e b/examples/websocket/application_execution.e
new file mode 100644
index 00000000..5ebd702e
--- /dev/null
+++ b/examples/websocket/application_execution.e
@@ -0,0 +1,176 @@
+note
+ description : "simple application execution"
+ date : "$Date$"
+ revision : "$Revision$"
+
+class
+ APPLICATION_EXECUTION
+
+inherit
+ WSF_WEBSOCKET_EXECUTION
+
+ WEB_SOCKET_EVENT_I
+
+create
+ make
+
+feature -- Basic operations
+
+ execute
+ local
+ s: STRING
+ dt: HTTP_DATE
+ do
+ -- To send a response we need to setup, the status code and
+ -- the response headers.
+ if request.path_info.same_string_general ("/app") then
+ s := websocket_app_html (9090)
+ else
+ s := "Hello World!"
+ create dt.make_now_utc
+ s.append (" (UTC time is " + dt.rfc850_string + ").")
+ s.append ("
Websocket demo
")
+ end
+ response.put_header ({HTTP_STATUS_CODE}.ok, <<["Content-Type", "text/html"], ["Content-Length", s.count.out]>>)
+ response.set_status_code ({HTTP_STATUS_CODE}.ok)
+ response.header.put_content_type_text_html
+ response.header.put_content_length (s.count)
+ if attached request.http_connection as l_connection and then l_connection.is_case_insensitive_equal_general ("keep-alive") then
+ response.header.put_header_key_value ("Connection", "keep-alive")
+ end
+ response.put_string (s)
+ end
+
+feature -- Websocket execution
+
+ new_websocket_handler (ws: WEB_SOCKET): WEB_SOCKET_HANDLER
+ do
+ create Result.make (ws, Current)
+ end
+
+feature -- Websocket execution
+
+ on_open (ws: WEB_SOCKET)
+ do
+ ws.put_error ("Connecting")
+ 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
+ -- Echo the message for testing.
+ ws.send (Text_frame, a_message)
+ end
+
+ on_close (ws: WEB_SOCKET)
+ -- Called after the WebSocket connection is closed.
+ do
+ ws.put_error ("Connection closed")
+ end
+
+feature -- HTML Resource
+
+ websocket_app_html (a_port: INTEGER): STRING
+ do
+ Result := "[
+
+
+
+
+
+
+
+WebSockets Client
+
+
+
+
+
WebSockets Client
+
+
+
+
+
+
+
+ ]"
+ Result.replace_substring_all ("##PORTNUMBER##", a_port.out)
+ end
+
+
+end
diff --git a/examples/websocket/websocket_app.ecf b/examples/websocket/websocket_app.ecf
new file mode 100644
index 00000000..13f95276
--- /dev/null
+++ b/examples/websocket/websocket_app.ecf
@@ -0,0 +1,21 @@
+
+
+
+
+
+ /.svn$
+ /CVS$
+ /EIFGENs$
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/websocket/ws.ini b/examples/websocket/ws.ini
new file mode 100644
index 00000000..b04bd038
--- /dev/null
+++ b/examples/websocket/ws.ini
@@ -0,0 +1,8 @@
+verbose=true
+verbose_level=INFORMATION
+port=9090
+max_concurrent_connections=100
+keep_alive_timeout=35
+max_tcp_clients=100
+socket_timeout=30000
+max_keep_alive_requests=3000
diff --git a/library/server/ewsgi/connectors/standalone/src/wgi_standalone_connector_access.e b/library/server/ewsgi/connectors/standalone/src/wgi_standalone_connector_exporter.e
similarity index 82%
rename from library/server/ewsgi/connectors/standalone/src/wgi_standalone_connector_access.e
rename to library/server/ewsgi/connectors/standalone/src/wgi_standalone_connector_exporter.e
index 622cce56..bbfa7ca4 100644
--- a/library/server/ewsgi/connectors/standalone/src/wgi_standalone_connector_access.e
+++ b/library/server/ewsgi/connectors/standalone/src/wgi_standalone_connector_exporter.e
@@ -6,6 +6,6 @@ note
revision: "$Revision$"
deferred class
- WGI_STANDALONE_CONNECTOR_ACCESS
+ WGI_STANDALONE_CONNECTOR_EXPORTER
end
diff --git a/library/server/ewsgi/connectors/standalone/src/wgi_standalone_input_stream.e b/library/server/ewsgi/connectors/standalone/src/wgi_standalone_input_stream.e
index 0cdcb382..1cf333a7 100644
--- a/library/server/ewsgi/connectors/standalone/src/wgi_standalone_input_stream.e
+++ b/library/server/ewsgi/connectors/standalone/src/wgi_standalone_input_stream.e
@@ -24,7 +24,7 @@ feature {NONE} -- Initialization
set_source (a_source)
end
-feature {WGI_STANDALONE_CONNECTOR, WGI_SERVICE, WGI_STANDALONE_CONNECTOR_ACCESS} -- Standalone
+feature {WGI_STANDALONE_CONNECTOR, WGI_SERVICE, WGI_STANDALONE_CONNECTOR_EXPORTER} -- Standalone
set_source (i: like source)
do
diff --git a/library/server/wsf/connector/standalone_websocket.ecf b/library/server/wsf/connector/standalone_websocket.ecf
new file mode 100644
index 00000000..0783e778
--- /dev/null
+++ b/library/server/wsf/connector/standalone_websocket.ecf
@@ -0,0 +1,25 @@
+
+
+
+
+
+ /EIFGENs$
+ /\.git$
+ /\.svn$
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/library/server/wsf/connector/standalone_websocket/websocket/event/web_socket_event_i.e b/library/server/wsf/connector/standalone_websocket/websocket/event/web_socket_event_i.e
index b8e8994d..9ad0fec3 100644
--- a/library/server/wsf/connector/standalone_websocket/websocket/event/web_socket_event_i.e
+++ b/library/server/wsf/connector/standalone_websocket/websocket/event/web_socket_event_i.e
@@ -1,7 +1,18 @@
note
description: "[
- API to perform actions like opening and closing the connection, sending and receiving messages, and listening
- for events.
+ Websocket callback events for actions like opening and closing the connection,
+ sending and receiving messages, and listening.
+
+ Define the websocket events:
+ - on_open
+ - on_binary
+ - on_text
+ - on_close
+
+ note: the following features could also be redefined:
+ - on_pong
+ - on_ping
+ - on_unsupported
]"
date: "$Date$"
revision: "$Revision$"
@@ -16,16 +27,16 @@ inherit
feature -- Web Socket Interface
- on_event (conn: HTTPD_STREAM_SOCKET; a_message: detachable READABLE_STRING_8; a_opcode: INTEGER)
+ on_event (ws: WEB_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
+ ws_attached: ws /= Void
+ ws_valid: ws.is_open_read and then ws.is_open_write
local
l_message: READABLE_STRING_8
do
debug ("ws")
- print ("%Non_event (conn, a_message, " + opcode_name (a_opcode) + ")%N")
+ ws.log ("%Non_event (ws, a_message, " + opcode_name (a_opcode) + ")%N", {HTTPD_LOGGER_CONSTANTS}.debug_level)
end
if a_message = Void then
create {STRING} l_message.make_empty
@@ -34,150 +45,93 @@ feature -- Web Socket Interface
end
if a_opcode = Binary_frame then
- on_binary (conn, l_message)
+ on_binary (ws, l_message)
elseif a_opcode = Text_frame then
- on_text (conn, l_message)
+ on_text (ws, l_message)
elseif a_opcode = Pong_frame then
- on_pong (conn, l_message)
+ on_pong (ws, l_message)
elseif a_opcode = Ping_frame then
- on_ping (conn, l_message)
+ on_ping (ws, l_message)
elseif a_opcode = Connection_close_frame then
- on_connection_close (conn, "")
+ on_connection_close (ws, "")
else
- on_unsupported (conn, l_message, a_opcode)
+ on_unsupported (ws, l_message, a_opcode)
end
end
- on_open (conn: HTTPD_STREAM_SOCKET)
+feature -- Websocket events
+
+ on_open (ws: WEB_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
+ ws_attached: ws /= Void
+ ws_valid: ws.is_open_read and then ws.is_open_write
deferred
end
- on_binary (conn: HTTPD_STREAM_SOCKET; a_message: READABLE_STRING_8)
+ on_binary (ws: WEB_SOCKET; a_message: READABLE_STRING_8)
require
- conn_attached: conn /= Void
- conn_valid: conn.is_open_read and then conn.is_open_write
+ ws_attached: ws /= Void
+ ws_valid: ws.is_open_read and then ws.is_open_write
deferred
end
- on_pong (conn: HTTPD_STREAM_SOCKET; a_message: READABLE_STRING_8)
+ on_text (ws: WEB_SOCKET; a_message: READABLE_STRING_8)
require
- conn_attached: conn /= Void
- conn_valid: conn.is_open_read and then conn.is_open_write
+ ws_attached: ws /= Void
+ ws_valid: ws.is_open_read and then ws.is_open_write
+ deferred
+ end
+
+ on_close (ws: detachable WEB_SOCKET)
+ -- Called after the WebSocket connection is closed.
+ deferred
+ end
+
+feature -- Websocket events: implemented
+
+ on_pong (ws: WEB_SOCKET; a_message: READABLE_STRING_8)
+ require
+ ws_attached: ws /= Void
+ ws_valid: ws.is_open_read and then ws.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)
+ on_ping (ws: WEB_SOCKET; a_message: READABLE_STRING_8)
require
- conn_attached: conn /= Void
- conn_valid: conn.is_open_read and then conn.is_open_write
+ ws_attached: ws /= Void
+ ws_valid: ws.is_open_read and then ws.is_open_write
do
- send (conn, Pong_frame, a_message)
+ ws.send (Pong_frame, a_message)
end
- on_text (conn: HTTPD_STREAM_SOCKET; a_message: READABLE_STRING_8)
+ on_unsupported (ws: WEB_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
- 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
+ ws_attached: ws /= Void
+ ws_valid: ws.is_open_read and then ws.is_open_write
do
-- do nothing
end
- on_connection_close (conn: HTTPD_STREAM_SOCKET; a_message: detachable READABLE_STRING_8)
+ on_connection_close (ws: WEB_SOCKET; a_message: detachable READABLE_STRING_8)
require
- conn_attached: conn /= Void
- conn_valid: conn.is_open_read and then conn.is_open_write
+ ws_attached: ws /= Void
+ ws_valid: ws.is_open_read and then ws.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
+ ws.send (Connection_close_frame, "")
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
diff --git a/library/server/wsf/connector/standalone_websocket/websocket/web_socket.e b/library/server/wsf/connector/standalone_websocket/websocket/web_socket.e
index b1c4b8d8..e301f25f 100644
--- a/library/server/wsf/connector/standalone_websocket/websocket/web_socket.e
+++ b/library/server/wsf/connector/standalone_websocket/websocket/web_socket.e
@@ -1,6 +1,8 @@
note
- description: "Summary description for {WEB_SOCKET}."
- author: ""
+ description: "[
+ Object representing the websocket connection.
+ It contains the `request` and `response`, and more important the `socket` itself.
+ ]"
date: "$Date$"
revision: "$Revision$"
@@ -8,7 +10,11 @@ class
WEB_SOCKET
inherit
- WGI_STANDALONE_CONNECTOR_ACCESS
+ WGI_STANDALONE_CONNECTOR_EXPORTER
+
+ WSF_RESPONSE_EXPORTER
+
+ WGI_EXPORTER
HTTPD_LOGGER_CONSTANTS
@@ -25,7 +31,8 @@ feature {NONE} -- Initialization
do
request := req
response := res
- is_verbose := True
+ is_verbose := False
+ verbose_level := notice_level
if
attached {WGI_STANDALONE_INPUT_STREAM} req.input as r_input
@@ -40,30 +47,85 @@ feature {NONE} -- Initialization
feature -- Access
socket: HTTPD_STREAM_SOCKET
+ -- Underlying connected socket.
+
+feature {NONE} -- Access
request: WSF_REQUEST
+ -- Associated request.
response: WSF_RESPONSE
+ -- Associated response stream.
feature -- Access
is_websocket: BOOLEAN
+ -- Does `open_ws_handshake' detect valid websocket upgrade handshake?
- has_error: BOOLEAN
+feature -- Settings
is_verbose: BOOLEAN
+ -- Output verbose log messages?
- socket_is_ready_for_reading: BOOLEAN
+ verbose_level: INTEGER
+ -- Level of verbosity.
+
+feature -- Status
+
+ has_error: BOOLEAN
+ -- Error occured during processing?
+
+feature -- Socket status
+
+ is_ready_for_reading: BOOLEAN
+ -- Is `socket' ready for reading?
+ --| at this point, socket should be set to blocking.
do
Result := socket.ready_for_reading
end
+ is_open_read: BOOLEAN
+ -- Is `socket' open for reading?
+ do
+ Result := socket.is_open_read
+ end
+
+ is_open_write: BOOLEAN
+ -- Is `socket' open for writing?
+ do
+ Result := socket.is_open_write
+ end
+
+ socket_descriptor: INTEGER
+ -- Descriptor for current `socket'.
+ do
+ Result := socket.descriptor
+ end
+
feature -- Element change
+ set_is_verbose (b: BOOLEAN)
+ do
+ is_verbose := b
+ end
+
+ set_verbose_level (lev: INTEGER)
+ do
+ verbose_level := lev
+ end
+
+feature -- Basic operation
+
+ put_error (a_message: READABLE_STRING_8)
+ do
+ response.put_error (a_message)
+ end
+
log (m: READABLE_STRING_8; lev: INTEGER)
+ -- Log `m' in the error channel, i.e stderr for standalone.
do
if is_verbose then
- response.put_error (m)
+ put_error (m)
end
end
@@ -87,15 +149,17 @@ feature -- Basic Operation
local
l_sha1: SHA1
l_key : STRING
- l_handshake: STRING
req: like request
res: like response
do
- req := request
- res := response
+ -- Reset values.
is_websocket := False
has_error := False
+ -- Local cache.
+ req := request
+ res := response
+
-- Reading client's opening GT
-- TODO extract to a validator handshake or something like that.
@@ -111,12 +175,6 @@ feature -- Basic Operation
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
@@ -133,23 +191,18 @@ feature -- Basic Operation
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")
+ res.header.add_header_key_value ("Upgrade", "websocket")
+ res.header.add_header_key_value ("Connection", "Upgrade")
+ res.header.add_header_key_value ("Sec-WebSocket-Accept", l_key)
+
if is_verbose then
- log ("%N================> Send", debug_level)
- log (l_handshake, debug_level)
+ log ("%N================> Send Handshake", debug_level)
+ if attached {HTTP_HEADER} res.header as h then
+ log (h.string, debug_level)
+ end
end
- socket.put_string (l_handshake)
--- res.set_status_code_with_reason_phrase (101, "Switching Protocols")
--- res.put_header_text (l_handshake)
+ res.set_status_code_with_reason_phrase (101, "Switching Protocols")
+ res.wgi_response.push
else
has_error := True
if is_verbose then
@@ -158,7 +211,6 @@ feature -- Basic Operation
-- 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
@@ -177,7 +229,9 @@ feature -- Response!
n: NATURAL_64
retried: BOOLEAN
do
- print (">>do_send (..., "+ opcode_name (a_opcode) +", ..)%N")
+ debug ("ws")
+ print (">>do_send (..., "+ opcode_name (a_opcode) +", ..)%N")
+ end
if not retried then
create l_header_message.make_empty
l_header_message.append_code ((0x80 | a_opcode).to_natural_32)
@@ -203,7 +257,7 @@ feature -- Response!
end
socket.put_string (l_header_message)
- l_chunk_size := 16_384 -- 16K
+ l_chunk_size := 16_384 -- 16K TODO: see if we should make it customizable.
if l_message_count < l_chunk_size then
socket.put_string (a_message)
else
@@ -289,7 +343,6 @@ feature -- Response!
retried: BOOLEAN
do
if not retried then
--- l_input := request.input
l_socket := socket
debug ("ws")
print ("next_frame:%N")
@@ -467,9 +520,7 @@ feature -- Response!
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")
diff --git a/library/server/wsf/connector/standalone_websocket/websocket/web_socket_handler.e b/library/server/wsf/connector/standalone_websocket/websocket/web_socket_handler.e
new file mode 100644
index 00000000..5268c600
--- /dev/null
+++ b/library/server/wsf/connector/standalone_websocket/websocket/web_socket_handler.e
@@ -0,0 +1,152 @@
+note
+ description: "[
+ To implement websocket handling, provide a `callbacks` object implementing the {WEB_SOCKET_EVENT_I} interface.
+ ]"
+ date: "$Date$"
+ revision: "$Revision$"
+
+class
+ WEB_SOCKET_HANDLER
+
+inherit
+ WEB_SOCKET_CONSTANTS
+
+ REFACTORING_HELPER
+
+ HTTPD_LOGGER_CONSTANTS
+
+create
+ make
+
+feature {NONE} -- Initialization
+
+ make (ws: WEB_SOCKET; a_callbacks: WEB_SOCKET_EVENT_I)
+ do
+ web_socket := ws
+ callbacks := a_callbacks
+ end
+
+feature -- Access
+
+ web_socket: WEB_SOCKET
+ -- Associated websocket.
+
+ callbacks: WEB_SOCKET_EVENT_I
+
+feature -- Execution
+
+ frozen execute
+ do
+ callbacks.on_open (web_socket)
+ execute_websocket
+ end
+
+ execute_websocket
+ local
+ exit: BOOLEAN
+ l_frame: detachable WEB_SOCKET_FRAME
+ l_client_message: detachable READABLE_STRING_8
+ l_utf: UTF_CONVERTER
+ ws: like web_socket
+ s: STRING
+ do
+ from
+ -- loop until ws is closed or has error.
+ ws := web_socket
+ until
+ exit
+ loop
+ debug ("dbglog")
+ dbglog (generator + ".execute_websocket (loop) WS_REQUEST_HANDLER.process_request {" + ws.socket_descriptor.out + "}")
+ end
+ if ws.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.
+ callbacks.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 ...
+ callbacks.on_event (ws, ic.item.payload_data, ic.item.opcode)
+ else
+ callbacks.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")
+ create s.make_from_string ("%NExecute: %N")
+ s.append (" [opcode: "+ opcode_name (l_frame.opcode) +"]%N")
+ if l_frame.is_text then
+ s.append (" [client message: %""+ l_client_message +"%"]%N")
+ elseif l_frame.is_binary then
+ s.append (" [client binary message length: %""+ l_client_message.count.out +"%"]%N")
+ end
+ s.append (" [is_control: " + l_frame.is_control.out + "]%N")
+ s.append (" [is_binary: " + l_frame.is_binary.out + "]%N")
+ s.append (" [is_text: " + l_frame.is_text.out + "]%N")
+ dbglog (s)
+ end
+
+ if l_frame.is_connection_close then
+ callbacks.on_event (ws, l_client_message, l_frame.opcode)
+ exit := True
+ elseif l_frame.is_binary then
+ callbacks.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
+ callbacks.on_event (ws, l_client_message, l_frame.opcode)
+ else
+ callbacks.on_event (ws, l_client_message, l_frame.opcode)
+ end
+ else
+ debug ("ws")
+ create s.make_from_string ("%NExecute: %N")
+ s.append (" [ERROR: invalid frame]%N")
+ if l_frame /= Void and then attached l_frame.error as err then
+ s.append (" [Code: "+ err.code.out +"]%N")
+ s.append (" [Description: "+ err.description +"]%N")
+ end
+ dbglog (s)
+ end
+ callbacks.on_event (ws, "", connection_close_frame)
+ exit := True -- FIXME: check proper close protocol
+ end
+ else
+ debug ("ws")
+ dbglog (generator + ".WAITING WS_REQUEST_HANDLER.process_request {" + ws.socket_descriptor.out + "}")
+ end
+ end
+ end
+ end
+
+feature {NONE} -- Logging
+
+ dbglog (m: READABLE_STRING_8)
+ do
+ web_socket.log (m, debug_level)
+ 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
diff --git a/library/server/wsf/connector/standalone_websocket/wsf_websocket_execution.e b/library/server/wsf/connector/standalone_websocket/wsf_websocket_execution.e
index 0505795d..78be0243 100644
--- a/library/server/wsf/connector/standalone_websocket/wsf_websocket_execution.e
+++ b/library/server/wsf/connector/standalone_websocket/wsf_websocket_execution.e
@@ -1,6 +1,9 @@
note
description: "[
- Objects that ...
+ Request execution based on attributes `request' and `response'.
+ Also support Upgrade to Websocket protocol.
+
+
]"
author: "$Author$"
date: "$Date$"
@@ -15,138 +18,32 @@ inherit
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
+ frozen http_execute
local
ws: WEB_SOCKET
+ ws_h: like new_websocket_handler
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)
+ if ws.has_error then
+ -- Upgrade to websocket raised an error
+ -- stay on standard HTTP/1.1 protocol
+ execute
+ else
+ ws_h := new_websocket_handler (ws)
+ ws_h.execute
+ end
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'
@@ -154,79 +51,21 @@ feature -- Execution
deferred
end
-feature -- Web Socket Interface
+feature -- Factory
- 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.
+ new_websocket_handler (ws: WEB_SOCKET): WEB_SOCKET_HANDLER
+ -- Websocket request specific handler on socket `ws'.
+ --| For the creation, it requires an instance of `{WEB_SOCKET_EVENT_I}'
+ --| to receive the websocket events.
+ --| One can inherit from {WEB_SOCKET_EVENT_I} and implement the related
+ --| deferred features.
+ --| Or even provide a new class implementing {WEB_SOCKET_EVENT_I}.
+ require
+ is_websocket: ws.is_websocket
+ no_error: not ws.has_error
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)"