Compare commits

...

13 Commits

Author SHA1 Message Date
80254b2278 When possible keep ecf location relative within the same EiffelWeb directory structure. 2016-08-06 10:07:42 +02:00
8b172b5d33 Revisited WSF_REQUEST.read_input_data* functions:
- read_input_data_into_file now accepts a IO_MEDIUM argument instead of just FILE.
- cleaned the implementation, and make sure that eventual `raw_input_data` is containing only the raw input data.
2016-08-05 11:32:14 +02:00
cc2d7dbb1c Ignore empty header line. 2016-08-05 11:28:59 +02:00
c88394b9fd Added support for category in ATOM format (input and output). 2016-06-24 13:03:09 +02:00
4283662f43 Removed unwanted .ecf file. 2016-06-22 10:55:41 +02:00
1b951376f9 Added more application logic for the example. 2016-06-22 10:52:36 +02:00
193cc3cbde Renamed WGI_STANDALONE_CONNECTOR_ACCESS as WGI_STANDALONE_CONNECTOR_EXPORTER.
Isolate the websocket implementation in descendant of {WEB_SOCKET_EVENT_I}.
Added very simple echo websocket example.
+ code cleaning.
2016-06-22 10:46:15 +02:00
b49e841ac7 Added WSF standalone_websocket connector, that provides websocket on top of standalone connector. 2016-06-21 23:37:48 +02:00
8ba74e1c90 Log when a persistent connection is reused.
Use anchor type on `{WGI_STANDALONE_CONNECTOR}.configuration` and `{WSF_STANDALONE_SERVICE_LAUNCHER}.connector`.
Add access to the socket of standalone input stream from `{WSF_STANDALONE_CONNECTOR_ACCESS}`.
Removed a useless redefination in `WSF_EXECUTION`.
2016-06-21 23:36:22 +02:00
0cecb9594c Fixed signature of {HTTPD_CONFIGURATION_I}.set_ca_key . 2016-06-16 10:37:26 +02:00
e384a6d6ed Make it easier to reuse the http network classes.
This is to make it easier for websocket solution to reuse httpd implementation.
2016-06-16 10:23:30 +02:00
71a5c086a5 Moved httpd from src to lib, under standalone connector. 2016-06-15 18:04:00 +02:00
dfa60bf8f5 Prepared httpd_stream to be useable for client too.
Fixed obsolete tests/dev compilation (mainly to avoid wrong failure reports).
added package.iron files.
2016-06-15 17:56:22 +02:00
76 changed files with 2802 additions and 315 deletions

View File

@@ -0,0 +1,29 @@
note
description : "simple application root class"
date : "$Date$"
revision : "$Revision$"
class
APPLICATION
create
make_and_launch
feature {NONE} -- Initialization
make_and_launch
local
l_launcher: WSF_STANDALONE_WEBSOCKET_SERVICE_LAUNCHER [APPLICATION_EXECUTION]
opts: WSF_SERVICE_LAUNCHER_OPTIONS
do
create {WSF_SERVICE_LAUNCHER_OPTIONS_FROM_INI} opts.make_from_file ("ws.ini")
create l_launcher.make_and_launch (options)
end
options: WSF_SERVICE_LAUNCHER_OPTIONS
-- Initialize current service.
do
create {WSF_SERVICE_LAUNCHER_OPTIONS_FROM_INI} Result.make_from_file ("ws.ini")
end
end

View File

