Merge branch 'master' of https://github.com/EiffelWebFramework/EWF into ewf_ws_compression

# Conflicts:
#	library/server/wsf/connector/standalone_websocket/websocket/web_socket.e
This commit is contained in:
jvelilla
2016-10-17 10:37:44 -03:00
151 changed files with 14370 additions and 781 deletions

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-13-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-13-0 http://www.eiffel.com/developers/xml/configuration-1-13-0.xsd" name="debug" uuid="AA458565-7711-4BE1-ADA3-91716EABFA21" library_target="debug">
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-13-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-13-0 http://www.eiffel.com/developers/xml/configuration-1-13-0.xsd" name="debug" uuid="AA458565-7711-4BE1-ADA3-91716EABFA21" library_target="debug_standalone">
<target name="common" abstract="true">
<file_rule>
<exclude>/EIFGENs$</exclude>
@@ -18,7 +18,7 @@
</target>
<target name="debug_any" extends="common">
<root class="EWF_DEBUG_SERVER" feature="make_and_launch"/>
<setting name="concurrency" value="thread"/>
<setting name="concurrency" value="scoop"/>
<library name="cgi" location="..\..\library\server\wsf\connector\cgi-safe.ecf" readonly="false"/>
<library name="libfcgi" location="..\..\library\server\wsf\connector\libfcgi-safe.ecf" readonly="false"/>
<library name="standalone" location="..\..\library\server\wsf\connector\standalone-safe.ecf" readonly="false"/>
@@ -27,7 +27,7 @@
</target>
<target name="debug_standalone" extends="common">
<root class="EWF_DEBUG_SERVER" feature="make_and_launch"/>
<setting name="concurrency" value="thread"/>
<setting name="concurrency" value="scoop"/>
<library name="default_standalone" location="..\..\library\server\wsf\default\standalone-safe.ecf" readonly="false"/>
<cluster name="launcher" location=".\launcher\default\" recursive="true"/>
<cluster name="src" location=".\src\" recursive="true"/>
@@ -44,6 +44,4 @@
<cluster name="launcher" location=".\launcher\default\" recursive="true"/>
<cluster name="src" location=".\src\" recursive="true"/>
</target>
<target name="debug" extends="debug_standalone">
</target>
</system>

2
examples/debug/debug.ini Normal file
View File

@@ -0,0 +1,2 @@
port=9090
verbose=true

View File

@@ -28,6 +28,7 @@ feature {NONE} -- Initialization
-- set_service_option ("verbose", True)
set_service_option ("port", 9090)
-- set_service_option ("base", "/www-debug/debug_service.fcgi/")
import_service_options (create {WSF_SERVICE_LAUNCHER_OPTIONS_FROM_INI}.make_from_file ("debug.ini"))
end
-- execute (req: WSF_REQUEST; res: WSF_RESPONSE)

View File

@@ -1,13 +1,13 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-13-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-13-0 http://www.eiffel.com/developers/xml/configuration-1-13-0.xsd" name="desktop_app" uuid="E015841A-D456-46E1-8A18-E0CEB9E69CD5">
<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="desktop_app" uuid="E015841A-D456-46E1-8A18-E0CEB9E69CD5">
<description>Vision2+web browser widget+embedded web service</description>
<target name="desktop_app">
<description>This example demonstrates how to build a vision2 desktop application that embed a web browser accessing the service of an embedded web service.</description>
<root class="DESKTOP_APP" feature="make_and_launch"/>
<option warning="true" full_class_checking="true" is_attached_by_default="true" void_safety="transitional" syntax="transitional">
<option warning="true" full_class_checking="true" is_attached_by_default="true" is_obsolete_routine_type="true" void_safety="all" syntax="transitional">
<assertions precondition="true" postcondition="true" check="true"/>
</option>
<setting name="concurrency" value="thread"/>
<setting name="concurrency" value="scoop"/>
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
<library name="ewsgi" location="..\..\library\server\ewsgi\ewsgi-safe.ecf"/>
<library name="thread" location="$ISE_LIBRARY\library\thread\thread-safe.ecf"/>
@@ -18,9 +18,9 @@
<library name="wsf_standalone_connector" location="..\..\library\server\ewsgi\connectors\standalone\standalone-safe.ecf"/>
<cluster name="src" location=".\src\" recursive="true">
<file_rule>
<exclude>/EIFGENs$</exclude>
<exclude>/CVS$</exclude>
<exclude>/.svn$</exclude>
<exclude>/CVS$</exclude>
<exclude>/EIFGENs$</exclude>
</file_rule>
</cluster>
</target>

View File

@@ -24,20 +24,25 @@ feature {NONE} -- Initialization
-- then launch the application.
local
l_win: like main_window
l_embedded_service: APP_EMBEDDED_WEB_SERVICE
l_embedded_service: separate APP_EMBEDDED_WEB_SERVICE
do
default_create
create l_win.make
main_window := l_win
l_win.show
create l_embedded_service.make
l_embedded_service.set_port_number (0) -- Use first available port number
l_embedded_service.on_launched_actions.force (agent on_web_service_launched (l_win, l_embedded_service))
l_embedded_service.launch
setup_and_launch_web_service (l_embedded_service)
launch
end
setup_and_launch_web_service (a_web_service: separate APP_EMBEDDED_WEB_SERVICE)
do
a_web_service.set_port_number (0) -- Use first available port number
a_web_service.set_on_launched_action (agent on_web_service_launched (a_web_service))
a_web_service.launch
end
on_quit
do
if attached main_window as win then
@@ -45,13 +50,15 @@ feature {NONE} -- Initialization
end
end
on_web_service_launched (a_win: attached like main_window; s: APP_EMBEDDED_WEB_SERVICE)
on_web_service_launched (a_web_service: separate APP_EMBEDDED_WEB_SERVICE)
do
add_idle_action_kamikaze (agent wait_for_termination (s, Void))
add_idle_action_kamikaze (agent a_win.open_link)
if attached main_window as win then
add_idle_action_kamikaze (agent wait_for_termination (a_web_service, Void))
add_idle_action_kamikaze (agent win.open_link)
end
end
wait_for_termination (s: APP_EMBEDDED_WEB_SERVICE; a_timeout: detachable EV_TIMEOUT)
wait_for_termination (a_web_service: separate APP_EMBEDDED_WEB_SERVICE; a_timeout: detachable EV_TIMEOUT)
local
t: detachable EV_TIMEOUT
do
@@ -60,7 +67,7 @@ feature {NONE} -- Initialization
t.set_interval (0)
end
if
attached s.observer as obs and then
attached a_web_service.observer as obs and then
observer_has_terminaded (obs)
then
if t /= Void then
@@ -70,7 +77,7 @@ feature {NONE} -- Initialization
else
if t = Void then
create t
t.actions.extend (agent wait_for_termination (s, t))
t.actions.extend (agent wait_for_termination (a_web_service, t))
else
t.set_interval (1_000)
end

View File

@@ -23,13 +23,15 @@ feature {NONE} -- Execution
-- `request.meta_variables' for the CGI meta variable
-- and `response' for output buffer
local
filter: WSF_AGENT_FILTER
m: WSF_PAGE_RESPONSE
do
if local_connection_restriction_enabled then
if
attached request.remote_addr as l_remote_addr and then
(
l_remote_addr.is_case_insensitive_equal_general ("127.0.0.1")
or else l_remote_addr.is_case_insensitive_equal_general ("localhost")
)
then
execute
else

View File

@@ -16,7 +16,6 @@ feature -- Initialization
make
do
create on_launched_actions
end
feature -- Execution
@@ -27,7 +26,6 @@ feature -- Execution
opts: WSF_SERVICE_LAUNCHER_OPTIONS
do
create opts.default_create
opts.set_verbose (True)
opts.set_option ("port", port_number)
create launcher.make (opts)
observer := launcher.connector.observer
@@ -40,11 +38,23 @@ feature -- Execution
on_launched (conn: WGI_STANDALONE_CONNECTOR [G])
do
set_port_number (conn.port)
on_launched_actions.call (Void)
if attached on_launched_action as act then
call_action (act)
end
end
call_action (act: attached like on_launched_action)
do
act.call (Void)
end
feature -- Access
on_launched_actions: ACTION_SEQUENCE [TUPLE]
on_launched_action: detachable separate PROCEDURE [ANY, TUPLE]
set_on_launched_action (act: like on_launched_action)
do
on_launched_action := act
end
end

View File

@@ -1,5 +1,5 @@
<?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="simple" uuid="C28C4F53-9963-46C0-A080-8F13E94E7486" library_target="simple">
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-15-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-15-0 http://www.eiffel.com/developers/xml/configuration-1-15-0.xsd" name="simple" uuid="C28C4F53-9963-46C0-A080-8F13E94E7486" library_target="simple_standalone">
<target name="common" abstract="true">
<file_rule>
<exclude>/.svn$</exclude>
@@ -16,13 +16,20 @@
</target>
<target name="simple_standalone" extends="common">
<root class="APPLICATION" feature="make_and_launch"/>
<option warning="true" is_attached_by_default="true" void_safety="all" syntax="transitional">
<option debug="false" warning="true" is_attached_by_default="true" void_safety="all" syntax="transitional">
<debug name="dbglog" enabled="true"/>
<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="simple" location=".\" recursive="true"/>
</target>
<target name="simple_standalone_mt" extends="simple_standalone">
<setting name="concurrency" value="thread"/>
</target>
<target name="simple_standalone_st" extends="simple_standalone">
<setting name="concurrency" value="none"/>
</target>
<target name="simple_cgi" extends="common">
<root class="APPLICATION" feature="make_and_launch"/>
<option warning="true" is_attached_by_default="true" void_safety="transitional" syntax="transitional">
@@ -39,6 +46,4 @@
<library name="default_libfcgi" location="..\..\library\server\wsf\default\libfcgi-safe.ecf"/>
<cluster name="simple" location=".\" recursive="true"/>
</target>
<target name="simple" extends="simple_standalone">
</target>
</system>

View File

@@ -2,7 +2,8 @@ verbose=true
verbose_level=ALERT
port=9090
#max_concurrent_connections=100
#keep_alive_timeout=15
keep_alive_timeout=3
#max_tcp_clients=100
#socket_timeout=300
socket_timeout=60
socket_recv_timeout=15
#max_keep_alive_requests=300

View File

@@ -7,7 +7,7 @@ class
APPLICATION
inherit
WSF_DEFAULT_SERVICE [APPLICATION_EXECUTION]
WSF_STANDALONE_SERVICE [APPLICATION_EXECUTION]
redefine
initialize
end
@@ -19,11 +19,17 @@ feature {NONE} -- Initialization
initialize
-- Initialize current service.
local
opts: WSF_STANDALONE_SERVICE_OPTIONS
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 ("simple.ini"))
create opts
opts.port := 9090
opts.socket_recv_timeout := 5 -- seconds
opts.import_ini_file_options ("simple.ini")
import_service_options (opts)
end
end

View File

@@ -17,11 +17,11 @@ port=9090
#keep_alive_timeout=15
#max_keep_alive_requests=100
### SSL settings
### Secure connection settings
# enable SSL, with file certificate.
ssl_enabled=true
ssl_ca_key=simple.key
ssl_ca_crt=simple.crt
is_secure=true
secure_certificate=ca.crt
secure_certificate_key=ca.key
### App settings
verbose=true

View File

@@ -13,9 +13,9 @@
</option>
<setting name="console_application" value="true"/>
<setting name="concurrency" value="scoop"/>
<variable name="httpd_ssl_enabled" value="true"/>
<variable name="ssl_enabled" value="true"/>
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
<library name="default_standalone" location="..\..\library\server\wsf\default\standalone-safe.ecf"/>
<library name="wsf_standalone" location="..\..\library\server\wsf\connector\standalone-safe.ecf"/>
<library name="http" location="..\..\library\network\protocol\http\http-safe.ecf"/>
<library name="wsf" location="..\..\library\server\wsf\wsf-safe.ecf"/>
<cluster name="simple" location=".\" recursive="true"/>

View File

@@ -14,16 +14,18 @@ feature {NONE} -- Initialization
make_and_launch
local
l_launcher: WSF_STANDALONE_WEBSOCKET_SERVICE_LAUNCHER [APPLICATION_EXECUTION]
opts: WSF_SERVICE_LAUNCHER_OPTIONS
opts: WSF_STANDALONE_WEBSOCKET_SERVICE_OPTIONS
do
create {WSF_SERVICE_LAUNCHER_OPTIONS_FROM_INI} opts.make_from_file ("ws.ini")
create l_launcher.make_and_launch (options)
create opts
if opts.is_secure_connection_supported then
opts.is_secure := True
opts.set_secure_protocol_to_tls_1_2
opts.secure_certificate := "ca.crt"
opts.secure_certificate_key := "ca.key"
end
options: WSF_SERVICE_LAUNCHER_OPTIONS
-- Initialize current service.
do
create {WSF_SERVICE_LAUNCHER_OPTIONS_FROM_INI} Result.make_from_file ("ws.ini")
opts.import_ini_file_options ("ws.ini")
create l_launcher.make_and_launch (opts)
end
end

View File

