Compare commits

...

29 Commits

Author SHA1 Message Date
b4fd04ad9f Updated has_incoming_data comment. 2016-10-05 16:19:48 +02:00
71a98f3c28 Make EiffelWeb standalone easier to debug by using in some locations error instead of exception for network error.
- Added C external to use C `recv` feature with error (as opposed to have exception raised on network error).
2016-10-05 10:45:57 +02:00
ed22be2551 Commented the execute_bad_request, since it is not ready and will trigger error most of the time. 2016-10-05 00:04:21 +02:00
77085364ee Improve socket management for EiffelWeb standalone connector. 2016-10-04 18:49:48 +02:00
0217c6d3f4 First attempt to response with bad request message when bad request is detected. 2016-10-04 13:00:38 +02:00
55fec2423c Added ssl test case for standalone wsf connector. 2016-10-04 12:59:56 +02:00
1f7a81a2d6 Updated workbook, minor changes (removed mention about nino, added libfcgi info). 2016-10-04 12:56:11 +02:00
612ff243c1 Also display SSL information when verbose is enabled for EiffelWeb standalone connector. 2016-10-02 20:05:44 +02:00
40fb3893af Include wsf_proxy to the installation process. 2016-09-27 16:18:06 +02:00
21407f8dcf Fixed SSL support on the httpd component, and also on the EiffelWeb standalone connector.
- the standalone connector support for SSL, is using certicate files for now (no in-memory support).
  - to enable ssl support, set ecf variable `httpd_ssl_enabled=true`.
  - added the `simple_ssl` example to demonstrate how to have standalone ssl server.
    (be careful when using EiffelNet SSL and the http_client library, disable the libcurl
      via ecf variable `libcurl_http_client_disabled=true` )

Added support for recv timeout to the EiffelWeb standalone connector.
  - made EiffelWeb compilable with 16.05 and upcoming 16.11.
    Done via ecfs condition on version to accept EiffelNet with recv_timeout (from 16.11), and without (until 16.05).
  - adding recv timeout prevents server to hang for ever if a client wait too long to send data.

