Policy-driven URI template handlers

This commit is contained in:
Colin Adams
2013-08-06 13:57:12 +01:00
parent 0a9d208529
commit 8dbd24afd1
8 changed files with 1084 additions and 26 deletions

View File

@@ -0,0 +1,125 @@
note
description: "[
Policies for determing caching of responses.
]"
date: "$Date$"
revision: "$Revision$"
deferred class WSF_CACHING_POLICY
feature -- Access
age (req: WSF_REQUEST): NATURAL
-- Maximum age in seconds before response to `req` is considered stale;
-- This is used to generate a Cache-Control: max-age header.
-- Return 0 to indicate already expired.
-- Return (365 * 1440 = 1 year) to indicate never expires.
require
req_attached: req /= Void
deferred
ensure
not_more_than_1_year: Result <= (365 * 1440).as_natural_32
end
shared_age (req: WSF_REQUEST): NATURAL
-- Maximum age in seconds before response to `req` is considered stale in a shared cache;
-- This is used to generate a Cache-Control: s-maxage header.
-- If you wish to have different expiry ages for shared and provate caches, redefine this routine.
-- Return 0 to indicate already expired.
-- Return (365 * 1440 = 1 year) to indicate never expires.
require
req_attached: req /= Void
do
Result := age (req)
ensure
not_more_than_1_year: Result <= (365 * 1440).as_natural_32
end
http_1_0_age (req: WSF_REQUEST): NATURAL
-- Maximum age in seconds before response to `req` is considered stale;
-- This is used to generate an Expires header, which HTTP/1.0 caches understand.
-- If you wish to generate a different age for HTTP/1.0 caches, then redefine this routine.
-- Return 0 to indicate already expired.
-- Return (365 * 1440 = 1 year) to indicate never expires. Note this will
-- make a result cachecable that would not normally be cacheable (such as as response
-- to a POST), unless overriden by cache-control headers, so be sure to check `req.request_method'.
require
req_attached: req /= Void
do
Result := age (req)
ensure
not_more_than_1_year: Result <= (365 * 1440).as_natural_32
end
is_freely_cacheable (req: WSF_REQUEST): BOOLEAN
-- Should the response to `req' be freely cachable in shared caches?
-- If `True', then a Cache-Control: public header will be generated.
require
req_attached: req /= Void
deferred
end
is_transformable (req: WSF_REQUEST): BOOLEAN
-- Should a non-transparent proxy be allowed to modify headers of response to `req`?
-- The headers concerned are listed in http://www.w3.org/Protocols/rfc2616-sec14.html#sec14,9.
-- If `False' then a Cache-Control: no-transorm header will be generated.
require
req_attached: req /= Void
do
-- We choose a conservative default. But most applications can
-- redefine to return `True'.
end
must_revalidate (req: WSF_REQUEST): BOOLEAN
-- If a client has requested, or a cache is configured, to ignore server's expiration time,
-- should we force revalidation anyway?
-- If `True' then a Cache-Control: must-revalidate header will be generated.
require
req_attached: req /= Void
do
-- Redefine to force revalidation.
end
must_proxy_revalidate (req: WSF_REQUEST): BOOLEAN
-- If a shared cache is configured to ignore server's expiration time,
-- should we force revalidation anyway?
-- If `True' then a Cache-Control: proxy-revalidate header will be generated.
require
req_attached: req /= Void
do
-- Redefine to force revalidation.
end
private_headers (req: WSF_REQUEST): detachable LIST [READABLE_STRING_8]
-- Header names intended for a single user.
-- If non-Void, then a Cache-Control: private header will be generated.
-- Returning an empty list prevents the entire response from being served from a shared cache.
require
req_attached: req /= Void
deferred
ensure
not_freely_cacheable: Result /= Void implies not is_freely_cacheable (req)
end
non_cacheable_headers (req: WSF_REQUEST): detachable LIST [READABLE_STRING_8]
-- Header names that will not be sent from a cache without revalidation;
-- If non-Void, then a Cache-Control: no-cache header will be generated.
-- Returning an empty list prevents the response being served from a cache
-- without revalidation.
require
req_attached: req /= Void
deferred
end
is_sensitive (req: WSF_REQUEST): BOOLEAN
-- Is the response to `req' of a sensitive nature?
-- If `True' then a Cache-Control: no-store header will be generated.
require
req_attached: req /= Void
deferred
end
end

View File

@@ -0,0 +1,60 @@
note
description: "[
Policy-driven helpers to implement the DELETE method.
]"
date: "$Date$"
revision: "$Revision$"
class WSF_DELETE_HELPER
inherit
WSF_METHOD_HELPER
rename
send_get_response as handle_delete
redefine
handle_delete
end
feature {NONE} -- Implementation
handle_delete (req: WSF_REQUEST; res: WSF_RESPONSE; a_handler: WSF_SKELETON_HANDLER; a_header: HTTP_HEADER;
a_media_type, a_language_type, a_character_type, a_compression_type: detachable READABLE_STRING_8; a_new_resource: BOOLEAN)
-- Write response to deletion of resource named by `req' into `res' in accordance with `a_media_type' etc.
local
l_dt: STRING
do
a_handler.delete (req)
if a_handler.includes_response_entity (req) then
a_handler.ensure_content_available (req, a_media_type, a_language_type, a_character_type, a_compression_type)
a_header.put_content_length (a_handler.content_length (req).as_integer_32)
-- we don't bother supporting chunked responses for DELETE.
else
a_header.put_content_length (0)
end
if attached req.request_time as l_time then
l_dt := (create {HTTP_DATE}.make_from_date_time (l_time)).rfc1123_string
a_header.put_header_key_value ({HTTP_HEADER_NAMES}.header_date, l_dt)
generate_cache_headers (req, a_handler, a_header, l_time)
end
if a_handler.delete_queued (req) then
res.set_status_code ({HTTP_STATUS_CODE}.accepted)
res.put_header_text (a_header.string)
res.put_string (a_handler.content (req, a_media_type, a_language_type, a_character_type, a_compression_type))
elseif a_handler.deleted (req) then
if a_handler.includes_response_entity (req) then
res.set_status_code ({HTTP_STATUS_CODE}.ok)
res.put_header_text (a_header.string)
res.put_string (a_handler.content (req, a_media_type, a_language_type, a_character_type, a_compression_type))
else
res.set_status_code ({HTTP_STATUS_CODE}.no_content)
res.put_header_text (a_header.string)
end
else
-- TODO - req.error_handler.has_error = True
--handle_internal_server_error (a_handler.last_error (req), req, res)
end
end
end

View File

@@ -2,11 +2,12 @@ note
description: "[ description: "[
Policy-driven helpers to implement a method. Policy-driven helpers to implement a method.
This implementation is suitable for GET and HEAD.
]" ]"
date: "$Date$" date: "$Date$"
revision: "$Revision$" revision: "$Revision$"
deferred class WSF_METHOD_HELPER class WSF_METHOD_HELPER
feature -- Access feature -- Access
@@ -34,24 +35,37 @@ feature -- Basic operations
req_attached: req /= Void req_attached: req /= Void
res_attached: res /= Void res_attached: res /= Void
a_handler_attached: a_handler /= Void a_handler_attached: a_handler /= Void
local
l_locs: LIST [URI]
h: HTTP_HEADER
do do
if a_handler.resource_previously_existed (req) then if a_handler.resource_previously_existed (req) then
--| TODO - should we be passing the entire request, or should we further
--| simplify the programmer's task by passing `req.path_translated'?
if a_handler.resource_moved_permanently (req) then if a_handler.resource_moved_permanently (req) then
-- TODO 301 Moved Permanently l_locs := a_handler.previous_location (req)
-- TODO 301 Moved permanently response
elseif a_handler.resource_moved_temporarily (req) then elseif a_handler.resource_moved_temporarily (req) then
-- TODO 302 Found l_locs := a_handler.previous_location (req)
-- TODO := 302 Found response
else else
-- TODO 410 Gone create h.make
h.put_content_type_text_plain
h.put_current_date
h.put_content_length (0)
res.set_status_code ({HTTP_STATUS_CODE}.gone)
res.put_header_text (h.string)
end end
else else
-- TODO 404 Not Found create h.make
h.put_content_type_text_plain
h.put_current_date
h.put_content_length (0)
res.set_status_code ({HTTP_STATUS_CODE}.not_found)
res.put_header_text (h.string)
end end
end end
execute_existing_resource (req: WSF_REQUEST; res: WSF_RESPONSE; a_handler: WSF_SKELETON_HANDLER) execute_existing_resource (req: WSF_REQUEST; res: WSF_RESPONSE; a_handler: WSF_SKELETON_HANDLER)
-- Write response to existing resource requested by `req.' into `res'. -- Write response to existing resource requested by `req' into `res'.
-- Policy routines are available in `a_handler'. -- Policy routines are available in `a_handler'.
require require
req_attached: req /= Void req_attached: req /= Void
@@ -59,22 +73,347 @@ feature -- Basic operations
a_handler_attached: a_handler /= Void a_handler_attached: a_handler /= Void
not_if_match_star: attached req.http_if_match as l_if_match implies not l_if_match.same_string ("*") not_if_match_star: attached req.http_if_match as l_if_match implies not l_if_match.same_string ("*")
local local
l_etags: LIST [READABLE_STRING_32] l_etags: LIST [READABLE_STRING_8]
l_failed: BOOLEAN
l_date: HTTP_DATE
do do
if attached req.http_if_match as l_if_match then if attached req.http_if_match as l_if_match then
-- TODO - also if-range when we add support for range requests
if not l_if_match.same_string ("*") then
l_etags := l_if_match.split (',') l_etags := l_if_match.split (',')
l_failed := not across l_etags as i_etags some a_handler.matching_etag (req, i_etags.item, True) end
end
end
if l_failed then
handle_precondition_failed (req, res)
else else
-- TODO: check_if_unmodified_since (req, res, a_handler) if attached req.http_if_unmodified_since as l_if_unmodified_since then
if l_if_unmodified_since.is_string_8 then
create l_date.make_from_string (l_if_unmodified_since.as_string_8)
if not l_date.has_error then
if a_handler.modified_since (req, l_date.date_time) then
handle_precondition_failed (req, res)
l_failed := True
end
end
end
end
if not l_failed then
if attached req.http_if_none_match as l_if_none_match then
l_etags := l_if_none_match.split (',')
l_failed := l_if_none_match.same_string ("*") or
across l_etags as i_etags some a_handler.matching_etag (req, i_etags.item, False) end
end
if l_failed then
handle_if_none_match_failed (req, res)
else
if attached req.http_if_modified_since as l_if_modified_since then
if l_if_modified_since.is_string_8 then
create l_date.make_from_string (l_if_modified_since.as_string_8)
if not l_date.has_error then
if not a_handler.modified_since (req, l_date.date_time) then
handle_not_modified (req, res)
l_failed := True
end
end
end
end
end
if not l_failed then
handle_content_negotiation (req, res, a_handler, False)
end
end
end end
end end
handle_precondition_failed (req: WSF_REQUEST; res: WSF_RESPONSE) feature {NONE} -- Implementation
--
handle_content_negotiation (req: WSF_REQUEST; res: WSF_RESPONSE;
a_handler: WSF_SKELETON_HANDLER; a_new_resource: BOOLEAN)
-- Negotiate acceptable content for, then write, response requested by `req' into `res'.
-- Policy routines are available in `a_handler'.
-- This default version applies to GET and HEAD.
require
req_attached: req /= Void
res_attached: res /= Void
a_handler_attached: a_handler /= Void
local
l_conneg: CONNEG_SERVER_SIDE
h: HTTP_HEADER
l_media: MEDIA_TYPE_VARIANT_RESULTS
l_lang: LANGUAGE_VARIANT_RESULTS
l_charset: CHARACTER_ENCODING_VARIANT_RESULTS
l_encoding: COMPRESSION_VARIANT_RESULTS
l_mime_types, l_langs, l_charsets, l_encodings: LIST [STRING]
l_vary_star: BOOLEAN
do
create h.make
l_vary_star := not a_handler.predictable_response (req)
if l_vary_star then
h.add_header_key_value ({HTTP_HEADER_NAMES}.header_vary, "*")
elseif attached a_handler.additional_variant_headers (req) as l_additional then
across l_additional as i_additional loop
h.add_header_key_value ({HTTP_HEADER_NAMES}.header_vary, i_additional.item)
end
end
l_conneg := a_handler.conneg (req)
l_mime_types := a_handler.mime_types_supported (req)
l_media := l_conneg.media_type_preference (l_mime_types, req.http_accept)
if not l_vary_star and l_mime_types.count > 1 and attached l_media.variant_header as l_media_variant then
h.add_header_key_value ({HTTP_HEADER_NAMES}.header_vary, l_media_variant)
end
if not l_media.is_acceptable then
handle_not_accepted ("None of the requested ContentTypes were acceptable", req, res)
else
l_langs := a_handler.languages_supported (req)
l_lang := l_conneg.language_preference (l_langs, req.http_accept_language)
if not l_vary_star and l_langs.count > 1 and attached l_lang.variant_header as l_lang_variant then
h.add_header_key_value ({HTTP_HEADER_NAMES}.header_vary, l_lang_variant)
end
if not l_lang.is_acceptable then
handle_not_accepted ("None of the requested languages were acceptable", req, res)
else
if attached l_lang.language_type as l_language_type then
h.put_content_language (l_language_type)
end
l_charsets := a_handler.charsets_supported (req)
l_charset := l_conneg.charset_preference (l_charsets, req.http_accept_charset)
if not l_vary_star and l_charsets.count > 1 and attached l_charset.variant_header as l_charset_variant then
h.add_header_key_value ({HTTP_HEADER_NAMES}.header_vary, l_charset_variant)
end
if not l_charset.is_acceptable then
handle_not_accepted ("None of the requested character encodings were acceptable", req, res)
else
if attached l_media.media_type as l_media_type and attached l_charset.character_type as l_character_type then
h.put_content_type (l_media_type + "; charset=" + l_character_type)
end
l_encodings := a_handler.encodings_supported (req)
l_encoding := l_conneg.encoding_preference (l_encodings, req.http_accept_encoding)
if not l_vary_star and l_encodings.count > 1 and attached l_encoding.variant_header as l_encoding_variant then
h.add_header_key_value ({HTTP_HEADER_NAMES}.header_vary, l_encoding_variant)
end
if not l_encoding.is_acceptable then
handle_not_accepted ("None of the requested transfer encodings were acceptable", req, res)
else
if attached l_encoding.compression_type as l_compression_type then
h.put_content_encoding (l_compression_type)
end
-- We do not support multiple choices, so
send_get_response (req, res, a_handler, h,
l_media.media_type, l_lang.language_type, l_charset.character_type, l_encoding.compression_type, a_new_resource)
end
end
end
end
end
send_get_response (req: WSF_REQUEST; res: WSF_RESPONSE; a_handler: WSF_SKELETON_HANDLER; a_header: HTTP_HEADER;
a_media_type, a_language_type, a_character_type, a_compression_type: detachable READABLE_STRING_8; a_new_resource: BOOLEAN)
-- Write response to `req' into `res' in accordance with `a_media_type' etc.
require
req_attached: req /= Void
res_attached: res /= Void
a_handler_attached: a_handler /= Void
a_header_attached: a_header /= Void
a_media_type_attached: a_media_type /= Void
a_language_type_attached: a_language_type /= Void
a_character_type_attached: a_character_type /= Void
a_compression_type_attached: a_compression_type /= Void
local
l_chunked, l_ok: BOOLEAN
l_dt: STRING
do
a_handler.ensure_content_available (req, a_media_type, a_language_type, a_character_type, a_compression_type)
l_chunked := a_handler.is_chunking (req)
if l_chunked then
a_header.put_transfer_encoding_chunked
else
a_header.put_content_length (a_handler.content_length (req).as_integer_32)
end
if attached req.request_time as l_time then
l_dt := (create {HTTP_DATE}.make_from_date_time (l_time)).rfc1123_string
a_header.put_header_key_value ({HTTP_HEADER_NAMES}.header_date, l_dt)
generate_cache_headers (req, a_handler, a_header, l_time)
end
l_ok := a_handler.response_ok (req)
if l_ok then
res.set_status_code ({HTTP_STATUS_CODE}.ok)
else
-- TODO - req.error_handler.has_error = True
--handle_internal_server_error (a_handler.last_error (req), req, res)
end
if attached a_handler.etag (req, a_media_type, a_language_type, a_character_type, a_compression_type) as l_etag then
a_header.put_header_key_value ({HTTP_HEADER_NAMES}.header_etag, l_etag)
end
res.put_header_text (a_header.string)
if l_ok then
if l_chunked then
send_chunked_response (req, res, a_handler, a_header, a_media_type, a_language_type, a_character_type, a_compression_type)
else
res.put_string (a_handler.content (req, a_media_type, a_language_type, a_character_type, a_compression_type))
end
end
end
send_chunked_response (req: WSF_REQUEST; res: WSF_RESPONSE; a_handler: WSF_SKELETON_HANDLER; a_header: HTTP_HEADER;
a_media_type, a_language_type, a_character_type, a_compression_type: READABLE_STRING_8)
-- Write response in chunks to `req' into `res' in accordance with `a_media_type' etc.
require
req_attached: req /= Void
res_attached: res /= Void
a_handler_attached: a_handler /= Void
a_media_type_attached: a_media_type /= Void
a_language_type_attached: a_language_type /= Void
a_character_type_attached: a_character_type /= Void
a_compression_type_attached: a_compression_type /= Void
local
l_chunk: TUPLE [a_chunk: READABLE_STRING_8; a_extension: detachable READABLE_STRING_8]
do
from
if a_handler.response_ok (req) then
l_chunk := a_handler.next_chunk (req, a_media_type, a_language_type, a_character_type, a_compression_type)
res.put_chunk (l_chunk.a_chunk, l_chunk.a_extension)
else
-- TODO - req.error_handler.has_error = True
-- handle_internal_server_error (a_handler.last_error (req), req, res)
end
until
a_handler.finished (req) or not a_handler.response_ok (req)
loop
a_handler.generate_next_chunk (req, a_media_type, a_language_type, a_character_type, a_compression_type)
if a_handler.response_ok (req) then
l_chunk := a_handler.next_chunk (req, a_media_type, a_language_type, a_character_type, a_compression_type)
res.put_chunk (l_chunk.a_chunk, l_chunk.a_extension)
else
-- TODO - req.error_handler.has_error = True
-- handle_internal_server_error (a_handler.last_error (req), req, res)
end
end
if a_handler.finished (req) then
-- TODO - support for trailers
res.put_chunk_end
end
end
generate_cache_headers (req: WSF_REQUEST; a_handler: WSF_SKELETON_HANDLER; a_header: HTTP_HEADER; a_request_time: DATE_TIME)
-- Write headers affecting caching for `req' into `a_header'.
require
req_attached: req /= Void
a_handler_attached: a_handler /= Void
a_header_attached: a_header /= Void
a_request_time_attached: a_request_time /= Void
local
l_age, l_age_1, l_age_2: NATURAL
l_dur: DATE_TIME_DURATION
l_dt, l_field_names: STRING
do
l_age := a_handler.http_1_0_age (req)
create l_dur.make (0, 0, 0, 0, 0, l_age.as_integer_32)
l_dt := (create {HTTP_DATE}.make_from_date_time (a_request_time + l_dur)).rfc1123_string
a_header.put_header_key_value ({HTTP_HEADER_NAMES}.header_expires, l_dt)
l_age_1 := a_handler.age (req)
if l_age_1 /= l_age then
a_header.add_header_key_value ({HTTP_HEADER_NAMES}.header_cache_control, "max-age=" + l_age_1.out)
end
l_age_2 := a_handler.shared_age (req)
if l_age_2 /= l_age_1 then
a_header.add_header_key_value ({HTTP_HEADER_NAMES}.header_cache_control, "s-maxage=" + l_age_2.out)
end
if a_handler.is_freely_cacheable (req) then
a_header.add_header_key_value ({HTTP_HEADER_NAMES}.header_cache_control, "public")
elseif attached a_handler.private_headers (req) as l_fields then
l_field_names := "="
if not l_fields.is_empty then
append_field_name (l_field_names, l_fields)
end
a_header.add_header_key_value ({HTTP_HEADER_NAMES}.header_cache_control, "private" + l_field_names)
end
if attached a_handler.non_cacheable_headers (req) as l_fields then
l_field_names := "="
if not l_fields.is_empty then
append_field_name (l_field_names, l_fields)
end
a_header.add_header_key_value ({HTTP_HEADER_NAMES}.header_cache_control, "no-cache" + l_field_names)
end
if not a_handler.is_transformable (req) then
a_header.add_header_key_value ({HTTP_HEADER_NAMES}.header_cache_control, "no-transform")
end
if a_handler.must_revalidate (req) then
a_header.add_header_key_value ({HTTP_HEADER_NAMES}.header_cache_control, "must-revalidate")
end
if a_handler.must_proxy_revalidate (req) then
a_header.add_header_key_value ({HTTP_HEADER_NAMES}.header_cache_control, "proxy-revalidate")
end
end
append_field_name (a_field_names: STRING; a_fields: LIST [READABLE_STRING_8])
-- Append all of `a_fields' as a comma-separated list to `a_field_names'.
require
a_field_names_attached: a_field_names /= Void
a_fields_attached: a_fields /= Void
do
across a_fields as i_fields loop
a_field_names.append_string (i_fields.item)
if not i_fields.is_last then
a_field_names.append_character (',')
end
end
end
feature -- Basic operations
handle_not_accepted (a_message: READABLE_STRING_8; req: WSF_REQUEST; res: WSF_RESPONSE)
-- Write a Not Accepted response to `res'.
require
req_attached: req /= Void
res_attached: res /= Void
a_message_attached: a_message /= Void
do
-- TODO: flag this if it gets to code review.
end
handle_if_none_match_failed (req: WSF_REQUEST; res: WSF_RESPONSE)
-- Write a Precondition Failed response to `res'.
require require
req_attached: req /= Void req_attached: req /= Void
res_attached: res /= Void res_attached: res /= Void
do do
-- TODO: flag this if it gets to code review. Why not just handle_precondition_failed?
end end
handle_not_modified (req: WSF_REQUEST; res: WSF_RESPONSE)
-- Write a Precondition Failed response to `res'.
require
req_attached: req /= Void
res_attached: res /= Void
do
-- TODO: flag this if it gets to code review. Why not just handle_precondition_failed?
end
handle_precondition_failed (req: WSF_REQUEST; res: WSF_RESPONSE)
-- Write a precondition failed response for `req' to `res'.
require
req_attached: req /= Void
res_attached: res /= Void
local
h: HTTP_HEADER
do
create h.make
h.put_content_type_text_plain
h.put_current_date
h.put_content_length (0)
res.set_status_code ({HTTP_STATUS_CODE}.precondition_failed)
res.put_header_text (h.string)
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 end