@@ -88,7 +88,7 @@ feature -- HTML Resource
<!DOCTYPE html>
<html>
<head>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
<script src="##HTTPSCHEME##://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
<script type="text/javascript">
$(document).ready(function() {
@@ -96,7 +96,7 @@ $(document).ready(function() {
function connect(){
var host = "ws://127.0.0.1:##PORTNUMBER##";
var host = "##WSSCHEME##://127.0.0.1:##PORTNUMBER##";
try{
socket = new WebSocket(host);
@@ -178,6 +178,13 @@ body {font-family:Arial, Helvetica, sans-serif;}
</html>
]"
Result.replace_substring_all ("##PORTNUMBER##", a_port.out)
if request.is_https then
Result.replace_substring_all ("##HTTPSCHEME##", "https")
Result.replace_substring_all ("##WSSCHEME##", "wss")
else
Result.replace_substring_all ("##HTTPSCHEME##", "http")
Result.replace_substring_all ("##WSSCHEME##", "ws")
end
end

15
examples/websocket/ca.crt Normal file
View File

@@ -0,0 +1,15 @@
-----BEGIN CERTIFICATE-----
MIICWDCCAcGgAwIBAgIJAJnXGtV+PtiYMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV
BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX
aWRnaXRzIFB0eSBMdGQwHhcNMTUwNDAzMjIxNTA0WhcNMTYwNDAyMjIxNTA0WjBF
MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50
ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB
gQDFMK6ojzg+KlklhTossR13c51izMgGc3B0z9ttfHIcx2kxra3HtHcKIl5wSUvn
G8zmSyFAyQTs5LUv65q46FM9qU8tP+vTeFCfNXvjRcIEpouta3J53K0xuUlxz4d4
4D6qvdDWAez/0AkI4y5etW5zXtg7IQorJhsI9TmfGuruzwIDAQABo1AwTjAdBgNV
HQ4EFgQUbWpk2HoHa0YqpEwr7CGEatBFTMkwHwYDVR0jBBgwFoAUbWpk2HoHa0Yq
pEwr7CGEatBFTMkwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOBgQAi+h4/
IgEocWkdRZBKHEcTrRxz5WhEDJMoVo9LhnXvCfn1G/4p6Un6sYv7Xzpi9NuSY8uV
cjfJJXhtF3AtyZ70iTAxWaRWjGaZ03PYOjlledJ5rqJEt6CCn8m+JsfznduZvbxQ
zQ6jCLXfyD/tvemB+yYEI3NntvRKx5/zt6Q26Q==
-----END CERTIFICATE-----

15
examples/websocket/ca.key Normal file
View File

@@ -0,0 +1,15 @@
-----BEGIN RSA PRIVATE KEY-----
MIICXAIBAAKBgQDFMK6ojzg+KlklhTossR13c51izMgGc3B0z9ttfHIcx2kxra3H
tHcKIl5wSUvnG8zmSyFAyQTs5LUv65q46FM9qU8tP+vTeFCfNXvjRcIEpouta3J5
3K0xuUlxz4d44D6qvdDWAez/0AkI4y5etW5zXtg7IQorJhsI9TmfGuruzwIDAQAB
AoGAR5efMg+dieRyLU8rieJcImxVbfOPg9gRsjdtIVkXTR+RL7ow59q7hXBo/Td/
WU8cm1gXoJ/bK+71YYqWyB+BaLRIWvRWb7Gdw203tu4e136Ca5uuY+71qdbVTVcl
NQ7J+T+eAQFP+a+DdT3ZQxu9eze87SMbu6i5YSpIk2kusOECQQDunv/DQ+nc+NgR
DF+Td3sNYUVRT9a1CWi6abAG6reXwp8MS4NobWDf+Ps4JODhEEwlIdq5qL7qqYBZ
Gc1TJJ53AkEA0404Fn6vAzzegBcS4RLlYTK7nMr0m4pMmDMCI6YzAYdMmKHp1e6f
IwxSmQrmwyAgwcT01bc0+A8yipcC2BWQaQJBAJ01QZm635OGmos41KsKF5bsE8gL
SpBBH69Yu/ECqGwie7iU84FUNnO4zIHjwghlPVVlZX3Vz9o4S+fn2N9DC+cCQGyZ
QyCxGdC0r5fbwHJQS/ZQn+UGfvlVzqoXDVMVn3t6ZES6YZrT61eHnOM5qGqklIxE
Old3vDZXPt/MU8Zvk3kCQBOgUx2VxvTrHN37hk9/QIDiM62+RenBm1M3ah8xTosf
1mSeEb6d9Kwb3TgPBmA7YXzJuAQfRIvEPMPxT5SSr6Q=
-----END RSA PRIVATE KEY-----

View File

@@ -18,4 +18,7 @@
<library name="wsf" location="..\..\library\server\wsf\wsf-safe.ecf"/>
<cluster name="app" location=".\" recursive="true"/>
</target>
<target name="websocket_app_ssl" extends="websocket_app">
<variable name="ssl_enabled" value="true"/>
</target>
</system>

View File

@@ -4,5 +4,10 @@ port=9090
max_concurrent_connections=100
keep_alive_timeout=35
max_tcp_clients=100
socket_timeout=30000
max_keep_alive_requests=3000
socket_timeout=30
socket_recv_timeout=5
max_keep_alive_requests=300
is_secure=false
secure_certificate=ca.crt
secure_certificate_key=ca.key

View File

@@ -0,0 +1,15 @@
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
### SSL settings
# enable SSL, with file certificate.
ssl_enabled=true
ssl_ca_key=simple.key
ssl_ca_crt=simple.crt

View File

@@ -18,12 +18,17 @@
<library name="encoder" location="..\..\text\encoder\encoder-safe.ecf"/>
<library name="http" location="..\protocol\http\http-safe.ecf"/>
<library name="http_auth" location="..\..\server\authentication\http_authorization\http_authorization-safe.ecf"/>
<library name="http_network" location="..\http_network\http_network-safe.ecf"/>
<library name="net" location="$ISE_LIBRARY\library\net\net-safe.ecf"/>
<library name="net_ssl" location="$ISE_LIBRARY\unstable\library\network\socket\netssl\net_ssl-safe.ecf">
<condition>
<custom name="net_http_client_disabled" excluded_value="true"/>
<custom name="netssl_http_client_enabled" value="true"/>
</condition>
<condition>
<custom name="net_http_client_disabled" excluded_value="true"/>
<custom name="ssl_enabled" value="true"/>
</condition>
</library>
<library name="uri" location="$ISE_LIBRARY\library\text\uri\uri-safe.ecf"/>
<cluster name="src" location=".\src\">
@@ -37,18 +42,6 @@
<custom name="net_http_client_disabled" excluded_value="true"/>
</condition>
</cluster>
<cluster name="net_ssl_disabled" location="$|no_ssl\">
<condition>
<custom name="net_http_client_disabled" excluded_value="true"/>
<custom name="netssl_http_client_enabled" excluded_value="true"/>
</condition>
</cluster>
<cluster name="net_ssl_enabled" location="$|ssl\">
<condition>
<custom name="net_http_client_disabled" excluded_value="true"/>
<custom name="netssl_http_client_enabled" value="true"/>
</condition>
</cluster>
</cluster>
<cluster name="spec_libcurl" location="$|spec\libcurl\" recursive="true">
<condition>

View File

@@ -18,12 +18,17 @@
<library name="encoder" location="..\..\text\encoder\encoder.ecf"/>
<library name="http" location="..\protocol\http\http.ecf"/>
<library name="http_auth" location="..\..\server\authentication\http_authorization\http_authorization.ecf"/>
<library name="http_network" location="..\http_network\http_network.ecf"/>
<library name="net" location="$ISE_LIBRARY\library\net\net.ecf"/>
<library name="net_ssl" location="$ISE_LIBRARY\unstable\library\network\socket\netssl\net_ssl.ecf">
<condition>
<custom name="net_http_client_disabled" excluded_value="true"/>
<custom name="netssl_http_client_enabled" value="true"/>
</condition>
<condition>
<custom name="net_http_client_disabled" excluded_value="true"/>
<custom name="ssl_enabled" value="true"/>
</condition>
</library>
<library name="uri" location="$ISE_LIBRARY\library\text\uri\uri.ecf"/>
<cluster name="src" location=".\src\">
@@ -37,18 +42,6 @@
<custom name="net_http_client_disabled" excluded_value="true"/>
</condition>
</cluster>
<cluster name="net_ssl_disabled" location="$|no_ssl\">
<condition>
<custom name="net_http_client_disabled" excluded_value="true"/>
<custom name="netssl_http_client_enabled" excluded_value="true"/>
</condition>
</cluster>
<cluster name="net_ssl_enabled" location="$|ssl\">
<condition>
<custom name="net_http_client_disabled" excluded_value="true"/>
<custom name="netssl_http_client_enabled" value="true"/>
</condition>
</cluster>
</cluster>
<cluster name="spec_libcurl" location="$|spec\libcurl\" recursive="true">
<condition>

View File

@@ -13,8 +13,12 @@
<library name="encoder" location="..\..\text\encoder\encoder-safe.ecf"/>
<library name="http" location="..\protocol\http\http-safe.ecf"/>
<library name="http_auth" location="..\..\server\authentication\http_authorization\http_authorization-safe.ecf"/>
<library name="http_network" location="..\http_network\http_network-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_enabled" value="true"/>
</condition>
<condition>
<custom name="netssl_http_client_enabled" value="true"/>
</condition>
@@ -23,16 +27,6 @@
<cluster name="src" location=".\src\">
<cluster name="spec_net" location="$|spec\net\">
<cluster name="net_implementation" location="$|implementation\" hidden="true"/>
<cluster name="net_ssl_disabled" location="$|no_ssl\">
<condition>
<custom name="netssl_http_client_enabled" excluded_value="true"/>
</condition>
</cluster>
<cluster name="net_ssl_enabled" location="$|ssl\">
<condition>
<custom name="netssl_http_client_enabled" value="true"/>
</condition>
</cluster>
</cluster>
<cluster name="default_net" location="$|default\net\"/>
</cluster>

View File

@@ -13,8 +13,12 @@
<library name="encoder" location="..\..\text\encoder\encoder.ecf"/>
<library name="http" location="..\protocol\http\http.ecf"/>
<library name="http_auth" location="..\..\server\authentication\http_authorization\http_authorization.ecf"/>
<library name="http_network" location="..\http_network\http_network.ecf"/>
<library name="net" location="$ISE_LIBRARY\library\net\net.ecf"/>
<library name="net_ssl" location="$ISE_LIBRARY\unstable\library\network\socket\netssl\net_ssl.ecf">
<condition>
<custom name="ssl_enabled" value="true"/>
</condition>
<condition>
<custom name="netssl_http_client_enabled" value="true"/>
</condition>
@@ -23,16 +27,6 @@
<cluster name="src" location=".\src\">
<cluster name="spec_net" location="$|spec\net\">
<cluster name="net_implementation" location="$|implementation\" hidden="true"/>
<cluster name="net_ssl_disabled" location="$|no_ssl\">
<condition>
<custom name="netssl_http_client_enabled" excluded_value="true"/>
</condition>
</cluster>
<cluster name="net_ssl_enabled" location="$|ssl\">
<condition>
<custom name="netssl_http_client_enabled" value="true"/>
</condition>
</cluster>
</cluster>
<cluster name="default_net" location="$|default\net\"/>
</cluster>

View File

@@ -164,11 +164,24 @@ feature -- Settings
Result := session.ignore_content_length
end
buffer_size: NATURAL
-- Set the buffer size for request. This option will
-- only be set if buffer_size is positive
buffer_size: INTEGER
-- Buffer size for request,
-- initialized from the session buffer_size value, or default 2_048.
do
Result := session.buffer_size
Result := session.buffer_size.to_integer_32
if Result <= 0 then
Result := 2_048
end
end
chunk_size: INTEGER
-- Chunk size for request, when "Transfer-Encoding: chunked"
-- initialized from the session buffer_size value, or default 2_048.
do
Result := session.chunk_size.to_integer_32
if Result <= 0 then
Result := 2_048
end
end
default_response_charset: detachable READABLE_STRING_8
@@ -249,7 +262,7 @@ feature {NONE} -- Utilities: encoding
end
note
copyright: "2011-2015, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
copyright: "2011-2016, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
source: "[
Eiffel Software

View File

@@ -243,9 +243,13 @@ feature -- Settings
ignore_content_length: BOOLEAN
-- Does this session ignore Content-Size headers?
buffer_size: NATURAL
buffer_size: NATURAL assign set_buffer_size
-- Set the buffer size for request. This option will
-- only be set if buffer_size is positive
-- only be set if buffer_size > 0.
chunk_size: NATURAL assign set_chunk_size
-- Set the chunk size for request, when "Transfer-Encoding: chunked".
-- This option will only be set if chunk_size > 0.
default_response_charset: detachable READABLE_STRING_8
-- Default encoding of responses. Used if no charset is provided by the host.
@@ -405,8 +409,18 @@ feature -- Element change
end
end
set_buffer_size (a_size: like buffer_size)
do
buffer_size := a_size
end
set_chunk_size (a_size: like chunk_size)
do
chunk_size := a_size
end
note
copyright: "2011-2015, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
copyright: "2011-2016, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
source: "[
Eiffel Software

View File

@@ -11,7 +11,7 @@ create
feature {NONE} -- Initialization
make (a_socket: NETWORK_STREAM_SOCKET; a_host: READABLE_STRING_GENERAL; a_port: INTEGER)
make (a_socket: HTTP_STREAM_SOCKET; a_host: READABLE_STRING_GENERAL; a_port: INTEGER)
do
socket := a_socket
host := a_host
@@ -20,7 +20,7 @@ feature {NONE} -- Initialization
feature -- Access
socket: NETWORK_STREAM_SOCKET
socket: HTTP_STREAM_SOCKET
-- Persistent connection socket.
host: READABLE_STRING_GENERAL
@@ -40,7 +40,7 @@ feature -- Status report
end
note
copyright: "2011-2015, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
copyright: "2011-2016, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
source: "[
Eiffel Software

View File

@@ -28,11 +28,11 @@ feature {NONE} -- Internal
session: NET_HTTP_CLIENT_SESSION
net_http_client_version: STRING = "0.1"
session_socket (a_host: READABLE_STRING_8; a_port: INTEGER; a_is_https: BOOLEAN; ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT): NETWORK_STREAM_SOCKET
session_socket (a_host: READABLE_STRING_8; a_port: INTEGER; a_is_https: BOOLEAN; ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT): HTTP_STREAM_SOCKET
-- Session socket to use for connection.
-- Eventually reuse the persistent connection if any.
local
l_socket: detachable NETWORK_STREAM_SOCKET
l_socket: detachable HTTP_STREAM_SOCKET
do
if
attached session.persistent_connection as l_persistent_connection and then
@@ -40,12 +40,12 @@ feature {NONE} -- Internal
then
l_socket := l_persistent_connection.socket
if a_is_https then
if attached {SSL_NETWORK_STREAM_SOCKET} l_socket as l_ssl_socket then
if attached {HTTP_STREAM_SECURE_SOCKET} l_socket as l_ssl_socket then
Result := l_ssl_socket
else
l_socket := Void
end
elseif attached {SSL_NETWORK_STREAM_SOCKET} l_socket as l_ssl_socket then
elseif attached {HTTP_STREAM_SECURE_SOCKET} l_socket as l_ssl_socket then
l_socket := Void
end
if l_socket /= Void and then not l_socket.is_connected then
@@ -59,7 +59,7 @@ feature {NONE} -- Internal
else
session.set_persistent_connection (Void)
if a_is_https then
create {SSL_NETWORK_STREAM_SOCKET} Result.make_client_by_port (a_port, a_host)
create {HTTP_STREAM_SECURE_SOCKET} Result.make_client_by_port (a_port, a_host)
else
create Result.make_client_by_port (a_port, a_host)
end
@@ -81,7 +81,7 @@ feature -- Access
l_cookie: detachable READABLE_STRING_8
l_request_uri: STRING
l_url: HTTP_URL
l_socket: NETWORK_STREAM_SOCKET
l_socket: HTTP_STREAM_SOCKET
s: STRING
l_message: STRING
l_content_length: INTEGER
@@ -100,6 +100,7 @@ feature -- Access
l_boundary: READABLE_STRING_8
l_is_http_1_0_request: BOOLEAN
l_is_keep_alive: BOOLEAN
l_is_chunked_transfer_encoding: BOOLEAN
retried: BOOLEAN
do
if not retried then
@@ -179,6 +180,8 @@ feature -- Access
end
-- handle sending data
l_is_chunked_transfer_encoding := attached headers.item ("Transfer-Encoding") as l_transfer_encoding and then l_transfer_encoding.same_string ("chunked")
if ctx /= Void then
if ctx.has_upload_filename then
l_upload_filename := ctx.upload_filename
@@ -195,14 +198,21 @@ feature -- Access
headers.extend ("application/x-www-form-urlencoded", "Content-Type")
l_upload_data := ctx.form_parameters_to_url_encoded_string
headers.force (l_upload_data.count.out, "Content-Length")
else
if l_is_chunked_transfer_encoding then
-- Discard chunked transfer encoding
headers.remove ("Transfer-Encoding")
l_is_chunked_transfer_encoding := False
end
elseif l_form_data /= Void then
-- create form using multipart/form-data encoding
l_boundary := new_mime_boundary
l_boundary := new_mime_boundary (l_form_data)
headers.extend ("multipart/form-data; boundary=" + l_boundary, "Content-Type")
if l_form_data /= Void then
l_upload_data := form_date_and_uploaded_files_to_mime_string (l_form_data, l_upload_filename, l_boundary)
headers.extend (l_upload_data.count.out, "Content-Length")
if l_is_chunked_transfer_encoding then
-- Discard chunked transfer encoding
headers.remove ("Transfer-Encoding")
l_is_chunked_transfer_encoding := False
end
end
elseif l_upload_data /= Void then
@@ -210,13 +220,17 @@ feature -- Access
if not headers.has ("Content-Type") then
headers.extend ("application/x-www-form-urlencoded", "Content-Type")
end
if not l_is_chunked_transfer_encoding then
headers.extend (l_upload_data.count.out, "Content-Length")
end
elseif l_upload_filename /= Void then
check ctx.has_upload_filename end
create l_upload_file.make_with_name (l_upload_filename)
if l_upload_file.exists and then l_upload_file.readable then
if not l_is_chunked_transfer_encoding then
headers.extend (l_upload_file.count.out, "Content-Length")
end
end
check l_upload_file /= Void end
end
end
@@ -289,12 +303,7 @@ feature -- Access
--| End of client header.
s.append (Http_end_of_header_line)
if l_upload_data /= Void then
s.append (l_upload_data)
s.append (http_end_of_header_line)
end
--| Note that any remaining file to upload will be done directly via the socket
--| Note that any remaining data or file to upload will be done directly via the socket
--| to optimize memory usage
@@ -315,10 +324,21 @@ feature -- Access
end
l_socket.put_string (s)
--| Send remaining payload data, if needed.
if l_upload_data /= Void then
if l_is_chunked_transfer_encoding then
put_string_using_chunked_transfer_encoding (l_upload_data, chunk_size, l_socket)
else
l_socket.put_string (l_upload_data)
end
end
if l_upload_file /= Void then
if l_is_chunked_transfer_encoding then
-- i.e: not yet processed
append_file_content_to_socket_using_chunked_transfer_encoding (l_upload_file, l_upload_file.count, chunk_size, l_socket)
else
append_file_content_to_socket (l_upload_file, l_upload_file.count, l_socket)
end
end
--|-------------------------|--
--| Get response. |--
@@ -418,7 +438,7 @@ feature {NONE} -- Helpers
io.error.put_string (m)
end
is_ready_for_reading (a_socket: NETWORK_STREAM_SOCKET): BOOLEAN
is_ready_for_reading (a_socket: HTTP_STREAM_SOCKET): BOOLEAN
-- Is `a_socket' ready for reading?
do
Result := a_socket.ready_for_reading
@@ -498,8 +518,85 @@ feature {NONE} -- Helpers
Result := utf.utf_32_string_to_utf_8_string_8 (s)
end
append_file_content_to_socket (a_file: FILE; a_len: INTEGER; a_output: NETWORK_STREAM_SOCKET)
-- Append `a_file' content to `a_output'.
put_string_using_chunked_transfer_encoding (a_string: READABLE_STRING_8; a_chunk_size: INTEGER; a_output: HTTP_STREAM_SOCKET)
local
i,n: INTEGER
do
from
i := 1
n := a_string.count
until
i > n
loop
put_chunk (a_string.substring (i, i + a_chunk_size), Void, a_output)
i := i + a_chunk_size
end
put_chunk_end (Void, Void, a_output)
end
put_chunk (a_content: READABLE_STRING_8; a_ext: detachable READABLE_STRING_8; a_output: HTTP_STREAM_SOCKET)
-- Write chunk non empty `a_content' to `a_output'
-- with optional extension `a_ext': chunk-extension= *( ";" chunk-ext-name [ "=" chunk-ext-val ] )
-- Note: that header "Transfer-Encoding: chunked" is required.
require
a_content_not_empty: a_content /= Void and then not a_content.is_empty
valid_chunk_extension: (a_ext /= Void and then not a_ext.is_empty) implies
( a_ext.starts_with (";") and not a_ext.has ('%N') and not not a_ext.has ('%R') )
local
l_chunk_size_line: STRING_8
i: INTEGER
do
--| Remove all left '0'
l_chunk_size_line := a_content.count.to_hex_string
from
i := 1
until
l_chunk_size_line[i] /= '0'
loop
i := i + 1
end
if i > 1 then
l_chunk_size_line := l_chunk_size_line.substring (i, l_chunk_size_line.count)
end
if a_ext /= Void then
l_chunk_size_line.append (a_ext)
end
l_chunk_size_line.append (crlf)
a_output.put_string (l_chunk_size_line)
a_output.put_string (a_content)
a_output.put_string (crlf)
end
put_chunk_end (a_ext: detachable READABLE_STRING_8; a_trailer: detachable READABLE_STRING_8; a_output: HTTP_STREAM_SOCKET)
-- Put end of chunked content,
-- with optional extension `a_ext': chunk-extension= *( ";" chunk-ext-name [ "=" chunk-ext-val ] )
-- and with optional trailer `a_trailer' : trailer= *(entity-header CRLF)
local
l_chunk_size_line: STRING_8
do
-- Chunk end
create l_chunk_size_line.make (1)
l_chunk_size_line.append_integer (0)
if a_ext /= Void then
l_chunk_size_line.append (a_ext)
end
l_chunk_size_line.append (crlf)
a_output.put_string (l_chunk_size_line)
-- Optional trailer
if a_trailer /= Void and then not a_trailer.is_empty then
a_output.put_string (a_trailer)
end
-- Final CRLF
a_output.put_string (crlf)
end
append_file_content_to_socket_using_chunked_transfer_encoding (a_file: FILE; a_len: INTEGER; a_chunk_size: INTEGER; a_output: HTTP_STREAM_SOCKET)
-- Append `a_file' content as chunks of `a_chunk_size' length to `a_output'.
-- If `a_len' >= 0 then read only `a_len' characters.
require
a_file_readable: a_file.exists and then a_file.is_access_readable
@@ -523,7 +620,44 @@ feature {NONE} -- Helpers
until
l_count = 0 or a_file.exhausted
loop
a_file.read_stream_thread_aware (l_count.min (2_048))
a_file.read_stream_thread_aware (l_count.min (a_chunk_size))
put_chunk (a_file.last_string, Void, a_output)
l_count := l_count - a_file.bytes_read
end
if not l_was_open then
a_file.close
end
put_chunk_end (Void, Void, a_output)
end
end
append_file_content_to_socket (a_file: FILE; a_len: INTEGER; a_output: HTTP_STREAM_SOCKET)
-- Append `a_file' content to `a_output'.
-- If `a_len' >= 0 then read only `a_len' characters.
require
a_file_readable: a_file.exists and then a_file.is_access_readable
local
l_was_open: BOOLEAN
l_count, l_buffer_size: INTEGER
do
if a_len >= 0 then
l_count := a_len
else
l_count := a_file.count
end
if l_count > 0 then
l_was_open := a_file.is_open_read
if a_file.is_open_read then
l_was_open := True
else
a_file.open_read
end
from
l_buffer_size := buffer_size
until
l_count = 0 or a_file.exhausted
loop
a_file.read_stream_thread_aware (l_count.min (l_buffer_size))
a_output.put_string (a_file.last_string)
l_count := l_count - a_file.bytes_read
end
@@ -541,6 +675,7 @@ feature {NONE} -- Helpers
local
l_was_open: BOOLEAN
l_count: INTEGER
l_buffer_size: INTEGER
do
if a_len >= 0 then
l_count := a_len
@@ -555,11 +690,11 @@ feature {NONE} -- Helpers
a_file.open_read
end
from
l_buffer_size := buffer_size
until
l_count = 0 or a_file.exhausted
loop
a_file.read_stream_thread_aware (l_count.min (2_048))
a_file.read_stream_thread_aware (l_count.min (l_buffer_size))
a_output.append (a_file.last_string)
l_count := l_count - a_file.bytes_read
end
@@ -569,7 +704,7 @@ feature {NONE} -- Helpers
end
end
append_socket_header_content_to (a_response: HTTP_CLIENT_RESPONSE; a_socket: NETWORK_STREAM_SOCKET; a_output: STRING)
append_socket_header_content_to (a_response: HTTP_CLIENT_RESPONSE; a_socket: HTTP_STREAM_SOCKET; a_output: STRING)
-- Get header from `a_socket' into `a_output'.
local
s: READABLE_STRING_8
@@ -579,7 +714,7 @@ feature {NONE} -- Helpers
until
s.same_string ("%R") or not a_socket.readable or a_response.error_occurred
loop
a_socket.read_line_thread_aware
a_socket.read_line_noexception
s := a_socket.last_string
if s.is_empty then
if session.is_debug_verbose then
@@ -595,7 +730,7 @@ feature {NONE} -- Helpers
end
end
append_socket_content_to (a_response: HTTP_CLIENT_RESPONSE; a_socket: NETWORK_STREAM_SOCKET; a_len: INTEGER; a_output: STRING)
append_socket_content_to (a_response: HTTP_CLIENT_RESPONSE; a_socket: HTTP_STREAM_SOCKET; a_len: INTEGER; a_output: STRING)
-- Get content from `a_socket' and append it to `a_output'.
-- If `a_len' is negative, try to get as much as possible,
-- this is probably HTTP/1.0 without any Content-Length.
@@ -614,7 +749,7 @@ feature {NONE} -- Helpers
until
r = 0 or else not a_socket.readable or else a_response.error_occurred
loop
a_socket.read_stream_thread_aware (r)
a_socket.read_stream_noexception (r)
l_count := l_count + a_socket.bytes_read
if session.is_debug_verbose then
log ("Debug: - byte read=" + a_socket.bytes_read.out + "%N")
@@ -632,12 +767,12 @@ feature {NONE} -- Helpers
-- FIXME: check solution!
from
l_count := 0
l_chunk_size := 1_024
l_chunk_size := buffer_size
n := l_chunk_size --| value to satisfy until condition on first loop.
until
n < l_chunk_size or not a_socket.readable
loop
a_socket.read_stream_thread_aware (l_chunk_size)
a_socket.read_stream_noexception (l_chunk_size)
s := a_socket.last_string
n := a_socket.bytes_read
l_count := l_count + n
@@ -647,7 +782,7 @@ feature {NONE} -- Helpers
end
end
append_socket_chunked_content_to (a_response: HTTP_CLIENT_RESPONSE; a_socket: NETWORK_STREAM_SOCKET; a_output: STRING)
append_socket_chunked_content_to (a_response: HTTP_CLIENT_RESPONSE; a_socket: HTTP_STREAM_SOCKET; a_output: STRING)
-- Get chunked content from `a_socket' and append it to `a_output'.
require
socket_readable: a_socket.readable
@@ -668,7 +803,7 @@ feature {NONE} -- Helpers
until
n = 0 or not a_socket.readable
loop
a_socket.read_line_thread_aware -- Read chunk info
a_socket.read_line_noexception
s := a_socket.last_string
s.right_adjust
if session.is_debug_verbose then
@@ -697,7 +832,7 @@ feature {NONE} -- Helpers
until
r = 0 or else not a_socket.readable or else a_response.error_occurred
loop
a_socket.read_stream_thread_aware (r)
a_socket.read_stream_noexception (r)
l_count := l_count + a_socket.bytes_read
if session.is_debug_verbose then
log ("Debug: - byte read=" + a_socket.bytes_read.out + "%N")
@@ -707,9 +842,9 @@ feature {NONE} -- Helpers
a_output.append (a_socket.last_string)
end
a_socket.read_character
a_socket.read_character_noexception
check a_socket.last_character = '%R' end
a_socket.read_character
a_socket.read_character_noexception
check a_socket.last_character = '%N' end
if session.is_debug_verbose then
log ("Debug: - Found CRNL %N")
@@ -718,16 +853,42 @@ feature {NONE} -- Helpers
end
end
new_mime_boundary: STRING
new_mime_boundary (a_data: HASH_TABLE [READABLE_STRING_32, READABLE_STRING_32]): STRING
-- New MIME boundary.
local
s: STRING
ran: RANDOM
n: INTEGER
i,j: INTEGER
do
-- FIXME: better boundary creation
Result := "----------------------------5eadfcf3bb3e"
across
a_data as ic
loop
i := i + ic.item.count + ic.key.count
end
create ran.set_seed (i) -- FIXME: use a real random seed.
ran.start
ran.forth
n := (20 * ran.real_item).truncated_to_integer
create Result.make_filled ('-', 3 + n)
s := "_1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
from
until
Result.count >= 40
loop
ran.forth
j := (ran.real_item * s.count).truncated_to_integer.max (1)
Result.append_character (s[j])
end
check Result.count = 40 and Result.starts_with ("---") end
end
crlf: STRING = "%R%N"
-- CR and NL sequence.
invariant
note
copyright: "2011-2015, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
copyright: "2011-2016, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
source: "[
Eiffel Software

View File

@@ -14,7 +14,7 @@ inherit
close
end
NET_HTTP_CLIENT_INFO
HTTP_SECURE_HELPER
create
make
@@ -32,7 +32,7 @@ feature -- Status report
do
Result := True
if base_url.starts_with_general ("https://") then
Result := has_https_support
Result := is_secure_connection_supported
end
end
@@ -172,7 +172,7 @@ feature {NONE} -- Implementation
end
note
copyright: "2011-2015, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
copyright: "2011-2016, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
source: "[
Eiffel Software

View File

@@ -1,22 +0,0 @@
note
description: "[
A fake SSL network stream socket... when SSL is disabled at compilation time.
Its behavior is similar to NETWORK_STREAM_SOCKET.
]"
date: "$Date$"
revision: "$Revision$"
class
SSL_NETWORK_STREAM_SOCKET
inherit
NETWORK_STREAM_SOCKET
create
make, make_empty, make_client_by_port, make_client_by_address_and_port, make_server_by_port, make_loopback_server_by_port
create {SSL_NETWORK_STREAM_SOCKET}
make_from_descriptor_and_address, create_from_descriptor
end

View File

@@ -1,24 +0,0 @@
note
description: "Additional information related to NET HTTP Client.."
date: "$Date$"
revision: "$Revision$"
class
NET_HTTP_CLIENT_INFO
feature -- Access
has_https_support: BOOLEAN = True
-- Is HTTPS supported?
note
copyright: "2011-2015, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
source: "[
Eiffel Software
5949 Hollister Ave., Goleta, CA 93117 USA
Telephone 805-685-1006, Fax 805-685-6869
Website http://www.eiffel.com
Customer support http://support.eiffel.com
]"
end

View File

@@ -64,4 +64,9 @@ feature -- Tests
test_get_with_authentication
end
net_test_post_with_file_using_chunked_transfer_encoding
do
test_post_with_file_using_chunked_transfer_encoding
end
end

View File

@@ -253,6 +253,38 @@ feature -- Factory
end
end
test_post_with_file_using_chunked_transfer_encoding
local
sess: HTTP_CLIENT_SESSION
h: STRING_8
l_ctx: HTTP_CLIENT_REQUEST_CONTEXT
do
if attached global_requestbin_path as requestbin_path then
-- POST REQUEST WITH A FILE AND FORM DATA
-- check requestbin to ensure the file and form parameters are correctly received
-- set filename to a local file
sess := new_session ("http://requestb.in")
create l_ctx.make
l_ctx.add_header ("Transfer-Encoding", "chunked")
l_ctx.set_upload_filename ("logo.jpg")
create h.make_empty
if
attached sess.post (requestbin_path, l_ctx, Void) as res and then
attached res.headers as hds
then
across
hds as c
loop
h.append (c.item.name + ": " + c.item.value + "%R%N")
end
end
print (h)
else
assert ("Has requestbin path", False)
end
end
test_get_with_redirection
local
sess: HTTP_CLIENT_SESSION

View File

@@ -0,0 +1,123 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-15-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-15-0 http://www.eiffel.com/developers/xml/configuration-1-15-0.xsd" name="http_network" uuid="7C7AD84D-B7BD-4709-B4B2-9365B86582AE" library_target="http_network">
<target name="http_network">
<root all_classes="true"/>
<file_rule>
<exclude>/.git$</exclude>
<exclude>/.svn$</exclude>
<exclude>/EIFGENs$</exclude>
</file_rule>
<option warning="true" full_class_checking="false" is_attached_by_default="true" void_safety="all" syntax="standard">
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
</option>
<setting name="concurrency" value="scoop"/>
<external_include location="$ECF_CONFIG_PATH/spec/include">
<condition>
<version type="compiler" min="16.11.0.0"/>
</condition>
</external_include>
<external_include location="$ECF_CONFIG_PATH/spec/include_until_16_05">
<condition>
<version type="compiler" max="16.11.0.0"/>
</condition>
</external_include>
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
<library name="net" location="$ISE_LIBRARY\library\net\net-safe.ecf"/>
<library name="net_ssl" location="$ISE_LIBRARY\unstable\library\network\socket\netssl\net_ssl-safe.ecf">
<condition>
<custom name="ssl_enabled" value="true"/>
</condition>
<condition>
<custom name="net_ssl_enabled" value="true"/>
</condition>
<condition>
<custom name="httpd_ssl_enabled" value="true"/>
</condition>
</library>
<external_include location="$(ISE_LIBRARY)/unstable/library/network/socket/netssl/spec/include">
<condition>
<platform excluded_value="windows"/>
<custom name="ssl_enabled" value="true"/>
<version type="compiler" max="16.11.0.0"/>
</condition>
</external_include>
<external_include location="$(ISE_LIBRARY)\unstable\library\network\socket\netssl\spec\include">
<condition>
<platform value="windows"/>
<custom name="ssl_enabled" value="true"/>
<version type="compiler" max="16.11.0.0"/>
</condition>
</external_include>
<external_cflag value="-D_WINSOCKAPI_">
<condition>
<platform value="windows"/>
<custom name="ssl_enabled" value="true"/>
<version type="compiler" max="16.11.0.0"/>
</condition>
</external_cflag>
<cluster name="network" location=".\src\">
<file_rule>
<exclude>/http_stream_socket_ext.e$</exclude>
<condition>
<version type="compiler" max="16.11.0.0"/>
</condition>
</file_rule>
<cluster name="disabled_ssl_network" location="$|no_ssl\" recursive="true">
<condition>
<custom name="ssl_enabled" excluded_value="true"/>
<custom name="net_ssl_enabled" excluded_value="true"/>
<custom name="httpd_ssl_enabled" excluded_value="true"/>
</condition>
</cluster>
<cluster name="ssl_network" location="$|ssl\" recursive="true">
<condition>
<custom name="ssl_enabled" value="true"/>
</condition>
<condition>
<custom name="net_ssl_enabled" value="true"/>
</condition>
<condition>
<custom name="httpd_ssl_enabled" value="true"/>
</condition>
<file_rule>
<exclude>/http_stream_secure_socket_ext.e$</exclude>
<condition>
<version type="compiler" max="16.11.0.0"/>
<custom name="ssl_enabled" value="true"/>
</condition>
<condition>
<version type="compiler" max="16.11.0.0"/>
<custom name="net_ssl_enabled" value="true"/>
</condition>
<condition>
<version type="compiler" max="16.11.0.0"/>
<custom name="httpd_ssl_enabled" value="true"/>
</condition>
</file_rule>
</cluster>
</cluster>
<cluster name="network_until_16_05" location=".\src\until_16_05\" recursive="false">
<condition>
<version type="compiler" max="16.11.0.0"/>
</condition>
<cluster name="ssl_network_until_16_05" location="$|ssl\" recursive="true">
<condition>
<custom name="ssl_enabled" value="true"/>
<version type="compiler" max="16.11.0.0"/>
</condition>
<condition>
<custom name="net_ssl_enabled" value="true"/>
<version type="compiler" max="16.11.0.0"/>
</condition>
<condition>
<custom name="httpd_ssl_enabled" value="true"/>
<version type="compiler" max="16.11.0.0"/>
</condition>
</cluster>
</cluster>
</target>
<target name="http_network_ssl" extends="http_network">
<variable name="ssl_enabled" value="true" />
</target>
</system>

View File

@@ -0,0 +1,123 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-15-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-15-0 http://www.eiffel.com/developers/xml/configuration-1-15-0.xsd" name="http_network" uuid="7C7AD84D-B7BD-4709-B4B2-9365B86582AE" library_target="http_network">
<target name="http_network">
<root all_classes="true"/>
<file_rule>
<exclude>/.git$</exclude>
<exclude>/.svn$</exclude>
<exclude>/EIFGENs$</exclude>
</file_rule>
<option warning="true" full_class_checking="false" void_safety="none" syntax="standard">
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
</option>
<setting name="concurrency" value="scoop"/>
<external_include location="$ECF_CONFIG_PATH/spec/include">
<condition>
<version type="compiler" min="16.11.0.0"/>
</condition>
</external_include>
<external_include location="$ECF_CONFIG_PATH/spec/include_until_16_05">
<condition>
<version type="compiler" max="16.11.0.0"/>
</condition>
</external_include>
<library name="base" location="$ISE_LIBRARY\library\base\base.ecf"/>
<library name="net" location="$ISE_LIBRARY\library\net\net.ecf"/>
<library name="net_ssl" location="$ISE_LIBRARY\unstable\library\network\socket\netssl\net_ssl.ecf">
<condition>
<custom name="ssl_enabled" value="true"/>
</condition>
<condition>
<custom name="net_ssl_enabled" value="true"/>
</condition>
<condition>
<custom name="httpd_ssl_enabled" value="true"/>
</condition>
</library>
<external_include location="$(ISE_LIBRARY)/unstable/library/network/socket/netssl/spec/include">
<condition>
<platform excluded_value="windows"/>
<custom name="ssl_enabled" value="true"/>
<version type="compiler" max="16.11.0.0"/>
</condition>
</external_include>
<external_include location="$(ISE_LIBRARY)\unstable\library\network\socket\netssl\spec\include">
<condition>
<platform value="windows"/>
<custom name="ssl_enabled" value="true"/>
<version type="compiler" max="16.11.0.0"/>
</condition>
</external_include>
<external_cflag value="-D_WINSOCKAPI_">
<condition>
<platform value="windows"/>
<custom name="ssl_enabled" value="true"/>
<version type="compiler" max="16.11.0.0"/>
</condition>
</external_cflag>
<cluster name="network" location=".\src\">
<file_rule>
<exclude>/http_stream_socket_ext.e$</exclude>
<condition>
<version type="compiler" max="16.11.0.0"/>
</condition>
</file_rule>
<cluster name="disabled_ssl_network" location="$|no_ssl\" recursive="true">
<condition>
<custom name="ssl_enabled" excluded_value="true"/>
<custom name="net_ssl_enabled" excluded_value="true"/>
<custom name="httpd_ssl_enabled" excluded_value="true"/>
</condition>
</cluster>
<cluster name="ssl_network" location="$|ssl\" recursive="true">
<condition>
<custom name="ssl_enabled" value="true"/>
</condition>
<condition>
<custom name="net_ssl_enabled" value="true"/>
</condition>
<condition>
<custom name="httpd_ssl_enabled" value="true"/>
</condition>
<file_rule>
<exclude>/http_stream_secure_socket_ext.e$</exclude>
<condition>
<version type="compiler" max="16.11.0.0"/>
<custom name="ssl_enabled" value="true"/>
</condition>
<condition>
<version type="compiler" max="16.11.0.0"/>
<custom name="net_ssl_enabled" value="true"/>
</condition>
<condition>
<version type="compiler" max="16.11.0.0"/>
<custom name="httpd_ssl_enabled" value="true"/>
</condition>
</file_rule>
</cluster>
</cluster>
<cluster name="network_until_16_05" location=".\src\until_16_05\" recursive="false">
<condition>
<version type="compiler" max="16.11.0.0"/>
</condition>
<cluster name="ssl_network_until_16_05" location="$|ssl\" recursive="true">
<condition>
<custom name="ssl_enabled" value="true"/>
<version type="compiler" max="16.11.0.0"/>
</condition>
<condition>
<custom name="net_ssl_enabled" value="true"/>
<version type="compiler" max="16.11.0.0"/>
</condition>
<condition>
<custom name="httpd_ssl_enabled" value="true"/>
<version type="compiler" max="16.11.0.0"/>
</condition>
</cluster>
</cluster>
</target>
<target name="http_network_ssl" extends="http_network">
<variable name="ssl_enabled" value="true" />
</target>
</system>

View File

@@ -0,0 +1,10 @@
${NOTE_KEYWORD}
copyright: "2011-${YEAR}, Jocelyn Fiat, Javier Velilla, Olivier Ligot, Colin Adams, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
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
]"

View File

@@ -1,6 +1,6 @@
/*
indexing
description: "Functions used by the EiffelWeb httpd networking classes. "
description: "Functions used by the EiffelWeb http networking classes. "
copyright: "Copyright (c) 2011-2016, Jocelyn Fiat, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
source: "[
@@ -12,8 +12,8 @@ indexing
]"
*/
#ifndef _ew_httpd_net_h_
#define _ew_httpd_net_h_
#ifndef _ew_network_h_
#define _ew_network_h_
#include "eif_config.h"

View File

@@ -1,15 +1,15 @@
note
description: "Summary description for {HTTPD_STREAM_SOCKET}."
description: "Summary description for {HTTP_STREAM_SOCKET}."
date: "$Date$"
revision: "$Revision$"
class
HTTPD_STREAM_SOCKET
HTTP_STREAM_SOCKET
inherit
NETWORK_STREAM_SOCKET
HTTPD_STREAM_SOCKET_EXT
HTTP_STREAM_SOCKET_EXT
create
make, make_empty,
@@ -19,6 +19,16 @@ create
create {NETWORK_STREAM_SOCKET}
make_from_descriptor_and_address
feature -- Status report
is_secure_connection_supported: BOOLEAN
-- SSL/TLS supported?
once
Result := False
ensure
Result = {HTTP_SECURE_HELPER}.is_secure_connection_supported
end
feature -- Input
read_character_noexception
@@ -27,7 +37,9 @@ feature -- Input
-- No exception raised!
do
read_to_managed_pointer_noexception (socket_buffer, 0, character_8_bytes)
if bytes_read /= character_8_bytes then
if was_error then
-- Socket error already set.
elseif bytes_read /= character_8_bytes then
socket_error := "Peer closed connection"
else
last_character := socket_buffer.read_character (0)
@@ -135,6 +147,8 @@ feature {NONE} -- Input
l_last_read := c_read_stream_noexception (descriptor, nb_bytes - l_read, p + start_pos + l_read)
if l_last_read >= 0 then
l_read := l_read + l_last_read
else
socket_error := "Network error!"
end
end
bytes_read := l_read
@@ -198,7 +212,7 @@ feature -- Output
put_managed_pointer_noexception (socket_buffer, 0, character_8_bytes)
end
put_readable_string_8_noexception (s: READABLE_STRING_8)
put_string_8_noexception (s: READABLE_STRING_8)
-- Write readable string `s' to socket.
-- No exception raised!
local
@@ -230,7 +244,7 @@ feature -- Status report
end
note
copyright: "2011-2015, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
copyright: "2011-2016, Jocelyn Fiat, Javier Velilla, Olivier Ligot, Colin Adams, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
source: "[
Eiffel Software

View File

@@ -6,7 +6,7 @@ note
]"
deferred class
HTTPD_STREAM_SOCKET_EXT
HTTP_STREAM_SOCKET_EXT
feature {NONE} -- No-Exception network operation

View File

@@ -0,0 +1,18 @@
note
description: "[
Interface helping using SSL.
For now, mainly for `is_secure_connection_supported' to indicate if current project is compiled with SSL support.
i.e compiled with EiffelNet-SSL library.
]"
date: "$Date$"
revision: "$Revision$"
class
HTTP_SECURE_HELPER
feature -- Status
is_secure_connection_supported: BOOLEAN = False
-- Is Current system compiled with EiffelNet-SSL support?
end

View File

@@ -0,0 +1,69 @@
note
description: "[
A fake SSL network stream socket... when SSL is disabled at compilation time.
Its behavior is similar to HTTP_STREAM_SOCKET.
]"
date: "$Date$"
revision: "$Revision$"
class
HTTP_STREAM_SECURE_SOCKET
inherit
HTTP_STREAM_SOCKET
create
make, make_empty,
make_client_by_port, make_client_by_address_and_port,
make_server_by_port, make_server_by_address_and_port, make_loopback_server_by_port
create {HTTP_STREAM_SECURE_SOCKET}
make_from_descriptor_and_address
feature -- Element change
set_certificate_file_path (a_crt_filename: PATH)
do
end
set_key_file_path (a_key_filename: PATH)
do
end
feature -- SSL Helpers
set_secure_protocol (v: NATURAL)
do
end
set_secure_protocol_to_ssl_2_or_3
-- Set `ssl_protocol' with `Ssl_23'.
do
end
set_secure_protocol_to_tls_1_0
-- Set `ssl_protocol' with `Tls_1_0'.
do
end
set_secure_protocol_to_tls_1_1
-- Set `ssl_protocol' with `Tls_1_1'.
do
end
set_secure_protocol_to_tls_1_2
-- Set `ssl_protocol' with `Tls_1_2'.
do
end
set_secure_protocol_to_dtls_1_0
-- Set `ssl_protocol' with `Dtls_1_0'.
do
end
invariant
secure_connection_not_supported: not is_secure_connection_supported -- Current is a Fake SSL interface!
note
copyright: "2011-2013, Javier Velilla, Jocelyn Fiat and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -0,0 +1,18 @@
note
description: "[
Interface helping using SSL.
For now, mainly for `is_secure_connection_supported' to indicate if current project is compiled with SSL support.
i.e compiled with EiffelNet-SSL library.
]"
date: "$Date$"
revision: "$Revision$"
class
HTTP_SECURE_HELPER
feature -- Status
is_secure_connection_supported: BOOLEAN = True
-- Is Current system compiled with EiffelNet-SSL support?
end

View File

@@ -4,10 +4,10 @@ note
revision: "$Revision$"
class
HTTPD_STREAM_SSL_SOCKET
HTTP_STREAM_SECURE_SOCKET
inherit
HTTPD_STREAM_SOCKET
HTTP_STREAM_SOCKET
undefine
make_empty, make_from_descriptor_and_address,
error_number,
@@ -20,6 +20,7 @@ inherit
connect, shutdown,
do_accept
redefine
is_secure_connection_supported,
put_managed_pointer,
read_stream_noexception,
read_into_pointer_noexception,
@@ -31,7 +32,7 @@ inherit
put_managed_pointer -- Redefine to allow support of compiler before 16.11.
end
HTTPD_STREAM_SSL_SOCKET_EXT
HTTP_STREAM_SECURE_SOCKET_EXT
create
make, make_empty,
@@ -41,6 +42,48 @@ create
create {SSL_NETWORK_STREAM_SOCKET}
make_from_descriptor_and_address
feature -- Status report
is_secure_connection_supported: BOOLEAN = True
-- SSL supported?
feature -- Secure connection Helpers
set_secure_protocol (v: NATURAL)
do
set_tls_protocol (v)
end
set_secure_protocol_to_ssl_2_or_3
-- Set `ssl_protocol' with `Ssl_23'.
do
set_secure_protocol ({SSL_PROTOCOL}.Ssl_23)
end
set_secure_protocol_to_tls_1_0
-- Set `ssl_protocol' with `Tls_1_0'.
do
set_secure_protocol ({SSL_PROTOCOL}.Tls_1_0)
end
set_secure_protocol_to_tls_1_1
-- Set `ssl_protocol' with `Tls_1_1'.
do
set_secure_protocol ({SSL_PROTOCOL}.Tls_1_1)
end
set_secure_protocol_to_tls_1_2
-- Set `ssl_protocol' with `Tls_1_2'.
do
set_secure_protocol ({SSL_PROTOCOL}.Tls_1_2)
end
set_secure_protocol_to_dtls_1_0
-- Set `ssl_protocol' with `Dtls_1_0'.
do
set_secure_protocol ({SSL_PROTOCOL}.Dtls_1_0)
end
feature -- Input
read_stream_noexception (nb_char: INTEGER)
@@ -91,6 +134,8 @@ feature {NONE} -- Input
l_last_read := l_ssl.read (p + start_pos + l_read, nb_bytes - l_read)
if l_last_read >= 0 then
l_read := l_read + l_last_read
else
socket_error := "Secure network error!"
end
end
bytes_read := l_read
@@ -105,7 +150,7 @@ feature -- Output
-- Put data of length `nb_bytes' pointed by `start_pos' index in `p' at
-- current position.
do
Precursor {HTTPD_STREAM_SOCKET} (p, start_pos, nb_bytes)
Precursor {HTTP_STREAM_SOCKET} (p, start_pos, nb_bytes)
end
put_pointer_content_noexception (a_pointer: POINTER; a_offset, a_byte_count: INTEGER)
@@ -130,18 +175,6 @@ feature -- Output
end
end
feature -- Element change
set_certificate_filenames (a_crt_filename, a_key_filename: detachable READABLE_STRING_GENERAL)
do
if a_crt_filename /= Void then
set_certificate_file_name (a_crt_filename)
end
if a_key_filename /= Void then
set_key_file_name (a_key_filename)
end
end
note
copyright: "2011-2013, Javier Velilla, Jocelyn Fiat and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"