Updated various comments.
2016-09-27 16:11:47 +02:00
356eb143ea Fixed the non void-safe ecf for wsf_proxy. 2016-09-26 17:42:49 +02:00
df551d4a4f Use latest API from http_client using DEFAULT_HTTP_CLIENT,
that could use libcurl or EiffelNet depending on the configuration (.ecf).
2016-09-26 13:13:57 +02:00
f010da04e9 Merge branch 'reverse_proxy' 2016-09-19 22:19:21 +02:00
5029049ef0 Replaced host+port by uri (http://remotemachine:port/path).
Added support for SSL (https).
2016-08-08 12:30:28 +02:00
80254b2278 When possible keep ecf location relative within the same EiffelWeb directory structure. 2016-08-06 10:07:42 +02:00
210fae5000 First step towards SSL support. 2016-08-06 10:04:45 +02:00
9cc9b95190 Added a simple reverse proxy handler.
- For now, it does not support SSL connection on the target yet.
- No external config file support, this is all about coding.
2016-08-05 11:38:35 +02:00
8b172b5d33 Revisited WSF_REQUEST.read_input_data* functions:
- read_input_data_into_file now accepts a IO_MEDIUM argument instead of just FILE.
- cleaned the implementation, and make sure that eventual `raw_input_data` is containing only the raw input data.
2016-08-05 11:32:14 +02:00
cc2d7dbb1c Ignore empty header line. 2016-08-05 11:28:59 +02:00
c88394b9fd Added support for category in ATOM format (input and output). 2016-06-24 13:03:09 +02:00
4283662f43 Removed unwanted .ecf file. 2016-06-22 10:55:41 +02:00
1b951376f9 Added more application logic for the example. 2016-06-22 10:52:36 +02:00
193cc3cbde Renamed WGI_STANDALONE_CONNECTOR_ACCESS as WGI_STANDALONE_CONNECTOR_EXPORTER.
Isolate the websocket implementation in descendant of {WEB_SOCKET_EVENT_I}.
Added very simple echo websocket example.
+ code cleaning.
2016-06-22 10:46:15 +02:00
b49e841ac7 Added WSF standalone_websocket connector, that provides websocket on top of standalone connector. 2016-06-21 23:37:48 +02:00
8ba74e1c90 Log when a persistent connection is reused.
Use anchor type on `{WGI_STANDALONE_CONNECTOR}.configuration` and `{WSF_STANDALONE_SERVICE_LAUNCHER}.connector`.
Add access to the socket of standalone input stream from `{WSF_STANDALONE_CONNECTOR_ACCESS}`.
Removed a useless redefination in `WSF_EXECUTION`.
2016-06-21 23:36:22 +02:00
0cecb9594c Fixed signature of {HTTPD_CONFIGURATION_I}.set_ca_key . 2016-06-16 10:37:26 +02:00
e384a6d6ed Make it easier to reuse the http network classes.
This is to make it easier for websocket solution to reuse httpd implementation.
2016-06-16 10:23:30 +02:00
71a5c086a5 Moved httpd from src to lib, under standalone connector. 2016-06-15 18:04:00 +02:00
dfa60bf8f5 Prepared httpd_stream to be useable for client too.
Fixed obsolete tests/dev compilation (mainly to avoid wrong failure reports).
added package.iron files.
2016-06-15 17:56:22 +02:00
108 changed files with 4380 additions and 426 deletions

View File

@@ -101,7 +101,6 @@ Other connectors:
**WSF_STANDALONE_SERVICE_LAUNCHER**
**WSF_CGI_SERVICE_LAUNCHER**
**WSF_NINO_SERVICE_LAUNCHER**
**WSF_LIBFCGI_SERVICE_LAUNCHER**
A basic EWF service inherits from **WSF_DEFAULT_SERVICE**, which has a formal generic that should conform to **WSF_EXECUTION** class with a `make' creation procedure, in our case the class **APPLICATION_EXECUTION**.

View File

@@ -13,7 +13,7 @@ EWF Deployment
4. Deploying EWF FCGI
5. FCGI overview
1. Build EWF application
2. Copy the generated exe file and the www content.htaccess CGI
2. Copy the generated exe file and the www content.htaccess CGI
@@ -25,10 +25,14 @@ EWF Deployment
>Apache Version: Apache 2.4.4
>Windows: http://www.apachelounge.com/download/
note: on linux (debian), use
> sudo apt-get install apache2
#### Deploying EWF CGI
#### CGI overview
>A new process is started for each HTTP request. So if there are N requests to the same >CGI program, the code of the CGI program is loaded into memory N times.
>A new process is started for each HTTP request. So if there are N requests to the same
>CGI program, the code of the CGI program is loaded into memory N times.
>When a CGI program finishes handling a request, the program terminates.
* Build EWF application
@@ -95,6 +99,9 @@ Check that you have the following modules enabled
>To deploy FCGI you will need to download the mod_fcgi module.
>You can get it from here http://www.apachelounge.com/download/
note: on linux (debian), use
> sudo apt-get install libapache2-mod-fastcgi
#### FCGI overview
>FastCGI allows a single, long-running process to handle more than one user request while keeping close to the CGI programming model, retaining the simplicity while eliminating the overhead of creating a new process for each request. Unlike converting an application to a web server plug-in, FastCGI applications remain independent of the web server.
@@ -128,6 +135,22 @@ Copy the app.exe and the folder "www" into a folder served by apache2, for exam
>NOTE: By default Apache does not come with fcgid module, so you will need to download it, and put the module under Apache2/modules
It is also possible to set various parameters in the apache site configuration file such as:
```
<IfModule mod_fcgid.c>
# FcgidIdleTimeout 600
# FcgidBusyScanInterval 120
# FcgidProcessLifeTime 3600
# FcgidMaxProcesses 5
# FcgidMaxProcessesPerClass 100
# FcgidMinProcessesPerClass 100
# FcgidConnectTimeout 8
# FcgidIOTimeout 60
# FcgidBusyTimeout 1200
</IfModule>
```
See https://httpd.apache.org/mod_fcgid/mod/mod_fcgid.html for more information.
# .htaccess FCGI
```

View File

@@ -0,0 +1,29 @@
note
description: "Launcher for reverse proxy web application."
date: "$Date$"
revision: "$Revision$"
class
APPLICATION
inherit
WSF_DEFAULT_SERVICE [APPLICATION_EXECUTION]
redefine
initialize
end
create
make_and_launch
feature {NONE} -- Initialization
initialize
-- Initialize current service.
do
-- Specific to `standalone' connector (the EiffelWeb server).
-- See `{WSF_STANDALONE_SERVICE_LAUNCHER}.initialize'
set_service_option ("port", 9090)
import_service_options (create {WSF_SERVICE_LAUNCHER_OPTIONS_FROM_INI}.make_from_file ("server.ini"))
end
end

View File

@@ -0,0 +1,49 @@
note
description: "Reverse proxy example."
date: "$Date$"
revision: "$Revision$"
class
APPLICATION_EXECUTION
inherit
WSF_EXECUTION
WSF_URI_REWRITER
rename
uri as proxy_uri
end
create
make
feature -- Basic operations
execute
do
-- NOTE: please enter the target server uri here
-- replace "http://localhost:8080/foobar"
send_proxy_response ("http://localhost:8080/foobar", Current)
end
send_proxy_response (a_remote: READABLE_STRING_8; a_rewriter: detachable WSF_URI_REWRITER)
local
h: WSF_SIMPLE_REVERSE_PROXY_HANDLER
do
create h.make (a_remote)
h.set_uri_rewriter (a_rewriter)
h.set_uri_rewriter (create {WSF_AGENT_URI_REWRITER}.make (agent proxy_uri))
h.set_timeout (30) -- 30 seconds
h.set_connect_timeout (5_000) -- milliseconds = 5 seconds
h.execute (request, response)
end
feature -- Helpers
proxy_uri (a_request: WSF_REQUEST): STRING
-- Request uri rewriten as url.
do
Result := a_request.request_uri
end
end

28
examples/proxy/proxy.ecf Normal file
View File

@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-15-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-15-0 http://www.eiffel.com/developers/xml/configuration-1-15-0.xsd" name="proxy" uuid="B55F0D95-3793-4C90-BBAC-BF5F2DECD5E6" library_target="proxy">
<target name="common" abstract="true">
<file_rule>
<exclude>/.svn$</exclude>
<exclude>/CVS$</exclude>
<exclude>/EIFGENs$</exclude>
</file_rule>
<option warning="true" full_class_checking="false" is_attached_by_default="true" void_safety="transitional" syntax="transitional">
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
</option>
<setting name="console_application" value="true"/>
<variable name="ssl_supported" value="false"/>
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
<library name="http" location="..\..\library\network\protocol\http\http-safe.ecf"/>
<library name="wsf" location="..\..\library\server\wsf\wsf-safe.ecf"/>
<library name="wsf_proxy" location="..\..\library\server\wsf_proxy\wsf_proxy-safe.ecf" readonly="false"/>
</target>
<target name="proxy" extends="common">
<root class="APPLICATION" feature="make_and_launch"/>
<option warning="true" is_attached_by_default="true" void_safety="all" syntax="transitional">
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
</option>
<setting name="concurrency" value="scoop"/>
<library name="default_standalone" location="..\..\library\server\wsf\default\standalone-safe.ecf"/>
<cluster name="proxy" location=".\" recursive="true"/>
</target>
</system>

View File

@@ -0,0 +1,8 @@
verbose=true
verbose_level=ALERT
port=9090
#max_concurrent_connections=100
#keep_alive_timeout=15
#max_tcp_clients=100
#socket_timeout=300
#max_keep_alive_requests=300

View File

@@ -24,6 +24,8 @@ feature -- Basic operations
s := "Hello World!"
create dt.make_now_utc
s.append (" (UTC time is " + dt.rfc850_string + ").")
s.append ("%N")
s.append ("Your request: " + request.request_uri + " %N")
response.put_header ({HTTP_STATUS_CODE}.ok, <<["Content-Type", "text/html"], ["Content-Length", s.count.out]>>)
response.set_status_code ({HTTP_STATUS_CODE}.ok)
response.header.put_content_type_text_html

View File

@@ -0,0 +1,29 @@
note
description : "simple application root class"
date : "$Date$"
revision : "$Revision$"
class
APPLICATION
inherit
WSF_DEFAULT_SERVICE [APPLICATION_EXECUTION]
redefine
initialize
end
create
make_and_launch
feature {NONE} -- Initialization
initialize
-- Initialize current service.
do
-- Specific to `standalone' connector (the EiffelWeb server).
-- See `{WSF_STANDALONE_SERVICE_LAUNCHER}.initialize'
set_service_option ("port", 9090)
import_service_options (create {WSF_SERVICE_LAUNCHER_OPTIONS_FROM_INI}.make_from_file ("simple.ini"))
end
end

View File

@@ -0,0 +1,41 @@
note
description : "simple application execution"
date : "$Date$"
revision : "$Revision$"
class
APPLICATION_EXECUTION
inherit
WSF_EXECUTION
create
make
feature -- Basic operations
execute
local
s: STRING
dt: HTTP_DATE
do
-- To send a response we need to setup, the status code and
-- the response headers.
s := "Hello World!"
create dt.make_now_utc
s.append (" (UTC time is " + dt.rfc850_string + ").")
if request.is_https then
s.append ("<p>This is a secured connection! (https)</p>%N")
end
response.put_header ({HTTP_STATUS_CODE}.ok, <<["Content-Type", "text/html"], ["Content-Length", s.count.out]>>)
response.set_status_code ({HTTP_STATUS_CODE}.ok)
response.header.put_content_type_text_html
response.header.put_content_length (s.count)
if attached request.http_connection as l_connection and then l_connection.is_case_insensitive_equal_general ("keep-alive") then
response.header.put_header_key_value ("Connection", "keep-alive")
end
response.put_string (s)
end
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,28 @@
##########################################################
### EiffelWeb settings for related connector ###
### Mostly for EiffelWeb standalone connector ###
### See {WGI_STANDALONE_CONSTANTS} for default values. ###
##########################################################
### Connection settings
port=9090
#max_concurrent_connections=100
#max_tcp_clients=100
### Timeout settings
#socket_timeout=60
#socket_recv_timeout=5
### Persistent connection settings
#keep_alive_timeout=15
#max_keep_alive_requests=100
### SSL settings
# enable SSL, with file certificate.
ssl_enabled=true
ssl_ca_key=simple.key
ssl_ca_crt=simple.crt
### App settings
verbose=true
verbose_level=ALERT

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,27 @@
<?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_ssl" uuid="C2FE296C-3C18-4609-A5AB-F604BDEE4410" library_target="simple_ssl">
<target name="simple_ssl">
<description>Simple EiffelWeb standalone server with SSL support (Concurrent connection supported thanks to SCOOP).</description>
<root class="APPLICATION" feature="make_and_launch"/>
<file_rule>
<exclude>/.svn$</exclude>
<exclude>/CVS$</exclude>
<exclude>/EIFGENs$</exclude>
</file_rule>
<option warning="true" is_attached_by_default="true" void_safety="all" syntax="transitional">
<assertions/>
</option>
<setting name="console_application" value="true"/>
<setting name="concurrency" value="scoop"/>
<variable name="httpd_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="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"/>
</target>
<target name="simple_ssl_st" extends="simple_ssl">
<description>Simple EiffelWeb standalone server with SSL support (Single threaded, thus no concurrent connection.)</description>
<setting name="concurrency" value="none"/>
</target>
</system>

View File

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

View File

@@ -0,0 +1,184 @@
note
description : "simple application execution"
date : "$Date$"
revision : "$Revision$"
class
APPLICATION_EXECUTION
inherit
WSF_WEBSOCKET_EXECUTION
WEB_SOCKET_EVENT_I
create
make
feature -- Basic operations
execute
local
s: STRING
dt: HTTP_DATE
do
-- To send a response we need to setup, the status code and
-- the response headers.
if request.path_info.same_string_general ("/app") then
s := websocket_app_html (9090)
else
s := "Hello World!"
create dt.make_now_utc
s.append (" (UTC time is " + dt.rfc850_string + ").")
s.append ("<p><a href=%"/app%">Websocket demo</a></p>")
end
response.put_header ({HTTP_STATUS_CODE}.ok, <<["Content-Type", "text/html"], ["Content-Length", s.count.out]>>)
response.set_status_code ({HTTP_STATUS_CODE}.ok)
response.header.put_content_type_text_html
response.header.put_content_length (s.count)
if attached request.http_connection as l_connection and then l_connection.is_case_insensitive_equal_general ("keep-alive") then
response.header.put_header_key_value ("Connection", "keep-alive")
end
response.put_string (s)
end
feature -- Websocket execution
new_websocket_handler (ws: WEB_SOCKET): WEB_SOCKET_HANDLER
do
create Result.make (ws, Current)
end
feature -- Websocket execution
on_open (ws: WEB_SOCKET)
do
ws.put_error ("Connecting")
ws.send (Text_frame, "Hello, this is a simple demo with Websocket using Eiffel. (/help for more information).%N")
end
on_binary (ws: WEB_SOCKET; a_message: READABLE_STRING_8)
do
ws.send (Binary_frame, a_message)
end
on_text (ws: WEB_SOCKET; a_message: READABLE_STRING_8)
do
if a_message.same_string_general ("/help") then
-- Echo the message for testing.
ws.send (Text_frame, "Help: available commands%N - /time : return the server UTC time.%N")
elseif a_message.starts_with_general ("/time") then
ws.send (Text_frame, "Server time is " + (create {HTTP_DATE}.make_now_utc).string)
else
-- Echo the message for testing.
ws.send (Text_frame, a_message)
end
end
on_close (ws: WEB_SOCKET)
-- Called after the WebSocket connection is closed.
do
ws.put_error ("Connection closed")
end
feature -- HTML Resource
websocket_app_html (a_port: INTEGER): STRING
do
Result := "[
<!DOCTYPE html>
<html>
<head>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
<script type="text/javascript">
$(document).ready(function() {
var socket;
function connect(){
var host = "ws://127.0.0.1:##PORTNUMBER##";
try{
socket = new WebSocket(host);
message('<p class="event">Socket Status: '+socket.readyState);
socket.onopen = function(){
message('<p class="event">Socket Status: '+socket.readyState+' (open)');
}
socket.onmessage = function(msg){
message('<p class="message">Received: '+msg.data);
}
socket.onclose = function(){
message('<p class="event">Socket Status: '+socket.readyState+' (Closed)');
}
} catch(exception){
message('<p>Error'+exception);
}
}
function send(){
var text = $('#text').val();
if(text==""){
message('<p class="warning">Please enter a message');
return ;
}
try{
socket.send(text);
message('<p class="event">Sent: '+text)
} catch(exception){
message('<p class="warning">');
}
$('#text').val("");
}
function message(msg){
$('#chatLog').append(msg+'</p>');
}//End message()
$('#text').keypress(function(event) {
if (event.keyCode == '13') {
send();
}
});
$('#disconnect').click(function(){
socket.close();
});
if (!("WebSocket" in window)){
$('#chatLog, input, button, #examples').fadeOut("fast");
$('<p>Oh no, you need a browser that supports WebSockets. How about <a href="http://www.google.com/chrome">Google Chrome</a>?</p>').appendTo('#container');
}else{
//The user has WebSockets
connect();
}
});
</script>
<meta charset="utf-8" />
<style type="text/css">
body {font-family:Arial, Helvetica, sans-serif;}
#container { border:5px solid grey; width:800px; margin:0 auto; padding:10px; }
#chatLog { padding:5px; border:1px solid black; }
#chatLog p {margin:0;}
.event {color:#999;}
.warning { font-weight:bold; color:#CCC; }
</style>
<title>WebSockets Client</title>
</head>
<body>
<div id="wrapper">
<div id="container">
<h1>WebSockets Client</h1>
<div id="chatLog"></div>
<input id="text" type="text" />
<button id="disconnect">Disconnect</button>
</div>
</div>
</body>
</html>
]"
Result.replace_substring_all ("##PORTNUMBER##", a_port.out)
end
end

View File

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

View File

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

View File

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

View File

@@ -508,9 +508,9 @@ feature -- Helper
new_session (a_uri: READABLE_STRING_8): HTTP_CLIENT_SESSION
local
cl: LIBCURL_HTTP_CLIENT
cl: DEFAULT_HTTP_CLIENT
do
create cl.make
create cl
Result := cl.new_session (a_uri)
Result.set_is_insecure (True)
Result.set_max_redirects (5)

View File

@@ -25,20 +25,27 @@ feature {CONCURRENT_POOL, HTTPD_CONNECTION_HANDLER_I} -- Basic operation
release
-- <Precursor>
local
d: STRING
d: detachable STRING
do
if attached internal_client_socket as l_socket then
d := l_socket.descriptor.out
else
d := "N/A"
end
debug ("dbglog")
if
attached internal_client_socket as l_socket and then
l_socket.descriptor_available
then
d := l_socket.descriptor.out
else
d := "N/A"
end
dbglog (generator + ".release: ENTER {" + d + "}")
end
Precursor {HTTPD_REQUEST_HANDLER_I}
release_pool_item
debug ("dbglog")
dbglog (generator + ".release: LEAVE {" + d + "}")
if d /= Void then
dbglog (generator + ".release: LEAVE {" + d + "}")
else
dbglog (generator + ".release: LEAVE {N/A}")
end
end
end

View File

@@ -18,21 +18,28 @@ inherit
feature {HTTPD_CONNECTION_HANDLER_I} -- Basic operation
release
-- <Precursor>
local
d: STRING
d: detachable STRING
do
-- FIXME: for log purpose
if attached internal_client_socket as l_socket then
d := l_socket.descriptor.out
else
d := "N/A"
end
debug ("dbglog")
if
attached internal_client_socket as l_socket and then
l_socket.descriptor_available
then
d := l_socket.descriptor.out
else
d := "N/A"
end
dbglog (generator + ".release: ENTER {" + d + "}")
end
Precursor {HTTPD_REQUEST_HANDLER_I}
debug ("dbglog")
dbglog (generator + ".release: LEAVE {" + d + "}")
if d /= Void then
dbglog (generator + ".release: LEAVE {" + d + "}")
else
dbglog (generator + ".release: LEAVE {N/A}")
end
end
end

View File

@@ -6,6 +6,11 @@ note
deferred class
HTTPD_CONFIGURATION_I
inherit
ANY
HTTPD_CONSTANTS
feature {NONE} -- Initialization
make
@@ -14,6 +19,7 @@ feature {NONE} -- Initialization
max_concurrent_connections := default_max_concurrent_connections
max_tcp_clients := default_max_tcp_clients
socket_timeout := default_socket_timeout
socket_recv_timeout := default_socket_recv_timeout
keep_alive_timeout := default_keep_alive_timeout
max_keep_alive_requests := default_max_keep_alive_requests
is_secure := False
@@ -21,15 +27,6 @@ feature {NONE} -- Initialization
create ca_key.make_empty
end
feature -- Defaults
default_http_server_port: INTEGER = 80
default_max_concurrent_connections: INTEGER = 100
default_max_tcp_clients: INTEGER = 100
default_socket_timeout: INTEGER = 300 -- seconds
default_keep_alive_timeout: INTEGER = 15 -- seconds
default_max_keep_alive_requests: INTEGER = 100
feature -- Access
Server_details: STRING_8
@@ -45,7 +42,12 @@ feature -- Access
socket_timeout: INTEGER assign set_socket_timeout
-- Amount of seconds that the server waits for receipts and transmissions during communications.
-- note: with timeout of 0, socket can wait for ever.
-- By default: 300 seconds, which is appropriate for most situations.
-- By default: 60 seconds, which is appropriate for most situations.
socket_recv_timeout: INTEGER assign set_socket_recv_timeout
-- Amount of seconds that the server waits for receiving data during communications.
-- note: with timeout of 0, socket can wait for ever.
-- By default: 5 seconds.
max_concurrent_connections: INTEGER assign set_max_concurrent_connections
-- Max number of concurrent connections.
@@ -83,8 +85,10 @@ feature -- Access
Result.is_verbose := is_verbose
Result.verbose_level := verbose_level
Result.timeout := socket_timeout
Result.socket_recv_timeout := socket_recv_timeout
Result.keep_alive_timeout := keep_alive_timeout
Result.max_keep_alive_requests := max_keep_alive_requests
Result.is_secure := is_secure
end
feature -- Access: SSL
@@ -92,10 +96,10 @@ feature -- Access: SSL
is_secure: BOOLEAN
-- Is SSL/TLS session?.
ca_crt: STRING
ca_crt: detachable IMMUTABLE_STRING_32
-- the signed certificate.
ca_key: STRING
ca_key: detachable IMMUTABLE_STRING_32
-- private key to the certificate.
ssl_protocol: NATURAL
@@ -103,6 +107,22 @@ feature -- Access: SSL
feature -- Element change
set_ssl_settings (v: detachable separate TUPLE [protocol: separate READABLE_STRING_GENERAL; ca_crt, ca_key: detachable separate READABLE_STRING_GENERAL])
local
prot: STRING_32
do
is_secure := False
ca_crt := Void
ca_key := Void
if v /= Void then
is_secure := True
create prot.make_from_separate (v.protocol)
set_ssl_protocol_from_string (prot)
set_ca_crt (v.ca_crt)
set_ca_key (v.ca_key)
end
end
set_http_server_name (v: detachable separate READABLE_STRING_8)
do
if v = Void then
@@ -152,6 +172,14 @@ feature -- Element change
socket_timeout_set: socket_timeout = a_nb_seconds
end
set_socket_recv_timeout (a_nb_seconds: like socket_recv_timeout)
-- Set `socket_recv_timeout' with `a_nb_seconds'
do
socket_recv_timeout := a_nb_seconds
ensure
socket_recv_timeout_set: socket_recv_timeout = a_nb_seconds
end
set_keep_alive_timeout (a_seconds: like keep_alive_timeout)
-- Set `keep_alive_timeout' with `a_seconds'
do
@@ -198,17 +226,33 @@ feature -- Element change
verbose_level_set: verbose_level = lev
end
mark_secure
-- Set is_secure in True
set_is_secure (b: BOOLEAN)
-- Set `is_secure' to `b'.
do
if has_ssl_support then
if b and has_ssl_support then
is_secure := True
if http_server_port = 80 then
if
http_server_port = 80
then
set_http_server_port (443)
end
else
is_secure := False
if
http_server_port = 443
then
set_http_server_port (80)
end
end
ensure
is_secure_set: has_ssl_support implies is_secure
is_not_secure: not has_ssl_support implies not is_secure
end
mark_secure
-- Set is_secure in True
do
set_is_secure (True)
ensure
is_secure_set: has_ssl_support implies is_secure
-- http_server_port_set: has_ssl_support implies http_server_port = 443
@@ -218,20 +262,24 @@ feature -- Element change
feature -- Element change
set_ca_crt (a_value: STRING)
-- Set `ca_crt' with `a_value'
set_ca_crt (a_value: detachable separate READABLE_STRING_GENERAL)
-- Set `ca_crt' from `a_value'.
do
ca_crt := a_value
ensure
ca_crt_set: ca_crt = a_value
if a_value /= Void then
create ca_crt.make_from_separate (a_value)
else
ca_crt := Void
end
end
set_ca_key (a_value: STRING)
-- Set `ca_key' with `a_value'
set_ca_key (a_value: detachable separate READABLE_STRING_GENERAL)
-- Set `ca_key' with `a_value'.
do
ca_key := a_value
ensure
ca_key_set: ca_key = a_value
if a_value /= Void then
create ca_key.make_from_separate (a_value)
else
ca_key := Void
end
end
set_ssl_protocol (a_version: NATURAL)
@@ -242,6 +290,24 @@ feature -- Element change
ssl_protocol_set: ssl_protocol = a_version
end
set_ssl_protocol_from_string (a_ssl_version: READABLE_STRING_GENERAL)
-- Set `ssl_protocol' with `a_ssl_version'
do
if a_ssl_version.is_case_insensitive_equal ("ssl_2_3") then
set_ssl_protocol_to_ssl_2_or_3
elseif a_ssl_version.is_case_insensitive_equal ("tls_1_0") then
set_ssl_protocol_to_tls_1_0
elseif a_ssl_version.is_case_insensitive_equal ("tls_1_1") then
set_ssl_protocol_to_tls_1_1
elseif a_ssl_version.is_case_insensitive_equal ("tls_1_2") then
set_ssl_protocol_to_tls_1_2
elseif a_ssl_version.is_case_insensitive_equal ("dtls_1_0") then
set_ssl_protocol_to_dtls_1_0
else -- Default
set_ssl_protocol_to_tls_1_2
end
end
feature -- SSL Helpers
set_ssl_protocol_to_ssl_2_or_3

View File

@@ -0,0 +1,28 @@
note
description: "[
Various constant values used in httpd settings.
]"
author: "$Author$"
date: "$Date$"
revision: "$Revision$"
deferred class
HTTPD_CONSTANTS
feature -- Default connection settings
default_http_server_port: INTEGER = 80
default_max_concurrent_connections: INTEGER = 100
default_max_tcp_clients: INTEGER = 100
feature -- Default timeout settings
default_socket_timeout: INTEGER = 60 -- seconds
default_socket_recv_timeout: INTEGER = 5 -- seconds
feature -- Default persistent connection settings
default_keep_alive_timeout: INTEGER = 15 -- seconds
default_max_keep_alive_requests: INTEGER = 100
end

View File

@@ -17,9 +17,15 @@ feature -- Access
verbose_level: INTEGER assign set_verbose_level
-- Verbosity of output.
is_secure: BOOLEAN assign set_is_secure
-- Is using secure connection? i.e SSL?
timeout: INTEGER assign set_timeout
-- Amount of seconds that the server waits for receipts and transmissions during communications.
socket_recv_timeout: INTEGER assign set_socket_recv_timeout
-- Amount of seconds that the server waits for receiving data on socket during communications.
keep_alive_timeout: INTEGER assign set_keep_alive_timeout
-- Keep-alive timeout, also known as persistent-connection timeout.
-- Number of seconds the server waits after a request has been served before it closes the connection.
@@ -42,12 +48,24 @@ feature -- Change
verbose_level := lev
end
set_is_secure (b: BOOLEAN)
-- Set `is_secure' to `b'.
do
is_secure := b
end
set_timeout (a_timeout_in_seconds: INTEGER)
-- Set `timeout' to `a_timeout_in_seconds'.
do
timeout := a_timeout_in_seconds
end
set_socket_recv_timeout (a_timeout_in_seconds: INTEGER)
-- Set `socket_recv_timeout' to `a_timeout_in_seconds'.
do
socket_recv_timeout := a_timeout_in_seconds
end
set_keep_alive_timeout (a_timeout_in_seconds: INTEGER)
-- Set `keep_alive_timeout' to `a_timeout_in_seconds'.
do

View File

@@ -0,0 +1,38 @@
<?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\">
<cluster name="ssl_network" location="$|ssl\" recursive="true">
<condition>
<custom name="net_ssl_enabled" value="true"/>
</condition>
</cluster>
<cluster name="network_until_16_05" location="$|until_16_05\">
<condition>
<version type="compiler" max="16.11.0.0"/>
</condition>
</cluster>
<cluster name="network_from_16_11" location="$|from_16_11\">
<condition>
<version type="compiler" min="16.11.0.0"/>
</condition>
</cluster>
</cluster>
</target>
</system>

View File

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

View File

@@ -24,11 +24,29 @@
</condition>
</library>
<library name="time" location="$ISE_LIBRARY\library\time\time-safe.ecf"/>
<cluster name="network" location=".\network" recursive="false">
<cluster name="ssl_network" location="$|ssl" recursive="true">
<condition>
<custom name="httpd_ssl_enabled" value="true"/>
</condition>
</cluster>
<cluster name="network_until_16_05" location="$|until_16_05\">
<condition>
<version type="compiler" max="16.11.0.0"/>
</condition>
</cluster>
<cluster name="network_from_16_11" location="$|from_16_11\">
<condition>
<version type="compiler" min="16.11.0.0"/>
</condition>
</cluster>
</cluster>
<cluster name="httpd_server" location=".\" recursive="true">
<file_rule>
<exclude>/concurrency$</exclude>
<exclude>/no_ssl$</exclude>
<exclude>/ssl$</exclude>
<exclude>/network$</exclude>
</file_rule>
<cluster name="no_ssl" location="$|no_ssl\" recursive="true">
<condition>

View File

@@ -11,7 +11,6 @@
</option>
<setting name="concurrency" value="thread"/>
<library name="base" location="$ISE_LIBRARY\library\base\base.ecf"/>
<library name="encoder" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\text\encoder\encoder.ecf"/>
<library name="net" location="$ISE_LIBRARY\library\net\net.ecf"/>
<library name="net_ssl" location="$ISE_LIBRARY\unstable\library\network\socket\netssl\net_ssl.ecf">
<condition>
@@ -24,12 +23,29 @@
</condition>
</library>
<library name="time" location="$ISE_LIBRARY\library\time\time.ecf"/>
<cluster name="network" location=".\network" recursive="false">
<cluster name="ssl_network" location="$|ssl" recursive="true">
<condition>
<custom name="httpd_ssl_enabled" value="true"/>
</condition>
</cluster>
<cluster name="network_until_16_05" location="$|until_16_05\">
<condition>
<version type="compiler" max="16.11.0.0"/>
</condition>
</cluster>
<cluster name="network_from_16_11" location="$|from_16_11\">
<condition>
<version type="compiler" min="16.11.0.0"/>
</condition>
</cluster>
</cluster>
<cluster name="httpd_server" location=".\" recursive="true">
<file_rule>
<exclude>/EIFGENs$</exclude>
<exclude>/concurrency$</exclude>
<exclude>/no_ssl$</exclude>
<exclude>/ssl$</exclude>
<exclude>/network$</exclude>
</file_rule>
<cluster name="no_ssl" location="$|no_ssl\" recursive="true">
<condition>

View File

@@ -11,6 +11,8 @@ inherit
HTTPD_LOGGER_CONSTANTS
HTTPD_SOCKET_FACTORY
feature {NONE} -- Initialization
make (a_request_settings: HTTPD_REQUEST_SETTINGS)
@@ -18,18 +20,20 @@ feature {NONE} -- Initialization
reset
-- Import global request settings.
timeout := a_request_settings.timeout -- seconds
socket_recv_timeout := a_request_settings.socket_recv_timeout -- seconds
keep_alive_timeout := a_request_settings.keep_alive_timeout -- seconds
max_keep_alive_requests := a_request_settings.max_keep_alive_requests
is_verbose := a_request_settings.is_verbose
verbose_level := a_request_settings.verbose_level
is_secure := a_request_settings.is_secure
end
reset
do
reset_request
has_error := False
reset_error
if attached internal_client_socket as l_sock then
l_sock.cleanup
end
@@ -68,7 +72,7 @@ feature -- Access
do
s := internal_client_socket
if s = Void then
create s.make_empty
s := new_client_socket (is_secure)
internal_client_socket := s
end
Result := s
@@ -121,6 +125,10 @@ feature -- Settings
verbose_level: INTEGER
-- Output verbosity.
is_secure: BOOLEAN
-- Is secure socket?
-- i.e: SSL?
is_persistent_connection_supported: BOOLEAN
-- Is persistent connection supported?
do
@@ -134,6 +142,9 @@ feature -- Settings
timeout: INTEGER -- seconds
-- Amount of seconds that the server waits for receipts and transmissions during communications.
socket_recv_timeout: INTEGER -- seconds
-- Amount of seconds that the server waits for receiving data on socket during communications.
max_keep_alive_requests: INTEGER
-- Maximum number of requests allowed per persistent connection.
@@ -145,6 +156,20 @@ feature -- Status report
has_error: BOOLEAN
-- Error occurred during `analyze_request_message'
feature -- Status change
report_error (m: detachable READABLE_STRING_GENERAL)
-- Report error occurred, with optional message `m'.
do
has_error := True
end
reset_error
-- Reset previous error for current request handler.
do
has_error := False
end
feature -- Change
set_is_verbose (b: BOOLEAN)
@@ -187,6 +212,7 @@ feature -- Execution
n,m: INTEGER
do
l_socket := client_socket
l_socket.set_recv_timeout (socket_recv_timeout)
check
socket_attached: l_socket /= Void
socket_valid: l_socket.is_open_read and then l_socket.is_open_write
@@ -202,25 +228,40 @@ feature -- Execution
n := n + 1
if n >= m then
is_next_persistent_connection_supported := False
elseif n > 1 and is_verbose then
log ("Reuse connection (" + n.out + ")", information_level)
end
-- FIXME: it seems to be called one more time, mostly to see this is done.
execute_request
execute_request (n > 1)
l_exit := not is_persistent_connection_supported
or not is_next_persistent_connection_supported -- related to `max_keep_alive_requests'
or not is_persistent_connection_requested
or has_error or l_socket.is_closed or not l_socket.is_open_read
reset_request
end
if l_exit and has_error and not l_socket.is_closed then
l_socket.close
end
end
execute_request
execute_request (a_is_reusing_connection: BOOLEAN)
-- Execute http request, and if `a_is_reusing_connection' is True
-- the execution is reusing the persistent connection.
require
is_connected: is_connected
reuse_connection_when_possible: a_is_reusing_connection implies is_persistent_connection_supported
no_error: not has_error
local
l_remote_info: detachable like remote_info
l_socket: like client_socket
l_is_ready: BOOLEAN
do
debug ("dbglog")
if a_is_reusing_connection then
dbglog ("execute_request: wait on persistent connection.")
end
end
reset_error
l_socket := client_socket
check
socket_attached: l_socket /= Void
@@ -228,20 +269,23 @@ feature -- Execution
end
if l_socket.is_closed then
debug ("dbglog")
dbglog (generator + ".execute_request {socket is Closed!}")
dbglog ("execute_request {socket is Closed!}")
end
else
debug ("dbglog")
dbglog (generator + ".execute_request socket=" + l_socket.descriptor.out + " ENTER")
dbglog ("execute_request socket=" + l_socket.descriptor.out + " ENTER")
end
--| TODO: add configuration options for socket timeout.
--| set by default 5 seconds.
l_socket.set_timeout (keep_alive_timeout) -- 5 seconds!
l_is_ready := l_socket.ready_for_reading
if a_is_reusing_connection then
--| set by default 5 seconds.
l_socket.set_recv_timeout (keep_alive_timeout) -- in seconds!
l_is_ready := socket_has_incoming_data (l_socket)
else
l_is_ready := True
end
if l_is_ready then
l_socket.set_timeout (timeout) -- FIXME: return a 408 Request Timeout response ..
l_socket.set_recv_timeout (socket_recv_timeout) -- FIXME: return a 408 Request Timeout response ..
create l_remote_info
if attached l_socket.peer_address as l_addr then
l_remote_info.addr := l_addr.host_address.host_address
@@ -250,28 +294,31 @@ feature -- Execution
remote_info := l_remote_info
end
analyze_request_message (l_socket)
else
has_error := True
debug ("dbglog")
dbglog (generator + ".execute_request socket=" + l_socket.descriptor.out + "} timeout!")
end
end
if has_error then
if l_is_ready then
if has_error then
-- check catch_bad_incoming_connection: False end
if is_verbose then
log (request_header + "%NWARNING: invalid HTTP incoming request", warning_level)
end
end
else
if is_verbose then
log (request_header, information_level)
end
process_request (l_socket)
end
process_bad_request (l_socket)
is_persistent_connection_requested := False
else
if is_verbose then
log (request_header, information_level)
end
process_request (l_socket)
end
else
check is_reusing_connection: a_is_reusing_connection end
-- Close persistent connection, since no new connection occurred in the delay `keep_alive_timeout'.
is_persistent_connection_requested := False
debug ("dbglog")
dbglog ("execute_request socket=" + l_socket.descriptor.out + "} close persistent connection.")
end
end
debug ("dbglog")
dbglog (generator + ".execute_request {" + l_socket.descriptor.out + "} LEAVE")
dbglog ("execute_request {" + l_socket.descriptor.out + "} LEAVE")
end
end
end
@@ -284,7 +331,7 @@ feature -- Execution
feature -- Request processing
process_request (a_socket: HTTPD_STREAM_SOCKET)
-- Process request ...
-- Process request on socket `a_socket'.
require
no_error: not has_error
a_uri_attached: uri /= Void
@@ -295,6 +342,39 @@ feature -- Request processing
deferred
end
process_bad_request (a_socket: HTTPD_STREAM_SOCKET)
-- Process bad request catched on `a_socket'.
require
has_error: has_error
a_socket_attached: a_socket /= Void
local
-- h: STRING
-- s: STRING
do
-- NOTE: this is experiment code, and not ready yet.
-- if a_socket.ready_for_writing then
-- s := "{
--<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
--<html><head>
--<title>400 Bad Request</title>
--</head><body>
--<h1>Bad Request</h1>
--</body></html>
-- }"
-- create h.make (1_024)
-- h.append ("HTTP/1.1 400 Bad Request%R%N")
-- h.append ("Content-Length: " + s.count.out + "%R%N")
-- h.append ("Connection: close%R%N")
-- h.append ("Content-Type: text/html; charset=iso-8859-1%R%N")
-- h.append ("%R%N")
-- a_socket.put_string (h)
-- if a_socket.ready_for_writing then
-- a_socket.put_string (s)
-- end
-- end
end
feature -- Parsing
analyze_request_message (a_socket: HTTPD_STREAM_SOCKET)
@@ -313,19 +393,23 @@ feature -- Parsing
request_header := txt
if
not has_error and then
a_socket.is_readable and then
attached next_line (a_socket) as l_request_line and then
not l_request_line.is_empty
a_socket.readable
then
txt.append (l_request_line)
txt.append_character ('%N')
analyze_request_line (l_request_line)
if
attached next_line (a_socket) as l_request_line and then
not l_request_line.is_empty
then
txt.append (l_request_line)
txt.append_character ('%N')
analyze_request_line (l_request_line)
else
report_error ("Bad header line (empty)")
end
else
has_error := True
report_error ("Socket is not readable")
end
l_is_verbose := is_verbose
if not has_error or l_is_verbose then
-- if `is_verbose' we can try to print the request, even if it is a bad HTTP request
if not has_error then
from
line := next_line (a_socket)
until
@@ -397,7 +481,9 @@ feature -- Parsing
n := n - 1
end
version := line.substring (next_pos + 1, n)
has_error := method.is_empty
if method.is_empty then
report_error ("Missing request method data")
end
end
next_line (a_socket: HTTPD_STREAM_SOCKET): detachable STRING
@@ -409,22 +495,26 @@ feature -- Parsing
retried: BOOLEAN
do
if retried then
has_error := True
report_error ("Rescue in next_line")
Result := Void
elseif a_socket.readable then
elseif
a_socket.readable and then
socket_has_incoming_data (a_socket)
then
a_socket.read_line_thread_aware
Result := a_socket.last_string
-- Do no check `socket_ok' before socket operation,
-- otherwise it may be False, due to error during other socket operation in same thread.
if not a_socket.socket_ok then
has_error := True
report_error ("Socket error")
if is_verbose then
log (request_header +"%N" + Result + "%N## socket_ok=False! ##", debug_level)
end
end
else
-- Error with socket...
has_error := True
report_error ("Socket error: not readable")
if is_verbose then
log (request_header + "%N## Socket is not readable! ##", debug_level)
end
@@ -460,6 +550,17 @@ feature -- Output
end
end
feature {NONE} -- Helpers
socket_has_incoming_data (a_socket: HTTPD_STREAM_SOCKET): BOOLEAN
-- Is there any data to read on `a_socket' ?
require
a_socket.readable
do
-- FIXME: check if both are really needed.
Result := a_socket.ready_for_reading and then a_socket.has_incoming_data
end
invariant
request_header_attached: request_header /= Void

View File

@@ -108,8 +108,18 @@ feature -- Execution
log (" - max_tcp_clients = " + configuration.max_tcp_clients.out)
log (" - max_concurrent_connections = " + configuration.max_concurrent_connections.out)
log (" - socket_timeout = " + configuration.socket_timeout.out + " seconds")
log (" - socket_recv_timeout = " + configuration.socket_recv_timeout.out + " seconds")
log (" - keep_alive_timeout = " + configuration.keep_alive_timeout.out + " seconds")
log (" - max_keep_alive_requests = " + configuration.max_keep_alive_requests.out)
if configuration.has_ssl_support then
if configuration.is_secure then
log (" - SSL = enabled")
else
log (" - SSL = disabled")
end
else
log (" - SSL = not supported")
end
if configuration.verbose_level > 0 then
log (" - verbose_level = " + configuration.verbose_level.out)
end

View File

@@ -0,0 +1,9 @@
note
description: "[
Since 16.11, the EiffelNet socket interface has recv_timeout and send_timeout.
]"
deferred class
TCP_STREAM_SOCKET_EXT
end

