Added handler to add support for CGI scripts.
Added a new tool `httpd` which is a basic httpd server product (with file server and CGI handler).
This commit is contained in:
236
library/server/wsf/extension/handler/cgi/wsf_cgi_handler.e
Normal file
236
library/server/wsf/extension/handler/cgi/wsf_cgi_handler.e
Normal file
@@ -0,0 +1,236 @@
|
||||
note
|
||||
description: "Handler to process CGI script."
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
class
|
||||
WSF_CGI_HANDLER
|
||||
|
||||
inherit
|
||||
WSF_EXECUTE_HANDLER
|
||||
|
||||
create
|
||||
make
|
||||
|
||||
feature {NONE} -- Creation
|
||||
|
||||
make (a_dir: PATH)
|
||||
do
|
||||
working_direction := a_dir
|
||||
end
|
||||
|
||||
feature -- Settings
|
||||
|
||||
buffer_size: INTEGER = 1_024
|
||||
|
||||
working_direction: PATH
|
||||
|
||||
feature -- Status report
|
||||
|
||||
cgi_file_path (req: WSF_REQUEST): PATH
|
||||
local
|
||||
l_path_info: READABLE_STRING_32
|
||||
do
|
||||
-- Path to CGI executable.
|
||||
l_path_info := req.path_info
|
||||
if l_path_info.starts_with_general ("/") then
|
||||
l_path_info := l_path_info.substring (2, l_path_info.count)
|
||||
end
|
||||
|
||||
-- Process
|
||||
Result := working_direction.extended (l_path_info)
|
||||
end
|
||||
|
||||
exists (req: WSF_REQUEST): BOOLEAN
|
||||
-- CGI file exists?
|
||||
do
|
||||
Result := (create {FILE_UTILITIES}).file_path_exists (cgi_file_path (req))
|
||||
end
|
||||
|
||||
feature -- Execution
|
||||
|
||||
execute (req: WSF_REQUEST; res: WSF_RESPONSE)
|
||||
local
|
||||
fut: FILE_UTILITIES
|
||||
l_exec_path: PATH
|
||||
proc: BASE_PROCESS
|
||||
l_input_env: STRING_TABLE [READABLE_STRING_GENERAL]
|
||||
l_input_header: detachable STRING
|
||||
l_input_buf: STRING
|
||||
l_output: STRING
|
||||
l_output_header_sent: BOOLEAN
|
||||
l_error: STRING
|
||||
s: STRING
|
||||
i, j, n: INTEGER
|
||||
do
|
||||
-- Header
|
||||
if attached req.raw_header_data as l_header then
|
||||
l_input_header := l_header
|
||||
end
|
||||
-- Input data
|
||||
create l_input_buf.make (req.content_length_value.to_integer_32)
|
||||
req.read_input_data_into (l_input_buf)
|
||||
|
||||
-- Input environment
|
||||
create l_input_env.make (10)
|
||||
across
|
||||
req.meta_variables as ic
|
||||
loop
|
||||
if attached {WSF_STRING} ic.item as var then
|
||||
l_input_env.force (var.value, var.name)
|
||||
else
|
||||
check supported: False end
|
||||
end
|
||||
end
|
||||
-- No need to import `l_input_header` in environment
|
||||
-- As current connector already did the job.
|
||||
|
||||
-- Process
|
||||
l_exec_path := cgi_file_path (req)
|
||||
if fut.file_path_exists (l_exec_path) then
|
||||
proc := (create {BASE_PROCESS_FACTORY}).process_launcher (l_exec_path.name, Void, working_direction.name)
|
||||
proc.set_hidden (True)
|
||||
proc.set_environment_variable_table (l_input_env)
|
||||
proc.set_separate_console (False)
|
||||
proc.redirect_input_to_stream
|
||||
proc.redirect_output_to_stream
|
||||
proc.redirect_error_to_stream
|
||||
|
||||
-- Launch CGI execution
|
||||
proc.launch
|
||||
if proc.launched then
|
||||
-- Do not send the header to CGI script
|
||||
-- value are passed via environment variables
|
||||
-- proc.put_string (l_input_header)
|
||||
-- Send payload.
|
||||
proc.put_string (l_input_buf)
|
||||
|
||||
create l_output.make_empty
|
||||
create l_error.make_empty
|
||||
get_output_and_error_from_process (proc, l_output, l_error)
|
||||
if l_error /= Void and then not l_error.is_whitespace then
|
||||
res.put_error (l_error)
|
||||
end
|
||||
|
||||
-- Wait for process exit
|
||||
if not proc.has_exited then
|
||||
proc.wait_for_exit
|
||||
end
|
||||
if proc.exit_code /= 0 then
|
||||
res.set_status_code ({HTTP_STATUS_CODE}.internal_server_error)
|
||||
s := "CGI script execution failed [exit code=" + proc.exit_code.out+ "]!"
|
||||
res.header.put_content_type_utf_8_text_plain
|
||||
res.header.put_content_length (s.count)
|
||||
res.put_string (s)
|
||||
else
|
||||
-- Send the response
|
||||
-- error already sent via `res.put_error (l_error)`
|
||||
from
|
||||
i := 1
|
||||
n := l_output.count
|
||||
until
|
||||
i > n or l_output_header_sent
|
||||
loop
|
||||
j := l_output.index_of ('%N', i)
|
||||
if j > 0 then
|
||||
s := l_output.substring (i, j)
|
||||
s.right_adjust
|
||||
if s.is_empty then
|
||||
-- Reached end of header
|
||||
l_output_header_sent := True
|
||||
else
|
||||
res.add_header_line (s)
|
||||
end
|
||||
else
|
||||
-- ERROR
|
||||
l_output_header_sent := True
|
||||
end
|
||||
i := j + 1
|
||||
end
|
||||
if l_output_header_sent then
|
||||
if i <= n then
|
||||
res.put_string (l_output.substring (i, n))
|
||||
end
|
||||
else
|
||||
res.set_status_code ({HTTP_STATUS_CODE}.internal_server_error)
|
||||
s := "Internal server error!"
|
||||
res.header.put_content_type_utf_8_text_plain
|
||||
res.header.put_content_length (s.count)
|
||||
res.put_string (s)
|
||||
end
|
||||
end
|
||||
else
|
||||
res.set_status_code ({HTTP_STATUS_CODE}.internal_server_error)
|
||||
s := "Could not launch CGI script!"
|
||||
res.header.put_content_type_utf_8_text_plain
|
||||
res.header.put_content_length (s.count)
|
||||
res.put_string (s)
|
||||
end
|
||||
else
|
||||
res.set_status_code ({HTTP_STATUS_CODE}.not_found)
|
||||
s := "Not found!"
|
||||
res.header.put_content_type_utf_8_text_plain
|
||||
res.header.put_content_length (s.count)
|
||||
res.put_string (s)
|
||||
end
|
||||
end
|
||||
|
||||
get_output_and_error_from_process (proc: BASE_PROCESS; a_output: STRING; a_error: STRING)
|
||||
local
|
||||
output_buf, error_buf: SPECIAL [NATURAL_8]
|
||||
do
|
||||
from
|
||||
create output_buf.make_filled (0, buffer_size)
|
||||
create error_buf.make_filled (0, buffer_size)
|
||||
until
|
||||
not attached output_buf and not attached error_buf
|
||||
loop
|
||||
if attached output_buf then
|
||||
if proc.has_output_stream_error or proc.has_output_stream_closed then
|
||||
output_buf := Void
|
||||
end
|
||||
if attached output_buf then
|
||||
-- Try reading from standard output.
|
||||
proc.read_output_to_special (output_buf)
|
||||
across
|
||||
output_buf as ic
|
||||
loop
|
||||
a_output.append_code (ic.item)
|
||||
end
|
||||
end
|
||||
end
|
||||
if attached output_buf implies output_buf.count = 0 and then attached error_buf then
|
||||
-- Nothing is read from standard output, switch to standard error.
|
||||
if proc.has_error_stream_error or proc.has_error_stream_closed then
|
||||
error_buf := Void
|
||||
end
|
||||
if attached error_buf then
|
||||
-- Try reading from standard error.
|
||||
proc.read_error_to_special (error_buf)
|
||||
across
|
||||
error_buf as ic
|
||||
loop
|
||||
a_error.append_code (ic.item)
|
||||
end
|
||||
end
|
||||
end
|
||||
if attached output_buf then
|
||||
output_buf.extend_filled (0)
|
||||
end
|
||||
if attached error_buf then
|
||||
error_buf.extend_filled (0)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
note
|
||||
copyright: "2011-2017, Jocelyn Fiat, Javier Velilla, Olivier Ligot, Colin Adams, Eiffel Software and others"
|
||||
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
|
||||
source: "[
|
||||
Eiffel Software
|
||||
5949 Hollister Ave., Goleta, CA 93117 USA
|
||||
Telephone 805-685-1006, Fax 805-685-6869
|
||||
Website http://www.eiffel.com
|
||||
Customer support http://support.eiffel.com
|
||||
]"
|
||||
end
|
||||
@@ -3,9 +3,9 @@
|
||||
<target name="wsf_extension">
|
||||
<root all_classes="true"/>
|
||||
<file_rule>
|
||||
<exclude>/EIFGENs$</exclude>
|
||||
<exclude>/\.git$</exclude>
|
||||
<exclude>/\.svn$</exclude>
|
||||
<exclude>/EIFGENs$</exclude>
|
||||
</file_rule>
|
||||
<option warning="true">
|
||||
</option>
|
||||
@@ -13,6 +13,7 @@
|
||||
<library name="encoder" location="..\..\text\encoder\encoder.ecf"/>
|
||||
<library name="ewsgi" location="..\ewsgi\ewsgi.ecf"/>
|
||||
<library name="http" location="..\..\network\protocol\http\http.ecf"/>
|
||||
<library name="process" location="$ISE_LIBRARY\library\process\base\base_process.ecf"/>
|
||||
<library name="wsf" location="wsf.ecf"/>
|
||||
<library name="wsf_router_context" location="wsf_router_context.ecf" readonly="true"/>
|
||||
<cluster name="extension" location=".\extension\" recursive="true"/>
|
||||
|
||||
12
tools/httpd/README.md
Normal file
12
tools/httpd/README.md
Normal file
@@ -0,0 +1,12 @@
|
||||
Simple web server
|
||||
=================
|
||||
|
||||
This server is very simple and limited, it is, for now:
|
||||
- a file server
|
||||
- a CGI handler (for `*.cgi` executables)
|
||||
|
||||
Usage:
|
||||
httpd (--root path)
|
||||
|
||||
--root <path>: document directory for file server (default: www)
|
||||
|
||||
56
tools/httpd/httpd.ecf
Normal file
56
tools/httpd/httpd.ecf
Normal file
@@ -0,0 +1,56 @@
|
||||
<?xml version="1.0" encoding="ISO-8859-1"?>
|
||||
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-17-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-17-0 http://www.eiffel.com/developers/xml/configuration-1-17-0.xsd" name="httpd" uuid="60DBA808-B014-47BB-BDEF-11B87C1DC6DE">
|
||||
<target name="common" abstract="true">
|
||||
<file_rule>
|
||||
<exclude>/CVS$</exclude>
|
||||
<exclude>/EIFGENs$</exclude>
|
||||
<exclude>/\.svn$</exclude>
|
||||
</file_rule>
|
||||
<option warning="true">
|
||||
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
|
||||
</option>
|
||||
<setting name="console_application" value="true"/>
|
||||
<library name="base" location="$ISE_LIBRARY\library\base\base.ecf"/>
|
||||
<library name="http" location="..\..\library\network\protocol\http\http.ecf"/>
|
||||
<library name="process" location="$ISE_LIBRARY\library\process\base\base_process.ecf"/>
|
||||
<library name="wsf" location="..\..\library\server\wsf\wsf.ecf" readonly="false"/>
|
||||
<library name="wsf_extension" location="..\..\library\server\wsf\wsf_extension.ecf" readonly="false"/>
|
||||
</target>
|
||||
<target name="httpd_standalone" extends="common">
|
||||
<root class="APPLICATION" feature="make_and_launch"/>
|
||||
<option warning="true">
|
||||
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
|
||||
</option>
|
||||
<library name="default_standalone" location="..\..\library\server\wsf\default\standalone.ecf"/>
|
||||
<cluster name="httpd" location=".\src" recursive="true"/>
|
||||
</target>
|
||||
<target name="httpd_standalone_mt" extends="httpd_standalone">
|
||||
<capability>
|
||||
<concurrency support="thread" use="thread"/>
|
||||
</capability>
|
||||
</target>
|
||||
<target name="httpd_standalone_st" extends="httpd_standalone">
|
||||
<capability>
|
||||
<concurrency support="none" use="none"/>
|
||||
</capability>
|
||||
</target>
|
||||
<target name="httpd_cgi" extends="common">
|
||||
<root class="APPLICATION" feature="make_and_launch"/>
|
||||
<option warning="true">
|
||||
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
|
||||
</option>
|
||||
<capability>
|
||||
<concurrency use="scoop"/>
|
||||
</capability>
|
||||
<library name="default_cgi" location="..\..\library\server\wsf\default\cgi.ecf"/>
|
||||
<cluster name="httpd" location=".\src" recursive="true"/>
|
||||
</target>
|
||||
<target name="httpd_libfcgi" extends="common">
|
||||
<root class="APPLICATION" feature="make_and_launch"/>
|
||||
<option warning="true">
|
||||
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
|
||||
</option>
|
||||
<library name="default_libfcgi" location="..\..\library\server\wsf\default\libfcgi.ecf"/>
|
||||
<cluster name="httpd" location=".\src" recursive="true"/>
|
||||
</target>
|
||||
</system>
|
||||
9
tools/httpd/server.ini
Normal file
9
tools/httpd/server.ini
Normal file
@@ -0,0 +1,9 @@
|
||||
verbose=true
|
||||
verbose_level=ALERT
|
||||
port=9090
|
||||
#max_concurrent_connections=100
|
||||
#keep_alive_timeout=5
|
||||
#max_keep_alive_requests=300
|
||||
#max_tcp_clients=100
|
||||
#socket_timeout=60
|
||||
#socket_recv_timeout=15
|
||||
31
tools/httpd/src/application.e
Normal file
31
tools/httpd/src/application.e
Normal file
@@ -0,0 +1,31 @@
|
||||
note
|
||||
description : "simple application root class"
|
||||
date : "$Date$"
|
||||
revision : "$Revision$"
|
||||
|
||||
class
|
||||
APPLICATION
|
||||
|
||||
inherit
|
||||
WSF_DEFAULT_SERVICE [APPLICATION_EXECUTION]
|
||||
redefine
|
||||
initialize
|
||||
end
|
||||
|
||||
SHARED_EXECUTION_ENVIRONMENT
|
||||
|
||||
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
|
||||
95
tools/httpd/src/application_execution.e
Normal file
95
tools/httpd/src/application_execution.e
Normal file
@@ -0,0 +1,95 @@
|
||||
note
|
||||
description : "simple application execution"
|
||||
date : "$Date$"
|
||||
revision : "$Revision$"
|
||||
|
||||
class
|
||||
APPLICATION_EXECUTION
|
||||
|
||||
inherit
|
||||
WSF_ROUTED_EXECUTION
|
||||
|
||||
SHARED_EXECUTION_ENVIRONMENT
|
||||
|
||||
create
|
||||
make
|
||||
|
||||
feature -- Setup
|
||||
|
||||
cgi_file_extensions: ITERABLE [READABLE_STRING_GENERAL]
|
||||
once
|
||||
Result := <<"cgi">>
|
||||
end
|
||||
|
||||
directory_index_file_names: ITERABLE [READABLE_STRING_GENERAL]
|
||||
once
|
||||
Result := <<"index.html">>
|
||||
end
|
||||
|
||||
document_location: PATH
|
||||
-- Root folder for the httpd files server.
|
||||
local
|
||||
i,n: INTEGER
|
||||
d: detachable READABLE_STRING_GENERAL
|
||||
once
|
||||
Result := execution_environment.current_working_path.extended ("www")
|
||||
if attached execution_environment.arguments as args then
|
||||
from
|
||||
i := 1
|
||||
n := args.argument_count
|
||||
until
|
||||
i > n or d /= Void
|
||||
loop
|
||||
if
|
||||
attached args.argument (i) as v and then
|
||||
v.same_string_general ("--root") and then
|
||||
i < n
|
||||
then
|
||||
i := i + 1
|
||||
d := args.argument (i)
|
||||
end
|
||||
i := i + 1
|
||||
end
|
||||
if d /= Void then
|
||||
create Result.make_from_string (d)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
setup_router
|
||||
-- Setup `router'
|
||||
local
|
||||
cgi: WSF_CGI_HANDLER
|
||||
cgi_cond: WSF_ROUTING_CONDITION
|
||||
fs: WSF_FILE_SYSTEM_HANDLER
|
||||
m: WSF_STARTS_WITH_MAPPING
|
||||
cond: WSF_WITH_CONDITION_MAPPING
|
||||
s: STRING_32
|
||||
do
|
||||
create cgi.make (document_location)
|
||||
cgi_cond := create {WSF_ROUTING_FILE_EXISTS_CONDITION}.make (document_location) and create {WSF_ROUTING_EXTENSION_CONDITION}.make (cgi_file_extensions)
|
||||
create cond.make (cgi_cond, cgi)
|
||||
create s.make_empty
|
||||
across
|
||||
cgi_file_extensions as ic
|
||||
loop
|
||||
if not s.is_empty then
|
||||
s.append_string_general (", ")
|
||||
end
|
||||
s.append_string_general ("*.")
|
||||
s.append_string_general (ic.item)
|
||||
end
|
||||
cond.set_condition_description (s)
|
||||
|
||||
router.map (cond, Void)
|
||||
|
||||
create fs.make_with_path (document_location)
|
||||
fs.enable_index
|
||||
fs.set_directory_index (directory_index_file_names)
|
||||
fs.set_not_found_handler (agent (i_uri: READABLE_STRING_8; req: WSF_REQUEST; res: WSF_RESPONSE) do execute_default (req, res) end)
|
||||
|
||||
create m.make ("", fs)
|
||||
router.map (m, router.methods_get)
|
||||
end
|
||||
|
||||
end
|
||||
1
tools/httpd/www/home.html
Normal file
1
tools/httpd/www/home.html
Normal file
@@ -0,0 +1 @@
|
||||
Hello EiffelWeb httpd server.
|
||||
Reference in New Issue
Block a user