View File

@@ -1,12 +1,12 @@
note
description: "[
Extension to HTTPD_STREAM_SOCKET to support backward compatibility.
Extension to HTTP_STREAM_SOCKET to support backward compatibility.
TO BE REMOVED IN THE FUTURE, WHEN 16.05 IS OLD.
]"
deferred class
HTTPD_STREAM_SSL_SOCKET_EXT
HTTP_STREAM_SECURE_SOCKET_EXT
feature {NONE} -- SSL bridge

View File

@@ -9,7 +9,7 @@ note
]"
deferred class
HTTPD_STREAM_SOCKET_EXT
HTTP_STREAM_SOCKET_EXT
inherit
PLATFORM
@@ -81,7 +81,7 @@ feature {NONE} -- Externals
c_set_sock_recv_timeout (a_fd, a_level: INTEGER; a_timeout_seconds: INTEGER)
-- C routine to set socket option `SO_RCVTIMEO' with `a_timeout_seconds' seconds.
external
"C inline use %"ew_httpd_net.h%""
"C inline use %"ew_network.h%""
alias
"[
#ifdef SO_RCVTIMEO
@@ -105,7 +105,7 @@ feature {NONE} -- Externals
c_set_sock_send_timeout (a_fd, a_level: INTEGER; a_timeout_seconds: INTEGER)
-- C routine to set socket option `SO_SNDTIMEO' with `a_timeout_seconds' seconds.
external
"C inline use %"ew_httpd_net.h%""
"C inline use %"ew_network.h%""
alias
"[
#ifdef SO_RCVTIMEO
@@ -131,7 +131,7 @@ feature {NONE} -- No-Exception network operation
-- External routine to read a `len' number of characters
-- into buffer `buf' from socket `a_fd' with options `flags'.
external
"C inline use %"ew_httpd_net.h%""
"C inline use %"ew_network.h%""
alias
"[
recv((int) $a_fd, (char *) $buf, (int) $len, (int) $flags)
@@ -150,7 +150,7 @@ feature {NONE} -- No-Exception network operation
-- length `length' to socket `fd'.
-- Note: does not raise exception on error, but return error value as Result.
external
"C inline use %"ew_httpd_net.h%""
"C inline use %"ew_network.h%""
alias
"[
send((int) $a_fd, (char *) $buf, (int) $len, (int) 0)