View File

@@ -12,6 +12,8 @@ class
create
make_server_by_address_and_port,
make_server_by_port,
make_client_by_address_and_port,
make_client_by_port,
make_from_separate,
make_empty
@@ -30,6 +32,16 @@ feature {NONE} -- Initialization
create {TCP_STREAM_SOCKET} socket.make_server_by_port (a_port)
end
make_client_by_address_and_port (an_address: INET_ADDRESS; a_port: INTEGER)
do
create {TCP_STREAM_SOCKET} socket.make_client_by_address_and_port (an_address, a_port)
end
make_client_by_port (a_peer_port: INTEGER; a_peer_host: STRING)
do
create {TCP_STREAM_SOCKET} socket.make_client_by_port (a_peer_port, a_peer_host)
end
make_from_separate (s: separate HTTPD_STREAM_SOCKET)
require
descriptor_available: s.descriptor_available
@@ -50,6 +62,7 @@ feature {NONE} -- Initialization
feature -- Change
set_timeout (n: INTEGER)
-- Set timeout to `n' seconds.
do
if attached {NETWORK_STREAM_SOCKET} socket as l_socket then
l_socket.set_timeout (n)
@@ -70,6 +83,22 @@ feature -- Change
end
end
set_recv_timeout (a_timeout_seconds: INTEGER)
-- Set the receive timeout in seconds on Current socket.
do
if attached {TCP_STREAM_SOCKET} socket as l_socket then
l_socket.set_recv_timeout (a_timeout_seconds)
end
end
set_send_timeout (a_timeout_seconds: INTEGER)
-- Set the send timeout in seconds on Current socket.
do
if attached {TCP_STREAM_SOCKET} socket as l_socket then
l_socket.set_send_timeout (a_timeout_seconds)
end
end
feature -- Access
last_string: STRING
@@ -112,6 +141,15 @@ feature -- Input
socket.read_character
end
peek_stream (nb_char: INTEGER)
require
nb_char_positive: nb_char > 0
do
if attached {TCP_STREAM_SOCKET} socket as l_socket then
l_socket.peek_stream (nb_char)
end
end
bytes_read: INTEGER
do
Result := socket.bytes_read
@@ -164,6 +202,11 @@ feature -- Status Report
end
end
exists: BOOLEAN
do
Result := socket.exists
end
is_blocking: BOOLEAN
do
Result := socket.is_blocking
@@ -176,6 +219,13 @@ feature -- Status Report
end
end
is_connected: BOOLEAN
do
if attached {TCP_STREAM_SOCKET} socket as l_socket then
Result := l_socket.is_connected
end
end
is_created: BOOLEAN
do
if attached {NETWORK_SOCKET} socket as l_socket then
@@ -220,6 +270,16 @@ feature -- Status Report
end
end
connect
do
socket.connect
end
close
do
socket.close
end
listen (a_queue: INTEGER)
do
socket.listen (a_queue)
@@ -257,6 +317,15 @@ feature -- Status Report
Result := socket.readable
end
has_incoming_data: BOOLEAN
-- Check if Current has available data to be read.
-- note: no data will not be removed from the queue.
do
if attached {TCP_STREAM_SOCKET} socket as l_socket then
Result := l_socket.has_incoming_data
end
end
ready_for_reading: BOOLEAN
do
if attached {TCP_STREAM_SOCKET} socket as l_socket then

View File

@@ -17,36 +17,66 @@ inherit
ready_for_writing,
ready_for_reading,
try_ready_for_reading,
put_readable_string_8
put_readable_string_8,
make_empty
end
create
make_ssl_server_by_address_and_port, make_ssl_server_by_port,
make_server_by_address_and_port, make_server_by_port
make_server_by_address_and_port, make_server_by_port,
make_ssl_client_by_address_and_port, make_ssl_client_by_port,
make_client_by_address_and_port, make_client_by_port,
make_empty
create {HTTPD_STREAM_SOCKET}
make
feature {NONE} -- Initialization
make_ssl_server_by_address_and_port (an_address: INET_ADDRESS; a_port: INTEGER; a_ssl_protocol: NATURAL; a_crt: STRING; a_key: STRING)
make_ssl_server_by_address_and_port (an_address: INET_ADDRESS; a_port: INTEGER; a_ssl_protocol: NATURAL; a_crt_fn, a_key_fn: detachable READABLE_STRING_GENERAL)
local
l_socket: SSL_TCP_STREAM_SOCKET
do
create l_socket.make_server_by_address_and_port (an_address, a_port)
l_socket.set_tls_protocol (a_ssl_protocol)
socket := l_socket
set_certificates (a_crt, a_key)
set_certificates (a_crt_fn, a_key_fn)
end
make_ssl_server_by_port (a_port: INTEGER; a_ssl_protocol: NATURAL; a_crt: STRING; a_key: STRING)
make_ssl_server_by_port (a_port: INTEGER; a_ssl_protocol: NATURAL; a_crt_fn, a_key_fn: detachable READABLE_STRING_GENERAL)
local
l_socket: SSL_TCP_STREAM_SOCKET
do
create l_socket.make_server_by_port (a_port)
l_socket.set_tls_protocol (a_ssl_protocol)
socket := l_socket
set_certificates (a_crt, a_key)
set_certificates (a_crt_fn, a_key_fn)
end
make_ssl_client_by_address_and_port (an_address: INET_ADDRESS; a_port: INTEGER; a_ssl_protocol: NATURAL; a_crt_fn, a_key_fn: detachable READABLE_STRING_GENERAL)
local
l_socket: SSL_TCP_STREAM_SOCKET
do
create l_socket.make_client_by_address_and_port (an_address, a_port)
l_socket.set_tls_protocol (a_ssl_protocol)
socket := l_socket
set_certificates (a_crt_fn, a_key_fn)
end
make_ssl_client_by_port (a_peer_port: INTEGER; a_peer_host: STRING; a_ssl_protocol: NATURAL; a_crt_fn, a_key_fn: detachable READABLE_STRING_GENERAL)
local
l_socket: SSL_TCP_STREAM_SOCKET
do
create l_socket.make_client_by_port (a_peer_port, a_peer_host)
l_socket.set_tls_protocol (a_ssl_protocol)
socket := l_socket
set_certificates (a_crt_fn, a_key_fn)
end
make_empty
-- <Precursor>.
do
create {SSL_TCP_STREAM_SOCKET} socket.make_empty
end
feature -- Output
@@ -114,15 +144,15 @@ feature -- Status Report
feature {HTTPD_STREAM_SOCKET} -- Implementation
set_certificates (a_crt: STRING; a_key: STRING)
local
a_file_name: FILE_NAME
set_certificates (a_crt_filename, a_key_filename: detachable READABLE_STRING_GENERAL)
do
if attached {SSL_NETWORK_STREAM_SOCKET} socket as l_socket then
create a_file_name.make_from_string (a_crt)
l_socket.set_certificate_file_name (a_file_name)
create a_file_name.make_from_string (a_key)
l_socket.set_key_file_name (a_file_name)
if a_crt_filename /= Void then
l_socket.set_certificate_file_name (a_crt_filename)
end
if a_key_filename /= Void then
l_socket.set_key_file_name (a_key_filename)
end
end
end

View File

@@ -12,6 +12,7 @@ inherit
create
make_server_by_address_and_port, make_server_by_port,
make_client_by_address_and_port, make_client_by_port,
make_empty
create {SSL_NETWORK_STREAM_SOCKET}

View File

@@ -12,9 +12,13 @@ inherit
make
end
TCP_STREAM_SOCKET_EXT
create
make_server_by_address_and_port,
make_server_by_port,
make_client_by_address_and_port,
make_client_by_port,
make_from_separate,
make_empty
@@ -51,6 +55,38 @@ feature {NONE} -- Initialization
feature -- Basic operation
peek_stream (nb_char: INTEGER)
-- Read a string of at most `nb_char' characters without removing the data from the queue.
-- Make result available in last_string.
require
readable: readable
socket_exists: exists
local
ext: C_STRING
retval: INTEGER
l: like last_string
do
create ext.make_empty (nb_char + 1)
retval := clib_recv (descriptor, ext.item, nb_char, c_peekmsg)
if retval = 0 then
last_string.wipe_out
socket_error := Void
elseif retval > 0 then
ext.set_count (retval)
l := last_string
l.wipe_out
l.grow (retval)
l.set_count (retval)
ext.read_substring_into (l, 1, retval)
socket_error := Void
else
last_string.wipe_out
socket_error := "Socket error (MSG_PEEK)"
end
ensure
last_string_not_void: last_string /= Void
end
send_message (a_msg: STRING)
do
put_string (a_msg)
@@ -69,6 +105,16 @@ feature -- Output
feature -- Status report
has_incoming_data: BOOLEAN
-- Check if Current has available data to be read.
-- note: no data will not be removed from the queue.
require
socket_exists: exists
do
peek_stream (1)
Result := last_string.count = 1
end
try_ready_for_reading: BOOLEAN
-- Is data available for reading from the socket right now?
require
@@ -80,6 +126,19 @@ feature -- Status report
Result := (retval > 0)
end
feature {NONE} -- C implementation
clib_recv (a_fd: INTEGER; buf: POINTER; len: INTEGER; flags: INTEGER): INTEGER
-- External routine to receive at most `len' number of
-- bytes into buffer `buf' from socket `fd' with `flags' options.
external
"C inline"
alias
"[
return (EIF_INTEGER) recv ((int) $a_fd, (char *) $buf, (int) $len, (int) $flags);
]"
end
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)"

View File

@@ -0,0 +1,95 @@
note
description: "[
Until 16.05, the EiffelNet socket interface DOES NOT have recv_timeout and send_timeout.
]"
deferred class
TCP_STREAM_SOCKET_EXT
feature -- Access
descriptor: INTEGER
-- Socket descriptor of current socket
deferred
end
feature -- Socket Recv and Send timeout.
-- recv_timeout: INTEGER
-- -- Receive timeout in seconds on Current socket.
-- do
-- Result := c_get_sock_recv_timeout (descriptor, level_sol_socket)
-- ensure
-- result_not_negative: Result >= 0
-- end
--
-- send_timeout: INTEGER
-- -- Send timeout in seconds on Current socket.
-- do
-- Result := c_get_sock_send_timeout (descriptor, level_sol_socket)
-- ensure
-- result_not_negative: Result >= 0
-- end
set_recv_timeout (a_timeout_seconds: INTEGER)
-- Set the receive timeout in seconds on Current socket.
-- if `0' the related operations will never timeout.
require
positive_timeout: a_timeout_seconds >= 0
do
c_set_sock_recv_timeout (descriptor, level_sol_socket, a_timeout_seconds)
end
set_send_timeout (a_timeout_seconds: INTEGER)
-- Set the send timeout in milliseconds on Current socket.
-- if `0' the related operations will never timeout.
require
positive_timeout: a_timeout_seconds >= 0
do
c_set_sock_send_timeout (descriptor, level_sol_socket, a_timeout_seconds)
end
feature {NONE} -- Externals
level_sol_socket: INTEGER
-- SOL_SOCKET level of options
deferred
end
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"
alias
"[
#ifdef EIF_WINDOWS
int arg = (int) 1000 * $a_timeout_seconds; /* Timeout in milliseconds */
setsockopt((SOCKET) $a_fd, (int) $a_level, (int) SO_RCVTIMEO, (char *) &arg, sizeof(arg));
#else
struct timeval tv;
tv.tv_sec = $a_timeout_seconds; /* Timeout in seconds */
setsockopt((int) $a_fd, (int) $a_level, (int) SO_RCVTIMEO, (struct timeval *)&tv, sizeof(struct timeval));
#endif
]"
end
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"
alias
"[
#ifdef EIF_WINDOWS
int arg = (int) 1000 * $a_timeout_seconds; /* Timeout in milliseconds */
setsockopt((SOCKET) $a_fd, (int) $a_level, (int) SO_SNDTIMEO, (char *) &arg, sizeof(arg));
#else
struct timeval tv;
tv.tv_sec = $a_timeout_seconds; /* Timeout in seconds */
setsockopt((int) $a_fd, (int) $a_level, (int) SO_SNDTIMEO, (struct timeval *)&tv, sizeof(struct timeval));
#endif
]"
end
end

View File

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

@@ -0,0 +1,20 @@
package httpd
project
httpd = "httpd-safe.ecf"
httpd = "httpd.ecf"
note
title: HTTP server
description: "[
Simple HTTP listener and handler, that can be extended easily.
]"
tags: http,httpd,server,web
collection: EWF
copyright: 2011-2016, Jocelyn Fiat, Javier Velilla, Olivier Ligot, Colin Adams, Eiffel Software and others
license: Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)
link[license]: http://www.eiffel.com/licensing/forum.txt
link[source]: "Github" https://github.com/EiffelWebFramework/EWF/library/server/httpd
link[doc]: "Documentation" http://eiffelwebframework.github.io/EWF/
end

View File

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

@@ -97,7 +97,9 @@ feature -- Request processing
end
end
rescue
has_error := l_output = Void or else not l_output.is_available
if l_output = Void or else not l_output.is_available then
report_error ("Missing WGI output")
end
if not retried then
retried := True
retry
@@ -230,6 +232,9 @@ feature -- Request processing
set_environment_variable (l_server_port, "SERVER_PORT", Result)
set_environment_variable (version, "SERVER_PROTOCOL", Result)
set_environment_variable ({HTTPD_CONFIGURATION}.Server_details, "SERVER_SOFTWARE", Result)
if is_secure then
set_environment_variable ("on", "HTTPS", Result)
end
--| Apply `base' value
l_base := base