@@ -0,0 +1,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

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-15-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-15-0 http://www.eiffel.com/developers/xml/configuration-1-15-0.xsd" name="websocket_app" uuid="75D17C20-10A8-4E4C-A059-33D72A2B6AEF">
<target name="websocket_app">
<root class="APPLICATION" feature="make_and_launch"/>
<file_rule>
<exclude>/.svn$</exclude>
<exclude>/CVS$</exclude>
<exclude>/EIFGENs$</exclude>
</file_rule>
<option warning="true" is_attached_by_default="true" void_safety="all" syntax="transitional">
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
</option>
<setting name="console_application" value="true"/>
<setting name="concurrency" value="scoop"/>
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
<library name="http" location="..\..\library\network\protocol\http\http-safe.ecf"/>
<library name="standalone_websocket_connector" location="..\..\library\server\wsf\connector\standalone_websocket-safe.ecf" readonly="false"/>
<library name="wsf" location="..\..\library\server\wsf\wsf-safe.ecf"/>
<cluster name="app" location=".\" recursive="true"/>
</target>
</system>

View File

@@ -0,0 +1,8 @@
verbose=true
verbose_level=INFORMATION
port=9090
max_concurrent_connections=100
keep_alive_timeout=35
max_tcp_clients=100
socket_timeout=30000
max_keep_alive_requests=3000

View File

@@ -180,7 +180,9 @@ feature -- Header: adding
if line [line.count] = '%R' then
line.remove_tail (1)
end
add_header (line)
if not line.is_empty then
add_header (line)
end
end
end
end

View File

@@ -92,10 +92,10 @@ 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
@@ -218,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)

View File

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

View File

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

View File

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

View File

@@ -11,7 +11,6 @@
</option>
<setting name="concurrency" value="thread"/>
<library name="base" location="$ISE_LIBRARY\library\base\base.ecf"/>
<library name="encoder" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\text\encoder\encoder.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>
@@ -24,12 +23,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>

View File

