Files
EWF/library/server/wsf/src/wsf_response.e
Jocelyn Fiat c44cf5e983 Fixed assertion that were broken with recent delayed header response.
Changed semantic of put_header_lines and add_header_lines,
Now the arguments are iterable of string (i.e the header line)

The previous features were not used, and were not well named.
So we removed them, and reused the names for adpated implementation.
2013-03-22 16:06:10 +01:00

492 lines
14 KiB
Plaintext

note
description: "[
Main interface to send message back to the client
You can send part by part, i.e:
- set the status code, see: set_status_code (a_code)
- put the header's text, see: put_header_text (a_text)
- put string for the body, see: put_string (s), put_character (c)
- using put_error will eventually send error message directly to the underlying connector's log
(i.e for apache, it will go to the error log)
- And you can also send the message as "chunked", see put_chunk (..) for more details
- Or you can send a WSF_RESPONSE_MESSAGE in once, see `send (mesg)'
]"
date: "$Date$"
revision: "$Revision$"
EIS: "name=Hypertext Transfer Protocol -- HTTP/1.1 ", "protocol=URI", "src=http://www.w3.org/Protocols/rfc2616/rfc2616.html"
EIS: "name=Chunked Transfer Coding", "protocol=URI", "src=http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.6.1"
class
WSF_RESPONSE
create {WSF_TO_WGI_SERVICE}
make_from_wgi
convert
make_from_wgi ({WGI_RESPONSE})
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.
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
status_committed: BOOLEAN
-- Status line committed?
do
Result := wgi_response.status_committed
end
header_committed: BOOLEAN
-- Header committed?
do
Result := wgi_response.header_committed
end
message_committed: BOOLEAN
-- Message committed?
do
Result := wgi_response.message_committed
end
message_writable: BOOLEAN
-- Can message be written?
do
Result := wgi_response.message_writable
end
feature -- Status setting
status_is_set: BOOLEAN
-- Is status set?
do
Result := status_code > 0
end
set_status_code (a_code: INTEGER)
-- 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
header_not_committed: not header_committed
do
status_code := a_code
status_reason_phrase := Void
ensure
status_code_set: status_code = a_code
status_set: status_is_set
status_reason_phrase_unset: status_reason_phrase = Void
end
set_status_code_with_reason_phrase (a_code: INTEGER; a_reason_phrase: READABLE_STRING_8)
-- 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
require
a_code_valid: a_code > 0
status_not_set: not status_committed
header_not_committed: not header_committed
do
set_status_code (a_code)
status_reason_phrase := a_reason_phrase
ensure
status_code_set: status_code = a_code
status_reason_phrase_set: status_reason_phrase = a_reason_phrase
status_set: status_is_set
end
status_code: INTEGER
-- Response status
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_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
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
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]])
-- 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)
put_header_text (h.string)
end
ensure
status_set: status_is_set
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 [READABLE_STRING_8])
-- Put headers from `a_lines'
require
header_not_committed: not header_committed
do
across a_lines as c loop
put_header_line (c.item)
end
end
add_header_lines (a_lines: ITERABLE [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)
end
end
feature -- Output report
transfered_content_length: NATURAL_64
-- Length of the content transfered via `put_string', `put_character'
-- `put_chunk', `put_substring'
feature {NONE} -- Implementation
increment_transfered_content_length (n: INTEGER)
-- Increment `transfered_content_length' by `n'
do
transfered_content_length := transfered_content_length + n.to_natural_64
end
feature -- Body
put_character (c: CHARACTER_8)
-- Send the character `c'
require
message_writable: message_writable
do
wgi_response.put_character (c)
increment_transfered_content_length (1)
end
put_string (s: READABLE_STRING_8)
-- Send the string `s'
require
message_writable: message_writable
do
wgi_response.put_string (s)
increment_transfered_content_length (s.count)
end
put_substring (s: READABLE_STRING_8; a_begin_index, a_end_index: INTEGER)
-- Send the substring `s[a_begin_index:a_end_index]'
require
message_writable: message_writable
do
wgi_response.put_substring (s, a_begin_index, a_end_index)
increment_transfered_content_length (a_end_index - a_begin_index + 1)
end
feature -- Chunk body
put_chunk (a_content: READABLE_STRING_8; a_ext: detachable READABLE_STRING_8)
-- Write chunk non empty `a_content'
-- with optional extension `a_ext': chunk-extension= *( ";" chunk-ext-name [ "=" chunk-ext-val ] )
-- Note: that you should have header "Transfer-Encoding: chunked"
require
a_content_not_empty: a_content /= Void and then not a_content.is_empty
message_writable: message_writable
valid_chunk_extension: (a_ext /= Void and then not a_ext.is_empty) implies
( a_ext.starts_with (";") and not a_ext.has ('%N') and not not a_ext.has ('%R') )
local
l_chunk_size_line: STRING_8
i: INTEGER
do
--| Remove all left '0'
l_chunk_size_line := a_content.count.to_hex_string
from
i := 1
until
l_chunk_size_line[i] /= '0'
loop
i := i + 1
end
if i > 1 then
l_chunk_size_line := l_chunk_size_line.substring (i, l_chunk_size_line.count)
end
if a_ext /= Void then
l_chunk_size_line.append (a_ext)
end
l_chunk_size_line.append ({HTTP_CONSTANTS}.crlf)
wgi_response.put_string (l_chunk_size_line)
put_string (a_content)
wgi_response.put_string ({HTTP_CONSTANTS}.crlf)
flush
ensure
transfered_content_length = old transfered_content_length + a_content.count.to_natural_64
end
put_custom_chunk_end (a_ext: detachable READABLE_STRING_8; a_trailer: detachable READABLE_STRING_8)
-- Put end of chunked content,
-- with optional extension `a_ext': chunk-extension= *( ";" chunk-ext-name [ "=" chunk-ext-val ] )
-- and with optional trailer `a_trailer' : trailer= *(entity-header CRLF)
local
l_chunk_size_line: STRING_8
do
-- Chunk end
create l_chunk_size_line.make (1)
l_chunk_size_line.append_integer (0)
if a_ext /= Void then
l_chunk_size_line.append (a_ext)
end
l_chunk_size_line.append ({HTTP_CONSTANTS}.crlf)
wgi_response.put_string (l_chunk_size_line)
-- Optional trailer
if a_trailer /= Void and then not a_trailer.is_empty then
wgi_response.put_string (a_trailer)
end
-- Final CRLF
wgi_response.put_string ({HTTP_CONSTANTS}.crlf)
flush
end
put_chunk_end
-- Put end of chunked content
-- without any optional trailer.
do
put_custom_chunk_end (Void, Void)
end
flush
-- Flush if it makes sense
do
wgi_response.flush
end
feature -- Response object
send (a_message: WSF_RESPONSE_MESSAGE)
-- Set `a_message' as the whole response to the client
--| `a_message' is responsible to sent the status code, the header and the content
require
header_not_committed: not header_committed
status_not_committed: not status_committed
no_message_committed: not message_committed
do
a_message.send_to (Current)
ensure
status_committed: status_is_set
end
feature -- Redirect
redirect_now_custom (a_url: READABLE_STRING_8; a_status_code: INTEGER; a_header: detachable HTTP_HEADER; a_content: detachable TUPLE [body: READABLE_STRING_8; type: READABLE_STRING_8])
-- Redirect to the given url `a_url' and precise custom `a_status_code', custom header and content
-- Please see http://www.faqs.org/rfcs/rfc2616 to use proper status code.
-- if `a_status_code' is 0, use the default {HTTP_STATUS_CODE}.temp_redirect
require
header_not_committed: not header_committed
local
h: HTTP_HEADER
b: READABLE_STRING_8
do
if header_committed then
-- This might be a trouble about content-length
put_string ("Headers already sent.%NCannot redirect, for now please follow this <a %"href=%"" + a_url + "%">link</a> instead%N")
else
if a_header /= Void then
create h.make_from_header (a_header)
else
create h.make_with_count (1)
end
h.put_location (a_url)
if a_status_code = 0 then
if not status_is_set then
set_status_code ({HTTP_STATUS_CODE}.temp_redirect)
end
else
set_status_code (a_status_code)
end
if a_content /= Void then
b := a_content.body
h.put_content_length (b.count)
h.put_content_type (a_content.type)
put_header_text (h.string)
put_string (b)
else
h.put_content_length (0)
put_header_text (h.string)
end
end
end
redirect_now (a_url: READABLE_STRING_8)
-- Redirect to the given url `a_url'
require
header_not_committed: not header_committed
do
redirect_now_custom (a_url, {HTTP_STATUS_CODE}.found, Void, Void)
end
redirect_now_with_content (a_url: READABLE_STRING_8; a_content: READABLE_STRING_8; a_content_type: READABLE_STRING_8)
-- Redirect to the given url `a_url'
do
redirect_now_custom (a_url, {HTTP_STATUS_CODE}.found, Void, [a_content, a_content_type])
end
feature -- Error reporting
put_error (a_message: READABLE_STRING_8)
-- Report error described by `a_message'
-- This might be used by the underlying connector
do
wgi_response.put_error (a_message)
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