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:
2016-06-22 10:46:15 +02:00
parent b49e841ac7
commit 193cc3cbde
11 changed files with 595 additions and 340 deletions

View 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

View 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

View 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>

View 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

View File

@@ -6,6 +6,6 @@ note
revision: "$Revision$"
deferred class
WGI_STANDALONE_CONNECTOR_ACCESS
WGI_STANDALONE_CONNECTOR_EXPORTER
end

View File

@@ -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

View 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>

View File

@@ -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

View File

@@ -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")

View File

@@ -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

View File

@@ -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)"