@@ -202,6 +202,8 @@ feature -- Execution
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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
note
description: "[
Standalone Web Server connector
Standalone Web Server connector.
]"
date: "$Date$"
revision: "$Revision$"
@@ -20,7 +20,7 @@ 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
@@ -51,7 +51,7 @@ feature {NONE} -- Separate helper
a_server.set_observer (observer)
end
update_factory (conn: detachable separate WGI_STANDALONE_CONNECTOR [G]; fac: separate WGI_HTTPD_REQUEST_HANDLER_FACTORY [G]; a_conf: separate HTTPD_CONFIGURATION)
update_factory (conn: detachable separate WGI_STANDALONE_CONNECTOR [G]; fac: separate WGI_HTTPD_REQUEST_HANDLER_FACTORY [G]; a_conf: like configuration)
do
fac.update_with (conn, a_conf)
end
@@ -63,11 +63,17 @@ 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
@@ -238,7 +244,7 @@ feature {NONE} -- Implementation: element change
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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-15-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-15-0 http://www.eiffel.com/developers/xml/configuration-1-15-0.xsd" name="wsf_standalone_websocket" uuid="7C83D4B4-39C9-4D27-941B-0F0AAD45122E" library_target="wsf_standalone_websocket">
<target name="wsf_standalone_websocket">
<root all_classes="true"/>
<file_rule>
<exclude>/EIFGENs$</exclude>
<exclude>/\.git$</exclude>
<exclude>/\.svn$</exclude>
</file_rule>
<option warning="true" full_class_checking="true" is_obsolete_routine_type="true" void_safety="none" syntax="provisional">
</option>
<library name="base" location="$ISE_LIBRARY\library\base\base.ecf"/>
<library name="connector_standalone" location="..\..\ewsgi\connectors\standalone\standalone.ecf"/>
<library name="crypto" location="$ISE_LIBRARY\unstable\library\text\encryption\crypto\crypto.ecf"/>
<library name="encoder" location="..\..\..\text\encoder\encoder.ecf" readonly="false"/>
<library name="error" location="..\..\..\utility\general\error\error.ecf"/>
<library name="ewsgi" location="..\..\ewsgi\ewsgi.ecf"/>
<library name="http" location="..\..\..\network\protocol\http\http.ecf"/>
<library name="httpd" location="..\..\ewsgi\connectors\standalone\lib\httpd\httpd.ecf"/>
<library name="time" location="$ISE_LIBRARY\library\time\time.ecf"/>
<library name="wsf" location="..\wsf.ecf"/>
<library name="wsf_standalone" location="standalone.ecf"/>
<cluster name="wsf_standalone_websocket" location=".\standalone_websocket\" recursive="true"/>
</target>
</system>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,152 @@
note
description: "[
To implement websocket handling, provide a `callbacks` object implementing the {WEB_SOCKET_EVENT_I} interface.
]"
date: "$Date$"
revision: "$Revision$"
class
WEB_SOCKET_HANDLER
inherit
WEB_SOCKET_CONSTANTS
REFACTORING_HELPER
HTTPD_LOGGER_CONSTANTS
create
make
feature {NONE} -- Initialization
make (ws: WEB_SOCKET; a_callbacks: WEB_SOCKET_EVENT_I)
do
web_socket := ws
callbacks := a_callbacks
end
feature -- Access
web_socket: WEB_SOCKET
-- Associated websocket.
callbacks: WEB_SOCKET_EVENT_I
feature -- Execution
frozen execute
do
callbacks.on_open (web_socket)
execute_websocket
end
execute_websocket
local
exit: BOOLEAN
l_frame: detachable WEB_SOCKET_FRAME
l_client_message: detachable READABLE_STRING_8
l_utf: UTF_CONVERTER
ws: like web_socket
s: STRING
do
from
-- loop until ws is closed or has error.
ws := web_socket
until
exit
loop
debug ("dbglog")
dbglog (generator + ".execute_websocket (loop) WS_REQUEST_HANDLER.process_request {" + ws.socket_descriptor.out + "}")
end
if ws.is_ready_for_reading then
l_frame := ws.next_frame
if l_frame /= Void and then l_frame.is_valid then
if attached l_frame.injected_control_frames as l_injections then
-- Process injected control frames now.
-- FIXME
across
l_injections as ic
loop
if ic.item.is_connection_close then
-- FIXME: we should probably send this event .. after the `l_frame.parent' frame event.
callbacks.on_event (ws, ic.item.payload_data, ic.item.opcode)
exit := True
elseif ic.item.is_ping then
-- FIXME reply only to the most recent ping ...
callbacks.on_event (ws, ic.item.payload_data, ic.item.opcode)
else
callbacks.on_event (ws, ic.item.payload_data, ic.item.opcode)
end
end
end
l_client_message := l_frame.payload_data
if l_client_message = Void then
l_client_message := ""
end
debug ("ws")
create s.make_from_string ("%NExecute: %N")
s.append (" [opcode: "+ opcode_name (l_frame.opcode) +"]%N")
if l_frame.is_text then
s.append (" [client message: %""+ l_client_message +"%"]%N")
elseif l_frame.is_binary then
s.append (" [client binary message length: %""+ l_client_message.count.out +"%"]%N")
end
s.append (" [is_control: " + l_frame.is_control.out + "]%N")
s.append (" [is_binary: " + l_frame.is_binary.out + "]%N")
s.append (" [is_text: " + l_frame.is_text.out + "]%N")
dbglog (s)
end
if l_frame.is_connection_close then
callbacks.on_event (ws, l_client_message, l_frame.opcode)
exit := True
elseif l_frame.is_binary then
callbacks.on_event (ws, l_client_message, l_frame.opcode)
elseif l_frame.is_text then
check is_valid_utf_8: l_utf.is_valid_utf_8_string_8 (l_client_message) end
callbacks.on_event (ws, l_client_message, l_frame.opcode)
else
callbacks.on_event (ws, l_client_message, l_frame.opcode)
end
else
debug ("ws")
create s.make_from_string ("%NExecute: %N")
s.append (" [ERROR: invalid frame]%N")
if l_frame /= Void and then attached l_frame.error as err then
s.append (" [Code: "+ err.code.out +"]%N")
s.append (" [Description: "+ err.description +"]%N")
end
dbglog (s)
end
callbacks.on_event (ws, "", connection_close_frame)
exit := True -- FIXME: check proper close protocol
end
else
debug ("ws")
dbglog (generator + ".WAITING WS_REQUEST_HANDLER.process_request {" + ws.socket_descriptor.out + "}")
end
end
end
end
feature {NONE} -- Logging
dbglog (m: READABLE_STRING_8)
do
web_socket.log (m, debug_level)
end
note
copyright: "2011-2016, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
source: "[
Eiffel Software
5949 Hollister Ave., Goleta, CA 93117 USA
Telephone 805-685-1006, Fax 805-685-6869
Website http://www.eiffel.com
Customer support http://support.eiffel.com
]"
end

View File

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

View File

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

View File

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

View File

@@ -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: "[

View File

@@ -14,7 +14,6 @@ inherit
make_from_execution as make_from_wgi_execution
redefine
make,
execute,
clean,
is_valid_end_of_execution
end

View File

@@ -261,10 +261,12 @@ feature -- Access: Input
local
l_input: WGI_INPUT_STREAM
n: INTEGER
buf_initial_size: INTEGER
do
if raw_input_data_recorded and then attached raw_input_data as d then
buf.append (d)
else
buf_initial_size := buf.count
l_input := input
if is_chunked_input then
from
@@ -286,73 +288,53 @@ feature -- Access: Input
end
end
if raw_input_data_recorded then
set_raw_input_data (buf)
set_raw_input_data (buf.substring (buf_initial_size + 1, buf.count))
-- Only the input data! And differente reference.
end
end
end
read_input_data_into_file (a_file: FILE)
read_input_data_into_file (a_medium: IO_MEDIUM)
-- retrieve the content from the `input' stream into `s'
-- warning: if the input data has already been retrieved
-- you might not get anything
require
a_file_is_open_write: a_file.is_open_write
a_medium_is_open_write: a_medium.is_open_write
local
s: STRING
l_input: WGI_INPUT_STREAM
l_raw_data: detachable STRING_8
len: NATURAL_64
nb, l_step: INTEGER
l_size: NATURAL_64
do
if raw_input_data_recorded and then attached raw_input_data as d then
a_file.put_string (d)
a_medium.put_string (d)
else
if raw_input_data_recorded then
create l_raw_data.make_empty
end
l_input := input
len := content_length_value
debug ("wsf")
io.error.put_string (generator + ".read_input_data_into_file (a_file) content_length=" + len.out + "%N")
end
from
l_size := 0
l_step := 8_192
create s.make (l_step)
until
l_step = 0 or l_input.end_of_input
loop
if len < l_step.to_natural_64 then
l_step := len.to_integer_32
l_input.append_to_string (s, l_step)
nb := l_input.last_appended_count
a_medium.put_string (s)
if l_raw_data /= Void then
l_raw_data.append (s)
end
if l_step > 0 then
l_input.append_to_string (s, l_step)
nb := l_input.last_appended_count
l_size := l_size + nb.to_natural_64
len := len - nb.to_natural_64
debug ("wsf")
io.error.put_string (" append (s, " + l_step.out + ") -> " + nb.out + " (" + l_size.out + " / "+ content_length_value.out + ")%N")
end
a_file.put_string (s)
if l_raw_data /= Void then
l_raw_data.append (s)
end
s.wipe_out
if nb < l_step then
l_step := 0
end
s.wipe_out
if nb < l_step then
l_step := 0
end
end
a_file.flush
debug ("wsf")
io.error.put_string ("offset =" + len.out + "%N")
if attached {FILE} a_medium as f then
f.flush
end
check got_all_data: len = 0 end
if l_raw_data /= Void then
set_raw_input_data (l_raw_data)
end

