Renamed WGI_STANDALONE_CONNECTOR_ACCESS as WGI_STANDALONE_CONNECTOR_EXPORTER.
Isolate the websocket implementation in descendant of {WEB_SOCKET_EVENT_I}.
Added very simple echo websocket example.
+ code cleaning.
This commit is contained in:
29
examples/websocket/application.e
Normal file
29
examples/websocket/application.e
Normal file
@@ -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
|
||||
176
examples/websocket/application_execution.e
Normal file
176
examples/websocket/application_execution.e
Normal file
@@ -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 ("<p><a href=%"/app%">Websocket demo</a></p>")
|
||||
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 := "[
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function() {
|
||||
|
||||
var socket;
|
||||
|
||||
function connect(){
|
||||
|
||||
var host = "ws://127.0.0.1:##PORTNUMBER##";
|
||||
|
||||
try{
|
||||
socket = new WebSocket(host);
|
||||
message('<p class="event">Socket Status: '+socket.readyState);
|
||||
socket.onopen = function(){
|
||||
message('<p class="event">Socket Status: '+socket.readyState+' (open)');
|
||||
}
|
||||
socket.onmessage = function(msg){
|
||||
message('<p class="message">Received: '+msg.data);
|
||||
}
|
||||
socket.onclose = function(){
|
||||
message('<p class="event">Socket Status: '+socket.readyState+' (Closed)');
|
||||
}
|
||||
} catch(exception){
|
||||
message('<p>Error'+exception);
|
||||
}
|
||||
}
|
||||
|
||||
function send(){
|
||||
var text = $('#text').val();
|
||||
if(text==""){
|
||||
message('<p class="warning">Please enter a message');
|
||||
return ;
|
||||
}
|
||||
try{
|
||||
socket.send(text);
|
||||
message('<p class="event">Sent: '+text)
|
||||
} catch(exception){
|
||||
message('<p class="warning">');
|
||||
}
|
||||
$('#text').val("");
|
||||
}
|
||||
|
||||
function message(msg){
|
||||
$('#chatLog').append(msg+'</p>');
|
||||
}//End message()
|
||||
|
||||
$('#text').keypress(function(event) {
|
||||
if (event.keyCode == '13') {
|
||||
send();
|
||||
}
|
||||
});
|
||||
|
||||
$('#disconnect').click(function(){
|
||||
socket.close();
|
||||
});
|
||||
|
||||
if (!("WebSocket" in window)){
|
||||
$('#chatLog, input, button, #examples').fadeOut("fast");
|
||||
$('<p>Oh no, you need a browser that supports WebSockets. How about <a href="http://www.google.com/chrome">Google Chrome</a>?</p>').appendTo('#container');
|
||||
}else{
|
||||
//The user has WebSockets
|
||||
connect();
|
||||
}
|
||||
|
||||
});
|
||||
</script>
|
||||
<meta charset="utf-8" />
|
||||
<style type="text/css">
|
||||
body {font-family:Arial, Helvetica, sans-serif;}
|
||||
#container { border:5px solid grey; width:800px; margin:0 auto; padding:10px; }
|
||||
#chatLog { padding:5px; border:1px solid black; }
|
||||
#chatLog p {margin:0;}
|
||||
.event {color:#999;}
|
||||
.warning { font-weight:bold; color:#CCC; }
|
||||
</style>
|
||||
<title>WebSockets Client</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="wrapper">
|
||||
<div id="container">
|
||||
<h1>WebSockets Client</h1>
|
||||
<div id="chatLog"></div>
|
||||
<input id="text" type="text" />
|
||||
<button id="disconnect">Disconnect</button>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
]"
|
||||
Result.replace_substring_all ("##PORTNUMBER##", a_port.out)
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
21
examples/websocket/websocket_app.ecf
Normal file
21
examples/websocket/websocket_app.ecf
Normal file
@@ -0,0 +1,21 @@
|
||||
<?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="websocket_app" uuid="75D17C20-10A8-4E4C-A059-33D72A2B6AEF">
|
||||
<target name="websocket_app">
|
||||
<root class="APPLICATION" feature="make_and_launch"/>
|
||||
<file_rule>
|
||||
<exclude>/.svn$</exclude>
|
||||
<exclude>/CVS$</exclude>
|
||||
<exclude>/EIFGENs$</exclude>
|
||||
</file_rule>
|
||||
<option warning="true" is_attached_by_default="true" void_safety="all" syntax="transitional">
|
||||
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
|
||||
</option>
|
||||
<setting name="console_application" value="true"/>
|
||||
<setting name="concurrency" value="scoop"/>
|
||||
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
|
||||
<library name="http" location="..\..\library\network\protocol\http\http-safe.ecf"/>
|
||||
<library name="standalone_websocket_connector" location="..\..\library\server\wsf\connector\standalone_websocket-safe.ecf" readonly="false"/>
|
||||
<library name="wsf" location="..\..\library\server\wsf\wsf-safe.ecf"/>
|
||||
<cluster name="app" location=".\" recursive="true"/>
|
||||
</target>
|
||||
</system>
|
||||
8
examples/websocket/ws.ini
Normal file
8
examples/websocket/ws.ini
Normal file
@@ -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
|
||||
@@ -6,6 +6,6 @@ note
|
||||
revision: "$Revision$"
|
||||
|
||||
deferred class
|
||||
WGI_STANDALONE_CONNECTOR_ACCESS
|
||||
WGI_STANDALONE_CONNECTOR_EXPORTER
|
||||
|
||||
end
|
||||
@@ -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
|
||||
|
||||
25
library/server/wsf/connector/standalone_websocket.ecf
Normal file
25
library/server/wsf/connector/standalone_websocket.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-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="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_obsolete_routine_type="true" void_safety="none" syntax="provisional">
|
||||
</option>
|
||||
<library name="base" location="$ISE_LIBRARY\library\base\base.ecf"/>
|
||||
<library name="connector_standalone" location="..\..\ewsgi\connectors\standalone\standalone.ecf"/>
|
||||
<library name="crypto" location="$ISE_LIBRARY\unstable\library\text\encryption\crypto\crypto.ecf"/>
|
||||
<library name="encoder" location="..\..\..\text\encoder\encoder.ecf" readonly="false"/>
|
||||
<library name="error" location="..\..\..\utility\general\error\error.ecf"/>
|
||||
<library name="ewsgi" location="..\..\ewsgi\ewsgi.ecf"/>
|
||||
<library name="http" location="..\..\..\network\protocol\http\http.ecf"/>
|
||||
<library name="httpd" location="..\..\ewsgi\connectors\standalone\lib\httpd\httpd.ecf"/>
|
||||
<library name="time" location="$ISE_LIBRARY\library\time\time.ecf"/>
|
||||
<library name="wsf" location="..\wsf.ecf"/>
|
||||
<library name="wsf_standalone" location="standalone.ecf"/>
|
||||
<cluster name="wsf_standalone_websocket" location=".\standalone_websocket\" recursive="true"/>
|
||||
</target>
|
||||
</system>
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
socket.put_string (l_handshake)
|
||||
-- res.set_status_code_with_reason_phrase (101, "Switching Protocols")
|
||||
-- res.put_header_text (l_handshake)
|
||||
end
|
||||
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
|
||||
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")
|
||||
|
||||
@@ -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
|
||||
@@ -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)"
|
||||
|
||||
Reference in New Issue
Block a user