Compare commits
11 Commits
es_rev9893
...
reverse_pr
| Author | SHA1 | Date | |
|---|---|---|---|
| 5029049ef0 | |||
| 210fae5000 | |||
| 9cc9b95190 | |||
| 8b172b5d33 | |||
| cc2d7dbb1c | |||
| c88394b9fd | |||
| 4283662f43 | |||
| 1b951376f9 | |||
| 193cc3cbde | |||
| b49e841ac7 | |||
| 8ba74e1c90 |
29
examples/proxy/application.e
Normal file
29
examples/proxy/application.e
Normal file
@@ -0,0 +1,29 @@
|
||||
note
|
||||
description: "Launcher for reverse proxy web application."
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
class
|
||||
APPLICATION
|
||||
|
||||
inherit
|
||||
WSF_DEFAULT_SERVICE [APPLICATION_EXECUTION]
|
||||
redefine
|
||||
initialize
|
||||
end
|
||||
|
||||
create
|
||||
make_and_launch
|
||||
|
||||
feature {NONE} -- Initialization
|
||||
|
||||
initialize
|
||||
-- Initialize current service.
|
||||
do
|
||||
-- Specific to `standalone' connector (the EiffelWeb server).
|
||||
-- See `{WSF_STANDALONE_SERVICE_LAUNCHER}.initialize'
|
||||
set_service_option ("port", 9090)
|
||||
import_service_options (create {WSF_SERVICE_LAUNCHER_OPTIONS_FROM_INI}.make_from_file ("server.ini"))
|
||||
end
|
||||
|
||||
end
|
||||
49
examples/proxy/application_execution.e
Normal file
49
examples/proxy/application_execution.e
Normal file
@@ -0,0 +1,49 @@
|
||||
note
|
||||
description: "Reverse proxy example."
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
class
|
||||
APPLICATION_EXECUTION
|
||||
|
||||
inherit
|
||||
WSF_EXECUTION
|
||||
|
||||
WSF_URI_REWRITER
|
||||
rename
|
||||
uri as proxy_uri
|
||||
end
|
||||
|
||||
create
|
||||
make
|
||||
|
||||
feature -- Basic operations
|
||||
|
||||
execute
|
||||
do
|
||||
-- NOTE: please enter the target server uri here
|
||||
-- replace "http://localhost:8080/foobar"
|
||||
send_proxy_response ("http://localhost:8080/foobar", Current)
|
||||
end
|
||||
|
||||
send_proxy_response (a_remote: READABLE_STRING_8; a_rewriter: detachable WSF_URI_REWRITER)
|
||||
local
|
||||
h: WSF_SIMPLE_REVERSE_PROXY_HANDLER
|
||||
do
|
||||
create h.make (a_remote)
|
||||
h.set_uri_rewriter (a_rewriter)
|
||||
h.set_uri_rewriter (create {WSF_AGENT_URI_REWRITER}.make (agent proxy_uri))
|
||||
h.set_timeout (30) -- 30 seconds
|
||||
h.set_connect_timeout (5_000) -- milliseconds = 5 seconds
|
||||
h.execute (request, response)
|
||||
end
|
||||
|
||||
feature -- Helpers
|
||||
|
||||
proxy_uri (a_request: WSF_REQUEST): STRING
|
||||
-- Request uri rewriten as url.
|
||||
do
|
||||
Result := a_request.request_uri
|
||||
end
|
||||
|
||||
end
|
||||
28
examples/proxy/proxy.ecf
Normal file
28
examples/proxy/proxy.ecf
Normal file
@@ -0,0 +1,28 @@
|
||||
<?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="proxy" uuid="B55F0D95-3793-4C90-BBAC-BF5F2DECD5E6" library_target="proxy">
|
||||
<target name="common" abstract="true">
|
||||
<file_rule>
|
||||
<exclude>/.svn$</exclude>
|
||||
<exclude>/CVS$</exclude>
|
||||
<exclude>/EIFGENs$</exclude>
|
||||
</file_rule>
|
||||
<option warning="true" full_class_checking="false" is_attached_by_default="true" void_safety="transitional" syntax="transitional">
|
||||
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
|
||||
</option>
|
||||
<setting name="console_application" value="true"/>
|
||||
<variable name="ssl_supported" value="false"/>
|
||||
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
|
||||
<library name="http" location="..\..\library\network\protocol\http\http-safe.ecf"/>
|
||||
<library name="wsf" location="..\..\library\server\wsf\wsf-safe.ecf"/>
|
||||
<library name="wsf_proxy" location="..\..\library\server\wsf_proxy\wsf_proxy-safe.ecf" readonly="false"/>
|
||||
</target>
|
||||
<target name="proxy" extends="common">
|
||||
<root class="APPLICATION" feature="make_and_launch"/>
|
||||
<option warning="true" is_attached_by_default="true" void_safety="all" syntax="transitional">
|
||||
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
|
||||
</option>
|
||||
<setting name="concurrency" value="scoop"/>
|
||||
<library name="default_standalone" location="..\..\library\server\wsf\default\standalone-safe.ecf"/>
|
||||
<cluster name="proxy" location=".\" recursive="true"/>
|
||||
</target>
|
||||
</system>
|
||||
8
examples/proxy/server.ini
Normal file
8
examples/proxy/server.ini
Normal file
@@ -0,0 +1,8 @@
|
||||
verbose=true
|
||||
verbose_level=ALERT
|
||||
port=9090
|
||||
#max_concurrent_connections=100
|
||||
#keep_alive_timeout=15
|
||||
#max_tcp_clients=100
|
||||
#socket_timeout=300
|
||||
#max_keep_alive_requests=300
|
||||
29
examples/websocket/application.e
Normal file
29
examples/websocket/application.e
Normal file
@@ -0,0 +1,29 @@
|
||||
note
|
||||
description : "simple application root class"
|
||||
date : "$Date$"
|
||||
revision : "$Revision$"
|
||||
|
||||
class
|
||||
APPLICATION
|
||||
|
||||
create
|
||||
make_and_launch
|
||||
|
||||
feature {NONE} -- Initialization
|
||||
|
||||
make_and_launch
|
||||
local
|
||||
l_launcher: WSF_STANDALONE_WEBSOCKET_SERVICE_LAUNCHER [APPLICATION_EXECUTION]
|
||||
opts: WSF_SERVICE_LAUNCHER_OPTIONS
|
||||
do
|
||||
create {WSF_SERVICE_LAUNCHER_OPTIONS_FROM_INI} opts.make_from_file ("ws.ini")
|
||||
create l_launcher.make_and_launch (options)
|
||||
end
|
||||
|
||||
options: WSF_SERVICE_LAUNCHER_OPTIONS
|
||||
-- Initialize current service.
|
||||
do
|
||||
create {WSF_SERVICE_LAUNCHER_OPTIONS_FROM_INI} Result.make_from_file ("ws.ini")
|
||||
end
|
||||
|
||||
end
|
||||
184
examples/websocket/application_execution.e
Normal file
184
examples/websocket/application_execution.e
Normal file
@@ -0,0 +1,184 @@
|
||||
note
|
||||
description : "simple application execution"
|
||||
date : "$Date$"
|
||||
revision : "$Revision$"
|
||||
|
||||
class
|
||||
APPLICATION_EXECUTION
|
||||
|
||||
inherit
|
||||
WSF_WEBSOCKET_EXECUTION
|
||||
|
||||
WEB_SOCKET_EVENT_I
|
||||
|
||||
create
|
||||
make
|
||||
|
||||
feature -- Basic operations
|
||||
|
||||
execute
|
||||
local
|
||||
s: STRING
|
||||
dt: HTTP_DATE
|
||||
do
|
||||
-- To send a response we need to setup, the status code and
|
||||
-- the response headers.
|
||||
if request.path_info.same_string_general ("/app") then
|
||||
s := websocket_app_html (9090)
|
||||
else
|
||||
s := "Hello World!"
|
||||
create dt.make_now_utc
|
||||
s.append (" (UTC time is " + dt.rfc850_string + ").")
|
||||
s.append ("<p><a href=%"/app%">Websocket demo</a></p>")
|
||||
end
|
||||
response.put_header ({HTTP_STATUS_CODE}.ok, <<["Content-Type", "text/html"], ["Content-Length", s.count.out]>>)
|
||||
response.set_status_code ({HTTP_STATUS_CODE}.ok)
|
||||
response.header.put_content_type_text_html
|
||||
response.header.put_content_length (s.count)
|
||||
if attached request.http_connection as l_connection and then l_connection.is_case_insensitive_equal_general ("keep-alive") then
|
||||
response.header.put_header_key_value ("Connection", "keep-alive")
|
||||
end
|
||||
response.put_string (s)
|
||||
end
|
||||
|
||||
feature -- Websocket execution
|
||||
|
||||
new_websocket_handler (ws: WEB_SOCKET): WEB_SOCKET_HANDLER
|
||||
do
|
||||
create Result.make (ws, Current)
|
||||
end
|
||||
|
||||
feature -- Websocket execution
|
||||
|
||||
on_open (ws: WEB_SOCKET)
|
||||
do
|
||||
ws.put_error ("Connecting")
|
||||
ws.send (Text_frame, "Hello, this is a simple demo with Websocket using Eiffel. (/help for more information).%N")
|
||||
end
|
||||
|
||||
on_binary (ws: WEB_SOCKET; a_message: READABLE_STRING_8)
|
||||
do
|
||||
ws.send (Binary_frame, a_message)
|
||||
end
|
||||
|
||||
on_text (ws: WEB_SOCKET; a_message: READABLE_STRING_8)
|
||||
do
|
||||
if a_message.same_string_general ("/help") then
|
||||
-- Echo the message for testing.
|
||||
ws.send (Text_frame, "Help: available commands%N - /time : return the server UTC time.%N")
|
||||
elseif a_message.starts_with_general ("/time") then
|
||||
ws.send (Text_frame, "Server time is " + (create {HTTP_DATE}.make_now_utc).string)
|
||||
else
|
||||
-- Echo the message for testing.
|
||||
ws.send (Text_frame, a_message)
|
||||
end
|
||||
end
|
||||
|
||||
on_close (ws: WEB_SOCKET)
|
||||
-- Called after the WebSocket connection is closed.
|
||||
do
|
||||
ws.put_error ("Connection closed")
|
||||
end
|
||||
|
||||
feature -- HTML Resource
|
||||
|
||||
websocket_app_html (a_port: INTEGER): STRING
|
||||
do
|
||||
Result := "[
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function() {
|
||||
|
||||
var socket;
|
||||
|
||||
function connect(){
|
||||
|
||||
var host = "ws://127.0.0.1:##PORTNUMBER##";
|
||||
|
||||
try{
|
||||
socket = new WebSocket(host);
|
||||
message('<p class="event">Socket Status: '+socket.readyState);
|
||||
socket.onopen = function(){
|
||||
message('<p class="event">Socket Status: '+socket.readyState+' (open)');
|
||||
}
|
||||
socket.onmessage = function(msg){
|
||||
message('<p class="message">Received: '+msg.data);
|
||||
}
|
||||
socket.onclose = function(){
|
||||
message('<p class="event">Socket Status: '+socket.readyState+' (Closed)');
|
||||
}
|
||||
} catch(exception){
|
||||
message('<p>Error'+exception);
|
||||
}
|
||||
}
|
||||
|
||||
function send(){
|
||||
var text = $('#text').val();
|
||||
if(text==""){
|
||||
message('<p class="warning">Please enter a message');
|
||||
return ;
|
||||
}
|
||||
try{
|
||||
socket.send(text);
|
||||
message('<p class="event">Sent: '+text)
|
||||
} catch(exception){
|
||||
message('<p class="warning">');
|
||||
}
|
||||
$('#text').val("");
|
||||
}
|
||||
|
||||
function message(msg){
|
||||
$('#chatLog').append(msg+'</p>');
|
||||
}//End message()
|
||||
|
||||
$('#text').keypress(function(event) {
|
||||
if (event.keyCode == '13') {
|
||||
send();
|
||||
}
|
||||
});
|
||||
|
||||
$('#disconnect').click(function(){
|
||||
socket.close();
|
||||
});
|
||||
|
||||
if (!("WebSocket" in window)){
|
||||
$('#chatLog, input, button, #examples').fadeOut("fast");
|
||||
$('<p>Oh no, you need a browser that supports WebSockets. How about <a href="http://www.google.com/chrome">Google Chrome</a>?</p>').appendTo('#container');
|
||||
}else{
|
||||
//The user has WebSockets
|
||||
connect();
|
||||
}
|
||||
|
||||
});
|
||||
</script>
|
||||
<meta charset="utf-8" />
|
||||
<style type="text/css">
|
||||
body {font-family:Arial, Helvetica, sans-serif;}
|
||||
#container { border:5px solid grey; width:800px; margin:0 auto; padding:10px; }
|
||||
#chatLog { padding:5px; border:1px solid black; }
|
||||
#chatLog p {margin:0;}
|
||||
.event {color:#999;}
|
||||
.warning { font-weight:bold; color:#CCC; }
|
||||
</style>
|
||||
<title>WebSockets Client</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="wrapper">
|
||||
<div id="container">
|
||||
<h1>WebSockets Client</h1>
|
||||
<div id="chatLog"></div>
|
||||
<input id="text" type="text" />
|
||||
<button id="disconnect">Disconnect</button>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
]"
|
||||
Result.replace_substring_all ("##PORTNUMBER##", a_port.out)
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
21
examples/websocket/websocket_app.ecf
Normal file
21
examples/websocket/websocket_app.ecf
Normal file
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="ISO-8859-1"?>
|
||||
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-15-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-15-0 http://www.eiffel.com/developers/xml/configuration-1-15-0.xsd" name="websocket_app" uuid="75D17C20-10A8-4E4C-A059-33D72A2B6AEF">
|
||||
<target name="websocket_app">
|
||||
<root class="APPLICATION" feature="make_and_launch"/>
|
||||
<file_rule>
|
||||
<exclude>/.svn$</exclude>
|
||||
<exclude>/CVS$</exclude>
|
||||
<exclude>/EIFGENs$</exclude>
|
||||
</file_rule>
|
||||
<option warning="true" is_attached_by_default="true" void_safety="all" syntax="transitional">
|
||||
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
|
||||
</option>
|
||||
<setting name="console_application" value="true"/>
|
||||
<setting name="concurrency" value="scoop"/>
|
||||
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
|
||||
<library name="http" location="..\..\library\network\protocol\http\http-safe.ecf"/>
|
||||
<library name="standalone_websocket_connector" location="..\..\library\server\wsf\connector\standalone_websocket-safe.ecf" readonly="false"/>
|
||||
<library name="wsf" location="..\..\library\server\wsf\wsf-safe.ecf"/>
|
||||
<cluster name="app" location=".\" recursive="true"/>
|
||||
</target>
|
||||
</system>
|
||||
8
examples/websocket/ws.ini
Normal file
8
examples/websocket/ws.ini
Normal file
@@ -0,0 +1,8 @@
|
||||
verbose=true
|
||||
verbose_level=INFORMATION
|
||||
port=9090
|
||||
max_concurrent_connections=100
|
||||
keep_alive_timeout=35
|
||||
max_tcp_clients=100
|
||||
socket_timeout=30000
|
||||
max_keep_alive_requests=3000
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
25
library/server/wsf/connector/standalone_websocket-safe.ecf
Normal file
25
library/server/wsf/connector/standalone_websocket-safe.ecf
Normal file
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="ISO-8859-1"?>
|
||||
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-12-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-12-0 http://www.eiffel.com/developers/xml/configuration-1-12-0.xsd" name="wsf_standalone_websocket" uuid="7C83D4B4-39C9-4D27-941B-0F0AAD45122E" library_target="wsf_standalone_websocket">
|
||||
<target name="wsf_standalone_websocket">
|
||||
<root all_classes="true"/>
|
||||
<file_rule>
|
||||
<exclude>/EIFGENs$</exclude>
|
||||
<exclude>/\.git$</exclude>
|
||||
<exclude>/\.svn$</exclude>
|
||||
</file_rule>
|
||||
<option warning="true" full_class_checking="true" is_attached_by_default="true" void_safety="all" syntax="provisional">
|
||||
</option>
|
||||
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
|
||||
<library name="connector_standalone" location="..\..\ewsgi\connectors\standalone\standalone-safe.ecf"/>
|
||||
<library name="crypto" location="$ISE_LIBRARY\unstable\library\text\encryption\crypto\crypto-safe.ecf"/>
|
||||
<library name="encoder" location="..\..\..\text\encoder\encoder-safe.ecf" readonly="false"/>
|
||||
<library name="error" location="..\..\..\utility\general\error\error-safe.ecf"/>
|
||||
<library name="ewsgi" location="..\..\ewsgi\ewsgi-safe.ecf"/>
|
||||
<library name="http" location="..\..\..\network\protocol\http\http-safe.ecf"/>
|
||||
<library name="httpd" location="..\..\ewsgi\connectors\standalone\lib\httpd\httpd-safe.ecf"/>
|
||||
<library name="time" location="$ISE_LIBRARY\library\time\time-safe.ecf"/>
|
||||
<library name="wsf" location="..\wsf-safe.ecf"/>
|
||||
<library name="wsf_standalone" location="standalone-safe.ecf"/>
|
||||
<cluster name="wsf_standalone_websocket" location=".\standalone_websocket\" recursive="true"/>
|
||||
</target>
|
||||
</system>
|
||||
25
library/server/wsf/connector/standalone_websocket.ecf
Normal file
25
library/server/wsf/connector/standalone_websocket.ecf
Normal file
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="ISO-8859-1"?>
|
||||
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-15-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-15-0 http://www.eiffel.com/developers/xml/configuration-1-15-0.xsd" name="wsf_standalone_websocket" uuid="7C83D4B4-39C9-4D27-941B-0F0AAD45122E" library_target="wsf_standalone_websocket">
|
||||
<target name="wsf_standalone_websocket">
|
||||
<root all_classes="true"/>
|
||||
<file_rule>
|
||||
<exclude>/EIFGENs$</exclude>
|
||||
<exclude>/\.git$</exclude>
|
||||
<exclude>/\.svn$</exclude>
|
||||
</file_rule>
|
||||
<option warning="true" full_class_checking="true" is_obsolete_routine_type="true" void_safety="none" syntax="provisional">
|
||||
</option>
|
||||
<library name="base" location="$ISE_LIBRARY\library\base\base.ecf"/>
|
||||
<library name="connector_standalone" location="..\..\ewsgi\connectors\standalone\standalone.ecf"/>
|
||||
<library name="crypto" location="$ISE_LIBRARY\unstable\library\text\encryption\crypto\crypto.ecf"/>
|
||||
<library name="encoder" location="..\..\..\text\encoder\encoder.ecf" readonly="false"/>
|
||||
<library name="error" location="..\..\..\utility\general\error\error.ecf"/>
|
||||
<library name="ewsgi" location="..\..\ewsgi\ewsgi.ecf"/>
|
||||
<library name="http" location="..\..\..\network\protocol\http\http.ecf"/>
|
||||
<library name="httpd" location="..\..\ewsgi\connectors\standalone\lib\httpd\httpd.ecf"/>
|
||||
<library name="time" location="$ISE_LIBRARY\library\time\time.ecf"/>
|
||||
<library name="wsf" location="..\wsf.ecf"/>
|
||||
<library name="wsf_standalone" location="standalone.ecf"/>
|
||||
<cluster name="wsf_standalone_websocket" location=".\standalone_websocket\" recursive="true"/>
|
||||
</target>
|
||||
</system>
|
||||
@@ -0,0 +1,137 @@
|
||||
note
|
||||
description: "[
|
||||
Websocket callback events for actions like opening and closing the connection,
|
||||
sending and receiving messages, and listening.
|
||||
|
||||
Define the websocket events:
|
||||
- on_open
|
||||
- on_binary
|
||||
- on_text
|
||||
- on_close
|
||||
|
||||
note: the following features could also be redefined:
|
||||
- on_pong
|
||||
- on_ping
|
||||
- on_unsupported
|
||||
]"
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
deferred class
|
||||
WEB_SOCKET_EVENT_I
|
||||
|
||||
inherit
|
||||
WEB_SOCKET_CONSTANTS
|
||||
|
||||
REFACTORING_HELPER
|
||||
|
||||
feature -- Web Socket Interface
|
||||
|
||||
on_event (ws: WEB_SOCKET; a_message: detachable READABLE_STRING_8; a_opcode: INTEGER)
|
||||
-- Called when a frame from the client has been receive
|
||||
require
|
||||
ws_attached: ws /= Void
|
||||
ws_valid: ws.is_open_read and then ws.is_open_write
|
||||
local
|
||||
l_message: READABLE_STRING_8
|
||||
do
|
||||
debug ("ws")
|
||||
ws.log ("%Non_event (ws, a_message, " + opcode_name (a_opcode) + ")%N", {HTTPD_LOGGER_CONSTANTS}.debug_level)
|
||||
end
|
||||
if a_message = Void then
|
||||
create {STRING} l_message.make_empty
|
||||
else
|
||||
l_message := a_message
|
||||
end
|
||||
|
||||
if a_opcode = Binary_frame then
|
||||
on_binary (ws, l_message)
|
||||
elseif a_opcode = Text_frame then
|
||||
on_text (ws, l_message)
|
||||
elseif a_opcode = Pong_frame then
|
||||
on_pong (ws, l_message)
|
||||
elseif a_opcode = Ping_frame then
|
||||
on_ping (ws, l_message)
|
||||
elseif a_opcode = Connection_close_frame then
|
||||
on_connection_close (ws, "")
|
||||
else
|
||||
on_unsupported (ws, l_message, a_opcode)
|
||||
end
|
||||
end
|
||||
|
||||
feature -- Websocket events
|
||||
|
||||
on_open (ws: WEB_SOCKET)
|
||||
-- Called after handshake, indicates that a complete WebSocket connection has been established.
|
||||
require
|
||||
ws_attached: ws /= Void
|
||||
ws_valid: ws.is_open_read and then ws.is_open_write
|
||||
deferred
|
||||
end
|
||||
|
||||
on_binary (ws: WEB_SOCKET; a_message: READABLE_STRING_8)
|
||||
require
|
||||
ws_attached: ws /= Void
|
||||
ws_valid: ws.is_open_read and then ws.is_open_write
|
||||
deferred
|
||||
end
|
||||
|
||||
on_text (ws: WEB_SOCKET; a_message: READABLE_STRING_8)
|
||||
require
|
||||
ws_attached: ws /= Void
|
||||
ws_valid: ws.is_open_read and then ws.is_open_write
|
||||
deferred
|
||||
end
|
||||
|
||||
on_close (ws: detachable WEB_SOCKET)
|
||||
-- Called after the WebSocket connection is closed.
|
||||
deferred
|
||||
end
|
||||
|
||||
feature -- Websocket events: implemented
|
||||
|
||||
on_pong (ws: WEB_SOCKET; a_message: READABLE_STRING_8)
|
||||
require
|
||||
ws_attached: ws /= Void
|
||||
ws_valid: ws.is_open_read and then ws.is_open_write
|
||||
do
|
||||
-- log ("Its a pong frame")
|
||||
-- at first we ignore pong
|
||||
-- FIXME: provide better explanation
|
||||
end
|
||||
|
||||
on_ping (ws: WEB_SOCKET; a_message: READABLE_STRING_8)
|
||||
require
|
||||
ws_attached: ws /= Void
|
||||
ws_valid: ws.is_open_read and then ws.is_open_write
|
||||
do
|
||||
ws.send (Pong_frame, a_message)
|
||||
end
|
||||
|
||||
on_unsupported (ws: WEB_SOCKET; a_message: READABLE_STRING_8; a_opcode: INTEGER)
|
||||
require
|
||||
ws_attached: ws /= Void
|
||||
ws_valid: ws.is_open_read and then ws.is_open_write
|
||||
do
|
||||
-- do nothing
|
||||
end
|
||||
|
||||
on_connection_close (ws: WEB_SOCKET; a_message: detachable READABLE_STRING_8)
|
||||
require
|
||||
ws_attached: ws /= Void
|
||||
ws_valid: ws.is_open_read and then ws.is_open_write
|
||||
do
|
||||
ws.send (Connection_close_frame, "")
|
||||
end
|
||||
|
||||
note
|
||||
copyright: "2011-2016, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
|
||||
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
|
||||
source: "[
|
||||
Eiffel Software
|
||||
5949 Hollister Ave., Goleta, CA 93117 USA
|
||||
Telephone 805-685-1006, Fax 805-685-6869
|
||||
Website http://www.eiffel.com
|
||||
Customer support http://support.eiffel.com
|
||||
]"
|
||||
end
|
||||
@@ -0,0 +1,39 @@
|
||||
note
|
||||
description: "[
|
||||
A web socket message has an opcode specifying the type of the message payload. The
|
||||
opcode consists of the last four bits in the first byte of the frame header.
|
||||
]"
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
EIS: "name=Data Frame", "src=http://tools.ietf.org/html/rfc6455#section-5.6", "protocol=uri"
|
||||
EIS: "name=Control Frame", "src=http://tools.ietf.org/html/rfc6455#section-5.5", "protocol=uri"
|
||||
|
||||
class
|
||||
WEB_SOCKET_MESSAGE_TYPE
|
||||
|
||||
feature -- Data Frames
|
||||
|
||||
Text: INTEGER = 0x1
|
||||
-- The data type of the message is text.
|
||||
|
||||
Binary: INTEGER = 0x2
|
||||
-- The data type of the message is binary.
|
||||
|
||||
feature -- Control Frames
|
||||
|
||||
Close: INTEGER = 0x8
|
||||
-- The client or server is sending a closing
|
||||
-- handshake to the server or client.
|
||||
|
||||
Ping: INTEGER = 0x9
|
||||
-- The client or server sends a ping to the server or client.
|
||||
|
||||
Pong: INTEGER = 0xA
|
||||
-- The client or server sends a pong to the server or client.
|
||||
|
||||
feature -- Reserverd
|
||||
|
||||
-- Opcodes 0x3-0x7 are reserved for further non-control frames yet to be
|
||||
-- defined.
|
||||
|
||||
end
|
||||
@@ -0,0 +1,203 @@
|
||||
note
|
||||
description: "Constants for WebSockets"
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
class
|
||||
WEB_SOCKET_CONSTANTS
|
||||
|
||||
feature -- Constants
|
||||
|
||||
|
||||
HTTP_1_1: STRING = "HTTP/1.1 101 WebSocket Protocol Handshake"
|
||||
|
||||
Upgrade_ws: STRING = "Upgrade: websocket"
|
||||
|
||||
Connection_ws: STRING = "Connection: Upgrade"
|
||||
|
||||
Sec_WebSocket_Origin: STRING = "Sec-WebSocket-Origin: "
|
||||
|
||||
Sec_WebSocket_Protocol: STRING = "Sec-WebSocket-Protocol: "
|
||||
|
||||
Sec_WebSocket_Location: STRING = "Sec-WebSocket-Location: "
|
||||
|
||||
Sec_WebSocket_Version: STRING = "Sec-WebSocket-Version: "
|
||||
|
||||
Sec_WebSocket_Extensions: STRING = "Sec-WebSocket-Extensions: "
|
||||
|
||||
WebSocket_Origin: STRING = "WebSocket-Origin: "
|
||||
|
||||
WebSocket_Protocol: STRING = "WebSocket-Protocol: "
|
||||
|
||||
WebSocket_Location: STRING = "WebSocket-Location: "
|
||||
|
||||
Origin: STRING = "Origin"
|
||||
|
||||
Server: STRING = "EWSS"
|
||||
|
||||
Sec_WebSocket_Key: STRING = "Sec-WebSocket-Key"
|
||||
|
||||
Ws_scheme: STRING = "ws://"
|
||||
|
||||
Wss_scheme: STRING = "wss://"
|
||||
|
||||
Magic_guid: STRING = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
|
||||
|
||||
-- The handshake from the client looks as follows:
|
||||
|
||||
-- GET /chat HTTP/1.1
|
||||
-- Host: server.example.com
|
||||
-- Upgrade: websocket
|
||||
-- Connection: Upgrade
|
||||
-- Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
|
||||
-- Origin: http://example.com
|
||||
-- Sec-WebSocket-Protocol: chat, superchat
|
||||
-- Sec-WebSocket-Version: 13
|
||||
|
||||
-- The handshake from the server looks as follows:
|
||||
|
||||
-- HTTP/1.1 101 Switching Protocols
|
||||
-- Upgrade: websocket
|
||||
-- Connection: Upgrade
|
||||
-- Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
|
||||
-- Sec-WebSocket-Protocol: chat
|
||||
|
||||
feature -- Opcodes Standard actions
|
||||
|
||||
--| Maybe we need an enum STANDARD_ACTIONS_OPCODES?
|
||||
-- |Opcode | Meaning | Reference |
|
||||
-- -+--------+-------------------------------------+-----------|
|
||||
-- | 0 | Continuation Frame | RFC 6455 |
|
||||
-- -+--------+-------------------------------------+-----------|
|
||||
-- | 1 | Text Frame | RFC 6455 |
|
||||
-- -+--------+-------------------------------------+-----------|
|
||||
-- | 2 | Binary Frame | RFC 6455 |
|
||||
-- -+--------+-------------------------------------+-----------|
|
||||
-- | 8 | Connection Close Frame | RFC 6455 |
|
||||
-- -+--------+-------------------------------------+-----------|
|
||||
-- | 9 | Ping Frame | RFC 6455 |
|
||||
-- -+--------+-------------------------------------+-----------|
|
||||
-- | 10 | Pong Frame | RFC 6455 |
|
||||
-- -+--------+-------------------------------------+-----------|
|
||||
|
||||
Continuation_frame: INTEGER = 0
|
||||
|
||||
Text_frame: INTEGER = 1
|
||||
|
||||
Binary_frame: INTEGER = 2
|
||||
|
||||
Connection_close_frame: INTEGER = 8
|
||||
|
||||
Ping_frame: INTEGER = 9
|
||||
|
||||
Pong_frame: INTEGER = 10
|
||||
|
||||
is_control_frame (a_opcode: INTEGER): BOOLEAN
|
||||
-- Is `a_opcode' a control frame?
|
||||
do
|
||||
inspect a_opcode
|
||||
when Connection_close_frame, Ping_frame, Pong_frame then
|
||||
Result := True
|
||||
else
|
||||
end
|
||||
end
|
||||
|
||||
opcode_name (a_opcode: INTEGER): STRING
|
||||
do
|
||||
inspect a_opcode
|
||||
when Continuation_frame then Result := "Continuation"
|
||||
when Text_frame then Result := "Text"
|
||||
when Binary_frame then Result := "Binary"
|
||||
when Connection_close_frame then Result := "Connection Close"
|
||||
when Ping_frame then Result := "Ping"
|
||||
when Pong_frame then Result := "Pong"
|
||||
else
|
||||
Result := "Unknown-Opcode"
|
||||
end
|
||||
Result := "0x" + a_opcode.to_hex_string + " " + Result
|
||||
end
|
||||
|
||||
feature -- Close code numbers
|
||||
|
||||
-- Maybe an ENUM CLOSE_CODES
|
||||
|
||||
-- |Status Code | Meaning | Contact | Reference |
|
||||
-- -+------------+-----------------+---------------+-----------|
|
||||
-- | 1000 | Normal Closure | hybi@ietf.org | RFC 6455 |
|
||||
-- -+------------+-----------------+---------------+-----------|
|
||||
-- | 1001 | Going Away | hybi@ietf.org | RFC 6455 |
|
||||
-- -+------------+-----------------+---------------+-----------|
|
||||
-- | 1002 | Protocol error | hybi@ietf.org | RFC 6455 |
|
||||
-- -+------------+-----------------+---------------+-----------|
|
||||
-- | 1003 | Unsupported Data| hybi@ietf.org | RFC 6455 |
|
||||
-- -+------------+-----------------+---------------+-----------|
|
||||
-- | 1004 | ---Reserved---- | hybi@ietf.org | RFC 6455 |
|
||||
-- -+------------+-----------------+---------------+-----------|
|
||||
-- | 1005 | No Status Rcvd | hybi@ietf.org | RFC 6455 |
|
||||
-- -+------------+-----------------+---------------+-----------|
|
||||
-- | 1006 | Abnormal Closure| hybi@ietf.org | RFC 6455 |
|
||||
-- -+------------+-----------------+---------------+-----------|
|
||||
-- | 1007 | Invalid frame | hybi@ietf.org | RFC 6455 |
|
||||
-- | | payload data | | |
|
||||
-- -+------------+-----------------+---------------+-----------|
|
||||
-- | 1008 | Policy Violation| hybi@ietf.org | RFC 6455 |
|
||||
-- -+------------+-----------------+---------------+-----------|
|
||||
-- | 1009 | Message Too Big | hybi@ietf.org | RFC 6455 |
|
||||
-- -+------------+-----------------+---------------+-----------|
|
||||
-- | 1010 | Mandatory Ext. | hybi@ietf.org | RFC 6455 |
|
||||
-- -+------------+-----------------+---------------+-----------|
|
||||
-- | 1011 | Internal Server | hybi@ietf.org | RFC 6455 |
|
||||
-- | | Error | | |
|
||||
-- -+------------+-----------------+---------------+-----------|
|
||||
-- | 1015 | TLS handshake | hybi@ietf.org | RFC 6455 |
|
||||
-- -+------------+-----------------+---------------+-----------|
|
||||
|
||||
Normal_closure: INTEGER = 1000
|
||||
-- Indicates a normal closure, meaning that the purpose for
|
||||
-- which the connection was established has been fulfilled.
|
||||
|
||||
Going_away: INTEGER = 1001
|
||||
-- Indicates that an endpoint is "going away", such as a server
|
||||
-- going down or a browser having navigated away from a page.
|
||||
|
||||
Protocol_error: INTEGER = 1002
|
||||
-- Indicates that an endpoint is terminating the connection due
|
||||
-- to a protocol error.
|
||||
|
||||
Unsupported_data: INTEGER = 1003
|
||||
-- Indicates that an endpoint is terminating the connection
|
||||
-- because it has received a type of data it cannot accept (e.g., an
|
||||
-- endpoint that understands only text data MAY send this if it
|
||||
-- receives a binary message).
|
||||
|
||||
Invalid_data: INTEGER = 1007
|
||||
-- Indicates that an endpoint is terminating the connection
|
||||
-- because it has received data within a message that was not
|
||||
-- consistent with the type of the message (e.g., non-UTF-8 [RFC3629]
|
||||
-- data within a text message).
|
||||
|
||||
Policy_violation: INTEGER = 1008
|
||||
-- Indicates that an endpoint is terminating the connection
|
||||
-- because it has received a message that violates its policy. This
|
||||
-- is a generic status code that can be returned when there is no
|
||||
-- other more suitable status code (e.g., 1003 or 1009) or if there
|
||||
-- is a need to hide specific details about the policy.
|
||||
|
||||
Message_too_large: INTEGER = 1009
|
||||
-- Indicates that an endpoint is terminating the connection
|
||||
-- because it has received a message that is too big for it to
|
||||
-- process.
|
||||
|
||||
Extension_required: INTEGER = 1010
|
||||
-- Indicates that an endpoint (client) is terminating the
|
||||
-- connection because it has expected the server to negotiate one or
|
||||
-- more extension, but the server didn't return them in the response
|
||||
-- message of the WebSocket handshake.
|
||||
|
||||
Internal_error: INTEGER = 1011
|
||||
-- Indicates that a server is terminating the connection because
|
||||
-- it encountered an unexpected condition that prevented it from
|
||||
-- fulfilling the request.
|
||||
|
||||
|
||||
end
|
||||
@@ -0,0 +1,35 @@
|
||||
note
|
||||
description: "Summary description for {WEB_SOCKET_ERROR_FRAME}."
|
||||
author: ""
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
class
|
||||
WEB_SOCKET_ERROR_FRAME
|
||||
|
||||
create
|
||||
make
|
||||
|
||||
feature {NONE} -- Initialization
|
||||
|
||||
make (a_code: INTEGER; a_desc: like description)
|
||||
do
|
||||
code := a_code
|
||||
description := a_desc
|
||||
end
|
||||
|
||||
feature -- Access
|
||||
|
||||
code: INTEGER
|
||||
|
||||
description: READABLE_STRING_8
|
||||
|
||||
feature -- Conversion
|
||||
|
||||
string: STRING
|
||||
do
|
||||
create Result.make_from_string ("Error(" + code.out + "): " + description)
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
@@ -0,0 +1,437 @@
|
||||
note
|
||||
description: "[
|
||||
Summary description for {WEB_SOCKET_FRAME}.
|
||||
See Base Framing Protocol: http://tools.ietf.org/html/rfc6455#section-5.2
|
||||
0 1 2 3
|
||||
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
+-+-+-+-+-------+-+-------------+-------------------------------+
|
||||
|F|R|R|R| opcode|M| Payload len | Extended payload length |
|
||||
|I|S|S|S| (4) |A| (7) | (16/64) |
|
||||
|N|V|V|V| |S| | (if payload len==126/127) |
|
||||
| |1|2|3| |K| | |
|
||||
+-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
|
||||
| Extended payload length continued, if payload len == 127 |
|
||||
+ - - - - - - - - - - - - - - - +-------------------------------+
|
||||
| |Masking-key, if MASK set to 1 |
|
||||
+-------------------------------+-------------------------------+
|
||||
| Masking-key (continued) | Payload Data |
|
||||
+-------------------------------- - - - - - - - - - - - - - - - +
|
||||
: Payload Data continued ... :
|
||||
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
|
||||
| Payload Data continued ... |
|
||||
+---------------------------------------------------------------+
|
||||
|
||||
Check the `check_utf_8_validity_on_chop' if there is performance issue
|
||||
with bigger data.
|
||||
]"
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
EIS: "name=Websocket RFC6455 section-5.2", "protocol=URI", "src=http://tools.ietf.org/html/rfc6455#section-5.2", "tag=rfc"
|
||||
|
||||
class
|
||||
WEB_SOCKET_FRAME
|
||||
|
||||
inherit
|
||||
ANY
|
||||
|
||||
WEB_SOCKET_CONSTANTS
|
||||
|
||||
create
|
||||
make,
|
||||
make_as_injected_control
|
||||
|
||||
feature {NONE} -- Initialization
|
||||
|
||||
make (a_opcode: INTEGER; flag_is_fin: BOOLEAN)
|
||||
-- Create current frame with opcode `a_opcode'
|
||||
-- and `a_fin' to indicate if this is the final fragment.
|
||||
do
|
||||
is_incomplete := False
|
||||
opcode := a_opcode
|
||||
is_fin := flag_is_fin
|
||||
|
||||
inspect opcode
|
||||
when
|
||||
Continuation_frame, -- 0
|
||||
Text_frame, -- 1
|
||||
Binary_frame -- 2
|
||||
then
|
||||
--| Supported opcode
|
||||
when
|
||||
Connection_close_frame, -- 8
|
||||
Ping_frame, -- 9
|
||||
Pong_frame -- 10
|
||||
then
|
||||
--| Supported control opcode
|
||||
-- All control frames MUST have a payload length of 125 bytes or less
|
||||
-- and MUST NOT be fragmented.
|
||||
if flag_is_fin then
|
||||
-- So far it is valid.
|
||||
else
|
||||
report_error (Protocol_error, "Control frames MUST NOT be fragmented.")
|
||||
end
|
||||
else
|
||||
report_error (Protocol_error, "Unknown opcode")
|
||||
end
|
||||
end
|
||||
|
||||
make_as_injected_control (a_opcode: INTEGER; a_parent: WEB_SOCKET_FRAME)
|
||||
require
|
||||
parent_is_not_control_frame: not a_parent.is_control
|
||||
a_opcode_is_control_frame: is_control_frame (a_opcode)
|
||||
do
|
||||
make (a_opcode, True)
|
||||
parent := a_parent
|
||||
a_parent.add_injected_control_frame (Current)
|
||||
end
|
||||
|
||||
feature -- Access
|
||||
|
||||
opcode: INTEGER
|
||||
-- CONTINUOUS, TEXT, BINARY, PING, PONG, CLOSING
|
||||
|
||||
is_fin: BOOLEAN
|
||||
-- is the final fragment in a message?
|
||||
|
||||
fragment_count: INTEGER
|
||||
|
||||
payload_length: NATURAL_64
|
||||
payload_data: detachable STRING_8
|
||||
-- Maybe we need a buffer here.
|
||||
|
||||
uncoded_payload_data: detachable STRING_32
|
||||
local
|
||||
utf: UTF_CONVERTER
|
||||
do
|
||||
if attached payload_data as d then
|
||||
Result := utf.utf_8_string_8_to_string_32 (d)
|
||||
end
|
||||
end
|
||||
|
||||
error: detachable WEB_SOCKET_ERROR_FRAME
|
||||
-- Describe the type of error
|
||||
|
||||
feature -- Access: injected control frames
|
||||
|
||||
injected_control_frames: detachable LIST [WEB_SOCKET_FRAME]
|
||||
|
||||
parent: detachable WEB_SOCKET_FRAME
|
||||
-- If Current is injected, `parent' is the related fragmented frame
|
||||
|
||||
is_injected_control: BOOLEAN
|
||||
do
|
||||
Result := parent /= Void
|
||||
ensure
|
||||
Result implies (is_control_frame (opcode))
|
||||
end
|
||||
|
||||
feature -- Operation
|
||||
|
||||
update_fin (a_flag_is_fin: BOOLEAN)
|
||||
do
|
||||
is_fin := a_flag_is_fin
|
||||
end
|
||||
|
||||
feature {WEB_SOCKET_FRAME} -- Change: injected control frames
|
||||
|
||||
add_injected_control_frame (f: WEB_SOCKET_FRAME)
|
||||
require
|
||||
Current_is_not_control: not is_control
|
||||
f_is_control_frame: f.is_control
|
||||
parented_to_current: f.parent = Current
|
||||
local
|
||||
lst: like injected_control_frames
|
||||
do
|
||||
lst := injected_control_frames
|
||||
if lst = Void then
|
||||
create {ARRAYED_LIST [WEB_SOCKET_FRAME]} lst.make (1)
|
||||
injected_control_frames := lst
|
||||
end
|
||||
lst.force (f)
|
||||
ensure
|
||||
parented_to_current: f.parent = Current
|
||||
end
|
||||
|
||||
remove_injected_control_frame (f: WEB_SOCKET_FRAME)
|
||||
require
|
||||
Current_is_not_control: not is_control
|
||||
f_is_control_frame: f.is_control
|
||||
parented_to_current: f.parent = Current
|
||||
local
|
||||
lst: like injected_control_frames
|
||||
do
|
||||
lst := injected_control_frames
|
||||
if lst /= Void then
|
||||
lst.prune (f)
|
||||
if lst.is_empty then
|
||||
injected_control_frames := Void
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
feature -- Query
|
||||
|
||||
is_binary: BOOLEAN
|
||||
do
|
||||
Result := opcode = binary_frame
|
||||
end
|
||||
|
||||
is_text: BOOLEAN
|
||||
do
|
||||
Result := opcode = text_frame
|
||||
end
|
||||
|
||||
is_continuation: BOOLEAN
|
||||
do
|
||||
Result := opcode = continuation_frame
|
||||
end
|
||||
|
||||
is_connection_close: BOOLEAN
|
||||
do
|
||||
Result := opcode = connection_close_frame
|
||||
end
|
||||
|
||||
is_control: BOOLEAN
|
||||
do
|
||||
inspect opcode
|
||||
when connection_close_frame, Ping_frame, Pong_frame then
|
||||
Result := True
|
||||
else
|
||||
end
|
||||
end
|
||||
|
||||
is_ping: BOOLEAN
|
||||
do
|
||||
Result := opcode = ping_frame
|
||||
end
|
||||
|
||||
is_pong: BOOLEAN
|
||||
do
|
||||
Result := opcode = pong_frame
|
||||
end
|
||||
|
||||
feature -- Status report
|
||||
|
||||
is_valid: BOOLEAN
|
||||
do
|
||||
Result := not has_error
|
||||
end
|
||||
|
||||
is_incomplete: BOOLEAN
|
||||
|
||||
has_error: BOOLEAN
|
||||
do
|
||||
Result := error /= Void
|
||||
end
|
||||
|
||||
feature -- Change
|
||||
|
||||
increment_fragment_count
|
||||
do
|
||||
fragment_count := fragment_count + 1
|
||||
end
|
||||
|
||||
check_utf_8_validity_on_chop: BOOLEAN = False
|
||||
-- True: check for each chop
|
||||
-- False: check only for each fragment
|
||||
--| see autobahntestsuite #6.4.3 and #6.4.4
|
||||
|
||||
append_payload_data_chop (a_data: STRING_8; a_len: INTEGER; a_flag_chop_complete: BOOLEAN)
|
||||
do
|
||||
if a_flag_chop_complete then
|
||||
increment_fragment_count
|
||||
end
|
||||
if attached payload_data as l_payload_data then
|
||||
l_payload_data.append (a_data)
|
||||
else
|
||||
payload_data := a_data
|
||||
end
|
||||
payload_length := payload_length + a_len.to_natural_64
|
||||
|
||||
if is_text then
|
||||
if is_fin and a_flag_chop_complete then
|
||||
-- Check the whole message is a valid UTF-8 string
|
||||
if attached payload_data as d then
|
||||
if not is_valid_utf_8_string (d) then
|
||||
report_error (invalid_data, "The text message is not a valid UTF-8 text!")
|
||||
end
|
||||
else
|
||||
-- empty payload??
|
||||
end
|
||||
elseif check_utf_8_validity_on_chop or else a_flag_chop_complete then
|
||||
-- Check the payload data as utf-8 stream (may be incomplete at this point)
|
||||
if not is_valid_text_payload_stream then
|
||||
report_error (invalid_data, "This is not a valid UTF-8 stream!")
|
||||
-- is_valid implies the connection will be closed!
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
report_error (a_code: INTEGER; a_description: READABLE_STRING_8)
|
||||
require
|
||||
not has_error
|
||||
do
|
||||
create error.make (a_code, a_description)
|
||||
ensure
|
||||
has_error: has_error
|
||||
is_not_valid: not is_valid
|
||||
end
|
||||
|
||||
feature {NONE} -- Helper
|
||||
|
||||
last_utf_8_stream_validation_position: INTEGER
|
||||
-- In relation with `is_valid_utf_8 (.., a_is_stream=True)'
|
||||
|
||||
is_valid_text_payload_stream: BOOLEAN
|
||||
require
|
||||
is_text_frame: is_text
|
||||
do
|
||||
if attached payload_data as s then
|
||||
Result := is_valid_utf_8 (s, not is_fin)
|
||||
end
|
||||
end
|
||||
|
||||
is_valid_utf_8_string (s: READABLE_STRING_8): BOOLEAN
|
||||
do
|
||||
Result := is_valid_utf_8 (s, False)
|
||||
-- and (create {UTF_CONVERTER}).is_valid_utf_8_string_8 (s)
|
||||
end
|
||||
|
||||
is_valid_utf_8 (s: READABLE_STRING_8; a_is_stream: BOOLEAN): BOOLEAN
|
||||
-- UTF-8 validity checker.
|
||||
note
|
||||
EIS: "name=UTF-8 RFC3629", "protocol=URI", "src=https://tools.ietf.org/html/rfc3629", "tag=rfc"
|
||||
require
|
||||
is_text_frame: is_text
|
||||
local
|
||||
i: like {STRING_8}.count
|
||||
n: like {STRING_8}.count
|
||||
c,c2,c3,c4,w: NATURAL_32
|
||||
l_is_incomplete_stream: BOOLEAN
|
||||
do
|
||||
Result := True
|
||||
-- Following code also check that codepoint is between 0 and 0x10FFFF
|
||||
-- (as expected by spec, and tested by autobahn ws testsuite)
|
||||
from
|
||||
if a_is_stream then
|
||||
i := last_utf_8_stream_validation_position -- to avoid recomputing from the beginning each time.
|
||||
else
|
||||
i := 0
|
||||
end
|
||||
n := s.count
|
||||
until
|
||||
i >= n or not Result
|
||||
loop
|
||||
i := i + 1
|
||||
c := s.code (i)
|
||||
if c <= 0x7F then
|
||||
-- 0xxxxxxx
|
||||
w := c
|
||||
elseif c <= 0xC1 then
|
||||
-- The octet values C0, C1, F5 to FF never appear.
|
||||
--| case 0xC0 and 0xC1
|
||||
Result := False
|
||||
elseif (c & 0xE0) = 0xC0 then
|
||||
-- 110xxxxx 10xxxxxx
|
||||
i := i + 1
|
||||
if i <= n then
|
||||
c2 := s.code (i)
|
||||
if
|
||||
(c2 & 0xC0) = 0x80
|
||||
then
|
||||
w := ((c & 0x1F) |<< 6)
|
||||
| (c2 & 0x3F)
|
||||
Result := 0x80 <= w and w <= 0x7FF
|
||||
else
|
||||
Result := False
|
||||
end
|
||||
else
|
||||
l_is_incomplete_stream := True
|
||||
end
|
||||
elseif (c & 0xF0) = 0xE0 then
|
||||
-- 1110xxxx 10xxxxxx 10xxxxxx
|
||||
i := i + 2
|
||||
if i <= n then
|
||||
c2 := s.code (i - 1)
|
||||
c3 := s.code (i)
|
||||
if
|
||||
(c2 & 0xC0) = 0x80 and
|
||||
(c3 & 0xC0) = 0x80
|
||||
then
|
||||
w := ((c & 0xF) |<< 12)
|
||||
| ((c2 & 0x3F) |<< 6)
|
||||
| (c3 & 0x3F)
|
||||
if 0x800 <= w and w <= 0xFFFF then
|
||||
if 0xD800 <= w and w <= 0xDFFF then
|
||||
-- The definition of UTF-8 prohibits encoding character numbers between U+D800 and U+DFFF
|
||||
Result := False
|
||||
end
|
||||
else
|
||||
Result := False
|
||||
end
|
||||
else
|
||||
Result := False
|
||||
end
|
||||
else
|
||||
if i - 1 <= n then
|
||||
Result := (s.code (i - 1) & 0xC0) = 0x80
|
||||
end
|
||||
l_is_incomplete_stream := True
|
||||
end
|
||||
elseif (c & 0xF8) = 0xF0 then -- 0001 0000-0010 FFFF
|
||||
-- 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
|
||||
if 0xF5 <= c and c <= 0xFF then
|
||||
-- The octet values C0, C1, F5 to FF never appear.
|
||||
Result := False
|
||||
else
|
||||
i := i + 3
|
||||
if i <= n then
|
||||
c2 := s.code (i - 2)
|
||||
c3 := s.code (i - 1)
|
||||
c4 := s.code (i)
|
||||
if
|
||||
(c2 & 0xC0) = 0x80 and
|
||||
(c3 & 0xC0) = 0x80 and
|
||||
(c4 & 0xC0) = 0x80
|
||||
then
|
||||
w := ((c & 0x7) |<< 18) |
|
||||
((c2 & 0x3F) |<< 12) |
|
||||
((c3 & 0x3F) |<< 6) |
|
||||
(c4 & 0x3F)
|
||||
Result := 0x1_0000 <= w and w <= 0x10_FFFF
|
||||
else
|
||||
Result := False
|
||||
end
|
||||
else
|
||||
if i - 2 <= n then
|
||||
c2 := s.code (i - 2)
|
||||
Result := (c2 & 0xC0) = 0x80
|
||||
if Result then
|
||||
if c = 0xF4 and c2 >= 0x90 then
|
||||
--| any byte 10xxxxxx (i.e >= 0x80) that would come after,
|
||||
-- will result in out of range code point
|
||||
-- indeed 0xF4 0x90 0x80 0x80 = 0x1100 0000 > 0x10_FFFF
|
||||
Result := False
|
||||
elseif i - 1 <= n then
|
||||
Result := (s.code (i - 1) & 0xC0) = 0x80
|
||||
end
|
||||
end
|
||||
end
|
||||
l_is_incomplete_stream := True
|
||||
end
|
||||
end
|
||||
else
|
||||
-- Invalid byte in UTF-8
|
||||
Result := False
|
||||
end
|
||||
if Result then
|
||||
if l_is_incomplete_stream then
|
||||
Result := a_is_stream
|
||||
elseif a_is_stream then
|
||||
last_utf_8_stream_validation_position := i
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,805 @@
|
||||
note
|
||||
description: "[
|
||||
Object representing the websocket connection.
|
||||
It contains the `request` and `response`, and more important the `socket` itself.
|
||||
]"
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
class
|
||||
WEB_SOCKET
|
||||
|
||||
inherit
|
||||
WGI_STANDALONE_CONNECTOR_EXPORTER
|
||||
|
||||
WSF_RESPONSE_EXPORTER
|
||||
|
||||
WGI_EXPORTER
|
||||
|
||||
HTTPD_LOGGER_CONSTANTS
|
||||
|
||||
WEB_SOCKET_CONSTANTS
|
||||
|
||||
SHARED_BASE64
|
||||
|
||||
create
|
||||
make
|
||||
|
||||
feature {NONE} -- Initialization
|
||||
|
||||
make (req: WSF_REQUEST; res: WSF_RESPONSE)
|
||||
do
|
||||
request := req
|
||||
response := res
|
||||
is_verbose := False
|
||||
verbose_level := notice_level
|
||||
|
||||
if
|
||||
attached {WGI_STANDALONE_INPUT_STREAM} req.input as r_input
|
||||
then
|
||||
socket := r_input.source
|
||||
else
|
||||
create socket.make_empty
|
||||
check has_socket: False end
|
||||
end
|
||||
end
|
||||
|
||||
feature -- Access
|
||||
|
||||
socket: HTTPD_STREAM_SOCKET
|
||||
-- Underlying connected socket.
|
||||
|
||||
feature {NONE} -- Access
|
||||
|
||||
request: WSF_REQUEST
|
||||
-- Associated request.
|
||||
|
||||
response: WSF_RESPONSE
|
||||
-- Associated response stream.
|
||||
|
||||
feature -- Access
|
||||
|
||||
is_websocket: BOOLEAN
|
||||
-- Does `open_ws_handshake' detect valid websocket upgrade handshake?
|
||||
|
||||
feature -- Settings
|
||||
|
||||
is_verbose: BOOLEAN
|
||||
-- Output verbose log messages?
|
||||
|
||||
verbose_level: INTEGER
|
||||
-- Level of verbosity.
|
||||
|
||||
feature -- Status
|
||||
|
||||
has_error: BOOLEAN
|
||||
-- Error occured during processing?
|
||||
|
||||
feature -- Socket status
|
||||
|
||||
is_ready_for_reading: BOOLEAN
|
||||
-- Is `socket' ready for reading?
|
||||
--| at this point, socket should be set to blocking.
|
||||
do
|
||||
Result := socket.ready_for_reading
|
||||
end
|
||||
|
||||
is_open_read: BOOLEAN
|
||||
-- Is `socket' open for reading?
|
||||
do
|
||||
Result := socket.is_open_read
|
||||
end
|
||||
|
||||
is_open_write: BOOLEAN
|
||||
-- Is `socket' open for writing?
|
||||
do
|
||||
Result := socket.is_open_write
|
||||
end
|
||||
|
||||
socket_descriptor: INTEGER
|
||||
-- Descriptor for current `socket'.
|
||||
do
|
||||
Result := socket.descriptor
|
||||
end
|
||||
|
||||
feature -- Element change
|
||||
|
||||
set_is_verbose (b: BOOLEAN)
|
||||
do
|
||||
is_verbose := b
|
||||
end
|
||||
|
||||
set_verbose_level (lev: INTEGER)
|
||||
do
|
||||
verbose_level := lev
|
||||
end
|
||||
|
||||
feature -- Basic operation
|
||||
|
||||
put_error (a_message: READABLE_STRING_8)
|
||||
do
|
||||
response.put_error (a_message)
|
||||
end
|
||||
|
||||
log (m: READABLE_STRING_8; lev: INTEGER)
|
||||
-- Log `m' in the error channel, i.e stderr for standalone.
|
||||
do
|
||||
if is_verbose then
|
||||
put_error (m)
|
||||
end
|
||||
end
|
||||
|
||||
feature -- Basic Operation
|
||||
|
||||
open_ws_handshake
|
||||
-- The opening handshake is intended to be compatible with HTTP-based
|
||||
-- server-side software and intermediaries, so that a single port can be
|
||||
-- used by both HTTP clients alking to that server and WebSocket
|
||||
-- clients talking to that server. To this end, the WebSocket client's
|
||||
-- handshake is an HTTP Upgrade request:
|
||||
|
||||
-- GET /chat HTTP/1.1
|
||||
-- Host: server.example.com
|
||||
-- Upgrade: websocket
|
||||
-- Connection: Upgrade
|
||||
-- Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
|
||||
-- Origin: http://example.com
|
||||
-- Sec-WebSocket-Protocol: chat, superchat
|
||||
-- Sec-WebSocket-Version: 13
|
||||
local
|
||||
l_sha1: SHA1
|
||||
l_key : STRING
|
||||
req: like request
|
||||
res: like response
|
||||
do
|
||||
-- Reset values.
|
||||
is_websocket := False
|
||||
has_error := False
|
||||
|
||||
-- Local cache.
|
||||
req := request
|
||||
res := response
|
||||
|
||||
-- Reading client's opening GT
|
||||
|
||||
-- TODO extract to a validator handshake or something like that.
|
||||
if is_verbose then
|
||||
log ("%NReceive <====================", debug_level)
|
||||
if attached req.raw_header_data as rhd then
|
||||
log (rhd, debug_level)
|
||||
end
|
||||
end
|
||||
if
|
||||
req.is_get_request_method and then -- MUST be GET request!
|
||||
attached req.meta_string_variable ("HTTP_UPGRADE") as l_upgrade_key and then
|
||||
l_upgrade_key.is_case_insensitive_equal_general ("websocket") -- Upgrade header must be present with value websocket
|
||||
then
|
||||
is_websocket := True
|
||||
socket.set_blocking
|
||||
if
|
||||
attached req.meta_string_variable ("HTTP_SEC_WEBSOCKET_KEY") as l_ws_key and then -- Sec-websocket-key must be present
|
||||
attached req.meta_string_variable ("HTTP_CONNECTION") as l_connection_key and then -- Connection header must be present with value Upgrade
|
||||
l_connection_key.has_substring ("Upgrade") and then
|
||||
attached req.meta_string_variable ("HTTP_SEC_WEBSOCKET_VERSION") as l_version_key and then -- Version header must be present with value 13
|
||||
l_version_key.is_case_insensitive_equal ("13") and then
|
||||
attached req.http_host -- Host header must be present
|
||||
then
|
||||
if is_verbose then
|
||||
log ("key " + l_ws_key, debug_level)
|
||||
end
|
||||
-- Sending the server's opening handshake
|
||||
create l_sha1.make
|
||||
l_sha1.update_from_string (l_ws_key + magic_guid)
|
||||
l_key := Base64_encoder.encoded_string (digest (l_sha1))
|
||||
res.header.add_header_key_value ("Upgrade", "websocket")
|
||||
res.header.add_header_key_value ("Connection", "Upgrade")
|
||||
res.header.add_header_key_value ("Sec-WebSocket-Accept", l_key)
|
||||
|
||||
if is_verbose then
|
||||
log ("%N================> Send Handshake", debug_level)
|
||||
if attached {HTTP_HEADER} res.header as h then
|
||||
log (h.string, debug_level)
|
||||
end
|
||||
end
|
||||
res.set_status_code_with_reason_phrase (101, "Switching Protocols")
|
||||
res.wgi_response.push
|
||||
else
|
||||
has_error := True
|
||||
if is_verbose then
|
||||
log ("Error (opening_handshake)!!!", debug_level)
|
||||
end
|
||||
-- If we cannot complete the handshake, then the server MUST stop processing the client's handshake and return an HTTP response with an
|
||||
-- appropriate error code (such as 400 Bad Request).
|
||||
res.set_status_code_with_reason_phrase (400, "Bad Request")
|
||||
end
|
||||
else
|
||||
is_websocket := False
|
||||
end
|
||||
end
|
||||
|
||||
feature -- Response!
|
||||
|
||||
send (a_opcode:INTEGER; a_message: READABLE_STRING_8)
|
||||
local
|
||||
i: INTEGER
|
||||
l_chunk_size: INTEGER
|
||||
l_chunk: READABLE_STRING_8
|
||||
l_header_message: STRING
|
||||
l_message_count: INTEGER
|
||||
n: NATURAL_64
|
||||
retried: BOOLEAN
|
||||
do
|
||||
debug ("ws")
|
||||
print (">>do_send (..., "+ opcode_name (a_opcode) +", ..)%N")
|
||||
end
|
||||
if not retried then
|
||||
create l_header_message.make_empty
|
||||
l_header_message.append_code ((0x80 | a_opcode).to_natural_32)
|
||||
l_message_count := a_message.count
|
||||
n := l_message_count.to_natural_64
|
||||
if l_message_count > 0xffff then
|
||||
--! Improve. this code needs to be checked.
|
||||
l_header_message.append_code ((0 | 127).to_natural_32)
|
||||
l_header_message.append_character ((n |>> 56).to_character_8)
|
||||
l_header_message.append_character ((n |>> 48).to_character_8)
|
||||
l_header_message.append_character ((n |>> 40).to_character_8)
|
||||
l_header_message.append_character ((n |>> 32).to_character_8)
|
||||
l_header_message.append_character ((n |>> 24).to_character_8)
|
||||
l_header_message.append_character ((n |>> 16).to_character_8)
|
||||
l_header_message.append_character ((n |>> 8).to_character_8)
|
||||
l_header_message.append_character ( n.to_character_8)
|
||||
elseif l_message_count > 125 then
|
||||
l_header_message.append_code ((0 | 126).to_natural_32)
|
||||
l_header_message.append_code ((n |>> 8).as_natural_32)
|
||||
l_header_message.append_character (n.to_character_8)
|
||||
else
|
||||
l_header_message.append_code (n.as_natural_32)
|
||||
end
|
||||
socket.put_string (l_header_message)
|
||||
|
||||
l_chunk_size := 16_384 -- 16K TODO: see if we should make it customizable.
|
||||
if l_message_count < l_chunk_size then
|
||||
socket.put_string (a_message)
|
||||
else
|
||||
from
|
||||
i := 0
|
||||
until
|
||||
l_chunk_size = 0
|
||||
loop
|
||||
debug ("ws")
|
||||
print ("Sending chunk " + (i + 1).out + " -> " + (i + l_chunk_size).out +" / " + l_message_count.out + "%N")
|
||||
end
|
||||
l_chunk := a_message.substring (i + 1, l_message_count.min (i + l_chunk_size))
|
||||
socket.put_string (l_chunk)
|
||||
if l_chunk.count < l_chunk_size then
|
||||
l_chunk_size := 0
|
||||
end
|
||||
i := i + l_chunk_size
|
||||
end
|
||||
debug ("ws")
|
||||
print ("Sending chunk done%N")
|
||||
end
|
||||
end
|
||||
else
|
||||
-- FIXME: what should be done on rescue?
|
||||
end
|
||||
rescue
|
||||
retried := True
|
||||
io.put_string ("Internal error in " + generator + ".do_send (conn, a_opcode=" + a_opcode.out + ", a_message) !%N")
|
||||
retry
|
||||
end
|
||||
|
||||
next_frame: detachable WEB_SOCKET_FRAME
|
||||
-- TODO Binary messages
|
||||
-- Handle error responses in a better way.
|
||||
-- IDEA:
|
||||
-- class FRAME
|
||||
-- is_fin: BOOLEAN
|
||||
-- opcode: WEB_SOCKET_STATUS_CODE (TEXT, BINARY, CLOSE, CONTINUE,PING, PONG)
|
||||
-- data/payload
|
||||
-- status_code: #see Status Codes http://tools.ietf.org/html/rfc6455#section-7.3
|
||||
-- has_error
|
||||
--
|
||||
-- See Base Framing Protocol: http://tools.ietf.org/html/rfc6455#section-5.2
|
||||
-- 0 1 2 3
|
||||
-- 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
-- +-+-+-+-+-------+-+-------------+-------------------------------+
|
||||
-- |F|R|R|R| opcode|M| Payload len | Extended payload length |
|
||||
-- |I|S|S|S| (4) |A| (7) | (16/64) |
|
||||
-- |N|V|V|V| |S| | (if payload len==126/127) |
|
||||
-- | |1|2|3| |K| | |
|
||||
-- +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
|
||||
-- | Extended payload length continued, if payload len == 127 |
|
||||
-- + - - - - - - - - - - - - - - - +-------------------------------+
|
||||
-- | |Masking-key, if MASK set to 1 |
|
||||
-- +-------------------------------+-------------------------------+
|
||||
-- | Masking-key (continued) | Payload Data |
|
||||
-- +-------------------------------- - - - - - - - - - - - - - - - +
|
||||
-- : Payload Data continued ... :
|
||||
-- + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
|
||||
-- | Payload Data continued ... |
|
||||
-- +---------------------------------------------------------------+
|
||||
note
|
||||
EIS: "name=WebSocket RFC", "protocol=URI", "src=http://tools.ietf.org/html/rfc6455#section-5.2"
|
||||
require
|
||||
socket_in_blocking_mode: socket.is_blocking
|
||||
local
|
||||
l_socket: like socket
|
||||
l_opcode: INTEGER
|
||||
l_len: INTEGER
|
||||
l_remaining_len: INTEGER
|
||||
l_payload_len: NATURAL_64
|
||||
l_masking_key: detachable READABLE_STRING_8
|
||||
l_chunk: STRING
|
||||
l_rsv: BOOLEAN
|
||||
l_fin: BOOLEAN
|
||||
l_has_mask: BOOLEAN
|
||||
l_chunk_size: INTEGER
|
||||
l_byte: INTEGER
|
||||
l_fetch_count: INTEGER
|
||||
l_bytes_read: INTEGER
|
||||
s: STRING
|
||||
is_data_frame_ok: BOOLEAN -- Is the last process data framing ok?
|
||||
retried: BOOLEAN
|
||||
do
|
||||
if not retried then
|
||||
l_socket := socket
|
||||
debug ("ws")
|
||||
print ("next_frame:%N")
|
||||
end
|
||||
from
|
||||
is_data_frame_ok := True
|
||||
until
|
||||
l_fin or not is_data_frame_ok
|
||||
loop
|
||||
-- multi-frames or continue is only valid for Binary or Text
|
||||
s := next_bytes (l_socket, 1)
|
||||
if s.is_empty then
|
||||
is_data_frame_ok := False
|
||||
debug ("ws")
|
||||
print ("[ERROR] incomplete_data!%N")
|
||||
end
|
||||
else
|
||||
l_byte := s [1].code
|
||||
debug ("ws")
|
||||
print (" fin,rsv(3),opcode(4)=")
|
||||
print (to_byte_representation (l_byte))
|
||||
print ("%N")
|
||||
end
|
||||
l_fin := l_byte & (0b10000000) /= 0
|
||||
l_rsv := l_byte & (0b01110000) = 0
|
||||
l_opcode := l_byte & 0b00001111
|
||||
if Result /= Void then
|
||||
if l_opcode = Result.opcode then
|
||||
-- should not occur in multi-fragment frame!
|
||||
create Result.make (l_opcode, l_fin)
|
||||
Result.report_error (protocol_error, "Unexpected injected frame")
|
||||
elseif l_opcode = continuation_frame then
|
||||
-- Expected
|
||||
Result.update_fin (l_fin)
|
||||
elseif is_control_frame (l_opcode) then
|
||||
-- Control frames (see Section 5.5) MAY be injected in the middle of
|
||||
-- a fragmented message. Control frames themselves MUST NOT be fragmented.
|
||||
-- if the l_opcode is a control frame then there is an error!!!
|
||||
-- CLOSE, PING, PONG
|
||||
create Result.make_as_injected_control (l_opcode, Result)
|
||||
else
|
||||
-- should not occur in multi-fragment frame!
|
||||
create Result.make (l_opcode, l_fin)
|
||||
Result.report_error (protocol_error, "Unexpected frame")
|
||||
end
|
||||
else
|
||||
create Result.make (l_opcode, l_fin)
|
||||
if Result.is_continuation then
|
||||
-- Continuation frame is not expected without parent frame!
|
||||
Result.report_error (protocol_error, "There is no message to continue!")
|
||||
end
|
||||
end
|
||||
if Result.is_valid then
|
||||
--| valid frame/fragment
|
||||
if is_verbose then
|
||||
log ("+ frame " + opcode_name (l_opcode) + " (fin=" + l_fin.out + ")", debug_level)
|
||||
end
|
||||
|
||||
-- rsv validation
|
||||
if not l_rsv then
|
||||
-- RSV1, RSV2, RSV3: 1 bit each
|
||||
|
||||
-- MUST be 0 unless an extension is negotiated that defines meanings
|
||||
-- for non-zero values. If a nonzero value is received and none of
|
||||
-- the negotiated extensions defines the meaning of such a nonzero
|
||||
-- value, the receiving endpoint MUST _Fail the WebSocket
|
||||
-- Connection_
|
||||
|
||||
-- FIXME: add support for extension ?
|
||||
Result.report_error (protocol_error, "RSV values MUST be 0 unless an extension is negotiated that defines meanings for non-zero values")
|
||||
end
|
||||
else
|
||||
if is_verbose then
|
||||
log ("+ INVALID frame " + opcode_name (l_opcode) + " (fin=" + l_fin.out + ")", debug_level)
|
||||
end
|
||||
end
|
||||
|
||||
-- At the moment only TEXT, (pending Binary)
|
||||
if Result.is_valid then
|
||||
if Result.is_text or Result.is_binary or Result.is_control then
|
||||
-- Reading next byte (mask+payload_len)
|
||||
s := next_bytes (l_socket, 1)
|
||||
if s.is_empty then
|
||||
Result.report_error (invalid_data, "Incomplete data for mask and payload len")
|
||||
else
|
||||
l_byte := s [1].code
|
||||
debug ("ws")
|
||||
print (" mask,payload_len(7)=")
|
||||
print (to_byte_representation (l_byte))
|
||||
io.put_new_line
|
||||
end
|
||||
l_has_mask := l_byte & (0b10000000) /= 0 -- MASK
|
||||
l_len := l_byte & 0b01111111 -- 7bits
|
||||
|
||||
debug ("ws")
|
||||
print (" payload_len=" + l_len.out)
|
||||
io.put_new_line
|
||||
end
|
||||
if Result.is_control and then l_len > 125 then
|
||||
-- All control frames MUST have a payload length of 125 bytes or less
|
||||
-- and MUST NOT be fragmented.
|
||||
Result.report_error (protocol_error, "Control frame MUST have a payload length of 125 bytes or less")
|
||||
elseif l_len = 127 then -- TODO proof of concept read 8 bytes.
|
||||
-- the following 8 bytes interpreted as a 64-bit unsigned integer
|
||||
-- (the most significant bit MUST be 0) are the payload length.
|
||||
-- Multibyte length quantities are expressed in network byte order.
|
||||
s := next_bytes (l_socket, 8) -- 64 bits
|
||||
debug ("ws")
|
||||
print (" extended payload length=" + string_to_byte_representation (s))
|
||||
io.put_new_line
|
||||
end
|
||||
if s.count < 8 then
|
||||
Result.report_error (Invalid_data, "Incomplete data for 64 bit Extended payload length")
|
||||
else
|
||||
l_payload_len := s [8].natural_32_code.to_natural_64
|
||||
l_payload_len := l_payload_len | (s [7].natural_32_code.to_natural_64 |<< 8)
|
||||
l_payload_len := l_payload_len | (s [6].natural_32_code.to_natural_64 |<< 16)
|
||||
l_payload_len := l_payload_len | (s [5].natural_32_code.to_natural_64 |<< 24)
|
||||
l_payload_len := l_payload_len | (s [4].natural_32_code.to_natural_64 |<< 32)
|
||||
l_payload_len := l_payload_len | (s [3].natural_32_code.to_natural_64 |<< 40)
|
||||
l_payload_len := l_payload_len | (s [2].natural_32_code.to_natural_64 |<< 48)
|
||||
l_payload_len := l_payload_len | (s [1].natural_32_code.to_natural_64 |<< 56)
|
||||
end
|
||||
elseif l_len = 126 then
|
||||
s := next_bytes (l_socket, 2) -- 16 bits
|
||||
debug ("ws")
|
||||
print (" extended payload length bits=" + string_to_byte_representation (s))
|
||||
io.put_new_line
|
||||
end
|
||||
if s.count < 2 then
|
||||
Result.report_error (Invalid_data, "Incomplete data for 16 bit Extended payload length")
|
||||
else
|
||||
l_payload_len := s [2].natural_32_code.to_natural_64
|
||||
l_payload_len := l_payload_len | (s [1].natural_32_code.to_natural_64 |<< 8)
|
||||
end
|
||||
else
|
||||
l_payload_len := l_len.to_natural_64
|
||||
end
|
||||
debug ("ws")
|
||||
print (" Full payload length=" + l_payload_len.out)
|
||||
io.put_new_line
|
||||
end
|
||||
if Result.is_valid then
|
||||
if l_has_mask then
|
||||
l_masking_key := next_bytes (l_socket, 4) -- 32 bits
|
||||
debug ("ws")
|
||||
print (" Masking key bits=" + string_to_byte_representation (l_masking_key))
|
||||
io.put_new_line
|
||||
end
|
||||
if l_masking_key.count < 4 then
|
||||
debug ("ws")
|
||||
print ("masking-key read stream -> " + l_socket.bytes_read.out + " bits%N")
|
||||
end
|
||||
Result.report_error (Invalid_data, "Incomplete data for Masking-key")
|
||||
l_masking_key := Void
|
||||
end
|
||||
else
|
||||
Result.report_error (protocol_error, "All frames sent from client to server are masked!")
|
||||
end
|
||||
if Result.is_valid then
|
||||
l_chunk_size := 0x4000 -- 16 K
|
||||
if l_payload_len > {INTEGER_32}.max_value.to_natural_64 then
|
||||
-- Issue .. to big to store in STRING
|
||||
-- FIXME !!!
|
||||
Result.report_error (Message_too_large, "Can not handle payload data (len=" + l_payload_len.out + ")")
|
||||
else
|
||||
l_len := l_payload_len.to_integer_32
|
||||
end
|
||||
from
|
||||
l_fetch_count := 0
|
||||
l_remaining_len := l_len
|
||||
until
|
||||
l_fetch_count >= l_len or l_len = 0 or not Result.is_valid
|
||||
loop
|
||||
if l_remaining_len < l_chunk_size then
|
||||
l_chunk_size := l_remaining_len
|
||||
end
|
||||
l_socket.read_stream (l_chunk_size)
|
||||
l_bytes_read := l_socket.bytes_read
|
||||
debug ("ws")
|
||||
print ("read chunk size=" + l_chunk_size.out + " fetch_count=" + l_fetch_count.out + " l_len=" + l_len.out + " -> " + l_bytes_read.out + "bytes%N")
|
||||
end
|
||||
if l_bytes_read > 0 then
|
||||
l_remaining_len := l_remaining_len - l_bytes_read
|
||||
l_chunk := l_socket.last_string
|
||||
if l_masking_key /= Void then
|
||||
-- Masking
|
||||
-- http://tools.ietf.org/html/rfc6455#section-5.3
|
||||
unmask (l_chunk, l_fetch_count + 1, l_masking_key)
|
||||
else
|
||||
check
|
||||
client_frame_should_always_be_encoded: False
|
||||
end
|
||||
end
|
||||
l_fetch_count := l_fetch_count + l_bytes_read
|
||||
Result.append_payload_data_chop (l_chunk, l_bytes_read, l_remaining_len = 0)
|
||||
else
|
||||
Result.report_error (internal_error, "Issue reading payload data...")
|
||||
end
|
||||
end
|
||||
if is_verbose then
|
||||
log (" Received " + l_fetch_count.out + " out of " + l_len.out + " bytes <===============", debug_level)
|
||||
end
|
||||
debug ("ws")
|
||||
print (" -> ")
|
||||
if attached Result.payload_data as l_payload_data then
|
||||
s := l_payload_data.tail (l_fetch_count)
|
||||
if s.count > 50 then
|
||||
print (string_to_byte_hexa_representation (s.head (50) + ".."))
|
||||
else
|
||||
print (string_to_byte_hexa_representation (s))
|
||||
end
|
||||
print ("%N")
|
||||
if Result.is_text and Result.is_fin and Result.fragment_count = 0 then
|
||||
print (" -> ")
|
||||
if s.count > 50 then
|
||||
print (s.head (50) + "..")
|
||||
else
|
||||
print (s)
|
||||
end
|
||||
print ("%N")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
if Result /= Void then
|
||||
if attached Result.error as err then
|
||||
if is_verbose then
|
||||
log (" !Invalid frame: " + err.string, debug_level)
|
||||
end
|
||||
end
|
||||
if Result.is_injected_control then
|
||||
if attached Result.parent as l_parent then
|
||||
if not Result.is_valid then
|
||||
l_parent.report_error (protocol_error, "Invalid injected frame")
|
||||
end
|
||||
if Result.is_connection_close then
|
||||
-- Return this and process the connection close right away!
|
||||
l_parent.update_fin (True)
|
||||
l_fin := Result.is_fin
|
||||
else
|
||||
Result := l_parent
|
||||
l_fin := l_parent.is_fin
|
||||
check
|
||||
-- This is a control frame but occurs in fragmented frame.
|
||||
inside_fragmented_frame: not l_fin
|
||||
end
|
||||
end
|
||||
else
|
||||
check
|
||||
has_parent: False
|
||||
end
|
||||
l_fin := False -- This is a control frame but occurs in fragmented frame.
|
||||
end
|
||||
end
|
||||
if not Result.is_valid then
|
||||
is_data_frame_ok := False
|
||||
end
|
||||
else
|
||||
is_data_frame_ok := False
|
||||
end
|
||||
end
|
||||
end
|
||||
has_error := Result = Void or else Result.has_error
|
||||
rescue
|
||||
retried := True
|
||||
if Result /= Void then
|
||||
Result.report_error (internal_error, "Internal error")
|
||||
end
|
||||
retry
|
||||
end
|
||||
|
||||
|
||||
feature -- Encoding
|
||||
|
||||
digest (a_sha1: SHA1): STRING
|
||||
-- Digest of `a_sha1'.
|
||||
-- Should by in SHA1 class
|
||||
local
|
||||
l_digest: SPECIAL [NATURAL_8]
|
||||
index, l_upper: INTEGER
|
||||
do
|
||||
l_digest := a_sha1.digest
|
||||
create Result.make (l_digest.count // 2)
|
||||
from
|
||||
index := l_digest.Lower
|
||||
l_upper := l_digest.upper
|
||||
until
|
||||
index > l_upper
|
||||
loop
|
||||
Result.append_character (l_digest [index].to_character_8)
|
||||
index := index + 1
|
||||
end
|
||||
end
|
||||
|
||||
feature {NONE} -- Socket helpers
|
||||
|
||||
next_bytes (a_socket: HTTPD_STREAM_SOCKET; nb: INTEGER): STRING
|
||||
require
|
||||
nb > 0
|
||||
local
|
||||
n, l_bytes_read: INTEGER
|
||||
do
|
||||
create Result.make (nb)
|
||||
from
|
||||
n := nb
|
||||
until
|
||||
n = 0
|
||||
loop
|
||||
a_socket.read_stream (nb)
|
||||
l_bytes_read := a_socket.bytes_read
|
||||
if l_bytes_read > 0 then
|
||||
Result.append (a_socket.last_string)
|
||||
n := n - l_bytes_read
|
||||
else
|
||||
n := 0
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
feature -- Masking Data Client - Server
|
||||
|
||||
unmask (a_chunk: STRING_8; a_pos: INTEGER; a_key: READABLE_STRING_8)
|
||||
local
|
||||
i, n: INTEGER
|
||||
do
|
||||
from
|
||||
i := 1
|
||||
n := a_chunk.count
|
||||
until
|
||||
i > n
|
||||
loop
|
||||
a_chunk.put_code (a_chunk.code (i).bit_xor (a_key [((i + (a_pos - 1) - 1) \\ 4) + 1].natural_32_code), i)
|
||||
i := i + 1
|
||||
end
|
||||
end
|
||||
|
||||
append_chunk_unmasked (a_chunk: READABLE_STRING_8; a_pos: INTEGER; a_key: READABLE_STRING_8; a_target: STRING)
|
||||
-- To convert masked data into unmasked data, or vice versa, the following
|
||||
-- algorithm is applied. The same algorithm applies regardless of the
|
||||
-- direction of the translation, e.g., the same steps are applied to
|
||||
-- mask the data as to unmask the data.
|
||||
|
||||
-- Octet i of the transformed data ("transformed-octet-i") is the XOR of
|
||||
-- octet i of the original data ("original-octet-i") with octet at index
|
||||
-- i modulo 4 of the masking key ("masking-key-octet-j"):
|
||||
|
||||
-- j = i MOD 4
|
||||
-- transformed-octet-i = original-octet-i XOR masking-key-octet-j
|
||||
|
||||
-- The payload length, indicated in the framing as frame-payload-length,
|
||||
-- does NOT include the length of the masking key. It is the length of
|
||||
-- the "Payload data", e.g., the number of bytes following the masking
|
||||
-- key.
|
||||
note
|
||||
EIS: "name=Masking", "src=http://tools.ietf.org/html/rfc6455#section-5.3", "protocol=uri"
|
||||
local
|
||||
i, n: INTEGER
|
||||
do
|
||||
-- debug ("ws")
|
||||
-- print ("append_chunk_unmasked (%"" + string_to_byte_representation (a_chunk) + "%",%N%Ta_pos=" + a_pos.out+ ", a_key, a_target #.count=" + a_target.count.out + ")%N")
|
||||
-- end
|
||||
from
|
||||
i := 1
|
||||
n := a_chunk.count
|
||||
until
|
||||
i > n
|
||||
loop
|
||||
a_target.append_code (a_chunk.code (i).bit_xor (a_key [((i + (a_pos - 1) - 1) \\ 4) + 1].natural_32_code))
|
||||
i := i + 1
|
||||
end
|
||||
end
|
||||
|
||||
feature {NONE} -- Debug
|
||||
|
||||
to_byte_representation (a_integer: INTEGER): STRING
|
||||
require
|
||||
valid: a_integer >= 0 and then a_integer <= 255
|
||||
local
|
||||
l_val: INTEGER
|
||||
do
|
||||
create Result.make (8)
|
||||
from
|
||||
l_val := a_integer
|
||||
until
|
||||
l_val < 2
|
||||
loop
|
||||
Result.prepend_integer (l_val \\ 2)
|
||||
l_val := l_val // 2
|
||||
end
|
||||
Result.prepend_integer (l_val)
|
||||
end
|
||||
|
||||
string_to_byte_representation (s: STRING): STRING
|
||||
require
|
||||
valid: s.count > 0
|
||||
local
|
||||
i, n: INTEGER
|
||||
do
|
||||
n := s.count
|
||||
create Result.make (8 * n)
|
||||
if n > 0 then
|
||||
from
|
||||
i := 1
|
||||
until
|
||||
i > n
|
||||
loop
|
||||
if not Result.is_empty then
|
||||
Result.append_character (':')
|
||||
end
|
||||
Result.append (to_byte_representation (s [i].code))
|
||||
i := i + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
string_to_byte_hexa_representation (s: STRING): STRING
|
||||
local
|
||||
i, n: INTEGER
|
||||
c: INTEGER
|
||||
do
|
||||
n := s.count
|
||||
create Result.make (8 * n)
|
||||
if n > 0 then
|
||||
from
|
||||
i := 1
|
||||
until
|
||||
i > n
|
||||
loop
|
||||
if not Result.is_empty then
|
||||
Result.append_character (':')
|
||||
end
|
||||
c := s [i].code
|
||||
check
|
||||
c <= 0xFF
|
||||
end
|
||||
Result.append_character (((c |>> 4) & 0xF).to_hex_character)
|
||||
Result.append_character (((c) & 0xF).to_hex_character)
|
||||
i := i + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
note
|
||||
copyright: "2011-2016, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
|
||||
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
|
||||
source: "[
|
||||
Eiffel Software
|
||||
5949 Hollister Ave., Goleta, CA 93117 USA
|
||||
Telephone 805-685-1006, Fax 805-685-6869
|
||||
Website http://www.eiffel.com
|
||||
Customer support http://support.eiffel.com
|
||||
]"
|
||||
end
|
||||
@@ -0,0 +1,152 @@
|
||||
note
|
||||
description: "[
|
||||
To implement websocket handling, provide a `callbacks` object implementing the {WEB_SOCKET_EVENT_I} interface.
|
||||
]"
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
class
|
||||
WEB_SOCKET_HANDLER
|
||||
|
||||
inherit
|
||||
WEB_SOCKET_CONSTANTS
|
||||
|
||||
REFACTORING_HELPER
|
||||
|
||||
HTTPD_LOGGER_CONSTANTS
|
||||
|
||||
create
|
||||
make
|
||||
|
||||
feature {NONE} -- Initialization
|
||||
|
||||
make (ws: WEB_SOCKET; a_callbacks: WEB_SOCKET_EVENT_I)
|
||||
do
|
||||
web_socket := ws
|
||||
callbacks := a_callbacks
|
||||
end
|
||||
|
||||
feature -- Access
|
||||
|
||||
web_socket: WEB_SOCKET
|
||||
-- Associated websocket.
|
||||
|
||||
callbacks: WEB_SOCKET_EVENT_I
|
||||
|
||||
feature -- Execution
|
||||
|
||||
frozen execute
|
||||
do
|
||||
callbacks.on_open (web_socket)
|
||||
execute_websocket
|
||||
end
|
||||
|
||||
execute_websocket
|
||||
local
|
||||
exit: BOOLEAN
|
||||
l_frame: detachable WEB_SOCKET_FRAME
|
||||
l_client_message: detachable READABLE_STRING_8
|
||||
l_utf: UTF_CONVERTER
|
||||
ws: like web_socket
|
||||
s: STRING
|
||||
do
|
||||
from
|
||||
-- loop until ws is closed or has error.
|
||||
ws := web_socket
|
||||
until
|
||||
exit
|
||||
loop
|
||||
debug ("dbglog")
|
||||
dbglog (generator + ".execute_websocket (loop) WS_REQUEST_HANDLER.process_request {" + ws.socket_descriptor.out + "}")
|
||||
end
|
||||
if ws.is_ready_for_reading then
|
||||
l_frame := ws.next_frame
|
||||
if l_frame /= Void and then l_frame.is_valid then
|
||||
if attached l_frame.injected_control_frames as l_injections then
|
||||
-- Process injected control frames now.
|
||||
-- FIXME
|
||||
across
|
||||
l_injections as ic
|
||||
loop
|
||||
if ic.item.is_connection_close then
|
||||
-- FIXME: we should probably send this event .. after the `l_frame.parent' frame event.
|
||||
callbacks.on_event (ws, ic.item.payload_data, ic.item.opcode)
|
||||
exit := True
|
||||
elseif ic.item.is_ping then
|
||||
-- FIXME reply only to the most recent ping ...
|
||||
callbacks.on_event (ws, ic.item.payload_data, ic.item.opcode)
|
||||
else
|
||||
callbacks.on_event (ws, ic.item.payload_data, ic.item.opcode)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
l_client_message := l_frame.payload_data
|
||||
if l_client_message = Void then
|
||||
l_client_message := ""
|
||||
end
|
||||
|
||||
debug ("ws")
|
||||
create s.make_from_string ("%NExecute: %N")
|
||||
s.append (" [opcode: "+ opcode_name (l_frame.opcode) +"]%N")
|
||||
if l_frame.is_text then
|
||||
s.append (" [client message: %""+ l_client_message +"%"]%N")
|
||||
elseif l_frame.is_binary then
|
||||
s.append (" [client binary message length: %""+ l_client_message.count.out +"%"]%N")
|
||||
end
|
||||
s.append (" [is_control: " + l_frame.is_control.out + "]%N")
|
||||
s.append (" [is_binary: " + l_frame.is_binary.out + "]%N")
|
||||
s.append (" [is_text: " + l_frame.is_text.out + "]%N")
|
||||
dbglog (s)
|
||||
end
|
||||
|
||||
if l_frame.is_connection_close then
|
||||
callbacks.on_event (ws, l_client_message, l_frame.opcode)
|
||||
exit := True
|
||||
elseif l_frame.is_binary then
|
||||
callbacks.on_event (ws, l_client_message, l_frame.opcode)
|
||||
elseif l_frame.is_text then
|
||||
check is_valid_utf_8: l_utf.is_valid_utf_8_string_8 (l_client_message) end
|
||||
callbacks.on_event (ws, l_client_message, l_frame.opcode)
|
||||
else
|
||||
callbacks.on_event (ws, l_client_message, l_frame.opcode)
|
||||
end
|
||||
else
|
||||
debug ("ws")
|
||||
create s.make_from_string ("%NExecute: %N")
|
||||
s.append (" [ERROR: invalid frame]%N")
|
||||
if l_frame /= Void and then attached l_frame.error as err then
|
||||
s.append (" [Code: "+ err.code.out +"]%N")
|
||||
s.append (" [Description: "+ err.description +"]%N")
|
||||
end
|
||||
dbglog (s)
|
||||
end
|
||||
callbacks.on_event (ws, "", connection_close_frame)
|
||||
exit := True -- FIXME: check proper close protocol
|
||||
end
|
||||
else
|
||||
debug ("ws")
|
||||
dbglog (generator + ".WAITING WS_REQUEST_HANDLER.process_request {" + ws.socket_descriptor.out + "}")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
feature {NONE} -- Logging
|
||||
|
||||
dbglog (m: READABLE_STRING_8)
|
||||
do
|
||||
web_socket.log (m, debug_level)
|
||||
end
|
||||
|
||||
note
|
||||
copyright: "2011-2016, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
|
||||
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
|
||||
source: "[
|
||||
Eiffel Software
|
||||
5949 Hollister Ave., Goleta, CA 93117 USA
|
||||
Telephone 805-685-1006, Fax 805-685-6869
|
||||
Website http://www.eiffel.com
|
||||
Customer support http://support.eiffel.com
|
||||
]"
|
||||
end
|
||||
@@ -0,0 +1,44 @@
|
||||
note
|
||||
description: "Summary description for {WGI_STANDALONE_WEBSOCKET_CONNECTOR}."
|
||||
author: ""
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
class
|
||||
WGI_STANDALONE_WEBSOCKET_CONNECTOR [G -> WGI_EXECUTION create make end]
|
||||
|
||||
inherit
|
||||
WGI_STANDALONE_CONNECTOR [G]
|
||||
redefine
|
||||
name, version
|
||||
end
|
||||
|
||||
create
|
||||
make,
|
||||
make_with_base
|
||||
|
||||
feature -- Access
|
||||
|
||||
name: STRING_8
|
||||
-- Name of Current connector
|
||||
once
|
||||
Result := "ws_httpd"
|
||||
end
|
||||
|
||||
version: STRING_8
|
||||
-- Version of Current connector
|
||||
once
|
||||
Result := "1.0"
|
||||
end
|
||||
|
||||
note
|
||||
copyright: "2011-2016, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
|
||||
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
|
||||
source: "[
|
||||
Eiffel Software
|
||||
5949 Hollister Ave., Goleta, CA 93117 USA
|
||||
Telephone 805-685-1006, Fax 805-685-6869
|
||||
Website http://www.eiffel.com
|
||||
Customer support http://support.eiffel.com
|
||||
]"
|
||||
end
|
||||
@@ -0,0 +1,49 @@
|
||||
note
|
||||
description: "[
|
||||
Component to launch the service using the default connector
|
||||
|
||||
Eiffel Web httpd for this class
|
||||
|
||||
|
||||
The httpd default connector support options:
|
||||
port: numeric such as 8099 (or equivalent string as "8099")
|
||||
base: base_url (very specific to standalone server)
|
||||
verbose: to display verbose output, useful for standalone connector
|
||||
force_single_threaded: use only one thread, useful for standalone connector
|
||||
|
||||
check WSF_SERVICE_LAUNCHER for more documentation
|
||||
]"
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
class
|
||||
WSF_STANDALONE_WEBSOCKET_SERVICE_LAUNCHER [G -> WSF_WEBSOCKET_EXECUTION create make end]
|
||||
|
||||
inherit
|
||||
WSF_STANDALONE_SERVICE_LAUNCHER [G]
|
||||
redefine
|
||||
connector
|
||||
end
|
||||
|
||||
create
|
||||
make,
|
||||
make_and_launch
|
||||
|
||||
feature -- Status report
|
||||
|
||||
connector: WGI_STANDALONE_WEBSOCKET_CONNECTOR [G]
|
||||
-- Default connector
|
||||
|
||||
;note
|
||||
copyright: "2011-2016, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
|
||||
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
|
||||
source: "[
|
||||
Eiffel Software
|
||||
5949 Hollister Ave., Goleta, CA 93117 USA
|
||||
Telephone 805-685-1006, Fax 805-685-6869
|
||||
Website http://www.eiffel.com
|
||||
Customer support http://support.eiffel.com
|
||||
]"
|
||||
|
||||
end
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
note
|
||||
description: "[
|
||||
Request execution based on attributes `request' and `response'.
|
||||
Also support Upgrade to Websocket protocol.
|
||||
|
||||
|
||||
]"
|
||||
author: "$Author$"
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
deferred class
|
||||
WSF_WEBSOCKET_EXECUTION
|
||||
|
||||
inherit
|
||||
WSF_EXECUTION
|
||||
rename
|
||||
execute as http_execute
|
||||
end
|
||||
|
||||
--create
|
||||
-- make
|
||||
|
||||
feature -- Execution
|
||||
|
||||
frozen http_execute
|
||||
local
|
||||
ws: WEB_SOCKET
|
||||
ws_h: like new_websocket_handler
|
||||
do
|
||||
create ws.make (request, response)
|
||||
ws.open_ws_handshake
|
||||
if ws.is_websocket then
|
||||
if ws.has_error then
|
||||
-- Upgrade to websocket raised an error
|
||||
-- stay on standard HTTP/1.1 protocol
|
||||
execute
|
||||
else
|
||||
ws_h := new_websocket_handler (ws)
|
||||
ws_h.execute
|
||||
end
|
||||
else
|
||||
execute
|
||||
end
|
||||
end
|
||||
|
||||
execute
|
||||
-- Execute Current request,
|
||||
-- getting data from `request'
|
||||
-- and response to client via `response'.
|
||||
deferred
|
||||
end
|
||||
|
||||
feature -- Factory
|
||||
|
||||
new_websocket_handler (ws: WEB_SOCKET): WEB_SOCKET_HANDLER
|
||||
-- Websocket request specific handler on socket `ws'.
|
||||
--| For the creation, it requires an instance of `{WEB_SOCKET_EVENT_I}'
|
||||
--| to receive the websocket events.
|
||||
--| One can inherit from {WEB_SOCKET_EVENT_I} and implement the related
|
||||
--| deferred features.
|
||||
--| Or even provide a new class implementing {WEB_SOCKET_EVENT_I}.
|
||||
require
|
||||
is_websocket: ws.is_websocket
|
||||
no_error: not ws.has_error
|
||||
deferred
|
||||
end
|
||||
|
||||
note
|
||||
copyright: "2011-2016, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
|
||||
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
|
||||
source: "[
|
||||
Eiffel Software
|
||||
5949 Hollister Ave., Goleta, CA 93117 USA
|
||||
Telephone 805-685-1006, Fax 805-685-6869
|
||||
Website http://www.eiffel.com
|
||||
Customer support http://support.eiffel.com
|
||||
]"
|
||||
end
|
||||
@@ -14,7 +14,6 @@ inherit
|
||||
make_from_execution as make_from_wgi_execution
|
||||
redefine
|
||||
make,
|
||||
execute,
|
||||
clean,
|
||||
is_valid_end_of_execution
|
||||
end
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
note
|
||||
description: "Summary description for {WSF_PROXY_SOCKET_FACTORY}."
|
||||
author: ""
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
class
|
||||
WSF_PROXY_SOCKET_FACTORY
|
||||
|
||||
inherit
|
||||
WSF_PROXY_SOCKET_FACTORY_I
|
||||
|
||||
feature {NONE} -- Implementation
|
||||
|
||||
ssl_socket (a_host: READABLE_STRING_8; a_port: INTEGER): detachable NETWORK_STREAM_SOCKET
|
||||
do
|
||||
check supported: False end
|
||||
end
|
||||
|
||||
end
|
||||
@@ -0,0 +1,30 @@
|
||||
note
|
||||
description: "Summary description for {WSF_PROXY_SOCKET_FACTORY}."
|
||||
author: ""
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
class
|
||||
WSF_PROXY_SOCKET_FACTORY
|
||||
|
||||
inherit
|
||||
WSF_PROXY_SOCKET_FACTORY_I
|
||||
redefine
|
||||
is_ssl_supported
|
||||
end
|
||||
|
||||
feature {NONE} -- Implementation
|
||||
|
||||
ssl_socket (a_host: READABLE_STRING_8; a_port: INTEGER): detachable SSL_NETWORK_STREAM_SOCKET
|
||||
do
|
||||
if attached create_from_name (a_host) as l_peer_address then
|
||||
create Result.make_client_by_address_and_port (l_peer_address, a_port)
|
||||
end
|
||||
end
|
||||
|
||||
feature -- Status
|
||||
|
||||
is_ssl_supported: BOOLEAN = True
|
||||
-- Is https:// supported?
|
||||
|
||||
end
|
||||
@@ -0,0 +1,67 @@
|
||||
note
|
||||
description: "Summary description for {WSF_PROXY_SOCKET_FACTORY_I}."
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
deferred class
|
||||
WSF_PROXY_SOCKET_FACTORY_I
|
||||
|
||||
inherit
|
||||
INET_ADDRESS_FACTORY
|
||||
|
||||
feature -- Access
|
||||
|
||||
socket_from_uri (a_uri: URI): like socket
|
||||
local
|
||||
l_port: INTEGER
|
||||
do
|
||||
if a_uri.is_valid and then attached a_uri.host as l_host then
|
||||
l_port := a_uri.port
|
||||
if a_uri.scheme.is_case_insensitive_equal_general ("https") then
|
||||
if is_ssl_supported then
|
||||
if l_port <= 0 then
|
||||
l_port := 443
|
||||
end
|
||||
Result := ssl_socket (l_host, l_port)
|
||||
end
|
||||
elseif a_uri.scheme.is_case_insensitive_equal_general ("http") then
|
||||
if l_port <= 0 then
|
||||
l_port := 80
|
||||
end
|
||||
Result := socket (l_host, l_port)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
feature -- Status
|
||||
|
||||
is_uri_supported (a_uri: URI): BOOLEAN
|
||||
do
|
||||
Result := a_uri.scheme.is_case_insensitive_equal_general ("http")
|
||||
or else (
|
||||
a_uri.scheme.is_case_insensitive_equal_general ("https")
|
||||
and is_ssl_supported
|
||||
)
|
||||
end
|
||||
|
||||
is_ssl_supported: BOOLEAN
|
||||
-- Is https:// supported?
|
||||
do
|
||||
end
|
||||
|
||||
feature {NONE} -- Implementation
|
||||
|
||||
socket (a_host: READABLE_STRING_8; a_port: INTEGER): detachable NETWORK_STREAM_SOCKET
|
||||
do
|
||||
if attached create_from_name (a_host) as l_peer_address then
|
||||
create Result.make_client_by_address_and_port (l_peer_address, a_port)
|
||||
end
|
||||
end
|
||||
|
||||
ssl_socket (a_host: READABLE_STRING_8; a_port: INTEGER): detachable NETWORK_STREAM_SOCKET
|
||||
require
|
||||
is_ssl_supported: is_ssl_supported
|
||||
deferred
|
||||
end
|
||||
|
||||
end
|
||||
@@ -0,0 +1,303 @@
|
||||
note
|
||||
description: "Summary description for {WSF_SIMPLE_REVERSE_PROXY_HANDLER}."
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
class
|
||||
WSF_SIMPLE_REVERSE_PROXY_HANDLER
|
||||
|
||||
create
|
||||
make
|
||||
|
||||
feature {NONE} -- Initialization
|
||||
|
||||
make (a_remote_uri: READABLE_STRING_8)
|
||||
do
|
||||
create remote_uri.make_from_string (a_remote_uri)
|
||||
timeout := 30 -- seconds. See {NETWORK_SOCKET}.default_timeout
|
||||
connect_timeout := 5_000 -- 5 seconds.
|
||||
is_via_header_supported := True
|
||||
end
|
||||
|
||||
feature -- Access
|
||||
|
||||
remote_uri: URI
|
||||
-- Url for the targetted service.
|
||||
|
||||
uri_rewriter: detachable WSF_URI_REWRITER assign set_uri_rewriter
|
||||
-- URI rewriter component, to compute the URI on targetted service
|
||||
-- based on current request.
|
||||
|
||||
feature -- Settings
|
||||
|
||||
connect_timeout: INTEGER assign set_connect_timeout
|
||||
-- In milliseconds.
|
||||
|
||||
timeout: INTEGER assign set_timeout
|
||||
-- In seconds.
|
||||
|
||||
is_via_header_supported: BOOLEAN
|
||||
-- Via: header supported.
|
||||
-- Default: True.
|
||||
|
||||
feature -- Change
|
||||
|
||||
set_uri_rewriter (a_rewriter: like uri_rewriter)
|
||||
do
|
||||
uri_rewriter := a_rewriter
|
||||
end
|
||||
|
||||
set_timeout (a_timeout_in_seconds: INTEGER)
|
||||
-- in seconds.
|
||||
do
|
||||
timeout := a_timeout_in_seconds
|
||||
end
|
||||
|
||||
set_connect_timeout (a_timeout_in_milliseconds: INTEGER)
|
||||
-- in milliseconds.
|
||||
do
|
||||
connect_timeout := a_timeout_in_milliseconds
|
||||
end
|
||||
|
||||
set_is_via_header_supported (b: BOOLEAN)
|
||||
-- Set `is_via_header_supported' to `b'.
|
||||
do
|
||||
is_via_header_supported := b
|
||||
end
|
||||
|
||||
feature -- Execution
|
||||
|
||||
proxy_uri (request: WSF_REQUEST): STRING
|
||||
-- URI to query on proxyfied host.
|
||||
do
|
||||
if attached uri_rewriter as r then
|
||||
Result := r.uri (request)
|
||||
else
|
||||
Result := request.request_uri
|
||||
end
|
||||
end
|
||||
|
||||
execute (request: WSF_REQUEST; response: WSF_RESPONSE)
|
||||
-- Execute reverse proxy request.
|
||||
local
|
||||
h: HTTP_HEADER
|
||||
l_http_query: STRING
|
||||
l_status_line: STRING
|
||||
l_max_forward: INTEGER
|
||||
l_via: detachable STRING
|
||||
l_protocol: STRING
|
||||
i: INTEGER
|
||||
l_completed: BOOLEAN
|
||||
l_remote_uri: like remote_uri
|
||||
l_socket_factory: WSF_PROXY_SOCKET_FACTORY
|
||||
do
|
||||
l_remote_uri := remote_uri
|
||||
create l_socket_factory
|
||||
if not l_socket_factory.is_uri_supported (l_remote_uri) then
|
||||
send_error (request, response, {HTTP_STATUS_CODE}.bad_gateway, l_remote_uri.scheme + " is not supported! [for remote " + l_remote_uri.string + "]")
|
||||
elseif attached l_socket_factory.socket_from_uri (l_remote_uri) as l_socket then
|
||||
l_socket.set_connect_timeout (connect_timeout) -- milliseconds
|
||||
l_socket.set_timeout (timeout) -- seconds
|
||||
|
||||
l_socket.connect
|
||||
if l_socket.is_connected then
|
||||
create l_http_query.make_from_string (request.request_method)
|
||||
l_http_query.append_character (' ')
|
||||
l_http_query.append (l_remote_uri.path)
|
||||
l_http_query.append (proxy_uri (request))
|
||||
l_http_query.append_character (' ')
|
||||
l_http_query.append (request.server_protocol)
|
||||
if attached request.raw_header_data as l_raw_header then
|
||||
i := l_raw_header.substring_index ("%R%N", 1)
|
||||
if i > 0 then
|
||||
-- Skip the first status line.
|
||||
create h.make_from_raw_header_data (l_raw_header.substring (i + 2, l_raw_header.count))
|
||||
else
|
||||
create h.make_from_raw_header_data (l_raw_header)
|
||||
end
|
||||
if attached l_remote_uri.host as l_remote_host then
|
||||
if l_remote_uri.port > 0 then
|
||||
h.put_header_key_value ("Host", l_remote_host + ":" + l_remote_uri.port.out)
|
||||
else
|
||||
h.put_header_key_value ("Host", l_remote_host)
|
||||
end
|
||||
end
|
||||
|
||||
-- Via header
|
||||
if is_via_header_supported then
|
||||
if attached h.item ("Via") as v then
|
||||
l_via := v
|
||||
l_via.append (", ")
|
||||
else
|
||||
create l_via.make_empty
|
||||
end
|
||||
l_via.append (request.server_protocol + " " + request.server_name + " (PROXY-" + request.server_software + ")")
|
||||
h.put_header_key_value ("Via", l_via)
|
||||
end
|
||||
|
||||
-- Max-Forwards header handling
|
||||
if attached h.item ("Max-Forwards") as h_max_forward then
|
||||
-- Max-Forwards: 0 stop, otherwise decrement by one.
|
||||
-- see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.31
|
||||
if h_max_forward.is_integer then
|
||||
l_max_forward := h_max_forward.to_integer - 1
|
||||
if l_max_forward >= 0 then
|
||||
h.put_header_key_value ("Max-Forwards", l_max_forward.out)
|
||||
end
|
||||
end
|
||||
end
|
||||
if l_max_forward < 0 then
|
||||
-- i.e previous Max-Forwards was '0'
|
||||
send_error (request, response, {HTTP_STATUS_CODE}.bad_gateway, "Reached maximum number of Forwards, not forwarded to " + l_remote_uri.string)
|
||||
else
|
||||
l_socket.put_string (l_http_query)
|
||||
l_socket.put_string ("%R%N")
|
||||
l_socket.put_string (h.string)
|
||||
l_socket.put_string ("%R%N")
|
||||
if request.content_length_value > 0 then
|
||||
request.read_input_data_into_file (l_socket)
|
||||
end
|
||||
|
||||
-- Get HTTP status
|
||||
l_socket.read_line_thread_aware
|
||||
create l_status_line.make_from_string (l_socket.last_string)
|
||||
-- Get HTTP header block
|
||||
if attached next_http_header_block (l_socket) as l_resp_header then
|
||||
create h.make_from_raw_header_data (l_resp_header)
|
||||
if attached status_line_info (l_status_line) as l_status_info then
|
||||
l_protocol := l_status_info.protocol
|
||||
if attached l_status_info.reason_phrase as l_phrase then
|
||||
response.set_status_code_with_reason_phrase (l_status_info.status_code, l_phrase)
|
||||
else
|
||||
response.set_status_code (l_status_info.status_code)
|
||||
end
|
||||
else
|
||||
check has_status_line: False end
|
||||
l_protocol := "1.0" -- Default?
|
||||
response.set_status_code (80)
|
||||
end
|
||||
|
||||
if is_via_header_supported then
|
||||
if attached h.item ("Via") as v then
|
||||
l_via := v
|
||||
l_via.append (", ")
|
||||
else
|
||||
create l_via.make_empty
|
||||
end
|
||||
l_via.append (l_protocol + " " + request.server_name + " (PROXY-" + request.server_software + ")")
|
||||
h.put_header_key_value ("Via", l_via)
|
||||
end
|
||||
|
||||
response.add_header_lines (h)
|
||||
from
|
||||
l_socket.read_stream (2_048)
|
||||
until
|
||||
l_socket.was_error
|
||||
or not l_socket.is_connected
|
||||
or l_socket.bytes_read <= 0
|
||||
or l_completed
|
||||
loop
|
||||
response.put_string (l_socket.last_string)
|
||||
if l_socket.bytes_read = 2_048 then
|
||||
l_socket.read_stream (2_048)
|
||||
else
|
||||
l_completed := True
|
||||
end
|
||||
end
|
||||
else
|
||||
send_error (request, response, {HTTP_STATUS_CODE}.internal_server_error, "Invalid response header!")
|
||||
end
|
||||
end
|
||||
else
|
||||
send_error (request, response, {HTTP_STATUS_CODE}.internal_server_error, "Can not access request header!")
|
||||
end
|
||||
else
|
||||
send_error (request, response, {HTTP_STATUS_CODE}.gateway_timeout, "Unable to connect " + l_remote_uri.string)
|
||||
end
|
||||
else
|
||||
send_error (request, response, {HTTP_STATUS_CODE}.bad_gateway, "Unable to connect " + l_remote_uri.string)
|
||||
end
|
||||
end
|
||||
|
||||
feature {NONE} -- Implementation
|
||||
|
||||
status_line_info (a_line: READABLE_STRING_8): detachable TUPLE [protocol: READABLE_STRING_8; status_code: INTEGER; reason_phrase: detachable READABLE_STRING_8]
|
||||
-- Info from status line
|
||||
--| Such as "HTTP/1.1 200 OK" -> ["1.1", 200, "OK"]
|
||||
local
|
||||
i,j: INTEGER
|
||||
p,s: detachable READABLE_STRING_8
|
||||
c: INTEGER
|
||||
do
|
||||
i := a_line.index_of (' ', 1)
|
||||
if i > 0 then
|
||||
p := a_line.substring (1, i - 1)
|
||||
if p.starts_with_general ("HTTP/") then
|
||||
p := p.substring (6, p.count) -- We could also keep HTTP/
|
||||
end
|
||||
j := i + 1
|
||||
i := a_line.index_of (' ', j)
|
||||
if i > 0 then
|
||||
s := a_line.substring (j, i - 1)
|
||||
if s.is_integer then
|
||||
c := s.to_integer
|
||||
s := a_line.substring (i + 1, a_line.count)
|
||||
if s.is_whitespace then
|
||||
s := Void
|
||||
elseif s[s.count].is_space then
|
||||
s := s.substring (1, s.count - 1)
|
||||
end
|
||||
Result := [p, c, s]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
next_http_header_block (a_socket: NETWORK_STREAM_SOCKET): detachable STRING
|
||||
local
|
||||
h: STRING
|
||||
do
|
||||
create h.make_empty
|
||||
from
|
||||
a_socket.read_line_thread_aware
|
||||
until
|
||||
Result /= Void
|
||||
or a_socket.was_error
|
||||
or (a_socket.bytes_read = 0 or a_socket.bytes_read = -1)
|
||||
or not a_socket.is_connected
|
||||
loop
|
||||
if a_socket.last_string.same_string ("%R") then
|
||||
-- End of header
|
||||
Result := h
|
||||
else
|
||||
h.append (a_socket.last_string)
|
||||
h.append ("%N")
|
||||
a_socket.read_line_thread_aware
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
send_error (request: WSF_REQUEST; response: WSF_RESPONSE; a_status_code: INTEGER; a_message: READABLE_STRING_8)
|
||||
local
|
||||
s: STRING
|
||||
do
|
||||
-- To send a response we need to setup, the status code and
|
||||
-- the response headers.
|
||||
create s.make_from_string (a_message)
|
||||
debug
|
||||
s.append ("%N(UTC time is " + (create {HTTP_DATE}.make_now_utc).rfc850_string + ").%N")
|
||||
end
|
||||
response.put_header ({HTTP_STATUS_CODE}.ok, <<["Content-Type", "plain/text"], ["Content-Length", s.count.out]>>)
|
||||
response.set_status_code (a_status_code)
|
||||
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
|
||||
|
||||
end
|
||||
38
library/server/wsf_proxy/rewriter/wsf_agent_uri_rewriter.e
Normal file
38
library/server/wsf_proxy/rewriter/wsf_agent_uri_rewriter.e
Normal file
@@ -0,0 +1,38 @@
|
||||
note
|
||||
description: "Summary description for {WSF_AGENT_URI_REWRITER}."
|
||||
author: ""
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
class
|
||||
WSF_AGENT_URI_REWRITER
|
||||
|
||||
inherit
|
||||
WSF_URI_REWRITER
|
||||
|
||||
create
|
||||
make
|
||||
|
||||
--convert
|
||||
-- make ({FUNCTION [TUPLE [WSF_REQUEST], STRING]})
|
||||
|
||||
feature {NONE} -- Initialization
|
||||
|
||||
make (a_rewriter_function: like rewriter_function)
|
||||
do
|
||||
rewriter_function := a_rewriter_function
|
||||
end
|
||||
|
||||
feature -- Access
|
||||
|
||||
rewriter_function: FUNCTION [TUPLE [WSF_REQUEST], STRING]
|
||||
|
||||
feature -- Conversion
|
||||
|
||||
uri (a_request: WSF_REQUEST): STRING
|
||||
-- <Precursor>.
|
||||
do
|
||||
Result := rewriter_function (a_request)
|
||||
end
|
||||
|
||||
end
|
||||
16
library/server/wsf_proxy/rewriter/wsf_uri_rewriter.e
Normal file
16
library/server/wsf_proxy/rewriter/wsf_uri_rewriter.e
Normal file
@@ -0,0 +1,16 @@
|
||||
note
|
||||
description: "Summary description for {WSF_URI_REWRITER}."
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
deferred class
|
||||
WSF_URI_REWRITER
|
||||
|
||||
feature -- Conversion
|
||||
|
||||
uri (a_request: WSF_REQUEST): STRING
|
||||
-- Rewritten request uri based on `a_request'.
|
||||
deferred
|
||||
end
|
||||
|
||||
end
|
||||
34
library/server/wsf_proxy/wsf_proxy-safe.ecf
Normal file
34
library/server/wsf_proxy/wsf_proxy-safe.ecf
Normal file
@@ -0,0 +1,34 @@
|
||||
<?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_proxy" uuid="A39CCC27-BF63-4959-B881-7D0713F4C84A" library_target="wsf_proxy">
|
||||
<target name="wsf_proxy">
|
||||
<root all_classes="true"/>
|
||||
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
|
||||
<library name="http" location="..\..\..\library\network\protocol\http\http-safe.ecf"/>
|
||||
<library name="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="ssl_supported" value="true"/>
|
||||
</condition>
|
||||
</library>
|
||||
<library name="uri" location="$ISE_LIBRARY\library\text\uri\uri-safe.ecf"/>
|
||||
<library name="wsf" location="..\wsf\wsf-safe.ecf"/>
|
||||
<cluster name="network" location=".\network\" recursive="true">
|
||||
<file_rule>
|
||||
<exclude>no_ssl</exclude>
|
||||
<exclude>ssl</exclude>
|
||||
</file_rule>
|
||||
<cluster name="network_ssl" location="$|ssl\">
|
||||
<condition>
|
||||
<custom name="ssl_supported" value="true"/>
|
||||
</condition>
|
||||
</cluster>
|
||||
<cluster name="network_no_ssl" location="$|no_ssl\">
|
||||
<condition>
|
||||
<custom name="ssl_supported" excluded_value="true"/>
|
||||
</condition>
|
||||
</cluster>
|
||||
</cluster>
|
||||
<cluster name="reverse_proxy" location=".\reverse_proxy\" recursive="true"/>
|
||||
<cluster name="rewriter" location=".\rewriter\" recursive="true"/>
|
||||
</target>
|
||||
</system>
|
||||
14
library/server/wsf_proxy/wsf_proxy.ecf
Normal file
14
library/server/wsf_proxy/wsf_proxy.ecf
Normal file
@@ -0,0 +1,14 @@
|
||||
<?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_proxy" uuid="A39CCC27-BF63-4959-B881-7D0713F4C84A" library_target="wsf_proxy">
|
||||
<target name="wsf_proxy">
|
||||
<root all_classes="true"/>
|
||||
<option void_safety="none">
|
||||
</option>
|
||||
<library name="base" location="$ISE_LIBRARY\library\base\base.ecf"/>
|
||||
<library name="http" location="..\..\..\library\network\protocol\http\http.ecf"/>
|
||||
<library name="net" location="$ISE_LIBRARY\library\net\net.ecf"/>
|
||||
<library name="wsf" location="..\wsf\wsf.ecf"/>
|
||||
<cluster name="reverse_proxy" location=".\reverse_proxy\" recursive="true"/>
|
||||
<cluster name="rewriter" location=".\rewriter\" recursive="true"/>
|
||||
</target>
|
||||
</system>
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user