Added advanced settings for standalone connector
- max_concurrent_connections=100 - keep_alive_timeout=15 - max_tcp_clients=100 - socket_timeout=300 - max_keep_alive_requests=300 And then can be set via the options as well, and via .ini file. Also improved the verbose console output system.
This commit is contained in:
@@ -23,11 +23,7 @@ feature {NONE} -- Initialization
|
|||||||
-- Specific to `standalone' connector (the EiffelWeb server).
|
-- Specific to `standalone' connector (the EiffelWeb server).
|
||||||
-- See `{WSF_STANDALONE_SERVICE_LAUNCHER}.initialize'
|
-- See `{WSF_STANDALONE_SERVICE_LAUNCHER}.initialize'
|
||||||
set_service_option ("port", 9090)
|
set_service_option ("port", 9090)
|
||||||
set_service_option ("max_concurrent_connections", 10)
|
import_service_options (create {WSF_SERVICE_LAUNCHER_OPTIONS_FROM_INI}.make_from_file ("simple.ini"))
|
||||||
set_service_option ("keep_alive_timeout", 1)
|
|
||||||
set_service_option ("verbose", True)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|||||||
8
examples/simple/simple.ini
Normal file
8
examples/simple/simple.ini
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
verbose=true
|
||||||
|
verbose_level=ALERT
|
||||||
|
port=9090
|
||||||
|
#max_concurrent_connections=100
|
||||||
|
#keep_alive_timeout=15
|
||||||
|
#max_tcp_clients=100
|
||||||
|
#socket_timeout=300
|
||||||
|
#max_keep_alive_requests=300
|
||||||
@@ -10,17 +10,26 @@ feature {NONE} -- Initialization
|
|||||||
|
|
||||||
make
|
make
|
||||||
do
|
do
|
||||||
http_server_port := 80
|
http_server_port := default_http_server_port
|
||||||
max_concurrent_connections := 100
|
max_concurrent_connections := default_max_concurrent_connections
|
||||||
max_tcp_clients := 100
|
max_tcp_clients := default_max_tcp_clients
|
||||||
socket_accept_timeout := 1_000
|
socket_timeout := default_socket_timeout
|
||||||
socket_connect_timeout := 5_000
|
keep_alive_timeout := default_keep_alive_timeout
|
||||||
keep_alive_timeout := 5
|
max_keep_alive_requests := default_max_keep_alive_requests
|
||||||
is_secure := False
|
is_secure := False
|
||||||
create ca_crt.make_empty
|
create ca_crt.make_empty
|
||||||
create ca_key.make_empty
|
create ca_key.make_empty
|
||||||
end
|
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
|
feature -- Access
|
||||||
|
|
||||||
Server_details: STRING_8
|
Server_details: STRING_8
|
||||||
@@ -31,9 +40,15 @@ feature -- Access
|
|||||||
http_server_name: detachable READABLE_STRING_8 assign set_http_server_name
|
http_server_name: detachable READABLE_STRING_8 assign set_http_server_name
|
||||||
http_server_port: INTEGER assign set_http_server_port
|
http_server_port: INTEGER assign set_http_server_port
|
||||||
max_tcp_clients: INTEGER assign set_max_tcp_clients
|
max_tcp_clients: INTEGER assign set_max_tcp_clients
|
||||||
|
-- Listen on socket for at most `queue' connections.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
max_concurrent_connections: INTEGER assign set_max_concurrent_connections
|
max_concurrent_connections: INTEGER assign set_max_concurrent_connections
|
||||||
socket_accept_timeout: INTEGER assign set_socket_accept_timeout
|
-- Max number of concurrent connections.
|
||||||
socket_connect_timeout: INTEGER assign set_socket_connect_timeout
|
|
||||||
|
|
||||||
force_single_threaded: BOOLEAN assign set_force_single_threaded
|
force_single_threaded: BOOLEAN assign set_force_single_threaded
|
||||||
do
|
do
|
||||||
@@ -43,15 +58,35 @@ feature -- Access
|
|||||||
is_verbose: BOOLEAN assign set_is_verbose
|
is_verbose: BOOLEAN assign set_is_verbose
|
||||||
-- Display verbose message to the output?
|
-- Display verbose message to the output?
|
||||||
|
|
||||||
|
verbose_level: INTEGER assign set_verbose_level
|
||||||
|
-- Verbosity of output.
|
||||||
|
|
||||||
keep_alive_timeout: INTEGER assign set_keep_alive_timeout
|
keep_alive_timeout: INTEGER assign set_keep_alive_timeout
|
||||||
-- Persistent connection timeout
|
-- Persistent connection timeout.
|
||||||
|
-- Number of seconds the server waits after a request has been served before it closes the connection.
|
||||||
-- Timeout unit in Seconds.
|
-- Timeout unit in Seconds.
|
||||||
|
-- By default: 5 seconds.
|
||||||
|
|
||||||
|
max_keep_alive_requests: INTEGER assign set_max_keep_alive_requests
|
||||||
|
-- Maximum number of requests allowed per persistent connection.
|
||||||
|
-- Recommended a high setting.
|
||||||
|
-- To disable KeepAlive, set `max_keep_alive_requests' to 0.
|
||||||
|
-- By default: 100 .
|
||||||
|
|
||||||
has_ssl_support: BOOLEAN
|
has_ssl_support: BOOLEAN
|
||||||
-- Has SSL support?
|
-- Has SSL support?
|
||||||
deferred
|
deferred
|
||||||
end
|
end
|
||||||
|
|
||||||
|
request_settings: HTTPD_REQUEST_SETTINGS
|
||||||
|
do
|
||||||
|
Result.is_verbose := is_verbose
|
||||||
|
Result.verbose_level := verbose_level
|
||||||
|
Result.timeout := socket_timeout
|
||||||
|
Result.keep_alive_timeout := keep_alive_timeout
|
||||||
|
Result.max_keep_alive_requests := max_keep_alive_requests
|
||||||
|
end
|
||||||
|
|
||||||
feature -- Access: SSL
|
feature -- Access: SSL
|
||||||
|
|
||||||
is_secure: BOOLEAN
|
is_secure: BOOLEAN
|
||||||
@@ -75,7 +110,6 @@ feature -- Element change
|
|||||||
else
|
else
|
||||||
create {IMMUTABLE_STRING_8} http_server_name.make_from_separate (v)
|
create {IMMUTABLE_STRING_8} http_server_name.make_from_separate (v)
|
||||||
end
|
end
|
||||||
--| Missing postcondition.
|
|
||||||
end
|
end
|
||||||
|
|
||||||
unset_http_server_name
|
unset_http_server_name
|
||||||
@@ -110,27 +144,35 @@ feature -- Element change
|
|||||||
max_concurrent_connections_set : max_concurrent_connections = v
|
max_concurrent_connections_set : max_concurrent_connections = v
|
||||||
end
|
end
|
||||||
|
|
||||||
set_socket_accept_timeout (v: like socket_accept_timeout)
|
set_socket_timeout (a_nb_seconds: like socket_timeout)
|
||||||
-- Set `socket_accept_timeout' with `v'
|
-- Set `socket_timeout' with `a_nb_seconds'
|
||||||
do
|
do
|
||||||
socket_accept_timeout := v
|
socket_timeout := a_nb_seconds
|
||||||
ensure
|
ensure
|
||||||
socket_accept_timeout_set: socket_accept_timeout = v
|
socket_timeout_set: socket_timeout = a_nb_seconds
|
||||||
end
|
end
|
||||||
|
|
||||||
set_socket_connect_timeout (v: like socket_connect_timeout)
|
set_keep_alive_timeout (a_seconds: like keep_alive_timeout)
|
||||||
-- Set `socket_connect_timeout' with `v'
|
-- Set `keep_alive_timeout' with `a_seconds'
|
||||||
do
|
do
|
||||||
socket_connect_timeout := v
|
keep_alive_timeout := a_seconds
|
||||||
ensure
|
ensure
|
||||||
socket_connect_timeout_set: socket_connect_timeout = v
|
keep_alive_timeout_set: keep_alive_timeout = a_seconds
|
||||||
|
end
|
||||||
|
|
||||||
|
set_max_keep_alive_requests (nb: like max_keep_alive_requests)
|
||||||
|
-- Set `max_keep_alive_requests' with `nb'
|
||||||
|
do
|
||||||
|
max_keep_alive_requests := nb
|
||||||
|
ensure
|
||||||
|
max_keep_alive_requests_set: max_keep_alive_requests = nb
|
||||||
end
|
end
|
||||||
|
|
||||||
set_force_single_threaded (v: like force_single_threaded)
|
set_force_single_threaded (v: like force_single_threaded)
|
||||||
-- Force server to handle incoming request in a single thread.
|
-- Force server to handle incoming request in a single thread.
|
||||||
-- i.e set max_concurrent_connections to 0!
|
-- i.e set max_concurrent_connections to 0!
|
||||||
obsolete
|
obsolete
|
||||||
"Use set_max_concurrent_connections (0) [June/2016]"
|
"Use set_max_concurrent_connections (0) [June/2016]"
|
||||||
do
|
do
|
||||||
if v then
|
if v then
|
||||||
set_max_concurrent_connections (0)
|
set_max_concurrent_connections (0)
|
||||||
@@ -148,12 +190,12 @@ feature -- Element change
|
|||||||
is_verbose_set: is_verbose = b
|
is_verbose_set: is_verbose = b
|
||||||
end
|
end
|
||||||
|
|
||||||
set_keep_alive_timeout (a_seconds: like keep_alive_timeout)
|
set_verbose_level (lev: INTEGER)
|
||||||
-- Set `keep_alive_timeout' with `a_seconds'
|
-- Set `verbose_level' to `lev'.
|
||||||
do
|
do
|
||||||
keep_alive_timeout := a_seconds
|
verbose_level := lev
|
||||||
ensure
|
ensure
|
||||||
keep_alive_timeout_set: keep_alive_timeout = a_seconds
|
verbose_level_set: verbose_level = lev
|
||||||
end
|
end
|
||||||
|
|
||||||
mark_secure
|
mark_secure
|
||||||
|
|||||||
@@ -0,0 +1,64 @@
|
|||||||
|
note
|
||||||
|
description: "[
|
||||||
|
Request settings for the standalone HTTPd server.
|
||||||
|
]"
|
||||||
|
author: "$Author$"
|
||||||
|
date: "$Date$"
|
||||||
|
revision: "$Revision$"
|
||||||
|
|
||||||
|
expanded class
|
||||||
|
HTTPD_REQUEST_SETTINGS
|
||||||
|
|
||||||
|
feature -- Access
|
||||||
|
|
||||||
|
is_verbose: BOOLEAN assign set_is_verbose
|
||||||
|
-- Is verbose?
|
||||||
|
|
||||||
|
verbose_level: INTEGER assign set_verbose_level
|
||||||
|
-- Verbosity of output.
|
||||||
|
|
||||||
|
timeout: INTEGER assign set_timeout
|
||||||
|
-- Amount of seconds that the server waits for receipts and transmissions 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.
|
||||||
|
-- Unit in Seconds.
|
||||||
|
|
||||||
|
max_keep_alive_requests: INTEGER assign set_max_keep_alive_requests
|
||||||
|
-- Maximum number of requests allowed per persistent connection.
|
||||||
|
|
||||||
|
feature -- Change
|
||||||
|
|
||||||
|
set_is_verbose (b: BOOLEAN)
|
||||||
|
-- Set `is_verbose' to `b'.
|
||||||
|
do
|
||||||
|
is_verbose := b
|
||||||
|
end
|
||||||
|
|
||||||
|
set_verbose_level (lev: INTEGER)
|
||||||
|
-- Set `verbose_level' to `lev'.
|
||||||
|
do
|
||||||
|
verbose_level := lev
|
||||||
|
end
|
||||||
|
|
||||||
|
set_timeout (a_timeout_in_seconds: INTEGER)
|
||||||
|
-- Set `timeout' to `a_timeout_in_seconds'.
|
||||||
|
do
|
||||||
|
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
|
||||||
|
keep_alive_timeout := a_timeout_in_seconds
|
||||||
|
end
|
||||||
|
|
||||||
|
set_max_keep_alive_requests (nb: like max_keep_alive_requests)
|
||||||
|
-- Set `max_keep_alive_requests' with `nb'
|
||||||
|
do
|
||||||
|
max_keep_alive_requests := nb
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
note
|
||||||
|
description: "[
|
||||||
|
Constant value to define the logging level.
|
||||||
|
]"
|
||||||
|
date: "$Date$"
|
||||||
|
revision: "$Revision$"
|
||||||
|
|
||||||
|
deferred class
|
||||||
|
HTTPD_LOGGER_CONSTANTS
|
||||||
|
|
||||||
|
feature -- Access
|
||||||
|
|
||||||
|
alert_level: INTEGER = 1 -- 0000 0001
|
||||||
|
critical_level: INTEGER = 2 -- 0000 0010
|
||||||
|
error_level: INTEGER = 4 -- 0000 0100
|
||||||
|
warning_level: INTEGER = 8 -- 0000 1000
|
||||||
|
|
||||||
|
notice_level: INTEGER = 16 -- 0001 0000
|
||||||
|
information_level: INTEGER = 32 -- 0010 0000
|
||||||
|
debug_level: INTEGER = 64 -- 0100 0000
|
||||||
|
|
||||||
|
end
|
||||||
@@ -9,12 +9,20 @@ deferred class
|
|||||||
inherit
|
inherit
|
||||||
HTTPD_DEBUG_FACILITIES
|
HTTPD_DEBUG_FACILITIES
|
||||||
|
|
||||||
|
HTTPD_LOGGER_CONSTANTS
|
||||||
|
|
||||||
feature {NONE} -- Initialization
|
feature {NONE} -- Initialization
|
||||||
|
|
||||||
make
|
make (a_request_settings: HTTPD_REQUEST_SETTINGS)
|
||||||
do
|
do
|
||||||
reset
|
reset
|
||||||
persistent_connection_timeout := 5 -- seconds
|
-- Import global request settings.
|
||||||
|
timeout := a_request_settings.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
|
||||||
end
|
end
|
||||||
|
|
||||||
reset
|
reset
|
||||||
@@ -108,16 +116,29 @@ feature -- Access
|
|||||||
feature -- Settings
|
feature -- Settings
|
||||||
|
|
||||||
is_verbose: BOOLEAN
|
is_verbose: BOOLEAN
|
||||||
|
-- Output messages?
|
||||||
|
|
||||||
|
verbose_level: INTEGER
|
||||||
|
-- Output verbosity.
|
||||||
|
|
||||||
is_persistent_connection_supported: BOOLEAN
|
is_persistent_connection_supported: BOOLEAN
|
||||||
-- Is persistent connection supported?
|
-- Is persistent connection supported?
|
||||||
do
|
do
|
||||||
Result := {HTTPD_SERVER}.is_persistent_connection_supported
|
Result := {HTTPD_SERVER}.is_persistent_connection_supported and then max_keep_alive_requests > 0
|
||||||
end
|
end
|
||||||
|
|
||||||
persistent_connection_timeout: INTEGER -- seconds
|
is_next_persistent_connection_supported: BOOLEAN
|
||||||
|
-- Is next persistent connection supported?
|
||||||
|
-- note: it is relevant only if `is_persistent_connection_supported' is True.
|
||||||
|
|
||||||
|
timeout: INTEGER -- seconds
|
||||||
|
-- Amount of seconds that the server waits for receipts and transmissions during communications.
|
||||||
|
|
||||||
|
max_keep_alive_requests: INTEGER
|
||||||
|
-- Maximum number of requests allowed per persistent connection.
|
||||||
|
|
||||||
|
keep_alive_timeout: INTEGER -- seconds
|
||||||
-- Number of seconds for persistent connection timeout.
|
-- Number of seconds for persistent connection timeout.
|
||||||
-- Default: 5 sec.
|
|
||||||
|
|
||||||
feature -- Status report
|
feature -- Status report
|
||||||
|
|
||||||
@@ -163,7 +184,7 @@ feature -- Execution
|
|||||||
local
|
local
|
||||||
l_socket: like client_socket
|
l_socket: like client_socket
|
||||||
l_exit: BOOLEAN
|
l_exit: BOOLEAN
|
||||||
n: INTEGER
|
n,m: INTEGER
|
||||||
do
|
do
|
||||||
l_socket := client_socket
|
l_socket := client_socket
|
||||||
check
|
check
|
||||||
@@ -173,15 +194,21 @@ feature -- Execution
|
|||||||
from
|
from
|
||||||
-- Process persistent connection as long the socket is not closed.
|
-- Process persistent connection as long the socket is not closed.
|
||||||
n := 0
|
n := 0
|
||||||
|
m := max_keep_alive_requests
|
||||||
|
is_next_persistent_connection_supported := True
|
||||||
until
|
until
|
||||||
l_exit
|
l_exit
|
||||||
loop
|
loop
|
||||||
n := n + 1
|
n := n + 1
|
||||||
|
if n >= m then
|
||||||
|
is_next_persistent_connection_supported := False
|
||||||
|
end
|
||||||
-- FIXME: it seems to be called one more time, mostly to see this is done.
|
-- FIXME: it seems to be called one more time, mostly to see this is done.
|
||||||
execute_request
|
execute_request
|
||||||
l_exit := not is_persistent_connection_supported
|
l_exit := not is_persistent_connection_supported
|
||||||
or has_error or l_socket.is_closed or not l_socket.is_open_read
|
or not is_next_persistent_connection_supported -- related to `max_keep_alive_requests'
|
||||||
or not is_persistent_connection_requested
|
or not is_persistent_connection_requested
|
||||||
|
or has_error or l_socket.is_closed or not l_socket.is_open_read
|
||||||
reset_request
|
reset_request
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -193,7 +220,6 @@ feature -- Execution
|
|||||||
l_remote_info: detachable like remote_info
|
l_remote_info: detachable like remote_info
|
||||||
l_socket: like client_socket
|
l_socket: like client_socket
|
||||||
l_is_ready: BOOLEAN
|
l_is_ready: BOOLEAN
|
||||||
i: INTEGER
|
|
||||||
do
|
do
|
||||||
l_socket := client_socket
|
l_socket := client_socket
|
||||||
check
|
check
|
||||||
@@ -211,10 +237,11 @@ feature -- Execution
|
|||||||
|
|
||||||
--| TODO: add configuration options for socket timeout.
|
--| TODO: add configuration options for socket timeout.
|
||||||
--| set by default 5 seconds.
|
--| set by default 5 seconds.
|
||||||
l_socket.set_timeout (persistent_connection_timeout) -- 5 seconds!
|
l_socket.set_timeout (keep_alive_timeout) -- 5 seconds!
|
||||||
l_is_ready := l_socket.ready_for_reading
|
l_is_ready := l_socket.ready_for_reading
|
||||||
|
|
||||||
if l_is_ready then
|
if l_is_ready then
|
||||||
|
l_socket.set_timeout (timeout) -- FIXME: return a 408 Request Timeout response ..
|
||||||
create l_remote_info
|
create l_remote_info
|
||||||
if attached l_socket.peer_address as l_addr then
|
if attached l_socket.peer_address as l_addr then
|
||||||
l_remote_info.addr := l_addr.host_address.host_address
|
l_remote_info.addr := l_addr.host_address.host_address
|
||||||
@@ -234,10 +261,13 @@ feature -- Execution
|
|||||||
if l_is_ready then
|
if l_is_ready then
|
||||||
-- check catch_bad_incoming_connection: False end
|
-- check catch_bad_incoming_connection: False end
|
||||||
if is_verbose then
|
if is_verbose then
|
||||||
log ("ERROR: invalid HTTP incoming request")
|
log (request_header + "%NWARNING: invalid HTTP incoming request", warning_level)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
|
if is_verbose then
|
||||||
|
log (request_header, information_level)
|
||||||
|
end
|
||||||
process_request (l_socket)
|
process_request (l_socket)
|
||||||
end
|
end
|
||||||
debug ("dbglog")
|
debug ("dbglog")
|
||||||
@@ -281,11 +311,11 @@ feature -- Parsing
|
|||||||
do
|
do
|
||||||
create txt.make (64)
|
create txt.make (64)
|
||||||
request_header := txt
|
request_header := txt
|
||||||
if
|
if
|
||||||
not has_error and then
|
not has_error and then
|
||||||
a_socket.is_readable and then
|
a_socket.is_readable and then
|
||||||
attached next_line (a_socket) as l_request_line and then
|
attached next_line (a_socket) as l_request_line and then
|
||||||
not l_request_line.is_empty
|
not l_request_line.is_empty
|
||||||
then
|
then
|
||||||
txt.append (l_request_line)
|
txt.append (l_request_line)
|
||||||
txt.append_character ('%N')
|
txt.append_character ('%N')
|
||||||
@@ -302,8 +332,10 @@ feature -- Parsing
|
|||||||
line = Void or end_of_stream or has_error
|
line = Void or end_of_stream or has_error
|
||||||
loop
|
loop
|
||||||
n := line.count
|
n := line.count
|
||||||
if l_is_verbose then
|
debug ("ew_standalone")
|
||||||
log (line)
|
if l_is_verbose then
|
||||||
|
log (line, debug_level)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
pos := line.index_of (':', 1)
|
pos := line.index_of (':', 1)
|
||||||
if pos > 0 then
|
if pos > 0 then
|
||||||
@@ -350,9 +382,11 @@ feature -- Parsing
|
|||||||
local
|
local
|
||||||
n, pos, next_pos: INTEGER
|
n, pos, next_pos: INTEGER
|
||||||
do
|
do
|
||||||
if is_verbose then
|
debug ("ew_standalone")
|
||||||
log ("%N## Parse HTTP request line ##")
|
if is_verbose then
|
||||||
log (line)
|
log ("%N## Parse HTTP request line ##", debug_level)
|
||||||
|
log (line, debug_level)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
pos := line.index_of (' ', 1)
|
pos := line.index_of (' ', 1)
|
||||||
method := line.substring (1, pos - 1)
|
method := line.substring (1, pos - 1)
|
||||||
@@ -369,7 +403,7 @@ feature -- Parsing
|
|||||||
next_line (a_socket: HTTPD_STREAM_SOCKET): detachable STRING
|
next_line (a_socket: HTTPD_STREAM_SOCKET): detachable STRING
|
||||||
-- Next line fetched from `a_socket' is available.
|
-- Next line fetched from `a_socket' is available.
|
||||||
require
|
require
|
||||||
not_has_error: not has_error
|
not_has_error: not has_error or is_verbose
|
||||||
is_readable: a_socket.is_open_read
|
is_readable: a_socket.is_open_read
|
||||||
local
|
local
|
||||||
retried: BOOLEAN
|
retried: BOOLEAN
|
||||||
@@ -380,19 +414,19 @@ feature -- Parsing
|
|||||||
elseif a_socket.readable then
|
elseif a_socket.readable then
|
||||||
a_socket.read_line_thread_aware
|
a_socket.read_line_thread_aware
|
||||||
Result := a_socket.last_string
|
Result := a_socket.last_string
|
||||||
-- Do no check `socket_ok' before socket operation,
|
-- Do no check `socket_ok' before socket operation,
|
||||||
-- otherwise it may be False, due to error during other socket operation in same thread.
|
-- otherwise it may be False, due to error during other socket operation in same thread.
|
||||||
if not a_socket.socket_ok then
|
if not a_socket.socket_ok then
|
||||||
has_error := True
|
has_error := True
|
||||||
if is_verbose then
|
if is_verbose then
|
||||||
log ("%N## Socket is not ok! ##")
|
log (request_header +"%N" + Result + "%N## socket_ok=False! ##", debug_level)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
-- Error with socket...
|
-- Error with socket...
|
||||||
has_error := True
|
has_error := True
|
||||||
if is_verbose then
|
if is_verbose then
|
||||||
log ("%N## Socket is not readable! ##")
|
log (request_header + "%N## Socket is not readable! ##", debug_level)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
rescue
|
rescue
|
||||||
@@ -412,13 +446,17 @@ feature -- Output
|
|||||||
logger_set: logger = a_logger
|
logger_set: logger = a_logger
|
||||||
end
|
end
|
||||||
|
|
||||||
log (m: STRING)
|
log (m: STRING; a_level: INTEGER)
|
||||||
-- Log message `m'.
|
-- Log message `m'.
|
||||||
|
require
|
||||||
|
is_verbose: is_verbose
|
||||||
do
|
do
|
||||||
if attached logger as l_logger then
|
if is_verbose and (verbose_level & a_level) = a_level then
|
||||||
l_logger.log (m)
|
if attached logger as l_logger then
|
||||||
else
|
l_logger.log (m)
|
||||||
io.put_string (m + "%N")
|
else
|
||||||
|
io.put_string (m + "%N")
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -51,6 +51,9 @@ feature -- Access
|
|||||||
is_verbose: BOOLEAN
|
is_verbose: BOOLEAN
|
||||||
-- Is verbose for output messages.
|
-- Is verbose for output messages.
|
||||||
|
|
||||||
|
verbose_level: INTEGER
|
||||||
|
-- Verbosity of output.
|
||||||
|
|
||||||
configuration: HTTPD_CONFIGURATION
|
configuration: HTTPD_CONFIGURATION
|
||||||
-- Associated server configuration.
|
-- Associated server configuration.
|
||||||
|
|
||||||
@@ -100,7 +103,16 @@ feature -- Execution
|
|||||||
apply_configuration
|
apply_configuration
|
||||||
is_terminated := False
|
is_terminated := False
|
||||||
if is_verbose then
|
if is_verbose then
|
||||||
log ("%N%NStarting Web Application Server (port=" + configuration.http_server_port.out + "):%N")
|
log ("%N%NStarting Web Application Server ...")
|
||||||
|
log (" - port = " + configuration.http_server_port.out)
|
||||||
|
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 (" - keep_alive_timeout = " + configuration.keep_alive_timeout.out + " seconds")
|
||||||
|
log (" - max_keep_alive_requests = " + configuration.max_keep_alive_requests.out)
|
||||||
|
if configuration.verbose_level > 0 then
|
||||||
|
log (" - verbose_level = " + configuration.verbose_level.out)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
is_shutdown_requested := False
|
is_shutdown_requested := False
|
||||||
listen
|
listen
|
||||||
@@ -150,7 +162,7 @@ feature -- Listening
|
|||||||
|
|
||||||
if not l_listening_socket.is_bound then
|
if not l_listening_socket.is_bound then
|
||||||
if is_verbose then
|
if is_verbose then
|
||||||
log ("Socket could not be bound on port " + l_http_port.out)
|
log ("Socket could not be bound on port " + l_http_port.out + " !")
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
l_http_port := l_listening_socket.port
|
l_http_port := l_listening_socket.port
|
||||||
@@ -159,9 +171,9 @@ feature -- Listening
|
|||||||
l_listening_socket.listen (configuration.max_tcp_clients)
|
l_listening_socket.listen (configuration.max_tcp_clients)
|
||||||
if is_verbose then
|
if is_verbose then
|
||||||
if configuration.is_secure then
|
if configuration.is_secure then
|
||||||
log ("%NHTTP Connection Server ready on port " + l_http_port.out +" : https://localhost:" + l_http_port.out + "/")
|
log ("%NListening on port " + l_http_port.out +" : https://localhost:" + l_http_port.out + "/")
|
||||||
else
|
else
|
||||||
log ("%NHTTP Connection Server ready on port " + l_http_port.out +" : http://localhost:" + l_http_port.out + "/")
|
log ("%NListening on port " + l_http_port.out +" : http://localhost:" + l_http_port.out + "/")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
on_launched (l_http_port)
|
on_launched (l_http_port)
|
||||||
@@ -312,6 +324,7 @@ feature -- Configuration change
|
|||||||
is_not_launched: not is_launched
|
is_not_launched: not is_launched
|
||||||
do
|
do
|
||||||
is_verbose := configuration.is_verbose
|
is_verbose := configuration.is_verbose
|
||||||
|
verbose_level := configuration.verbose_level
|
||||||
end
|
end
|
||||||
|
|
||||||
feature -- Output
|
feature -- Output
|
||||||
|
|||||||
@@ -24,13 +24,10 @@ create
|
|||||||
|
|
||||||
feature {NONE} -- Initialization
|
feature {NONE} -- Initialization
|
||||||
|
|
||||||
make_with_connector (conn: like connector)
|
make_with_connector (a_request_settings: HTTPD_REQUEST_SETTINGS; conn: like connector)
|
||||||
do
|
do
|
||||||
make
|
make (a_request_settings)
|
||||||
connector := conn
|
connector := conn
|
||||||
-- if conn /= Void then
|
|
||||||
-- set_is_verbose (is_connector_verbose (conn))
|
|
||||||
-- end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
feature -- Access
|
feature -- Access
|
||||||
@@ -56,11 +53,6 @@ feature -- SCOOP helpers
|
|||||||
Result := conn.base
|
Result := conn.base
|
||||||
end
|
end
|
||||||
|
|
||||||
is_connector_verbose (conn: separate WGI_STANDALONE_CONNECTOR [G]): BOOLEAN
|
|
||||||
do
|
|
||||||
Result := conn.is_verbose
|
|
||||||
end
|
|
||||||
|
|
||||||
feature -- Request processing
|
feature -- Request processing
|
||||||
|
|
||||||
process_request (a_socket: HTTPD_STREAM_SOCKET)
|
process_request (a_socket: HTTPD_STREAM_SOCKET)
|
||||||
@@ -87,7 +79,7 @@ feature -- Request processing
|
|||||||
else
|
else
|
||||||
l_output.set_http_version (version)
|
l_output.set_http_version (version)
|
||||||
end
|
end
|
||||||
res.set_is_persistent_connection_supported ({HTTPD_SERVER}.is_persistent_connection_supported)
|
res.set_is_persistent_connection_supported (is_persistent_connection_supported and is_next_persistent_connection_supported)
|
||||||
res.set_is_persistent_connection_requested (is_persistent_connection_requested)
|
res.set_is_persistent_connection_requested (is_persistent_connection_requested)
|
||||||
|
|
||||||
req.set_meta_string_variable ("RAW_HEADER_DATA", request_header)
|
req.set_meta_string_variable ("RAW_HEADER_DATA", request_header)
|
||||||
|
|||||||
@@ -14,19 +14,23 @@ feature -- Access
|
|||||||
connector: detachable separate WGI_STANDALONE_CONNECTOR [G]
|
connector: detachable separate WGI_STANDALONE_CONNECTOR [G]
|
||||||
-- httpd solution.
|
-- httpd solution.
|
||||||
|
|
||||||
|
request_settings: HTTPD_REQUEST_SETTINGS
|
||||||
|
-- Settings specific to request handling.
|
||||||
|
|
||||||
feature -- Element change
|
feature -- Element change
|
||||||
|
|
||||||
update_with (conn: like connector)
|
update_with (conn: like connector; a_conf: separate HTTPD_CONFIGURATION)
|
||||||
-- Set `connector' with `conn'.
|
-- Set `connector' with `conn'.
|
||||||
do
|
do
|
||||||
connector := conn
|
connector := conn
|
||||||
|
request_settings := a_conf.request_settings
|
||||||
end
|
end
|
||||||
|
|
||||||
feature -- Factory
|
feature -- Factory
|
||||||
|
|
||||||
new_handler: separate WGI_HTTPD_REQUEST_HANDLER [G]
|
new_handler: separate WGI_HTTPD_REQUEST_HANDLER [G]
|
||||||
do
|
do
|
||||||
create Result.make_with_connector (connector)
|
create Result.make_with_connector (request_settings, connector)
|
||||||
end
|
end
|
||||||
|
|
||||||
note
|
note
|
||||||
|
|||||||
@@ -27,11 +27,11 @@ feature {NONE} -- Initialization
|
|||||||
|
|
||||||
-- Server
|
-- Server
|
||||||
create <NONE> fac
|
create <NONE> fac
|
||||||
|
request_handler_factory := fac
|
||||||
create server.make (fac)
|
create server.make (fac)
|
||||||
create <NONE> observer
|
create <NONE> observer
|
||||||
configuration := server_configuration (server)
|
configuration := server_configuration (server)
|
||||||
controller := server_controller (server)
|
controller := server_controller (server)
|
||||||
set_factory_connector (Current, fac)
|
|
||||||
initialize_server (server)
|
initialize_server (server)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -51,9 +51,9 @@ feature {NONE} -- Separate helper
|
|||||||
a_server.set_observer (observer)
|
a_server.set_observer (observer)
|
||||||
end
|
end
|
||||||
|
|
||||||
set_factory_connector (conn: detachable separate WGI_STANDALONE_CONNECTOR [G]; fac: separate WGI_HTTPD_REQUEST_HANDLER_FACTORY [G])
|
update_factory (conn: detachable separate WGI_STANDALONE_CONNECTOR [G]; fac: separate WGI_HTTPD_REQUEST_HANDLER_FACTORY [G]; a_conf: separate HTTPD_CONFIGURATION)
|
||||||
do
|
do
|
||||||
fac.update_with (conn)
|
fac.update_with (conn, a_conf)
|
||||||
end
|
end
|
||||||
|
|
||||||
server_configuration (a_server: like server): like configuration
|
server_configuration (a_server: like server): like configuration
|
||||||
@@ -74,6 +74,9 @@ feature -- Access
|
|||||||
server: separate HTTPD_SERVER
|
server: separate HTTPD_SERVER
|
||||||
-- HTTPd server object.
|
-- HTTPd server object.
|
||||||
|
|
||||||
|
request_handler_factory: separate WGI_HTTPD_REQUEST_HANDLER_FACTORY [G]
|
||||||
|
-- Factory for request handlers.
|
||||||
|
|
||||||
controller: separate HTTPD_CONTROLLER
|
controller: separate HTTPD_CONTROLLER
|
||||||
-- Controller used to shutdown server.
|
-- Controller used to shutdown server.
|
||||||
|
|
||||||
@@ -97,9 +100,6 @@ feature -- Status report
|
|||||||
-- Listening port.
|
-- Listening port.
|
||||||
--| 0: not launched
|
--| 0: not launched
|
||||||
|
|
||||||
is_verbose: BOOLEAN
|
|
||||||
-- Is verbose?
|
|
||||||
|
|
||||||
feature -- Callbacks
|
feature -- Callbacks
|
||||||
|
|
||||||
on_launched_actions: ACTION_SEQUENCE [TUPLE [WGI_STANDALONE_CONNECTOR [WGI_EXECUTION]]]
|
on_launched_actions: ACTION_SEQUENCE [TUPLE [WGI_STANDALONE_CONNECTOR [WGI_EXECUTION]]]
|
||||||
@@ -190,18 +190,22 @@ feature {NONE} -- Implementation
|
|||||||
Result := a_server.controller
|
Result := a_server.controller
|
||||||
end
|
end
|
||||||
|
|
||||||
configure_server (a_configuration: like configuration)
|
apply_configuration (a_configuration: like configuration)
|
||||||
|
local
|
||||||
|
v: BOOLEAN
|
||||||
do
|
do
|
||||||
if a_configuration.is_verbose then
|
v := a_configuration.is_verbose
|
||||||
if attached base as l_base then
|
if v then
|
||||||
|
if attached base as l_base and then not l_base.is_whitespace then
|
||||||
io.error.put_string ("Base=" + l_base + "%N")
|
io.error.put_string ("Base=" + l_base + "%N")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
update_factory (Current, request_handler_factory, a_configuration)
|
||||||
end
|
end
|
||||||
|
|
||||||
launch_server (a_server: like server)
|
launch_server (a_server: like server)
|
||||||
do
|
do
|
||||||
configure_server (a_server.configuration)
|
apply_configuration (a_server.configuration)
|
||||||
a_server.launch
|
a_server.launch
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -229,7 +233,6 @@ feature {NONE} -- Implementation: element change
|
|||||||
|
|
||||||
set_is_verbose_on_configuration (b: BOOLEAN; cfg: like configuration)
|
set_is_verbose_on_configuration (b: BOOLEAN; cfg: like configuration)
|
||||||
do
|
do
|
||||||
is_verbose := b
|
|
||||||
cfg.set_is_verbose (b)
|
cfg.set_is_verbose (b)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
note
|
||||||
|
description: "Export HTTPD_LOGGER_CONSTANTS to Standlone connector interfaces."
|
||||||
|
|
||||||
|
class
|
||||||
|
WGI_STANDALONE_HTTPD_LOGGER_CONSTANTS
|
||||||
|
|
||||||
|
inherit
|
||||||
|
HTTPD_LOGGER_CONSTANTS
|
||||||
|
|
||||||
|
end
|
||||||
@@ -108,7 +108,6 @@ feature -- Header output operation
|
|||||||
l_connection := s.substring (i + 12, j - 1)
|
l_connection := s.substring (i + 12, j - 1)
|
||||||
l_connection.adjust
|
l_connection.adjust
|
||||||
if
|
if
|
||||||
not is_http_version_1_0 and
|
|
||||||
not l_connection.is_case_insensitive_equal_general ("close")
|
not l_connection.is_case_insensitive_equal_general ("close")
|
||||||
then
|
then
|
||||||
s.replace_substring ("Connection: close", i + 1, j - 1)
|
s.replace_substring ("Connection: close", i + 1, j - 1)
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ inherit
|
|||||||
launchable
|
launchable
|
||||||
end
|
end
|
||||||
|
|
||||||
|
WGI_STANDALONE_HTTPD_LOGGER_CONSTANTS
|
||||||
|
|
||||||
create
|
create
|
||||||
make,
|
make,
|
||||||
make_and_launch
|
make_and_launch
|
||||||
@@ -34,12 +36,20 @@ feature {NONE} -- Initialization
|
|||||||
initialize
|
initialize
|
||||||
local
|
local
|
||||||
conn: like connector
|
conn: like connector
|
||||||
|
s: READABLE_STRING_GENERAL
|
||||||
do
|
do
|
||||||
create on_launched_actions
|
create on_launched_actions
|
||||||
create on_stopped_actions
|
create on_stopped_actions
|
||||||
|
|
||||||
port_number := 80 --| Default, but quite often, this port is already used ...
|
port_number := 80 --| Default, but quite often, this port is already used ...
|
||||||
keep_alive_timeout := 5_000 -- 5 seconds.
|
max_concurrent_connections := 100
|
||||||
|
max_tcp_clients := 100
|
||||||
|
socket_timeout := 300 -- 300 seconds
|
||||||
|
keep_alive_timeout := 15 -- 15 seconds.
|
||||||
|
max_keep_alive_requests := 100
|
||||||
|
verbose := False
|
||||||
|
verbose_level := notice_level
|
||||||
|
|
||||||
base_url := ""
|
base_url := ""
|
||||||
|
|
||||||
if attached options as opts then
|
if attached options as opts then
|
||||||
@@ -50,13 +60,44 @@ feature {NONE} -- Initialization
|
|||||||
base_url := l_base_str.as_string_8
|
base_url := l_base_str.as_string_8
|
||||||
end
|
end
|
||||||
verbose := opts.option_boolean_value ("verbose", verbose)
|
verbose := opts.option_boolean_value ("verbose", verbose)
|
||||||
|
-- See `{HTTPD_REQUEST_HANDLER_I}.*_verbose_level`
|
||||||
|
|
||||||
|
if opts.has_integer_option ("verbose_level") then
|
||||||
|
verbose_level := opts.option_integer_value ("verbose_level", verbose_level)
|
||||||
|
elseif attached {READABLE_STRING_GENERAL} opts.option ("verbose_level") as s_verbose_level then
|
||||||
|
verbose_level := 0 -- Reset
|
||||||
|
across
|
||||||
|
s_verbose_level.split ('+') as ic
|
||||||
|
loop
|
||||||
|
s := ic.item
|
||||||
|
if s.is_case_insensitive_equal ("alert") then
|
||||||
|
verbose_level := verbose_level | alert_level
|
||||||
|
elseif s.is_case_insensitive_equal ("critical") then
|
||||||
|
verbose_level := verbose_level | critical_level
|
||||||
|
elseif s.is_case_insensitive_equal ("error") then
|
||||||
|
verbose_level := verbose_level | error_level
|
||||||
|
elseif s.is_case_insensitive_equal ("warning") then
|
||||||
|
verbose_level := verbose_level | warning_level
|
||||||
|
elseif s.is_case_insensitive_equal ("notice") then
|
||||||
|
verbose_level := verbose_level | notice_level
|
||||||
|
elseif s.is_case_insensitive_equal ("information") then
|
||||||
|
verbose_level := verbose_level | information_level
|
||||||
|
elseif s.is_case_insensitive_equal ("debug") then
|
||||||
|
verbose_level := verbose_level | debug_level
|
||||||
|
else
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
port_number := opts.option_integer_value ("port", port_number)
|
port_number := opts.option_integer_value ("port", port_number)
|
||||||
|
|
||||||
if opts.option_boolean_value ("force_single_threaded", single_threaded) then
|
if opts.option_boolean_value ("force_single_threaded", False) then
|
||||||
force_single_threaded
|
force_single_threaded
|
||||||
end
|
end
|
||||||
max_concurrent_connections := opts.option_integer_value ("max_concurrent_connections", max_concurrent_connections)
|
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)
|
||||||
keep_alive_timeout := opts.option_integer_value ("keep_alive_timeout", keep_alive_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)
|
||||||
end
|
end
|
||||||
|
|
||||||
create conn.make
|
create conn.make
|
||||||
@@ -78,13 +119,14 @@ feature -- Execution
|
|||||||
update_configuration (cfg: like connector.configuration)
|
update_configuration (cfg: like connector.configuration)
|
||||||
do
|
do
|
||||||
cfg.set_is_verbose (verbose)
|
cfg.set_is_verbose (verbose)
|
||||||
if attached server_name as l_server_name then
|
cfg.set_verbose_level (verbose_level)
|
||||||
cfg.set_http_server_name (l_server_name)
|
cfg.set_http_server_name (server_name)
|
||||||
end
|
|
||||||
cfg.http_server_port := port_number
|
cfg.http_server_port := port_number
|
||||||
cfg.set_max_concurrent_connections (max_concurrent_connections)
|
cfg.set_max_concurrent_connections (max_concurrent_connections)
|
||||||
|
cfg.set_max_tcp_clients (max_tcp_clients)
|
||||||
|
cfg.set_socket_timeout (socket_timeout)
|
||||||
cfg.set_keep_alive_timeout (keep_alive_timeout)
|
cfg.set_keep_alive_timeout (keep_alive_timeout)
|
||||||
-- conn.update_configuration (cfg)
|
cfg.set_max_keep_alive_requests (max_keep_alive_requests)
|
||||||
end
|
end
|
||||||
|
|
||||||
launch
|
launch
|
||||||
@@ -131,16 +173,21 @@ feature {NONE} -- Implementation
|
|||||||
base_url: READABLE_STRING_8
|
base_url: READABLE_STRING_8
|
||||||
|
|
||||||
verbose: BOOLEAN
|
verbose: BOOLEAN
|
||||||
|
verbose_level: INTEGER
|
||||||
|
-- Help defining the verbosity.
|
||||||
|
-- The higher, the more output.
|
||||||
|
|
||||||
|
max_concurrent_connections: INTEGER
|
||||||
|
max_tcp_clients: INTEGER
|
||||||
|
socket_timeout: INTEGER
|
||||||
|
keep_alive_timeout: INTEGER
|
||||||
|
max_keep_alive_requests: INTEGER
|
||||||
|
|
||||||
single_threaded: BOOLEAN
|
single_threaded: BOOLEAN
|
||||||
do
|
do
|
||||||
Result := max_concurrent_connections = 0
|
Result := max_concurrent_connections = 0
|
||||||
end
|
end
|
||||||
|
|
||||||
max_concurrent_connections: INTEGER
|
|
||||||
|
|
||||||
keep_alive_timeout: INTEGER
|
|
||||||
|
|
||||||
feature -- Status report
|
feature -- Status report
|
||||||
|
|
||||||
connector: WGI_STANDALONE_CONNECTOR [G]
|
connector: WGI_STANDALONE_CONNECTOR [G]
|
||||||
|
|||||||
@@ -85,6 +85,21 @@ feature -- Access
|
|||||||
|
|
||||||
feature -- Helpers
|
feature -- Helpers
|
||||||
|
|
||||||
|
has_integer_option (a_opt_name: READABLE_STRING_GENERAL): BOOLEAN
|
||||||
|
-- Is there any INTEGER value associated to option name `a_opt_name'?
|
||||||
|
local
|
||||||
|
s: READABLE_STRING_GENERAL
|
||||||
|
do
|
||||||
|
if attached option (a_opt_name) as opt then
|
||||||
|
if attached {INTEGER} opt as i then
|
||||||
|
Result := True
|
||||||
|
else
|
||||||
|
s := opt.out
|
||||||
|
Result := s.is_integer
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
option_integer_value (a_opt_name: READABLE_STRING_GENERAL; a_default: INTEGER): INTEGER
|
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'.
|
-- INTEGER value associated to option name `a_opt_name', other return `a_default'.
|
||||||
local
|
local
|
||||||
|
|||||||
BIN
library/text/parser/feed/tests/msys-iconv-2.dll
Normal file
BIN
library/text/parser/feed/tests/msys-iconv-2.dll
Normal file
Binary file not shown.
Reference in New Issue
Block a user