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$"
|
revision: "$Revision$"
|
||||||
|
|
||||||
deferred class
|
deferred class
|
||||||
WGI_STANDALONE_CONNECTOR_ACCESS
|
WGI_STANDALONE_CONNECTOR_EXPORTER
|
||||||
|
|
||||||
end
|
end
|
||||||
@@ -24,7 +24,7 @@ feature {NONE} -- Initialization
|
|||||||
set_source (a_source)
|
set_source (a_source)
|
||||||
end
|
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)
|
set_source (i: like source)
|
||||||
do
|
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
|
note
|
||||||
description: "[
|
description: "[
|
||||||
API to perform actions like opening and closing the connection, sending and receiving messages, and listening
|
Websocket callback events for actions like opening and closing the connection,
|
||||||
for events.
|
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$"
|
date: "$Date$"
|
||||||
revision: "$Revision$"
|
revision: "$Revision$"
|
||||||
@@ -16,16 +27,16 @@ inherit
|
|||||||
|
|
||||||
feature -- Web Socket Interface
|
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
|
-- Called when a frame from the client has been receive
|
||||||
require
|
require
|
||||||
conn_attached: conn /= Void
|
ws_attached: ws /= Void
|
||||||
conn_valid: conn.is_open_read and then conn.is_open_write
|
ws_valid: ws.is_open_read and then ws.is_open_write
|
||||||
local
|
local
|
||||||
l_message: READABLE_STRING_8
|
l_message: READABLE_STRING_8
|
||||||
do
|
do
|
||||||
debug ("ws")
|
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
|
end
|
||||||
if a_message = Void then
|
if a_message = Void then
|
||||||
create {STRING} l_message.make_empty
|
create {STRING} l_message.make_empty
|
||||||
@@ -34,150 +45,93 @@ feature -- Web Socket Interface
|
|||||||
end
|
end
|
||||||
|
|
||||||
if a_opcode = Binary_frame then
|
if a_opcode = Binary_frame then
|
||||||
on_binary (conn, l_message)
|
on_binary (ws, l_message)
|
||||||
elseif a_opcode = Text_frame then
|
elseif a_opcode = Text_frame then
|
||||||
on_text (conn, l_message)
|
on_text (ws, l_message)
|
||||||
elseif a_opcode = Pong_frame then
|
elseif a_opcode = Pong_frame then
|
||||||
on_pong (conn, l_message)
|
on_pong (ws, l_message)
|
||||||
elseif a_opcode = Ping_frame then
|
elseif a_opcode = Ping_frame then
|
||||||
on_ping (conn, l_message)
|
on_ping (ws, l_message)
|
||||||
elseif a_opcode = Connection_close_frame then
|
elseif a_opcode = Connection_close_frame then
|
||||||
on_connection_close (conn, "")
|
on_connection_close (ws, "")
|
||||||
else
|
else
|
||||||
on_unsupported (conn, l_message, a_opcode)
|
on_unsupported (ws, l_message, a_opcode)
|
||||||
end
|
end
|
||||||
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.
|
-- Called after handshake, indicates that a complete WebSocket connection has been established.
|
||||||
require
|
require
|
||||||
conn_attached: conn /= Void
|
ws_attached: ws /= Void
|
||||||
conn_valid: conn.is_open_read and then conn.is_open_write
|
ws_valid: ws.is_open_read and then ws.is_open_write
|
||||||
deferred
|
deferred
|
||||||
end
|
end
|
||||||
|
|
||||||
on_binary (conn: HTTPD_STREAM_SOCKET; a_message: READABLE_STRING_8)
|
on_binary (ws: WEB_SOCKET; a_message: READABLE_STRING_8)
|
||||||
require
|
require
|
||||||
conn_attached: conn /= Void
|
ws_attached: ws /= Void
|
||||||
conn_valid: conn.is_open_read and then conn.is_open_write
|
ws_valid: ws.is_open_read and then ws.is_open_write
|
||||||
deferred
|
deferred
|
||||||
end
|
end
|
||||||
|
|
||||||
on_pong (conn: HTTPD_STREAM_SOCKET; a_message: READABLE_STRING_8)
|
on_text (ws: WEB_SOCKET; a_message: READABLE_STRING_8)
|
||||||
require
|
require
|
||||||
conn_attached: conn /= Void
|
ws_attached: ws /= Void
|
||||||
conn_valid: conn.is_open_read and then conn.is_open_write
|
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
|
do
|
||||||
-- log ("Its a pong frame")
|
-- log ("Its a pong frame")
|
||||||
-- at first we ignore pong
|
-- at first we ignore pong
|
||||||
-- FIXME: provide better explanation
|
-- FIXME: provide better explanation
|
||||||
end
|
end
|
||||||
|
|
||||||
on_ping (conn: HTTPD_STREAM_SOCKET; a_message: READABLE_STRING_8)
|
on_ping (ws: WEB_SOCKET; a_message: READABLE_STRING_8)
|
||||||
require
|
require
|
||||||
conn_attached: conn /= Void
|
ws_attached: ws /= Void
|
||||||
conn_valid: conn.is_open_read and then conn.is_open_write
|
ws_valid: ws.is_open_read and then ws.is_open_write
|
||||||
do
|
do
|
||||||
send (conn, Pong_frame, a_message)
|
ws.send (Pong_frame, a_message)
|
||||||
end
|
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
|
require
|
||||||
conn_attached: conn /= Void
|
ws_attached: ws /= Void
|
||||||
conn_valid: conn.is_open_read and then conn.is_open_write
|
ws_valid: ws.is_open_read and then ws.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
|
||||||
-- do nothing
|
-- do nothing
|
||||||
end
|
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
|
require
|
||||||
conn_attached: conn /= Void
|
ws_attached: ws /= Void
|
||||||
conn_valid: conn.is_open_read and then conn.is_open_write
|
ws_valid: ws.is_open_read and then ws.is_open_write
|
||||||
do
|
do
|
||||||
send (conn, Connection_close_frame, "")
|
ws.send (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
|
||||||
|
|
||||||
|
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
|
end
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
note
|
note
|
||||||
description: "Summary description for {WEB_SOCKET}."
|
description: "[
|
||||||
author: ""
|
Object representing the websocket connection.
|
||||||
|
It contains the `request` and `response`, and more important the `socket` itself.
|
||||||
|
]"
|
||||||
date: "$Date$"
|
date: "$Date$"
|
||||||
revision: "$Revision$"
|
revision: "$Revision$"
|
||||||
|
|
||||||
@@ -8,7 +10,11 @@ class
|
|||||||
WEB_SOCKET
|
WEB_SOCKET
|
||||||
|
|
||||||
inherit
|
inherit
|
||||||
WGI_STANDALONE_CONNECTOR_ACCESS
|
WGI_STANDALONE_CONNECTOR_EXPORTER
|
||||||
|
|
||||||
|
WSF_RESPONSE_EXPORTER
|
||||||
|
|
||||||
|
WGI_EXPORTER
|
||||||
|
|
||||||
HTTPD_LOGGER_CONSTANTS
|
HTTPD_LOGGER_CONSTANTS
|
||||||
|
|
||||||
@@ -25,7 +31,8 @@ feature {NONE} -- Initialization
|
|||||||
do
|
do
|
||||||
request := req
|
request := req
|
||||||
response := res
|
response := res
|
||||||
is_verbose := True
|
is_verbose := False
|
||||||
|
verbose_level := notice_level
|
||||||
|
|
||||||
if
|
if
|
||||||
attached {WGI_STANDALONE_INPUT_STREAM} req.input as r_input
|
attached {WGI_STANDALONE_INPUT_STREAM} req.input as r_input
|
||||||
@@ -40,30 +47,85 @@ feature {NONE} -- Initialization
|
|||||||
feature -- Access
|
feature -- Access
|
||||||
|
|
||||||
socket: HTTPD_STREAM_SOCKET
|
socket: HTTPD_STREAM_SOCKET
|
||||||
|
-- Underlying connected socket.
|
||||||
|
|
||||||
|
feature {NONE} -- Access
|
||||||
|
|
||||||
request: WSF_REQUEST
|
request: WSF_REQUEST
|
||||||
|
-- Associated request.
|
||||||
|
|
||||||
response: WSF_RESPONSE
|
response: WSF_RESPONSE
|
||||||
|
-- Associated response stream.
|
||||||
|
|
||||||
feature -- Access
|
feature -- Access
|
||||||
|
|
||||||
is_websocket: BOOLEAN
|
is_websocket: BOOLEAN
|
||||||
|
-- Does `open_ws_handshake' detect valid websocket upgrade handshake?
|
||||||
|
|
||||||
has_error: BOOLEAN
|
feature -- Settings
|
||||||
|
|
||||||
is_verbose: BOOLEAN
|
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
|
do
|
||||||
Result := socket.ready_for_reading
|
Result := socket.ready_for_reading
|
||||||
end
|
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
|
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: READABLE_STRING_8; lev: INTEGER)
|
||||||
|
-- Log `m' in the error channel, i.e stderr for standalone.
|
||||||
do
|
do
|
||||||
if is_verbose then
|
if is_verbose then
|
||||||
response.put_error (m)
|
put_error (m)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -87,15 +149,17 @@ feature -- Basic Operation
|
|||||||
local
|
local
|
||||||
l_sha1: SHA1
|
l_sha1: SHA1
|
||||||
l_key : STRING
|
l_key : STRING
|
||||||
l_handshake: STRING
|
|
||||||
req: like request
|
req: like request
|
||||||
res: like response
|
res: like response
|
||||||
do
|
do
|
||||||
req := request
|
-- Reset values.
|
||||||
res := response
|
|
||||||
is_websocket := False
|
is_websocket := False
|
||||||
has_error := False
|
has_error := False
|
||||||
|
|
||||||
|
-- Local cache.
|
||||||
|
req := request
|
||||||
|
res := response
|
||||||
|
|
||||||
-- Reading client's opening GT
|
-- Reading client's opening GT
|
||||||
|
|
||||||
-- TODO extract to a validator handshake or something like that.
|
-- 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
|
l_upgrade_key.is_case_insensitive_equal_general ("websocket") -- Upgrade header must be present with value websocket
|
||||||
then
|
then
|
||||||
is_websocket := True
|
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
|
socket.set_blocking
|
||||||
if
|
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_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
|
create l_sha1.make
|
||||||
l_sha1.update_from_string (l_ws_key + magic_guid)
|
l_sha1.update_from_string (l_ws_key + magic_guid)
|
||||||
l_key := Base64_encoder.encoded_string (digest (l_sha1))
|
l_key := Base64_encoder.encoded_string (digest (l_sha1))
|
||||||
-- create l_handshake.make_from_string ("") --HTTP/1.1 101 Switching Protocols%R%N")
|
res.header.add_header_key_value ("Upgrade", "websocket")
|
||||||
create l_handshake.make_from_string ("HTTP/1.1 101 Switching Protocols%R%N")
|
res.header.add_header_key_value ("Connection", "Upgrade")
|
||||||
l_handshake.append_string ("Upgrade: websocket%R%N")
|
res.header.add_header_key_value ("Sec-WebSocket-Accept", l_key)
|
||||||
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
|
if is_verbose then
|
||||||
log ("%N================> Send", debug_level)
|
log ("%N================> Send Handshake", debug_level)
|
||||||
log (l_handshake, debug_level)
|
if attached {HTTP_HEADER} res.header as h then
|
||||||
|
log (h.string, debug_level)
|
||||||
end
|
end
|
||||||
socket.put_string (l_handshake)
|
end
|
||||||
-- res.set_status_code_with_reason_phrase (101, "Switching Protocols")
|
res.set_status_code_with_reason_phrase (101, "Switching Protocols")
|
||||||
-- res.put_header_text (l_handshake)
|
res.wgi_response.push
|
||||||
else
|
else
|
||||||
has_error := True
|
has_error := True
|
||||||
if is_verbose then
|
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
|
-- 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).
|
-- appropriate error code (such as 400 Bad Request).
|
||||||
res.set_status_code_with_reason_phrase (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
|
end
|
||||||
else
|
else
|
||||||
is_websocket := False
|
is_websocket := False
|
||||||
@@ -177,7 +229,9 @@ feature -- Response!
|
|||||||
n: NATURAL_64
|
n: NATURAL_64
|
||||||
retried: BOOLEAN
|
retried: BOOLEAN
|
||||||
do
|
do
|
||||||
|
debug ("ws")
|
||||||
print (">>do_send (..., "+ opcode_name (a_opcode) +", ..)%N")
|
print (">>do_send (..., "+ opcode_name (a_opcode) +", ..)%N")
|
||||||
|
end
|
||||||
if not retried then
|
if not retried then
|
||||||
create l_header_message.make_empty
|
create l_header_message.make_empty
|
||||||
l_header_message.append_code ((0x80 | a_opcode).to_natural_32)
|
l_header_message.append_code ((0x80 | a_opcode).to_natural_32)
|
||||||
@@ -203,7 +257,7 @@ feature -- Response!
|
|||||||
end
|
end
|
||||||
socket.put_string (l_header_message)
|
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
|
if l_message_count < l_chunk_size then
|
||||||
socket.put_string (a_message)
|
socket.put_string (a_message)
|
||||||
else
|
else
|
||||||
@@ -289,7 +343,6 @@ feature -- Response!
|
|||||||
retried: BOOLEAN
|
retried: BOOLEAN
|
||||||
do
|
do
|
||||||
if not retried then
|
if not retried then
|
||||||
-- l_input := request.input
|
|
||||||
l_socket := socket
|
l_socket := socket
|
||||||
debug ("ws")
|
debug ("ws")
|
||||||
print ("next_frame:%N")
|
print ("next_frame:%N")
|
||||||
@@ -467,9 +520,7 @@ feature -- Response!
|
|||||||
if l_remaining_len < l_chunk_size then
|
if l_remaining_len < l_chunk_size then
|
||||||
l_chunk_size := l_remaining_len
|
l_chunk_size := l_remaining_len
|
||||||
end
|
end
|
||||||
-- l_input.read_string (l_chunk_size)
|
|
||||||
l_socket.read_stream (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
|
l_bytes_read := l_socket.bytes_read
|
||||||
debug ("ws")
|
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")
|
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
|
note
|
||||||
description: "[
|
description: "[
|
||||||
Objects that ...
|
Request execution based on attributes `request' and `response'.
|
||||||
|
Also support Upgrade to Websocket protocol.
|
||||||
|
|
||||||
|
|
||||||
]"
|
]"
|
||||||
author: "$Author$"
|
author: "$Author$"
|
||||||
date: "$Date$"
|
date: "$Date$"
|
||||||
@@ -15,138 +18,32 @@ inherit
|
|||||||
execute as http_execute
|
execute as http_execute
|
||||||
end
|
end
|
||||||
|
|
||||||
WEB_SOCKET_CONSTANTS
|
|
||||||
|
|
||||||
REFACTORING_HELPER
|
|
||||||
|
|
||||||
HTTPD_LOGGER_CONSTANTS
|
|
||||||
|
|
||||||
WGI_STANDALONE_CONNECTOR_ACCESS
|
|
||||||
|
|
||||||
--create
|
--create
|
||||||
-- make
|
-- make
|
||||||
|
|
||||||
feature -- Execution
|
feature -- Execution
|
||||||
|
|
||||||
is_verbose: BOOLEAN
|
frozen http_execute
|
||||||
|
|
||||||
is_websocket: BOOLEAN
|
|
||||||
|
|
||||||
has_error: BOOLEAN
|
|
||||||
|
|
||||||
log (m: READABLE_STRING_8; lev: INTEGER)
|
|
||||||
do
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
http_execute
|
|
||||||
local
|
local
|
||||||
ws: WEB_SOCKET
|
ws: WEB_SOCKET
|
||||||
|
ws_h: like new_websocket_handler
|
||||||
do
|
do
|
||||||
has_error := False
|
|
||||||
is_websocket := False
|
|
||||||
create ws.make (request, response)
|
create ws.make (request, response)
|
||||||
ws.open_ws_handshake
|
ws.open_ws_handshake
|
||||||
if ws.is_websocket then
|
if ws.is_websocket then
|
||||||
has_error := ws.has_error
|
if ws.has_error then
|
||||||
is_websocket := True
|
-- Upgrade to websocket raised an error
|
||||||
on_open (ws)
|
-- stay on standard HTTP/1.1 protocol
|
||||||
execute_websocket (ws)
|
execute
|
||||||
|
else
|
||||||
|
ws_h := new_websocket_handler (ws)
|
||||||
|
ws_h.execute
|
||||||
|
end
|
||||||
else
|
else
|
||||||
execute
|
execute
|
||||||
end
|
end
|
||||||
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
|
||||||
-- Execute Current request,
|
-- Execute Current request,
|
||||||
-- getting data from `request'
|
-- getting data from `request'
|
||||||
@@ -154,79 +51,21 @@ feature -- Execution
|
|||||||
deferred
|
deferred
|
||||||
end
|
end
|
||||||
|
|
||||||
feature -- Web Socket Interface
|
feature -- Factory
|
||||||
|
|
||||||
on_event (ws: WEB_SOCKET; a_message: detachable READABLE_STRING_8; a_opcode: INTEGER)
|
new_websocket_handler (ws: WEB_SOCKET): WEB_SOCKET_HANDLER
|
||||||
-- Called when a frame from the client has been receive
|
-- Websocket request specific handler on socket `ws'.
|
||||||
local
|
--| For the creation, it requires an instance of `{WEB_SOCKET_EVENT_I}'
|
||||||
l_message: READABLE_STRING_8
|
--| to receive the websocket events.
|
||||||
do
|
--| One can inherit from {WEB_SOCKET_EVENT_I} and implement the related
|
||||||
debug ("ws")
|
--| deferred features.
|
||||||
print ("%Non_event (conn, a_message, " + opcode_name (a_opcode) + ")%N")
|
--| Or even provide a new class implementing {WEB_SOCKET_EVENT_I}.
|
||||||
end
|
require
|
||||||
if a_message = Void then
|
is_websocket: ws.is_websocket
|
||||||
create {STRING} l_message.make_empty
|
no_error: not ws.has_error
|
||||||
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
|
deferred
|
||||||
end
|
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
|
note
|
||||||
copyright: "2011-2016, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
|
copyright: "2011-2016, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
|
||||||
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
|
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
|
||||||
|
|||||||
Reference in New Issue
Block a user