diff --git a/library/network/protocol/http/src/http_header.e b/library/network/protocol/http/src/http_header.e index 91c8a201..a9cee805 100644 --- a/library/network/protocol/http/src/http_header.e +++ b/library/network/protocol/http/src/http_header.e @@ -69,23 +69,9 @@ feature {NONE} -- Initialization make_from_raw_header_data (h: READABLE_STRING_8) -- Create Current from raw header data - local - line : detachable STRING - lines: LIST [READABLE_STRING_8] do - lines := h.split ('%N') - make_with_count (lines.count) - across - lines as c - loop - line := c.item - if not line.is_empty then - if line [line.count] = '%R' then - line.remove_tail (1) - end - add_header (line) - end - end + make + append_raw_header_data (h) end feature -- Recycle @@ -171,7 +157,28 @@ feature -- Access Result := headers.new_cursor end -feature -- Header: filling +feature -- Header: adding + + append_raw_header_data (h: READABLE_STRING_8) + -- Append raw header data `h' to Current + local + line : detachable STRING + lines: LIST [READABLE_STRING_8] + do + lines := h.split ('%N') + headers.grow (headers.count + lines.count) + across + lines as c + loop + line := c.item + if not line.is_empty then + if line [line.count] = '%R' then + line.remove_tail (1) + end + add_header (line) + end + end + end append_array (a_headers: ARRAY [TUPLE [key: READABLE_STRING_8; value: READABLE_STRING_8]]) -- Append array of key,value headers @@ -180,7 +187,7 @@ feature -- Header: filling across a_headers as c loop - put_header_key_value (c.item.key, c.item.value) + add_header_key_value (c.item.key, c.item.value) end end @@ -195,6 +202,54 @@ feature -- Header: filling end end +feature -- Header: merging + + put_raw_header_data (h: READABLE_STRING_8) + -- Append raw header data `h' to Current + -- Overwrite existing header with same name + local + line : detachable STRING + lines: LIST [READABLE_STRING_8] + do + lines := h.split ('%N') + headers.grow (headers.count + lines.count) + across + lines as c + loop + line := c.item + if not line.is_empty then + if line [line.count] = '%R' then + line.remove_tail (1) + end + put_header (line) + end + end + end + + put_array (a_headers: ARRAY [TUPLE [key: READABLE_STRING_8; value: READABLE_STRING_8]]) + -- Append array of key,value headers + -- Overwrite existing header with same name + do + headers.grow (headers.count + a_headers.count) + across + a_headers as c + loop + put_header_key_value (c.item.key, c.item.value) + end + end + + put_header_object (h: HTTP_HEADER) + -- Append headers from `h' + -- Overwrite existing header with same name + do + headers.grow (headers.count + h.headers.count) + across + h.headers as c + loop + put_header (c.item.string) + end + end + feature -- Header change: general add_header (h: READABLE_STRING_8) diff --git a/library/server/ewsgi/connectors/cgi/src/wgi_cgi_connector.e b/library/server/ewsgi/connectors/cgi/src/wgi_cgi_connector.e index dc98f64f..56718745 100644 --- a/library/server/ewsgi/connectors/cgi/src/wgi_cgi_connector.e +++ b/library/server/ewsgi/connectors/cgi/src/wgi_cgi_connector.e @@ -44,6 +44,7 @@ feature -- Execution create req.make ((create {EXECUTION_ENVIRONMENT}).starting_environment_variables, create {WGI_CGI_INPUT_STREAM}.make, Current) create res.make (create {WGI_CGI_OUTPUT_STREAM}.make, create {WGI_CGI_ERROR_STREAM}.make) service.execute (req, res) + res.push else if attached (create {EXCEPTION_MANAGER}).last_exception as e and then attached e.exception_trace as l_trace then if res /= Void then @@ -53,6 +54,7 @@ feature -- Execution if res.message_writable then res.put_string ("
" + l_trace + "
") end + res.push end end end diff --git a/library/server/ewsgi/connectors/libfcgi/src/wgi_libfcgi_connector.e b/library/server/ewsgi/connectors/libfcgi/src/wgi_libfcgi_connector.e index 08efc4a4..32f54566 100644 --- a/library/server/ewsgi/connectors/libfcgi/src/wgi_libfcgi_connector.e +++ b/library/server/ewsgi/connectors/libfcgi/src/wgi_libfcgi_connector.e @@ -65,6 +65,7 @@ feature -- Execution create req.make (vars, a_input, Current) create res.make (a_output, a_output) service.execute (req, res) + res.push else if attached (create {EXCEPTION_MANAGER}).last_exception as e and then attached e.exception_trace as l_trace then if res /= Void then @@ -74,6 +75,7 @@ feature -- Execution if res.message_writable then res.put_string ("
" + l_trace + "
") end + res.push end end end diff --git a/library/server/ewsgi/connectors/nino/src/wgi_nino_connector.e b/library/server/ewsgi/connectors/nino/src/wgi_nino_connector.e index e66ebbd5..45f0a401 100644 --- a/library/server/ewsgi/connectors/nino/src/wgi_nino_connector.e +++ b/library/server/ewsgi/connectors/nino/src/wgi_nino_connector.e @@ -133,10 +133,10 @@ feature -- Server res: detachable WGI_NINO_RESPONSE_STREAM do create req.make (env, create {WGI_NINO_INPUT_STREAM}.make (a_socket), Current) - create res.make (create {WGI_NINO_OUTPUT_STREAM}.make (a_socket), Void) + create res.make (create {WGI_NINO_OUTPUT_STREAM}.make (a_socket), create {WGI_NINO_ERROR_STREAM}.make_stderr (a_socket.descriptor.out)) req.set_meta_string_variable ("RAW_HEADER_DATA", a_headers_text) service.execute (req, res) - res.commit + res.push end note diff --git a/library/server/ewsgi/connectors/nino/src/wgi_nino_error_stream.e b/library/server/ewsgi/connectors/nino/src/wgi_nino_error_stream.e new file mode 100644 index 00000000..facbfb66 --- /dev/null +++ b/library/server/ewsgi/connectors/nino/src/wgi_nino_error_stream.e @@ -0,0 +1,70 @@ +note + description: "Summary description for WGI_CGI_ERROR_STREAM." + legal: "See notice at end of class." + status: "See notice at end of class." + date: "$Date$" + revision: "$Revision$" + +class + WGI_NINO_ERROR_STREAM + +inherit + WGI_ERROR_STREAM + +create + make, + make_stderr, + make_stdout + +feature {NONE} -- Initialization + + make (a_identifier: READABLE_STRING_8; a_file: PLAIN_TEXT_FILE) + do + identifier := a_identifier + output := a_file + end + + make_stderr (a_identifier: READABLE_STRING_8) + do + make (a_identifier, io.error) + end + + make_stdout (a_identifier: READABLE_STRING_8) + do + make (a_identifier, io.error) + end + +feature -- Access + + identifier: READABLE_STRING_8 + + output: FILE + +feature -- Error + + put_error (a_message: READABLE_STRING_8) + local + s: STRING + do + create s.make (a_message.count + identifier.count + 4) + s.append_character ('[') + s.append (identifier) + s.append_character (']') + s.append_character (' ') + s.append (a_message) + s.append_character ('%N') + -- Display it at once. + output.put_string (s) + end + +note + copyright: "2011-2011, 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 diff --git a/library/server/ewsgi/specification/response/wgi_filter_response.e b/library/server/ewsgi/specification/response/wgi_filter_response.e index 5a1ce391..ec7235a2 100644 --- a/library/server/ewsgi/specification/response/wgi_filter_response.e +++ b/library/server/ewsgi/specification/response/wgi_filter_response.e @@ -17,6 +17,7 @@ feature {NONE} -- Initialization make_with_response (res: WGI_RESPONSE) do wgi_response := res + res.set_post_commit_action (agent commit) end wgi_response: WGI_RESPONSE @@ -26,7 +27,7 @@ feature {WGI_CONNECTOR, WGI_SERVICE} -- Commit commit -- Commit the current response do - -- do nothing, this will be done internal on the original `wgi_response' object + wgi_response.set_post_commit_action (Void) end feature -- Status report @@ -129,7 +130,7 @@ feature -- Error reporting end note - copyright: "2011-2012, Jocelyn Fiat, Javier Velilla, Eiffel Software and others" + copyright: "2011-2013, Jocelyn Fiat, Javier Velilla, Eiffel Software and others" license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)" source: "[ Eiffel Software diff --git a/library/server/ewsgi/specification/response/wgi_response.e b/library/server/ewsgi/specification/response/wgi_response.e index 25a570a4..11db233e 100644 --- a/library/server/ewsgi/specification/response/wgi_response.e +++ b/library/server/ewsgi/specification/response/wgi_response.e @@ -8,6 +8,15 @@ deferred class feature {WGI_CONNECTOR, WGI_SERVICE} -- Commit + push + -- Commit and push response + do + commit + if attached post_commit_action as act then + act.call (Void) + end + end + commit -- Commit the current response deferred @@ -17,6 +26,21 @@ feature {WGI_CONNECTOR, WGI_SERVICE} -- Commit message_committed: message_committed end +feature -- Access: commit + + post_commit_action: detachable PROCEDURE [ANY, TUPLE] + -- Action associated with the final `commit' execution + -- Note: useful to trigger action just after the + -- response is transfered to the client. + +feature -- Change: commit + + set_post_commit_action (act: like post_commit_action) + -- Assign `act' to `post_commit_action' + do + post_commit_action := act + end + feature -- Status report status_committed: BOOLEAN @@ -131,7 +155,7 @@ feature -- Error reporting end note - copyright: "2011-2012, Jocelyn Fiat, Javier Velilla, Eiffel Software and others" + copyright: "2011-2013, Jocelyn Fiat, Javier Velilla, Eiffel Software and others" license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)" source: "[ Eiffel Software diff --git a/library/server/wsf/src/implementation/wsf_wgi_delayed_header_response.e b/library/server/wsf/src/implementation/wsf_wgi_delayed_header_response.e new file mode 100644 index 00000000..006b67ff --- /dev/null +++ b/library/server/wsf/src/implementation/wsf_wgi_delayed_header_response.e @@ -0,0 +1,108 @@ +note + description: "Summary description for {WGI_DELAYED_HEADER_RESPONSE}." + date: "$Date$" + revision: "$Revision$" + +class + WSF_WGI_DELAYED_HEADER_RESPONSE + +inherit + WGI_FILTER_RESPONSE + redefine + commit, + put_character, + put_string, + put_substring, + flush, + message_writable + end + + WSF_RESPONSE_EXPORTER + +create + make + +feature {NONE} -- Initialization + + make (r: WGI_RESPONSE; res: WSF_RESPONSE) + do + wsf_response := res + make_with_response (r) + end + +feature {NONE} -- Implementation + + wsf_response: WSF_RESPONSE + + commit + do + Precursor + if not header_committed then + process_header + end + end + + process_header + require + header_not_committed: not header_committed + do + -- If no content is sent, the final `{WGI_REPONSE}.push' will call `process_header' + -- via `{WGI_RESPONSE}.post_commit_action' + wgi_response.set_post_commit_action (Void) + + -- commit status code and reason phrase + -- commit header text + wsf_response.process_header + + -- update wgi_response on wsf_response to send content directly + wsf_response.set_wgi_response (wgi_response) + ensure + header_committed: header_committed + end + +feature -- Status report + + message_writable: BOOLEAN = True + -- Can message be written? + +feature -- Output operation + + put_character (c: CHARACTER_8) + -- Send the character `c' + do + process_header + Precursor (c) + end + + put_string (s: READABLE_STRING_8) + -- Send the string `s' + do + process_header + Precursor (s) + end + + put_substring (s: READABLE_STRING_8; a_begin_index, a_end_index: INTEGER) + -- Send the substring `s[a_begin_index:a_end_index]' + do + process_header + Precursor (s, a_begin_index, a_end_index) + end + + flush + -- Flush if it makes sense + do + process_header + Precursor + end + +note + copyright: "2011-2013, Jocelyn Fiat, Javier Velilla, Olivier Ligot, 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 diff --git a/library/server/wsf/src/wsf_response.e b/library/server/wsf/src/wsf_response.e index edd69fed..c70c1da3 100644 --- a/library/server/wsf/src/wsf_response.e +++ b/library/server/wsf/src/wsf_response.e @@ -28,15 +28,32 @@ convert feature {NONE} -- Initialization make_from_wgi (r: WGI_RESPONSE) + local + wres: detachable WSF_WGI_DELAYED_HEADER_RESPONSE do transfered_content_length := 0 + create header.make wgi_response := r + create wres.make (r, Current) + wgi_response := wres + set_status_code ({HTTP_STATUS_CODE}.ok) -- Default value end feature {WSF_RESPONSE_EXPORTER} -- Properties wgi_response: WGI_RESPONSE - -- Associated WGI_RESPONSE + -- Associated WGI_RESPONSE. + + header: WSF_HEADER + -- Associated response header. + +feature {WSF_RESPONSE_EXPORTER} -- Change + + set_wgi_response (res: WGI_RESPONSE) + -- Set associated WGI_RESPONSE + do + wgi_response := res + end feature -- Status report @@ -76,6 +93,7 @@ feature -- Status setting -- Set response status code -- Should be done before sending any data back to the client --| note: the status is really sent when the header are set + --| Default value might be set to 200 {HTTP_HEADER}.ok. require a_code_valid: a_code > 0 status_not_set: not status_committed @@ -112,61 +130,146 @@ feature -- Status setting status_reason_phrase: detachable READABLE_STRING_8 -- Custom status reason phrase (optional) +feature {WSF_RESPONSE_EXPORTER} -- Header output operation + + process_header + require + header_not_committed: not header_committed + do + if not header_committed then + -- commit status code and reason phrase + wgi_response.set_status_code (status_code, status_reason_phrase) + -- commit header text + wgi_response.put_header_text (header.string) + end + ensure + status_committed: status_committed + header_committed: header_committed + end + + report_content_already_sent_and_header_ignored + do + put_error ("Content already sent, new header text ignored!") + end + feature -- Header output operation - put_header_text (a_text: READABLE_STRING_8) - -- Sent `a_text' and just before send the status code + put_header_line (h: READABLE_STRING_8) + -- Put header `h' + -- Replace any existing value + require + header_not_committed: not header_committed + do + if header_committed then + report_content_already_sent_and_header_ignored + else + header.put_header (h) + end + end + + add_header_line (h: READABLE_STRING_8) + -- Add header `h' + -- This can lead to duplicated header entries + require + header_not_committed: not header_committed + do + if header_committed then + report_content_already_sent_and_header_ignored + else + header.add_header (h) + end + end + + put_header_text (a_text: READABLE_STRING_8) + -- Put the multiline header `a_text' + -- Overwite potential existing header require - status_set: status_is_set header_not_committed: not header_committed a_text_ends_with_single_crlf: a_text.count > 2 implies not a_text.substring (a_text.count - 2, a_text.count).same_string ("%R%N") a_text_does_not_end_with_double_crlf: a_text.count > 4 implies not a_text.substring (a_text.count - 4, a_text.count).same_string ("%R%N%R%N") do - wgi_response.set_status_code (status_code, status_reason_phrase) - wgi_response.put_header_text (a_text) + if header_committed then + report_content_already_sent_and_header_ignored + else + header.append_raw_header_data (a_text) + end ensure - status_set: status_is_set - status_committed: status_committed - header_committed: header_committed message_writable: message_writable end + add_header_text (a_text: READABLE_STRING_8) + -- Add the multiline header `a_text' + -- Does not replace existing header with same name + -- This could leads to multiple header with the same name + require + header_not_committed: not header_committed + a_text_ends_with_single_crlf: a_text.count > 2 implies not a_text.substring (a_text.count - 2, a_text.count).same_string ("%R%N") + a_text_does_not_end_with_double_crlf: a_text.count > 4 implies not a_text.substring (a_text.count - 4, a_text.count).same_string ("%R%N%R%N") + do + if header_committed then + report_content_already_sent_and_header_ignored + else + header.append_raw_header_data (a_text) + end + ensure + status_set: status_is_set + message_writable: message_writable + end + +feature -- Header output operation: helpers + put_header (a_status_code: INTEGER; a_headers: detachable ARRAY [TUPLE [name: READABLE_STRING_8; value: READABLE_STRING_8]]) - -- Send headers with status `a_status', and headers from `a_headers' + -- Put headers with status `a_status', and headers from `a_headers' require status_not_committed: not status_committed header_not_committed: not header_committed local h: HTTP_HEADER do - set_status_code (a_status_code) if a_headers /= Void then create h.make_from_array (a_headers) put_header_text (h.string) end ensure status_set: status_is_set - header_committed: header_committed + message_writable: message_writable + end + + add_header (a_status_code: INTEGER; a_headers: detachable ARRAY [TUPLE [name: READABLE_STRING_8; value: READABLE_STRING_8]]) + -- Put headers with status `a_status', and headers from `a_headers' + require + status_not_committed: not status_committed + header_not_committed: not header_committed + local + h: HTTP_HEADER + do + if a_headers /= Void then + create h.make_from_array (a_headers) + add_header_text (h.string) + end + ensure + status_set: status_is_set message_writable: message_writable end put_header_lines (a_lines: ITERABLE [TUPLE [name: READABLE_STRING_8; value: READABLE_STRING_8]]) - -- Send headers from `a_lines' - local - h: STRING_8 + -- Put headers from `a_lines' + require + header_not_committed: not header_committed do - create h.make (256) - across - a_lines as c - loop - h.append (c.item.name) - h.append_character (':') - h.append_character (' ') - h.append (c.item.value) - h.append_character ('%R') - h.append_character ('%N') + across a_lines as c loop + put_header_line (c.item.name + ": " + c.item.value) + end + end + + add_header_lines (a_lines: ITERABLE [TUPLE [name: READABLE_STRING_8; value: READABLE_STRING_8]]) + -- Add headers from `a_lines' + require + header_not_committed: not header_committed + do + across a_lines as c loop + add_header_line (c.item.name + ": " + c.item.value) end - put_header_text (h) end feature -- Output report @@ -376,7 +479,7 @@ feature -- Error reporting end note - copyright: "2011-2012, Jocelyn Fiat, Javier Velilla, Olivier Ligot, Eiffel Software and others" + copyright: "2011-2013, Jocelyn Fiat, Javier Velilla, Olivier Ligot, Eiffel Software and others" license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)" source: "[ Eiffel Software