class FCGI_SERVLET_SERVICE
Client examples: FAST_CGI_TEST_SERVICE
Fast-CGI service that services HTTP requests forwarded by a web server from a table of servlets. The service is configured from a Pyxis format configuration file and listens either on a port number or a Unix socket for request from the web server.
Origins
This FastCGI implementation evolved from the one found in the Goanna library as a radical refactoring and redesign. There is almost nothing left of the old Goanna implementation except perhaps for the use of the L4E logging system. It uses EiffelNet sockets in preference to Eposix.
Servlet Mapping
The servlets are mapped to uri paths that are relative to the service path defined in the web server configuration. For example:
/servlet/one /servlet/two /servlet/three
The service path is "/servlet" and the relative paths are "one", "two" and "three"
The servlet service is configured by a file in Pyxis format.
Logging
Logging uses both the Eiffel-Loop system and the L4E system which it inherits from the Goanna predecessor to this library.
Testing
This and related classes have been tested in production with the Cherokee Webserver
note
description: "[
Fast-CGI service that services HTTP requests forwarded by a web server from a table of servlets.
The service is configured from a Pyxis format configuration file and listens either on a port number
or a Unix socket for request from the web server.
]"
notes: "See end of page"
author: "Finnian Reilly"
copyright: "Copyright (c) 2001-2022 Finnian Reilly"
contact: "finnian at eiffel hyphen loop dot com"
license: "MIT license (See: en.wikipedia.org/wiki/MIT_License)"
date: "2024-08-25 17:11:09 GMT (Sunday 25th August 2024)"
revision: "34"
deferred class
FCGI_SERVLET_SERVICE
inherit
FCGI_APPLICATION_COMMAND
feature {EL_COMMAND_CLIENT} -- Initialization
make (config_path: FILE_PATH)
require
configuration_exists: config_path.exists
do
make_with_config (new_config (config_path))
end
make_port (a_port: INTEGER)
do
make_with_config (create {like config}.make (create {FILE_PATH}, a_port))
end
feature {NONE} -- Initialization
initialize_listening
-- Set up port to listen for requests from the web server
local
retry_count: INTEGER
do
unable_to_listen := True
retry_count := retry_count + 1
socket := config.new_socket
socket.listen (server_backlog)
socket.set_blocking
lio.put_labeled_string ("Listening on", socket.description)
lio.put_new_line
-- Potentially this can be used to poll for application ending but has some problems
-- srv_socket.set_accept_timeout (500)
unable_to_listen := False
rescue
if not socket.is_closed then
socket.close
end
if retry_count <= Max_initialization_retry_count then
Execution_environment.sleep (1000) -- Wait a second
retry
end
end
initialize_servlets
-- initialize servlets
deferred
end
make_default
do
create broker.make
create {EL_NETWORK_STREAM_SOCKET} socket.make
create servlet_table
create date_time.make_now
state := agent do_nothing; final := state.twin
server_backlog := 10
end
make_with_config (a_config: like config)
do
make_default
config := a_config
if not config.unix_socket_exists then
create_server_socket_dir
end
end
feature -- Access
config: FCGI_SERVICE_CONFIG
-- Configuration for servlets
description: READABLE_STRING_GENERAL
do
Result := default_description
end
feature -- Basic operations
execute
do
log.enter ("execute")
config.check_validity
if config.is_valid then
-- Call initialize here rather than in `make' so that a background thread will have it's
-- own template copies stored in once per thread instance of EVOLICITY_TEMPLATES
log.enter ("initialize_servlets")
initialize_servlets
log.exit
initialize_listening
if unable_to_listen then
log_error (Empty_string_8, "Application unable to listen")
else
do_transitions
broker.close
across servlet_table as servlet loop
servlet.item.on_shutdown
end
on_shutdown
end
socket.close
else
across config.error_messages as message loop
log_error ("Configuration error " + message.cursor_index.out, message.item)
end
end
log.exit
end
feature -- Status change
stop_service: BOOLEAN
-- stop running the service
do
state := Final
end
feature -- Status query
unable_to_listen: BOOLEAN
-- True if the application is unable to listen on host_port
feature {NONE} -- States
accepting_connection
do
socket.accept
if attached {EL_STREAM_SOCKET} socket.accepted as connection_socket then
-- accept new connection (blocking)
state := agent reading_request (connection_socket)
end
end
finishing_request
-- Finish the current request from the HTTP server. The
-- current request was started by the most recent call to
-- 'accept'.
do
broker.end_request
state := agent accepting_connection
end
processing_request (table: like servlet_table)
-- Redefined process request to have type of response and request object defined in servlet
local
path: ZSTRING
do
if attached broker.relative_path_info as relative_path then
table.search (relative_path)
if not table.found then
table.search (Default_servlet_key)
end
if table.found then
path := Service_info_template #$ [relative_path, table.found_item.servlet_info]
date_time.update
if date_time.date.ordered_compact_date /= compact_date then
lio.put_line (Date.formatted (date_time.date, Date_format))
compact_date := date_time.date.ordered_compact_date
end
log_message (date_time.time.formatted_out (Time_format), path)
if log_parameters_enabled and then attached broker.parameters.query_string as query_string
and then query_string.count > 0
then
log_parameters (query_string)
end
table.found_item.serve_request
log_separator
else
on_missing_servlet (create {FCGI_SERVLET_RESPONSE}.make (broker))
end
end
state := agent finishing_request
end
reading_request (a_socket: EL_STREAM_SOCKET)
-- Wait for a request to be received; Returns true if request was successfully read
do
broker.set_socket (a_socket)
broker.read
if broker.is_aborted then
state := agent accepting_connection
elseif broker.read_ok then
if broker.is_end_service then
state := Final
else
state := agent processing_request (servlet_table)
end
else
a_socket.close
log_error ("routine reading_request", "failed")
state := agent accepting_connection
end
end
feature {NONE} -- Implementation
create_server_socket_dir
local
make_dir_cmd: EL_OS_COMMAND; var: TUPLE [socket_dir, user: STRING]
socket_dir: DIR_PATH
do
socket_dir := config.server_socket_path.parent
create var
create make_dir_cmd.make ("sudo mkdir $SOCKET_DIR; sudo chown $USER:www-data $SOCKET_DIR")
make_dir_cmd.fill_variables (var)
make_dir_cmd.put_path (Var.socket_dir, socket_dir)
make_dir_cmd.put_string (Var.user, Operating_environ.user_name)
log.put_path_field ("Creating Unix socket", socket_dir)
log.put_new_line
make_dir_cmd.execute
log.put_new_line
end
do_transitions
-- iterate over state transitions
local
except: EXCEPTION; signal: INTEGER
do
if state /= Final then
from state := agent accepting_connection until state = Final loop
state.apply
end
end
rescue
except := Exception.last_exception.cause -- `cause' gets cause of ROUTINE_FAILURE
if attached {OPERATING_SYSTEM_SIGNAL_FAILURE} except as os then
signal := os.signal_code
elseif attached {IO_FAILURE} except then
-- arrives here in workbench mode
if broker.is_pipe_broken then
signal := Unix_signals.broken_pipe
end
end
if Unix_signals.is_termination (signal) then
log_message (except.generator, except.description)
log_message ("Ctrl-C detected", "shutting down ..")
state := Final
retry
elseif signal = Unix_signals.broken_pipe then
broker.close
log_message (except.generator, Unix_signals.broken_pipe_message)
retry
else
log_message ("Exiting after unrescueable exception", except.generator)
Exception.write_last_trace (Current)
end
end
log_parameters_enabled: BOOLEAN
do
Result := False
end
new_config (file_path: FILE_PATH): like config
do
create Result.make_from_file (file_path)
end
feature {FCGI_HTTP_SERVLET, FCGI_SERVLET_REQUEST} -- Access
broker: FCGI_REQUEST_BROKER
-- broker to read and write request messages from the web server
feature {NONE} -- Internal attributes
compact_date: INTEGER
date_time: EL_DATE_TIME
final: PROCEDURE
server_backlog: INTEGER
-- The number of requests that can remain outstanding.
servlet_table: EL_ZSTRING_HASH_TABLE [FCGI_HTTP_SERVLET]
socket: EL_STREAM_SOCKET
-- server socket
state: PROCEDURE;
note
notes: "[
**Origins**
This FastCGI implementation evolved from the one found in the Goanna library as a radical
refactoring and redesign. There is almost nothing left of the old Goanna implementation except
perhaps for the use of the L4E logging system. It uses EiffelNet sockets in preference to Eposix.
**Servlet Mapping**
The servlets are mapped to uri paths that are relative to the service path defined in the
web server configuration. For example:
/servlet/one
/servlet/two
/servlet/three
The service path is "/servlet" and the relative paths are "one", "two" and "three"
The servlet service is configured by a file in Pyxis format.
**Logging**
Logging uses both the Eiffel-Loop system and the L4E system which it inherits from the Goanna
predecessor to this library.
**Testing**
This and related classes have been tested in production with the
[http://cherokee-project.com/ Cherokee Webserver]
]"
end