Files
EWF/library/server/httpd/httpd_request_handler_i.e
Jocelyn Fiat d4ec640ac8 Renamed many classes and feature to use "secure" term instead of "ssl". (note, the .ecf are still using the "ssl" terminologie).
Provided easy way to set secure settings for Standalone.
For wsf launcher boolean option accept "true" or "yes" for True boolean, anything else is False.
2016-10-14 11:52:48 +02:00

598 lines
16 KiB
Plaintext

note
description: "HTTPD handler interface processing request."
date: "$Date$"
revision: "$Revision$"
deferred class
HTTPD_REQUEST_HANDLER_I
inherit
HTTPD_DEBUG_FACILITIES
HTTPD_LOGGER_CONSTANTS
HTTPD_SOCKET_FACTORY
feature {NONE} -- Initialization
make (a_request_settings: HTTPD_REQUEST_SETTINGS)
do
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 (False)
reset_error
if attached internal_client_socket as l_sock then
l_sock.cleanup
end
internal_client_socket := Void
end
reset_request (a_is_reusing_connection: BOOLEAN)
-- Reset the request, and `a_is_reusing_connection' says if the peristent connection is
-- still alive.
do
if a_is_reusing_connection then
-- Keep `remote_info' as it stays the same for the successive
-- persistent connections.
else
remote_info := Void
end
version := Void
-- FIXME: optimize to just wipe_out if needed
create method.make_empty
create uri.make_empty
create request_header.make_empty
create request_header_map.make (10)
is_persistent_connection_requested := False
end
feature -- Status report
is_connected: BOOLEAN
-- Is handler connected to incoming request via `client_socket'?
do
Result := client_socket.descriptor_available
end
feature -- Access
internal_client_socket: detachable HTTPD_STREAM_SOCKET
client_socket: HTTPD_STREAM_SOCKET
local
s: like internal_client_socket
do
s := internal_client_socket
if s = Void then
s := new_client_socket (is_secure)
internal_client_socket := s
end
Result := s
end
request_header: STRING
-- Header' source
request_header_map: HASH_TABLE [STRING, STRING]
-- Contains key:value of the header
method: STRING
-- http verb
uri: STRING
-- http endpoint
version: detachable STRING
-- http_version
--| unused for now
remote_info: detachable TUPLE [addr: STRING; hostname: STRING; port: INTEGER]
-- Information related to remote client
is_persistent_connection_requested: BOOLEAN
-- Persistent connection requested?
-- either has "Connection: keep-alive" header,
-- or is HTTP/1.1 and no header "Connection: close".
is_http_version_1_0: BOOLEAN
do
Result := not attached version as v or else v.same_string ("HTTP/1.0")
end
is_http_version_1_1: BOOLEAN
do
Result := not attached version as v or else v.same_string ("HTTP/1.1")
end
is_http_version_2: BOOLEAN
do
Result := not attached version as v or else v.same_string ("HTTP/2.0")
end
feature -- Settings
is_verbose: BOOLEAN
-- Output messages?
verbose_level: INTEGER
-- Output verbosity.
is_secure: BOOLEAN
-- Is secure socket?
-- i.e: SSL?
is_persistent_connection_supported: BOOLEAN
-- Is persistent connection supported?
do
Result := {HTTPD_SERVER}.is_persistent_connection_supported and then max_keep_alive_requests > 0
end
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.
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.
keep_alive_timeout: INTEGER -- seconds
-- Number of seconds for persistent connection timeout.
feature -- Status report
has_error: BOOLEAN
-- Error occurred during `get_request_header'
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)
-- Set `is_verbose' with `b'.
do
is_verbose := b
ensure
is_verbose_set: is_verbose = b
end
feature -- Execution
safe_execute
-- Execute accepted incoming connection as request.
local
retried: BOOLEAN
do
if retried then
release
else
if
not has_error and then
is_connected
then
execute
end
release
end
rescue
retried := True
retry
end
execute
require
is_connected: is_connected
local
l_socket: like client_socket
l_remote_info: detachable like remote_info
l_exit: BOOLEAN
n,m: INTEGER
do
l_socket := client_socket
-- Compute remote info once for the persistent connection.
create l_remote_info
if attached l_socket.peer_address as l_addr then
l_remote_info.addr := l_addr.host_address.host_address
l_remote_info.hostname := l_addr.host_address.host_name
l_remote_info.port := l_addr.port
end
remote_info := l_remote_info
check
socket_attached: l_socket /= Void
socket_valid: l_socket.is_open_read and then l_socket.is_open_write
end
from
-- Process persistent connection as long the socket is not closed.
n := 0
m := max_keep_alive_requests
is_next_persistent_connection_supported := True
until
l_exit
loop
n := n + 1
if n >= m then
is_next_persistent_connection_supported := False
elseif n > 1 and is_verbose then
log ("Reuse connection (" + n.out + ")", information_level)
end
-- FIXME: it seems to be called one more time, mostly to see this is done.
execute_request (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 (not l_exit)
end
if l_exit and has_error and not l_socket.is_closed then
l_socket.close
end
end
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
remote_info_set: remote_info /= Void
local
l_socket: like client_socket
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
socket_valid: l_socket.is_open_read and then l_socket.is_open_write
end
if l_socket.is_closed then
debug ("dbglog")
dbglog ("execute_request {socket is Closed!}")
end
else
debug ("dbglog")
dbglog ("execute_request socket=" + l_socket.descriptor.out + " ENTER")
end
-- Try to get request header.
-- If the request is reusing persistent connection, use `keep_alive_timeout',
-- otherwise `socket_recv_timeout'.
get_request_header (l_socket, a_is_reusing_connection)
if has_error then
if a_is_reusing_connection and then request_header.is_empty then
-- Close persistent connection, since no new connection occurred in the delay `keep_alive_timeout'.
debug ("dbglog")
dbglog ("execute_request socket=" + l_socket.descriptor.out + "} close persistent connection.")
end
else
if is_verbose then
log (request_header + "%NWARNING: invalid HTTP incoming request", warning_level)
end
process_bad_request (l_socket)
end
is_persistent_connection_requested := False
else
if is_verbose then
log (request_header, information_level)
end
process_request (l_socket)
end
debug ("dbglog")
dbglog ("execute_request {" + l_socket.descriptor.out + "} LEAVE")
end
end
end
release
do
reset
end
feature -- Request processing
process_request (a_socket: HTTPD_STREAM_SOCKET)
-- Process request on socket `a_socket'.
require
no_error: not has_error
a_uri_attached: uri /= Void
a_method_attached: method /= Void
a_header_map_attached: request_header_map /= Void
a_header_text_attached: request_header /= Void
a_socket_attached: a_socket /= Void
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
get_request_header (a_socket: HTTPD_STREAM_SOCKET; a_is_reusing_connection: BOOLEAN)
-- Analyze message extracted from `a_socket' as HTTP request.
-- If `a_is_reusing_connection' is True, then first use
-- Note: it reads from socket.
-- Note: it updates `request_header' and `request_header_map', and eventually `is_persistent_connection_requested'.
require
input_readable: a_socket /= Void and then a_socket.is_open_read
local
end_of_stream: BOOLEAN
pos, n: INTEGER
line: detachable STRING
k, val: STRING
txt: STRING
l_is_verbose: BOOLEAN
do
create txt.make (64)
request_header := txt
l_is_verbose := is_verbose
if
not has_error and then
a_socket.readable
then
if a_is_reusing_connection then
a_socket.set_recv_timeout (keep_alive_timeout) -- in seconds!
else
a_socket.set_recv_timeout (socket_recv_timeout) -- FIXME: return a 408 Request Timeout response ..
end
if
attached next_line (a_socket) as l_request_line and then
not l_request_line.is_empty and then
not has_error
then
txt.append (l_request_line)
txt.append_character ('%N')
analyze_request_line (l_request_line)
if not has_error then
if a_is_reusing_connection then
-- Restore normal recv timeout!
a_socket.set_recv_timeout (socket_recv_timeout) -- FIXME: return a 408 Request Timeout response ..
end
from
line := next_line (a_socket)
until
line = Void or end_of_stream or has_error
loop
n := line.count
debug ("ew_standalone")
if l_is_verbose then
log (line, debug_level)
end
end
pos := line.index_of (':', 1)
if pos > 0 then
k := line.substring (1, pos - 1)
if line [pos + 1].is_space then
pos := pos + 1
end
if line [n] = '%R' then
n := n - 1
end
val := line.substring (pos + 1, n)
request_header_map.put (val, k)
end
txt.append (line)
txt.append_character ('%N')
if line.is_empty or else line [1] = '%R' then
end_of_stream := True
else
line := next_line (a_socket)
end
end
-- Except for HTTP/1.0, persistent connection is the default.
is_persistent_connection_requested := True
if is_http_version_1_0 then
is_persistent_connection_requested := attached request_header_map.item ("Connection") as l_connection and then
l_connection.is_case_insensitive_equal_general ("keep-alive")
else
-- By default HTTP:1/1 support persistent connection.
if attached request_header_map.item ("Connection") as l_connection then
if l_connection.is_case_insensitive_equal_general ("close") then
is_persistent_connection_requested := False
end
else
is_persistent_connection_requested := True
end
end
end
else
report_error ("Bad header line (empty)")
end
else
report_error ("Socket is not readable")
end
end
analyze_request_line (line: STRING)
-- Analyze `line' as a HTTP request line.
-- note: may update `has_error'.
require
valid_line: line /= Void and then not line.is_empty
local
n, pos, next_pos: INTEGER
do
debug ("ew_standalone")
if is_verbose then
log ("%N## Parse HTTP request line ##", debug_level)
log (line, debug_level)
end
end
pos := line.index_of (' ', 1)
method := line.substring (1, pos - 1)
next_pos := line.index_of (' ', pos + 1)
uri := line.substring (pos + 1, next_pos - 1)
n := line.count
if line[n] = '%R' then
n := n - 1
end
version := line.substring (next_pos + 1, n)
if method.is_empty then
report_error ("Missing request method data")
end
end
next_line (a_socket: HTTPD_STREAM_SOCKET): detachable STRING
-- Next line fetched from `a_socket' is available.
-- note: may update `has_error'.
require
not_has_error: not has_error
is_readable: a_socket.is_open_read
local
retried: BOOLEAN
do
if retried then
report_error ("Rescue in next_line")
a_socket.close
Result := Void
elseif a_socket.readable then
a_socket.read_line_noexception
Result := a_socket.last_string
-- Do no check `was_error' before socket operation,
-- otherwise it may be False, due to error during other socket operation in same thread.
if a_socket.was_error then
report_error ("Socket error")
if is_verbose then
log (request_header +"%N" + Result + "%N## Network error: " + a_socket.error + " ##", debug_level)
end
end
else
-- Error with socket...
report_error ("Socket error: not readable")
if is_verbose then
log (request_header + "%N## Socket is not readable! ##", debug_level)
end
end
rescue
-- In case of network error exception (as EiffelNet reports error raising exception)
retried := True
retry
end
feature -- Output
logger: detachable HTTPD_LOGGER
set_logger (a_logger: like logger)
-- Set `logger' with `a_logger'.
do
logger := a_logger
ensure
logger_set: logger = a_logger
end
log (m: STRING; a_level: INTEGER)
-- Log message `m'.
require
is_verbose: is_verbose
do
if is_verbose and (verbose_level & a_level) = a_level then
if attached logger as l_logger then
l_logger.log (m)
else
io.put_string (m + "%N")
end
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
-- Result := a_socket.has_incoming_data
Result := True
end
invariant
request_header_attached: request_header /= Void
note
copyright: "2011-2016, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
source: "[
Eiffel Software
5949 Hollister Ave., Goleta, CA 93117 USA
Telephone 805-685-1006, Fax 805-685-6869
Website http://www.eiffel.com
Customer support http://support.eiffel.com
]"
end