class FCGI_SERVLET_RESPONSE
Fcgi servlet response
note
description: "Fcgi servlet response"
author: "Finnian Reilly"
copyright: "Copyright (c) 2001-2022 Finnian Reilly"
contact: "finnian at eiffel hyphen loop dot com"
license: "MIT license (See: en.wikipedia.org/wiki/MIT_License)"
date: "2024-11-07 12:31:45 GMT (Thursday 7th November 2024)"
revision: "37"
class
FCGI_SERVLET_RESPONSE
inherit
SINGLE_MATH
FCGI_SHARED_HEADER
EL_SHARED_DOCUMENT_TYPES; EL_SHARED_HTTP_STATUS; EL_SHARED_STRING_8_BUFFER_POOL
EL_SHARED_UTF_8_ZCODEC
EL_SHARED_DOCUMENT_TYPES
export
{ANY} Text_type
end
EL_STRING_8_CONSTANTS
EL_ENCODING_TYPE
export
{NONE} all
end
create
make
feature {NONE}-- Initialization
make (a_broker: FCGI_REQUEST_BROKER)
-- Build a new Fast CGI response object that provides access to
-- 'broker' information.
-- Initialise the response information to allow a successful (Sc_ok) response
-- to be sent immediately.
do
broker := a_broker
create cookie_list.make (5)
create header_list.make (5)
reset
end
feature -- Access
content_length: INTEGER
-- the sent header length for `content'
content_type: EL_DOC_TYPE
socket_error: STRING
-- A string describing the socket error which occurred
do
Result := broker.socket_error
end
status: NATURAL_16
-- The result status that will be send with this response.
status_message: STRING
-- The status message
do
create Result.make (30)
Result.append_natural_16 (status)
Result.append_character (' ')
Result.append (Http_status.name (status))
end
feature -- Status query
is_head_request: BOOLEAN
do
Result := broker.is_head_request
end
is_encoded: BOOLEAN
-- `True' if `content' is already encoded as `content_type.encoding'
is_sent: BOOLEAN
-- `True' when response has already had it's status code and headers written.
write_ok: BOOLEAN
-- Was there a problem sending the data to the client?
feature -- Contract Support
is_valid_utf_8_string_8 (s: READABLE_STRING_8): BOOLEAN
local
c: EL_UTF_CONVERTER
do
Result := c.is_valid_utf_8_string_8 (s)
end
is_string_8_content: BOOLEAN
do
Result := attached {READABLE_STRING_8} content
end
feature -- Basic operations
send
-- send response headers and content
local
buffer, content_buffer: STRING
do
if not is_sent then
content_buffer := encoded_content
set_header (Header.status, status_message)
set_header (Header.content_length, content_buffer.count.out)
set_header (Header.content_type, content_type.specification)
set_header (Header.x_powered_by, Powered_by)
if status = Http_status.ok then
set_cookie_headers
end
if attached String_8_pool.biggest_item as borrowed then
buffer := borrowed.empty
header_list.sort_by_key (True)
if attached header_list as list then
from list.start until list.after loop
buffer.append (Header.name (list.item_key)); buffer.append (Colon_space)
buffer.append (list.item_value)
buffer.append (Carriage_return_new_line)
list.forth
end
end
buffer.append (Carriage_return_new_line) -- This is required even for HEAD requests
if not is_head_request then
buffer.append (content_buffer)
end
broker.write_stdout (buffer)
borrowed.return
end
write_ok := broker.write_ok
if write_ok then
content_length := content_buffer.count
end
is_sent := True
end
end
send_error (sc: like status; message: READABLE_STRING_GENERAL; doc_type: NATURAL_8; encoding: NATURAL)
-- Send an error response to the client using the specified
-- status code and descriptive message. The server generally
-- creates the response to look like a normal server error page.
require
valid_type_and_encoding: Text_type.valid_value (doc_type)
local
code_name: IMMUTABLE_STRING_8; html: ZSTRING
do
code_name := Http_status.name (sc)
content_type := document_type (doc_type, encoding)
if doc_type = Text_type.html then
html := Error_page_template #$ [code_name, code_name, message]
if content_type.encoding.is_utf_encoded then
content := html.to_utf_8
else
content := html.to_latin_1
end
else
content := message.to_string_8
end
set_status (sc)
send
end
feature -- Element change
reset
-- Clear any data that exists in the buffer as well as the status code
-- and headers.
do
cookie_list.wipe_out
header_list.wipe_out
content := Empty_string_8
content_type := document_type (Text_type.plain, Latin_1)
content_length := 0
set_status (Http_status.ok)
write_ok := False
is_sent := False
end
send_as_cookies (object: EL_COOKIE_SETTABLE)
do
send_cookies (object.cookie_list)
end
send_cookie (name, value: STRING)
do
cookie_list.extend (create {EL_HTTP_COOKIE}.make (name, value))
end
send_cookies (a_cookie_list: ITERABLE [EL_HTTP_COOKIE])
do
across a_cookie_list as cookie loop
cookie_list.extend (cookie.item)
end
end
set_encoded_content (text: READABLE_STRING_8; type: EL_DOC_TYPE)
-- set 8-bit content that is already encode as `type.encoding'
require
valid_utf_8: type.is_utf_8_encoded implies is_valid_utf_8_string_8 (text)
do
content := text; content_type := type
is_encoded := True
end
set_content (text: READABLE_STRING_GENERAL; doc_type: NATURAL_8; encoding: NATURAL)
require
valid_type_and_encoding: Text_type.valid_value (doc_type)
do
if text.is_string_8 and then encoding = Latin_1
and then attached {READABLE_STRING_8} text as encoded_text
then
set_encoded_content (encoded_text, document_type (doc_type, encoding))
else
content := text; set_content_type (document_type (doc_type, encoding))
is_encoded := False
end
end
set_content_ok
do
content_type := document_type (Text_type.plain, Latin_1)
content := once "OK"
end
set_content_type (type: EL_DOC_TYPE)
-- set content type of the response being sent to the client.
do
content_type := type
end
set_header (header_enum: NATURAL_8; value: STRING)
-- Set a response header with the given name and value. If the
-- header already exists, the new value overwrites the previous
-- one.
do
header_list.set_key_item (header_enum, value)
end
set_status (a_status: like status)
-- Set the status code for this response. This method is used to
-- set the return status code when there is no error (for example,
-- for the status codes Sc_ok or Sc_moved_temporarily). If there
-- is an error, the 'send_error' method should be used instead.
do
status := a_status
end
feature {NONE} -- Implementation
add_header (header_enum: NATURAL_8; value: STRING)
-- Adds a response header with the given `header_enum' and value. This
-- method allows response headers to have multiple values.
do
header_list.extend (header_enum, value)
end
encoded_content: STRING
local
buffer: like Encoding_buffer
do
if is_encoded and then attached {READABLE_STRING_8} content as encoded then
Result := encoded
else
buffer := Encoding_buffer
buffer.wipe_out
buffer.set_encoding_from_other (content_type.encoding)
buffer.put_string_general (content)
Result := buffer.text
end
end
set_cookie_headers
-- Add 'Set-Cookie' header for cookies. Add a separate 'Set-Cookie' header
-- for each new cookie.
-- Also add cookie caching directive headers.
do
if not cookie_list.is_empty then
across cookie_list as cookie loop
add_header (Header.set_cookie, cookie.item.header_string)
end
-- add cache control headers for cookie management
add_header (Header.cache_control, once "no-cache=%"Set-Cookie%"")
set_header (Header.expires, Expired_date)
end
end
feature {FCGI_SERVLET_REQUEST} -- Internal attributes
broker: FCGI_REQUEST_BROKER
-- broker to read and write request messages from the web server
content: READABLE_STRING_GENERAL
feature {NONE} -- Internal attributes
cookie_list: ARRAYED_LIST [EL_HTTP_COOKIE]
-- The cookie_list that will be sent with this response.
header_list: EL_ARRAYED_MAP_LIST [NATURAL_8, STRING]
-- list of mappings: header enumeration code -> value
feature {NONE} -- Constants
Carriage_return_new_line: STRING = "%R%N"
Colon_space: STRING = ": "
Encoding_buffer: EL_STRING_8_IO_MEDIUM
once
create Result.make (0)
end
Error_page_template: ZSTRING
once
Result := "[
<html>
<head>
<title>#</title>
</head>
<body>
<center><h1>#</h1></center>
<p>#</p>
</body>
</html>
]"
end
Expired_date: STRING = "Tue, 01-Jan-1970 00:00:00 GMT"
Powered_by: STRING = "Eiffel-Loop Fast-CGI servlets"
end