View File

@@ -1,9 +1,9 @@
note
description: "[
Standalone Web Server connector
Standalone Web Server connector.
]"
date: "$Date$"
revision: "$Revision$"
date: "$Date: 2016-08-06 13:34:52 +0200 (sam., 06 août 2016) $"
revision: "$Revision: 99106 $"
class
WGI_STANDALONE_CONNECTOR [G -> WGI_EXECUTION create make end]
@@ -20,7 +20,7 @@ feature {NONE} -- Initialization
make
-- Create current standalone connector.
local
fac: separate WGI_HTTPD_REQUEST_HANDLER_FACTORY [G]
fac: like request_handler_factory
do
-- Callbacks
create on_launched_actions
@@ -51,7 +51,7 @@ feature {NONE} -- Separate helper
a_server.set_observer (observer)
end
update_factory (conn: detachable separate WGI_STANDALONE_CONNECTOR [G]; fac: separate WGI_HTTPD_REQUEST_HANDLER_FACTORY [G]; a_conf: separate HTTPD_CONFIGURATION)
update_factory (conn: detachable separate WGI_STANDALONE_CONNECTOR [G]; fac: separate WGI_HTTPD_REQUEST_HANDLER_FACTORY [G]; a_conf: like configuration)
do
fac.update_with (conn, a_conf)
end
@@ -63,11 +63,17 @@ feature {NONE} -- Separate helper
feature -- Access
name: STRING_8 = "httpd"
name: STRING_8
-- Name of Current connector
once
Result := "httpd"
end
version: STRING_8 = "0.1"
version: STRING_8
-- Version of Current connector
once
Result := "1.0"
end
feature -- Access
@@ -149,6 +155,12 @@ feature -- Element change
set_is_verbose_on_configuration (b, configuration)
end
set_is_secure (b: BOOLEAN)
-- Set is_secure connection mode.
-- i.e: using SSL.
do
set_is_secure_on_configuration (b, configuration)
end
feature -- Server
@@ -236,9 +248,14 @@ feature {NONE} -- Implementation: element change
cfg.set_is_verbose (b)
end
set_is_secure_on_configuration (b: BOOLEAN; cfg: like configuration)
do
cfg.set_is_secure (b)
end
note
copyright: "2011-2015, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
copyright: "2011-2016, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
source: "[
Eiffel Software

View File

@@ -0,0 +1,11 @@
note
description: "[
Interface to access protected feature from {WGI_STANDALONE_CONNECTOR}.
]"
date: "$Date$"
revision: "$Revision$"
deferred class
WGI_STANDALONE_CONNECTOR_EXPORTER
end

View File

