Compare commits

..

16 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
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
48 changed files with 1593 additions and 126 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

@@ -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: IMMUTABLE_STRING_8
ca_crt: detachable IMMUTABLE_STRING_32
-- the signed certificate.
ca_key: IMMUTABLE_STRING_8
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,16 +262,24 @@ feature -- Element change
feature -- Element change
set_ca_crt (a_value: separate READABLE_STRING_8)
set_ca_crt (a_value: detachable separate READABLE_STRING_GENERAL)
-- Set `ca_crt' from `a_value'.
do
create ca_crt.make_from_separate (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: separate READABLE_STRING_8)
set_ca_key (a_value: detachable separate READABLE_STRING_GENERAL)
-- Set `ca_key' with `a_value'.
do
create ca_key.make_from_separate (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)
@@ -238,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

@@ -10,20 +10,29 @@
<option warning="true" full_class_checking="false" is_attached_by_default="true" void_safety="all" syntax="standard">
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
</option>
<setting name="concurrency" value="scoop"/>
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
<library name="net" location="$ISE_LIBRARY\library\net\net-safe.ecf"/>
<library name="net" 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" recursive="false">
<cluster name="ssl_network" location="$|ssl" recursive="true">
<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,6 +24,16 @@
<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

@@ -30,6 +30,16 @@
<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>

View File

@@ -29,6 +29,16 @@
<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>

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
@@ -206,23 +232,36 @@ feature -- Execution
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
@@ -230,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
@@ -252,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
@@ -286,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
@@ -297,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)
@@ -315,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
@@ -399,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
@@ -411,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
@@ -462,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

@@ -62,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)
@@ -82,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
@@ -124,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
@@ -291,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,58 +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_ssl_client_by_address_and_port, make_ssl_client_by_port,
make_client_by_address_and_port, make_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: STRING; a_key: STRING)
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, a_key)
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: STRING; a_key: STRING)
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, a_key)
set_certificates (a_crt_fn, a_key_fn)
end
make_empty
-- <Precursor>.
do
create {SSL_TCP_STREAM_SOCKET} socket.make_empty
end
feature -- Output
@@ -136,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,8 @@ inherit
make
end
TCP_STREAM_SOCKET_EXT
create
make_server_by_address_and_port,
make_server_by_port,
@@ -53,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)
@@ -71,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
@@ -82,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 @@
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

@@ -2,8 +2,8 @@ note
description: "[
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]
@@ -155,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
@@ -242,6 +248,11 @@ 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-2016, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"

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

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

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

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

@@ -59,6 +59,8 @@ echo Install library: ewf/wsf
%COPYCMD% %TMP_DIR%\library\server\wsf %TMP_CONTRIB_DIR%\library\web\framework\ewf\wsf
echo Install library: ewf/wsf_extension
%COPYCMD% %TMP_DIR%\library\server\wsf_extension %TMP_CONTRIB_DIR%\library\web\framework\ewf\wsf_extension
echo Install library: ewf/wsf_proxy
%COPYCMD% %TMP_DIR%\library\server\wsf_proxy %TMP_CONTRIB_DIR%\library\web\framework\ewf\wsf_proxy
echo Install library: ewf/wsf_html
%COPYCMD% %TMP_DIR%\library\server\wsf_html %TMP_CONTRIB_DIR%\library\web\framework\ewf\wsf_html
echo Install library: ewf/encoder