From 77085364ee9706edcc18266cc420deb7f10dec06 Mon Sep 17 00:00:00 2001 From: Jocelyn Fiat Date: Tue, 4 Oct 2016 18:49:48 +0200 Subject: [PATCH] Improve socket management for EiffelWeb standalone connector. --- examples/simple/application_execution.e | 2 + .../lib/httpd/httpd_request_handler_i.e | 126 ++++++++++++------ .../lib/httpd/network/httpd_stream_socket.e | 16 +++ .../lib/httpd/network/tcp_stream_socket.e | 40 ++++++ .../wgi_httpd_request_handler.e | 4 +- 5 files changed, 143 insertions(+), 45 deletions(-) diff --git a/examples/simple/application_execution.e b/examples/simple/application_execution.e index 179f215c..d823a16e 100644 --- a/examples/simple/application_execution.e +++ b/examples/simple/application_execution.e @@ -24,6 +24,8 @@ feature -- Basic operations s := "Hello World!" create dt.make_now_utc s.append (" (UTC time is " + dt.rfc850_string + ").") + s.append ("%N") + s.append ("Your request: " + request.request_uri + " %N") response.put_header ({HTTP_STATUS_CODE}.ok, <<["Content-Type", "text/html"], ["Content-Length", s.count.out]>>) response.set_status_code ({HTTP_STATUS_CODE}.ok) response.header.put_content_type_text_html diff --git a/library/server/ewsgi/connectors/standalone/lib/httpd/httpd_request_handler_i.e b/library/server/ewsgi/connectors/standalone/lib/httpd/httpd_request_handler_i.e index d401aa4a..38481237 100644 --- a/library/server/ewsgi/connectors/standalone/lib/httpd/httpd_request_handler_i.e +++ b/library/server/ewsgi/connectors/standalone/lib/httpd/httpd_request_handler_i.e @@ -33,7 +33,7 @@ feature {NONE} -- Initialization do reset_request - has_error := False + reset_error if attached internal_client_socket as l_sock then l_sock.cleanup end @@ -156,6 +156,20 @@ feature -- Status report has_error: BOOLEAN -- Error occurred during `analyze_request_message' +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) @@ -236,11 +250,18 @@ feature -- Execution require is_connected: is_connected reuse_connection_when_possible: a_is_reusing_connection implies is_persistent_connection_supported + no_error: not has_error local l_remote_info: detachable like remote_info l_socket: like client_socket l_is_ready: BOOLEAN 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 @@ -248,17 +269,18 @@ feature -- Execution end if l_socket.is_closed then debug ("dbglog") - dbglog (generator + ".execute_request {socket is Closed!}") + dbglog ("execute_request {socket is Closed!}") end else debug ("dbglog") - dbglog (generator + ".execute_request socket=" + l_socket.descriptor.out + " ENTER") + dbglog ("execute_request socket=" + l_socket.descriptor.out + " ENTER") end if a_is_reusing_connection then --| set by default 5 seconds. l_socket.set_recv_timeout (keep_alive_timeout) -- in seconds! - l_is_ready := l_socket.ready_for_reading + -- FIXME: check if both are really needed. + l_is_ready := l_socket.ready_for_reading and then l_socket.has_incoming_data else l_is_ready := True end @@ -273,30 +295,31 @@ feature -- Execution remote_info := l_remote_info end analyze_request_message (l_socket) - else - has_error := True - debug ("dbglog") - dbglog (generator + ".execute_request socket=" + l_socket.descriptor.out + "} timeout!") - end - end - if has_error then - if l_is_ready then + if has_error then -- check catch_bad_incoming_connection: False end if is_verbose then log (request_header + "%NWARNING: invalid HTTP incoming request", warning_level) end - end - process_bad_request (l_socket) - is_persistent_connection_requested := False - else - if is_verbose then - log (request_header, information_level) - end - process_request (l_socket) - end + process_bad_request (l_socket) + is_persistent_connection_requested := False + else + if is_verbose then + log (request_header, information_level) + end + process_request (l_socket) + end + else + check is_reusing_connection: a_is_reusing_connection end + -- Close persistent connection, since no new connection occurred in the delay `keep_alive_timeout'. + is_persistent_connection_requested := False + debug ("dbglog") + dbglog ("execute_request socket=" + l_socket.descriptor.out + "} close persistent connection.") + end + end + debug ("dbglog") - dbglog (generator + ".execute_request {" + l_socket.descriptor.out + "} LEAVE") + dbglog ("execute_request {" + l_socket.descriptor.out + "} LEAVE") end end end @@ -329,22 +352,27 @@ feature -- Request processing h: STRING s: STRING do - s := "{ + -- NOTE: this is experiment code, and not ready yet. + if a_socket.is_connected and then a_socket.ready_for_writing then + s := "{ 400 Bad Request

Bad Request