@@ -0,0 +1,18 @@
note
description: "[
Constants value related to Standalone connector,
and indirectly to `httpd' component.
]"
author: "$Author$"
date: "$Date$"
revision: "$Revision$"
deferred class
WGI_STANDALONE_CONSTANTS
inherit
ANY
HTTPD_CONSTANTS
end

View File

@@ -24,7 +24,7 @@ feature {NONE} -- Initialization
set_source (a_source)
end
feature {WGI_STANDALONE_CONNECTOR, WGI_SERVICE} -- Nino
feature {WGI_STANDALONE_CONNECTOR, WGI_SERVICE, WGI_STANDALONE_CONNECTOR_EXPORTER} -- Standalone
set_source (i: like source)
do

View File

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

View File

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

View File

@@ -10,12 +10,11 @@
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
<library name="connector_standalone" location="standalone-safe.ecf" readonly="false"/>
<library name="ewsgi" location="..\..\ewsgi-safe.ecf" readonly="false"/>
<library name="httpd_edit" location="src\httpd\httpd-safe.ecf" readonly="false">
<library name="httpd_edit" location="lib\httpd\httpd-safe.ecf" readonly="false">
<option debug="true">
<debug name="dbglog" enabled="true"/>
</option>
</library>
<library name="net_ssl_edit" location="$ISE_LIBRARY\unstable\library\network\socket\netssl\net_ssl-safe.ecf" readonly="false"/>
<library name="wsf" location="..\..\..\wsf\wsf-safe.ecf" readonly="false"/>
<cluster name="tests" location="tests\" recursive="true"/>
</target>
@@ -27,4 +26,10 @@
</target>
<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="libcurl_http_client_disabled" value="true"/>
<variable name="net_http_client_disabled" value="false"/>
<variable name="netssl_http_client_enabled" value="true"/>
</target>
</system>

View File

@@ -2,14 +2,27 @@ note
description: "[
Component to launch the service using the default connector
Eiffel Web httpd for this class
EiffelWeb httpd for this class
The httpd default connector support options:
verbose: to display verbose output
port: numeric such as 8099 (or equivalent string as "8099")
base: base_url (very specific to standalone server)
max_concurrent_connections: set one, for single threaded behavior
max_tcp_clients: max number of open tcp connection
socket_timeout: connection timeout
socket_recv_timeout: read data timeout
keep_alive_timeout: amount of time the server will wait for subsequent
requests on a persistent connection,
max_keep_alive_requests: number of requests allowed on a persistent connection,
ssl_enabled: set to True for https support.
ssl_ca_crt: path to the certificat crt file (relevant when ssl_enabled is True)
ssl_ca_key: path to the certificat key file (relevant when ssl_enabled is True)
The httpd default connector support options:
port: numeric such as 8099 (or equivalent string as "8099")
base: base_url (very specific to standalone server)
verbose: to display verbose output, useful for standalone connector
force_single_threaded: use only one thread, useful for standalone connector
check WSF_SERVICE_LAUNCHER for more documentation
]"
@@ -41,12 +54,13 @@ feature {NONE} -- Initialization
create on_launched_actions
create on_stopped_actions
port_number := 80 --| Default, but quite often, this port is already used ...
max_concurrent_connections := 100
max_tcp_clients := 100
socket_timeout := 300 -- 300 seconds
keep_alive_timeout := 15 -- 15 seconds.
max_keep_alive_requests := 100
port_number := {WGI_STANDALONE_CONSTANTS}.default_http_server_port --| Default, but quite often, this port is already used ...
max_concurrent_connections := {WGI_STANDALONE_CONSTANTS}.default_max_concurrent_connections
max_tcp_clients := {WGI_STANDALONE_CONSTANTS}.default_max_tcp_clients
socket_timeout := {WGI_STANDALONE_CONSTANTS}.default_socket_timeout -- seconds
socket_recv_timeout := {WGI_STANDALONE_CONSTANTS}.default_socket_recv_timeout -- seconds
keep_alive_timeout := {WGI_STANDALONE_CONSTANTS}.default_keep_alive_timeout -- seconds.
max_keep_alive_requests := {WGI_STANDALONE_CONSTANTS}.default_max_keep_alive_requests
verbose := False
verbose_level := notice_level
@@ -59,6 +73,7 @@ feature {NONE} -- Initialization
if attached {READABLE_STRING_GENERAL} opts.option ("base") as l_base_str then
base_url := l_base_str.as_string_8
end
verbose := opts.option_boolean_value ("verbose", verbose)
-- See `{HTTPD_REQUEST_HANDLER_I}.*_verbose_level`
@@ -96,8 +111,16 @@ feature {NONE} -- Initialization
max_concurrent_connections := opts.option_integer_value ("max_concurrent_connections", max_concurrent_connections)
max_tcp_clients := opts.option_integer_value ("max_tcp_clients", max_tcp_clients)
socket_timeout := opts.option_integer_value ("socket_timeout", socket_timeout)
socket_recv_timeout := opts.option_integer_value ("socket_recv_timeout", socket_recv_timeout)
keep_alive_timeout := opts.option_integer_value ("keep_alive_timeout", keep_alive_timeout)
max_keep_alive_requests := opts.option_integer_value ("max_keep_alive_requests", max_keep_alive_requests)
if
opts.option_boolean_value ("ssl_enabled", ssl_enabled) and then
attached opts.option_string_32_value ("ssl_protocol", "tls_1_2") as ssl_prot
then
ssl_settings := [ssl_prot, opts.option_string_32_value ("ssl_ca_crt", Void), opts.option_string_32_value ("ssl_ca_key", Void)]
end
end
create conn.make
@@ -120,11 +143,13 @@ feature -- Execution
do
cfg.set_is_verbose (verbose)
cfg.set_verbose_level (verbose_level)
cfg.set_ssl_settings (ssl_settings)
cfg.set_http_server_name (server_name)
cfg.http_server_port := port_number
cfg.set_max_concurrent_connections (max_concurrent_connections)
cfg.set_max_tcp_clients (max_tcp_clients)
cfg.set_socket_timeout (socket_timeout)
cfg.set_socket_recv_timeout (socket_recv_timeout)
cfg.set_keep_alive_timeout (keep_alive_timeout)
cfg.set_max_keep_alive_requests (max_keep_alive_requests)
end
@@ -140,11 +165,17 @@ feature -- Execution
debug ("ew_standalone")
if verbose then
io.error.put_string ("Launching standalone web server on port " + port_number.out)
if attached server_name as l_name then
io.error.put_string ("%N http://" + l_name + ":" + port_number.out + "/" + base_url + "%N")
if ssl_enabled then
io.error.put_string ("%N https://")
else
io.error.put_string ("%N http://localhost:" + port_number.out + "/" + base_url + "%N")
io.error.put_string ("%N http://")
end
if attached server_name as l_name then
io.error.put_string (l_name)
else
io.error.put_string ("localhost")
end
io.error.put_string (":" + port_number.out + "/" + base_url + "%N")
end
end
update_configuration (conn.configuration)
@@ -153,15 +184,15 @@ feature -- Execution
feature -- Callback
on_launched_actions: ACTION_SEQUENCE [TUPLE [WGI_STANDALONE_CONNECTOR [G]]]
on_launched_actions: ACTION_SEQUENCE [TUPLE [like connector]]
-- Actions triggered when launched
on_stopped_actions: ACTION_SEQUENCE [TUPLE [WGI_STANDALONE_CONNECTOR [G]]]
on_stopped_actions: ACTION_SEQUENCE [TUPLE [like connector]]
-- Actions triggered when stopped
feature {NONE} -- Implementation
on_launched (conn: WGI_STANDALONE_CONNECTOR [G])
on_launched (conn: like connector)
do
on_launched_actions.call ([conn])
end
@@ -177,9 +208,18 @@ feature {NONE} -- Implementation
-- Help defining the verbosity.
-- The higher, the more output.
ssl_settings: detachable TUPLE [protocol: READABLE_STRING_GENERAL; ca_crt, ca_key: detachable READABLE_STRING_GENERAL]
ssl_enabled: BOOLEAN
-- Is secure server? i.e using SSL?
do
Result := attached ssl_settings as ssl and then attached ssl.protocol as prot and then not prot.is_whitespace
end
max_concurrent_connections: INTEGER
max_tcp_clients: INTEGER
socket_timeout: INTEGER
socket_recv_timeout: INTEGER
keep_alive_timeout: INTEGER
max_keep_alive_requests: INTEGER

View File

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

View File

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

View File

@@ -0,0 +1,137 @@
note
description: "[
Websocket callback events for actions like opening and closing the connection,
sending and receiving messages, and listening.
Define the websocket events:
- on_open
- on_binary
- on_text
- on_close
note: the following features could also be redefined:
- on_pong
- on_ping
- on_unsupported
]"
date: "$Date$"
revision: "$Revision$"
deferred class
WEB_SOCKET_EVENT_I
inherit
WEB_SOCKET_CONSTANTS
REFACTORING_HELPER
feature -- Web Socket Interface
on_event (ws: WEB_SOCKET; a_message: detachable READABLE_STRING_8; a_opcode: INTEGER)
-- Called when a frame from the client has been receive
require
ws_attached: ws /= Void
ws_valid: ws.is_open_read and then ws.is_open_write
local
l_message: READABLE_STRING_8
do
debug ("ws")
ws.log ("%Non_event (ws, a_message, " + opcode_name (a_opcode) + ")%N", {HTTPD_LOGGER_CONSTANTS}.debug_level)
end
if a_message = Void then
create {STRING} l_message.make_empty
else
l_message := a_message
end
if a_opcode = Binary_frame then
on_binary (ws, l_message)
elseif a_opcode = Text_frame then
on_text (ws, l_message)
elseif a_opcode = Pong_frame then
on_pong (ws, l_message)
elseif a_opcode = Ping_frame then
on_ping (ws, l_message)
elseif a_opcode = Connection_close_frame then
on_connection_close (ws, "")
else
on_unsupported (ws, l_message, a_opcode)
end
end
feature -- Websocket events
on_open (ws: WEB_SOCKET)
-- Called after handshake, indicates that a complete WebSocket connection has been established.
require
ws_attached: ws /= Void
ws_valid: ws.is_open_read and then ws.is_open_write
deferred
end
on_binary (ws: WEB_SOCKET; a_message: READABLE_STRING_8)
require
ws_attached: ws /= Void
ws_valid: ws.is_open_read and then ws.is_open_write
deferred
end
on_text (ws: WEB_SOCKET; a_message: READABLE_STRING_8)
require
ws_attached: ws /= Void
ws_valid: ws.is_open_read and then ws.is_open_write
deferred
end
on_close (ws: detachable WEB_SOCKET)
-- Called after the WebSocket connection is closed.
deferred
end
feature -- Websocket events: implemented
on_pong (ws: WEB_SOCKET; a_message: READABLE_STRING_8)
require
ws_attached: ws /= Void
ws_valid: ws.is_open_read and then ws.is_open_write
do
-- log ("Its a pong frame")
-- at first we ignore pong
-- FIXME: provide better explanation
end
on_ping (ws: WEB_SOCKET; a_message: READABLE_STRING_8)
require
ws_attached: ws /= Void
ws_valid: ws.is_open_read and then ws.is_open_write
do
ws.send (Pong_frame, a_message)
end
on_unsupported (ws: WEB_SOCKET; a_message: READABLE_STRING_8; a_opcode: INTEGER)
require
ws_attached: ws /= Void
ws_valid: ws.is_open_read and then ws.is_open_write
do
-- do nothing
end
on_connection_close (ws: WEB_SOCKET; a_message: detachable READABLE_STRING_8)
require
ws_attached: ws /= Void
ws_valid: ws.is_open_read and then ws.is_open_write
do
ws.send (Connection_close_frame, "")
end
note
copyright: "2011-2016, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
source: "[
Eiffel Software
5949 Hollister Ave., Goleta, CA 93117 USA
Telephone 805-685-1006, Fax 805-685-6869
Website http://www.eiffel.com
Customer support http://support.eiffel.com
]"
end

View File

@@ -0,0 +1,39 @@
note
description: "[
A web socket message has an opcode specifying the type of the message payload. The
opcode consists of the last four bits in the first byte of the frame header.
]"
date: "$Date$"
revision: "$Revision$"
EIS: "name=Data Frame", "src=http://tools.ietf.org/html/rfc6455#section-5.6", "protocol=uri"
EIS: "name=Control Frame", "src=http://tools.ietf.org/html/rfc6455#section-5.5", "protocol=uri"
class
WEB_SOCKET_MESSAGE_TYPE
feature -- Data Frames
Text: INTEGER = 0x1
-- The data type of the message is text.
Binary: INTEGER = 0x2
-- The data type of the message is binary.
feature -- Control Frames
Close: INTEGER = 0x8
-- The client or server is sending a closing
-- handshake to the server or client.
Ping: INTEGER = 0x9
-- The client or server sends a ping to the server or client.
Pong: INTEGER = 0xA
-- The client or server sends a pong to the server or client.
feature -- Reserverd
-- Opcodes 0x3-0x7 are reserved for further non-control frames yet to be
-- defined.
end

View File

@@ -0,0 +1,203 @@
note
description: "Constants for WebSockets"
date: "$Date$"
revision: "$Revision$"
class
WEB_SOCKET_CONSTANTS
feature -- Constants
HTTP_1_1: STRING = "HTTP/1.1 101 WebSocket Protocol Handshake"
Upgrade_ws: STRING = "Upgrade: websocket"
Connection_ws: STRING = "Connection: Upgrade"
Sec_WebSocket_Origin: STRING = "Sec-WebSocket-Origin: "
Sec_WebSocket_Protocol: STRING = "Sec-WebSocket-Protocol: "
Sec_WebSocket_Location: STRING = "Sec-WebSocket-Location: "
Sec_WebSocket_Version: STRING = "Sec-WebSocket-Version: "
Sec_WebSocket_Extensions: STRING = "Sec-WebSocket-Extensions: "
WebSocket_Origin: STRING = "WebSocket-Origin: "
WebSocket_Protocol: STRING = "WebSocket-Protocol: "
WebSocket_Location: STRING = "WebSocket-Location: "
Origin: STRING = "Origin"
Server: STRING = "EWSS"
Sec_WebSocket_Key: STRING = "Sec-WebSocket-Key"
Ws_scheme: STRING = "ws://"
Wss_scheme: STRING = "wss://"
Magic_guid: STRING = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
-- The handshake from the client looks as follows:
-- GET /chat HTTP/1.1
-- Host: server.example.com
-- Upgrade: websocket
-- Connection: Upgrade
-- Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
-- Origin: http://example.com
-- Sec-WebSocket-Protocol: chat, superchat
-- Sec-WebSocket-Version: 13
-- The handshake from the server looks as follows:
-- HTTP/1.1 101 Switching Protocols
-- Upgrade: websocket
-- Connection: Upgrade
-- Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
-- Sec-WebSocket-Protocol: chat
feature -- Opcodes Standard actions
--| Maybe we need an enum STANDARD_ACTIONS_OPCODES?
-- |Opcode | Meaning | Reference |
-- -+--------+-------------------------------------+-----------|
-- | 0 | Continuation Frame | RFC 6455 |
-- -+--------+-------------------------------------+-----------|
-- | 1 | Text Frame | RFC 6455 |
-- -+--------+-------------------------------------+-----------|
-- | 2 | Binary Frame | RFC 6455 |
-- -+--------+-------------------------------------+-----------|
-- | 8 | Connection Close Frame | RFC 6455 |
-- -+--------+-------------------------------------+-----------|
-- | 9 | Ping Frame | RFC 6455 |
-- -+--------+-------------------------------------+-----------|
-- | 10 | Pong Frame | RFC 6455 |
-- -+--------+-------------------------------------+-----------|
Continuation_frame: INTEGER = 0
Text_frame: INTEGER = 1
Binary_frame: INTEGER = 2
Connection_close_frame: INTEGER = 8
Ping_frame: INTEGER = 9
Pong_frame: INTEGER = 10
is_control_frame (a_opcode: INTEGER): BOOLEAN
-- Is `a_opcode' a control frame?
do
inspect a_opcode
when Connection_close_frame, Ping_frame, Pong_frame then
Result := True
else
end
end
opcode_name (a_opcode: INTEGER): STRING
do
inspect a_opcode
when Continuation_frame then Result := "Continuation"
when Text_frame then Result := "Text"
when Binary_frame then Result := "Binary"
when Connection_close_frame then Result := "Connection Close"
when Ping_frame then Result := "Ping"
when Pong_frame then Result := "Pong"
else
Result := "Unknown-Opcode"
end
Result := "0x" + a_opcode.to_hex_string + " " + Result
end
feature -- Close code numbers
-- Maybe an ENUM CLOSE_CODES
-- |Status Code | Meaning | Contact | Reference |
-- -+------------+-----------------+---------------+-----------|
-- | 1000 | Normal Closure | hybi@ietf.org | RFC 6455 |
-- -+------------+-----------------+---------------+-----------|
-- | 1001 | Going Away | hybi@ietf.org | RFC 6455 |
-- -+------------+-----------------+---------------+-----------|
-- | 1002 | Protocol error | hybi@ietf.org | RFC 6455 |
-- -+------------+-----------------+---------------+-----------|
-- | 1003 | Unsupported Data| hybi@ietf.org | RFC 6455 |
-- -+------------+-----------------+---------------+-----------|
-- | 1004 | ---Reserved---- | hybi@ietf.org | RFC 6455 |
-- -+------------+-----------------+---------------+-----------|
-- | 1005 | No Status Rcvd | hybi@ietf.org | RFC 6455 |
-- -+------------+-----------------+---------------+-----------|
-- | 1006 | Abnormal Closure| hybi@ietf.org | RFC 6455 |
-- -+------------+-----------------+---------------+-----------|
-- | 1007 | Invalid frame | hybi@ietf.org | RFC 6455 |
-- | | payload data | | |
-- -+------------+-----------------+---------------+-----------|
-- | 1008 | Policy Violation| hybi@ietf.org | RFC 6455 |
-- -+------------+-----------------+---------------+-----------|
-- | 1009 | Message Too Big | hybi@ietf.org | RFC 6455 |
-- -+------------+-----------------+---------------+-----------|
-- | 1010 | Mandatory Ext. | hybi@ietf.org | RFC 6455 |
-- -+------------+-----------------+---------------+-----------|
-- | 1011 | Internal Server | hybi@ietf.org | RFC 6455 |
-- | | Error | | |
-- -+------------+-----------------+---------------+-----------|
-- | 1015 | TLS handshake | hybi@ietf.org | RFC 6455 |
-- -+------------+-----------------+---------------+-----------|
Normal_closure: INTEGER = 1000
-- Indicates a normal closure, meaning that the purpose for
-- which the connection was established has been fulfilled.
Going_away: INTEGER = 1001
-- Indicates that an endpoint is "going away", such as a server
-- going down or a browser having navigated away from a page.
Protocol_error: INTEGER = 1002
-- Indicates that an endpoint is terminating the connection due
-- to a protocol error.
Unsupported_data: INTEGER = 1003
-- Indicates that an endpoint is terminating the connection
-- because it has received a type of data it cannot accept (e.g., an
-- endpoint that understands only text data MAY send this if it
-- receives a binary message).
Invalid_data: INTEGER = 1007
-- Indicates that an endpoint is terminating the connection
-- because it has received data within a message that was not
-- consistent with the type of the message (e.g., non-UTF-8 [RFC3629]
-- data within a text message).
Policy_violation: INTEGER = 1008
-- Indicates that an endpoint is terminating the connection
-- because it has received a message that violates its policy. This
-- is a generic status code that can be returned when there is no
-- other more suitable status code (e.g., 1003 or 1009) or if there
-- is a need to hide specific details about the policy.
Message_too_large: INTEGER = 1009
-- Indicates that an endpoint is terminating the connection
-- because it has received a message that is too big for it to
-- process.
Extension_required: INTEGER = 1010
-- Indicates that an endpoint (client) is terminating the
-- connection because it has expected the server to negotiate one or
-- more extension, but the server didn't return them in the response
-- message of the WebSocket handshake.
Internal_error: INTEGER = 1011
-- Indicates that a server is terminating the connection because
-- it encountered an unexpected condition that prevented it from
-- fulfilling the request.
end

View File

@@ -0,0 +1,35 @@
note
description: "Summary description for {WEB_SOCKET_ERROR_FRAME}."
author: ""
date: "$Date$"
revision: "$Revision$"
class
WEB_SOCKET_ERROR_FRAME
create
make
feature {NONE} -- Initialization
make (a_code: INTEGER; a_desc: like description)
do
code := a_code
description := a_desc
end
feature -- Access
code: INTEGER
description: READABLE_STRING_8
feature -- Conversion
string: STRING
do
create Result.make_from_string ("Error(" + code.out + "): " + description)
end
end

View File

