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_ns := a_request_settings.timeout_ns -- nanoseconds socket_recv_timeout_ns := a_request_settings.socket_recv_timeout_ns -- nanoseconds keep_alive_timeout_ns := a_request_settings.keep_alive_timeout_ns -- nanoseconds 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 --| `-1` no limit 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_ns: NATURAL_64 -- nanoseconds -- Amount of nanoseconds that the server waits for receipts and transmissions during communications. socket_recv_timeout_ns: NATURAL_64 -- nanoseconds -- Amount of nanoseconds 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_ns: NATURAL_64 -- nanoseconds -- Number of nanoseconds 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 if m /= Void and then is_verbose then log ({UTF_CONVERTER}.utf_32_string_to_utf_8_string_8 (m), debug_level) end 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 -- Set to expected `timeout_ns`. l_socket.set_timeout_ns (timeout_ns) l_socket.set_recv_timeout_ns (socket_recv_timeout_ns) -- 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 := {UTF_CONVERTER}.utf_32_string_to_utf_8_string_8 (l_addr.host_address.host_name) -- FIXME jfiat [2022/01/20] : check what should be done for hostname... 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 `m` is `-1`, no limit for the number of keep_alive requests. if m >= 0 and 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_ns', -- otherwise `socket_recv_timeout_ns'. 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_ns'. 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_with_separation_line (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 := "{ -- --
--