- - }" - 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) - a_socket.put_string (s) + + }" + 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.is_connected and then a_socket.ready_for_writing then + a_socket.put_string (s) + end + end end feature -- Parsing @@ -365,15 +393,23 @@ feature -- Parsing request_header := txt if not has_error and then - a_socket.is_readable and then - attached next_line (a_socket) as l_request_line and then - not l_request_line.is_empty + a_socket.readable then - txt.append (l_request_line) - txt.append_character ('%N') - analyze_request_line (l_request_line) + if not a_socket.has_incoming_data then + dbglog ("analyze_request_message: NO INCOMING DATA !!!") + end + if + attached next_line (a_socket) as l_request_line and then + not l_request_line.is_empty + then + txt.append (l_request_line) + txt.append_character ('%N') + analyze_request_line (l_request_line) + else + report_error ("Bad header line (empty)") + end else - has_error := True + report_error ("Socket is not readable") end l_is_verbose := is_verbose if not has_error then @@ -448,7 +484,9 @@ feature -- Parsing n := n - 1 end version := line.substring (next_pos + 1, n) - has_error := method.is_empty + if method.is_empty then + report_error ("Missing request method data") + end end next_line (a_socket: HTTPD_STREAM_SOCKET): detachable STRING @@ -460,7 +498,7 @@ feature -- Parsing retried: BOOLEAN do if retried then - has_error := True + report_error ("Rescue in next_line") Result := Void elseif a_socket.readable then a_socket.read_line_thread_aware @@ -468,14 +506,14 @@ feature -- Parsing -- Do no check `socket_ok' before socket operation, -- otherwise it may be False, due to error during other socket operation in same thread. if not a_socket.socket_ok then - has_error := True + report_error ("Socket error") if is_verbose then log (request_header +"%N" + Result + "%N## socket_ok=False! ##", debug_level) end end else -- Error with socket... - has_error := True + report_error ("Socket error: not readable") if is_verbose then log (request_header + "%N## Socket is not readable! ##", debug_level) end diff --git a/library/server/ewsgi/connectors/standalone/lib/httpd/network/httpd_stream_socket.e b/library/server/ewsgi/connectors/standalone/lib/httpd/network/httpd_stream_socket.e index 76c0bb34..45f9f5cf 100644 --- a/library/server/ewsgi/connectors/standalone/lib/httpd/network/httpd_stream_socket.e +++ b/library/server/ewsgi/connectors/standalone/lib/httpd/network/httpd_stream_socket.e @@ -141,6 +141,15 @@ feature -- Input socket.read_character end + peek_stream (nb_char: INTEGER) + require + nb_char_positive: nb_char > 0 + do + if attached {TCP_STREAM_SOCKET} socket as l_socket then + l_socket.peek_stream (nb_char) + end + end + bytes_read: INTEGER do Result := socket.bytes_read @@ -308,6 +317,13 @@ feature -- Status Report Result := socket.readable end + has_incoming_data: BOOLEAN + do + if attached {TCP_STREAM_SOCKET} socket as l_socket then + Result := l_socket.has_incoming_data + end + end + ready_for_reading: BOOLEAN do if attached {TCP_STREAM_SOCKET} socket as l_socket then diff --git a/library/server/ewsgi/connectors/standalone/lib/httpd/network/tcp_stream_socket.e b/library/server/ewsgi/connectors/standalone/lib/httpd/network/tcp_stream_socket.e index 6961a777..4557ab60 100644 --- a/library/server/ewsgi/connectors/standalone/lib/httpd/network/tcp_stream_socket.e +++ b/library/server/ewsgi/connectors/standalone/lib/httpd/network/tcp_stream_socket.e @@ -55,6 +55,38 @@ feature {NONE} -- Initialization feature -- Basic operation + peek_stream (nb_char: INTEGER) + -- Read a string of at most `nb_char' characters without removing the data from the queue. + -- Make result available in last_string. + require + readable: readable + socket_exists: exists + local + ext: C_STRING + retval: INTEGER + l: like last_string + do + create ext.make_empty (nb_char + 1) + retval := c_receive (descriptor, ext.item, nb_char, c_peekmsg) + if retval = 0 then + last_string.wipe_out + socket_error := Void + elseif retval > 0 then + ext.set_count (retval) + l := last_string + l.wipe_out + l.grow (retval) + l.set_count (retval) + ext.read_substring_into (l, 1, retval) + socket_error := Void + else + last_string.wipe_out + socket_error := "Socket error (MSG_PEEK)" + end + ensure + last_string_not_void: last_string /= Void + end + send_message (a_msg: STRING) do put_string (a_msg) @@ -73,6 +105,14 @@ feature -- Output feature -- Status report + has_incoming_data: BOOLEAN + require + socket_exists: exists + do + peek_stream (1) + Result := last_string.count = 1 + end + try_ready_for_reading: BOOLEAN -- Is data available for reading from the socket right now? require diff --git a/library/server/ewsgi/connectors/standalone/src/implementation/wgi_httpd_request_handler.e b/library/server/ewsgi/connectors/standalone/src/implementation/wgi_httpd_request_handler.e index 4ff9ac89..1c3f8d60 100644 --- a/library/server/ewsgi/connectors/standalone/src/implementation/wgi_httpd_request_handler.e +++ b/library/server/ewsgi/connectors/standalone/src/implementation/wgi_httpd_request_handler.e @@ -97,7 +97,9 @@ feature -- Request processing end end rescue - has_error := l_output = Void or else not l_output.is_available + if l_output = Void or else not l_output.is_available then + report_error ("Missing WGI output") + end if not retried then retried := True retry