@@ -0,0 +1,437 @@
note
description: "[
Summary description for {WEB_SOCKET_FRAME}.
See Base Framing Protocol: http://tools.ietf.org/html/rfc6455#section-5.2
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-------+-+-------------+-------------------------------+
|F|R|R|R| opcode|M| Payload len | Extended payload length |
|I|S|S|S| (4) |A| (7) | (16/64) |
|N|V|V|V| |S| | (if payload len==126/127) |
| |1|2|3| |K| | |
+-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
| Extended payload length continued, if payload len == 127 |
+ - - - - - - - - - - - - - - - +-------------------------------+
| |Masking-key, if MASK set to 1 |
+-------------------------------+-------------------------------+
| Masking-key (continued) | Payload Data |
+-------------------------------- - - - - - - - - - - - - - - - +
: Payload Data continued ... :
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
| Payload Data continued ... |
+---------------------------------------------------------------+
Check the `check_utf_8_validity_on_chop' if there is performance issue
with bigger data.
]"
date: "$Date$"
revision: "$Revision$"
EIS: "name=Websocket RFC6455 section-5.2", "protocol=URI", "src=http://tools.ietf.org/html/rfc6455#section-5.2", "tag=rfc"
class
WEB_SOCKET_FRAME
inherit
ANY
WEB_SOCKET_CONSTANTS
create
make,
make_as_injected_control
feature {NONE} -- Initialization
make (a_opcode: INTEGER; flag_is_fin: BOOLEAN)
-- Create current frame with opcode `a_opcode'
-- and `a_fin' to indicate if this is the final fragment.
do
is_incomplete := False
opcode := a_opcode
is_fin := flag_is_fin
inspect opcode
when
Continuation_frame, -- 0
Text_frame, -- 1
Binary_frame -- 2
then
--| Supported opcode
when
Connection_close_frame, -- 8
Ping_frame, -- 9
Pong_frame -- 10
then
--| Supported control opcode
-- All control frames MUST have a payload length of 125 bytes or less
-- and MUST NOT be fragmented.
if flag_is_fin then
-- So far it is valid.
else
report_error (Protocol_error, "Control frames MUST NOT be fragmented.")
end
else
report_error (Protocol_error, "Unknown opcode")
end
end
make_as_injected_control (a_opcode: INTEGER; a_parent: WEB_SOCKET_FRAME)
require
parent_is_not_control_frame: not a_parent.is_control
a_opcode_is_control_frame: is_control_frame (a_opcode)
do
make (a_opcode, True)
parent := a_parent
a_parent.add_injected_control_frame (Current)
end
feature -- Access
opcode: INTEGER
-- CONTINUOUS, TEXT, BINARY, PING, PONG, CLOSING
is_fin: BOOLEAN
-- is the final fragment in a message?
fragment_count: INTEGER
payload_length: NATURAL_64
payload_data: detachable STRING_8
-- Maybe we need a buffer here.
uncoded_payload_data: detachable STRING_32
local
utf: UTF_CONVERTER
do
if attached payload_data as d then
Result := utf.utf_8_string_8_to_string_32 (d)
end
end
error: detachable WEB_SOCKET_ERROR_FRAME
-- Describe the type of error
feature -- Access: injected control frames
injected_control_frames: detachable LIST [WEB_SOCKET_FRAME]
parent: detachable WEB_SOCKET_FRAME
-- If Current is injected, `parent' is the related fragmented frame
is_injected_control: BOOLEAN
do
Result := parent /= Void
ensure
Result implies (is_control_frame (opcode))
end
feature -- Operation
update_fin (a_flag_is_fin: BOOLEAN)
do
is_fin := a_flag_is_fin
end
feature {WEB_SOCKET_FRAME} -- Change: injected control frames
add_injected_control_frame (f: WEB_SOCKET_FRAME)
require
Current_is_not_control: not is_control
f_is_control_frame: f.is_control
parented_to_current: f.parent = Current
local
lst: like injected_control_frames
do
lst := injected_control_frames
if lst = Void then
create {ARRAYED_LIST [WEB_SOCKET_FRAME]} lst.make (1)
injected_control_frames := lst
end
lst.force (f)
ensure
parented_to_current: f.parent = Current
end
remove_injected_control_frame (f: WEB_SOCKET_FRAME)
require
Current_is_not_control: not is_control
f_is_control_frame: f.is_control
parented_to_current: f.parent = Current
local
lst: like injected_control_frames
do
lst := injected_control_frames
if lst /= Void then
lst.prune (f)
if lst.is_empty then
injected_control_frames := Void
end
end
end
feature -- Query
is_binary: BOOLEAN
do
Result := opcode = binary_frame
end
is_text: BOOLEAN
do
Result := opcode = text_frame
end
is_continuation: BOOLEAN
do
Result := opcode = continuation_frame
end
is_connection_close: BOOLEAN
do
Result := opcode = connection_close_frame
end
is_control: BOOLEAN
do
inspect opcode
when connection_close_frame, Ping_frame, Pong_frame then
Result := True
else
end
end
is_ping: BOOLEAN
do
Result := opcode = ping_frame
end
is_pong: BOOLEAN
do
Result := opcode = pong_frame
end
feature -- Status report
is_valid: BOOLEAN
do
Result := not has_error
end
is_incomplete: BOOLEAN
has_error: BOOLEAN
do
Result := error /= Void
end
feature -- Change
increment_fragment_count
do
fragment_count := fragment_count + 1
end
check_utf_8_validity_on_chop: BOOLEAN = False
-- True: check for each chop
-- False: check only for each fragment
--| see autobahntestsuite #6.4.3 and #6.4.4
append_payload_data_chop (a_data: STRING_8; a_len: INTEGER; a_flag_chop_complete: BOOLEAN)
do
if a_flag_chop_complete then
increment_fragment_count
end
if attached payload_data as l_payload_data then
l_payload_data.append (a_data)
else
payload_data := a_data
end
payload_length := payload_length + a_len.to_natural_64
if is_text then
if is_fin and a_flag_chop_complete then
-- Check the whole message is a valid UTF-8 string
if attached payload_data as d then
if not is_valid_utf_8_string (d) then
report_error (invalid_data, "The text message is not a valid UTF-8 text!")
end
else
-- empty payload??
end
elseif check_utf_8_validity_on_chop or else a_flag_chop_complete then
-- Check the payload data as utf-8 stream (may be incomplete at this point)
if not is_valid_text_payload_stream then
report_error (invalid_data, "This is not a valid UTF-8 stream!")
-- is_valid implies the connection will be closed!
end
end
end
end
report_error (a_code: INTEGER; a_description: READABLE_STRING_8)
require
not has_error
do
create error.make (a_code, a_description)
ensure
has_error: has_error
is_not_valid: not is_valid
end
feature {NONE} -- Helper
last_utf_8_stream_validation_position: INTEGER
-- In relation with `is_valid_utf_8 (.., a_is_stream=True)'
is_valid_text_payload_stream: BOOLEAN
require
is_text_frame: is_text
do
if attached payload_data as s then
Result := is_valid_utf_8 (s, not is_fin)
end
end
is_valid_utf_8_string (s: READABLE_STRING_8): BOOLEAN
do
Result := is_valid_utf_8 (s, False)
-- and (create {UTF_CONVERTER}).is_valid_utf_8_string_8 (s)
end
is_valid_utf_8 (s: READABLE_STRING_8; a_is_stream: BOOLEAN): BOOLEAN
-- UTF-8 validity checker.
note
EIS: "name=UTF-8 RFC3629", "protocol=URI", "src=https://tools.ietf.org/html/rfc3629", "tag=rfc"
require
is_text_frame: is_text
local
i: like {STRING_8}.count
n: like {STRING_8}.count
c,c2,c3,c4,w: NATURAL_32
l_is_incomplete_stream: BOOLEAN
do
Result := True
-- Following code also check that codepoint is between 0 and 0x10FFFF
-- (as expected by spec, and tested by autobahn ws testsuite)
from
if a_is_stream then
i := last_utf_8_stream_validation_position -- to avoid recomputing from the beginning each time.
else
i := 0
end
n := s.count
until
i >= n or not Result
loop
i := i + 1
c := s.code (i)
if c <= 0x7F then
-- 0xxxxxxx
w := c
elseif c <= 0xC1 then
-- The octet values C0, C1, F5 to FF never appear.
--| case 0xC0 and 0xC1
Result := False
elseif (c & 0xE0) = 0xC0 then
-- 110xxxxx 10xxxxxx
i := i + 1
if i <= n then
c2 := s.code (i)
if
(c2 & 0xC0) = 0x80
then
w := ((c & 0x1F) |<< 6)
| (c2 & 0x3F)
Result := 0x80 <= w and w <= 0x7FF
else
Result := False
end
else
l_is_incomplete_stream := True
end
elseif (c & 0xF0) = 0xE0 then
-- 1110xxxx 10xxxxxx 10xxxxxx
i := i + 2
if i <= n then
c2 := s.code (i - 1)
c3 := s.code (i)
if
(c2 & 0xC0) = 0x80 and
(c3 & 0xC0) = 0x80
then
w := ((c & 0xF) |<< 12)
| ((c2 & 0x3F) |<< 6)
| (c3 & 0x3F)
if 0x800 <= w and w <= 0xFFFF then
if 0xD800 <= w and w <= 0xDFFF then
-- The definition of UTF-8 prohibits encoding character numbers between U+D800 and U+DFFF
Result := False
end
else
Result := False
end
else
Result := False
end
else
if i - 1 <= n then
Result := (s.code (i - 1) & 0xC0) = 0x80
end
l_is_incomplete_stream := True
end
elseif (c & 0xF8) = 0xF0 then -- 0001 0000-0010 FFFF
-- 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
if 0xF5 <= c and c <= 0xFF then
-- The octet values C0, C1, F5 to FF never appear.
Result := False
else
i := i + 3
if i <= n then
c2 := s.code (i - 2)
c3 := s.code (i - 1)
c4 := s.code (i)
if
(c2 & 0xC0) = 0x80 and
(c3 & 0xC0) = 0x80 and
(c4 & 0xC0) = 0x80
then
w := ((c & 0x7) |<< 18) |
((c2 & 0x3F) |<< 12) |
((c3 & 0x3F) |<< 6) |
(c4 & 0x3F)
Result := 0x1_0000 <= w and w <= 0x10_FFFF
else
Result := False
end
else
if i - 2 <= n then
c2 := s.code (i - 2)
Result := (c2 & 0xC0) = 0x80
if Result then
if c = 0xF4 and c2 >= 0x90 then
--| any byte 10xxxxxx (i.e >= 0x80) that would come after,
-- will result in out of range code point
-- indeed 0xF4 0x90 0x80 0x80 = 0x1100 0000 > 0x10_FFFF
Result := False
elseif i - 1 <= n then
Result := (s.code (i - 1) & 0xC0) = 0x80
end
end
end
l_is_incomplete_stream := True
end
end
else
-- Invalid byte in UTF-8
Result := False
end
if Result then
if l_is_incomplete_stream then
Result := a_is_stream
elseif a_is_stream then
last_utf_8_stream_validation_position := i
end
end
end
end
end

View File

@@ -0,0 +1,805 @@
note
description: "[
Object representing the websocket connection.
It contains the `request` and `response`, and more important the `socket` itself.
]"
date: "$Date$"
revision: "$Revision$"
class
WEB_SOCKET
inherit
WGI_STANDALONE_CONNECTOR_EXPORTER
WSF_RESPONSE_EXPORTER
WGI_EXPORTER
HTTPD_LOGGER_CONSTANTS
WEB_SOCKET_CONSTANTS
SHARED_BASE64
create
make
feature {NONE} -- Initialization
make (req: WSF_REQUEST; res: WSF_RESPONSE)
do
request := req
response := res
is_verbose := False
verbose_level := notice_level
if
attached {WGI_STANDALONE_INPUT_STREAM} req.input as r_input
then
socket := r_input.source
else
create socket.make_empty
check has_socket: False end
end
end
feature -- Access
socket: HTTPD_STREAM_SOCKET
-- Underlying connected socket.
feature {NONE} -- Access
request: WSF_REQUEST
-- Associated request.
response: WSF_RESPONSE
-- Associated response stream.
feature -- Access
is_websocket: BOOLEAN
-- Does `open_ws_handshake' detect valid websocket upgrade handshake?
feature -- Settings
is_verbose: BOOLEAN
-- Output verbose log messages?
verbose_level: INTEGER
-- Level of verbosity.
feature -- Status
has_error: BOOLEAN
-- Error occured during processing?
feature -- Socket status
is_ready_for_reading: BOOLEAN
-- Is `socket' ready for reading?
--| at this point, socket should be set to blocking.
do
Result := socket.ready_for_reading
end
is_open_read: BOOLEAN
-- Is `socket' open for reading?
do
Result := socket.is_open_read
end
is_open_write: BOOLEAN
-- Is `socket' open for writing?
do
Result := socket.is_open_write
end
socket_descriptor: INTEGER
-- Descriptor for current `socket'.
do
Result := socket.descriptor
end
feature -- Element change
set_is_verbose (b: BOOLEAN)
do
is_verbose := b
end
set_verbose_level (lev: INTEGER)
do
verbose_level := lev
end
feature -- Basic operation
put_error (a_message: READABLE_STRING_8)
do
response.put_error (a_message)
end
log (m: READABLE_STRING_8; lev: INTEGER)
-- Log `m' in the error channel, i.e stderr for standalone.
do
if is_verbose then
put_error (m)
end
end
feature -- Basic Operation
open_ws_handshake
-- The opening handshake is intended to be compatible with HTTP-based
-- server-side software and intermediaries, so that a single port can be
-- used by both HTTP clients alking to that server and WebSocket
-- clients talking to that server. To this end, the WebSocket client's
-- handshake is an HTTP Upgrade request:
-- GET /chat HTTP/1.1
-- Host: server.example.com
-- Upgrade: websocket
-- Connection: Upgrade
-- Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
-- Origin: http://example.com
-- Sec-WebSocket-Protocol: chat, superchat
-- Sec-WebSocket-Version: 13
local
l_sha1: SHA1
l_key : STRING
req: like request
res: like response
do
-- Reset values.
is_websocket := False
has_error := False
-- Local cache.
req := request
res := response
-- Reading client's opening GT
-- TODO extract to a validator handshake or something like that.
if is_verbose then
log ("%NReceive <====================", debug_level)
if attached req.raw_header_data as rhd then
log (rhd, debug_level)
end
end
if
req.is_get_request_method and then -- MUST be GET request!
attached req.meta_string_variable ("HTTP_UPGRADE") as l_upgrade_key and then
l_upgrade_key.is_case_insensitive_equal_general ("websocket") -- Upgrade header must be present with value websocket
then
is_websocket := True
socket.set_blocking
if
attached req.meta_string_variable ("HTTP_SEC_WEBSOCKET_KEY") as l_ws_key and then -- Sec-websocket-key must be present
attached req.meta_string_variable ("HTTP_CONNECTION") as l_connection_key and then -- Connection header must be present with value Upgrade
l_connection_key.has_substring ("Upgrade") and then
attached req.meta_string_variable ("HTTP_SEC_WEBSOCKET_VERSION") as l_version_key and then -- Version header must be present with value 13
l_version_key.is_case_insensitive_equal ("13") and then
attached req.http_host -- Host header must be present
then
if is_verbose then
log ("key " + l_ws_key, debug_level)
end
-- Sending the server's opening handshake
create l_sha1.make
l_sha1.update_from_string (l_ws_key + magic_guid)
l_key := Base64_encoder.encoded_string (digest (l_sha1))
res.header.add_header_key_value ("Upgrade", "websocket")
res.header.add_header_key_value ("Connection", "Upgrade")
res.header.add_header_key_value ("Sec-WebSocket-Accept", l_key)
if is_verbose then
log ("%N================> Send Handshake", debug_level)
if attached {HTTP_HEADER} res.header as h then
log (h.string, debug_level)
end
end
res.set_status_code_with_reason_phrase (101, "Switching Protocols")
res.wgi_response.push
else
has_error := True
if is_verbose then
log ("Error (opening_handshake)!!!", debug_level)
end
-- If we cannot complete the handshake, then the server MUST stop processing the client's handshake and return an HTTP response with an
-- appropriate error code (such as 400 Bad Request).
res.set_status_code_with_reason_phrase (400, "Bad Request")
end
else
is_websocket := False
end
end
feature -- Response!
send (a_opcode:INTEGER; a_message: READABLE_STRING_8)
local
i: INTEGER
l_chunk_size: INTEGER
l_chunk: READABLE_STRING_8
l_header_message: STRING
l_message_count: INTEGER
n: NATURAL_64
retried: BOOLEAN
do
debug ("ws")
print (">>do_send (..., "+ opcode_name (a_opcode) +", ..)%N")
end
if not retried then
create l_header_message.make_empty
l_header_message.append_code ((0x80 | a_opcode).to_natural_32)
l_message_count := a_message.count
n := l_message_count.to_natural_64
if l_message_count > 0xffff then
--! Improve. this code needs to be checked.
l_header_message.append_code ((0 | 127).to_natural_32)
l_header_message.append_character ((n |>> 56).to_character_8)
l_header_message.append_character ((n |>> 48).to_character_8)
l_header_message.append_character ((n |>> 40).to_character_8)
l_header_message.append_character ((n |>> 32).to_character_8)
l_header_message.append_character ((n |>> 24).to_character_8)
l_header_message.append_character ((n |>> 16).to_character_8)
l_header_message.append_character ((n |>> 8).to_character_8)
l_header_message.append_character ( n.to_character_8)
elseif l_message_count > 125 then
l_header_message.append_code ((0 | 126).to_natural_32)
l_header_message.append_code ((n |>> 8).as_natural_32)
l_header_message.append_character (n.to_character_8)
else
l_header_message.append_code (n.as_natural_32)
end
socket.put_string (l_header_message)
l_chunk_size := 16_384 -- 16K TODO: see if we should make it customizable.
if l_message_count < l_chunk_size then
socket.put_string (a_message)
else
from
i := 0
until
l_chunk_size = 0
loop
debug ("ws")
print ("Sending chunk " + (i + 1).out + " -> " + (i + l_chunk_size).out +" / " + l_message_count.out + "%N")
end
l_chunk := a_message.substring (i + 1, l_message_count.min (i + l_chunk_size))
socket.put_string (l_chunk)
if l_chunk.count < l_chunk_size then
l_chunk_size := 0
end
i := i + l_chunk_size
end
debug ("ws")
print ("Sending chunk done%N")
end
end
else
-- FIXME: what should be done on rescue?
end
rescue
retried := True
io.put_string ("Internal error in " + generator + ".do_send (conn, a_opcode=" + a_opcode.out + ", a_message) !%N")
retry
end
next_frame: detachable WEB_SOCKET_FRAME
-- TODO Binary messages
-- Handle error responses in a better way.
-- IDEA:
-- class FRAME
-- is_fin: BOOLEAN
-- opcode: WEB_SOCKET_STATUS_CODE (TEXT, BINARY, CLOSE, CONTINUE,PING, PONG)
-- data/payload
-- status_code: #see Status Codes http://tools.ietf.org/html/rfc6455#section-7.3
-- has_error
--
-- See Base Framing Protocol: http://tools.ietf.org/html/rfc6455#section-5.2
-- 0 1 2 3
-- 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
-- +-+-+-+-+-------+-+-------------+-------------------------------+
-- |F|R|R|R| opcode|M| Payload len | Extended payload length |
-- |I|S|S|S| (4) |A| (7) | (16/64) |
-- |N|V|V|V| |S| | (if payload len==126/127) |
-- | |1|2|3| |K| | |
-- +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
-- | Extended payload length continued, if payload len == 127 |
-- + - - - - - - - - - - - - - - - +-------------------------------+
-- | |Masking-key, if MASK set to 1 |
-- +-------------------------------+-------------------------------+
-- | Masking-key (continued) | Payload Data |
-- +-------------------------------- - - - - - - - - - - - - - - - +
-- : Payload Data continued ... :
-- + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
-- | Payload Data continued ... |
-- +---------------------------------------------------------------+
note
EIS: "name=WebSocket RFC", "protocol=URI", "src=http://tools.ietf.org/html/rfc6455#section-5.2"
require
socket_in_blocking_mode: socket.is_blocking
local
l_socket: like socket
l_opcode: INTEGER
l_len: INTEGER
l_remaining_len: INTEGER
l_payload_len: NATURAL_64
l_masking_key: detachable READABLE_STRING_8
l_chunk: STRING
l_rsv: BOOLEAN
l_fin: BOOLEAN
l_has_mask: BOOLEAN
l_chunk_size: INTEGER
l_byte: INTEGER
l_fetch_count: INTEGER
l_bytes_read: INTEGER
s: STRING
is_data_frame_ok: BOOLEAN -- Is the last process data framing ok?
retried: BOOLEAN
do
if not retried then
l_socket := socket
debug ("ws")
print ("next_frame:%N")
end
from
is_data_frame_ok := True
until
l_fin or not is_data_frame_ok
loop
-- multi-frames or continue is only valid for Binary or Text
s := next_bytes (l_socket, 1)
if s.is_empty then
is_data_frame_ok := False
debug ("ws")
print ("[ERROR] incomplete_data!%N")
end
else
l_byte := s [1].code
debug ("ws")
print (" fin,rsv(3),opcode(4)=")
print (to_byte_representation (l_byte))
print ("%N")
end
l_fin := l_byte & (0b10000000) /= 0
l_rsv := l_byte & (0b01110000) = 0
l_opcode := l_byte & 0b00001111
if Result /= Void then
if l_opcode = Result.opcode then
-- should not occur in multi-fragment frame!
create Result.make (l_opcode, l_fin)
Result.report_error (protocol_error, "Unexpected injected frame")
elseif l_opcode = continuation_frame then
-- Expected
Result.update_fin (l_fin)
elseif is_control_frame (l_opcode) then
-- Control frames (see Section 5.5) MAY be injected in the middle of
-- a fragmented message. Control frames themselves MUST NOT be fragmented.
-- if the l_opcode is a control frame then there is an error!!!
-- CLOSE, PING, PONG
create Result.make_as_injected_control (l_opcode, Result)
else
-- should not occur in multi-fragment frame!
create Result.make (l_opcode, l_fin)
Result.report_error (protocol_error, "Unexpected frame")
end
else
create Result.make (l_opcode, l_fin)
if Result.is_continuation then
-- Continuation frame is not expected without parent frame!
Result.report_error (protocol_error, "There is no message to continue!")
end
end
if Result.is_valid then
--| valid frame/fragment
if is_verbose then
log ("+ frame " + opcode_name (l_opcode) + " (fin=" + l_fin.out + ")", debug_level)
end
-- rsv validation
if not l_rsv then
-- RSV1, RSV2, RSV3: 1 bit each
-- MUST be 0 unless an extension is negotiated that defines meanings
-- for non-zero values. If a nonzero value is received and none of
-- the negotiated extensions defines the meaning of such a nonzero
-- value, the receiving endpoint MUST _Fail the WebSocket
-- Connection_
-- FIXME: add support for extension ?
Result.report_error (protocol_error, "RSV values MUST be 0 unless an extension is negotiated that defines meanings for non-zero values")
end
else
if is_verbose then
log ("+ INVALID frame " + opcode_name (l_opcode) + " (fin=" + l_fin.out + ")", debug_level)
end
end
-- At the moment only TEXT, (pending Binary)
if Result.is_valid then
if Result.is_text or Result.is_binary or Result.is_control then
-- Reading next byte (mask+payload_len)
s := next_bytes (l_socket, 1)
if s.is_empty then
Result.report_error (invalid_data, "Incomplete data for mask and payload len")
else
l_byte := s [1].code
debug ("ws")
print (" mask,payload_len(7)=")
print (to_byte_representation (l_byte))
io.put_new_line
end
l_has_mask := l_byte & (0b10000000) /= 0 -- MASK
l_len := l_byte & 0b01111111 -- 7bits
debug ("ws")
print (" payload_len=" + l_len.out)
io.put_new_line
end
if Result.is_control and then l_len > 125 then
-- All control frames MUST have a payload length of 125 bytes or less
-- and MUST NOT be fragmented.
Result.report_error (protocol_error, "Control frame MUST have a payload length of 125 bytes or less")
elseif l_len = 127 then -- TODO proof of concept read 8 bytes.
-- the following 8 bytes interpreted as a 64-bit unsigned integer
-- (the most significant bit MUST be 0) are the payload length.
-- Multibyte length quantities are expressed in network byte order.
s := next_bytes (l_socket, 8) -- 64 bits
debug ("ws")
print (" extended payload length=" + string_to_byte_representation (s))
io.put_new_line
end
if s.count < 8 then
Result.report_error (Invalid_data, "Incomplete data for 64 bit Extended payload length")
else
l_payload_len := s [8].natural_32_code.to_natural_64
l_payload_len := l_payload_len | (s [7].natural_32_code.to_natural_64 |<< 8)
l_payload_len := l_payload_len | (s [6].natural_32_code.to_natural_64 |<< 16)
l_payload_len := l_payload_len | (s [5].natural_32_code.to_natural_64 |<< 24)
l_payload_len := l_payload_len | (s [4].natural_32_code.to_natural_64 |<< 32)
l_payload_len := l_payload_len | (s [3].natural_32_code.to_natural_64 |<< 40)
l_payload_len := l_payload_len | (s [2].natural_32_code.to_natural_64 |<< 48)
l_payload_len := l_payload_len | (s [1].natural_32_code.to_natural_64 |<< 56)
end
elseif l_len = 126 then
s := next_bytes (l_socket, 2) -- 16 bits
debug ("ws")
print (" extended payload length bits=" + string_to_byte_representation (s))
io.put_new_line
end
if s.count < 2 then
Result.report_error (Invalid_data, "Incomplete data for 16 bit Extended payload length")
else
l_payload_len := s [2].natural_32_code.to_natural_64
l_payload_len := l_payload_len | (s [1].natural_32_code.to_natural_64 |<< 8)
end
else
l_payload_len := l_len.to_natural_64
end
debug ("ws")
print (" Full payload length=" + l_payload_len.out)
io.put_new_line
end
if Result.is_valid then
if l_has_mask then
l_masking_key := next_bytes (l_socket, 4) -- 32 bits
debug ("ws")
print (" Masking key bits=" + string_to_byte_representation (l_masking_key))
io.put_new_line
end
if l_masking_key.count < 4 then
debug ("ws")
print ("masking-key read stream -> " + l_socket.bytes_read.out + " bits%N")
end
Result.report_error (Invalid_data, "Incomplete data for Masking-key")
l_masking_key := Void
end
else
Result.report_error (protocol_error, "All frames sent from client to server are masked!")
end
if Result.is_valid then
l_chunk_size := 0x4000 -- 16 K
if l_payload_len > {INTEGER_32}.max_value.to_natural_64 then
-- Issue .. to big to store in STRING
-- FIXME !!!
Result.report_error (Message_too_large, "Can not handle payload data (len=" + l_payload_len.out + ")")
else
l_len := l_payload_len.to_integer_32
end
from
l_fetch_count := 0
l_remaining_len := l_len
until
l_fetch_count >= l_len or l_len = 0 or not Result.is_valid
loop
if l_remaining_len < l_chunk_size then
l_chunk_size := l_remaining_len
end
l_socket.read_stream (l_chunk_size)
l_bytes_read := l_socket.bytes_read
debug ("ws")
print ("read chunk size=" + l_chunk_size.out + " fetch_count=" + l_fetch_count.out + " l_len=" + l_len.out + " -> " + l_bytes_read.out + "bytes%N")
end
if l_bytes_read > 0 then
l_remaining_len := l_remaining_len - l_bytes_read
l_chunk := l_socket.last_string
if l_masking_key /= Void then
-- Masking
-- http://tools.ietf.org/html/rfc6455#section-5.3
unmask (l_chunk, l_fetch_count + 1, l_masking_key)
else
check
client_frame_should_always_be_encoded: False
end
end
l_fetch_count := l_fetch_count + l_bytes_read
Result.append_payload_data_chop (l_chunk, l_bytes_read, l_remaining_len = 0)
else
Result.report_error (internal_error, "Issue reading payload data...")
end
end
if is_verbose then
log (" Received " + l_fetch_count.out + " out of " + l_len.out + " bytes <===============", debug_level)
end
debug ("ws")
print (" -> ")
if attached Result.payload_data as l_payload_data then
s := l_payload_data.tail (l_fetch_count)
if s.count > 50 then
print (string_to_byte_hexa_representation (s.head (50) + ".."))
else
print (string_to_byte_hexa_representation (s))
end
print ("%N")
if Result.is_text and Result.is_fin and Result.fragment_count = 0 then
print (" -> ")
if s.count > 50 then
print (s.head (50) + "..")
else
print (s)
end
print ("%N")
end
end
end
end
end
end
end
end
end
if Result /= Void then
if attached Result.error as err then
if is_verbose then
log (" !Invalid frame: " + err.string, debug_level)
end
end
if Result.is_injected_control then
if attached Result.parent as l_parent then
if not Result.is_valid then
l_parent.report_error (protocol_error, "Invalid injected frame")
end
if Result.is_connection_close then
-- Return this and process the connection close right away!
l_parent.update_fin (True)
l_fin := Result.is_fin
else
Result := l_parent
l_fin := l_parent.is_fin
check
-- This is a control frame but occurs in fragmented frame.
inside_fragmented_frame: not l_fin
end
end
else
check
has_parent: False
end
l_fin := False -- This is a control frame but occurs in fragmented frame.
end
end
if not Result.is_valid then
is_data_frame_ok := False
end
else
is_data_frame_ok := False
end
end
end
has_error := Result = Void or else Result.has_error
rescue
retried := True
if Result /= Void then
Result.report_error (internal_error, "Internal error")
end
retry
end
feature -- Encoding
digest (a_sha1: SHA1): STRING
-- Digest of `a_sha1'.
-- Should by in SHA1 class
local
l_digest: SPECIAL [NATURAL_8]
index, l_upper: INTEGER
do
l_digest := a_sha1.digest
create Result.make (l_digest.count // 2)
from
index := l_digest.Lower
l_upper := l_digest.upper
until
index > l_upper
loop
Result.append_character (l_digest [index].to_character_8)
index := index + 1
end
end
feature {NONE} -- Socket helpers
next_bytes (a_socket: HTTPD_STREAM_SOCKET; nb: INTEGER): STRING
require
nb > 0
local
n, l_bytes_read: INTEGER
do
create Result.make (nb)
from
n := nb
until
n = 0
loop
a_socket.read_stream (nb)
l_bytes_read := a_socket.bytes_read
if l_bytes_read > 0 then
Result.append (a_socket.last_string)
n := n - l_bytes_read
else
n := 0
end
end
end
feature -- Masking Data Client - Server
unmask (a_chunk: STRING_8; a_pos: INTEGER; a_key: READABLE_STRING_8)
local
i, n: INTEGER
do
from
i := 1
n := a_chunk.count
until
i > n
loop
a_chunk.put_code (a_chunk.code (i).bit_xor (a_key [((i + (a_pos - 1) - 1) \\ 4) + 1].natural_32_code), i)
i := i + 1
end
end
append_chunk_unmasked (a_chunk: READABLE_STRING_8; a_pos: INTEGER; a_key: READABLE_STRING_8; a_target: STRING)
-- To convert masked data into unmasked data, or vice versa, the following
-- algorithm is applied. The same algorithm applies regardless of the
-- direction of the translation, e.g., the same steps are applied to
-- mask the data as to unmask the data.
-- Octet i of the transformed data ("transformed-octet-i") is the XOR of
-- octet i of the original data ("original-octet-i") with octet at index
-- i modulo 4 of the masking key ("masking-key-octet-j"):
-- j = i MOD 4
-- transformed-octet-i = original-octet-i XOR masking-key-octet-j
-- The payload length, indicated in the framing as frame-payload-length,
-- does NOT include the length of the masking key. It is the length of
-- the "Payload data", e.g., the number of bytes following the masking
-- key.
note
EIS: "name=Masking", "src=http://tools.ietf.org/html/rfc6455#section-5.3", "protocol=uri"
local
i, n: INTEGER
do
-- debug ("ws")
-- print ("append_chunk_unmasked (%"" + string_to_byte_representation (a_chunk) + "%",%N%Ta_pos=" + a_pos.out+ ", a_key, a_target #.count=" + a_target.count.out + ")%N")
-- end
from
i := 1
n := a_chunk.count
until
i > n
loop
a_target.append_code (a_chunk.code (i).bit_xor (a_key [((i + (a_pos - 1) - 1) \\ 4) + 1].natural_32_code))
i := i + 1
end
end
feature {NONE} -- Debug
to_byte_representation (a_integer: INTEGER): STRING
require
valid: a_integer >= 0 and then a_integer <= 255
local
l_val: INTEGER
do
create Result.make (8)
from
l_val := a_integer
until
l_val < 2
loop
Result.prepend_integer (l_val \\ 2)
l_val := l_val // 2
end
Result.prepend_integer (l_val)
end
string_to_byte_representation (s: STRING): STRING
require
valid: s.count > 0
local
i, n: INTEGER
do
n := s.count
create Result.make (8 * n)
if n > 0 then
from
i := 1
until
i > n
loop
if not Result.is_empty then
Result.append_character (':')
end
Result.append (to_byte_representation (s [i].code))
i := i + 1
end
end
end
string_to_byte_hexa_representation (s: STRING): STRING
local
i, n: INTEGER
c: INTEGER
do
n := s.count
create Result.make (8 * n)
if n > 0 then
from
i := 1
until
i > n
loop
if not Result.is_empty then
Result.append_character (':')
end
c := s [i].code
check
c <= 0xFF
end
Result.append_character (((c |>> 4) & 0xF).to_hex_character)
Result.append_character (((c) & 0xF).to_hex_character)
i := i + 1
end
end
end
note
copyright: "2011-2016, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
source: "[
Eiffel Software
5949 Hollister Ave., Goleta, CA 93117 USA
Telephone 805-685-1006, Fax 805-685-6869
Website http://www.eiffel.com
Customer support http://support.eiffel.com
]"
end