View File

@@ -1,12 +1,12 @@
note
description: "[
Extension to HTTPD_STREAM_SOCKET to support backward compatibility.
Extension to HTTP_STREAM_SOCKET to support backward compatibility.
TO BE REMOVED IN THE FUTURE, WHEN 16.05 IS OLD.
]"
deferred class
HTTPD_STREAM_SSL_SOCKET_EXT
HTTP_STREAM_SECURE_SOCKET_EXT
feature {NONE} -- SSL bridge

View File

@@ -0,0 +1,38 @@
note
description: "ws_client application root class"
date: "$Date$"
revision: "$Revision$"
class
APPLICATION
inherit
SHARED_EXECUTION_ENVIRONMENT
create
make
feature {NONE} -- Initialization
make
-- Run application.
local
ws_client: EXAMPLE_WS_CLIENT
do
-- create ws_client.make_with_port ("wss://echo.websocket.org", 443, Void)
-- if ws_client.is_ssl_supported and ws_client.is_tunneled then
-- ws_client.set_ssl_certificate_file ("ca.crt")
-- ws_client.set_ssl_key_file ("ca.key")
-- else
-- -- Not supported!
-- end
create ws_client.make_with_port ("ws://echo.websocket.org", 80, Void)
-- create ws_client.make_with_port ("ws://127.0.0.1", 9090, Void)
ws_client.launch
ws_client.join_all
execution_environment.sleep (5_000_000)
end
end

