Compare commits
10 Commits
es_rev9884
...
standalone
| Author | SHA1 | Date | |
|---|---|---|---|
| 1b951376f9 | |||
| 193cc3cbde | |||
| b49e841ac7 | |||
| 8ba74e1c90 | |||
| 0cecb9594c | |||
| e384a6d6ed | |||
| 71a5c086a5 | |||
| dfa60bf8f5 | |||
| 113aa69efc | |||
| af5fc75743 |
@@ -20,9 +20,10 @@ feature {NONE} -- Initialization
|
||||
initialize
|
||||
-- Initialize current service.
|
||||
do
|
||||
set_service_option ("port", 9090)
|
||||
-- Specific to `standalone' connector (the EiffelWeb server).
|
||||
-- See `{WSF_STANDALONE_SERVICE_LAUNCHER}.initialize'
|
||||
set_service_option ("port", 9090)
|
||||
import_service_options (create {WSF_SERVICE_LAUNCHER_OPTIONS_FROM_INI}.make_from_file ("simple.ini"))
|
||||
end
|
||||
|
||||
|
||||
|
||||
end
|
||||
|
||||
@@ -17,10 +17,13 @@ feature -- Basic operations
|
||||
execute
|
||||
local
|
||||
s: STRING
|
||||
do
|
||||
-- To send a response we need to setup, the status code and
|
||||
-- the response headers.
|
||||
s := "Hello World!"
|
||||
dt: HTTP_DATE
|
||||
do
|
||||
-- To send a response we need to setup, the status code and
|
||||
-- the response headers.
|
||||
s := "Hello World!"
|
||||
create dt.make_now_utc
|
||||
s.append (" (UTC time is " + dt.rfc850_string + ").")
|
||||
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
|
||||
|
||||
@@ -1,35 +1,28 @@
|
||||
<?xml version="1.0" encoding="ISO-8859-1"?>
|
||||
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-13-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-13-0 http://www.eiffel.com/developers/xml/configuration-1-13-0.xsd" name="simple" uuid="C28C4F53-9963-46C0-A080-8F13E94E7486" library_target="simple">
|
||||
<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="simple" uuid="C28C4F53-9963-46C0-A080-8F13E94E7486" library_target="simple">
|
||||
<target name="common" abstract="true">
|
||||
<file_rule>
|
||||
<exclude>/EIFGENs$</exclude>
|
||||
<exclude>/CVS$</exclude>
|
||||
<exclude>/.svn$</exclude>
|
||||
<exclude>/CVS$</exclude>
|
||||
<exclude>/EIFGENs$</exclude>
|
||||
</file_rule>
|
||||
<option warning="true" full_class_checking="false" is_attached_by_default="true" void_safety="transitional" syntax="transitional">
|
||||
<option warning="true" full_class_checking="false" is_attached_by_default="true" is_obsolete_routine_type="true" void_safety="transitional" syntax="transitional">
|
||||
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
|
||||
</option>
|
||||
<setting name="console_application" value="true"/>
|
||||
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
|
||||
<library name="http" location="..\..\library\network\protocol\http\http-safe.ecf"/>
|
||||
<library name="wsf" location="..\..\library\server\wsf\wsf-safe.ecf"/>
|
||||
</target>
|
||||
<target name="simple_standalone" extends="common">
|
||||
<root class="APPLICATION" feature="make_and_launch"/>
|
||||
<option warning="true" is_attached_by_default="true" void_safety="transitional" syntax="transitional">
|
||||
<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="concurrency" value="thread"/>
|
||||
<setting name="concurrency" value="scoop"/>
|
||||
<library name="default_standalone" location="..\..\library\server\wsf\default\standalone-safe.ecf"/>
|
||||
<cluster name="simple" location=".\" recursive="true"/>
|
||||
</target>
|
||||
<target name="simple_nino" extends="common">
|
||||
<root class="APPLICATION" feature="make_and_launch"/>
|
||||
<option warning="true" is_attached_by_default="true" void_safety="transitional" syntax="transitional">
|
||||
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
|
||||
</option>
|
||||
<library name="default_nino" location="..\..\library\server\wsf\default\nino-safe.ecf"/>
|
||||
<cluster name="simple" location=".\" recursive="true"/>
|
||||
</target>
|
||||
<target name="simple_cgi" extends="common">
|
||||
<root class="APPLICATION" feature="make_and_launch"/>
|
||||
<option warning="true" is_attached_by_default="true" void_safety="transitional" syntax="transitional">
|
||||
|
||||
8
examples/simple/simple.ini
Normal file
8
examples/simple/simple.ini
Normal file
@@ -0,0 +1,8 @@
|
||||
verbose=true
|
||||
verbose_level=ALERT
|
||||
port=9090
|
||||
#max_concurrent_connections=100
|
||||
#keep_alive_timeout=15
|
||||
#max_tcp_clients=100
|
||||
#socket_timeout=300
|
||||
#max_keep_alive_requests=300
|
||||
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
|
||||
184
examples/websocket/application_execution.e
Normal file
184
examples/websocket/application_execution.e
Normal file
@@ -0,0 +1,184 @@
|
||||
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")
|
||||
ws.send (Text_frame, "Hello, this is a simple demo with Websocket using Eiffel. (/help for more information).%N")
|
||||
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
|
||||
if a_message.same_string_general ("/help") then
|
||||
-- Echo the message for testing.
|
||||
ws.send (Text_frame, "Help: available commands%N - /time : return the server UTC time.%N")
|
||||
elseif a_message.starts_with_general ("/time") then
|
||||
ws.send (Text_frame, "Server time is " + (create {HTTP_DATE}.make_now_utc).string)
|
||||
else
|
||||
-- Echo the message for testing.
|
||||
ws.send (Text_frame, a_message)
|
||||
end
|
||||
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
|
||||
@@ -23,8 +23,8 @@ feature {NONE} -- Initialization
|
||||
local
|
||||
n: INTEGER
|
||||
p: like pool
|
||||
do
|
||||
n := max_concurrent_connections (server)
|
||||
do
|
||||
n := max_concurrent_connections (server).max (1) -- At least one processor!
|
||||
create p.make (n)
|
||||
initialize_pool (p, n)
|
||||
pool := p
|
||||
@@ -23,7 +23,7 @@ feature {NONE} -- Initialization
|
||||
local
|
||||
n: INTEGER
|
||||
do
|
||||
n := max_concurrent_connections (server)
|
||||
n := max_concurrent_connections (server).max (1) -- At least one thread!
|
||||
create pool.make (n.to_natural_32)
|
||||
end
|
||||
|
||||
@@ -10,17 +10,26 @@ feature {NONE} -- Initialization
|
||||
|
||||
make
|
||||
do
|
||||
http_server_port := 80
|
||||
max_concurrent_connections := 100
|
||||
max_tcp_clients := 100
|
||||
socket_accept_timeout := 1_000
|
||||
socket_connect_timeout := 5_000
|
||||
keep_alive_timeout := 5
|
||||
http_server_port := default_http_server_port
|
||||
max_concurrent_connections := default_max_concurrent_connections
|
||||
max_tcp_clients := default_max_tcp_clients
|
||||
socket_timeout := default_socket_timeout
|
||||
keep_alive_timeout := default_keep_alive_timeout
|
||||
max_keep_alive_requests := default_max_keep_alive_requests
|
||||
is_secure := False
|
||||
create ca_crt.make_empty
|
||||
create ca_key.make_empty
|
||||
end
|
||||
|
||||
feature -- Defaults
|
||||
|
||||
default_http_server_port: INTEGER = 80
|
||||
default_max_concurrent_connections: INTEGER = 100
|
||||
default_max_tcp_clients: INTEGER = 100
|
||||
default_socket_timeout: INTEGER = 300 -- seconds
|
||||
default_keep_alive_timeout: INTEGER = 15 -- seconds
|
||||
default_max_keep_alive_requests: INTEGER = 100
|
||||
|
||||
feature -- Access
|
||||
|
||||
Server_details: STRING_8
|
||||
@@ -31,35 +40,62 @@ feature -- Access
|
||||
http_server_name: detachable READABLE_STRING_8 assign set_http_server_name
|
||||
http_server_port: INTEGER assign set_http_server_port
|
||||
max_tcp_clients: INTEGER assign set_max_tcp_clients
|
||||
-- Listen on socket for at most `queue' connections.
|
||||
|
||||
socket_timeout: INTEGER assign set_socket_timeout
|
||||
-- Amount of seconds that the server waits for receipts and transmissions during communications.
|
||||
-- note: with timeout of 0, socket can wait for ever.
|
||||
-- By default: 300 seconds, which is appropriate for most situations.
|
||||
|
||||
max_concurrent_connections: INTEGER assign set_max_concurrent_connections
|
||||
socket_accept_timeout: INTEGER assign set_socket_accept_timeout
|
||||
socket_connect_timeout: INTEGER assign set_socket_connect_timeout
|
||||
-- Max number of concurrent connections.
|
||||
|
||||
force_single_threaded: BOOLEAN assign set_force_single_threaded
|
||||
do
|
||||
Result := (max_concurrent_connections = 0)
|
||||
Result := max_concurrent_connections = 0
|
||||
end
|
||||
|
||||
is_verbose: BOOLEAN assign set_is_verbose
|
||||
-- Display verbose message to the output?
|
||||
|
||||
verbose_level: INTEGER assign set_verbose_level
|
||||
-- Verbosity of output.
|
||||
|
||||
keep_alive_timeout: INTEGER assign set_keep_alive_timeout
|
||||
-- Persistent connection timeout
|
||||
-- Persistent connection timeout.
|
||||
-- Number of seconds the server waits after a request has been served before it closes the connection.
|
||||
-- Timeout unit in Seconds.
|
||||
-- By default: 5 seconds.
|
||||
|
||||
max_keep_alive_requests: INTEGER assign set_max_keep_alive_requests
|
||||
-- Maximum number of requests allowed per persistent connection.
|
||||
-- Recommended a high setting.
|
||||
-- To disable KeepAlive, set `max_keep_alive_requests' to 0.
|
||||
-- By default: 100 .
|
||||
|
||||
has_ssl_support: BOOLEAN
|
||||
-- Has SSL support?
|
||||
deferred
|
||||
end
|
||||
|
||||
request_settings: HTTPD_REQUEST_SETTINGS
|
||||
do
|
||||
Result.is_verbose := is_verbose
|
||||
Result.verbose_level := verbose_level
|
||||
Result.timeout := socket_timeout
|
||||
Result.keep_alive_timeout := keep_alive_timeout
|
||||
Result.max_keep_alive_requests := max_keep_alive_requests
|
||||
end
|
||||
|
||||
feature -- Access: SSL
|
||||
|
||||
is_secure: BOOLEAN
|
||||
-- Is SSL/TLS session?.
|
||||
|
||||
ca_crt: STRING
|
||||
ca_crt: IMMUTABLE_STRING_8
|
||||
-- the signed certificate.
|
||||
|
||||
ca_key: STRING
|
||||
ca_key: IMMUTABLE_STRING_8
|
||||
-- private key to the certificate.
|
||||
|
||||
ssl_protocol: NATURAL
|
||||
@@ -74,7 +110,6 @@ feature -- Element change
|
||||
else
|
||||
create {IMMUTABLE_STRING_8} http_server_name.make_from_separate (v)
|
||||
end
|
||||
--| Missing postcondition.
|
||||
end
|
||||
|
||||
unset_http_server_name
|
||||
@@ -109,23 +144,35 @@ feature -- Element change
|
||||
max_concurrent_connections_set : max_concurrent_connections = v
|
||||
end
|
||||
|
||||
set_socket_accept_timeout (v: like socket_accept_timeout)
|
||||
-- Set `socket_accept_timeout' with `v'
|
||||
set_socket_timeout (a_nb_seconds: like socket_timeout)
|
||||
-- Set `socket_timeout' with `a_nb_seconds'
|
||||
do
|
||||
socket_accept_timeout := v
|
||||
socket_timeout := a_nb_seconds
|
||||
ensure
|
||||
socket_accept_timeout_set: socket_accept_timeout = v
|
||||
socket_timeout_set: socket_timeout = a_nb_seconds
|
||||
end
|
||||
|
||||
set_socket_connect_timeout (v: like socket_connect_timeout)
|
||||
-- Set `socket_connect_timeout' with `v'
|
||||
set_keep_alive_timeout (a_seconds: like keep_alive_timeout)
|
||||
-- Set `keep_alive_timeout' with `a_seconds'
|
||||
do
|
||||
socket_connect_timeout := v
|
||||
keep_alive_timeout := a_seconds
|
||||
ensure
|
||||
socket_connect_timeout_set: socket_connect_timeout = v
|
||||
keep_alive_timeout_set: keep_alive_timeout = a_seconds
|
||||
end
|
||||
|
||||
set_max_keep_alive_requests (nb: like max_keep_alive_requests)
|
||||
-- Set `max_keep_alive_requests' with `nb'
|
||||
do
|
||||
max_keep_alive_requests := nb
|
||||
ensure
|
||||
max_keep_alive_requests_set: max_keep_alive_requests = nb
|
||||
end
|
||||
|
||||
set_force_single_threaded (v: like force_single_threaded)
|
||||
-- Force server to handle incoming request in a single thread.
|
||||
-- i.e set max_concurrent_connections to 0!
|
||||
obsolete
|
||||
"Use set_max_concurrent_connections (0) [June/2016]"
|
||||
do
|
||||
if v then
|
||||
set_max_concurrent_connections (0)
|
||||
@@ -143,12 +190,12 @@ feature -- Element change
|
||||
is_verbose_set: is_verbose = b
|
||||
end
|
||||
|
||||
set_keep_alive_timeout (a_seconds: like keep_alive_timeout)
|
||||
-- Set `keep_alive_timeout' with `a_seconds'
|
||||
set_verbose_level (lev: INTEGER)
|
||||
-- Set `verbose_level' to `lev'.
|
||||
do
|
||||
keep_alive_timeout := a_seconds
|
||||
verbose_level := lev
|
||||
ensure
|
||||
keep_alive_timeout_set: keep_alive_timeout = a_seconds
|
||||
verbose_level_set: verbose_level = lev
|
||||
end
|
||||
|
||||
mark_secure
|
||||
@@ -171,20 +218,16 @@ feature -- Element change
|
||||
|
||||
feature -- Element change
|
||||
|
||||
set_ca_crt (a_value: STRING)
|
||||
-- Set `ca_crt' with `a_value'
|
||||
set_ca_crt (a_value: separate READABLE_STRING_8)
|
||||
-- Set `ca_crt' from `a_value'.
|
||||
do
|
||||
ca_crt := a_value
|
||||
ensure
|
||||
ca_crt_set: ca_crt = a_value
|
||||
create ca_crt.make_from_separate (a_value)
|
||||
end
|
||||
|
||||
set_ca_key (a_value: STRING)
|
||||
-- Set `ca_key' with `a_value'
|
||||
set_ca_key (a_value: separate READABLE_STRING_8)
|
||||
-- Set `ca_key' with `a_value'.
|
||||
do
|
||||
ca_key := a_value
|
||||
ensure
|
||||
ca_key_set: ca_key = a_value
|
||||
create ca_key.make_from_separate (a_value)
|
||||
end
|
||||
|
||||
set_ssl_protocol (a_version: NATURAL)
|
||||
@@ -0,0 +1,64 @@
|
||||
note
|
||||
description: "[
|
||||
Request settings for the standalone HTTPd server.
|
||||
]"
|
||||
author: "$Author$"
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
expanded class
|
||||
HTTPD_REQUEST_SETTINGS
|
||||
|
||||
feature -- Access
|
||||
|
||||
is_verbose: BOOLEAN assign set_is_verbose
|
||||
-- Is verbose?
|
||||
|
||||
verbose_level: INTEGER assign set_verbose_level
|
||||
-- Verbosity of output.
|
||||
|
||||
timeout: INTEGER assign set_timeout
|
||||
-- Amount of seconds that the server waits for receipts and transmissions during communications.
|
||||
|
||||
keep_alive_timeout: INTEGER assign set_keep_alive_timeout
|
||||
-- Keep-alive timeout, also known as persistent-connection timeout.
|
||||
-- Number of seconds the server waits after a request has been served before it closes the connection.
|
||||
-- Unit in Seconds.
|
||||
|
||||
max_keep_alive_requests: INTEGER assign set_max_keep_alive_requests
|
||||
-- Maximum number of requests allowed per persistent connection.
|
||||
|
||||
feature -- Change
|
||||
|
||||
set_is_verbose (b: BOOLEAN)
|
||||
-- Set `is_verbose' to `b'.
|
||||
do
|
||||
is_verbose := b
|
||||
end
|
||||
|
||||
set_verbose_level (lev: INTEGER)
|
||||
-- Set `verbose_level' to `lev'.
|
||||
do
|
||||
verbose_level := lev
|
||||
end
|
||||
|
||||
set_timeout (a_timeout_in_seconds: INTEGER)
|
||||
-- Set `timeout' to `a_timeout_in_seconds'.
|
||||
do
|
||||
timeout := a_timeout_in_seconds
|
||||
end
|
||||
|
||||
set_keep_alive_timeout (a_timeout_in_seconds: INTEGER)
|
||||
-- Set `keep_alive_timeout' to `a_timeout_in_seconds'.
|
||||
do
|
||||
keep_alive_timeout := a_timeout_in_seconds
|
||||
end
|
||||
|
||||
set_max_keep_alive_requests (nb: like max_keep_alive_requests)
|
||||
-- Set `max_keep_alive_requests' with `nb'
|
||||
do
|
||||
max_keep_alive_requests := nb
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
<?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="http_network" uuid="56DAA1CE-0A2E-451A-BFC9-7821578E79F0" library_target="http_network">
|
||||
<target name="http_network">
|
||||
<root all_classes="true"/>
|
||||
<file_rule>
|
||||
<exclude>/.git$</exclude>
|
||||
<exclude>/.svn$</exclude>
|
||||
<exclude>/EIFGENs$</exclude>
|
||||
</file_rule>
|
||||
<option warning="true" full_class_checking="false" is_attached_by_default="true" void_safety="all" syntax="standard">
|
||||
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
|
||||
</option>
|
||||
<setting name="concurrency" value="scoop"/>
|
||||
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
|
||||
<library name="net" location="$ISE_LIBRARY\library\net\net-safe.ecf"/>
|
||||
<library name="net_ssl" location="$ISE_LIBRARY\unstable\library\network\socket\netssl\net_ssl-safe.ecf">
|
||||
<condition>
|
||||
<custom name="net_ssl_enabled" value="true"/>
|
||||
</condition>
|
||||
</library>
|
||||
<cluster name="network" location=".\network" recursive="false">
|
||||
<cluster name="ssl_network" location="$|ssl" recursive="true">
|
||||
<condition>
|
||||
<custom name="net_ssl_enabled" value="true"/>
|
||||
</condition>
|
||||
</cluster>
|
||||
</cluster>
|
||||
</target>
|
||||
</system>
|
||||
@@ -0,0 +1,29 @@
|
||||
<?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="http_network" uuid="56DAA1CE-0A2E-451A-BFC9-7821578E79F0" library_target="http_network">
|
||||
<target name="http_network">
|
||||
<root all_classes="true"/>
|
||||
<file_rule>
|
||||
<exclude>/.git$</exclude>
|
||||
<exclude>/.svn$</exclude>
|
||||
<exclude>/EIFGENs$</exclude>
|
||||
</file_rule>
|
||||
<option warning="true" full_class_checking="false" void_safety="none" syntax="standard">
|
||||
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
|
||||
</option>
|
||||
<setting name="concurrency" value="scoop"/>
|
||||
<library name="base" location="$ISE_LIBRARY\library\base\base.ecf"/>
|
||||
<library name="net" location="$ISE_LIBRARY\library\net\net.ecf"/>
|
||||
<library name="net_ssl" location="$ISE_LIBRARY\unstable\library\network\socket\netssl\net_ssl.ecf">
|
||||
<condition>
|
||||
<custom name="net_ssl_enabled" value="true"/>
|
||||
</condition>
|
||||
</library>
|
||||
<cluster name="network" location=".\network\">
|
||||
<cluster name="ssl_network" location="$|ssl\" recursive="true">
|
||||
<condition>
|
||||
<custom name="net_ssl_enabled" value="true"/>
|
||||
</condition>
|
||||
</cluster>
|
||||
</cluster>
|
||||
</target>
|
||||
</system>
|
||||
@@ -24,11 +24,19 @@
|
||||
</condition>
|
||||
</library>
|
||||
<library name="time" location="$ISE_LIBRARY\library\time\time-safe.ecf"/>
|
||||
<cluster name="network" location=".\network" recursive="false">
|
||||
<cluster name="ssl_network" location="$|ssl" recursive="true">
|
||||
<condition>
|
||||
<custom name="httpd_ssl_enabled" value="true"/>
|
||||
</condition>
|
||||
</cluster>
|
||||
</cluster>
|
||||
<cluster name="httpd_server" location=".\" recursive="true">
|
||||
<file_rule>
|
||||
<exclude>/concurrency$</exclude>
|
||||
<exclude>/no_ssl$</exclude>
|
||||
<exclude>/ssl$</exclude>
|
||||
<exclude>/network$</exclude>
|
||||
</file_rule>
|
||||
<cluster name="no_ssl" location="$|no_ssl\" recursive="true">
|
||||
<condition>
|
||||
@@ -24,12 +24,19 @@
|
||||
</condition>
|
||||
</library>
|
||||
<library name="time" location="$ISE_LIBRARY\library\time\time.ecf"/>
|
||||
<cluster name="network" location=".\network" recursive="false">
|
||||
<cluster name="ssl_network" location="$|ssl" recursive="true">
|
||||
<condition>
|
||||
<custom name="httpd_ssl_enabled" value="true"/>
|
||||
</condition>
|
||||
</cluster>
|
||||
</cluster>
|
||||
<cluster name="httpd_server" location=".\" recursive="true">
|
||||
<file_rule>
|
||||
<exclude>/EIFGENs$</exclude>
|
||||
<exclude>/concurrency$</exclude>
|
||||
<exclude>/no_ssl$</exclude>
|
||||
<exclude>/ssl$</exclude>
|
||||
<exclude>/network$</exclude>
|
||||
</file_rule>
|
||||
<cluster name="no_ssl" location="$|no_ssl\" recursive="true">
|
||||
<condition>
|
||||
@@ -0,0 +1,22 @@
|
||||
note
|
||||
description: "[
|
||||
Constant value to define the logging level.
|
||||
]"
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
deferred class
|
||||
HTTPD_LOGGER_CONSTANTS
|
||||
|
||||
feature -- Access
|
||||
|
||||
alert_level: INTEGER = 1 -- 0000 0001
|
||||
critical_level: INTEGER = 2 -- 0000 0010
|
||||
error_level: INTEGER = 4 -- 0000 0100
|
||||
warning_level: INTEGER = 8 -- 0000 1000
|
||||
|
||||
notice_level: INTEGER = 16 -- 0001 0000
|
||||
information_level: INTEGER = 32 -- 0010 0000
|
||||
debug_level: INTEGER = 64 -- 0100 0000
|
||||
|
||||
end
|
||||
@@ -9,11 +9,20 @@ deferred class
|
||||
inherit
|
||||
HTTPD_DEBUG_FACILITIES
|
||||
|
||||
HTTPD_LOGGER_CONSTANTS
|
||||
|
||||
feature {NONE} -- Initialization
|
||||
|
||||
make
|
||||
make (a_request_settings: HTTPD_REQUEST_SETTINGS)
|
||||
do
|
||||
reset
|
||||
-- Import global request settings.
|
||||
timeout := a_request_settings.timeout -- seconds
|
||||
keep_alive_timeout := a_request_settings.keep_alive_timeout -- seconds
|
||||
max_keep_alive_requests := a_request_settings.max_keep_alive_requests
|
||||
|
||||
is_verbose := a_request_settings.is_verbose
|
||||
verbose_level := a_request_settings.verbose_level
|
||||
end
|
||||
|
||||
reset
|
||||
@@ -107,16 +116,29 @@ feature -- Access
|
||||
feature -- Settings
|
||||
|
||||
is_verbose: BOOLEAN
|
||||
-- Output messages?
|
||||
|
||||
verbose_level: INTEGER
|
||||
-- Output verbosity.
|
||||
|
||||
is_persistent_connection_supported: BOOLEAN
|
||||
-- Is persistent connection supported?
|
||||
do
|
||||
Result := {HTTPD_SERVER}.is_persistent_connection_supported
|
||||
end
|
||||
Result := {HTTPD_SERVER}.is_persistent_connection_supported and then max_keep_alive_requests > 0
|
||||
end
|
||||
|
||||
persistent_connection_timeout: INTEGER = 5 -- seconds
|
||||
is_next_persistent_connection_supported: BOOLEAN
|
||||
-- Is next persistent connection supported?
|
||||
-- note: it is relevant only if `is_persistent_connection_supported' is True.
|
||||
|
||||
timeout: INTEGER -- seconds
|
||||
-- Amount of seconds that the server waits for receipts and transmissions during communications.
|
||||
|
||||
max_keep_alive_requests: INTEGER
|
||||
-- Maximum number of requests allowed per persistent connection.
|
||||
|
||||
keep_alive_timeout: INTEGER -- seconds
|
||||
-- Number of seconds for persistent connection timeout.
|
||||
-- Default: 5 sec.
|
||||
|
||||
feature -- Status report
|
||||
|
||||
@@ -162,7 +184,7 @@ feature -- Execution
|
||||
local
|
||||
l_socket: like client_socket
|
||||
l_exit: BOOLEAN
|
||||
n: INTEGER
|
||||
n,m: INTEGER
|
||||
do
|
||||
l_socket := client_socket
|
||||
check
|
||||
@@ -172,15 +194,23 @@ feature -- Execution
|
||||
from
|
||||
-- Process persistent connection as long the socket is not closed.
|
||||
n := 0
|
||||
m := max_keep_alive_requests
|
||||
is_next_persistent_connection_supported := True
|
||||
until
|
||||
l_exit
|
||||
loop
|
||||
n := n + 1
|
||||
if n >= m then
|
||||
is_next_persistent_connection_supported := False
|
||||
elseif n > 1 and is_verbose then
|
||||
log ("Reuse connection (" + n.out + ")", information_level)
|
||||
end
|
||||
-- FIXME: it seems to be called one more time, mostly to see this is done.
|
||||
execute_request
|
||||
l_exit := not is_persistent_connection_supported
|
||||
or has_error or l_socket.is_closed or not l_socket.is_open_read
|
||||
or not is_next_persistent_connection_supported -- related to `max_keep_alive_requests'
|
||||
or not is_persistent_connection_requested
|
||||
or has_error or l_socket.is_closed or not l_socket.is_open_read
|
||||
reset_request
|
||||
end
|
||||
end
|
||||
@@ -192,7 +222,6 @@ feature -- Execution
|
||||
l_remote_info: detachable like remote_info
|
||||
l_socket: like client_socket
|
||||
l_is_ready: BOOLEAN
|
||||
i: INTEGER
|
||||
do
|
||||
l_socket := client_socket
|
||||
check
|
||||
@@ -210,19 +239,11 @@ feature -- Execution
|
||||
|
||||
--| TODO: add configuration options for socket timeout.
|
||||
--| set by default 5 seconds.
|
||||
-- l_socket.set_timeout (persistent_connection_timeout) -- 5 seconds!
|
||||
l_socket.set_timeout (1) -- 1 second!
|
||||
from
|
||||
i := persistent_connection_timeout -- * 1 sec
|
||||
until
|
||||
l_is_ready or i <= 0 or has_error
|
||||
loop
|
||||
l_is_ready := l_socket.ready_for_reading
|
||||
check not l_socket.is_closed end
|
||||
i := i - 1
|
||||
end
|
||||
l_socket.set_timeout (keep_alive_timeout) -- 5 seconds!
|
||||
l_is_ready := l_socket.ready_for_reading
|
||||
|
||||
if l_is_ready then
|
||||
l_socket.set_timeout (timeout) -- FIXME: return a 408 Request Timeout response ..
|
||||
create l_remote_info
|
||||
if attached l_socket.peer_address as l_addr then
|
||||
l_remote_info.addr := l_addr.host_address.host_address
|
||||
@@ -242,10 +263,13 @@ feature -- Execution
|
||||
if l_is_ready then
|
||||
-- check catch_bad_incoming_connection: False end
|
||||
if is_verbose then
|
||||
log ("ERROR: invalid HTTP incoming request")
|
||||
log (request_header + "%NWARNING: invalid HTTP incoming request", warning_level)
|
||||
end
|
||||
end
|
||||
else
|
||||
if is_verbose then
|
||||
log (request_header, information_level)
|
||||
end
|
||||
process_request (l_socket)
|
||||
end
|
||||
debug ("dbglog")
|
||||
@@ -289,11 +313,11 @@ feature -- Parsing
|
||||
do
|
||||
create txt.make (64)
|
||||
request_header := txt
|
||||
if
|
||||
if
|
||||
not has_error and then
|
||||
a_socket.is_readable and then
|
||||
attached next_line (a_socket) as l_request_line and then
|
||||
not l_request_line.is_empty
|
||||
not l_request_line.is_empty
|
||||
then
|
||||
txt.append (l_request_line)
|
||||
txt.append_character ('%N')
|
||||
@@ -310,8 +334,10 @@ feature -- Parsing
|
||||
line = Void or end_of_stream or has_error
|
||||
loop
|
||||
n := line.count
|
||||
if l_is_verbose then
|
||||
log (line)
|
||||
debug ("ew_standalone")
|
||||
if l_is_verbose then
|
||||
log (line, debug_level)
|
||||
end
|
||||
end
|
||||
pos := line.index_of (':', 1)
|
||||
if pos > 0 then
|
||||
@@ -358,9 +384,11 @@ feature -- Parsing
|
||||
local
|
||||
n, pos, next_pos: INTEGER
|
||||
do
|
||||
if is_verbose then
|
||||
log ("%N## Parse HTTP request line ##")
|
||||
log (line)
|
||||
debug ("ew_standalone")
|
||||
if is_verbose then
|
||||
log ("%N## Parse HTTP request line ##", debug_level)
|
||||
log (line, debug_level)
|
||||
end
|
||||
end
|
||||
pos := line.index_of (' ', 1)
|
||||
method := line.substring (1, pos - 1)
|
||||
@@ -377,7 +405,7 @@ feature -- Parsing
|
||||
next_line (a_socket: HTTPD_STREAM_SOCKET): detachable STRING
|
||||
-- Next line fetched from `a_socket' is available.
|
||||
require
|
||||
not_has_error: not has_error
|
||||
not_has_error: not has_error or is_verbose
|
||||
is_readable: a_socket.is_open_read
|
||||
local
|
||||
retried: BOOLEAN
|
||||
@@ -388,19 +416,19 @@ feature -- Parsing
|
||||
elseif a_socket.readable then
|
||||
a_socket.read_line_thread_aware
|
||||
Result := a_socket.last_string
|
||||
-- Do no check `socket_ok' before socket operation,
|
||||
-- Do no check `socket_ok' before socket operation,
|
||||
-- otherwise it may be False, due to error during other socket operation in same thread.
|
||||
if not a_socket.socket_ok then
|
||||
has_error := True
|
||||
if is_verbose then
|
||||
log ("%N## Socket is not ok! ##")
|
||||
log (request_header +"%N" + Result + "%N## socket_ok=False! ##", debug_level)
|
||||
end
|
||||
end
|
||||
else
|
||||
-- Error with socket...
|
||||
has_error := True
|
||||
if is_verbose then
|
||||
log ("%N## Socket is not readable! ##")
|
||||
log (request_header + "%N## Socket is not readable! ##", debug_level)
|
||||
end
|
||||
end
|
||||
rescue
|
||||
@@ -420,13 +448,17 @@ feature -- Output
|
||||
logger_set: logger = a_logger
|
||||
end
|
||||
|
||||
log (m: STRING)
|
||||
log (m: STRING; a_level: INTEGER)
|
||||
-- Log message `m'.
|
||||
require
|
||||
is_verbose: is_verbose
|
||||
do
|
||||
if attached logger as l_logger then
|
||||
l_logger.log (m)
|
||||
else
|
||||
io.put_string (m + "%N")
|
||||
if is_verbose and (verbose_level & a_level) = a_level then
|
||||
if attached logger as l_logger then
|
||||
l_logger.log (m)
|
||||
else
|
||||
io.put_string (m + "%N")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -37,7 +37,7 @@ feature {NONE} -- Initialization
|
||||
build_controller
|
||||
-- Build `controller'.
|
||||
do
|
||||
create controller
|
||||
create <NONE> controller
|
||||
end
|
||||
|
||||
initialize
|
||||
@@ -51,6 +51,9 @@ feature -- Access
|
||||
is_verbose: BOOLEAN
|
||||
-- Is verbose for output messages.
|
||||
|
||||
verbose_level: INTEGER
|
||||
-- Verbosity of output.
|
||||
|
||||
configuration: HTTPD_CONFIGURATION
|
||||
-- Associated server configuration.
|
||||
|
||||
@@ -100,7 +103,16 @@ feature -- Execution
|
||||
apply_configuration
|
||||
is_terminated := False
|
||||
if is_verbose then
|
||||
log ("%N%NStarting Web Application Server (port=" + configuration.http_server_port.out + "):%N")
|
||||
log ("%N%NStarting Web Application Server ...")
|
||||
log (" - port = " + configuration.http_server_port.out)
|
||||
log (" - max_tcp_clients = " + configuration.max_tcp_clients.out)
|
||||
log (" - max_concurrent_connections = " + configuration.max_concurrent_connections.out)
|
||||
log (" - socket_timeout = " + configuration.socket_timeout.out + " seconds")
|
||||
log (" - keep_alive_timeout = " + configuration.keep_alive_timeout.out + " seconds")
|
||||
log (" - max_keep_alive_requests = " + configuration.max_keep_alive_requests.out)
|
||||
if configuration.verbose_level > 0 then
|
||||
log (" - verbose_level = " + configuration.verbose_level.out)
|
||||
end
|
||||
end
|
||||
is_shutdown_requested := False
|
||||
listen
|
||||
@@ -150,7 +162,7 @@ feature -- Listening
|
||||
|
||||
if not l_listening_socket.is_bound then
|
||||
if is_verbose then
|
||||
log ("Socket could not be bound on port " + l_http_port.out)
|
||||
log ("Socket could not be bound on port " + l_http_port.out + " !")
|
||||
end
|
||||
else
|
||||
l_http_port := l_listening_socket.port
|
||||
@@ -159,9 +171,9 @@ feature -- Listening
|
||||
l_listening_socket.listen (configuration.max_tcp_clients)
|
||||
if is_verbose then
|
||||
if configuration.is_secure then
|
||||
log ("%NHTTP Connection Server ready on port " + l_http_port.out +" : https://localhost:" + l_http_port.out + "/")
|
||||
log ("%NListening on port " + l_http_port.out +" : https://localhost:" + l_http_port.out + "/")
|
||||
else
|
||||
log ("%NHTTP Connection Server ready on port " + l_http_port.out +" : http://localhost:" + l_http_port.out + "/")
|
||||
log ("%NListening on port " + l_http_port.out +" : http://localhost:" + l_http_port.out + "/")
|
||||
end
|
||||
end
|
||||
on_launched (l_http_port)
|
||||
@@ -312,6 +324,7 @@ feature -- Configuration change
|
||||
is_not_launched: not is_launched
|
||||
do
|
||||
is_verbose := configuration.is_verbose
|
||||
verbose_level := configuration.verbose_level
|
||||
end
|
||||
|
||||
feature -- Output
|
||||
@@ -12,6 +12,8 @@ class
|
||||
create
|
||||
make_server_by_address_and_port,
|
||||
make_server_by_port,
|
||||
make_client_by_address_and_port,
|
||||
make_client_by_port,
|
||||
make_from_separate,
|
||||
make_empty
|
||||
|
||||
@@ -30,6 +32,16 @@ feature {NONE} -- Initialization
|
||||
create {TCP_STREAM_SOCKET} socket.make_server_by_port (a_port)
|
||||
end
|
||||
|
||||
make_client_by_address_and_port (an_address: INET_ADDRESS; a_port: INTEGER)
|
||||
do
|
||||
create {TCP_STREAM_SOCKET} socket.make_client_by_address_and_port (an_address, a_port)
|
||||
end
|
||||
|
||||
make_client_by_port (a_peer_port: INTEGER; a_peer_host: STRING)
|
||||
do
|
||||
create {TCP_STREAM_SOCKET} socket.make_client_by_port (a_peer_port, a_peer_host)
|
||||
end
|
||||
|
||||
make_from_separate (s: separate HTTPD_STREAM_SOCKET)
|
||||
require
|
||||
descriptor_available: s.descriptor_available
|
||||
@@ -164,6 +176,11 @@ feature -- Status Report
|
||||
end
|
||||
end
|
||||
|
||||
exists: BOOLEAN
|
||||
do
|
||||
Result := socket.exists
|
||||
end
|
||||
|
||||
is_blocking: BOOLEAN
|
||||
do
|
||||
Result := socket.is_blocking
|
||||
@@ -176,6 +193,13 @@ feature -- Status Report
|
||||
end
|
||||
end
|
||||
|
||||
is_connected: BOOLEAN
|
||||
do
|
||||
if attached {TCP_STREAM_SOCKET} socket as l_socket then
|
||||
Result := l_socket.is_connected
|
||||
end
|
||||
end
|
||||
|
||||
is_created: BOOLEAN
|
||||
do
|
||||
if attached {NETWORK_SOCKET} socket as l_socket then
|
||||
@@ -220,6 +244,16 @@ feature -- Status Report
|
||||
end
|
||||
end
|
||||
|
||||
connect
|
||||
do
|
||||
socket.connect
|
||||
end
|
||||
|
||||
close
|
||||
do
|
||||
socket.close
|
||||
end
|
||||
|
||||
listen (a_queue: INTEGER)
|
||||
do
|
||||
socket.listen (a_queue)
|
||||
@@ -22,7 +22,9 @@ inherit
|
||||
|
||||
create
|
||||
make_ssl_server_by_address_and_port, make_ssl_server_by_port,
|
||||
make_server_by_address_and_port, make_server_by_port
|
||||
make_server_by_address_and_port, make_server_by_port,
|
||||
make_ssl_client_by_address_and_port, make_ssl_client_by_port,
|
||||
make_client_by_address_and_port, make_client_by_port
|
||||
|
||||
create {HTTPD_STREAM_SOCKET}
|
||||
make
|
||||
@@ -49,6 +51,26 @@ feature {NONE} -- Initialization
|
||||
set_certificates (a_crt, a_key)
|
||||
end
|
||||
|
||||
make_ssl_client_by_address_and_port (an_address: INET_ADDRESS; a_port: INTEGER; a_ssl_protocol: NATURAL; a_crt: STRING; a_key: STRING)
|
||||
local
|
||||
l_socket: SSL_TCP_STREAM_SOCKET
|
||||
do
|
||||
create l_socket.make_client_by_address_and_port (an_address, a_port)
|
||||
l_socket.set_tls_protocol (a_ssl_protocol)
|
||||
socket := l_socket
|
||||
set_certificates (a_crt, a_key)
|
||||
end
|
||||
|
||||
make_ssl_client_by_port (a_peer_port: INTEGER; a_peer_host: STRING; a_ssl_protocol: NATURAL; a_crt: STRING; a_key: STRING)
|
||||
local
|
||||
l_socket: SSL_TCP_STREAM_SOCKET
|
||||
do
|
||||
create l_socket.make_client_by_port (a_peer_port, a_peer_host)
|
||||
l_socket.set_tls_protocol (a_ssl_protocol)
|
||||
socket := l_socket
|
||||
set_certificates (a_crt, a_key)
|
||||
end
|
||||
|
||||
feature -- Output
|
||||
|
||||
put_readable_string_8 (s: READABLE_STRING_8)
|
||||
@@ -12,6 +12,7 @@ inherit
|
||||
|
||||
create
|
||||
make_server_by_address_and_port, make_server_by_port,
|
||||
make_client_by_address_and_port, make_client_by_port,
|
||||
make_empty
|
||||
|
||||
create {SSL_NETWORK_STREAM_SOCKET}
|
||||
@@ -15,6 +15,8 @@ inherit
|
||||
create
|
||||
make_server_by_address_and_port,
|
||||
make_server_by_port,
|
||||
make_client_by_address_and_port,
|
||||
make_client_by_port,
|
||||
make_from_separate,
|
||||
make_empty
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
package httpd
|
||||
|
||||
project
|
||||
httpd = "httpd-safe.ecf"
|
||||
httpd = "httpd.ecf"
|
||||
|
||||
note
|
||||
title: HTTP server
|
||||
description: "[
|
||||
Simple HTTP listener and handler, that can be extended easily.
|
||||
]"
|
||||
tags: http,httpd,server,web
|
||||
collection: EWF
|
||||
copyright: 2011-2016, Jocelyn Fiat, Javier Velilla, Olivier Ligot, Colin Adams, Eiffel Software and others
|
||||
license: Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)
|
||||
link[license]: http://www.eiffel.com/licensing/forum.txt
|
||||
link[source]: "Github" https://github.com/EiffelWebFramework/EWF/library/server/httpd
|
||||
link[doc]: "Documentation" http://eiffelwebframework.github.io/EWF/
|
||||
|
||||
end
|
||||
@@ -24,13 +24,10 @@ create
|
||||
|
||||
feature {NONE} -- Initialization
|
||||
|
||||
make_with_connector (conn: like connector)
|
||||
make_with_connector (a_request_settings: HTTPD_REQUEST_SETTINGS; conn: like connector)
|
||||
do
|
||||
make
|
||||
make (a_request_settings)
|
||||
connector := conn
|
||||
-- if conn /= Void then
|
||||
-- set_is_verbose (is_connector_verbose (conn))
|
||||
-- end
|
||||
end
|
||||
|
||||
feature -- Access
|
||||
@@ -56,11 +53,6 @@ feature -- SCOOP helpers
|
||||
Result := conn.base
|
||||
end
|
||||
|
||||
is_connector_verbose (conn: separate WGI_STANDALONE_CONNECTOR [G]): BOOLEAN
|
||||
do
|
||||
Result := conn.is_verbose
|
||||
end
|
||||
|
||||
feature -- Request processing
|
||||
|
||||
process_request (a_socket: HTTPD_STREAM_SOCKET)
|
||||
@@ -87,7 +79,7 @@ feature -- Request processing
|
||||
else
|
||||
l_output.set_http_version (version)
|
||||
end
|
||||
res.set_is_persistent_connection_supported ({HTTPD_SERVER}.is_persistent_connection_supported)
|
||||
res.set_is_persistent_connection_supported (is_persistent_connection_supported and is_next_persistent_connection_supported)
|
||||
res.set_is_persistent_connection_requested (is_persistent_connection_requested)
|
||||
|
||||
req.set_meta_string_variable ("RAW_HEADER_DATA", request_header)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
note
|
||||
description: "Implementation of WGI request handler factory for WGI_STANDALOE_CONNECTOR."
|
||||
description: "Implementation of WGI request handler factory for WGI_STANDALONE_CONNECTOR."
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
@@ -14,19 +14,23 @@ feature -- Access
|
||||
connector: detachable separate WGI_STANDALONE_CONNECTOR [G]
|
||||
-- httpd solution.
|
||||
|
||||
request_settings: HTTPD_REQUEST_SETTINGS
|
||||
-- Settings specific to request handling.
|
||||
|
||||
feature -- Element change
|
||||
|
||||
set_connector (conn: like connector)
|
||||
update_with (conn: like connector; a_conf: separate HTTPD_CONFIGURATION)
|
||||
-- Set `connector' with `conn'.
|
||||
do
|
||||
connector := conn
|
||||
request_settings := a_conf.request_settings
|
||||
end
|
||||
|
||||
feature -- Factory
|
||||
|
||||
new_handler: separate WGI_HTTPD_REQUEST_HANDLER [G]
|
||||
do
|
||||
create Result.make_with_connector (connector)
|
||||
create Result.make_with_connector (request_settings, connector)
|
||||
end
|
||||
|
||||
note
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
note
|
||||
description: "[
|
||||
Standalone Web Server connector
|
||||
Standalone Web Server connector.
|
||||
]"
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
@@ -20,18 +20,18 @@ feature {NONE} -- Initialization
|
||||
make
|
||||
-- Create current standalone connector.
|
||||
local
|
||||
fac: separate WGI_HTTPD_REQUEST_HANDLER_FACTORY [G]
|
||||
fac: like request_handler_factory
|
||||
do
|
||||
-- Callbacks
|
||||
create on_launched_actions
|
||||
|
||||
-- Server
|
||||
create fac
|
||||
create <NONE> fac
|
||||
request_handler_factory := fac
|
||||
create server.make (fac)
|
||||
create observer
|
||||
create <NONE> observer
|
||||
configuration := server_configuration (server)
|
||||
controller := server_controller (server)
|
||||
set_factory_connector (Current, fac)
|
||||
initialize_server (server)
|
||||
end
|
||||
|
||||
@@ -51,9 +51,9 @@ feature {NONE} -- Separate helper
|
||||
a_server.set_observer (observer)
|
||||
end
|
||||
|
||||
set_factory_connector (conn: detachable separate WGI_STANDALONE_CONNECTOR [G]; fac: separate WGI_HTTPD_REQUEST_HANDLER_FACTORY [G])
|
||||
update_factory (conn: detachable separate WGI_STANDALONE_CONNECTOR [G]; fac: separate WGI_HTTPD_REQUEST_HANDLER_FACTORY [G]; a_conf: like configuration)
|
||||
do
|
||||
fac.set_connector (conn)
|
||||
fac.update_with (conn, a_conf)
|
||||
end
|
||||
|
||||
server_configuration (a_server: like server): like configuration
|
||||
@@ -63,17 +63,26 @@ feature {NONE} -- Separate helper
|
||||
|
||||
feature -- Access
|
||||
|
||||
name: STRING_8 = "httpd"
|
||||
name: STRING_8
|
||||
-- Name of Current connector
|
||||
once
|
||||
Result := "httpd"
|
||||
end
|
||||
|
||||
version: STRING_8 = "0.1"
|
||||
version: STRING_8
|
||||
-- Version of Current connector
|
||||
once
|
||||
Result := "1.0"
|
||||
end
|
||||
|
||||
feature -- Access
|
||||
|
||||
server: separate HTTPD_SERVER
|
||||
-- HTTPd server object.
|
||||
|
||||
request_handler_factory: separate WGI_HTTPD_REQUEST_HANDLER_FACTORY [G]
|
||||
-- Factory for request handlers.
|
||||
|
||||
controller: separate HTTPD_CONTROLLER
|
||||
-- Controller used to shutdown server.
|
||||
|
||||
@@ -97,9 +106,6 @@ feature -- Status report
|
||||
-- Listening port.
|
||||
--| 0: not launched
|
||||
|
||||
is_verbose: BOOLEAN
|
||||
-- Is verbose?
|
||||
|
||||
feature -- Callbacks
|
||||
|
||||
on_launched_actions: ACTION_SEQUENCE [TUPLE [WGI_STANDALONE_CONNECTOR [WGI_EXECUTION]]]
|
||||
@@ -190,18 +196,22 @@ feature {NONE} -- Implementation
|
||||
Result := a_server.controller
|
||||
end
|
||||
|
||||
configure_server (a_configuration: like configuration)
|
||||
apply_configuration (a_configuration: like configuration)
|
||||
local
|
||||
v: BOOLEAN
|
||||
do
|
||||
if a_configuration.is_verbose then
|
||||
if attached base as l_base then
|
||||
v := a_configuration.is_verbose
|
||||
if v then
|
||||
if attached base as l_base and then not l_base.is_whitespace then
|
||||
io.error.put_string ("Base=" + l_base + "%N")
|
||||
end
|
||||
end
|
||||
update_factory (Current, request_handler_factory, a_configuration)
|
||||
end
|
||||
|
||||
launch_server (a_server: like server)
|
||||
do
|
||||
configure_server (a_server.configuration)
|
||||
apply_configuration (a_server.configuration)
|
||||
a_server.launch
|
||||
end
|
||||
|
||||
@@ -229,13 +239,12 @@ feature {NONE} -- Implementation: element change
|
||||
|
||||
set_is_verbose_on_configuration (b: BOOLEAN; cfg: like configuration)
|
||||
do
|
||||
is_verbose := b
|
||||
cfg.set_is_verbose (b)
|
||||
end
|
||||
|
||||
|
||||
note
|
||||
copyright: "2011-2015, 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)"
|
||||
source: "[
|
||||
Eiffel Software
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
note
|
||||
description: "[
|
||||
Interface to access protected feature from {WGI_STANDALONE_CONNECTOR}.
|
||||
]"
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
deferred class
|
||||
WGI_STANDALONE_CONNECTOR_EXPORTER
|
||||
|
||||
end
|
||||
@@ -0,0 +1,10 @@
|
||||
note
|
||||
description: "Export HTTPD_LOGGER_CONSTANTS to Standlone connector interfaces."
|
||||
|
||||
class
|
||||
WGI_STANDALONE_HTTPD_LOGGER_CONSTANTS
|
||||
|
||||
inherit
|
||||
HTTPD_LOGGER_CONSTANTS
|
||||
|
||||
end
|
||||
@@ -24,7 +24,7 @@ feature {NONE} -- Initialization
|
||||
set_source (a_source)
|
||||
end
|
||||
|
||||
feature {WGI_STANDALONE_CONNECTOR, WGI_SERVICE} -- Nino
|
||||
feature {WGI_STANDALONE_CONNECTOR, WGI_SERVICE, WGI_STANDALONE_CONNECTOR_EXPORTER} -- Standalone
|
||||
|
||||
set_source (i: like source)
|
||||
do
|
||||
|
||||
@@ -108,7 +108,6 @@ feature -- Header output operation
|
||||
l_connection := s.substring (i + 12, j - 1)
|
||||
l_connection.adjust
|
||||
if
|
||||
not is_http_version_1_0 and
|
||||
not l_connection.is_case_insensitive_equal_general ("close")
|
||||
then
|
||||
s.replace_substring ("Connection: close", i + 1, j - 1)
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
<library name="encoder" location="..\..\..\..\text\encoder\encoder-safe.ecf"/>
|
||||
<library name="ewsgi" location="..\..\ewsgi-safe.ecf" readonly="false"/>
|
||||
<library name="http" location="..\..\..\..\network\protocol\http\http-safe.ecf"/>
|
||||
<library name="httpd" location="src\httpd\httpd-safe.ecf" readonly="false"/>
|
||||
<library name="httpd" location="lib\httpd\httpd-safe.ecf" readonly="false"/>
|
||||
<cluster name="src" location=".\src\">
|
||||
<cluster name="implementation" location="$|implementation\" hidden="true"/>
|
||||
</cluster>
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<library name="encoder" location="..\..\..\..\text\encoder\encoder.ecf"/>
|
||||
<library name="ewsgi" location="..\..\ewsgi.ecf" readonly="false"/>
|
||||
<library name="http" location="..\..\..\..\network\protocol\http\http.ecf"/>
|
||||
<library name="httpd" location="src\httpd\httpd.ecf" readonly="false"/>
|
||||
<library name="httpd" location="lib\httpd\httpd.ecf" readonly="false"/>
|
||||
<cluster name="src" location=".\src\">
|
||||
<cluster name="implementation" location="$|implementation\" hidden="true"/>
|
||||
</cluster>
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="ISO-8859-1"?>
|
||||
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-15-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-15-0 http://www.eiffel.com/developers/xml/configuration-1-15-0.xsd" name="connector_standalone_websocket" uuid="38E6F413-E001-4774-9B4C-E0C08753D9F7" library_target="connector_standalone_websocket">
|
||||
<target name="connector_standalone_websocket">
|
||||
<root all_classes="true"/>
|
||||
<file_rule>
|
||||
<exclude>/EIFGENs$</exclude>
|
||||
<exclude>/\.git$</exclude>
|
||||
<exclude>/\.svn$</exclude>
|
||||
</file_rule>
|
||||
<option debug="true" warning="true" full_class_checking="true" is_attached_by_default="true" void_safety="all" syntax="transitional">
|
||||
<debug name="dbglog" enabled="true"/>
|
||||
<assertions precondition="true"/>
|
||||
</option>
|
||||
<setting name="concurrency" value="scoop"/>
|
||||
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
|
||||
<library name="encoder" location="..\..\..\..\text\encoder\encoder-safe.ecf"/>
|
||||
<library name="ewsgi" location="..\..\ewsgi-safe.ecf" readonly="false"/>
|
||||
<library name="http" location="..\..\..\..\network\protocol\http\http-safe.ecf"/>
|
||||
<library name="httpd" location="lib\httpd\httpd-safe.ecf" readonly="false"/>
|
||||
<cluster name="src" location=".\src\">
|
||||
<cluster name="implementation" location="$|implementation\" hidden="true"/>
|
||||
</cluster>
|
||||
</target>
|
||||
</system>
|
||||
@@ -10,12 +10,11 @@
|
||||
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
|
||||
<library name="connector_standalone" location="standalone-safe.ecf" readonly="false"/>
|
||||
<library name="ewsgi" location="..\..\ewsgi-safe.ecf" readonly="false"/>
|
||||
<library name="httpd_edit" location="src\httpd\httpd-safe.ecf" readonly="false">
|
||||
<library name="httpd_edit" location="lib\httpd\httpd-safe.ecf" readonly="false">
|
||||
<option debug="true">
|
||||
<debug name="dbglog" enabled="true"/>
|
||||
</option>
|
||||
</library>
|
||||
<library name="net_ssl_edit" location="$ISE_LIBRARY\unstable\library\network\socket\netssl\net_ssl-safe.ecf" readonly="false"/>
|
||||
<library name="wsf" location="..\..\..\wsf\wsf-safe.ecf" readonly="false"/>
|
||||
<cluster name="tests" location="tests\" recursive="true"/>
|
||||
</target>
|
||||
|
||||
@@ -25,6 +25,8 @@ inherit
|
||||
launchable
|
||||
end
|
||||
|
||||
WGI_STANDALONE_HTTPD_LOGGER_CONSTANTS
|
||||
|
||||
create
|
||||
make,
|
||||
make_and_launch
|
||||
@@ -34,38 +36,68 @@ feature {NONE} -- Initialization
|
||||
initialize
|
||||
local
|
||||
conn: like connector
|
||||
s: READABLE_STRING_GENERAL
|
||||
do
|
||||
create on_launched_actions
|
||||
create on_stopped_actions
|
||||
|
||||
port_number := 80 --| Default, but quite often, this port is already used ...
|
||||
max_concurrent_connections := 100
|
||||
max_tcp_clients := 100
|
||||
socket_timeout := 300 -- 300 seconds
|
||||
keep_alive_timeout := 15 -- 15 seconds.
|
||||
max_keep_alive_requests := 100
|
||||
verbose := False
|
||||
verbose_level := notice_level
|
||||
|
||||
base_url := ""
|
||||
|
||||
if attached options as opts then
|
||||
if attached {READABLE_STRING_GENERAL} opts.option ("server_name") as l_server_name then
|
||||
server_name := l_server_name.to_string_8
|
||||
end
|
||||
if attached {INTEGER} opts.option ("port") as l_port then
|
||||
port_number := l_port
|
||||
elseif
|
||||
attached {READABLE_STRING_GENERAL} opts.option ("port") as l_port_str and then
|
||||
l_port_str.is_integer
|
||||
then
|
||||
port_number := l_port_str.as_string_8.to_integer
|
||||
end
|
||||
if attached {READABLE_STRING_GENERAL} opts.option ("base") as l_base_str then
|
||||
base_url := l_base_str.as_string_8
|
||||
end
|
||||
if attached {BOOLEAN} opts.option ("force_single_threaded") as l_single_threaded then
|
||||
single_threaded := l_single_threaded
|
||||
elseif attached {READABLE_STRING_GENERAL} opts.option ("force_single_threaded") as l_single_threaded_str then
|
||||
single_threaded := l_single_threaded_str.as_lower.same_string ("true")
|
||||
verbose := opts.option_boolean_value ("verbose", verbose)
|
||||
-- See `{HTTPD_REQUEST_HANDLER_I}.*_verbose_level`
|
||||
|
||||
if opts.has_integer_option ("verbose_level") then
|
||||
verbose_level := opts.option_integer_value ("verbose_level", verbose_level)
|
||||
elseif attached {READABLE_STRING_GENERAL} opts.option ("verbose_level") as s_verbose_level then
|
||||
verbose_level := 0 -- Reset
|
||||
across
|
||||
s_verbose_level.split ('+') as ic
|
||||
loop
|
||||
s := ic.item
|
||||
if s.is_case_insensitive_equal ("alert") then
|
||||
verbose_level := verbose_level | alert_level
|
||||
elseif s.is_case_insensitive_equal ("critical") then
|
||||
verbose_level := verbose_level | critical_level
|
||||
elseif s.is_case_insensitive_equal ("error") then
|
||||
verbose_level := verbose_level | error_level
|
||||
elseif s.is_case_insensitive_equal ("warning") then
|
||||
verbose_level := verbose_level | warning_level
|
||||
elseif s.is_case_insensitive_equal ("notice") then
|
||||
verbose_level := verbose_level | notice_level
|
||||
elseif s.is_case_insensitive_equal ("information") then
|
||||
verbose_level := verbose_level | information_level
|
||||
elseif s.is_case_insensitive_equal ("debug") then
|
||||
verbose_level := verbose_level | debug_level
|
||||
else
|
||||
end
|
||||
end
|
||||
end
|
||||
if attached {BOOLEAN} opts.option ("verbose") as l_verbose then
|
||||
verbose := l_verbose
|
||||
elseif attached {READABLE_STRING_GENERAL} opts.option ("verbose") as l_verbose_str then
|
||||
verbose := l_verbose_str.as_lower.same_string ("true")
|
||||
port_number := opts.option_integer_value ("port", port_number)
|
||||
|
||||
if opts.option_boolean_value ("force_single_threaded", False) then
|
||||
force_single_threaded
|
||||
end
|
||||
max_concurrent_connections := opts.option_integer_value ("max_concurrent_connections", max_concurrent_connections)
|
||||
max_tcp_clients := opts.option_integer_value ("max_tcp_clients", max_tcp_clients)
|
||||
socket_timeout := opts.option_integer_value ("socket_timeout", socket_timeout)
|
||||
keep_alive_timeout := opts.option_integer_value ("keep_alive_timeout", keep_alive_timeout)
|
||||
max_keep_alive_requests := opts.option_integer_value ("max_keep_alive_requests", max_keep_alive_requests)
|
||||
end
|
||||
|
||||
create conn.make
|
||||
@@ -76,18 +108,25 @@ feature {NONE} -- Initialization
|
||||
update_configuration (conn.configuration)
|
||||
end
|
||||
|
||||
force_single_threaded
|
||||
-- Set `single_threaded' to True.
|
||||
do
|
||||
max_concurrent_connections := 1
|
||||
end
|
||||
|
||||
feature -- Execution
|
||||
|
||||
update_configuration (cfg: like connector.configuration)
|
||||
do
|
||||
if single_threaded then
|
||||
cfg.set_force_single_threaded (True)
|
||||
end
|
||||
cfg.set_is_verbose (verbose)
|
||||
if attached server_name as l_server_name then
|
||||
cfg.set_http_server_name (l_server_name)
|
||||
end
|
||||
cfg.set_verbose_level (verbose_level)
|
||||
cfg.set_http_server_name (server_name)
|
||||
cfg.http_server_port := port_number
|
||||
cfg.set_max_concurrent_connections (max_concurrent_connections)
|
||||
cfg.set_max_tcp_clients (max_tcp_clients)
|
||||
cfg.set_socket_timeout (socket_timeout)
|
||||
cfg.set_keep_alive_timeout (keep_alive_timeout)
|
||||
cfg.set_max_keep_alive_requests (max_keep_alive_requests)
|
||||
end
|
||||
|
||||
launch
|
||||
@@ -98,7 +137,7 @@ feature -- Execution
|
||||
do
|
||||
conn := connector
|
||||
conn.set_base (base_url)
|
||||
debug ("nino")
|
||||
debug ("ew_standalone")
|
||||
if verbose then
|
||||
io.error.put_string ("Launching standalone web server on port " + port_number.out)
|
||||
if attached server_name as l_name then
|
||||
@@ -114,15 +153,15 @@ feature -- Execution
|
||||
|
||||
feature -- Callback
|
||||
|
||||
on_launched_actions: ACTION_SEQUENCE [TUPLE [WGI_STANDALONE_CONNECTOR [G]]]
|
||||
on_launched_actions: ACTION_SEQUENCE [TUPLE [like connector]]
|
||||
-- Actions triggered when launched
|
||||
|
||||
on_stopped_actions: ACTION_SEQUENCE [TUPLE [WGI_STANDALONE_CONNECTOR [G]]]
|
||||
on_stopped_actions: ACTION_SEQUENCE [TUPLE [like connector]]
|
||||
-- Actions triggered when stopped
|
||||
|
||||
feature {NONE} -- Implementation
|
||||
|
||||
on_launched (conn: WGI_STANDALONE_CONNECTOR [G])
|
||||
on_launched (conn: like connector)
|
||||
do
|
||||
on_launched_actions.call ([conn])
|
||||
end
|
||||
@@ -134,8 +173,20 @@ feature {NONE} -- Implementation
|
||||
base_url: READABLE_STRING_8
|
||||
|
||||
verbose: BOOLEAN
|
||||
verbose_level: INTEGER
|
||||
-- Help defining the verbosity.
|
||||
-- The higher, the more output.
|
||||
|
||||
max_concurrent_connections: INTEGER
|
||||
max_tcp_clients: INTEGER
|
||||
socket_timeout: INTEGER
|
||||
keep_alive_timeout: INTEGER
|
||||
max_keep_alive_requests: INTEGER
|
||||
|
||||
single_threaded: BOOLEAN
|
||||
do
|
||||
Result := max_concurrent_connections = 0
|
||||
end
|
||||
|
||||
feature -- Status report
|
||||
|
||||
|
||||
25
library/server/wsf/connector/standalone_websocket-safe.ecf
Normal file
25
library/server/wsf/connector/standalone_websocket-safe.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-12-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-12-0 http://www.eiffel.com/developers/xml/configuration-1-12-0.xsd" name="wsf_standalone_websocket" uuid="7C83D4B4-39C9-4D27-941B-0F0AAD45122E" library_target="wsf_standalone_websocket">
|
||||
<target name="wsf_standalone_websocket">
|
||||
<root all_classes="true"/>
|
||||
<file_rule>
|
||||
<exclude>/EIFGENs$</exclude>
|
||||
<exclude>/\.git$</exclude>
|
||||
<exclude>/\.svn$</exclude>
|
||||
</file_rule>
|
||||
<option warning="true" full_class_checking="true" is_attached_by_default="true" void_safety="all" syntax="provisional">
|
||||
</option>
|
||||
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
|
||||
<library name="connector_standalone" location="..\..\ewsgi\connectors\standalone\standalone-safe.ecf"/>
|
||||
<library name="crypto" location="$ISE_LIBRARY\unstable\library\text\encryption\crypto\crypto-safe.ecf"/>
|
||||
<library name="encoder" location="..\..\..\text\encoder\encoder-safe.ecf" readonly="false"/>
|
||||
<library name="error" location="..\..\..\utility\general\error\error-safe.ecf"/>
|
||||
<library name="ewsgi" location="..\..\ewsgi\ewsgi-safe.ecf"/>
|
||||
<library name="http" location="..\..\..\network\protocol\http\http-safe.ecf"/>
|
||||
<library name="httpd" location="..\..\ewsgi\connectors\standalone\lib\httpd\httpd-safe.ecf"/>
|
||||
<library name="time" location="$ISE_LIBRARY\library\time\time-safe.ecf"/>
|
||||
<library name="wsf" location="..\wsf-safe.ecf"/>
|
||||
<library name="wsf_standalone" location="standalone-safe.ecf"/>
|
||||
<cluster name="wsf_standalone_websocket" location=".\standalone_websocket\" recursive="true"/>
|
||||
</target>
|
||||
</system>
|
||||
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>
|
||||
@@ -0,0 +1,137 @@
|
||||
note
|
||||
description: "[
|
||||
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$"
|
||||
|
||||
deferred class
|
||||
WEB_SOCKET_EVENT_I
|
||||
|
||||
inherit
|
||||
WEB_SOCKET_CONSTANTS
|
||||
|
||||
REFACTORING_HELPER
|
||||
|
||||
feature -- Web Socket Interface
|
||||
|
||||
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
|
||||
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")
|
||||
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
|
||||
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
|
||||
|
||||
feature -- Websocket events
|
||||
|
||||
on_open (ws: WEB_SOCKET)
|
||||
-- Called after handshake, indicates that a complete WebSocket connection has been established.
|
||||
require
|
||||
ws_attached: ws /= Void
|
||||
ws_valid: ws.is_open_read and then ws.is_open_write
|
||||
deferred
|
||||
end
|
||||
|
||||
on_binary (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
|
||||
deferred
|
||||
end
|
||||
|
||||
on_text (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
|
||||
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 (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
|
||||
ws.send (Pong_frame, a_message)
|
||||
end
|
||||
|
||||
on_unsupported (ws: WEB_SOCKET; a_message: READABLE_STRING_8; a_opcode: INTEGER)
|
||||
require
|
||||
ws_attached: ws /= Void
|
||||
ws_valid: ws.is_open_read and then ws.is_open_write
|
||||
do
|
||||
-- do nothing
|
||||
end
|
||||
|
||||
on_connection_close (ws: WEB_SOCKET; a_message: detachable READABLE_STRING_8)
|
||||
require
|
||||
ws_attached: ws /= Void
|
||||
ws_valid: ws.is_open_read and then ws.is_open_write
|
||||
do
|
||||
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
|
||||
@@ -0,0 +1,39 @@
|
||||
note
|
||||
description: "[
|
||||
A web socket message has an opcode specifying the type of the message payload. The
|
||||
opcode consists of the last four bits in the first byte of the frame header.
|
||||
]"
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
EIS: "name=Data Frame", "src=http://tools.ietf.org/html/rfc6455#section-5.6", "protocol=uri"
|
||||
EIS: "name=Control Frame", "src=http://tools.ietf.org/html/rfc6455#section-5.5", "protocol=uri"
|
||||
|
||||
class
|
||||
WEB_SOCKET_MESSAGE_TYPE
|
||||
|
||||
feature -- Data Frames
|
||||
|
||||
Text: INTEGER = 0x1
|
||||
-- The data type of the message is text.
|
||||
|
||||
Binary: INTEGER = 0x2
|
||||
-- The data type of the message is binary.
|
||||
|
||||
feature -- Control Frames
|
||||
|
||||
Close: INTEGER = 0x8
|
||||
-- The client or server is sending a closing
|
||||
-- handshake to the server or client.
|
||||
|
||||
Ping: INTEGER = 0x9
|
||||
-- The client or server sends a ping to the server or client.
|
||||
|
||||
Pong: INTEGER = 0xA
|
||||
-- The client or server sends a pong to the server or client.
|
||||
|
||||
feature -- Reserverd
|
||||
|
||||
-- Opcodes 0x3-0x7 are reserved for further non-control frames yet to be
|
||||
-- defined.
|
||||
|
||||
end
|
||||
@@ -0,0 +1,203 @@
|
||||
note
|
||||
description: "Constants for WebSockets"
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
class
|
||||
WEB_SOCKET_CONSTANTS
|
||||
|
||||
feature -- Constants
|
||||
|
||||
|
||||
HTTP_1_1: STRING = "HTTP/1.1 101 WebSocket Protocol Handshake"
|
||||
|
||||
Upgrade_ws: STRING = "Upgrade: websocket"
|
||||
|
||||
Connection_ws: STRING = "Connection: Upgrade"
|
||||
|
||||
Sec_WebSocket_Origin: STRING = "Sec-WebSocket-Origin: "
|
||||
|
||||
Sec_WebSocket_Protocol: STRING = "Sec-WebSocket-Protocol: "
|
||||
|
||||
Sec_WebSocket_Location: STRING = "Sec-WebSocket-Location: "
|
||||
|
||||
Sec_WebSocket_Version: STRING = "Sec-WebSocket-Version: "
|
||||
|
||||
Sec_WebSocket_Extensions: STRING = "Sec-WebSocket-Extensions: "
|
||||
|
||||
WebSocket_Origin: STRING = "WebSocket-Origin: "
|
||||
|
||||
WebSocket_Protocol: STRING = "WebSocket-Protocol: "
|
||||
|
||||
WebSocket_Location: STRING = "WebSocket-Location: "
|
||||
|
||||
Origin: STRING = "Origin"
|
||||
|
||||
Server: STRING = "EWSS"
|
||||
|
||||
Sec_WebSocket_Key: STRING = "Sec-WebSocket-Key"
|
||||
|
||||
Ws_scheme: STRING = "ws://"
|
||||
|
||||
Wss_scheme: STRING = "wss://"
|
||||
|
||||
Magic_guid: STRING = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
|
||||
|
||||
-- The handshake from the client looks as follows:
|
||||
|
||||
-- GET /chat HTTP/1.1
|
||||
-- Host: server.example.com
|
||||
-- Upgrade: websocket
|
||||
-- Connection: Upgrade
|
||||
-- Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
|
||||
-- Origin: http://example.com
|
||||
-- Sec-WebSocket-Protocol: chat, superchat
|
||||
-- Sec-WebSocket-Version: 13
|
||||
|
||||
-- The handshake from the server looks as follows:
|
||||
|
||||
-- HTTP/1.1 101 Switching Protocols
|
||||
-- Upgrade: websocket
|
||||
-- Connection: Upgrade
|
||||
-- Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
|
||||
-- Sec-WebSocket-Protocol: chat
|
||||
|
||||
feature -- Opcodes Standard actions
|
||||
|
||||
--| Maybe we need an enum STANDARD_ACTIONS_OPCODES?
|
||||
-- |Opcode | Meaning | Reference |
|
||||
-- -+--------+-------------------------------------+-----------|
|
||||
-- | 0 | Continuation Frame | RFC 6455 |
|
||||
-- -+--------+-------------------------------------+-----------|
|
||||
-- | 1 | Text Frame | RFC 6455 |
|
||||
-- -+--------+-------------------------------------+-----------|
|
||||
-- | 2 | Binary Frame | RFC 6455 |
|
||||
-- -+--------+-------------------------------------+-----------|
|
||||
-- | 8 | Connection Close Frame | RFC 6455 |
|
||||
-- -+--------+-------------------------------------+-----------|
|
||||
-- | 9 | Ping Frame | RFC 6455 |
|
||||
-- -+--------+-------------------------------------+-----------|
|
||||
-- | 10 | Pong Frame | RFC 6455 |
|
||||
-- -+--------+-------------------------------------+-----------|
|
||||
|
||||
Continuation_frame: INTEGER = 0
|
||||
|
||||
Text_frame: INTEGER = 1
|
||||
|
||||
Binary_frame: INTEGER = 2
|
||||
|
||||
Connection_close_frame: INTEGER = 8
|
||||
|
||||
Ping_frame: INTEGER = 9
|
||||
|
||||
Pong_frame: INTEGER = 10
|
||||
|
||||
is_control_frame (a_opcode: INTEGER): BOOLEAN
|
||||
-- Is `a_opcode' a control frame?
|
||||
do
|
||||
inspect a_opcode
|
||||
when Connection_close_frame, Ping_frame, Pong_frame then
|
||||
Result := True
|
||||
else
|
||||
end
|
||||
end
|
||||
|
||||
opcode_name (a_opcode: INTEGER): STRING
|
||||
do
|
||||
inspect a_opcode
|
||||
when Continuation_frame then Result := "Continuation"
|
||||
when Text_frame then Result := "Text"
|
||||
when Binary_frame then Result := "Binary"
|
||||
when Connection_close_frame then Result := "Connection Close"
|
||||
when Ping_frame then Result := "Ping"
|
||||
when Pong_frame then Result := "Pong"
|
||||
else
|
||||
Result := "Unknown-Opcode"
|
||||
end
|
||||
Result := "0x" + a_opcode.to_hex_string + " " + Result
|
||||
end
|
||||
|
||||
feature -- Close code numbers
|
||||
|
||||
-- Maybe an ENUM CLOSE_CODES
|
||||
|
||||
-- |Status Code | Meaning | Contact | Reference |
|
||||
-- -+------------+-----------------+---------------+-----------|
|
||||
-- | 1000 | Normal Closure | hybi@ietf.org | RFC 6455 |
|
||||
-- -+------------+-----------------+---------------+-----------|
|
||||
-- | 1001 | Going Away | hybi@ietf.org | RFC 6455 |
|
||||
-- -+------------+-----------------+---------------+-----------|
|
||||
-- | 1002 | Protocol error | hybi@ietf.org | RFC 6455 |
|
||||
-- -+------------+-----------------+---------------+-----------|
|
||||
-- | 1003 | Unsupported Data| hybi@ietf.org | RFC 6455 |
|
||||
-- -+------------+-----------------+---------------+-----------|
|
||||
-- | 1004 | ---Reserved---- | hybi@ietf.org | RFC 6455 |
|
||||
-- -+------------+-----------------+---------------+-----------|
|
||||
-- | 1005 | No Status Rcvd | hybi@ietf.org | RFC 6455 |
|
||||
-- -+------------+-----------------+---------------+-----------|
|
||||
-- | 1006 | Abnormal Closure| hybi@ietf.org | RFC 6455 |
|
||||
-- -+------------+-----------------+---------------+-----------|
|
||||
-- | 1007 | Invalid frame | hybi@ietf.org | RFC 6455 |
|
||||
-- | | payload data | | |
|
||||
-- -+------------+-----------------+---------------+-----------|
|
||||
-- | 1008 | Policy Violation| hybi@ietf.org | RFC 6455 |
|
||||
-- -+------------+-----------------+---------------+-----------|
|
||||
-- | 1009 | Message Too Big | hybi@ietf.org | RFC 6455 |
|
||||
-- -+------------+-----------------+---------------+-----------|
|
||||
-- | 1010 | Mandatory Ext. | hybi@ietf.org | RFC 6455 |
|
||||
-- -+------------+-----------------+---------------+-----------|
|
||||
-- | 1011 | Internal Server | hybi@ietf.org | RFC 6455 |
|
||||
-- | | Error | | |
|
||||
-- -+------------+-----------------+---------------+-----------|
|
||||
-- | 1015 | TLS handshake | hybi@ietf.org | RFC 6455 |
|
||||
-- -+------------+-----------------+---------------+-----------|
|
||||
|
||||
Normal_closure: INTEGER = 1000
|
||||
-- Indicates a normal closure, meaning that the purpose for
|
||||
-- which the connection was established has been fulfilled.
|
||||
|
||||
Going_away: INTEGER = 1001
|
||||
-- Indicates that an endpoint is "going away", such as a server
|
||||
-- going down or a browser having navigated away from a page.
|
||||
|
||||
Protocol_error: INTEGER = 1002
|
||||
-- Indicates that an endpoint is terminating the connection due
|
||||
-- to a protocol error.
|
||||
|
||||
Unsupported_data: INTEGER = 1003
|
||||
-- Indicates that an endpoint is terminating the connection
|
||||
-- because it has received a type of data it cannot accept (e.g., an
|
||||
-- endpoint that understands only text data MAY send this if it
|
||||
-- receives a binary message).
|
||||
|
||||
Invalid_data: INTEGER = 1007
|
||||
-- Indicates that an endpoint is terminating the connection
|
||||
-- because it has received data within a message that was not
|
||||
-- consistent with the type of the message (e.g., non-UTF-8 [RFC3629]
|
||||
-- data within a text message).
|
||||
|
||||
Policy_violation: INTEGER = 1008
|
||||
-- Indicates that an endpoint is terminating the connection
|
||||
-- because it has received a message that violates its policy. This
|
||||
-- is a generic status code that can be returned when there is no
|
||||
-- other more suitable status code (e.g., 1003 or 1009) or if there
|
||||
-- is a need to hide specific details about the policy.
|
||||
|
||||
Message_too_large: INTEGER = 1009
|
||||
-- Indicates that an endpoint is terminating the connection
|
||||
-- because it has received a message that is too big for it to
|
||||
-- process.
|
||||
|
||||
Extension_required: INTEGER = 1010
|
||||
-- Indicates that an endpoint (client) is terminating the
|
||||
-- connection because it has expected the server to negotiate one or
|
||||
-- more extension, but the server didn't return them in the response
|
||||
-- message of the WebSocket handshake.
|
||||
|
||||
Internal_error: INTEGER = 1011
|
||||
-- Indicates that a server is terminating the connection because
|
||||
-- it encountered an unexpected condition that prevented it from
|
||||
-- fulfilling the request.
|
||||
|
||||
|
||||
end
|
||||
@@ -0,0 +1,35 @@
|
||||
note
|
||||
description: "Summary description for {WEB_SOCKET_ERROR_FRAME}."
|
||||
author: ""
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
class
|
||||
WEB_SOCKET_ERROR_FRAME
|
||||
|
||||
create
|
||||
make
|
||||
|
||||
feature {NONE} -- Initialization
|
||||
|
||||
make (a_code: INTEGER; a_desc: like description)
|
||||
do
|
||||
code := a_code
|
||||
description := a_desc
|
||||
end
|
||||
|
||||
feature -- Access
|
||||
|
||||
code: INTEGER
|
||||
|
||||
description: READABLE_STRING_8
|
||||
|
||||
feature -- Conversion
|
||||
|
||||
string: STRING
|
||||
do
|
||||
create Result.make_from_string ("Error(" + code.out + "): " + description)
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
@@ -0,0 +1,437 @@
|
||||
note
|
||||
description: "[
|
||||
Summary description for {WEB_SOCKET_FRAME}.
|
||||
See Base Framing Protocol: http://tools.ietf.org/html/rfc6455#section-5.2
|
||||
0 1 2 3
|
||||
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
+-+-+-+-+-------+-+-------------+-------------------------------+
|
||||
|F|R|R|R| opcode|M| Payload len | Extended payload length |
|
||||
|I|S|S|S| (4) |A| (7) | (16/64) |
|
||||
|N|V|V|V| |S| | (if payload len==126/127) |
|
||||
| |1|2|3| |K| | |
|
||||
+-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
|
||||
| Extended payload length continued, if payload len == 127 |
|
||||
+ - - - - - - - - - - - - - - - +-------------------------------+
|
||||
| |Masking-key, if MASK set to 1 |
|
||||
+-------------------------------+-------------------------------+
|
||||
| Masking-key (continued) | Payload Data |
|
||||
+-------------------------------- - - - - - - - - - - - - - - - +
|
||||
: Payload Data continued ... :
|
||||
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
|
||||
| Payload Data continued ... |
|
||||
+---------------------------------------------------------------+
|
||||
|
||||
Check the `check_utf_8_validity_on_chop' if there is performance issue
|
||||
with bigger data.
|
||||
]"
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
EIS: "name=Websocket RFC6455 section-5.2", "protocol=URI", "src=http://tools.ietf.org/html/rfc6455#section-5.2", "tag=rfc"
|
||||
|
||||
class
|
||||
WEB_SOCKET_FRAME
|
||||
|
||||
inherit
|
||||
ANY
|
||||
|
||||
WEB_SOCKET_CONSTANTS
|
||||
|
||||
create
|
||||
make,
|
||||
make_as_injected_control
|
||||
|
||||
feature {NONE} -- Initialization
|
||||
|
||||
make (a_opcode: INTEGER; flag_is_fin: BOOLEAN)
|
||||
-- Create current frame with opcode `a_opcode'
|
||||
-- and `a_fin' to indicate if this is the final fragment.
|
||||
do
|
||||
is_incomplete := False
|
||||
opcode := a_opcode
|
||||
is_fin := flag_is_fin
|
||||
|
||||
inspect opcode
|
||||
when
|
||||
Continuation_frame, -- 0
|
||||
Text_frame, -- 1
|
||||
Binary_frame -- 2
|
||||
then
|
||||
--| Supported opcode
|
||||
when
|
||||
Connection_close_frame, -- 8
|
||||
Ping_frame, -- 9
|
||||
Pong_frame -- 10
|
||||
then
|
||||
--| Supported control opcode
|
||||
-- All control frames MUST have a payload length of 125 bytes or less
|
||||
-- and MUST NOT be fragmented.
|
||||
if flag_is_fin then
|
||||
-- So far it is valid.
|
||||
else
|
||||
report_error (Protocol_error, "Control frames MUST NOT be fragmented.")
|
||||
end
|
||||
else
|
||||
report_error (Protocol_error, "Unknown opcode")
|
||||
end
|
||||
end
|
||||
|
||||
make_as_injected_control (a_opcode: INTEGER; a_parent: WEB_SOCKET_FRAME)
|
||||
require
|
||||
parent_is_not_control_frame: not a_parent.is_control
|
||||
a_opcode_is_control_frame: is_control_frame (a_opcode)
|
||||
do
|
||||
make (a_opcode, True)
|
||||
parent := a_parent
|
||||
a_parent.add_injected_control_frame (Current)
|
||||
end
|
||||
|
||||
feature -- Access
|
||||
|
||||
opcode: INTEGER
|
||||
-- CONTINUOUS, TEXT, BINARY, PING, PONG, CLOSING
|
||||
|
||||
is_fin: BOOLEAN
|
||||
-- is the final fragment in a message?
|
||||
|
||||
fragment_count: INTEGER
|
||||
|
||||
payload_length: NATURAL_64
|
||||
payload_data: detachable STRING_8
|
||||
-- Maybe we need a buffer here.
|
||||
|
||||
uncoded_payload_data: detachable STRING_32
|
||||
local
|
||||
utf: UTF_CONVERTER
|
||||
do
|
||||
if attached payload_data as d then
|
||||
Result := utf.utf_8_string_8_to_string_32 (d)
|
||||
end
|
||||
end
|
||||
|
||||
error: detachable WEB_SOCKET_ERROR_FRAME
|
||||
-- Describe the type of error
|
||||
|
||||
feature -- Access: injected control frames
|
||||
|
||||
injected_control_frames: detachable LIST [WEB_SOCKET_FRAME]
|
||||
|
||||
parent: detachable WEB_SOCKET_FRAME
|
||||
-- If Current is injected, `parent' is the related fragmented frame
|
||||
|
||||
is_injected_control: BOOLEAN
|
||||
do
|
||||
Result := parent /= Void
|
||||
ensure
|
||||
Result implies (is_control_frame (opcode))
|
||||
end
|
||||
|
||||
feature -- Operation
|
||||
|
||||
update_fin (a_flag_is_fin: BOOLEAN)
|
||||
do
|
||||
is_fin := a_flag_is_fin
|
||||
end
|
||||
|
||||
feature {WEB_SOCKET_FRAME} -- Change: injected control frames
|
||||
|
||||
add_injected_control_frame (f: WEB_SOCKET_FRAME)
|
||||
require
|
||||
Current_is_not_control: not is_control
|
||||
f_is_control_frame: f.is_control
|
||||
parented_to_current: f.parent = Current
|
||||
local
|
||||
lst: like injected_control_frames
|
||||
do
|
||||
lst := injected_control_frames
|
||||
if lst = Void then
|
||||
create {ARRAYED_LIST [WEB_SOCKET_FRAME]} lst.make (1)
|
||||
injected_control_frames := lst
|
||||
end
|
||||
lst.force (f)
|
||||
ensure
|
||||
parented_to_current: f.parent = Current
|
||||
end
|
||||
|
||||
remove_injected_control_frame (f: WEB_SOCKET_FRAME)
|
||||
require
|
||||
Current_is_not_control: not is_control
|
||||
f_is_control_frame: f.is_control
|
||||
parented_to_current: f.parent = Current
|
||||
local
|
||||
lst: like injected_control_frames
|
||||
do
|
||||
lst := injected_control_frames
|
||||
if lst /= Void then
|
||||
lst.prune (f)
|
||||
if lst.is_empty then
|
||||
injected_control_frames := Void
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
feature -- Query
|
||||
|
||||
is_binary: BOOLEAN
|
||||
do
|
||||
Result := opcode = binary_frame
|
||||
end
|
||||
|
||||
is_text: BOOLEAN
|
||||
do
|
||||
Result := opcode = text_frame
|
||||
end
|
||||
|
||||
is_continuation: BOOLEAN
|
||||
do
|
||||
Result := opcode = continuation_frame
|
||||
end
|
||||
|
||||
is_connection_close: BOOLEAN
|
||||
do
|
||||
Result := opcode = connection_close_frame
|
||||
end
|
||||
|
||||
is_control: BOOLEAN
|
||||
do
|
||||
inspect opcode
|
||||
when connection_close_frame, Ping_frame, Pong_frame then
|
||||
Result := True
|
||||
else
|
||||
end
|
||||
end
|
||||
|
||||
is_ping: BOOLEAN
|
||||
do
|
||||
Result := opcode = ping_frame
|
||||
end
|
||||
|
||||
is_pong: BOOLEAN
|
||||
do
|
||||
Result := opcode = pong_frame
|
||||
end
|
||||
|
||||
feature -- Status report
|
||||
|
||||
is_valid: BOOLEAN
|
||||
do
|
||||
Result := not has_error
|
||||
end
|
||||
|
||||
is_incomplete: BOOLEAN
|
||||
|
||||
has_error: BOOLEAN
|
||||
do
|
||||
Result := error /= Void
|
||||
end
|
||||
|
||||
feature -- Change
|
||||
|
||||
increment_fragment_count
|
||||
do
|
||||
fragment_count := fragment_count + 1
|
||||
end
|
||||
|
||||
check_utf_8_validity_on_chop: BOOLEAN = False
|
||||
-- True: check for each chop
|
||||
-- False: check only for each fragment
|
||||
--| see autobahntestsuite #6.4.3 and #6.4.4
|
||||
|
||||
append_payload_data_chop (a_data: STRING_8; a_len: INTEGER; a_flag_chop_complete: BOOLEAN)
|
||||
do
|
||||
if a_flag_chop_complete then
|
||||
increment_fragment_count
|
||||
end
|
||||
if attached payload_data as l_payload_data then
|
||||
l_payload_data.append (a_data)
|
||||
else
|
||||
payload_data := a_data
|
||||
end
|
||||
payload_length := payload_length + a_len.to_natural_64
|
||||
|
||||
if is_text then
|
||||
if is_fin and a_flag_chop_complete then
|
||||
-- Check the whole message is a valid UTF-8 string
|
||||
if attached payload_data as d then
|
||||
if not is_valid_utf_8_string (d) then
|
||||
report_error (invalid_data, "The text message is not a valid UTF-8 text!")
|
||||
end
|
||||
else
|
||||
-- empty payload??
|
||||
end
|
||||
elseif check_utf_8_validity_on_chop or else a_flag_chop_complete then
|
||||
-- Check the payload data as utf-8 stream (may be incomplete at this point)
|
||||
if not is_valid_text_payload_stream then
|
||||
report_error (invalid_data, "This is not a valid UTF-8 stream!")
|
||||
-- is_valid implies the connection will be closed!
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
report_error (a_code: INTEGER; a_description: READABLE_STRING_8)
|
||||
require
|
||||
not has_error
|
||||
do
|
||||
create error.make (a_code, a_description)
|
||||
ensure
|
||||
has_error: has_error
|
||||
is_not_valid: not is_valid
|
||||
end
|
||||
|
||||
feature {NONE} -- Helper
|
||||
|
||||
last_utf_8_stream_validation_position: INTEGER
|
||||
-- In relation with `is_valid_utf_8 (.., a_is_stream=True)'
|
||||
|
||||
is_valid_text_payload_stream: BOOLEAN
|
||||
require
|
||||
is_text_frame: is_text
|
||||
do
|
||||
if attached payload_data as s then
|
||||
Result := is_valid_utf_8 (s, not is_fin)
|
||||
end
|
||||
end
|
||||
|
||||
is_valid_utf_8_string (s: READABLE_STRING_8): BOOLEAN
|
||||
do
|
||||
Result := is_valid_utf_8 (s, False)
|
||||
-- and (create {UTF_CONVERTER}).is_valid_utf_8_string_8 (s)
|
||||
end
|
||||
|
||||
is_valid_utf_8 (s: READABLE_STRING_8; a_is_stream: BOOLEAN): BOOLEAN
|
||||
-- UTF-8 validity checker.
|
||||
note
|
||||
EIS: "name=UTF-8 RFC3629", "protocol=URI", "src=https://tools.ietf.org/html/rfc3629", "tag=rfc"
|
||||
require
|
||||
is_text_frame: is_text
|
||||
local
|
||||
i: like {STRING_8}.count
|
||||
n: like {STRING_8}.count
|
||||
c,c2,c3,c4,w: NATURAL_32
|
||||
l_is_incomplete_stream: BOOLEAN
|
||||
do
|
||||
Result := True
|
||||
-- Following code also check that codepoint is between 0 and 0x10FFFF
|
||||
-- (as expected by spec, and tested by autobahn ws testsuite)
|
||||
from
|
||||
if a_is_stream then
|
||||
i := last_utf_8_stream_validation_position -- to avoid recomputing from the beginning each time.
|
||||
else
|
||||
i := 0
|
||||
end
|
||||
n := s.count
|
||||
until
|
||||
i >= n or not Result
|
||||
loop
|
||||
i := i + 1
|
||||
c := s.code (i)
|
||||
if c <= 0x7F then
|
||||
-- 0xxxxxxx
|
||||
w := c
|
||||
elseif c <= 0xC1 then
|
||||
-- The octet values C0, C1, F5 to FF never appear.
|
||||
--| case 0xC0 and 0xC1
|
||||
Result := False
|
||||
elseif (c & 0xE0) = 0xC0 then
|
||||
-- 110xxxxx 10xxxxxx
|
||||
i := i + 1
|
||||
if i <= n then
|
||||
c2 := s.code (i)
|
||||
if
|
||||
(c2 & 0xC0) = 0x80
|
||||
then
|
||||
w := ((c & 0x1F) |<< 6)
|
||||
| (c2 & 0x3F)
|
||||
Result := 0x80 <= w and w <= 0x7FF
|
||||
else
|
||||
Result := False
|
||||
end
|
||||
else
|
||||
l_is_incomplete_stream := True
|
||||
end
|
||||
elseif (c & 0xF0) = 0xE0 then
|
||||
-- 1110xxxx 10xxxxxx 10xxxxxx
|
||||
i := i + 2
|
||||
if i <= n then
|
||||
c2 := s.code (i - 1)
|
||||
c3 := s.code (i)
|
||||
if
|
||||
(c2 & 0xC0) = 0x80 and
|
||||
(c3 & 0xC0) = 0x80
|
||||
then
|
||||
w := ((c & 0xF) |<< 12)
|
||||
| ((c2 & 0x3F) |<< 6)
|
||||
| (c3 & 0x3F)
|
||||
if 0x800 <= w and w <= 0xFFFF then
|
||||
if 0xD800 <= w and w <= 0xDFFF then
|
||||
-- The definition of UTF-8 prohibits encoding character numbers between U+D800 and U+DFFF
|
||||
Result := False
|
||||
end
|
||||
else
|
||||
Result := False
|
||||
end
|
||||
else
|
||||
Result := False
|
||||
end
|
||||
else
|
||||
if i - 1 <= n then
|
||||
Result := (s.code (i - 1) & 0xC0) = 0x80
|
||||
end
|
||||
l_is_incomplete_stream := True
|
||||
end
|
||||
elseif (c & 0xF8) = 0xF0 then -- 0001 0000-0010 FFFF
|
||||
-- 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
|
||||
if 0xF5 <= c and c <= 0xFF then
|
||||
-- The octet values C0, C1, F5 to FF never appear.
|
||||
Result := False
|
||||
else
|
||||
i := i + 3
|
||||
if i <= n then
|
||||
c2 := s.code (i - 2)
|
||||
c3 := s.code (i - 1)
|
||||
c4 := s.code (i)
|
||||
if
|
||||
(c2 & 0xC0) = 0x80 and
|
||||
(c3 & 0xC0) = 0x80 and
|
||||
(c4 & 0xC0) = 0x80
|
||||
then
|
||||
w := ((c & 0x7) |<< 18) |
|
||||
((c2 & 0x3F) |<< 12) |
|
||||
((c3 & 0x3F) |<< 6) |
|
||||
(c4 & 0x3F)
|
||||
Result := 0x1_0000 <= w and w <= 0x10_FFFF
|
||||
else
|
||||
Result := False
|
||||
end
|
||||
else
|
||||
if i - 2 <= n then
|
||||
c2 := s.code (i - 2)
|
||||
Result := (c2 & 0xC0) = 0x80
|
||||
if Result then
|
||||
if c = 0xF4 and c2 >= 0x90 then
|
||||
--| any byte 10xxxxxx (i.e >= 0x80) that would come after,
|
||||
-- will result in out of range code point
|
||||
-- indeed 0xF4 0x90 0x80 0x80 = 0x1100 0000 > 0x10_FFFF
|
||||
Result := False
|
||||
elseif i - 1 <= n then
|
||||
Result := (s.code (i - 1) & 0xC0) = 0x80
|
||||
end
|
||||
end
|
||||
end
|
||||
l_is_incomplete_stream := True
|
||||
end
|
||||
end
|
||||
else
|
||||
-- Invalid byte in UTF-8
|
||||
Result := False
|
||||
end
|
||||
if Result then
|
||||
if l_is_incomplete_stream then
|
||||
Result := a_is_stream
|
||||
elseif a_is_stream then
|
||||
last_utf_8_stream_validation_position := i
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,805 @@
|
||||
note
|
||||
description: "[
|
||||
Object representing the websocket connection.
|
||||
It contains the `request` and `response`, and more important the `socket` itself.
|
||||
]"
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
class
|
||||
WEB_SOCKET
|
||||
|
||||
inherit
|
||||
WGI_STANDALONE_CONNECTOR_EXPORTER
|
||||
|
||||
WSF_RESPONSE_EXPORTER
|
||||
|
||||
WGI_EXPORTER
|
||||
|
||||
HTTPD_LOGGER_CONSTANTS
|
||||
|
||||
WEB_SOCKET_CONSTANTS
|
||||
|
||||
SHARED_BASE64
|
||||
|
||||
create
|
||||
make
|
||||
|
||||
feature {NONE} -- Initialization
|
||||
|
||||
make (req: WSF_REQUEST; res: WSF_RESPONSE)
|
||||
do
|
||||
request := req
|
||||
response := res
|
||||
is_verbose := False
|
||||
verbose_level := notice_level
|
||||
|
||||
if
|
||||
attached {WGI_STANDALONE_INPUT_STREAM} req.input as r_input
|
||||
then
|
||||
socket := r_input.source
|
||||
else
|
||||
create socket.make_empty
|
||||
check has_socket: False end
|
||||
end
|
||||
end
|
||||
|
||||
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?
|
||||
|
||||
feature -- Settings
|
||||
|
||||
is_verbose: BOOLEAN
|
||||
-- Output verbose log messages?
|
||||
|
||||
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
|
||||
put_error (m)
|
||||
end
|
||||
end
|
||||
|
||||
feature -- Basic Operation
|
||||
|
||||
open_ws_handshake
|
||||
-- The opening handshake is intended to be compatible with HTTP-based
|
||||
-- server-side software and intermediaries, so that a single port can be
|
||||
-- used by both HTTP clients alking to that server and WebSocket
|
||||
-- clients talking to that server. To this end, the WebSocket client's
|
||||
-- handshake is an HTTP Upgrade request:
|
||||
|
||||
-- GET /chat HTTP/1.1
|
||||
-- Host: server.example.com
|
||||
-- Upgrade: websocket
|
||||
-- Connection: Upgrade
|
||||
-- Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
|
||||
-- Origin: http://example.com
|
||||
-- Sec-WebSocket-Protocol: chat, superchat
|
||||
-- Sec-WebSocket-Version: 13
|
||||
local
|
||||
l_sha1: SHA1
|
||||
l_key : STRING
|
||||
req: like request
|
||||
res: like response
|
||||
do
|
||||
-- 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.
|
||||
if is_verbose then
|
||||
log ("%NReceive <====================", debug_level)
|
||||
if attached req.raw_header_data as rhd then
|
||||
log (rhd, debug_level)
|
||||
end
|
||||
end
|
||||
if
|
||||
req.is_get_request_method and then -- MUST be GET request!
|
||||
attached req.meta_string_variable ("HTTP_UPGRADE") as l_upgrade_key and then
|
||||
l_upgrade_key.is_case_insensitive_equal_general ("websocket") -- Upgrade header must be present with value websocket
|
||||
then
|
||||
is_websocket := True
|
||||
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
|
||||
attached req.meta_string_variable ("HTTP_CONNECTION") as l_connection_key and then -- Connection header must be present with value Upgrade
|
||||
l_connection_key.has_substring ("Upgrade") and then
|
||||
attached req.meta_string_variable ("HTTP_SEC_WEBSOCKET_VERSION") as l_version_key and then -- Version header must be present with value 13
|
||||
l_version_key.is_case_insensitive_equal ("13") and then
|
||||
attached req.http_host -- Host header must be present
|
||||
then
|
||||
if is_verbose then
|
||||
log ("key " + l_ws_key, debug_level)
|
||||
end
|
||||
-- Sending the server's opening handshake
|
||||
create l_sha1.make
|
||||
l_sha1.update_from_string (l_ws_key + magic_guid)
|
||||
l_key := Base64_encoder.encoded_string (digest (l_sha1))
|
||||
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 Handshake", debug_level)
|
||||
if attached {HTTP_HEADER} res.header as h then
|
||||
log (h.string, debug_level)
|
||||
end
|
||||
end
|
||||
res.set_status_code_with_reason_phrase (101, "Switching Protocols")
|
||||
res.wgi_response.push
|
||||
else
|
||||
has_error := True
|
||||
if is_verbose then
|
||||
log ("Error (opening_handshake)!!!", debug_level)
|
||||
end
|
||||
-- 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")
|
||||
end
|
||||
else
|
||||
is_websocket := False
|
||||
end
|
||||
end
|
||||
|
||||
feature -- Response!
|
||||
|
||||
send (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
|
||||
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)
|
||||
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
|
||||
socket.put_string (l_header_message)
|
||||
|
||||
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
|
||||
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))
|
||||
socket.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
|
||||
|
||||
next_frame: detachable WEB_SOCKET_FRAME
|
||||
-- TODO Binary messages
|
||||
-- Handle error responses in a better way.
|
||||
-- IDEA:
|
||||
-- class FRAME
|
||||
-- is_fin: BOOLEAN
|
||||
-- opcode: WEB_SOCKET_STATUS_CODE (TEXT, BINARY, CLOSE, CONTINUE,PING, PONG)
|
||||
-- data/payload
|
||||
-- status_code: #see Status Codes http://tools.ietf.org/html/rfc6455#section-7.3
|
||||
-- has_error
|
||||
--
|
||||
-- See Base Framing Protocol: http://tools.ietf.org/html/rfc6455#section-5.2
|
||||
-- 0 1 2 3
|
||||
-- 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
-- +-+-+-+-+-------+-+-------------+-------------------------------+
|
||||
-- |F|R|R|R| opcode|M| Payload len | Extended payload length |
|
||||
-- |I|S|S|S| (4) |A| (7) | (16/64) |
|
||||
-- |N|V|V|V| |S| | (if payload len==126/127) |
|
||||
-- | |1|2|3| |K| | |
|
||||
-- +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
|
||||
-- | Extended payload length continued, if payload len == 127 |
|
||||
-- + - - - - - - - - - - - - - - - +-------------------------------+
|
||||
-- | |Masking-key, if MASK set to 1 |
|
||||
-- +-------------------------------+-------------------------------+
|
||||
-- | Masking-key (continued) | Payload Data |
|
||||
-- +-------------------------------- - - - - - - - - - - - - - - - +
|
||||
-- : Payload Data continued ... :
|
||||
-- + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
|
||||
-- | Payload Data continued ... |
|
||||
-- +---------------------------------------------------------------+
|
||||
note
|
||||
EIS: "name=WebSocket RFC", "protocol=URI", "src=http://tools.ietf.org/html/rfc6455#section-5.2"
|
||||
require
|
||||
socket_in_blocking_mode: socket.is_blocking
|
||||
local
|
||||
l_socket: like socket
|
||||
l_opcode: INTEGER
|
||||
l_len: INTEGER
|
||||
l_remaining_len: INTEGER
|
||||
l_payload_len: NATURAL_64
|
||||
l_masking_key: detachable READABLE_STRING_8
|
||||
l_chunk: STRING
|
||||
l_rsv: BOOLEAN
|
||||
l_fin: BOOLEAN
|
||||
l_has_mask: BOOLEAN
|
||||
l_chunk_size: INTEGER
|
||||
l_byte: INTEGER
|
||||
l_fetch_count: INTEGER
|
||||
l_bytes_read: INTEGER
|
||||
s: STRING
|
||||
is_data_frame_ok: BOOLEAN -- Is the last process data framing ok?
|
||||
retried: BOOLEAN
|
||||
do
|
||||
if not retried then
|
||||
l_socket := socket
|
||||
debug ("ws")
|
||||
print ("next_frame:%N")
|
||||
end
|
||||
from
|
||||
is_data_frame_ok := True
|
||||
until
|
||||
l_fin or not is_data_frame_ok
|
||||
loop
|
||||
-- multi-frames or continue is only valid for Binary or Text
|
||||
s := next_bytes (l_socket, 1)
|
||||
if s.is_empty then
|
||||
is_data_frame_ok := False
|
||||
debug ("ws")
|
||||
print ("[ERROR] incomplete_data!%N")
|
||||
end
|
||||
else
|
||||
l_byte := s [1].code
|
||||
debug ("ws")
|
||||
print (" fin,rsv(3),opcode(4)=")
|
||||
print (to_byte_representation (l_byte))
|
||||
print ("%N")
|
||||
end
|
||||
l_fin := l_byte & (0b10000000) /= 0
|
||||
l_rsv := l_byte & (0b01110000) = 0
|
||||
l_opcode := l_byte & 0b00001111
|
||||
if Result /= Void then
|
||||
if l_opcode = Result.opcode then
|
||||
-- should not occur in multi-fragment frame!
|
||||
create Result.make (l_opcode, l_fin)
|
||||
Result.report_error (protocol_error, "Unexpected injected frame")
|
||||
elseif l_opcode = continuation_frame then
|
||||
-- Expected
|
||||
Result.update_fin (l_fin)
|
||||
elseif is_control_frame (l_opcode) then
|
||||
-- Control frames (see Section 5.5) MAY be injected in the middle of
|
||||
-- a fragmented message. Control frames themselves MUST NOT be fragmented.
|
||||
-- if the l_opcode is a control frame then there is an error!!!
|
||||
-- CLOSE, PING, PONG
|
||||
create Result.make_as_injected_control (l_opcode, Result)
|
||||
else
|
||||
-- should not occur in multi-fragment frame!
|
||||
create Result.make (l_opcode, l_fin)
|
||||
Result.report_error (protocol_error, "Unexpected frame")
|
||||
end
|
||||
else
|
||||
create Result.make (l_opcode, l_fin)
|
||||
if Result.is_continuation then
|
||||
-- Continuation frame is not expected without parent frame!
|
||||
Result.report_error (protocol_error, "There is no message to continue!")
|
||||
end
|
||||
end
|
||||
if Result.is_valid then
|
||||
--| valid frame/fragment
|
||||
if is_verbose then
|
||||
log ("+ frame " + opcode_name (l_opcode) + " (fin=" + l_fin.out + ")", debug_level)
|
||||
end
|
||||
|
||||
-- rsv validation
|
||||
if not l_rsv then
|
||||
-- RSV1, RSV2, RSV3: 1 bit each
|
||||
|
||||
-- MUST be 0 unless an extension is negotiated that defines meanings
|
||||
-- for non-zero values. If a nonzero value is received and none of
|
||||
-- the negotiated extensions defines the meaning of such a nonzero
|
||||
-- value, the receiving endpoint MUST _Fail the WebSocket
|
||||
-- Connection_
|
||||
|
||||
-- FIXME: add support for extension ?
|
||||
Result.report_error (protocol_error, "RSV values MUST be 0 unless an extension is negotiated that defines meanings for non-zero values")
|
||||
end
|
||||
else
|
||||
if is_verbose then
|
||||
log ("+ INVALID frame " + opcode_name (l_opcode) + " (fin=" + l_fin.out + ")", debug_level)
|
||||
end
|
||||
end
|
||||
|
||||
-- At the moment only TEXT, (pending Binary)
|
||||
if Result.is_valid then
|
||||
if Result.is_text or Result.is_binary or Result.is_control then
|
||||
-- Reading next byte (mask+payload_len)
|
||||
s := next_bytes (l_socket, 1)
|
||||
if s.is_empty then
|
||||
Result.report_error (invalid_data, "Incomplete data for mask and payload len")
|
||||
else
|
||||
l_byte := s [1].code
|
||||
debug ("ws")
|
||||
print (" mask,payload_len(7)=")
|
||||
print (to_byte_representation (l_byte))
|
||||
io.put_new_line
|
||||
end
|
||||
l_has_mask := l_byte & (0b10000000) /= 0 -- MASK
|
||||
l_len := l_byte & 0b01111111 -- 7bits
|
||||
|
||||
debug ("ws")
|
||||
print (" payload_len=" + l_len.out)
|
||||
io.put_new_line
|
||||
end
|
||||
if Result.is_control and then l_len > 125 then
|
||||
-- All control frames MUST have a payload length of 125 bytes or less
|
||||
-- and MUST NOT be fragmented.
|
||||
Result.report_error (protocol_error, "Control frame MUST have a payload length of 125 bytes or less")
|
||||
elseif l_len = 127 then -- TODO proof of concept read 8 bytes.
|
||||
-- the following 8 bytes interpreted as a 64-bit unsigned integer
|
||||
-- (the most significant bit MUST be 0) are the payload length.
|
||||
-- Multibyte length quantities are expressed in network byte order.
|
||||
s := next_bytes (l_socket, 8) -- 64 bits
|
||||
debug ("ws")
|
||||
print (" extended payload length=" + string_to_byte_representation (s))
|
||||
io.put_new_line
|
||||
end
|
||||
if s.count < 8 then
|
||||
Result.report_error (Invalid_data, "Incomplete data for 64 bit Extended payload length")
|
||||
else
|
||||
l_payload_len := s [8].natural_32_code.to_natural_64
|
||||
l_payload_len := l_payload_len | (s [7].natural_32_code.to_natural_64 |<< 8)
|
||||
l_payload_len := l_payload_len | (s [6].natural_32_code.to_natural_64 |<< 16)
|
||||
l_payload_len := l_payload_len | (s [5].natural_32_code.to_natural_64 |<< 24)
|
||||
l_payload_len := l_payload_len | (s [4].natural_32_code.to_natural_64 |<< 32)
|
||||
l_payload_len := l_payload_len | (s [3].natural_32_code.to_natural_64 |<< 40)
|
||||
l_payload_len := l_payload_len | (s [2].natural_32_code.to_natural_64 |<< 48)
|
||||
l_payload_len := l_payload_len | (s [1].natural_32_code.to_natural_64 |<< 56)
|
||||
end
|
||||
elseif l_len = 126 then
|
||||
s := next_bytes (l_socket, 2) -- 16 bits
|
||||
debug ("ws")
|
||||
print (" extended payload length bits=" + string_to_byte_representation (s))
|
||||
io.put_new_line
|
||||
end
|
||||
if s.count < 2 then
|
||||
Result.report_error (Invalid_data, "Incomplete data for 16 bit Extended payload length")
|
||||
else
|
||||
l_payload_len := s [2].natural_32_code.to_natural_64
|
||||
l_payload_len := l_payload_len | (s [1].natural_32_code.to_natural_64 |<< 8)
|
||||
end
|
||||
else
|
||||
l_payload_len := l_len.to_natural_64
|
||||
end
|
||||
debug ("ws")
|
||||
print (" Full payload length=" + l_payload_len.out)
|
||||
io.put_new_line
|
||||
end
|
||||
if Result.is_valid then
|
||||
if l_has_mask then
|
||||
l_masking_key := next_bytes (l_socket, 4) -- 32 bits
|
||||
debug ("ws")
|
||||
print (" Masking key bits=" + string_to_byte_representation (l_masking_key))
|
||||
io.put_new_line
|
||||
end
|
||||
if l_masking_key.count < 4 then
|
||||
debug ("ws")
|
||||
print ("masking-key read stream -> " + l_socket.bytes_read.out + " bits%N")
|
||||
end
|
||||
Result.report_error (Invalid_data, "Incomplete data for Masking-key")
|
||||
l_masking_key := Void
|
||||
end
|
||||
else
|
||||
Result.report_error (protocol_error, "All frames sent from client to server are masked!")
|
||||
end
|
||||
if Result.is_valid then
|
||||
l_chunk_size := 0x4000 -- 16 K
|
||||
if l_payload_len > {INTEGER_32}.max_value.to_natural_64 then
|
||||
-- Issue .. to big to store in STRING
|
||||
-- FIXME !!!
|
||||
Result.report_error (Message_too_large, "Can not handle payload data (len=" + l_payload_len.out + ")")
|
||||
else
|
||||
l_len := l_payload_len.to_integer_32
|
||||
end
|
||||
from
|
||||
l_fetch_count := 0
|
||||
l_remaining_len := l_len
|
||||
until
|
||||
l_fetch_count >= l_len or l_len = 0 or not Result.is_valid
|
||||
loop
|
||||
if l_remaining_len < l_chunk_size then
|
||||
l_chunk_size := l_remaining_len
|
||||
end
|
||||
l_socket.read_stream (l_chunk_size)
|
||||
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")
|
||||
end
|
||||
if l_bytes_read > 0 then
|
||||
l_remaining_len := l_remaining_len - l_bytes_read
|
||||
l_chunk := l_socket.last_string
|
||||
if l_masking_key /= Void then
|
||||
-- Masking
|
||||
-- http://tools.ietf.org/html/rfc6455#section-5.3
|
||||
unmask (l_chunk, l_fetch_count + 1, l_masking_key)
|
||||
else
|
||||
check
|
||||
client_frame_should_always_be_encoded: False
|
||||
end
|
||||
end
|
||||
l_fetch_count := l_fetch_count + l_bytes_read
|
||||
Result.append_payload_data_chop (l_chunk, l_bytes_read, l_remaining_len = 0)
|
||||
else
|
||||
Result.report_error (internal_error, "Issue reading payload data...")
|
||||
end
|
||||
end
|
||||
if is_verbose then
|
||||
log (" Received " + l_fetch_count.out + " out of " + l_len.out + " bytes <===============", debug_level)
|
||||
end
|
||||
debug ("ws")
|
||||
print (" -> ")
|
||||
if attached Result.payload_data as l_payload_data then
|
||||
s := l_payload_data.tail (l_fetch_count)
|
||||
if s.count > 50 then
|
||||
print (string_to_byte_hexa_representation (s.head (50) + ".."))
|
||||
else
|
||||
print (string_to_byte_hexa_representation (s))
|
||||
end
|
||||
print ("%N")
|
||||
if Result.is_text and Result.is_fin and Result.fragment_count = 0 then
|
||||
print (" -> ")
|
||||
if s.count > 50 then
|
||||
print (s.head (50) + "..")
|
||||
else
|
||||
print (s)
|
||||
end
|
||||
print ("%N")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
if Result /= Void then
|
||||
if attached Result.error as err then
|
||||
if is_verbose then
|
||||
log (" !Invalid frame: " + err.string, debug_level)
|
||||
end
|
||||
end
|
||||
if Result.is_injected_control then
|
||||
if attached Result.parent as l_parent then
|
||||
if not Result.is_valid then
|
||||
l_parent.report_error (protocol_error, "Invalid injected frame")
|
||||
end
|
||||
if Result.is_connection_close then
|
||||
-- Return this and process the connection close right away!
|
||||
l_parent.update_fin (True)
|
||||
l_fin := Result.is_fin
|
||||
else
|
||||
Result := l_parent
|
||||
l_fin := l_parent.is_fin
|
||||
check
|
||||
-- This is a control frame but occurs in fragmented frame.
|
||||
inside_fragmented_frame: not l_fin
|
||||
end
|
||||
end
|
||||
else
|
||||
check
|
||||
has_parent: False
|
||||
end
|
||||
l_fin := False -- This is a control frame but occurs in fragmented frame.
|
||||
end
|
||||
end
|
||||
if not Result.is_valid then
|
||||
is_data_frame_ok := False
|
||||
end
|
||||
else
|
||||
is_data_frame_ok := False
|
||||
end
|
||||
end
|
||||
end
|
||||
has_error := Result = Void or else Result.has_error
|
||||
rescue
|
||||
retried := True
|
||||
if Result /= Void then
|
||||
Result.report_error (internal_error, "Internal error")
|
||||
end
|
||||
retry
|
||||
end
|
||||
|
||||
|
||||
feature -- Encoding
|
||||
|
||||
digest (a_sha1: SHA1): STRING
|
||||
-- Digest of `a_sha1'.
|
||||
-- Should by in SHA1 class
|
||||
local
|
||||
l_digest: SPECIAL [NATURAL_8]
|
||||
index, l_upper: INTEGER
|
||||
do
|
||||
l_digest := a_sha1.digest
|
||||
create Result.make (l_digest.count // 2)
|
||||
from
|
||||
index := l_digest.Lower
|
||||
l_upper := l_digest.upper
|
||||
until
|
||||
index > l_upper
|
||||
loop
|
||||
Result.append_character (l_digest [index].to_character_8)
|
||||
index := index + 1
|
||||
end
|
||||
end
|
||||
|
||||
feature {NONE} -- Socket helpers
|
||||
|
||||
next_bytes (a_socket: HTTPD_STREAM_SOCKET; nb: INTEGER): STRING
|
||||
require
|
||||
nb > 0
|
||||
local
|
||||
n, l_bytes_read: INTEGER
|
||||
do
|
||||
create Result.make (nb)
|
||||
from
|
||||
n := nb
|
||||
until
|
||||
n = 0
|
||||
loop
|
||||
a_socket.read_stream (nb)
|
||||
l_bytes_read := a_socket.bytes_read
|
||||
if l_bytes_read > 0 then
|
||||
Result.append (a_socket.last_string)
|
||||
n := n - l_bytes_read
|
||||
else
|
||||
n := 0
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
feature -- Masking Data Client - Server
|
||||
|
||||
unmask (a_chunk: STRING_8; a_pos: INTEGER; a_key: READABLE_STRING_8)
|
||||
local
|
||||
i, n: INTEGER
|
||||
do
|
||||
from
|
||||
i := 1
|
||||
n := a_chunk.count
|
||||
until
|
||||
i > n
|
||||
loop
|
||||
a_chunk.put_code (a_chunk.code (i).bit_xor (a_key [((i + (a_pos - 1) - 1) \\ 4) + 1].natural_32_code), i)
|
||||
i := i + 1
|
||||
end
|
||||
end
|
||||
|
||||
append_chunk_unmasked (a_chunk: READABLE_STRING_8; a_pos: INTEGER; a_key: READABLE_STRING_8; a_target: STRING)
|
||||
-- To convert masked data into unmasked data, or vice versa, the following
|
||||
-- algorithm is applied. The same algorithm applies regardless of the
|
||||
-- direction of the translation, e.g., the same steps are applied to
|
||||
-- mask the data as to unmask the data.
|
||||
|
||||
-- Octet i of the transformed data ("transformed-octet-i") is the XOR of
|
||||
-- octet i of the original data ("original-octet-i") with octet at index
|
||||
-- i modulo 4 of the masking key ("masking-key-octet-j"):
|
||||
|
||||
-- j = i MOD 4
|
||||
-- transformed-octet-i = original-octet-i XOR masking-key-octet-j
|
||||
|
||||
-- The payload length, indicated in the framing as frame-payload-length,
|
||||
-- does NOT include the length of the masking key. It is the length of
|
||||
-- the "Payload data", e.g., the number of bytes following the masking
|
||||
-- key.
|
||||
note
|
||||
EIS: "name=Masking", "src=http://tools.ietf.org/html/rfc6455#section-5.3", "protocol=uri"
|
||||
local
|
||||
i, n: INTEGER
|
||||
do
|
||||
-- debug ("ws")
|
||||
-- print ("append_chunk_unmasked (%"" + string_to_byte_representation (a_chunk) + "%",%N%Ta_pos=" + a_pos.out+ ", a_key, a_target #.count=" + a_target.count.out + ")%N")
|
||||
-- end
|
||||
from
|
||||
i := 1
|
||||
n := a_chunk.count
|
||||
until
|
||||
i > n
|
||||
loop
|
||||
a_target.append_code (a_chunk.code (i).bit_xor (a_key [((i + (a_pos - 1) - 1) \\ 4) + 1].natural_32_code))
|
||||
i := i + 1
|
||||
end
|
||||
end
|
||||
|
||||
feature {NONE} -- Debug
|
||||
|
||||
to_byte_representation (a_integer: INTEGER): STRING
|
||||
require
|
||||
valid: a_integer >= 0 and then a_integer <= 255
|
||||
local
|
||||
l_val: INTEGER
|
||||
do
|
||||
create Result.make (8)
|
||||
from
|
||||
l_val := a_integer
|
||||
until
|
||||
l_val < 2
|
||||
loop
|
||||
Result.prepend_integer (l_val \\ 2)
|
||||
l_val := l_val // 2
|
||||
end
|
||||
Result.prepend_integer (l_val)
|
||||
end
|
||||
|
||||
string_to_byte_representation (s: STRING): STRING
|
||||
require
|
||||
valid: s.count > 0
|
||||
local
|
||||
i, n: INTEGER
|
||||
do
|
||||
n := s.count
|
||||
create Result.make (8 * n)
|
||||
if n > 0 then
|
||||
from
|
||||
i := 1
|
||||
until
|
||||
i > n
|
||||
loop
|
||||
if not Result.is_empty then
|
||||
Result.append_character (':')
|
||||
end
|
||||
Result.append (to_byte_representation (s [i].code))
|
||||
i := i + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
string_to_byte_hexa_representation (s: STRING): STRING
|
||||
local
|
||||
i, n: INTEGER
|
||||
c: INTEGER
|
||||
do
|
||||
n := s.count
|
||||
create Result.make (8 * n)
|
||||
if n > 0 then
|
||||
from
|
||||
i := 1
|
||||
until
|
||||
i > n
|
||||
loop
|
||||
if not Result.is_empty then
|
||||
Result.append_character (':')
|
||||
end
|
||||
c := s [i].code
|
||||
check
|
||||
c <= 0xFF
|
||||
end
|
||||
Result.append_character (((c |>> 4) & 0xF).to_hex_character)
|
||||
Result.append_character (((c) & 0xF).to_hex_character)
|
||||
i := i + 1
|
||||
end
|
||||
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
|
||||
@@ -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
|
||||
@@ -0,0 +1,44 @@
|
||||
note
|
||||
description: "Summary description for {WGI_STANDALONE_WEBSOCKET_CONNECTOR}."
|
||||
author: ""
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
class
|
||||
WGI_STANDALONE_WEBSOCKET_CONNECTOR [G -> WGI_EXECUTION create make end]
|
||||
|
||||
inherit
|
||||
WGI_STANDALONE_CONNECTOR [G]
|
||||
redefine
|
||||
name, version
|
||||
end
|
||||
|
||||
create
|
||||
make,
|
||||
make_with_base
|
||||
|
||||
feature -- Access
|
||||
|
||||
name: STRING_8
|
||||
-- Name of Current connector
|
||||
once
|
||||
Result := "ws_httpd"
|
||||
end
|
||||
|
||||
version: STRING_8
|
||||
-- Version of Current connector
|
||||
once
|
||||
Result := "1.0"
|
||||
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
|
||||
@@ -0,0 +1,49 @@
|
||||
note
|
||||
description: "[
|
||||
Component to launch the service using the default connector
|
||||
|
||||
Eiffel Web httpd for this class
|
||||
|
||||
|
||||
The httpd default connector support options:
|
||||
port: numeric such as 8099 (or equivalent string as "8099")
|
||||
base: base_url (very specific to standalone server)
|
||||
verbose: to display verbose output, useful for standalone connector
|
||||
force_single_threaded: use only one thread, useful for standalone connector
|
||||
|
||||
check WSF_SERVICE_LAUNCHER for more documentation
|
||||
]"
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
class
|
||||
WSF_STANDALONE_WEBSOCKET_SERVICE_LAUNCHER [G -> WSF_WEBSOCKET_EXECUTION create make end]
|
||||
|
||||
inherit
|
||||
WSF_STANDALONE_SERVICE_LAUNCHER [G]
|
||||
redefine
|
||||
connector
|
||||
end
|
||||
|
||||
create
|
||||
make,
|
||||
make_and_launch
|
||||
|
||||
feature -- Status report
|
||||
|
||||
connector: WGI_STANDALONE_WEBSOCKET_CONNECTOR [G]
|
||||
-- Default connector
|
||||
|
||||
;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
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
note
|
||||
description: "[
|
||||
Request execution based on attributes `request' and `response'.
|
||||
Also support Upgrade to Websocket protocol.
|
||||
|
||||
|
||||
]"
|
||||
author: "$Author$"
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
deferred class
|
||||
WSF_WEBSOCKET_EXECUTION
|
||||
|
||||
inherit
|
||||
WSF_EXECUTION
|
||||
rename
|
||||
execute as http_execute
|
||||
end
|
||||
|
||||
--create
|
||||
-- make
|
||||
|
||||
feature -- Execution
|
||||
|
||||
frozen http_execute
|
||||
local
|
||||
ws: WEB_SOCKET
|
||||
ws_h: like new_websocket_handler
|
||||
do
|
||||
create ws.make (request, response)
|
||||
ws.open_ws_handshake
|
||||
if ws.is_websocket then
|
||||
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
|
||||
-- Execute Current request,
|
||||
-- getting data from `request'
|
||||
-- and response to client via `response'.
|
||||
deferred
|
||||
end
|
||||
|
||||
feature -- Factory
|
||||
|
||||
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
|
||||
|
||||
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,13 +1,3 @@
|
||||
note
|
||||
title: Web Server Foundation
|
||||
description: Core of the Eiffel Web Framework, used to build web server application.
|
||||
tags: ewf,server,httpd,request,connector
|
||||
license: Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)
|
||||
link[license]: https://github.com/EiffelWebFramework/EWF/blob/master/LICENSE
|
||||
link[source]: "Github" https://github.com/EiffelWebFramework/EWF
|
||||
link[doc]: "Documentation" http://eiffelwebframework.github.io/EWF/
|
||||
|
||||
end
|
||||
|
||||
package wsf
|
||||
|
||||
@@ -37,6 +27,7 @@ project
|
||||
default_standalone = "default/standalone-safe.ecf"
|
||||
default_standalone = "default/standalone.ecf"
|
||||
|
||||
|
||||
note
|
||||
title: Web Server Foundation
|
||||
description: "[
|
||||
|
||||
@@ -83,6 +83,57 @@ feature -- Access
|
||||
Result := options.item (a_name)
|
||||
end
|
||||
|
||||
feature -- Helpers
|
||||
|
||||
has_integer_option (a_opt_name: READABLE_STRING_GENERAL): BOOLEAN
|
||||
-- Is there any INTEGER value associated to option name `a_opt_name'?
|
||||
local
|
||||
s: READABLE_STRING_GENERAL
|
||||
do
|
||||
if attached option (a_opt_name) as opt then
|
||||
if attached {INTEGER} opt as i then
|
||||
Result := True
|
||||
else
|
||||
s := opt.out
|
||||
Result := s.is_integer
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
option_integer_value (a_opt_name: READABLE_STRING_GENERAL; a_default: INTEGER): INTEGER
|
||||
-- INTEGER value associated to option name `a_opt_name', other return `a_default'.
|
||||
local
|
||||
s: READABLE_STRING_GENERAL
|
||||
do
|
||||
Result := a_default
|
||||
if attached option (a_opt_name) as opt then
|
||||
if attached {INTEGER} opt as i then
|
||||
Result := i
|
||||
else
|
||||
s := opt.out
|
||||
if s.is_integer then
|
||||
Result := s.to_integer
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
option_boolean_value (a_opt_name: READABLE_STRING_GENERAL; a_default: BOOLEAN): BOOLEAN
|
||||
-- BOOLEAN value associated to option name `a_opt_name', other return `a_default'.
|
||||
local
|
||||
s: READABLE_STRING_GENERAL
|
||||
do
|
||||
Result := a_default
|
||||
if attached option (a_opt_name) as opt then
|
||||
if attached {BOOLEAN} opt as b then
|
||||
Result := b
|
||||
else
|
||||
s := opt.out
|
||||
Result := s.is_case_insensitive_equal ("true")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
feature -- Access
|
||||
|
||||
new_cursor: TABLE_ITERATION_CURSOR [detachable ANY, READABLE_STRING_GENERAL]
|
||||
|
||||
@@ -14,7 +14,6 @@ inherit
|
||||
make_from_execution as make_from_wgi_execution
|
||||
redefine
|
||||
make,
|
||||
execute,
|
||||
clean,
|
||||
is_valid_end_of_execution
|
||||
end
|
||||
|
||||
BIN
library/text/parser/feed/tests/msys-iconv-2.dll
Normal file
BIN
library/text/parser/feed/tests/msys-iconv-2.dll
Normal file
Binary file not shown.
@@ -17,6 +17,7 @@
|
||||
<library name="connector_libfcgi" location="..\library\server\ewsgi\connectors\libfcgi\libfcgi-safe.ecf" readonly="false"/>
|
||||
<library name="connector_nino" location="..\library\server\ewsgi\connectors\nino\nino-safe.ecf" readonly="false"/>
|
||||
<library name="connector_null" location="..\library\server\ewsgi\connectors\null\null-safe.ecf" readonly="false"/>
|
||||
<library name="connector_standalone" location="..\library\server\ewsgi\connectors\standalone\standalone-safe.ecf" readonly="false"/>
|
||||
<library name="conneg" location="..\library\network\protocol\content_negotiation\conneg-safe.ecf" readonly="false"/>
|
||||
<library name="default_cgi" location="..\library\server\wsf\default\cgi-safe.ecf" readonly="false"/>
|
||||
<library name="default_libfcgi" location="..\library\server\wsf\default\libfcgi-safe.ecf" readonly="false"/>
|
||||
@@ -30,6 +31,7 @@
|
||||
<library name="filter" location="..\examples\filter\filter-safe.ecf" readonly="false"/>
|
||||
<library name="hello_world" location="..\library\server\ewsgi\examples\hello_world\hello-safe.ecf" readonly="false"/>
|
||||
<library name="http" location="..\library\network\protocol\http\http-safe.ecf" readonly="false"/>
|
||||
<library name="httpd" location="..\library\server\ewsgi\connectors\standalone\lib\httpd\httpd-safe.ecf" readonly="false"/>
|
||||
<library name="http_authorization" location="..\library\server\authentication\http_authorization\http_authorization-safe.ecf" readonly="false"/>
|
||||
<library name="http_client" location="..\library\network\http_client\http_client-safe.ecf" readonly="false"/>
|
||||
<library name="libfcgi" location="..\library\server\libfcgi\libfcgi-safe.ecf" readonly="false"/>
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
<?xml version="1.0" encoding="ISO-8859-1"?>
|
||||
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-9-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-9-0 http://www.eiffel.com/developers/xml/configuration-1-9-0.xsd" name="hello_dev" uuid="7C9887BD-4AE4-47F2-A0AA-4BBB6736D433">
|
||||
<target name="hello_dev" abstract="true">
|
||||
<root class="HELLO_ROUTED_WORLD" feature="make"/>
|
||||
<root class="HELLO_ROUTED_WORLD" feature="make_and_launch"/>
|
||||
<file_rule>
|
||||
<exclude>/EIFGENs$</exclude>
|
||||
<exclude>/\.git$</exclude>
|
||||
<exclude>/\.svn$</exclude>
|
||||
</file_rule>
|
||||
<option debug="true" warning="true" full_class_checking="true" is_attached_by_default="true" void_safety="all" syntax="provisional">
|
||||
<debug name="nino" enabled="true"/>
|
||||
<debug name="standalone" enabled="true"/>
|
||||
<debug name="ew_standalone" enabled="true"/>
|
||||
<assertions precondition="true" postcondition="true" check="true" invariant="true" supplier_precondition="true"/>
|
||||
</option>
|
||||
<setting name="concurrency" value="thread"/>
|
||||
@@ -16,20 +17,20 @@
|
||||
<library name="http" location="../../library/network/protocol/http/http-safe.ecf" readonly="false"/>
|
||||
<library name="wsf" location="..\..\library\server\wsf\wsf-safe.ecf" readonly="false"/>
|
||||
</target>
|
||||
<target name="hello_nino" extends="hello_connector">
|
||||
<target name="hello_standalone" extends="hello_dev">
|
||||
<option debug="true" warning="true" full_class_checking="true" is_attached_by_default="true" void_safety="all" syntax="provisional">
|
||||
<debug name="nino" enabled="true"/>
|
||||
<debug name="standalone" enabled="true"/>
|
||||
<assertions precondition="true" postcondition="true" check="true" invariant="true" supplier_precondition="true"/>
|
||||
</option>
|
||||
<library name="default_nino" location="..\..\library\server\wsf\default\nino-safe.ecf" readonly="false" use_application_options="true"/>
|
||||
<library name="default_standalone" location="..\..\library\server\wsf\default\standalone-safe.ecf" readonly="false" use_application_options="true"/>
|
||||
<cluster name="src" location="src\" recursive="true"/>
|
||||
<override name="override" location="override\" recursive="true"/>
|
||||
</target>
|
||||
<target name="hello_cgi" extends="hello_connector">
|
||||
<target name="hello_cgi" extends="hello_dev">
|
||||
<library name="default_cgi" location="..\..\library\server\wsf\default\cgi-safe.ecf"/>
|
||||
<cluster name="src" location="src\" recursive="true"/>
|
||||
</target>
|
||||
<target name="hello_libfcgi" extends="hello_connector">
|
||||
<target name="hello_libfcgi" extends="hello_dev">
|
||||
<library name="default_libfcgi" location="..\..\library\server\wsf\default\libfcgi-safe.ecf"/>
|
||||
<cluster name="src" location="src\" recursive="true"/>
|
||||
</target>
|
||||
|
||||
@@ -8,243 +8,24 @@ class
|
||||
HELLO_ROUTED_WORLD
|
||||
|
||||
inherit
|
||||
WSF_URI_TEMPLATE_ROUTED_SERVICE
|
||||
|
||||
WSF_HANDLER_HELPER
|
||||
|
||||
WSF_DEFAULT_SERVICE
|
||||
WSF_DEFAULT_SERVICE [HELLO_ROUTED_WORLD_EXECUTION]
|
||||
redefine
|
||||
initialize
|
||||
end
|
||||
|
||||
create
|
||||
make
|
||||
make_and_launch
|
||||
|
||||
feature {NONE} -- Initialization
|
||||
|
||||
make
|
||||
initialize
|
||||
do
|
||||
initialize_router
|
||||
Precursor
|
||||
set_service_option ("port", 8099)
|
||||
make_and_launch
|
||||
end
|
||||
|
||||
create_router
|
||||
do
|
||||
create router.make (5)
|
||||
end
|
||||
|
||||
setup_router
|
||||
local
|
||||
ra: WSF_AGENT_HANDLER [WSF_URI_TEMPLATE_HANDLER_CONTEXT]
|
||||
hello: WSF_URI_TEMPLATE_ROUTING_HANDLER
|
||||
www: WSF_FILE_SYSTEM_HANDLER [WSF_URI_TEMPLATE_HANDLER_CONTEXT]
|
||||
do
|
||||
router.map_agent ("/refresh", agent execute_refresh)
|
||||
router.map_agent ("/home", agent execute_home)
|
||||
create www.make (document_root)
|
||||
www.set_directory_index (<<"index.html">>)
|
||||
|
||||
router.map ("/www{/path}{?query}", www)
|
||||
|
||||
--| Map all "/hello*" using a ROUTING_HANDLER
|
||||
create hello.make (3)
|
||||
router.map ("/hello", hello)
|
||||
|
||||
create ra.make (agent handle_hello)
|
||||
hello.map ("/hello/{name}.{format}", ra)
|
||||
hello.map ("/hello.{format}/{name}", ra)
|
||||
hello.map ("/hello/{name}", ra)
|
||||
|
||||
create ra.make (agent handle_anonymous_hello)
|
||||
hello.map ("/hello", ra)
|
||||
hello.map ("/hello.{format}", ra)
|
||||
|
||||
--| Various various route, directly on the "router"
|
||||
router.map_agent_with_request_methods ("/method/any", agent handle_method_any, Void)
|
||||
router.map_agent_with_request_methods ("/method/guess", agent handle_method_get_or_post, <<"GET", "POST">>)
|
||||
router.map_agent_with_request_methods ("/method/custom", agent handle_method_get, <<"GET">>)
|
||||
router.map_agent_with_request_methods ("/method/custom", agent handle_method_post, <<"POST">>)
|
||||
end
|
||||
|
||||
|
||||
document_root: READABLE_STRING_8
|
||||
local
|
||||
e: EXECUTION_ENVIRONMENT
|
||||
dn: DIRECTORY_NAME
|
||||
once
|
||||
create e
|
||||
create dn.make_from_string (e.current_working_directory)
|
||||
dn.extend ("htdocs")
|
||||
Result := dn.string
|
||||
if Result[Result.count] = Operating_environment.directory_separator then
|
||||
Result := Result.substring (1, Result.count - 1)
|
||||
end
|
||||
end
|
||||
|
||||
feature -- Execution
|
||||
|
||||
|
||||
execute_default (req: WSF_REQUEST; res: WSF_RESPONSE)
|
||||
local
|
||||
l_url: STRING
|
||||
do
|
||||
l_url := req.absolute_script_url ("/home")
|
||||
res.redirect_now_with_content (l_url, "You are now being redirected to " + l_url, {HTTP_MIME_TYPES}.text_html)
|
||||
end
|
||||
|
||||
execute_refresh (ctx: WSF_URI_TEMPLATE_HANDLER_CONTEXT; req: WSF_REQUEST; res: WSF_RESPONSE)
|
||||
local
|
||||
h: HTTP_HEADER
|
||||
l_url: STRING
|
||||
e: EXECUTION_ENVIRONMENT
|
||||
n: INTEGER
|
||||
i: INTEGER
|
||||
s: STRING_8
|
||||
do
|
||||
l_url := req.absolute_script_url ("/home")
|
||||
|
||||
|
||||
|
||||
n := 3
|
||||
create h.make
|
||||
h.put_refresh (l_url, 5)
|
||||
h.put_location (l_url)
|
||||
h.put_content_type_text_plain
|
||||
h.put_transfer_encoding_chunked
|
||||
-- h.put_content_length (0)
|
||||
-- res.set_status_code ({HTTP_STATUS_CODE}.moved_permanently)
|
||||
res.set_status_code ({HTTP_STATUS_CODE}.ok)
|
||||
res.put_header_text (h.string)
|
||||
|
||||
from
|
||||
create e
|
||||
create s.make (255)
|
||||
until
|
||||
n = 0
|
||||
loop
|
||||
if n > 1 then
|
||||
s.append ("%NRedirected to " + l_url + " in " + n.out + " seconds :%N")
|
||||
else
|
||||
s.append ("%NRedirected to " + l_url + " in 1 second :%N")
|
||||
end
|
||||
res.put_chunk (s, Void); s.wipe_out
|
||||
from
|
||||
i := 1
|
||||
until
|
||||
i = 1001
|
||||
loop
|
||||
s.append_character ('.')
|
||||
if i \\ 100 = 0 then
|
||||
s.append_character ('%N')
|
||||
end
|
||||
res.put_chunk (s, Void); s.wipe_out
|
||||
e.sleep (1_000_000)
|
||||
i := i + 1
|
||||
end
|
||||
n := n - 1
|
||||
end
|
||||
s.append ("%NYou are now being redirected...%N")
|
||||
res.put_chunk (s, Void); s.wipe_out
|
||||
res.put_chunk_end
|
||||
end
|
||||
|
||||
execute_home (ctx: WSF_URI_TEMPLATE_HANDLER_CONTEXT; req: WSF_REQUEST; res: WSF_RESPONSE)
|
||||
local
|
||||
l_body: STRING_8
|
||||
do
|
||||
create l_body.make (255)
|
||||
l_body.append ("<html><body>Hello World ?!%N")
|
||||
l_body.append ("<h3>Please try the following links</h3><ul>%N")
|
||||
l_body.append ("<li><a href=%""+ req.script_url ("/") + "%">default</a></li>%N")
|
||||
l_body.append ("<li><a href=%""+ req.script_url ("/refresh") + "%">redirect using refresh and chunked encoding</a></li>%N")
|
||||
l_body.append ("<li><a href=%""+ req.script_url ("/hello") + "%">/hello</a></li>%N")
|
||||
l_body.append ("<li><a href=%""+ req.script_url ("/hello.html/Joce") + "%">/hello.html/Joce</a></li>%N")
|
||||
l_body.append ("<li><a href=%""+ req.script_url ("/hello.json/Joce") + "%">/hello.json/Joce</a></li>%N")
|
||||
l_body.append ("<li><a href=%""+ req.script_url ("/hello/Joce.html") + "%">/hello/Joce.html</a></li>%N")
|
||||
l_body.append ("<li><a href=%""+ req.script_url ("/hello/Joce.xml") + "%">/hello/Joce.xml</a></li>%N")
|
||||
l_body.append ("<li><a href=%""+ req.script_url ("/hello/Joce") + "%">/hello/Joce</a></li>%N")
|
||||
l_body.append ("</ul>%N")
|
||||
if attached req.item ("REQUEST_COUNT") as rqc then
|
||||
l_body.append ("request #"+ rqc.as_string.string + "%N")
|
||||
end
|
||||
l_body.append ("</body></html>%N")
|
||||
|
||||
res.put_header ({HTTP_STATUS_CODE}.ok, <<["Content-Type", "text/html"], ["Content-Length", l_body.count.out]>>)
|
||||
res.put_string (l_body)
|
||||
end
|
||||
|
||||
execute_hello (req: WSF_REQUEST; res: WSF_RESPONSE; a_name: detachable READABLE_STRING_32; ctx: WSF_HANDLER_CONTEXT)
|
||||
local
|
||||
l_response_content_type: detachable STRING
|
||||
h: HTTP_HEADER
|
||||
content_type_supported: ARRAY [STRING]
|
||||
l_body: STRING_8
|
||||
do
|
||||
if a_name /= Void then
|
||||
l_body := "Hello %"" + a_name + "%" !%N"
|
||||
else
|
||||
l_body := "Hello anonymous visitor !%N"
|
||||
end
|
||||
content_type_supported := <<{HTTP_MIME_TYPES}.application_json, {HTTP_MIME_TYPES}.text_html, {HTTP_MIME_TYPES}.text_xml, {HTTP_MIME_TYPES}.text_plain>>
|
||||
inspect ctx.request_format_id ("format", content_type_supported)
|
||||
when {HTTP_FORMAT_CONSTANTS}.json then
|
||||
l_response_content_type := {HTTP_MIME_TYPES}.application_json
|
||||
l_body := "{%N%"application%": %"/hello%",%N %"message%": %"" + l_body + "%" %N}"
|
||||
when {HTTP_FORMAT_CONSTANTS}.html then
|
||||
l_response_content_type := {HTTP_MIME_TYPES}.text_html
|
||||
when {HTTP_FORMAT_CONSTANTS}.xml then
|
||||
l_response_content_type := {HTTP_MIME_TYPES}.text_xml
|
||||
l_body := "<response><application>/hello</application><message>" + l_body + "</message></response>%N"
|
||||
when {HTTP_FORMAT_CONSTANTS}.text then
|
||||
l_response_content_type := {HTTP_MIME_TYPES}.text_plain
|
||||
else
|
||||
execute_content_type_not_allowed (req, res, content_type_supported,
|
||||
<<{HTTP_FORMAT_CONSTANTS}.json_name, {HTTP_FORMAT_CONSTANTS}.html_name, {HTTP_FORMAT_CONSTANTS}.xml_name, {HTTP_FORMAT_CONSTANTS}.text_name>>
|
||||
)
|
||||
end
|
||||
if l_response_content_type /= Void then
|
||||
create h.make
|
||||
h.put_content_type (l_response_content_type)
|
||||
h.put_content_length (l_body.count)
|
||||
res.set_status_code ({HTTP_STATUS_CODE}.ok)
|
||||
res.put_header_text (h.string)
|
||||
res.put_string (l_body)
|
||||
end
|
||||
end
|
||||
|
||||
handle_hello (ctx: WSF_URI_TEMPLATE_HANDLER_CONTEXT; req: WSF_REQUEST; res: WSF_RESPONSE)
|
||||
do
|
||||
execute_hello (req, res, ctx.string_parameter ("name"), ctx)
|
||||
end
|
||||
|
||||
handle_anonymous_hello (ctx: WSF_URI_TEMPLATE_HANDLER_CONTEXT; req: WSF_REQUEST; res: WSF_RESPONSE)
|
||||
do
|
||||
execute_hello (req, res, ctx.string_parameter ("name"), ctx)
|
||||
end
|
||||
|
||||
handle_method_any (ctx: WSF_URI_TEMPLATE_HANDLER_CONTEXT; req: WSF_REQUEST; res: WSF_RESPONSE)
|
||||
do
|
||||
execute_hello (req, res, req.request_method, ctx)
|
||||
end
|
||||
|
||||
handle_method_get (ctx: WSF_URI_TEMPLATE_HANDLER_CONTEXT; req: WSF_REQUEST; res: WSF_RESPONSE)
|
||||
do
|
||||
execute_hello (req, res, "GET", ctx)
|
||||
end
|
||||
|
||||
|
||||
handle_method_post (ctx: WSF_URI_TEMPLATE_HANDLER_CONTEXT; req: WSF_REQUEST; res: WSF_RESPONSE)
|
||||
do
|
||||
execute_hello (req, res, "POST", ctx)
|
||||
end
|
||||
|
||||
handle_method_get_or_post (ctx: WSF_URI_TEMPLATE_HANDLER_CONTEXT; req: WSF_REQUEST; res: WSF_RESPONSE)
|
||||
do
|
||||
execute_hello (req, res, "GET or POST", ctx)
|
||||
end
|
||||
|
||||
|
||||
|
||||
note
|
||||
copyright: "2011-2011, Eiffel Software and others"
|
||||
copyright: "2011-2016, Eiffel Software and others"
|
||||
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
|
||||
source: "[
|
||||
Eiffel Software
|
||||
|
||||
272
tests/dev/src/hello_routed_world_execution.e
Normal file
272
tests/dev/src/hello_routed_world_execution.e
Normal file
@@ -0,0 +1,272 @@
|
||||
note
|
||||
description : "Objects that ..."
|
||||
author : "$Author$"
|
||||
date : "$Date$"
|
||||
revision : "$Revision$"
|
||||
|
||||
class
|
||||
HELLO_ROUTED_WORLD_EXECUTION
|
||||
|
||||
inherit
|
||||
WSF_ROUTED_EXECUTION
|
||||
redefine
|
||||
execute_default
|
||||
end
|
||||
|
||||
WSF_URI_TEMPLATE_HELPER_FOR_ROUTED_EXECUTION
|
||||
|
||||
create
|
||||
make
|
||||
|
||||
feature {NONE} -- Initialization
|
||||
|
||||
setup_router
|
||||
local
|
||||
ra: WSF_URI_TEMPLATE_AGENT_HANDLER
|
||||
hello: WSF_URI_TEMPLATE_ROUTING_HANDLER
|
||||
www: WSF_FILE_SYSTEM_HANDLER
|
||||
do
|
||||
map_uri_template_agent ("/refresh", agent execute_refresh, Void)
|
||||
map_uri_template_agent ("/home", agent execute_home, Void)
|
||||
create www.make (document_root)
|
||||
www.set_directory_index (<<"index.html">>)
|
||||
router.handle ("/www{/path}{?query}", www, Void)
|
||||
|
||||
--| Map all "/hello*" using a ROUTING_HANDLER
|
||||
create hello.make (3)
|
||||
router.handle ("/hello", hello, Void)
|
||||
|
||||
create ra.make (agent handle_hello)
|
||||
hello.router.handle ("/hello/{name}.{format}", ra, Void)
|
||||
hello.router.handle ("/hello.{format}/{name}", ra, Void)
|
||||
hello.router.handle ("/hello/{name}", ra, Void)
|
||||
|
||||
create ra.make (agent handle_anonymous_hello)
|
||||
hello.router.handle ("/hello", ra, Void)
|
||||
hello.router.handle ("/hello.{format}", ra, Void)
|
||||
|
||||
--| Various various route, directly on the "router"
|
||||
map_uri_template_agent ("/method/any", agent handle_method_any, Void)
|
||||
map_uri_template_agent ("/method/guess", agent handle_method_get_or_post, <<"GET", "POST">>)
|
||||
map_uri_template_agent ("/method/custom", agent handle_method_get, <<"GET">>)
|
||||
map_uri_template_agent ("/method/custom", agent handle_method_post, <<"POST">>)
|
||||
end
|
||||
|
||||
|
||||
document_root: READABLE_STRING_8
|
||||
local
|
||||
e: EXECUTION_ENVIRONMENT
|
||||
dn: DIRECTORY_NAME
|
||||
once
|
||||
create e
|
||||
create dn.make_from_string (e.current_working_directory)
|
||||
dn.extend ("htdocs")
|
||||
Result := dn.string
|
||||
if Result[Result.count] = Operating_environment.directory_separator then
|
||||
Result := Result.substring (1, Result.count - 1)
|
||||
end
|
||||
end
|
||||
|
||||
feature -- Execution
|
||||
|
||||
|
||||
execute_default (req: WSF_REQUEST; res: WSF_RESPONSE)
|
||||
local
|
||||
l_url: STRING
|
||||
do
|
||||
l_url := req.absolute_script_url ("/home")
|
||||
res.redirect_now_with_content (l_url, "You are now being redirected to " + l_url, {HTTP_MIME_TYPES}.text_html)
|
||||
end
|
||||
|
||||
execute_refresh (req: WSF_REQUEST; res: WSF_RESPONSE) --ctx: WSF_URI_TEMPLATE_HANDLER_CONTEXT;
|
||||
local
|
||||
h: HTTP_HEADER
|
||||
l_url: STRING
|
||||
e: EXECUTION_ENVIRONMENT
|
||||
n: INTEGER
|
||||
i: INTEGER
|
||||
s: STRING_8
|
||||
do
|
||||
l_url := req.absolute_script_url ("/home")
|
||||
|
||||
|
||||
|
||||
n := 3
|
||||
create h.make
|
||||
h.put_refresh (l_url, 5)
|
||||
h.put_location (l_url)
|
||||
h.put_content_type_text_plain
|
||||
h.put_transfer_encoding_chunked
|
||||
-- h.put_content_length (0)
|
||||
-- res.set_status_code ({HTTP_STATUS_CODE}.moved_permanently)
|
||||
res.set_status_code ({HTTP_STATUS_CODE}.ok)
|
||||
res.put_header_text (h.string)
|
||||
|
||||
from
|
||||
create e
|
||||
create s.make (255)
|
||||
until
|
||||
n = 0
|
||||
loop
|
||||
if n > 1 then
|
||||
s.append ("%NRedirected to " + l_url + " in " + n.out + " seconds :%N")
|
||||
else
|
||||
s.append ("%NRedirected to " + l_url + " in 1 second :%N")
|
||||
end
|
||||
res.put_chunk (s, Void); s.wipe_out
|
||||
from
|
||||
i := 1
|
||||
until
|
||||
i = 1001
|
||||
loop
|
||||
s.append_character ('.')
|
||||
if i \\ 100 = 0 then
|
||||
s.append_character ('%N')
|
||||
end
|
||||
res.put_chunk (s, Void); s.wipe_out
|
||||
e.sleep (1_000_000)
|
||||
i := i + 1
|
||||
end
|
||||
n := n - 1
|
||||
end
|
||||
s.append ("%NYou are now being redirected...%N")
|
||||
res.put_chunk (s, Void); s.wipe_out
|
||||
res.put_chunk_end
|
||||
end
|
||||
|
||||
execute_home (req: WSF_REQUEST; res: WSF_RESPONSE) -- ctx: WSF_URI_TEMPLATE_HANDLER_CONTEXT;
|
||||
local
|
||||
l_body: STRING_8
|
||||
do
|
||||
create l_body.make (255)
|
||||
l_body.append ("<html><body>Hello World ?!%N")
|
||||
l_body.append ("<h3>Please try the following links</h3><ul>%N")
|
||||
l_body.append ("<li><a href=%""+ req.script_url ("/") + "%">default</a></li>%N")
|
||||
l_body.append ("<li><a href=%""+ req.script_url ("/refresh") + "%">redirect using refresh and chunked encoding</a></li>%N")
|
||||
l_body.append ("<li><a href=%""+ req.script_url ("/hello") + "%">/hello</a></li>%N")
|
||||
l_body.append ("<li><a href=%""+ req.script_url ("/hello.html/Joce") + "%">/hello.html/Joce</a></li>%N")
|
||||
l_body.append ("<li><a href=%""+ req.script_url ("/hello.json/Joce") + "%">/hello.json/Joce</a></li>%N")
|
||||
l_body.append ("<li><a href=%""+ req.script_url ("/hello/Joce.html") + "%">/hello/Joce.html</a></li>%N")
|
||||
l_body.append ("<li><a href=%""+ req.script_url ("/hello/Joce.xml") + "%">/hello/Joce.xml</a></li>%N")
|
||||
l_body.append ("<li><a href=%""+ req.script_url ("/hello/Joce") + "%">/hello/Joce</a></li>%N")
|
||||
l_body.append ("</ul>%N")
|
||||
if attached req.item ("REQUEST_COUNT") as rqc then
|
||||
l_body.append ("request #"+ rqc.as_string.url_encoded_value + "%N")
|
||||
end
|
||||
l_body.append ("</body></html>%N")
|
||||
|
||||
res.put_header ({HTTP_STATUS_CODE}.ok, <<["Content-Type", "text/html"], ["Content-Length", l_body.count.out]>>)
|
||||
res.put_string (l_body)
|
||||
end
|
||||
|
||||
execute_hello (req: WSF_REQUEST; res: WSF_RESPONSE; a_name: detachable READABLE_STRING_32)
|
||||
local
|
||||
l_response_content_type: detachable STRING
|
||||
h: HTTP_HEADER
|
||||
content_type_supported: ARRAY [STRING]
|
||||
l_body: STRING_8
|
||||
l_format: detachable READABLE_STRING_GENERAL
|
||||
l_http_format_constants: HTTP_FORMAT_CONSTANTS
|
||||
do
|
||||
if a_name /= Void then
|
||||
l_body := "Hello %"" + a_name + "%" !%N"
|
||||
else
|
||||
l_body := "Hello anonymous visitor !%N"
|
||||
end
|
||||
content_type_supported := <<{HTTP_MIME_TYPES}.application_json, {HTTP_MIME_TYPES}.text_html, {HTTP_MIME_TYPES}.text_xml, {HTTP_MIME_TYPES}.text_plain>>
|
||||
if attached {WSF_STRING} req.path_parameter ("format") as s_format then
|
||||
l_format := s_format.value
|
||||
end
|
||||
if l_format = Void then
|
||||
across
|
||||
content_type_supported as ic
|
||||
until
|
||||
l_format /= Void
|
||||
loop
|
||||
if req.is_content_type_accepted (ic.item) then
|
||||
l_format := ic.item
|
||||
end
|
||||
end
|
||||
end
|
||||
if l_format /= Void then
|
||||
create l_http_format_constants
|
||||
inspect
|
||||
l_http_format_constants.format_id (l_format)
|
||||
when {HTTP_FORMAT_CONSTANTS}.json then
|
||||
l_response_content_type := {HTTP_MIME_TYPES}.application_json
|
||||
l_body := "{%N%"application%": %"/hello%",%N %"message%": %"" + l_body + "%" %N}"
|
||||
when {HTTP_FORMAT_CONSTANTS}.html then
|
||||
l_response_content_type := {HTTP_MIME_TYPES}.text_html
|
||||
when {HTTP_FORMAT_CONSTANTS}.xml then
|
||||
l_response_content_type := {HTTP_MIME_TYPES}.text_xml
|
||||
l_body := "<response><application>/hello</application><message>" + l_body + "</message></response>%N"
|
||||
when {HTTP_FORMAT_CONSTANTS}.text then
|
||||
l_response_content_type := {HTTP_MIME_TYPES}.text_plain
|
||||
else
|
||||
l_response_content_type := Void
|
||||
end
|
||||
end
|
||||
|
||||
if l_response_content_type /= Void then
|
||||
create h.make
|
||||
h.put_content_type (l_response_content_type)
|
||||
h.put_content_length (l_body.count)
|
||||
res.set_status_code ({HTTP_STATUS_CODE}.ok)
|
||||
res.put_header_text (h.string)
|
||||
res.put_string (l_body)
|
||||
else
|
||||
res.send (create {WSF_PRECONDITION_FAILED_MESSAGE}.make (req)) -- FIXME: better error message!
|
||||
end
|
||||
end
|
||||
|
||||
string_path_parameter (req: WSF_REQUEST; a_name: READABLE_STRING_GENERAL): detachable STRING_32
|
||||
do
|
||||
if attached {WSF_STRING} req.path_parameter (a_name) as s then
|
||||
Result := s.value
|
||||
end
|
||||
end
|
||||
|
||||
handle_hello (req: WSF_REQUEST; res: WSF_RESPONSE)
|
||||
do
|
||||
execute_hello (req, res, string_path_parameter (req, "name"))
|
||||
end
|
||||
|
||||
handle_anonymous_hello (req: WSF_REQUEST; res: WSF_RESPONSE)
|
||||
do
|
||||
execute_hello (req, res, string_path_parameter (req, "name"))
|
||||
end
|
||||
|
||||
handle_method_any (req: WSF_REQUEST; res: WSF_RESPONSE)
|
||||
do
|
||||
execute_hello (req, res, req.request_method)
|
||||
end
|
||||
|
||||
handle_method_get (req: WSF_REQUEST; res: WSF_RESPONSE)
|
||||
do
|
||||
execute_hello (req, res, "GET")
|
||||
end
|
||||
|
||||
|
||||
handle_method_post (req: WSF_REQUEST; res: WSF_RESPONSE)
|
||||
do
|
||||
execute_hello (req, res, "POST")
|
||||
end
|
||||
|
||||
handle_method_get_or_post (req: WSF_REQUEST; res: WSF_RESPONSE)
|
||||
do
|
||||
execute_hello (req, res, "GET or POST")
|
||||
end
|
||||
|
||||
|
||||
|
||||
note
|
||||
copyright: "2011-2016, 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
|
||||
Reference in New Issue
Block a user