View File

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

View File

@@ -0,0 +1,44 @@
note
description: "Summary description for {WGI_STANDALONE_WEBSOCKET_CONNECTOR}."
author: ""
date: "$Date$"
revision: "$Revision$"
class
WGI_STANDALONE_WEBSOCKET_CONNECTOR [G -> WGI_EXECUTION create make end]
inherit
WGI_STANDALONE_CONNECTOR [G]
redefine
name, version
end
create
make,
make_with_base
feature -- Access
name: STRING_8
-- Name of Current connector
once
Result := "ws_httpd"
end
version: STRING_8
-- Version of Current connector
once
Result := "1.0"
end
note
copyright: "2011-2016, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
source: "[
Eiffel Software
5949 Hollister Ave., Goleta, CA 93117 USA
Telephone 805-685-1006, Fax 805-685-6869
Website http://www.eiffel.com
Customer support http://support.eiffel.com
]"
end

View File

@@ -0,0 +1,49 @@
note
description: "[
Component to launch the service using the default connector
Eiffel Web httpd for this class
The httpd default connector support options:
port: numeric such as 8099 (or equivalent string as "8099")
base: base_url (very specific to standalone server)
verbose: to display verbose output, useful for standalone connector
force_single_threaded: use only one thread, useful for standalone connector
check WSF_SERVICE_LAUNCHER for more documentation
]"
date: "$Date$"
revision: "$Revision$"
class
WSF_STANDALONE_WEBSOCKET_SERVICE_LAUNCHER [G -> WSF_WEBSOCKET_EXECUTION create make end]
inherit
WSF_STANDALONE_SERVICE_LAUNCHER [G]
redefine
connector
end
create
make,
make_and_launch
feature -- Status report
connector: WGI_STANDALONE_WEBSOCKET_CONNECTOR [G]
-- Default connector
;note
copyright: "2011-2016, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
source: "[
Eiffel Software
5949 Hollister Ave., Goleta, CA 93117 USA
Telephone 805-685-1006, Fax 805-685-6869
Website http://www.eiffel.com
Customer support http://support.eiffel.com
]"
end

View File

@@ -0,0 +1,79 @@
note
description: "[
Request execution based on attributes `request' and `response'.
Also support Upgrade to Websocket protocol.
]"
author: "$Author$"
date: "$Date$"
revision: "$Revision$"
deferred class
WSF_WEBSOCKET_EXECUTION
inherit
WSF_EXECUTION
rename
execute as http_execute
end
--create
-- make
feature -- Execution
frozen http_execute
local
ws: WEB_SOCKET
ws_h: like new_websocket_handler
do
create ws.make (request, response)
ws.open_ws_handshake
if ws.is_websocket then
if ws.has_error then
-- Upgrade to websocket raised an error
-- stay on standard HTTP/1.1 protocol
execute
else
ws_h := new_websocket_handler (ws)
ws_h.execute
end
else
execute
end
end
execute
-- Execute Current request,
-- getting data from `request'
-- and response to client via `response'.
deferred
end
feature -- Factory
new_websocket_handler (ws: WEB_SOCKET): WEB_SOCKET_HANDLER
-- Websocket request specific handler on socket `ws'.
--| For the creation, it requires an instance of `{WEB_SOCKET_EVENT_I}'
--| to receive the websocket events.
--| One can inherit from {WEB_SOCKET_EVENT_I} and implement the related
--| deferred features.
--| Or even provide a new class implementing {WEB_SOCKET_EVENT_I}.
require
is_websocket: ws.is_websocket
no_error: not ws.has_error
deferred
end
note
copyright: "2011-2016, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
source: "[
Eiffel Software
5949 Hollister Ave., Goleta, CA 93117 USA
Telephone 805-685-1006, Fax 805-685-6869
Website http://www.eiffel.com
Customer support http://support.eiffel.com
]"
end

View File

@@ -1,13 +1,3 @@
note
title: Web Server Foundation
description: Core of the Eiffel Web Framework, used to build web server application.
tags: ewf,server,httpd,request,connector
license: Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)
link[license]: https://github.com/EiffelWebFramework/EWF/blob/master/LICENSE
link[source]: "Github" https://github.com/EiffelWebFramework/EWF
link[doc]: "Documentation" http://eiffelwebframework.github.io/EWF/
end
package wsf
@@ -37,6 +27,7 @@ project
default_standalone = "default/standalone-safe.ecf"
default_standalone = "default/standalone.ecf"
note
title: Web Server Foundation
description: "[

View File

@@ -22,11 +22,11 @@ note
For instance, you can use
create s.make_and_launch_and_options (agent execute, <<["port", 8099]>>)
And if Nino is the default connector it will support:
And if the connector is the Standalone connector,
check {WSF_STANDALONE_SERVICE_LAUNCHER} for options description, such as:
port: numeric such as 8099 (or equivalent string as "8099")
base: base_url (very specific to standalone server)
force_single_threaded: use only one thread, useful for Nino
verbose: to display verbose output, useful for Nino
verbose: to display verbose output.
]"
date: "$Date$"
revision: "$Revision$"

View File