View File

@@ -0,0 +1,78 @@
note
description: "Summary description for {EXAMPLE_WS_CLIENT}."
author: ""
date: "$Date$"
revision: "$Revision$"
class
EXAMPLE_WS_CLIENT
inherit
WEB_SOCKET_CLIENT
create
make, make_with_port
feature -- Initialization
make (a_uri: STRING; a_protocols: detachable LIST [STRING])
do
initialize (a_uri, a_protocols)
create implementation.make (create {WEB_SOCKET_NULL_CLIENT}, a_uri)
end
make_with_port (a_uri: STRING; a_port: INTEGER; a_protocols: detachable LIST [STRING])
do
initialize_with_port (a_uri, a_port, a_protocols)
create implementation.make (create {WEB_SOCKET_NULL_CLIENT}, a_uri)
end
feature -- Access
count: INTEGER
feature -- Event
on_open (a_message: STRING)
do
print (a_message)
print ("%NProtocol:" + protocol)
on_text_message (a_message)
end
on_text_message (a_message: STRING)
local
l_message: STRING
do
if count <= 10 then
print ("%NMessage is %"" + a_message + "%".%N")
print ("%NCount:" + count.out)
create l_message.make_empty
l_message.append ("mesg#" + count.out)
send (l_message)
count := count + 1
else -- Send close initiated by the client
close (1001)
end
end
on_binary_message (a_message: STRING)
do
send_binary (a_message)
end
on_close (a_code: INTEGER; a_reason: STRING)
do
ready_state.set_state ({WEB_SOCKET_READY_STATE}.closed)
end
on_error (a_error: STRING)
do
end
connection: like socket
do
Result := socket
end
end

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-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="ws_client" uuid="AF6EDC56-D7B4-4E1F-A62B-40EBED3D93DF">
<target name="common">
<file_rule>
<exclude>/.git$</exclude>
<exclude>/.svn$</exclude>
<exclude>/CVS$</exclude>
<exclude>/EIFGENs$</exclude>
</file_rule>
<option warning="true" is_obsolete_routine_type="true" void_safety="transitional">
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
</option>
<setting name="console_application" value="true"/>
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
<library name="web_socket_client" location="..\..\web_socket_client-safe.ecf" readonly="false"/>
<cluster name="ws_client" location=".\" recursive="true"/>
</target>
<target name="ws_client" extends="common">
<root class="APPLICATION" feature="make"/>
<setting name="concurrency" value="thread"/>
</target>
<target name="ws_client_ssl" extends="ws_client">
<variable name="ssl_enabled" value="true"/>
</target>
</system>

View File

@@ -0,0 +1,29 @@
note
description: "[
API to perform actions like opening and closing the connection, sending and receiving messages, and listening
for events triggered by the server
]"
date: "$Date$"
revision: "$Revision$"
deferred class
WEB_SOCKET_CLIENT
inherit
WEB_SOCKET_CLIENT_I
feature -- Status report
is_secure_connection_supported: BOOLEAN = False
feature -- Factory
new_socket (a_port: INTEGER; a_host: STRING): HTTP_STREAM_SOCKET
do
if is_secure then
check is_secure_connection_supported: False end
end
create {HTTP_STREAM_SOCKET} Result.make_client_by_port (a_port, a_host)
end
end

View File

@@ -0,0 +1,54 @@
note
description: "[
API to perform actions like opening and closing the connection, sending and receiving messages, and listening
for events triggered by the server
]"
date: "$Date$"
revision: "$Revision$"
deferred class
WEB_SOCKET_CLIENT
inherit
WEB_SOCKET_CLIENT_I
feature -- Status report
is_secure_connection_supported: BOOLEAN = True
feature -- Factory
new_socket (a_port: INTEGER; a_host: STRING): HTTP_STREAM_SOCKET
local
l_secure: HTTP_STREAM_SECURE_SOCKET
do
if is_secure then
create l_secure.make_client_by_port (a_port, a_host)
Result := l_secure
if attached secure_protocol as l_prot then
if l_prot.is_case_insensitive_equal ("ssl_2_3") then
l_secure.set_secure_protocol_to_ssl_2_or_3
elseif l_prot.is_case_insensitive_equal ("tls_1_0") then
l_secure.set_secure_protocol_to_tls_1_0
elseif l_prot.is_case_insensitive_equal ("tls_1_1") then
l_secure.set_secure_protocol_to_tls_1_1
elseif l_prot.is_case_insensitive_equal ("tls_1_2") then
l_secure.set_secure_protocol_to_tls_1_2
elseif l_prot.is_case_insensitive_equal ("dtls_1_0") then
l_secure.set_secure_protocol_to_dtls_1_0
else -- Default
l_secure.set_secure_protocol_to_tls_1_2
end
end
if attached secure_certificate_file as c then
l_secure.set_certificate_file_path (c)
end
if attached secure_certificate_key_file as k then
l_secure.set_key_file_path (k)
end
else
create {HTTP_STREAM_SOCKET} Result.make_client_by_port (a_port, a_host)
end
end
end

View File

@@ -0,0 +1,99 @@
note
description: "[
API to perform actions like opening and closing the connection, sending and receiving messages, and listening
for events triggered by the server
]"
author: ""
date: "$Date$"
revision: "$Revision$"
deferred class
WEB_SOCKET
inherit
WEB_SOCKET_CONSTANTS
feature -- Access
uri: READABLE_STRING_GENERAL
-- WebSocket Protocol defines two URI schemes, ws and wss for
-- unencrypted and encrypted traffic between the client and the server.
port: INTEGER
-- Current WebSocket protocol.
ws_port_default: INTEGER = 80
-- WebSocket Protocol uses port 80 for regular WebSocket connections.
wss_port_default: INTEGER = 443
-- WebSocket connections tunneled over Transport Layer Security (TLS) use port 443.
protocols: detachable LIST [STRING]
-- List of protocol names that the client supports.
protocol: STRING
-- Client-Server protocol selected.
-- Has the result fo protocol negotiation between client and the server
-- By default it's an empty string.
is_secure: BOOLEAN
-- Is the current connection tunneled over TLS/SSL?
local
l_uri: STRING
do
l_uri := uri.as_lower.as_string_8
Result := l_uri.starts_with ("wss") -- TODO extract to ws_constants.
end
ready_state: WEB_SOCKET_READY_STATE
-- Connection state
feature -- Methods
send (a_message: STRING)
-- Send a message `a_message'
require
is_open: ready_state.is_open
deferred
end
close (a_id: INTEGER)
-- Close a websocket connection with a close id : `a_id'
deferred
ensure
is_closed: ready_state.is_closed
end
close_with_description (a_id: INTEGER; a_description: READABLE_STRING_GENERAL)
-- Close a websocket connection with a close id : `a_id' and a description `a_description'
deferred
ensure
is_closed: ready_state.is_closed
end
feature -- Change Element
set_protocol (a_protocol: STRING)
-- Set `protocol' with `a_protocol'
do
protocol := a_protocol
ensure
protocol_set: protocol = a_protocol
end
set_protocols (a_protocols: detachable ITERABLE [STRING])
local
l_protocols: LIST [STRING]
do
if a_protocols /= Void then
create {ARRAYED_LIST [STRING]} l_protocols.make (0)
across
a_protocols as ic
loop
l_protocols.force (ic.item)
end
protocols := l_protocols
end
end
end

View File