View File

@@ -5,7 +5,7 @@
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
<library name="base_extension" location="$ISE_LIBRARY\library\base_extension\base_extension-safe.ecf"/>
<library name="encoders" location="..\..\encoder\encoder-safe.ecf"/>
<library name="http" location="$ISE_LIBRARY\contrib\library\network\protocol\http\http-safe.ecf"/>
<library name="http" location="..\..\..\network\protocol\http\http-safe.ecf"/>
<library name="time" location="$ISE_LIBRARY\library\time\time-safe.ecf"/>
<library name="uuid" location="$ISE_LIBRARY\library\uuid\uuid-safe.ecf"/>
<library name="xml_parser" location="$ISE_LIBRARY\library\text\parser\xml\parser\xml_parser-safe.ecf"/>

View File

@@ -7,7 +7,7 @@
<library name="base" location="$ISE_LIBRARY\library\base\base.ecf"/>
<library name="base_extension" location="$ISE_LIBRARY\library\base_extension\base_extension.ecf"/>
<library name="encoders" location="..\..\encoder\encoder.ecf"/>
<library name="http" location="$ISE_LIBRARY\contrib\library\network\protocol\http\http.ecf"/>
<library name="http" location="..\..\..\network\protocol\http\http.ecf"/>
<library name="time" location="$ISE_LIBRARY\library\time\time.ecf"/>
<library name="uuid" location="$ISE_LIBRARY\library\uuid\uuid.ecf"/>
<library name="xml_parser" location="$ISE_LIBRARY\library\text\parser\xml\parser\xml_parser.ecf"/>