@@ -8,8 +8,8 @@ note
force_single_threaded: use only one thread, useful for Nino
verbose: to display verbose output, useful for Nino
]"
date: "$Date$"
revision: "$Revision$"
date: "$Date: 2016-08-06 13:34:52 +0200 (sam., 06 août 2016) $"
revision: "$Revision: 99106 $"
class
WSF_SERVICE_LAUNCHER_OPTIONS
@@ -85,6 +85,12 @@ feature -- Access
feature -- Helpers
has_option (a_opt_name: READABLE_STRING_GENERAL): BOOLEAN
-- Is there any value associated to option name `a_opt_name'?
do
Result := attached option (a_opt_name)
end
has_integer_option (a_opt_name: READABLE_STRING_GENERAL): BOOLEAN
-- Is there any INTEGER value associated to option name `a_opt_name'?
local
@@ -100,6 +106,29 @@ feature -- Helpers
end
end
has_string_32_option (a_opt_name: READABLE_STRING_GENERAL): BOOLEAN
-- Is there any string 32 value associated to option name `a_opt_name'?
do
if attached option (a_opt_name) as opt then
Result := attached {READABLE_STRING_GENERAL} opt
end
end
option_string_32_value (a_opt_name: READABLE_STRING_GENERAL; a_default: detachable READABLE_STRING_GENERAL): detachable IMMUTABLE_STRING_32
-- Unicode String value associated to option name `a_opt_name', other return `a_default'.
do
if attached option (a_opt_name) as opt then
if attached {READABLE_STRING_32} opt as s32 then
create Result.make_from_string (s32)
elseif attached {READABLE_STRING_GENERAL} opt as s then
create Result.make_from_string_general (s)
end
end
if Result = Void and a_default /= Void then
create Result.make_from_string_general (a_default)
end
end
option_integer_value (a_opt_name: READABLE_STRING_GENERAL; a_default: INTEGER): INTEGER
-- INTEGER value associated to option name `a_opt_name', other return `a_default'.
local

View File

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

View File

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

View File

@@ -0,0 +1,20 @@
note
description: "Summary description for {WSF_PROXY_SOCKET_FACTORY}."
author: ""
date: "$Date$"
revision: "$Revision$"
class
WSF_PROXY_SOCKET_FACTORY
inherit
WSF_PROXY_SOCKET_FACTORY_I
feature {NONE} -- Implementation
ssl_socket (a_host: READABLE_STRING_8; a_port: INTEGER): detachable NETWORK_STREAM_SOCKET
do
check supported: False end
end
end

View File

@@ -0,0 +1,30 @@
note
description: "Summary description for {WSF_PROXY_SOCKET_FACTORY}."
author: ""
date: "$Date$"
revision: "$Revision$"
class
WSF_PROXY_SOCKET_FACTORY
inherit
WSF_PROXY_SOCKET_FACTORY_I
redefine
is_ssl_supported
end
feature {NONE} -- Implementation
ssl_socket (a_host: READABLE_STRING_8; a_port: INTEGER): detachable SSL_NETWORK_STREAM_SOCKET
do
if attached create_from_name (a_host) as l_peer_address then
create Result.make_client_by_address_and_port (l_peer_address, a_port)
end
end
feature -- Status
is_ssl_supported: BOOLEAN = True
-- Is https:// supported?
end

View File

@@ -0,0 +1,67 @@
note
description: "Summary description for {WSF_PROXY_SOCKET_FACTORY_I}."
date: "$Date$"
revision: "$Revision$"
deferred class
WSF_PROXY_SOCKET_FACTORY_I
inherit
INET_ADDRESS_FACTORY
feature -- Access
socket_from_uri (a_uri: URI): like socket
local
l_port: INTEGER
do
if a_uri.is_valid and then attached a_uri.host as l_host then
l_port := a_uri.port
if a_uri.scheme.is_case_insensitive_equal_general ("https") then
if is_ssl_supported then
if l_port <= 0 then
l_port := 443
end
Result := ssl_socket (l_host, l_port)
end
elseif a_uri.scheme.is_case_insensitive_equal_general ("http") then
if l_port <= 0 then
l_port := 80
end
Result := socket (l_host, l_port)
end
end
end
feature -- Status
is_uri_supported (a_uri: URI): BOOLEAN
do
Result := a_uri.scheme.is_case_insensitive_equal_general ("http")
or else (
a_uri.scheme.is_case_insensitive_equal_general ("https")
and is_ssl_supported
)
end
is_ssl_supported: BOOLEAN
-- Is https:// supported?
do
end
feature {NONE} -- Implementation
socket (a_host: READABLE_STRING_8; a_port: INTEGER): detachable NETWORK_STREAM_SOCKET
do
if attached create_from_name (a_host) as l_peer_address then
create Result.make_client_by_address_and_port (l_peer_address, a_port)
end
end
ssl_socket (a_host: READABLE_STRING_8; a_port: INTEGER): detachable NETWORK_STREAM_SOCKET
require
is_ssl_supported: is_ssl_supported
deferred
end
end

View File

@@ -0,0 +1,303 @@
note
description: "Summary description for {WSF_SIMPLE_REVERSE_PROXY_HANDLER}."
date: "$Date$"
revision: "$Revision$"
class
WSF_SIMPLE_REVERSE_PROXY_HANDLER
create
make
feature {NONE} -- Initialization
make (a_remote_uri: READABLE_STRING_8)
do
create remote_uri.make_from_string (a_remote_uri)
timeout := 30 -- seconds. See {NETWORK_SOCKET}.default_timeout
connect_timeout := 5_000 -- 5 seconds.
is_via_header_supported := True
end
feature -- Access
remote_uri: URI
-- Url for the targetted service.
uri_rewriter: detachable WSF_URI_REWRITER assign set_uri_rewriter
-- URI rewriter component, to compute the URI on targetted service
-- based on current request.
feature -- Settings
connect_timeout: INTEGER assign set_connect_timeout
-- In milliseconds.
timeout: INTEGER assign set_timeout
-- In seconds.
is_via_header_supported: BOOLEAN
-- Via: header supported.
-- Default: True.
feature -- Change
set_uri_rewriter (a_rewriter: like uri_rewriter)
do
uri_rewriter := a_rewriter
end
set_timeout (a_timeout_in_seconds: INTEGER)
-- in seconds.
do
timeout := a_timeout_in_seconds
end
set_connect_timeout (a_timeout_in_milliseconds: INTEGER)
-- in milliseconds.
do
connect_timeout := a_timeout_in_milliseconds
end
set_is_via_header_supported (b: BOOLEAN)
-- Set `is_via_header_supported' to `b'.
do
is_via_header_supported := b
end
feature -- Execution
proxy_uri (request: WSF_REQUEST): STRING
-- URI to query on proxyfied host.
do
if attached uri_rewriter as r then
Result := r.uri (request)
else
Result := request.request_uri
end
end
execute (request: WSF_REQUEST; response: WSF_RESPONSE)
-- Execute reverse proxy request.
local
h: HTTP_HEADER
l_http_query: STRING
l_status_line: STRING
l_max_forward: INTEGER
l_via: detachable STRING
l_protocol: STRING
i: INTEGER
l_completed: BOOLEAN
l_remote_uri: like remote_uri
l_socket_factory: WSF_PROXY_SOCKET_FACTORY
do
l_remote_uri := remote_uri
create l_socket_factory
if not l_socket_factory.is_uri_supported (l_remote_uri) then
send_error (request, response, {HTTP_STATUS_CODE}.bad_gateway, l_remote_uri.scheme + " is not supported! [for remote " + l_remote_uri.string + "]")
elseif attached l_socket_factory.socket_from_uri (l_remote_uri) as l_socket then
l_socket.set_connect_timeout (connect_timeout) -- milliseconds
l_socket.set_timeout (timeout) -- seconds
l_socket.connect
if l_socket.is_connected then
create l_http_query.make_from_string (request.request_method)
l_http_query.append_character (' ')
l_http_query.append (l_remote_uri.path)
l_http_query.append (proxy_uri (request))
l_http_query.append_character (' ')
l_http_query.append (request.server_protocol)
if attached request.raw_header_data as l_raw_header then
i := l_raw_header.substring_index ("%R%N", 1)
if i > 0 then
-- Skip the first status line.
create h.make_from_raw_header_data (l_raw_header.substring (i + 2, l_raw_header.count))
else
create h.make_from_raw_header_data (l_raw_header)
end
if attached l_remote_uri.host as l_remote_host then
if l_remote_uri.port > 0 then
h.put_header_key_value ("Host", l_remote_host + ":" + l_remote_uri.port.out)
else
h.put_header_key_value ("Host", l_remote_host)
end
end
-- Via header
if is_via_header_supported then
if attached h.item ("Via") as v then
l_via := v
l_via.append (", ")
else
create l_via.make_empty
end
l_via.append (request.server_protocol + " " + request.server_name + " (PROXY-" + request.server_software + ")")
h.put_header_key_value ("Via", l_via)
end
-- Max-Forwards header handling
if attached h.item ("Max-Forwards") as h_max_forward then
-- Max-Forwards: 0 stop, otherwise decrement by one.
-- see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.31
if h_max_forward.is_integer then
l_max_forward := h_max_forward.to_integer - 1
if l_max_forward >= 0 then
h.put_header_key_value ("Max-Forwards", l_max_forward.out)
end
end
end
if l_max_forward < 0 then
-- i.e previous Max-Forwards was '0'
send_error (request, response, {HTTP_STATUS_CODE}.bad_gateway, "Reached maximum number of Forwards, not forwarded to " + l_remote_uri.string)
else
l_socket.put_string (l_http_query)
l_socket.put_string ("%R%N")
l_socket.put_string (h.string)
l_socket.put_string ("%R%N")
if request.content_length_value > 0 then
request.read_input_data_into_file (l_socket)
end
-- Get HTTP status
l_socket.read_line_thread_aware
create l_status_line.make_from_string (l_socket.last_string)
-- Get HTTP header block
if attached next_http_header_block (l_socket) as l_resp_header then
create h.make_from_raw_header_data (l_resp_header)
if attached status_line_info (l_status_line) as l_status_info then
l_protocol := l_status_info.protocol
if attached l_status_info.reason_phrase as l_phrase then
response.set_status_code_with_reason_phrase (l_status_info.status_code, l_phrase)
else
response.set_status_code (l_status_info.status_code)
end
else
check has_status_line: False end
l_protocol := "1.0" -- Default?
response.set_status_code (80)
end
if is_via_header_supported then
if attached h.item ("Via") as v then
l_via := v
l_via.append (", ")
else
create l_via.make_empty
end
l_via.append (l_protocol + " " + request.server_name + " (PROXY-" + request.server_software + ")")
h.put_header_key_value ("Via", l_via)
end
response.add_header_lines (h)
from
l_socket.read_stream (2_048)
until
l_socket.was_error
or not l_socket.is_connected
or l_socket.bytes_read <= 0
or l_completed
loop
response.put_string (l_socket.last_string)
if l_socket.bytes_read = 2_048 then
l_socket.read_stream (2_048)
else
l_completed := True
end
end
else
send_error (request, response, {HTTP_STATUS_CODE}.internal_server_error, "Invalid response header!")
end
end
else
send_error (request, response, {HTTP_STATUS_CODE}.internal_server_error, "Can not access request header!")
end
else
send_error (request, response, {HTTP_STATUS_CODE}.gateway_timeout, "Unable to connect " + l_remote_uri.string)
end
else
send_error (request, response, {HTTP_STATUS_CODE}.bad_gateway, "Unable to connect " + l_remote_uri.string)
end
end
feature {NONE} -- Implementation
status_line_info (a_line: READABLE_STRING_8): detachable TUPLE [protocol: READABLE_STRING_8; status_code: INTEGER; reason_phrase: detachable READABLE_STRING_8]
-- Info from status line
--| Such as "HTTP/1.1 200 OK" -> ["1.1", 200, "OK"]
local
i,j: INTEGER
p,s: detachable READABLE_STRING_8
c: INTEGER
do
i := a_line.index_of (' ', 1)
if i > 0 then
p := a_line.substring (1, i - 1)
if p.starts_with_general ("HTTP/") then
p := p.substring (6, p.count) -- We could also keep HTTP/
end
j := i + 1
i := a_line.index_of (' ', j)
if i > 0 then
s := a_line.substring (j, i - 1)
if s.is_integer then
c := s.to_integer
s := a_line.substring (i + 1, a_line.count)
if s.is_whitespace then
s := Void
elseif s[s.count].is_space then
s := s.substring (1, s.count - 1)
end
Result := [p, c, s]
end
end
end
end
next_http_header_block (a_socket: NETWORK_STREAM_SOCKET): detachable STRING
local
h: STRING
do
create h.make_empty
from
a_socket.read_line_thread_aware
until
Result /= Void
or a_socket.was_error
or (a_socket.bytes_read = 0 or a_socket.bytes_read = -1)
or not a_socket.is_connected
loop
if a_socket.last_string.same_string ("%R") then
-- End of header
Result := h
else
h.append (a_socket.last_string)
h.append ("%N")
a_socket.read_line_thread_aware
end
end
end
send_error (request: WSF_REQUEST; response: WSF_RESPONSE; a_status_code: INTEGER; a_message: READABLE_STRING_8)
local
s: STRING
do
-- To send a response we need to setup, the status code and
-- the response headers.
create s.make_from_string (a_message)
debug
s.append ("%N(UTC time is " + (create {HTTP_DATE}.make_now_utc).rfc850_string + ").%N")
end
response.put_header ({HTTP_STATUS_CODE}.ok, <<["Content-Type", "plain/text"], ["Content-Length", s.count.out]>>)
response.set_status_code (a_status_code)
response.header.put_content_type_text_html
response.header.put_content_length (s.count)
if
attached request.http_connection as l_connection and then
l_connection.is_case_insensitive_equal_general ("keep-alive")
then
response.header.put_header_key_value ("Connection", "keep-alive")
end
response.put_string (s)
end
end

View File

@@ -0,0 +1,38 @@
note
description: "Summary description for {WSF_AGENT_URI_REWRITER}."
author: ""
date: "$Date$"
revision: "$Revision$"
class
WSF_AGENT_URI_REWRITER
inherit
WSF_URI_REWRITER
create
make
--convert
-- make ({FUNCTION [TUPLE [WSF_REQUEST], STRING]})
feature {NONE} -- Initialization
make (a_rewriter_function: like rewriter_function)
do
rewriter_function := a_rewriter_function
end
feature -- Access
rewriter_function: FUNCTION [TUPLE [WSF_REQUEST], STRING]
feature -- Conversion
uri (a_request: WSF_REQUEST): STRING
-- <Precursor>.
do
Result := rewriter_function (a_request)
end
end

View File

@@ -0,0 +1,16 @@
note
description: "Summary description for {WSF_URI_REWRITER}."
date: "$Date$"
revision: "$Revision$"
deferred class
WSF_URI_REWRITER
feature -- Conversion
uri (a_request: WSF_REQUEST): STRING
-- Rewritten request uri based on `a_request'.
deferred
end
end

View File

@@ -0,0 +1,34 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-15-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-15-0 http://www.eiffel.com/developers/xml/configuration-1-15-0.xsd" name="wsf_proxy" uuid="A39CCC27-BF63-4959-B881-7D0713F4C84A" library_target="wsf_proxy">
<target name="wsf_proxy">
<root all_classes="true"/>
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
<library name="http" location="..\..\..\library\network\protocol\http\http-safe.ecf"/>
<library name="net" location="$ISE_LIBRARY\library\net\net-safe.ecf"/>
<library name="net_ssl" location="$ISE_LIBRARY\unstable\library\network\socket\netssl\net_ssl-safe.ecf">
<condition>
<custom name="ssl_supported" value="true"/>
</condition>
</library>
<library name="uri" location="$ISE_LIBRARY\library\text\uri\uri-safe.ecf"/>
<library name="wsf" location="..\wsf\wsf-safe.ecf"/>
<cluster name="network" location=".\network\" recursive="true">
<file_rule>
<exclude>no_ssl</exclude>
<exclude>ssl</exclude>
</file_rule>
<cluster name="network_ssl" location="$|ssl\">
<condition>
<custom name="ssl_supported" value="true"/>
</condition>
</cluster>
<cluster name="network_no_ssl" location="$|no_ssl\">
<condition>
<custom name="ssl_supported" excluded_value="true"/>
</condition>
</cluster>
</cluster>
<cluster name="reverse_proxy" location=".\reverse_proxy\" recursive="true"/>
<cluster name="rewriter" location=".\rewriter\" recursive="true"/>
</target>
</system>

View File

@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-15-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-15-0 http://www.eiffel.com/developers/xml/configuration-1-15-0.xsd" name="wsf_proxy" uuid="A39CCC27-BF63-4959-B881-7D0713F4C84A" library_target="wsf_proxy">
<target name="wsf_proxy">
<root all_classes="true"/>
<option void_safety="none">
</option>
<library name="base" location="$ISE_LIBRARY\library\base\base.ecf"/>
<library name="http" location="..\..\..\library\network\protocol\http\http.ecf"/>
<library name="net" location="$ISE_LIBRARY\library\net\net.ecf"/>
<library name="net_ssl" location="$ISE_LIBRARY\unstable\library\network\socket\netssl\net_ssl.ecf">
<condition>
<custom name="ssl_supported" value="true"/>
</condition>
</library>
<library name="uri" location="$ISE_LIBRARY\library\text\uri\uri.ecf"/>
<library name="wsf" location="..\wsf\wsf.ecf"/>
<cluster name="network" location=".\network\" recursive="true">
<file_rule>
<exclude>no_ssl</exclude>
<exclude>ssl</exclude>
</file_rule>
<cluster name="network_ssl" location="$|ssl\">
<condition>
<custom name="ssl_supported" value="true"/>
</condition>
</cluster>
<cluster name="network_no_ssl" location="$|no_ssl\">
<condition>
<custom name="ssl_supported" excluded_value="true"/>
</condition>
</cluster>
</cluster>
<cluster name="reverse_proxy" location=".\reverse_proxy\" recursive="true"/>
<cluster name="rewriter" location=".\rewriter\" recursive="true"/>
</target>
</system>

View File

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

View File

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

View File

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

View File

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

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