@@ -0,0 +1,603 @@
note
description: "[
API to perform actions like opening and closing the connection, sending and receiving messages, and listening
for events triggered by the server
]"
date: "$Date$"
revision: "$Revision$"
deferred class
WEB_SOCKET_CLIENT_I
inherit
WEB_SOCKET_SUBSCRIBER
redefine
on_websocket_error,
on_websocket_text_message,
on_websocket_binary_message,
on_websocket_close,
on_websocket_open,
on_websocket_ping,
on_websocket_pong
end
WEB_SOCKET
THREAD
rename
make as thread_make
end
feature -- Initialization
initialize (a_uri: READABLE_STRING_GENERAL; a_protocols: detachable ITERABLE [STRING])
-- Initialize websocket client
require
is_valid_uri: is_valid_uri (a_uri)
do
thread_make
uri := a_uri
set_default_port
create protocol.make_empty
set_protocols (a_protocols)
create ready_state.make
socket := new_socket (port, host)
create server_handshake.make
end
initialize_with_port (a_uri: READABLE_STRING_GENERAL; a_port: INTEGER; a_protocols: detachable ITERABLE [STRING])
-- Initialize websocket client
require
is_valid_uri: is_valid_uri (a_uri)
do
thread_make
uri := a_uri
port := a_port
create protocol.make_empty
set_protocols (a_protocols)
create ready_state.make
socket := new_socket (port, host)
create server_handshake.make
end
initialize_with_host_port_and_path (a_host: READABLE_STRING_GENERAL; a_port: INTEGER; a_path: READABLE_STRING_GENERAL)
require
is_valid_uri: is_valid_uri (a_host)
do
thread_make
uri := a_host + ":" + a_port.out + a_path
port := a_port
create protocol.make_empty
-- set_protocols (a_protocols)
create ready_state.make
socket := new_socket (port, host)
create server_handshake.make
end
feature -- Factory
new_socket (a_port: INTEGER; a_host: STRING): HTTP_STREAM_SOCKET
-- New socket for port `a_port' on host `a_host'.
deferred
end
feature -- Access
socket: HTTP_STREAM_SOCKET
-- Socket
has_error: BOOLEAN
do
Result := implementation.has_error
end
is_server_hanshake_accepted: BOOLEAN
is_valid_uri (a_uri: READABLE_STRING_GENERAL): BOOLEAN
-- Is `a_uri' a valid URI?
local
l_uri: URI
do
create l_uri.make_from_string (a_uri.as_string_8)
Result := l_uri.is_valid
end
server_handshake: WEB_SOCKET_HANDSHAKE_DATA
-- Handshake data received from the server
feature -- Access: secure
is_secure_connection_supported: BOOLEAN
-- Is SSL supported?
deferred
end
secure_protocol: detachable READABLE_STRING_GENERAL
-- SSL protocol , if `is_secure_connection_supported'.
secure_certificate_file: detachable PATH
-- SSL certificate file , if `is_secure_connection_supported'.
secure_certificate_key_file: detachable PATH
-- SSL key file , if `is_secure_connection_supported'.
feature -- Element change
set_secure_protocol (a_prot: like secure_protocol)
do
secure_protocol := a_prot
end
set_secure_certificate_file (p: detachable PATH)
-- Set SSL certificate from file at `p'.
do
secure_certificate_file := p
end
set_secure_certificate_key_file (p: detachable PATH)
-- Set SSL key from file at `p'.
do
secure_certificate_key_file := p
end
feature -- Events API
on_open (a_message: STRING)
deferred
end
on_text_message (a_message: STRING)
deferred
end
on_binary_message (a_message: STRING)
deferred
end
on_close (a_code: INTEGER; a_reason: STRING)
deferred
end
on_error (a_error: STRING)
deferred
end
feature -- Subscriber Events
on_websocket_handshake (a_request: STRING)
-- Send handshake message
do
socket.put_string (a_request)
end
on_websocket_text_message (a_message: STRING)
do
on_text_message (a_message)
end
on_websocket_binary_message (a_message: STRING)
do
on_binary_message (a_message)
end
on_websocket_open (a_message: STRING)
do
on_open (a_message)
end
on_websocket_close (a_message: STRING)
do
close_with_description (1000, a_message)
on_close (1000, a_message)
end
on_websocket_error (a_error: STRING)
do
on_error (a_error)
close_with_description (1002,"")
end
on_websocket_ping (a_message: STRING)
do
do_send (10, a_message)
end
on_websocket_pong (a_message: STRING)
do
-- do_send (9, a_message)
end
feature -- Execute
execute
require else
is_socket_valid: socket.exists
do
set_implementation
socket.connect
check
socket_connected: socket.is_connected
end
send_handshake
receive_handshake
if is_server_hanshake_accepted then
ready_state.set_state ({WEB_SOCKET_READY_STATE}.open)
on_websocket_open ("Open Connection")
from
until
ready_state.is_closed or has_error
loop
receive
end
else
on_websocket_error ("Server Handshake not accepted")
--log(Not connected)
socket.close
end
rescue
on_websocket_close ("")
socket.close
end
feature -- Methods
send (a_message: STRING)
do
do_send (1, a_message)
end
send_binary (a_message: STRING)
do
do_send (2, a_message)
end
close (a_id: INTEGER)
-- Close a websocket connection with a close id : `a_id'
do
do_send (8, "")
ready_state.set_state ({WEB_SOCKET_READY_STATE}.closed)
end
close_with_description (a_id: INTEGER; a_description: READABLE_STRING_GENERAL)
-- Close a websocket connection with a close id : `a_id' and a description `a_description'
do
do_send (8, "")
ready_state.set_state ({WEB_SOCKET_READY_STATE}.closed)
end
feature {NONE} -- Implementation
set_implementation
do
create implementation.make_with_protocols_and_port (Current, host, protocols, port)
end
send_handshake
local
l_uri: URI
l_handshake: STRING
l_random: SALT_XOR_SHIFT_64_GENERATOR
l_secure_protocol: STRING
do
create l_uri.make_from_string (uri.as_string_8)
create l_handshake.make_empty
if l_uri.path.is_empty then
l_handshake.append ("GET / HTTP/1.1")
l_handshake.append (crlf)
elseif l_uri.query = Void then
l_handshake.append ("GET " + l_uri.path + " HTTP/1.1")
l_handshake.append (crlf)
else
if attached l_uri.query as l_query then
l_handshake.append ("GET " + l_uri.path + "?" + l_query + " HTTP/1.1")
l_handshake.append (crlf)
end
end
if attached l_uri.host as l_host then
l_handshake.replace_substring_all ("$host", l_host)
l_handshake.append ("Host: " + l_host + ":" + port.out)
l_handshake.append (crlf)
end
l_handshake.append_string ("Upgrade: websocket")
l_handshake.append (crlf)
l_handshake.append_string ("Connection: Upgrade")
l_handshake.append (crlf)
l_handshake.append_string ("Sec-WebSocket-Key: ")
create l_random.make (16)
l_handshake.append_string (base64_encode_array (l_random.new_sequence))
l_handshake.append (crlf)
if attached protocols as l_protocols then
create l_secure_protocol.make_empty
across
l_protocols as c
loop
l_secure_protocol.append (c.item)
l_secure_protocol.append (" ,")
end
l_secure_protocol.remove_tail (1)
l_handshake.append_string ("Sec-WebSocket-Protocol:" + l_secure_protocol)
l_handshake.append (crlf)
end
l_handshake.append_string ("Sec-WebSocket-Version: 13")
l_handshake.append (crlf)
l_handshake.append (crlf)
implementation.start_handshake (l_handshake)
end
receive_handshake
do
analyze_request_message
if server_handshake.request_header.has_substring ("HTTP/1.1 101") and then attached server_handshake.request_header_map.item ("Upgrade") as l_upgrade_key and then -- Upgrade header must be present with value websocket
l_upgrade_key.is_case_insensitive_equal ("websocket") and then attached server_handshake.request_header_map.item ("Connection") as l_connection_key and then -- Connection header must be present with value Upgrade
l_connection_key.has_substring ("Upgrade")
then
is_server_hanshake_accepted := True
if attached server_handshake.request_header_map.item ("Sec-WebSocet-Protocol") as l_protocol then
set_protocol (l_protocol)
end
end
end
receive
do
implementation.receive
end
set_default_port
do
if is_secure then
port := wss_port_default
else
port := ws_port_default
end
end
client_handshake_required_template: STRING = "[
GET $resource HTTP/1.1
Host: $host
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: $key
Sec-WebSocket-Version: 13
]"
base64_encode_array (a_sequence: ARRAY [NATURAL_8]): STRING_8
-- Encode a byte array `a_sequence' into Base64 notation.
local
l_result: STRING
l_base_64: BASE64
do
create l_result.make_empty
across
a_sequence as i
loop
l_result.append_character (i.item.to_character_8)
end
create l_base_64
Result := l_base_64.encoded_string (l_result)
end
host: STRING
local
l_uri: URI
do
create Result.make_empty
create l_uri.make_from_string (uri.as_string_8)
if attached l_uri.host as l_host then
Result := l_host
end
end
feature -- Parse Request line
analyze_request_message
-- Analyze message extracted from `socket' as HTTP request
require
input_readable: socket /= Void and then socket.is_open_read
local
end_of_stream: BOOLEAN
pos, n: INTEGER
line: detachable STRING
k, val: STRING
txt: STRING
do
create txt.make (64)
server_handshake.set_request_header (txt)
if attached next_line as l_request_line and then not l_request_line.is_empty then
txt.append (l_request_line)
txt.append_character ('%N')
else
server_handshake.mark_error
end
-- l_is_verbose := is_verbose
if not server_handshake.has_error then -- or l_is_verbose then
-- if `is_verbose' we can try to print the request, even if it is a bad HTTP request
from
line := next_line
until
line = Void or end_of_stream
loop
n := line.count
-- if l_is_verbose then
-- log (line)
-- end
pos := line.index_of (':', 1)
if pos > 0 then
k := line.substring (1, pos - 1)
if line [pos + 1].is_space then
pos := pos + 1
end
if line [n] = '%R' then
n := n - 1
end
val := line.substring (pos + 1, n)
server_handshake.put_header (k, val)
end
txt.append (line)
txt.append_character ('%N')
if line.is_empty or else line [1] = '%R' then
end_of_stream := True
else
line := next_line
end
end
end
end
next_line: detachable STRING
-- Next line fetched from `socket' is available.
require
is_readable: socket.is_open_read
do
if socket.socket_ok then
socket.read_line_thread_aware
Result := socket.last_string
end
end
feature -- {WEB_SOCKET_CLIENT}
-- do_send (a_opcode: NATURAL_32; a_message: STRING)
-- local
-- l_chunks: INTEGER
-- i: INTEGER
-- l_index: INTEGER
-- l_chunk_size: INTEGER
-- l_key: STRING
-- l_message: STRING
-- l_frame : STRING
-- do
-- print ("%NMessage count:" + a_message.count.out)
-- create l_frame.make_empty
-- create l_message.make_empty
-- if a_message.count > 65535 then
-- --!Improve. this code need to be checked.
-- print("%N Case:1")
-- l_frame.append_code ((0x80 | a_opcode).to_natural_32)
-- l_frame.append_code ((0x80 | 127).to_natural_32)
-- l_frame.append_code (0x0)
-- l_frame.append_code (0x0)
-- l_frame.append_code (0x0)
-- l_frame.append_code (0x0)
-- l_frame.append_code (((a_message.count) |>> 32).to_character_8.code.as_natural_32)
-- l_frame.append_code (((a_message.count) |>> 16).to_character_8.code.as_natural_32)
-- l_frame.append_code (((a_message.count ) |>> 8).to_character_8.code.as_natural_32)
-- l_frame.append_code ((a_message.count).to_character_8.code.as_natural_32)
-- elseif a_message.count > 125 then
-- print("%N Case:2")
-- print ("Message count:" + a_message.count.out)
-- l_frame.append_code ((0x80 | a_opcode).to_natural_32)
-- l_frame.append_code ((0x80 | 126).to_natural_32)
-- l_frame.append_code (((a_message.count ) |>> 8).as_natural_32)
-- l_frame.append_code ((a_message.count).to_character_8.code.as_natural_32)
-- print ("%NHeaderMessage:" + l_frame)
-- else
-- print("%N Case:3")
-- print ("Message count:" + a_message.count.out)
-- l_frame.append_code ((0x80 | a_opcode).to_natural_32)
-- l_frame.append_code ((0x80 | a_message.count).to_natural_32)
-- end
--
-- l_key := new_key
-- l_frame.append (l_key.substring (1, 4))
-- l_message := implementation.unmmask (a_message, l_key.substring (1, 4))
-- l_frame.append (l_message)
-- socket.send_message (l_frame)
-- end
do_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
l_message : STRING
l_key: STRING
do
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 ((0x80 | 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 ((0x80 | 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 ((0x80 | n).as_natural_32)
end
l_key := new_key
l_header_message.append (l_key.substring (1, 4))
socket.put_string (l_header_message)
l_message := implementation.unmmask (a_message, l_key.substring (1, 4))
l_chunk_size := 16_384 -- 16K
if l_message_count < l_chunk_size then
socket.put_string (l_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 := l_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
new_key: STRING
local
l_random: SALT_XOR_SHIFT_64_GENERATOR
do
create Result.make_empty
create l_random.make (4)
across
l_random.new_sequence as i
loop
Result.append_integer (i.item)
end
end
implementation: WEB_SOCKET_IMPL
-- Web Socket implementation
crlf: STRING = "%R%N"
end

View File

@@ -0,0 +1,90 @@
note
description: "Summary description for {WEB_SOCKET_HANDSHAKE_DATA}."
author: ""
date: "$Date$"
revision: "$Revision$"
class
WEB_SOCKET_HANDSHAKE_DATA
create
make
feature {NONE} -- Initialization
make
do
reset
end
reset
do
has_error := False
version := Void
create method.make_empty
create uri.make_empty
create request_header.make_empty
create request_header_map.make (10)
end
feature -- Access
request_header: STRING
-- Header' source
request_header_map: HASH_TABLE [STRING, STRING]
-- Contains key:value of the header
has_error: BOOLEAN
-- Error occurred during `analyze_request_message'
method: STRING
-- http verb
uri: STRING
-- http endpoint
version: detachable STRING
-- http_version
feature -- Change Element
set_method (a_method: STRING)
-- Set `method' with `a_method'
do
method := a_method
ensure
method_set: method = a_method
end
set_version (a_version: STRING)
-- Set `version' with `a_version'
do
version := a_version
ensure
version_set: attached version as l_version implies l_version = a_version
end
mark_error
do
has_error := True
end
set_request_header (a_header: STRING)
-- Set `request_header' with `a_header'
do
request_header := a_header
ensure
request_header_set: request_header = a_header
end
put_header (a_key: STRING; a_value : STRING)
do
request_header_map.put (a_value, a_key)
end
end

View File

@@ -0,0 +1,739 @@
note
description: "Summary description for {WEB_SOCKET_IMPL}."
author: ""
date: "$Date$"
revision: "$Revision$"
class
WEB_SOCKET_IMPL
inherit
WEB_SOCKET
create
make, make_with_port, make_with_protocols, make_with_protocols_and_port
feature {NONE} -- Initialization
make (a_subscriber: WEB_SOCKET_SUBSCRIBER; a_uri: READABLE_STRING_GENERAL)
-- Create a websocket instante with a default port.
do
reset
subscriber := a_subscriber
uri := a_uri
create protocol.make_empty
set_default_port
create ready_state.make
ensure
uri_set: a_uri = uri
port_wss: is_secure implies port = wss_port_default
port_ws: not is_secure implies port = ws_port_default
ready_state_set: ready_state.state = {WEB_SOCKET_READY_STATE}.connecting
subscriber_set: subscriber = a_subscriber
protocol_set: protocol.is_empty
end
make_with_port (a_subscriber: WEB_SOCKET_SUBSCRIBER; a_uri: READABLE_STRING_GENERAL; a_port: INTEGER)
-- Create a websocket instance with port `a_port',
do
make (a_subscriber, a_uri)
port := a_port
ensure
uri_set: a_uri = uri
port_set: port = a_port
ready_state_set: ready_state.state = {WEB_SOCKET_READY_STATE}.connecting
subscriber_set: subscriber = a_subscriber
end
make_with_protocols (a_subscriber: WEB_SOCKET_SUBSCRIBER; a_uri: READABLE_STRING_GENERAL; a_protocols: detachable LIST [STRING])
-- Create a web socket instance with a list of protocols `a_protocols' and default port.
do
reset
subscriber := a_subscriber
uri := a_uri
create protocol.make_empty
protocols := a_protocols
set_default_port
create ready_state.make
ensure
uri_set: a_uri = uri
port_wss: is_secure implies port = wss_port_default
port_ws: not is_secure implies port = ws_port_default
protocols_set: protocols = a_protocols
ready_state_set: ready_state.state = {WEB_SOCKET_READY_STATE}.connecting
subscriber_set: subscriber = a_subscriber
protocol_set: protocol.is_empty
end
make_with_protocols_and_port (a_subscriber: WEB_SOCKET_SUBSCRIBER; a_uri: READABLE_STRING_GENERAL; a_protocols: detachable LIST [STRING]; a_port: INTEGER)
-- Create a web socket instance with a list of protocols `a_protocols' and port `a_port'.
do
make_with_protocols (a_subscriber, a_uri, a_protocols)
port := a_port
ensure
uri_set: a_uri = uri
protocols_set: protocols = a_protocols
port_set: port = a_port
ready_state_set: ready_state.state = {WEB_SOCKET_READY_STATE}.connecting
subscriber_set: subscriber = a_subscriber
end
reset
do
has_error := False
end
feature -- Access
has_error: BOOLEAN
is_verbose: BOOLEAN
feature -- Handshake
start_handshake (a_handshake: STRING)
do
subscriber.on_websocket_handshake (a_handshake)
end
feature -- Receive
receive
local
l_frame: detachable WEB_SOCKET_FRAME
l_client_message: detachable READABLE_STRING_8
do
l_frame := next_frame (subscriber.connection)
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.
subscriber.on_websocket_close ("Normal Close")
elseif ic.item.is_ping then
-- FIXME reply only to the most recent ping ...
subscriber.on_websocket_ping (ic.item.payload_data)
elseif ic.item.is_pong then
subscriber.on_websocket_pong (ic.item.payload_data)
else
subscriber.on_websocket_error ("Wrong Opcode")
end
end
end
l_client_message := l_frame.payload_data
if l_client_message = Void then
l_client_message := ""
end
debug ("ws")
print ("%NExecute: %N")
print (" [opcode: " + opcode_name (l_frame.opcode) + "]%N")
if l_frame.is_text then
print (" [client message: %"" + l_client_message + "%"]%N")
elseif l_frame.is_binary then
print (" [client binary message length: %"" + l_client_message.count.out + "%"]%N")
end
print (" [is_control: " + l_frame.is_control.out + "]%N")
print (" [is_binary: " + l_frame.is_binary.out + "]%N")
print (" [is_text: " + l_frame.is_text.out + "]%N")
end
if l_frame.is_connection_close then
subscriber.on_websocket_close ("Normal Close")
elseif l_frame.is_binary then
subscriber.on_websocket_binary_message (l_client_message)
elseif l_frame.is_text then
subscriber.on_websocket_text_message (l_client_message)
elseif l_frame.is_ping then
subscriber.on_websocket_ping (l_client_message)
elseif l_frame.is_pong then
subscriber.on_websocket_pong (l_client_message)
else
subscriber.on_websocket_error ("Wrong Opcode")
end
else
debug ("ws")
print ("%NExecute: %N")
print (" [ERROR: invalid frame]%N")
if l_frame /= Void and then attached l_frame.error as err then
print (" [Code: " + err.code.out + "]%N")
print (" [Description: " + err.description + "]%N")
end
end
subscriber.on_websocket_close ("")
end
-- if l_utf.is_valid_utf_8_string_8 (l_message) then
-- if is_data_frame_ok then
-- if opcode = text_frame then
-- subscriber.on_websocket_text_message (l_message)
-- elseif opcode = binary_frame then
-- subscriber.on_websocket_binary_message (l_message)
-- elseif opcode = ping_frame then
-- subscriber.on_websocket_ping (l_message)
-- elseif opcode = pong_frame then
-- subscriber.on_websocket_pong (l_message)
-- elseif opcode = Connection_close_frame then
-- subscriber.on_websocket_close ("Normal Close")
-- elseif opcode = ping_frame then
-- subscriber.on_websocket_ping (l_message)
-- elseif opcode = pong_frame then
-- subscriber.on_websocket_pong (l_message)
-- else
-- subscriber.on_websocket_error ("Wrong Opcode")
-- end
-- else
-- subscriber.on_websocket_close ("Invalid data frame")
-- end
-- else
-- subscriber.on_websocket_error ("Invalid UTF-8")
-- end
end
feature -- Methods
send (a_message: STRING)
do
end
close (a_id: INTEGER)
-- Close a websocket connection with a close id : `a_id'
do
end
close_with_description (a_id: INTEGER; a_description: READABLE_STRING_GENERAL)
-- Close a websocket connection with a close id : `a_id' and a description `a_description'
do
end
feature {NONE} -- Implementation
set_default_port
do
if is_secure then
port := wss_port_default
else
port := ws_port_default
end
end
subscriber: WEB_SOCKET_SUBSCRIBER
next_frame (a_socket: HTTP_STREAM_SOCKET): 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
a_socket_in_blocking_mode: a_socket.is_blocking
local
l_opcode: INTEGER
l_len: INTEGER
l_remaining_len: INTEGER
l_payload_len: NATURAL_64
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
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 (a_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 + ")")
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 + ")")
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 (a_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 (a_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 (a_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
Result.report_error (protocol_error, "Server sent a masked frame!")
else
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
a_socket.read_stream (l_chunk_size)
l_bytes_read := a_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 a_socket.bytes_read > 0 then
l_remaining_len := l_remaining_len - l_bytes_read
l_chunk := a_socket.last_string
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 <===============")
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)
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
rescue
retried := True
if Result /= Void then
Result.report_error (internal_error, "Internal error")
end
retry
end
next_bytes (a_socket: HTTP_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
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
log (m: STRING)
do
io.put_string (m + "%N")
end
to_byte (a_integer: INTEGER): ARRAY [INTEGER]
require
valid: a_integer >= 0 and then a_integer <= 255
local
l_val: INTEGER
l_index: INTEGER
do
create Result.make_filled (0, 1, 8)
from
l_val := a_integer
l_index := 8
until
l_val < 2
loop
Result.put (l_val \\ 2, l_index)
l_val := l_val // 2
l_index := l_index - 1
end
Result.put (l_val, l_index)
end
feature -- Masking
unmmask (a_frame: READABLE_STRING_8; a_key: READABLE_STRING_8): 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=S", "protocol=uri"
local
l_frame: STRING
i: INTEGER
do
l_frame := a_frame.twin
from
i := 1
until
i > l_frame.count
loop
l_frame [i] := (l_frame [i].code.to_integer_8.bit_xor (a_key [((i - 1) \\ 4) + 1].code.to_integer_8)).to_character_8
i := i + 1
end
Result := l_frame
end
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
end

View File

@@ -0,0 +1,30 @@
note
description: "[
Objects that is used to create dummy web socket client object
used for void-safety
]"
author: "$Author$"
date: "$Date$"
revision: "$Revision$"
class
WEB_SOCKET_NULL_CLIENT
inherit
WEB_SOCKET_SUBSCRIBER
feature -- Handshake
on_websocket_handshake (request: STRING)
-- Default do nothing
do
end
feature -- TCP connection
connection: HTTP_STREAM_SOCKET
do
create Result.make_client_by_port (0, "null")
end
end

View File

@@ -0,0 +1,120 @@
note
description: "Report the state of the connection: [CONNECTING, OPEN, CLOSING, CLOSED]"
date: "$Date$"
revision: "$Revision$"
class
WEB_SOCKET_READY_STATE
create
make
feature -- Initialization
make
do
set_state (Connecting)
ensure
state_connecting: state = Connecting
end
feature -- Access
state: INTEGER
-- Current ready state
feature -- Status Report
is_valid_state (a_state: INTEGER): BOOLEAN
-- Is `a_state' a valid ready state?
do
Result := Connecting = a_state or else Open = a_state or else Closing = a_state or else Closed = a_state
end
is_connecting: BOOLEAN
-- Is current `state' connecting?
do
Result := state = Connecting
end
is_open: BOOLEAN
-- Is current `state' open?
do
Result := state = Open
end
is_closing: BOOLEAN
-- Is current `state' closing?
do
Result := state = Closing
end
is_closed: BOOLEAN
-- Is current `state' closed?
do
Result := state = Closed
end
feature -- Change Element
set_state (a_state: INTEGER)
-- Set `state' to `a_state'
require
valid_state: is_valid_state (a_state)
do
state := a_state
ensure
state_set: state = a_state
end
mark_connecting
--Set `state' to `connecting'
do
set_state (Connecting)
ensure
connecting_state: state = Connecting
end
mark_open
--Set `state' to `open'
do
set_state (Open)
ensure
open_state: state = Open
end
mark_closing
--Set `state' to `closing'
do
set_state (Closing)
ensure
open_state: state = Closing
end
mark_closed
--Set `state' to `closed'
do
set_state (Closed)
ensure
open_state: state = Closed
end
feature --State connection
Connecting: INTEGER = 0
-- The connection is in progress but has not been established.
Open: INTEGER = 1
-- The connection has been stablished. Messages can flow between client and server.
Closing: INTEGER = 2
-- The connection is going through the closing handshake
Closed: INTEGER = 3
-- The connection has been closed or could not be opened.
invariant
is_valid_sate: is_valid_state (state)
end

View File

@@ -0,0 +1,61 @@
note
description: "Summary description for {WEB_SOCKET_SUBSCRIBER}."
author: ""
date: "$Date$"
revision: "$Revision$"
deferred class
WEB_SOCKET_SUBSCRIBER
feature -- Events
on_websocket_ping (a_message: detachable STRING)
-- Send a ping in response to a received ping.
do
end
on_websocket_pong (a_message: detachable STRING)
-- Default implementation do nothing.
do
end
on_websocket_text_message (a_message: detachable STRING)
-- Default implementation do nothing.
do
end
on_websocket_binary_message (a_message: detachable STRING)
-- Default implementation do nothing.
do
end
on_websocket_open (a_message: detachable STRING)
do
end
on_websocket_close (a_message: detachable STRING)
do
end
on_websocket_error (a_error: detachable STRING)
do
end
feature -- Handshake
on_websocket_handshake (request: STRING)
-- Default do nothing
deferred
end
feature -- TCP connection
connection: HTTP_STREAM_SOCKET
deferred
end
end

View File

@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-13-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-13-0 http://www.eiffel.com/developers/xml/configuration-1-13-0.xsd" name="web_socket_client" uuid="EE010507-597F-4FAD-8EFA-B7251E800911" library_target="web_socket_client">
<target name="web_socket_client">
<root all_classes="true"/>
<file_rule>
<exclude>/.git$</exclude>
<exclude>/EIFGENs$</exclude>
<exclude>/CVS$</exclude>
<exclude>/.svn$</exclude>
</file_rule>
<option warning="true" void_safety="all">
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
</option>
<setting name="concurrency" value="thread"/>
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
<library name="crypto" location="$ISE_LIBRARY\unstable\library\text\encryption\crypto\crypto-safe.ecf"/>
<library name="encoder" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\text\encoder\encoder-safe.ecf"/>
<library name="net" location="$ISE_LIBRARY\library\net\net-safe.ecf"/>
<library name="thread" location="$ISE_LIBRARY\library\thread\thread-safe.ecf"/>
<library name="uri" location="$ISE_LIBRARY\library\text\uri\uri-safe.ecf"/>
<library name="lib_http_network" location="..\..\http_network\http_network-safe.ecf"/>
<library name="lib_web_socket_protocol" location="..\protocol\web_socket_protocol-safe.ecf"/>
<cluster name="web_socket_client" location=".\src\" recursive="true">
<file_rule>
<exclude>/no_ssl$</exclude>
<exclude>/ssl$</exclude>
<exclude>/spec$</exclude>
</file_rule>
<cluster name="ssl" location="$|ssl\" recursive="true">
<condition>
<custom name="ssl_enabled" value="true"/>
</condition>
<condition>
<custom name="net_ssl_enabled" value="true"/>
</condition>
</cluster>
<cluster name="no_ssl" location="$|no_ssl\" recursive="true">
<condition>
<custom name="ssl_enabled" excluded_value="true"/>
<custom name="net_ssl_enabled" excluded_value="true"/>
</condition>
</cluster>
</cluster>
</target>
</system>

View File

@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-13-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-13-0 http://www.eiffel.com/developers/xml/configuration-1-13-0.xsd" name="web_socket_client" uuid="EE010507-597F-4FAD-8EFA-B7251E800911" library_target="web_socket_client">
<target name="web_socket_client">
<root all_classes="true"/>
<file_rule>
<exclude>/.git$</exclude>
<exclude>/EIFGENs$</exclude>
<exclude>/CVS$</exclude>
<exclude>/.svn$</exclude>
</file_rule>
<option warning="true" void_safety="none">
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
</option>
<setting name="concurrency" value="thread"/>
<library name="base" location="$ISE_LIBRARY\library\base\base.ecf"/>
<library name="crypto" location="$ISE_LIBRARY\unstable\library\text\encryption\crypto\crypto.ecf"/>
<library name="encoder" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\text\encoder\encoder.ecf"/>
<library name="net" location="$ISE_LIBRARY\library\net\net.ecf"/>
<library name="thread" location="$ISE_LIBRARY\library\thread\thread.ecf"/>
<library name="uri" location="$ISE_LIBRARY\library\text\uri\uri.ecf"/>
<library name="lib_http_network" location="..\..\http_network\http_network.ecf"/>
<library name="lib_web_socket_protocol" location="..\protocol\web_socket_protocol.ecf"/>
<cluster name="web_socket_client" location=".\src\" recursive="true">
<file_rule>
<exclude>/no_ssl$</exclude>
<exclude>/ssl$</exclude>
<exclude>/spec$</exclude>
</file_rule>
<cluster name="ssl" location="$|ssl\" recursive="true">
<condition>
<custom name="ssl_enabled" value="true"/>
</condition>
<condition>
<custom name="net_ssl_enabled" value="true"/>
</condition>
</cluster>
<cluster name="no_ssl" location="$|no_ssl\" recursive="true">
<condition>
<custom name="ssl_enabled" excluded_value="true"/>
<custom name="net_ssl_enabled" excluded_value="true"/>
</condition>
</cluster>
</cluster>
</target>
</system>

View File

@@ -0,0 +1,15 @@
<!DOCTYPE html>
<html>
<head>
<title>WebSocket prototype</title>
<script src="js/jquery-1.10.1.js"></script>
<script src="js/main.js"></script>
</head>
<body>
<h1>WebSocket prototype</h1>
<button id="run">Run</button>
<p>Output :</p>
<div id=console>
</div>
</body>
</html>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,37 @@
$(function() {
var wsEchoURI = "ws://localhost:9999/";
var websocket;
function printConsole(text) {
$("#console").append($(document.createElement("p")).html(text));
}
function onWSOpen(evt) {
printConsole("WebSocket open");
if(websocket.readyState == websocket.OPEN) {
printConsole("WebSocket ready");
}
}
function onWSClose(evt) {
printConsole("WebSocket closed");
printConsole(websocket.readyState);
}
function onWSMessage(evt) {
printConsole(evt.data);
}
function onWSError(evt) {
printConsole("An error occured in the WebSocket : " + evt.data);
}
$("#run").click(function buttonRun() {
printConsole("Connection");
websocket = new WebSocket(wsEchoURI);
websocket.onopen = function(evt) { onWSOpen(evt) };
websocket.onclose = function(evt) { onWSClose(evt) };
websocket.onmessage = function(evt) { onWSMessage(evt) };
websocket.onerror = function(evt) { onWSError(evt) };
});
});

View File

@@ -0,0 +1,8 @@
<?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="protocol" uuid="97582991-7BF3-4F4D-8944-8141ADE34274" library_target="protocol">
<target name="protocol">
<root all_classes="true"/>
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
<cluster name="src" location=".\" recursive="true"/>
</target>
</system>

View File

@@ -0,0 +1,10 @@
<?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="protocol" uuid="97582991-7BF3-4F4D-8944-8141ADE34274" library_target="protocol">
<target name="protocol">
<root all_classes="true"/>
<option void_safety="none">
</option>
<library name="base" location="$ISE_LIBRARY\library\base\base.ecf"/>
<cluster name="src" location=".\" recursive="true"/>
</target>
</system>

View File

@@ -0,0 +1,40 @@
note
description: "Websocket server."
date: "$Date$"
revision: "$Revision$"
class
APPLICATION
inherit
WEB_SOCKET_SERVICE [APPLICATION_EXECUTION]
redefine
set_options
end
create
make_and_launch
feature {NONE} -- Initialization
set_options (opts: WEB_SOCKET_SERVICE_OPTIONS)
do
opts.set_is_verbose (True) -- For debug purpose
opts.set_verbose_level ("debug")
opts.set_is_secure (True) -- If SSL is supported
opts.set_secure_certificate ("ca.crt") -- Change to use your own crt file.
opts.set_secure_certificate_key ("ca.key") -- Change to use your own key file.
opts.set_port (default_port_number)
end
feature -- Access
default_port_number: INTEGER = 9090
note
copyright: "2011-2016, Javier Velilla, Jocelyn Fiat and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -0,0 +1,173 @@
note
description: "[
Implementation of {WEB_SOCKET_EXECUTION} specific for this application.
]"
date: "$Date$"
revision: "$Revision$"
class
APPLICATION_EXECUTION
inherit
WEB_SOCKET_EXECUTION
create
make
feature -- Request processing
is_verbose: BOOLEAN = True
execute
-- Process request ...
local
s: STRING
do
if
request.is_get_request_method and then
request.request_uri.is_case_insensitive_equal_general ("/app")
then
s := websocket_app_html ({APPLICATION}.default_port_number)
else
s := "Open the websocket client example: <a href=%"/app%">click here</a>."
end
response.header.put_content_type_text_html
response.header.put_content_length (s.count)
response.put_string (s)
end
on_open (ws: WEB_SOCKET)
do
if is_verbose then
log ("Connecting", {HTTPD_LOGGER_CONSTANTS}.debug_level)
end
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 is_verbose then
-- log ("On text (message length=" + a_message.count.out + ")", {HTTPD_LOGGER_CONSTANTS}.debug_level)
-- end
ws.send (Text_frame, a_message)
end
on_close (ws: WEB_SOCKET)
-- Called after the WebSocket connection is closed.
do
if is_verbose then
log ("Connection closed", {HTTPD_LOGGER_CONSTANTS}.debug_level)
end
end
log (m: READABLE_STRING_8; lev: INTEGER)
do
response.put_error (m)
end
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
note
copyright: "2011-2016, Javier Velilla, Jocelyn Fiat and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -0,0 +1,15 @@
-----BEGIN CERTIFICATE-----
MIICWDCCAcGgAwIBAgIJAJnXGtV+PtiYMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV
BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX
aWRnaXRzIFB0eSBMdGQwHhcNMTUwNDAzMjIxNTA0WhcNMTYwNDAyMjIxNTA0WjBF
MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50
ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB
gQDFMK6ojzg+KlklhTossR13c51izMgGc3B0z9ttfHIcx2kxra3HtHcKIl5wSUvn
G8zmSyFAyQTs5LUv65q46FM9qU8tP+vTeFCfNXvjRcIEpouta3J53K0xuUlxz4d4
4D6qvdDWAez/0AkI4y5etW5zXtg7IQorJhsI9TmfGuruzwIDAQABo1AwTjAdBgNV
HQ4EFgQUbWpk2HoHa0YqpEwr7CGEatBFTMkwHwYDVR0jBBgwFoAUbWpk2HoHa0Yq
pEwr7CGEatBFTMkwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOBgQAi+h4/
IgEocWkdRZBKHEcTrRxz5WhEDJMoVo9LhnXvCfn1G/4p6Un6sYv7Xzpi9NuSY8uV
cjfJJXhtF3AtyZ70iTAxWaRWjGaZ03PYOjlledJ5rqJEt6CCn8m+JsfznduZvbxQ
zQ6jCLXfyD/tvemB+yYEI3NntvRKx5/zt6Q26Q==
-----END CERTIFICATE-----

View File

@@ -0,0 +1,15 @@
-----BEGIN RSA PRIVATE KEY-----
MIICXAIBAAKBgQDFMK6ojzg+KlklhTossR13c51izMgGc3B0z9ttfHIcx2kxra3H
tHcKIl5wSUvnG8zmSyFAyQTs5LUv65q46FM9qU8tP+vTeFCfNXvjRcIEpouta3J5
3K0xuUlxz4d44D6qvdDWAez/0AkI4y5etW5zXtg7IQorJhsI9TmfGuruzwIDAQAB
AoGAR5efMg+dieRyLU8rieJcImxVbfOPg9gRsjdtIVkXTR+RL7ow59q7hXBo/Td/
WU8cm1gXoJ/bK+71YYqWyB+BaLRIWvRWb7Gdw203tu4e136Ca5uuY+71qdbVTVcl
NQ7J+T+eAQFP+a+DdT3ZQxu9eze87SMbu6i5YSpIk2kusOECQQDunv/DQ+nc+NgR
DF+Td3sNYUVRT9a1CWi6abAG6reXwp8MS4NobWDf+Ps4JODhEEwlIdq5qL7qqYBZ
Gc1TJJ53AkEA0404Fn6vAzzegBcS4RLlYTK7nMr0m4pMmDMCI6YzAYdMmKHp1e6f
IwxSmQrmwyAgwcT01bc0+A8yipcC2BWQaQJBAJ01QZm635OGmos41KsKF5bsE8gL
SpBBH69Yu/ECqGwie7iU84FUNnO4zIHjwghlPVVlZX3Vz9o4S+fn2N9DC+cCQGyZ
QyCxGdC0r5fbwHJQS/ZQn+UGfvlVzqoXDVMVn3t6ZES6YZrT61eHnOM5qGqklIxE
Old3vDZXPt/MU8Zvk3kCQBOgUx2VxvTrHN37hk9/QIDiM62+RenBm1M3ah8xTosf
1mSeEb6d9Kwb3TgPBmA7YXzJuAQfRIvEPMPxT5SSr6Q=
-----END RSA PRIVATE KEY-----

View File

@@ -0,0 +1,46 @@
<?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="echo_websocket_server" uuid="C9B3DA5F-DF0D-4C0F-924A-130B5C1E6604">
<target name="common">
<file_rule>
<exclude>/.git$</exclude>
<exclude>/.svn$</exclude>
<exclude>/CVS$</exclude>
<exclude>/EIFGENs$</exclude>
</file_rule>
<option debug="true" warning="true" full_class_checking="false" is_attached_by_default="true" is_obsolete_routine_type="true" void_safety="all" syntax="transitional">
<debug name="ws" enabled="true"/>
<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">
<option>
<assertions precondition="true"/>
</option>
</library>
<library name="http_network" location="..\..\..\..\http_network\http_network-safe.ecf" readonly="false"/>
<library name="httpd" location="..\..\..\..\..\server\httpd\httpd-safe.ecf" readonly="false"/>
<library name="standalone_websocket_connector" location="..\..\..\..\..\server\wsf\connector\standalone_websocket-safe.ecf" readonly="false"/>
<library name="websocket_server" location="..\..\websocket_server-safe.ecf" readonly="false">
<option debug="true">
<debug name="ws" enabled="true"/>
</option>
</library>
<cluster name="src" location=".\" recursive="true">
</cluster>
</target>
<target name="echo_websocket_server_mt" extends="common">
<root class="APPLICATION" feature="make_and_launch"/>
<setting name="concurrency" value="thread"/>
</target>
<target name="echo_websocket_server_mt_ssl" extends="echo_websocket_server_mt">
<variable name="ssl_enabled" value="true"/>
</target>
<target name="echo_websocket_server_scoop" extends="common">
<root class="APPLICATION" feature="make_and_launch"/>
<setting name="concurrency" value="scoop"/>
</target>
<target name="echo_websocket_server_scoop_ssl" extends="echo_websocket_server_scoop">
<variable name="ssl_enabled" value="true"/>
</target>
</system>

View File

@@ -0,0 +1,4 @@
${NOTE_KEYWORD}
copyright: "2011-${YEAR}, Javier Velilla, Jocelyn Fiat and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"

View File

@@ -0,0 +1,116 @@
<!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() {
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();
function connect(){
var socket;
var host = "ws://127.0.0.1:9090";
try{
var 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();
});
}
}//End 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>

View File

@@ -0,0 +1,53 @@
<!DOCTYPE html>
<title>WebSocket Image Drop</title>
<h1>Drop Image Here</h1>
<script>
// Initialize WebSocket connection
var wsUrl = "ws://127.0.0.1:9090";
var ws = new WebSocket(wsUrl);
ws.onopen = function() {
console.log("open");
}
// Handle binary image data received on the WebSocket
ws.onmessage = function(e) {
var blob = e.data;
console.log("message: " + blob.size + " bytes");
// Work with prefixed URL API
if (window.webkitURL) {
URL = webkitURL;
}
var uri = URL.createObjectURL(blob);
var img = document.createElement("img");
img.src = uri;
document.body.appendChild(img);
}
// Handle drop event
document.ondrop = function(e) {
document.body.style.backgroundColor = "#fff";
try {
e.preventDefault();
handleFileDrop(e.dataTransfer.files[0]);
return false;
} catch(err) {
console.log(err);
}
}
// Provide visual feedback for the drop area
document.ondragover = function(e) {
e.preventDefault();
document.body.style.backgroundColor = "#6fff41";
}
document.ondragleave = function() {
document.body.style.backgroundColor = "#fff";
}
// Read binary file contents and send them over WebSocket
function handleFileDrop(file) {
var reader = new FileReader();
reader.readAsArrayBuffer(file);
reader.onload = function() {
console.log("sending: " + file.name);
ws.send(reader.result);
}
}
</script>

View File

@@ -0,0 +1,21 @@
note
description: "Summary description for {WEB_SOCKET_EXECUTION}."
date: "$Date$"
revision: "$Revision$"
deferred class
WEB_SOCKET_EXECUTION
inherit
WSF_WEBSOCKET_EXECUTION
WEB_SOCKET_EVENT_I
feature -- Websocket execution
new_websocket_handler (ws: WEB_SOCKET): WEB_SOCKET_HANDLER
do
create Result.make (ws, Current)
end
end

View File

@@ -0,0 +1,30 @@
note
description: "Summary description for {WEB_SOCKET_SERVICE}."
date: "$Date$"
revision: "$Revision$"
class
WEB_SOCKET_SERVICE [G -> WEB_SOCKET_EXECUTION create make end]
create
make_and_launch
feature {NONE} -- Initialization
make_and_launch
local
l_launcher: WSF_STANDALONE_WEBSOCKET_SERVICE_LAUNCHER [G]
opts: WEB_SOCKET_SERVICE_OPTIONS
do
create opts
set_options (opts)
opts.append_options (create {WSF_SERVICE_LAUNCHER_OPTIONS_FROM_INI}.make_from_file ("ws.ini"))
create l_launcher.make_and_launch (opts)
end
set_options (opts: WEB_SOCKET_SERVICE_OPTIONS)
-- Set values on `opts'.
do
end
end

View File

@@ -0,0 +1,12 @@
note
description: "Summary description for {WEB_SOCKET_SERVICE_OPTIONS}."
date: "$Date$"
revision: "$Revision$"
class
WEB_SOCKET_SERVICE_OPTIONS
inherit
WSF_STANDALONE_WEBSOCKET_SERVICE_OPTIONS
end

View File

@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-13-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-13-0 http://www.eiffel.com/developers/xml/configuration-1-13-0.xsd" name="websocket_server" uuid="FDD2276B-7149-4490-9E09-46AFEA122D1E" library_target="websocket_server">
<target name="websocket_server">
<root all_classes="true"/>
<file_rule>
<exclude>/.git$</exclude>
<exclude>/EIFGENs$</exclude>
<exclude>/CVS$</exclude>
<exclude>/.svn$</exclude>
</file_rule>
<option warning="true" is_attached_by_default="true" void_safety="all" syntax="standard">
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
</option>
<setting name="concurrency" value="thread"/>
<library name="base" location="$ISE_LIBRARY\library\base\base-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"/>
<library name="standalone_websocket_connector" location="..\..\..\server\wsf\connector\standalone_websocket-safe.ecf"/>
<library name="wsf" location="..\..\..\server\wsf\wsf-safe.ecf"/>
<library name="net" location="$ISE_LIBRARY\library\net\net-safe.ecf"/>
<library name="thread" location="$ISE_LIBRARY\library\thread\thread-safe.ecf">
<condition>
<concurrency excluded_value="none"/>
</condition>
</library>
<library name="time" location="$ISE_LIBRARY\library\time\time-safe.ecf"/>
<cluster name="websocket_server" location=".\src\" recursive="true"/>
</target>
</system>

View File

@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-11-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-11-0 http://www.eiffel.com/developers/xml/configuration-1-11-0.xsd" name="websocket_server" uuid="FDD2276B-7149-4490-9E09-46AFEA122D1E" library_target="websocket_server">
<target name="websocket_server">
<root all_classes="true"/>
<file_rule>
<exclude>/.git$</exclude>
<exclude>/EIFGENs$</exclude>
<exclude>/CVS$</exclude>
<exclude>/.svn$</exclude>
</file_rule>
<option warning="true" is_attached_by_default="false" void_safety="none" syntax="standard">
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
</option>
<setting name="concurrency" value="thread"/>
<library name="base" location="$ISE_LIBRARY\library\base\base.ecf"/>
<library name="crypto" location="$ISE_LIBRARY\unstable\library\text\encryption\crypto\crypto.ecf"/>
<library name="encoder" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\text\encoder\encoder.ecf"/>
<library name="standalone_websocket_connector" location="..\..\..\server\wsf\connector\standalone_websocket.ecf"/>
<library name="wsf" location="..\..\..\server\wsf\wsf.ecf"/>
<library name="net" location="$ISE_LIBRARY\library\net\net.ecf"/>
<library name="thread" location="$ISE_LIBRARY\library\thread\thread.ecf">
<condition>
<concurrency excluded_value="none"/>
</condition>
</library>
<library name="time" location="$ISE_LIBRARY\library\time\time.ecf"/>
<cluster name="websocket_server" location=".\src\" recursive="true"/>
</target>
</system>

View File

@@ -1,53 +0,0 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-15-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-15-0 http://www.eiffel.com/developers/xml/configuration-1-15-0.xsd" name="http_network" uuid="56DAA1CE-0A2E-451A-BFC9-7821578E79F0" library_target="http_network">
<target name="http_network">
<root all_classes="true"/>
<file_rule>
<exclude>/.git$</exclude>
<exclude>/.svn$</exclude>
<exclude>/EIFGENs$</exclude>
</file_rule>
<option warning="true" full_class_checking="false" is_attached_by_default="true" void_safety="all" syntax="standard">
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
</option>
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
<library name="net" location="$ISE_LIBRARY\library\net\net-safe.ecf" readonly="false"/>
<library name="net_ssl" location="$ISE_LIBRARY\unstable\library\network\socket\netssl\net_ssl-safe.ecf">
<condition>
<custom name="net_ssl_enabled" value="true"/>
</condition>
</library>
<cluster name="network" location=".\network\">
<file_rule>
<exclude>/httpd_stream_socket_ext.e$</exclude>
<condition>
<version type="compiler" max="16.11.0.0"/>
</condition>
</file_rule>
<cluster name="ssl_network" location="$|ssl\" recursive="true">
<condition>
<custom name="httpd_ssl_enabled" value="true"/>
</condition>
<file_rule>
<exclude>/httpd_stream_ssl_socket_ext.e$</exclude>
<condition>
<version type="compiler" max="16.11.0.0"/>
<custom name="httpd_ssl_enabled" value="true"/>
</condition>
</file_rule>
</cluster>
</cluster>
<cluster name="network_until_16_05" location=".\network\until_16_05\" recursive="false">
<condition>
<version type="compiler" max="16.11.0.0"/>
</condition>
<cluster name="ssl_network_until_16_05" location="$|ssl\" recursive="true">
<condition>
<custom name="httpd_ssl_enabled" value="true"/>
<version type="compiler" max="16.11.0.0"/>
</condition>
</cluster>
</cluster>
</target>
</system>

View File

@@ -1,53 +0,0 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-15-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-15-0 http://www.eiffel.com/developers/xml/configuration-1-15-0.xsd" name="http_network" uuid="56DAA1CE-0A2E-451A-BFC9-7821578E79F0" library_target="http_network">
<target name="http_network">
<root all_classes="true"/>
<file_rule>
<exclude>/.git$</exclude>
<exclude>/.svn$</exclude>
<exclude>/EIFGENs$</exclude>
</file_rule>
<option warning="true" full_class_checking="false" void_safety="none" syntax="standard">
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
</option>
<setting name="concurrency" value="scoop"/>
<library name="base" location="$ISE_LIBRARY\library\base\base.ecf"/>
<library name="net" location="$ISE_LIBRARY\library\net\net.ecf"/>
<library name="net_ssl" location="$ISE_LIBRARY\unstable\library\network\socket\netssl\net_ssl.ecf">
<condition>
<custom name="net_ssl_enabled" value="true"/>
</condition>
</library>
<cluster name="network" location=".\network\">
<file_rule>
<exclude>/httpd_stream_socket_ext.e$</exclude>
<condition>
<version type="compiler" max="16.11.0.0"/>
</condition>
</file_rule>
<cluster name="ssl_network" location="$|ssl\" recursive="true">
<condition>
<custom name="httpd_ssl_enabled" value="true"/>
</condition>
<file_rule>
<exclude>/httpd_stream_ssl_socket_ext.e$</exclude>
<condition>
<version type="compiler" max="16.11.0.0"/>
<custom name="httpd_ssl_enabled" value="true"/>
</condition>
</file_rule>
</cluster>
</cluster>
<cluster name="network_until_16_05" location=".\network\until_16_05\" recursive="false">
<condition>
<version type="compiler" max="16.11.0.0"/>
</condition>
<cluster name="ssl_network_until_16_05" location="$|ssl\" recursive="true">
<condition>
<custom name="httpd_ssl_enabled" value="true"/>
<version type="compiler" max="16.11.0.0"/>
</condition>
</cluster>
</cluster>
</target>
</system>

View File

@@ -1,22 +0,0 @@
note
description: "[
Constant value to define the logging level.
]"
date: "$Date$"
revision: "$Revision$"
deferred class
HTTPD_LOGGER_CONSTANTS
feature -- Access
alert_level: INTEGER = 1 -- 0000 0001
critical_level: INTEGER = 2 -- 0000 0010
error_level: INTEGER = 4 -- 0000 0100
warning_level: INTEGER = 8 -- 0000 1000
notice_level: INTEGER = 16 -- 0001 0000
information_level: INTEGER = 32 -- 0010 0000
debug_level: INTEGER = 64 -- 0100 0000
end

View File

@@ -1,17 +0,0 @@
note
description: "Summary description for {HTTPD_SOCKET_FACTORY}."
date: "$Date$"
revision: "$Revision$"
deferred class
HTTPD_SOCKET_FACTORY
feature -- Access
new_client_socket (a_is_secure: BOOLEAN): HTTPD_STREAM_SOCKET
do
check not_secure: not a_is_secure end
create Result.make_empty
end
end

View File

@@ -1,41 +0,0 @@
note
description: "[
SSL enabled server
]"
date: "$Date$"
revision: "$Revision$"
class
HTTPD_SERVER
inherit
HTTPD_SERVER_I
redefine
new_listening_socket
end
create
make
feature {NONE} -- Factory
new_listening_socket (a_addr: detachable INET_ADDRESS; a_http_port: INTEGER): HTTPD_STREAM_SOCKET
local
s_ssl: HTTPD_STREAM_SSL_SOCKET
do
if configuration.is_secure then
if a_addr /= Void then
create s_ssl.make_server_by_address_and_port (a_addr, a_http_port)
Result := s_ssl
else
create s_ssl.make_server_by_port (a_http_port)
end
s_ssl.set_tls_protocol (configuration.ssl_protocol)
s_ssl.set_certificate_filenames (configuration.ca_crt, configuration.ca_key)
Result := s_ssl
else
Result := Precursor (a_addr, a_http_port)
end
end
end

View File

@@ -1,20 +0,0 @@
note
description: "Summary description for {HTTPD_SOCKET_FACTORY}."
date: "$Date$"
revision: "$Revision$"
deferred class
HTTPD_SOCKET_FACTORY
feature -- Access
new_client_socket (a_is_secure: BOOLEAN): HTTPD_STREAM_SOCKET
do
if a_is_secure then
create {HTTPD_STREAM_SSL_SOCKET} Result.make_empty
else
create Result.make_empty
end
end
end

View File

@@ -75,7 +75,7 @@ feature -- Output
-- Send `s' to http client
do
last_target_call_succeed := False
target.put_readable_string_8_noexception (s)
target.put_string_8_noexception (s)
last_target_call_succeed := not target.was_error
end
@@ -83,7 +83,7 @@ feature -- Output
-- Send `s' to http client
do
last_target_call_succeed := False
target.put_readable_string_8_noexception (s)
target.put_string_8_noexception (s)
last_target_call_succeed := not target.was_error
end

View File

@@ -16,7 +16,7 @@
<library name="encoder" location="..\..\..\..\text\encoder\encoder-safe.ecf"/>
<library name="ewsgi" location="..\..\ewsgi-safe.ecf" readonly="false"/>
<library name="http" location="..\..\..\..\network\protocol\http\http-safe.ecf"/>
<library name="httpd" location="lib\httpd\httpd-safe.ecf" readonly="false"/>
<library name="httpd" location="..\..\..\httpd\httpd-safe.ecf" readonly="false"/>
<cluster name="src" location=".\src\">
<cluster name="implementation" location="$|implementation\" hidden="true"/>
</cluster>

View File

@@ -15,7 +15,7 @@
<library name="encoder" location="..\..\..\..\text\encoder\encoder.ecf"/>
<library name="ewsgi" location="..\..\ewsgi.ecf" readonly="false"/>
<library name="http" location="..\..\..\..\network\protocol\http\http.ecf"/>
<library name="httpd" location="lib\httpd\httpd.ecf" readonly="false"/>
<library name="httpd" location="..\..\..\httpd\httpd.ecf" readonly="false"/>
<cluster name="src" location=".\src\">
<cluster name="implementation" location="$|implementation\" hidden="true"/>
</cluster>

View File

@@ -10,11 +10,6 @@
<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="lib\httpd\httpd-safe.ecf" readonly="false">
<option debug="true">
<debug name="dbglog" enabled="true"/>
</option>
</library>
<library name="wsf" location="..\..\..\wsf\wsf-safe.ecf" readonly="false"/>
<cluster name="tests" location="tests\" recursive="true"/>
</target>
@@ -27,7 +22,7 @@
<target name="test_connector_standalone" extends="test_standalone_scoop">
</target>
<target name="test_standalone_scoop_ssl" extends="test_standalone_scoop">
<variable name="httpd_ssl_enabled" value="true"/>
<variable name="ssl_enabled" value="true"/>
<variable name="libcurl_http_client_disabled" value="true"/>
<variable name="net_http_client_disabled" value="false"/>
<variable name="netssl_http_client_enabled" value="true"/>

View File

@@ -35,6 +35,9 @@ feature -- Input
read_character
-- Read the next character in input stream.
-- Make the result available in `last_character'
local
d: like last_chunk_data
l_index_in_chunk: INTEGER
do
index := index + 1
if index > chunk_upper then
@@ -45,10 +48,24 @@ feature -- Input
read_trailer_and_crlf
last_character := '%U'
else
last_character := last_chunk_data.item (index)
l_index_in_chunk := chunk_index (index)
d := last_chunk_data
if d.valid_index (l_index_in_chunk) then
last_character := d [l_index_in_chunk]
else
check has_character: False end
last_character := '%U'
end
end
else
last_character := last_chunk_data.item (index)
l_index_in_chunk := chunk_index (index)
d := last_chunk_data
if d.valid_index (l_index_in_chunk) then
last_character := d [l_index_in_chunk]
else
check has_character: False end
last_character := '%U'
end
end
end
@@ -77,13 +94,13 @@ feature -- Input
i - index + 1 = nb or last_chunk_size = 0
loop
if i + nb - 1 <= chunk_upper then
last_string.append (last_chunk_data.substring (i - chunk_lower + 1, i - chunk_lower + 1 + nb - 1))
last_string.append (last_chunk_data.substring (chunk_index (i), chunk_index (i) + nb - 1))
i := i + nb - 1
else
-- Need to read new chunk
-- first get all available data from current chunk
if i <= chunk_upper then
last_string.append (last_chunk_data.substring (i - chunk_lower + 1, chunk_upper - chunk_lower + 1))
if i > chunk_upper then
last_string.append (last_chunk_data.substring (i - chunk_lower + 1, chunk_index (chunk_upper)))
i := chunk_upper
end
-- then continue
@@ -166,9 +183,25 @@ feature -- Status report
Result := last_trailer /= Void
end
feature {NONE} -- Parser
feature {NONE} -- Access: chunk
chunk_index (a_index: INTEGER): INTEGER
-- Index in `last_chunk_data' for global input index `a_index'.
do
Result := a_index - chunk_lower + 1
end
index: INTEGER
-- Global input index.
chunk_lower: INTEGER
-- Lower global index for `last_chunk_data'.
chunk_upper: INTEGER
-- Upper global index for `last_chunk_data'.
feature {NONE} -- Chunk parsing
index, chunk_lower, chunk_upper: INTEGER
tmp_hex_chunk_size: STRING_8
read_chunk_block
@@ -190,8 +223,10 @@ feature {NONE} -- Parser
check last_chunk_data.count = last_chunk_size end
l_input := input
check not l_input.end_of_input end
l_input.read_character
check l_input.last_character = '%R' end
check not l_input.end_of_input end
l_input.read_character
check l_input.last_character = '%N' end
end
@@ -214,8 +249,8 @@ feature {NONE} -- Parser
l_input.read_string (last_chunk_size)
last_chunk_data := l_input.last_string
ensure
last_chunk_attached: attached last_chunk_data as el_last_chunk
last_chunk_size_ok: el_last_chunk.count = last_chunk_size
last_chunk_attached: last_chunk_data /= Void
last_chunk_size_ok: last_chunk_data.count = last_chunk_size
end
read_chunk_size
@@ -350,7 +385,7 @@ feature {NONE} -- Implementation
-- Input Stream
;note
copyright: "2011-2013, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
copyright: "2011-2016, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
source: "[
Eiffel Software

View File

@@ -134,7 +134,7 @@ invariant
pool_attached: pool /= Void
note
copyright: "2011-2015, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
copyright: "2011-2016, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
source: "[
Eiffel Software

View File

@@ -50,7 +50,7 @@ feature {CONCURRENT_POOL, HTTPD_CONNECTION_HANDLER_I} -- Basic operation
end
note
copyright: "2011-2015, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
copyright: "2011-2016, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
source: "[
Eiffel Software

View File

@@ -15,7 +15,7 @@ inherit
end
note
copyright: "2011-2015, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
copyright: "2011-2016, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
source: "[
Eiffel Software

View File

@@ -178,7 +178,7 @@ feature {NONE} -- Implementation
end
note
copyright: "2011-2015, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
copyright: "2011-2016, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
source: "[
Eiffel Software

Some files were not shown because too many files have changed in this diff Show More