View File

@@ -18,7 +18,13 @@ feature -- Factory
do do
if a_method.is_case_insensitive_equal ({HTTP_REQUEST_METHODS}.method_get) or if a_method.is_case_insensitive_equal ({HTTP_REQUEST_METHODS}.method_get) or
a_method.is_case_insensitive_equal ({HTTP_REQUEST_METHODS}.method_head) then a_method.is_case_insensitive_equal ({HTTP_REQUEST_METHODS}.method_head) then
create {WSF_GET_HELPER} Result create Result
elseif a_method.is_case_insensitive_equal ({HTTP_REQUEST_METHODS}.method_put) then
create {WSF_PUT_HELPER} Result
elseif a_method.is_case_insensitive_equal ({HTTP_REQUEST_METHODS}.method_post) then
create {WSF_POST_HELPER} Result
elseif a_method.is_case_insensitive_equal ({HTTP_REQUEST_METHODS}.method_delete) then
create {WSF_DELETE_HELPER} Result
end end
end end

View File

@@ -0,0 +1,70 @@
note
description: "[
Policy-driven helpers to implement POST.
]"
date: "$Date$"
revision: "$Revision$"
class WSF_POST_HELPER
inherit
WSF_METHOD_HELPER
rename
send_get_response as do_post
redefine
execute_new_resource,
do_post
end
feature -- Basic operations
execute_new_resource (req: WSF_REQUEST; res: WSF_RESPONSE; a_handler: WSF_SKELETON_HANDLER)
-- Write response to non-existing resource requested by `req.' into `res'.
-- Policy routines are available in `a_handler'.
local
l_etags: LIST [READABLE_STRING_32]
l_failed: BOOLEAN
do
if a_handler.allow_post_to_missing_resource (req) then
handle_content_negotiation (req, res, a_handler, True)
else
-- TODO 404 Not Found
end
end
feature {NONE} -- Implementation
do_post (req: WSF_REQUEST; res: WSF_RESPONSE; a_handler: WSF_SKELETON_HANDLER; a_header: HTTP_HEADER;
a_media_type, a_language_type, a_character_type, a_compression_type: detachable READABLE_STRING_8; a_new_resource: BOOLEAN)
-- Write response to `req' into `res' in accordance with `a_media_type' etc. as a new URI.
do
a_handler.read_entity (req)
if a_handler.is_entity_too_large (req) then
handle_request_entity_too_large (req, res, a_handler)
else
a_handler.check_content_headers (req)
if a_handler.content_check_code (req) /= 0 then
-- TODO - 415 or 501
else
a_handler.check_request (req, res)
if a_handler.request_check_code (req) = 0 then
a_handler.append_resource (req, res)
-- 200 or 204 or 303 or 500 (add support for this?)
-- TODO: more support, such as includes_response_entity
end
end
end
end
handle_request_entity_too_large (req: WSF_REQUEST; res: WSF_RESPONSE; a_handler: WSF_SKELETON_HANDLER)
-- TODO.
require
-- TODO
do
-- Need to check if condition is temporary.
end
end

View File

@@ -7,11 +7,11 @@ note
date: "$Date$" date: "$Date$"
revision: "$Revision$" revision: "$Revision$"
class WSF_PREVIOUS_POLICY deferred class WSF_PREVIOUS_POLICY
feature -- Access feature -- Access
resource_previously_existed (req: WSF_REQUEST) : BOOLEAN resource_previously_existed (req: WSF_REQUEST): BOOLEAN
-- Did `req.path_translated' exist previously? -- Did `req.path_translated' exist previously?
require require
req_attached: req /= Void req_attached: req /= Void
@@ -19,7 +19,7 @@ feature -- Access
-- No. Override if this is not want you want. -- No. Override if this is not want you want.
end end
resource_moved_permanently (req: WSF_REQUEST) : BOOLEAN resource_moved_permanently (req: WSF_REQUEST): BOOLEAN
-- Was `req.path_translated' moved permanently? -- Was `req.path_translated' moved permanently?
require require
req_attached: req /= Void req_attached: req /= Void
@@ -28,7 +28,7 @@ feature -- Access
-- No. Override if this is not want you want. -- No. Override if this is not want you want.
end end
resource_moved_temporarily (req: WSF_REQUEST) : BOOLEAN resource_moved_temporarily (req: WSF_REQUEST): BOOLEAN
-- Was `req.path_translated' moved temporarily? -- Was `req.path_translated' moved temporarily?
require require
req_attached: req /= Void req_attached: req /= Void
@@ -37,4 +37,26 @@ feature -- Access
-- No. Override if this is not want you want. -- No. Override if this is not want you want.
end end
previous_location (req: WSF_REQUEST): LIST [URI]
-- Previous location(s) for resource named by `req';
require
req_attached: req /= Void
previously_existed: resource_previously_existed (req)
moved: resource_moved_permanently (req) or resource_moved_temporarily (req)
deferred
ensure
previous_location_attached: Result /= Void
non_empty_list: not Result.empty
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 end

View File

@@ -0,0 +1,75 @@
note
description: "[
Policy-driven helpers to implement PUT.
]"
date: "$Date$"
revision: "$Revision$"
class WSF_PUT_HELPER
inherit
WSF_METHOD_HELPER
rename
send_get_response as do_put
redefine
execute_new_resource,
do_put
end
feature -- Basic operations
execute_new_resource (req: WSF_REQUEST; res: WSF_RESPONSE; a_handler: WSF_SKELETON_HANDLER)
-- Write response to non-existing resource requested by `req.' into `res'.
-- Policy routines are available in `a_handler'.
do
if a_handler.treat_as_moved_permanently (req) then
-- TODO 301 Moved permanently response (single location)
else
handle_content_negotiation (req, res, a_handler, True)
end
end
feature {NONE} -- Implementation
do_put (req: WSF_REQUEST; res: WSF_RESPONSE; a_handler: WSF_SKELETON_HANDLER; a_header: HTTP_HEADER;
a_media_type, a_language_type, a_character_type, a_compression_type: detachable READABLE_STRING_8; a_new_resource: BOOLEAN)
-- Write response to `req' into `res' in accordance with `a_media_type' etc. as a new URI.
do
a_handler.read_entity (req)
if a_handler.is_entity_too_large (req) then
handle_request_entity_too_large (req, res, a_handler)
else
a_handler.check_content_headers (req)
if a_handler.content_check_code (req) /= 0 then
-- TODO - 415 or 501
else
a_handler.check_request (req, res)
if a_handler.request_check_code (req) = 0 then
if a_new_resource then
a_handler.create_resource (req, res)
-- 201 or 500 (add support for this?)
else
a_handler.check_conflict (req, res)
if a_handler.conflict_check_code (req) = 0 then
a_handler.update_resource (req, res)
-- 204 or 500 (add support for this?)
-- TODO: more support, such as includes_response_entity
end
end
end
end
end
end
handle_request_entity_too_large (req: WSF_REQUEST; res: WSF_RESPONSE; a_handler: WSF_SKELETON_HANDLER)
-- TODO.
require
-- TODO
do
-- Need to check if condition is temporary.
end
end

View File

@@ -20,8 +20,358 @@ inherit
WSF_PREVIOUS_POLICY WSF_PREVIOUS_POLICY
WSF_CACHING_POLICY
WSF_METHOD_HELPER_FACTORY WSF_METHOD_HELPER_FACTORY
feature -- Access
is_chunking (req: WSF_REQUEST): BOOLEAN
-- Will the response to `req' using chunked transfer encoding?
require
req_attached: req /= Void
deferred
end
includes_response_entity (req: WSF_REQUEST): BOOLEAN
-- Does the response to `req' include an entity?
-- Method will be DELETE, OUT, POST or an extension method.
require
req_attached: req /= Void
deferred
end
conneg (req: WSF_REQUEST): CONNEG_SERVER_SIDE
-- Content negotiatior for `req';
-- This would normally be a once object, ignoring `req'.
require
req_attached: req /= Void
deferred
end
mime_types_supported (req: WSF_REQUEST): LIST [STRING]
-- All values for Accept header that `Current' can serve
require
req_attached: req /= Void
deferred
ensure
mime_types_supported_includes_default: Result.has (conneg (req).mime_default)
end
languages_supported (req: WSF_REQUEST): LIST [STRING]
-- All values for Accept-Language header that `Current' can serve
require
req_attached: req /= Void
deferred
ensure
languages_supported_includes_default: Result.has (conneg (req).language_default)
end
charsets_supported (req: WSF_REQUEST): LIST [STRING]
-- All values for Accept-Charset header that `Current' can serve
require
req_attached: req /= Void
deferred
ensure
charsets_supported_includes_default: Result.has (conneg (req).charset_default)
end
encodings_supported (req: WSF_REQUEST): LIST [STRING]
-- All values for Accept-Encoding header that `Current' can serve
require
req_attached: req /= Void
deferred
ensure
encodings_supported_includes_default: Result.has (conneg (req).encoding_default)
end
additional_variant_headers (req: WSF_REQUEST): detachable LIST [STRING]
-- Header other than Accept, Accept-Language, Accept-Charset and Accept-Encoding,
-- which might affect the response
do
end
predictable_response (req: WSF_REQUEST): BOOLEAN
-- Does the response to `req' vary only on the dimensions of ContentType, Language, Charset and Transfer encoding,
-- plus those named in `additional_variant_headers'?
do
Result := True
-- redefine to return `False', so as to induce a Vary: * header
end
matching_etag (req: WSF_REQUEST; a_etag: READABLE_STRING_32; a_strong: BOOLEAN): BOOLEAN
-- Is `a_etag' a match for resource requested in `req'?
-- If `a_strong' then the strong comparison function must be used.
require
req_attached: req /= Void
deferred
end
etag (req: WSF_REQUEST; a_media_type, a_language_type, a_character_type, a_compression_type: READABLE_STRING_8): detachable READABLE_STRING_8
-- Optional Etag for `req' in the requested variant
require
req_attached: req /= Void
a_media_type_attached: a_media_type /= Void
a_language_type_attached: a_language_type /= Void
a_character_type_attached: a_character_type /= Void
a_compression_type_attached: a_compression_type /= Void
deferred
end
modified_since (req: WSF_REQUEST; a_date_time: DATE_TIME): BOOLEAN
-- Has resource requested in `req' been modified since `a_date_time' (UTC)?
require
req_attached: req /= Void
deferred
end
treat_as_moved_permanently (req: WSF_REQUEST): BOOLEAN
-- Rather than store as a new entity, do we treat it as an existing entity that has been moved?
require
req_attached: req /= Void
put_request: req.is_request_method ({HTTP_REQUEST_METHODS}.method_put)
do
-- No. Redefine this if needed.
end
allow_post_to_missing_resource (req: WSF_REQUEST): BOOLEAN
-- The resource named in `req' does not exist, and this is a POST. Do we allow it?
require
req_attached: req /= Void
deferred
end
feature -- Measurement
content_length (req: WSF_REQUEST): NATURAL
-- Length of entity-body of the response to `req'
require
req_attached: req /= Void
not_chunked: not is_chunking (req)
deferred
end
feature -- Status report
finished (req: WSF_REQUEST): BOOLEAN
-- Has the last chunk been generated for `req'?
require
req_attached: req /= Void
chunked: is_chunking (req)
deferred
end
feature -- DELETE
delete (req: WSF_REQUEST)
-- Delete resource named in `req' or set an error on `req.error_handler'.
require
req_attached: req /= Void
deferred
ensure
error_or_queued_or_deleted: not req.error_handler.has_error implies (delete_queued (req) or deleted (req))
end
deleted (req: WSF_REQUEST): BOOLEAN
-- Has resource named by `req' been deleted?
require
req_attached: req /= Void
deferred
end
delete_queued (req: WSF_REQUEST): BOOLEAN
-- Has resource named by `req' been queued for deletion?
require
req_attached: req /= Void
deferred
ensure
entity_available: includes_response_entity (req)
end
feature -- GET/HEAD content
ensure_content_available (req: WSF_REQUEST;
a_media_type, a_language_type, a_character_type, a_compression_type: READABLE_STRING_8)
-- Commence generation of response text (entity-body).
-- If not chunked, then this will create the entire entity-body so as to be available
-- for a subsequent call to `content'.
-- If chunked, only the first chunk will be made available to `next_chunk'. If chunk extensions
-- are used, then this will also generate the chunk extension for the first chunk.
require
req_attached: req /= Void
get_or_head: req.is_get_head_request_method
a_media_type_attached: a_media_type /= Void
a_language_type_attached: a_language_type /= Void
a_character_type_attached: a_character_type /= Void
a_compression_type_attached: a_compression_type /= Void
deferred
end
response_ok (req: WSF_REQUEST): BOOLEAN
-- Has generation of the response (so-far, if chunked) proceeded witout error?
require
req_attached: req /= Void
do
Result := not req.error_handler.has_error
ensure
last_error_set: Result = not req.error_handler.has_error
end
content (req: WSF_REQUEST; a_media_type, a_language_type, a_character_type, a_compression_type: READABLE_STRING_8): READABLE_STRING_8
-- Non-chunked entity body in response to `req'
require
req_attached: req /= Void
head_get_or_delete: req.is_get_head_request_method or req.is_delete_request_method
no_error: response_ok (req)
not_chunked: not is_chunking (req)
a_media_type_attached: a_media_type /= Void
a_language_type_attached: a_language_type /= Void
a_character_type_attached: a_character_type /= Void
a_compression_type_attached: a_compression_type /= Void
deferred
end
generate_next_chunk (req: WSF_REQUEST; a_media_type, a_language_type, a_character_type, a_compression_type: READABLE_STRING_8)
-- Prepare next chunk (including optional chunk extension) of entity body in response to `req'.
-- This is not called for the first chunk.
require
req_attached: req /= Void
no_error: response_ok (req)
chunked: is_chunking (req)
a_media_type_attached: a_media_type /= Void
a_language_type_attached: a_language_type /= Void
a_character_type_attached: a_character_type /= Void
a_compression_type_attached: a_compression_type /= Void
deferred
end
next_chunk (req: WSF_REQUEST; a_media_type, a_language_type, a_character_type, a_compression_type: READABLE_STRING_8): TUPLE [a_check: READABLE_STRING_8; a_extension: detachable READABLE_STRING_8]
-- Next chunk of entity body in response to `req';
-- The second field of the result is an optional chunk extension.
require
req_attached: req /= Void
no_error: response_ok (req)
chunked: is_chunking (req)
a_media_type_attached: a_media_type /= Void
a_language_type_attached: a_language_type /= Void
a_character_type_attached: a_character_type /= Void
a_compression_type_attached: a_compression_type /= Void
deferred
end
feature -- PUT/POST
read_entity (req: WSF_REQUEST)
-- Read request body and set as `req.execution_variable ("REQUEST_ENTITY")'.
require
req_attached: req /= Void
local
l_body: STRING
do
create l_body.make_empty
req.read_input_data_into (l_body)
if not l_body.is_empty then
req.set_execution_variable ("REQUEST_ENTITY", l_body)
end
end
is_entity_too_large (req: WSF_REQUEST): BOOLEAN
-- Is the entity stored in `req.execution_variable ("REQUEST_ENTITY")' too large for the application?
require
req_attached: req /= Void
deferred
end
check_content_headers (req: WSF_REQUEST)
-- Check we can support all content headers on request entity.
-- Set `req.execution_variable ("CONTENT_CHECK_CODE")' to {NATURAL} zero if OK, or 415 or 501 if not.
require
req_attached: req /= Void
deferred
end
content_check_code (req: WSF_REQUEST): NATURAL
-- Code set by `check_content_headers'.
require
req_attached: req /= Void
do
if attached {NATURAL} req.execution_variable ("CONTENT_CHECK_CODE") as l_code then
Result := l_code
end
end
create_resource (req: WSF_REQUEST; res: WSF_RESPONSE)
-- Create new resource in response to a PUT request when `check_resource_exists' returns `False'.
-- Implementor must set error code of 200 OK or 500 Server Error.
require
req_attached: req /= Void
res_attached: res /= Void
put_request: req.is_put_request_method
deferred
end
append_resource (req: WSF_REQUEST; res: WSF_RESPONSE)
-- Create new resource in response to a POST request.
-- Implementor must set error code of 200 OK or 204 No Content or 303 See Other or 500 Server Error.
require
req_attached: req /= Void
res_attached: res /= Void
post_request: req.is_post_request_method
deferred
end
check_conflict (req: WSF_REQUEST; res: WSF_RESPONSE)
-- Check we can support all content headers on request entity.
-- Set `req.execution_variable ("CONFLICT_CHECK_CODE")' to {NATURAL} zero if OK, or 409 if not.
-- In the latter case, write the full error response to `res'.
require
req_attached: req /= Void
res_attached: res /= Void
deferred
end
conflict_check_code (req: WSF_REQUEST): NATURAL
-- Code set by `check_conflict'.
require
req_attached: req /= Void
do
if attached {NATURAL} req.execution_variable ("CONFLICT_CHECK_CODE") as l_code then
Result := l_code
end
end
check_request (req: WSF_REQUEST; res: WSF_RESPONSE)
-- Check that the request entity is a valid request.
-- The entity is available as `req.execution_variable ("REQUEST_ENTITY")'.
-- Set `req.execution_variable ("REQUEST_CHECK_CODE")' to {NATURAL} zero if OK, or 400 if not.
-- In the latter case, write the full error response to `res'.
require
req_attached: req /= Void
res_attached: res /= Void
put_or_post: req.is_put_post_request_method
deferred
end
request_check_code (req: WSF_REQUEST): NATURAL
-- Code set by `check_request'.
require
req_attached: req /= Void
do
if attached {NATURAL} req.execution_variable ("REQUEST_CHECK_CODE") as l_code then
Result := l_code
end
end
update_resource (req: WSF_REQUEST; res: WSF_RESPONSE)
-- Perform the update requested in `req'.
-- Write a response to `res' with a code of 204 or 500.
require
req_attached: req /= Void
res_attached: res /= Void
put_request: req.is_put_request_method
deferred
end
feature -- Execution feature -- Execution
execute (req: WSF_REQUEST; res: WSF_RESPONSE) execute (req: WSF_REQUEST; res: WSF_RESPONSE)
@@ -66,14 +416,14 @@ feature -- Execution
check_resource_exists (req: WSF_REQUEST; a_helper: WSF_METHOD_HELPER) check_resource_exists (req: WSF_REQUEST; a_helper: WSF_METHOD_HELPER)
-- Call `a_helper.set_resource_exists' to indicate that `req.path_translated' -- Call `a_helper.set_resource_exists' to indicate that `req.path_translated'
-- is the name of an existing resource. -- is the name of an existing resource.
-- Optionally, also call `req.set_server_data', if this is now available as a by-product
-- of the existence check.
require require
req_attached: req /= Void req_attached: req /= Void
a_helper_attached: a_helper /= Void a_helper_attached: a_helper /= Void
deferred deferred
end end
feature {NONE} -- Implementation
handle_internal_server_error (res: WSF_RESPONSE) handle_internal_server_error (res: WSF_RESPONSE)
-- Write "Internal Server Error" response to `res'. -- Write "Internal Server Error" response to `res'.
require require
@@ -95,4 +445,15 @@ feature -- Execution
body_sent: res.message_committed and then res.transfered_content_length > 0 body_sent: res.message_committed and then res.transfered_content_length > 0
end 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 end