View File

@@ -72,6 +72,13 @@ feature -- Visitor
if attached a_entry.date as dt then
append_content_tag_to ("updated", Void, date_to_string (dt), buffer)
end
if attached a_entry.categories as cats then
across
cats as ic
loop
append_content_tag_to ("category", <<["term", ic.item]>>, Void, buffer)
end
end
append_content_tag_to ("summary", Void, a_entry.description, buffer)
if attached a_entry.content as l_content then

View File

@@ -81,6 +81,7 @@ feature -- Access
if attached x_entry.element_by_name ("content") as x_content then
e.set_content (xml_element_code (x_content), xml_attribute_text (x_content, "type"))
end
if attached x_entry.element_by_name ("author") as x_author then
if attached x_author.element_by_name ("name") as x_name and then
attached x_name.text as l_author_name
@@ -92,6 +93,17 @@ feature -- Access
e.set_author (l_author)
end
end
-- Optional "category"
if attached x_entry.elements_by_name ("category") as x_categories then
across
x_categories as cats_ic
loop
if attached xml_attribute_text (cats_ic.item, "term") as l_term then
e.set_category (l_term)
end
end
end
Result.extend (e)
end
end

View File

@@ -66,7 +66,7 @@ feature {NONE} -- Helpers
end
end
if a_content = Void then
a_output.append ("/>")
a_output.append ("/>%N")
else
a_output.append (">")
a_output.append (escaped_unicode_xml (a_content.as_string_32))

View File

@@ -65,6 +65,7 @@ feature {NONE} -- Data
<name>John Doe</name>
<email>johndoe@example.com</email>
</author>
<category term="foo"/><category term="bar"/>
</entry>
</feed>

View File

@@ -6,7 +6,7 @@
<setting name="concurrency" value="none"/>
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
<library name="feed" location="..\feed-safe.ecf" readonly="false"/>
<library name="http_client" location="$ISE_LIBRARY\contrib\library\network\http_client\http_client-safe.ecf"/>
<library name="http_client" location="..\..\..\..\network\http_client\http_client-safe.ecf"/>
<library name="testing" location="$ISE_LIBRARY\library\testing\testing-safe.ecf"/>
<library name="xml_parser" location="$ISE_LIBRARY\library\text\parser\xml\parser\xml_parser-safe.ecf"/>
<tests name="src" location=".\" recursive="true"/>

View File

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

View File

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

View File

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

View 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