From b4ab4875fc5ebc5dacc4dc6868fa56dd5de59089 Mon Sep 17 00:00:00 2001 From: Colin Adams Date: Sat, 13 Apr 2013 14:48:28 +0100 Subject: [PATCH 01/24] If-Match implemented in skeleton handler --- .../specification/request/wgi_meta_names.e | 12 ++++++ .../ewsgi/specification/request/wgi_request.e | 32 +++++++++++++++- .../implementation/wgi_request_from_table.e | 38 ++++++++++++++++++- library/server/wsf/src/wsf_request.e | 36 ++++++++++++++++++ 4 files changed, 116 insertions(+), 2 deletions(-) diff --git a/library/server/ewsgi/specification/request/wgi_meta_names.e b/library/server/ewsgi/specification/request/wgi_meta_names.e index a4578a6b..c4f9ee81 100644 --- a/library/server/ewsgi/specification/request/wgi_meta_names.e +++ b/library/server/ewsgi/specification/request/wgi_meta_names.e @@ -54,6 +54,18 @@ feature -- Access http_if_match: STRING = "HTTP_IF_MATCH" + http_if_modified_since: STRING = "HTTP_IF_MODIFIED_SINCE" + + http_if_none_match: STRING = "HTTP_IF_NONE_MATCH" + + http_if_range: STRING = "HTTP_IF_RANGE" + + http_if_unmodified_since: STRING = "HTTP_IF_UNMODIFIED_SINCE" + + http_last_modified: STRING = "HTTP_LAST_MODIFIED" + + http_range: STRING = "HTTP_RANGE" + gateway_interface: STRING = "GATEWAY_INTERFACE" auth_type: STRING = "AUTH_TYPE" diff --git a/library/server/ewsgi/specification/request/wgi_request.e b/library/server/ewsgi/specification/request/wgi_request.e index d8b77a4c..d860b4e8 100644 --- a/library/server/ewsgi/specification/request/wgi_request.e +++ b/library/server/ewsgi/specification/request/wgi_request.e @@ -605,7 +605,37 @@ feature -- HTTP_* end http_if_match: detachable READABLE_STRING_8 - -- Existance check on resource + -- Existence check on resource + deferred + end + + http_if_modified_since: detachable READABLE_STRING_8 + -- Modification check on resource + deferred + end + + http_if_none_match: detachable READABLE_STRING_8 + -- Existence check on resource + deferred + end + + http_if_range: detachable READABLE_STRING_8 + -- Range check on resource + deferred + end + + http_if_unmodified_since: detachable READABLE_STRING_8 + -- Modification check on resource + deferred + end + + http_last_modified: detachable READABLE_STRING_8 + -- Modification time of resource + deferred + end + + http_range: detachable READABLE_STRING_8 + -- Requested byte-range of resource deferred end diff --git a/library/server/ewsgi/src/implementation/wgi_request_from_table.e b/library/server/ewsgi/src/implementation/wgi_request_from_table.e index 9f2fdec4..934f5e10 100644 --- a/library/server/ewsgi/src/implementation/wgi_request_from_table.e +++ b/library/server/ewsgi/src/implementation/wgi_request_from_table.e @@ -249,10 +249,46 @@ feature -- Access: HTTP_* CGI meta parameters - 1.1 end http_if_match: detachable READABLE_STRING_8 - -- Existance check on resource + -- Existence check on resource do Result := meta_string_variable ({WGI_META_NAMES}.http_if_match) end + + http_if_modified_since: detachable READABLE_STRING_8 + -- Modification check on resource + do + Result := meta_string_variable ({WGI_META_NAMES}.http_if_modified_since) + end + + http_if_none_match: detachable READABLE_STRING_8 + -- Existence check on resource + do + Result := meta_string_variable ({WGI_META_NAMES}.http_if_none_match) + end + + http_if_range: detachable READABLE_STRING_8 + -- Range check on resource + do + Result := meta_string_variable ({WGI_META_NAMES}.http_if_range) + end + + http_if_unmodified_since: detachable READABLE_STRING_8 + -- Modification check on resource + do + Result := meta_string_variable ({WGI_META_NAMES}.http_if_unmodified_since) + end + + http_last_modified: detachable READABLE_STRING_8 + -- Modification time of resource + do + Result := meta_string_variable ({WGI_META_NAMES}.http_last_modified) + end + + http_range: detachable READABLE_STRING_8 + -- Requested byte-range of resource + do + Result := meta_string_variable ({WGI_META_NAMES}.http_range) + end feature -- Access: Extension to CGI meta parameters - 1.1 diff --git a/library/server/wsf/src/wsf_request.e b/library/server/wsf/src/wsf_request.e index f460180a..cf8eaa73 100644 --- a/library/server/wsf/src/wsf_request.e +++ b/library/server/wsf/src/wsf_request.e @@ -1048,6 +1048,42 @@ feature -- HTTP_* Result := wgi_request.http_if_match end + http_if_modified_since: detachable READABLE_STRING_8 + -- Modification check on resource + do + Result := wgi_request.http_if_modified_since + end + + http_if_none_match: detachable READABLE_STRING_8 + -- Existence check on resource + do + Result := wgi_request.http_if_none_match + end + + http_if_range: detachable READABLE_STRING_8 + -- Range check on resource + do + Result := wgi_request.http_if_range + end + + http_if_unmodified_since: detachable READABLE_STRING_8 + -- Modification check on resource + do + Result := wgi_request.http_if_unmodified_since + end + + http_last_modified: detachable READABLE_STRING_8 + -- Modification time of resource + do + Result := wgi_request.http_last_modified + end + + http_range: detachable READABLE_STRING_8 + -- Requested byte-range of resource + do + Result := wgi_request.http_range + end + feature -- Extra CGI environment variables request_uri: READABLE_STRING_8 From 98ad77a57d10d897ebe52bab59a9170bbf0aab52 Mon Sep 17 00:00:00 2001 From: Colin Adams Date: Sat, 13 Apr 2013 14:49:03 +0100 Subject: [PATCH 02/24] If-Match implemented in skeleton handler --- library/server/wsf/router/wsf_get_helper.e | 15 +++ library/server/wsf/router/wsf_method_helper.e | 80 +++++++++++++++ .../wsf/router/wsf_method_helper_factory.e | 25 +++++ .../server/wsf/router/wsf_options_policy.e | 37 +++++++ .../server/wsf/router/wsf_previous_policy.e | 40 ++++++++ .../server/wsf/router/wsf_skeleton_handler.e | 98 +++++++++++++++++++ 6 files changed, 295 insertions(+) create mode 100644 library/server/wsf/router/wsf_get_helper.e create mode 100644 library/server/wsf/router/wsf_method_helper.e create mode 100644 library/server/wsf/router/wsf_method_helper_factory.e create mode 100644 library/server/wsf/router/wsf_options_policy.e create mode 100644 library/server/wsf/router/wsf_previous_policy.e create mode 100644 library/server/wsf/router/wsf_skeleton_handler.e diff --git a/library/server/wsf/router/wsf_get_helper.e b/library/server/wsf/router/wsf_get_helper.e new file mode 100644 index 00000000..e20dd50b --- /dev/null +++ b/library/server/wsf/router/wsf_get_helper.e @@ -0,0 +1,15 @@ +note + + description: "[ + Policy-driven helpers to implement processing of GET and HEAD requests. + ]" + date: "$Date$" + revision: "$Revision$" + +class WSF_GET_HELPER + +inherit + + WSF_METHOD_HELPER + +end diff --git a/library/server/wsf/router/wsf_method_helper.e b/library/server/wsf/router/wsf_method_helper.e new file mode 100644 index 00000000..135876c5 --- /dev/null +++ b/library/server/wsf/router/wsf_method_helper.e @@ -0,0 +1,80 @@ +note + + description: "[ + Policy-driven helpers to implement a method. + ]" + date: "$Date$" + revision: "$Revision$" + +deferred class WSF_METHOD_HELPER + +feature -- Access + + resource_exists: BOOLEAN + -- Does the requested resource (request URI) exist? + +feature -- Setting + + set_resource_exists + -- Set `resource_exists' to `True'. + do + resource_exists := True + ensure + set: resource_exists + 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'. + -- This default implementation does not apply for PUT requests. + -- The behaviour for POST requests depends upon a policy. + require + req_attached: req /= Void + res_attached: res /= Void + a_handler_attached: a_handler /= Void + do + 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 + -- TODO 301 Moved Permanently + elseif a_handler.resource_moved_temporarily (req) then + -- TODO 302 Found + else + -- TODO 410 Gone + end + else + -- TODO 404 Not Found + end + end + + execute_existing_resource (req: WSF_REQUEST; res: WSF_RESPONSE; a_handler: WSF_SKELETON_HANDLER) + -- Write response to existing resource requested by `req.' into `res'. + -- Policy routines are available in `a_handler'. + require + req_attached: req /= Void + res_attached: res /= 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 ("*") + local + l_etags: LIST [READABLE_STRING_32] + do + if attached req.http_if_match as l_if_match then + l_etags := l_if_match.split (',') + + else + -- TODO: check_if_unmodified_since (req, res, a_handler) + end + end + + handle_precondition_failed (req: WSF_REQUEST; res: WSF_RESPONSE) + -- + require + req_attached: req /= Void + res_attached: res /= Void + do + end + +end diff --git a/library/server/wsf/router/wsf_method_helper_factory.e b/library/server/wsf/router/wsf_method_helper_factory.e new file mode 100644 index 00000000..a309b2ff --- /dev/null +++ b/library/server/wsf/router/wsf_method_helper_factory.e @@ -0,0 +1,25 @@ +note + + description: "[ + Default factory for policy-driven method helpers. + Extension methods can be implemented here. + ]" + date: "$Date$" + revision: "$Revision$" + +class WSF_METHOD_HELPER_FACTORY + +feature -- Factory + + new_method_helper (a_method: READABLE_STRING_8): detachable WSF_METHOD_HELPER + -- New object for processing `a_method' + require + a_method_attached: a_method /= Void + do + 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 + create {WSF_GET_HELPER} Result + end + end + +end diff --git a/library/server/wsf/router/wsf_options_policy.e b/library/server/wsf/router/wsf_options_policy.e new file mode 100644 index 00000000..418ea12b --- /dev/null +++ b/library/server/wsf/router/wsf_options_policy.e @@ -0,0 +1,37 @@ +note + + description: "[ + Default policy for responing to OPTIONS requests other than OPTIONS* + By overriding `execute_options', clients can add a body, for example. + ]" + date: "$Date$" + revision: "$Revision$" + +class WSF_OPTIONS_POLICY + +feature -- Basic operations + + execute_options (req: WSF_REQUEST; res: WSF_RESPONSE; a_router: WSF_ROUTER) + -- Write response to `req' into `res'. + require + req_attached: req /= Void + options_request: req.is_request_method ({HTTP_REQUEST_METHODS}.method_options) + res_attached: res /= Void + a_router_attached: a_router /= Void + local + l_methods: WSF_REQUEST_METHODS + h: HTTP_HEADER + do + create h.make + res.set_status_code ({HTTP_STATUS_CODE}.ok) + h.put_content_type ({HTTP_MIME_TYPES}.text_plain) + h.put_current_date + h.put_content_length (0) + l_methods := a_router.allowed_methods_for_request (req) + if not l_methods.is_empty then + h.put_allow (l_methods) + end + res.put_header_text (h.string) + end + +end diff --git a/library/server/wsf/router/wsf_previous_policy.e b/library/server/wsf/router/wsf_previous_policy.e new file mode 100644 index 00000000..9de00906 --- /dev/null +++ b/library/server/wsf/router/wsf_previous_policy.e @@ -0,0 +1,40 @@ +note + + description: "[ + Policies for deciding if a resource that currently doesn't exist used to do so. + This default implementation assumes that no resources used to exist. + ]" + date: "$Date$" + revision: "$Revision$" + +class WSF_PREVIOUS_POLICY + +feature -- Access + + resource_previously_existed (req: WSF_REQUEST) : BOOLEAN + -- Did `req.path_translated' exist previously? + require + req_attached: req /= Void + do + -- No. Override if this is not want you want. + end + + resource_moved_permanently (req: WSF_REQUEST) : BOOLEAN + -- Was `req.path_translated' moved permanently? + require + req_attached: req /= Void + previously_existed: resource_previously_existed (req) + do + -- No. Override if this is not want you want. + end + + resource_moved_temporarily (req: WSF_REQUEST) : BOOLEAN + -- Was `req.path_translated' moved temporarily? + require + req_attached: req /= Void + previously_existed: resource_previously_existed (req) + do + -- No. Override if this is not want you want. + end + +end diff --git a/library/server/wsf/router/wsf_skeleton_handler.e b/library/server/wsf/router/wsf_skeleton_handler.e new file mode 100644 index 00000000..a6389ee8 --- /dev/null +++ b/library/server/wsf/router/wsf_skeleton_handler.e @@ -0,0 +1,98 @@ +note + + description: "[ + Policy-driven handlers. + Implementers only need to concentrate on creating content. + ]" + date: "$Date$" + revision: "$Revision$" + +deferred class WSF_SKELETON_HANDLER + +inherit + + WSF_URI_TEMPLATE_ROUTING_HANDLER + redefine + execute + end + + WSF_OPTIONS_POLICY + + WSF_PREVIOUS_POLICY + + WSF_METHOD_HELPER_FACTORY + +feature -- Execution + + execute (req: WSF_REQUEST; res: WSF_RESPONSE) + -- + do + check + known_method: True -- Can't be done until WSF_METHOD_NOT_ALLOWED_RESPONSE + -- is refactored. + -- Then maybe this can become a precondition. But we will still (?) + -- need a check that it isn't CONNECT or TRACE (it MIGHT be HEAD). + end + if req.is_request_method ({HTTP_REQUEST_METHODS}.method_options) then + execute_options (req, res, router) + else + if attached new_method_helper (req.request_method) as l_helper then + execute_method (req, res, l_helper) + else + handle_internal_server_error (res) + end + end + end + + execute_method (req: WSF_REQUEST; res: WSF_RESPONSE; a_helper: WSF_METHOD_HELPER) + -- Write response to `req' into `res', using `a_helper' as a logic helper. + require + req_attached: req /= Void + res_attached: res /= Void + a_helper_attached: a_helper /= Void + do + check_resource_exists (req, a_helper) + if a_helper.resource_exists then + a_helper.execute_existing_resource (req, res, Current) + else + if attached req.http_if_match as l_if_match and then l_if_match.same_string ("*") then + a_helper.handle_precondition_failed (req, res) + else + a_helper.execute_new_resource (req, res, Current) + end + end + end + + check_resource_exists (req: WSF_REQUEST; a_helper: WSF_METHOD_HELPER) + -- Call `a_helper.set_resource_exists' to indicate that `req.path_translated' + -- 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 + req_attached: req /= Void + a_helper_attached: a_helper /= Void + deferred + end + + handle_internal_server_error (res: WSF_RESPONSE) + -- Write "Internal Server Error" response to `res'. + require + res_attached: res /= Void + local + h: HTTP_HEADER + m: STRING_8 + do + create h.make + h.put_content_type_text_plain + m := "Server failed to handle request properly" + h.put_content_length (m.count) + h.put_current_date + res.set_status_code ({HTTP_STATUS_CODE}.internal_server_error) + res.put_string (m) + ensure + response_status_is_set: res.status_is_set + status_is_service_unavailable: res.status_code = {HTTP_STATUS_CODE}.internal_server_error + body_sent: res.message_committed and then res.transfered_content_length > 0 + end + +end From ba11e84ed6e9ce8fb1b0cb0fa89c82b5d3f2c8cb Mon Sep 17 00:00:00 2001 From: Colin Adams Date: Mon, 8 Jul 2013 10:17:04 +0100 Subject: [PATCH 03/24] prior to merging --- .../wsf/src/response/wsf_precondition_failed_message.e | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/library/server/wsf/src/response/wsf_precondition_failed_message.e b/library/server/wsf/src/response/wsf_precondition_failed_message.e index da0ac71f..8ec3273a 100644 --- a/library/server/wsf/src/response/wsf_precondition_failed_message.e +++ b/library/server/wsf/src/response/wsf_precondition_failed_message.e @@ -53,8 +53,6 @@ feature {WSF_RESPONSE} -- Output send_to (res: WSF_RESPONSE) local s: STRING - l_text: detachable READABLE_STRING_GENERAL - l_loc: detachable READABLE_STRING_8 h: like header do h := header @@ -117,8 +115,8 @@ feature {WSF_RESPONSE} -- Output end note - - copyright: "2013, 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 From e0bfdab1065f5af51a394365e5dbe6f1d9e0a13d Mon Sep 17 00:00:00 2001 From: Colin Adams Date: Tue, 6 Aug 2013 13:47:07 +0100 Subject: [PATCH 04/24] Add CONNEG to wsf*.ecf to support ploicy-driven framework --- library/server/wsf/wsf-safe.ecf | 1 + library/server/wsf/wsf.ecf | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/library/server/wsf/wsf-safe.ecf b/library/server/wsf/wsf-safe.ecf index 06b6121b..e47d827a 100644 --- a/library/server/wsf/wsf-safe.ecf +++ b/library/server/wsf/wsf-safe.ecf @@ -18,6 +18,7 @@ + diff --git a/library/server/wsf/wsf.ecf b/library/server/wsf/wsf.ecf index 3d19ec4d..b315cfb2 100644 --- a/library/server/wsf/wsf.ecf +++ b/library/server/wsf/wsf.ecf @@ -15,7 +15,9 @@ - + + From 8ab6dba1c8a9dcc2f7ec256cd67365ed6d1564eb Mon Sep 17 00:00:00 2001 From: Colin Adams Date: Tue, 6 Aug 2013 13:49:58 +0100 Subject: [PATCH 05/24] New routines added to HTTP_HEADER to support ploicy-driven framework --- library/network/protocol/http/src/http_header.e | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/library/network/protocol/http/src/http_header.e b/library/network/protocol/http/src/http_header.e index b1eee52d..72e6fd37 100644 --- a/library/network/protocol/http/src/http_header.e +++ b/library/network/protocol/http/src/http_header.e @@ -509,6 +509,18 @@ feature -- Content related header put_header_key_value ({HTTP_HEADER_NAMES}.header_content_transfer_encoding, a_mechanism) end + put_content_language (a_enc: READABLE_STRING_8) + -- Put "Content-Language" header of value `a_enc'. + do + put_header_key_value ({HTTP_HEADER_NAMES}.header_content_language, a_enc) + end + + put_content_encoding (a_enc: READABLE_STRING_8) + -- Put "Content-Encoding" header of value `a_enc'. + do + put_header_key_value ({HTTP_HEADER_NAMES}.header_content_encoding, a_enc) + end + put_transfer_encoding (a_enc: READABLE_STRING_8) -- Put "Transfer-Encoding" header with for instance "chunked" do From 0a9d2085295f1765b3684c76c821aeb543503559 Mon Sep 17 00:00:00 2001 From: Colin Adams Date: Tue, 6 Aug 2013 13:51:43 +0100 Subject: [PATCH 06/24] New routines added to WSF_REQUEST to support ploicy-driven framework --- library/server/wsf/src/wsf_request.e | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/library/server/wsf/src/wsf_request.e b/library/server/wsf/src/wsf_request.e index 33887bda..572d5a28 100644 --- a/library/server/wsf/src/wsf_request.e +++ b/library/server/wsf/src/wsf_request.e @@ -311,18 +311,44 @@ feature -- Helper Result := request_method.is_case_insensitive_equal (m.as_string_8) end + is_put_request_method: BOOLEAN + -- Is Current a PUT request method? + do + Result := request_method.is_case_insensitive_equal ({HTTP_REQUEST_METHODS}.method_put) + end + is_post_request_method: BOOLEAN -- Is Current a POST request method? do Result := request_method.is_case_insensitive_equal ({HTTP_REQUEST_METHODS}.method_post) end + is_delete_request_method: BOOLEAN + -- Is Current a DELETE request method? + do + Result := request_method.is_case_insensitive_equal ({HTTP_REQUEST_METHODS}.method_delete) + end + is_get_request_method: BOOLEAN -- Is Current a GET request method? do Result := request_method.is_case_insensitive_equal ({HTTP_REQUEST_METHODS}.method_get) end + is_get_head_request_method: BOOLEAN + -- Is Current a GET or a HEAD request method? + do + Result := request_method.is_case_insensitive_equal ({HTTP_REQUEST_METHODS}.method_get) or + request_method.is_case_insensitive_equal ({HTTP_REQUEST_METHODS}.method_head) + end + + is_put_post_request_method: BOOLEAN + -- Is Current a PUT or a POST request method? + do + Result := request_method.is_case_insensitive_equal ({HTTP_REQUEST_METHODS}.method_put) or + request_method.is_case_insensitive_equal ({HTTP_REQUEST_METHODS}.method_post) + end + is_content_type_accepted (a_content_type: READABLE_STRING_GENERAL): BOOLEAN -- Does client accepts content_type for the response? --| Based on header "Accept:" that can be for instance From 8dbd24afd1dd7a75b71ce09e7661b6390775f856 Mon Sep 17 00:00:00 2001 From: Colin Adams Date: Tue, 6 Aug 2013 13:57:12 +0100 Subject: [PATCH 07/24] Policy-driven URI template handlers --- .../server/wsf/router/wsf_caching_policy.e | 125 ++++++ library/server/wsf/router/wsf_delete_helper.e | 60 +++ library/server/wsf/router/wsf_method_helper.e | 375 +++++++++++++++++- .../wsf/router/wsf_method_helper_factory.e | 8 +- library/server/wsf/router/wsf_post_helper.e | 70 ++++ .../server/wsf/router/wsf_previous_policy.e | 30 +- library/server/wsf/router/wsf_put_helper.e | 75 ++++ .../server/wsf/router/wsf_skeleton_handler.e | 367 ++++++++++++++++- 8 files changed, 1084 insertions(+), 26 deletions(-) create mode 100644 library/server/wsf/router/wsf_caching_policy.e create mode 100644 library/server/wsf/router/wsf_delete_helper.e create mode 100644 library/server/wsf/router/wsf_post_helper.e create mode 100644 library/server/wsf/router/wsf_put_helper.e diff --git a/library/server/wsf/router/wsf_caching_policy.e b/library/server/wsf/router/wsf_caching_policy.e new file mode 100644 index 00000000..4c780b19 --- /dev/null +++ b/library/server/wsf/router/wsf_caching_policy.e @@ -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 diff --git a/library/server/wsf/router/wsf_delete_helper.e b/library/server/wsf/router/wsf_delete_helper.e new file mode 100644 index 00000000..6bc0e646 --- /dev/null +++ b/library/server/wsf/router/wsf_delete_helper.e @@ -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 diff --git a/library/server/wsf/router/wsf_method_helper.e b/library/server/wsf/router/wsf_method_helper.e index 135876c5..6b23ebec 100644 --- a/library/server/wsf/router/wsf_method_helper.e +++ b/library/server/wsf/router/wsf_method_helper.e @@ -2,11 +2,12 @@ note description: "[ Policy-driven helpers to implement a method. - ]" + This implementation is suitable for GET and HEAD. + ]" date: "$Date$" revision: "$Revision$" -deferred class WSF_METHOD_HELPER +class WSF_METHOD_HELPER feature -- Access @@ -34,24 +35,37 @@ feature -- Basic operations req_attached: req /= Void res_attached: res /= Void a_handler_attached: a_handler /= Void + local + l_locs: LIST [URI] + h: HTTP_HEADER do 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 - -- TODO 301 Moved Permanently + l_locs := a_handler.previous_location (req) + -- TODO 301 Moved permanently response elseif a_handler.resource_moved_temporarily (req) then - -- TODO 302 Found + l_locs := a_handler.previous_location (req) + -- TODO := 302 Found response 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 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 - - execute_existing_resource (req: WSF_REQUEST; res: WSF_RESPONSE; a_handler: WSF_SKELETON_HANDLER) - -- Write response to existing resource requested by `req.' into `res'. + + execute_existing_resource (req: WSF_REQUEST; res: WSF_RESPONSE; a_handler: WSF_SKELETON_HANDLER) + -- Write response to existing resource requested by `req' into `res'. -- Policy routines are available in `a_handler'. require req_attached: req /= Void @@ -59,22 +73,347 @@ feature -- Basic operations 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 ("*") local - l_etags: LIST [READABLE_STRING_32] + l_etags: LIST [READABLE_STRING_8] + l_failed: BOOLEAN + l_date: HTTP_DATE do if attached req.http_if_match as l_if_match then - l_etags := l_if_match.split (',') - + -- 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_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 - -- 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 + +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 - handle_precondition_failed (req: WSF_REQUEST; res: WSF_RESPONSE) - -- + 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 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_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 diff --git a/library/server/wsf/router/wsf_method_helper_factory.e b/library/server/wsf/router/wsf_method_helper_factory.e index a309b2ff..c41a6e3d 100644 --- a/library/server/wsf/router/wsf_method_helper_factory.e +++ b/library/server/wsf/router/wsf_method_helper_factory.e @@ -18,7 +18,13 @@ feature -- Factory do 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 - 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 diff --git a/library/server/wsf/router/wsf_post_helper.e b/library/server/wsf/router/wsf_post_helper.e new file mode 100644 index 00000000..b05a40a7 --- /dev/null +++ b/library/server/wsf/router/wsf_post_helper.e @@ -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 diff --git a/library/server/wsf/router/wsf_previous_policy.e b/library/server/wsf/router/wsf_previous_policy.e index 9de00906..b64c0450 100644 --- a/library/server/wsf/router/wsf_previous_policy.e +++ b/library/server/wsf/router/wsf_previous_policy.e @@ -7,11 +7,11 @@ note date: "$Date$" revision: "$Revision$" -class WSF_PREVIOUS_POLICY +deferred class WSF_PREVIOUS_POLICY feature -- Access - resource_previously_existed (req: WSF_REQUEST) : BOOLEAN + resource_previously_existed (req: WSF_REQUEST): BOOLEAN -- Did `req.path_translated' exist previously? require req_attached: req /= Void @@ -19,7 +19,7 @@ feature -- Access -- No. Override if this is not want you want. end - resource_moved_permanently (req: WSF_REQUEST) : BOOLEAN + resource_moved_permanently (req: WSF_REQUEST): BOOLEAN -- Was `req.path_translated' moved permanently? require req_attached: req /= Void @@ -28,7 +28,7 @@ feature -- Access -- No. Override if this is not want you want. end - resource_moved_temporarily (req: WSF_REQUEST) : BOOLEAN + resource_moved_temporarily (req: WSF_REQUEST): BOOLEAN -- Was `req.path_translated' moved temporarily? require req_attached: req /= Void @@ -37,4 +37,26 @@ feature -- Access -- No. Override if this is not want you want. 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 diff --git a/library/server/wsf/router/wsf_put_helper.e b/library/server/wsf/router/wsf_put_helper.e new file mode 100644 index 00000000..67f123b8 --- /dev/null +++ b/library/server/wsf/router/wsf_put_helper.e @@ -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 diff --git a/library/server/wsf/router/wsf_skeleton_handler.e b/library/server/wsf/router/wsf_skeleton_handler.e index a6389ee8..aa8d01c3 100644 --- a/library/server/wsf/router/wsf_skeleton_handler.e +++ b/library/server/wsf/router/wsf_skeleton_handler.e @@ -20,8 +20,358 @@ inherit WSF_PREVIOUS_POLICY + WSF_CACHING_POLICY + 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 execute (req: WSF_REQUEST; res: WSF_RESPONSE) @@ -66,14 +416,14 @@ feature -- Execution check_resource_exists (req: WSF_REQUEST; a_helper: WSF_METHOD_HELPER) -- Call `a_helper.set_resource_exists' to indicate that `req.path_translated' -- 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 req_attached: req /= Void a_helper_attached: a_helper /= Void deferred end +feature {NONE} -- Implementation + handle_internal_server_error (res: WSF_RESPONSE) -- Write "Internal Server Error" response to `res'. require @@ -94,5 +444,16 @@ feature -- Execution status_is_service_unavailable: res.status_code = {HTTP_STATUS_CODE}.internal_server_error body_sent: res.message_committed and then res.transfered_content_length > 0 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 From 277eb0b4b68b147646470e4974f432fe5e64493d Mon Sep 17 00:00:00 2001 From: Colin Adams Date: Tue, 6 Aug 2013 15:01:24 +0100 Subject: [PATCH 08/24] restbucksCRUD example changed to use policy-driven framework --- examples/restbucksCRUD/restbucks-safe.ecf | 5 +- .../src/resource/order_handler.e | 595 ++++++++++++------ examples/restbucksCRUD/src/restbucks_server.e | 2 +- 3 files changed, 392 insertions(+), 210 deletions(-) diff --git a/examples/restbucksCRUD/restbucks-safe.ecf b/examples/restbucksCRUD/restbucks-safe.ecf index 86dcda7f..ca7c3f18 100644 --- a/examples/restbucksCRUD/restbucks-safe.ecf +++ b/examples/restbucksCRUD/restbucks-safe.ecf @@ -1,5 +1,5 @@ - + @@ -18,11 +18,14 @@ + + + diff --git a/examples/restbucksCRUD/src/resource/order_handler.e b/examples/restbucksCRUD/src/resource/order_handler.e index 0bddd924..f1db9d1a 100644 --- a/examples/restbucksCRUD/src/resource/order_handler.e +++ b/examples/restbucksCRUD/src/resource/order_handler.e @@ -5,18 +5,11 @@ note revision: "$Revision$" class ORDER_HANDLER + inherit - WSF_URI_TEMPLATE_HANDLER - - WSF_RESOURCE_HANDLER_HELPER - redefine - do_get, - do_post, - do_put, - do_delete - end - + WSF_SKELETON_HANDLER + SHARED_DATABASE_API SHARED_EJSON @@ -25,154 +18,394 @@ inherit SHARED_ORDER_VALIDATION - WSF_SELF_DOCUMENTED_HANDLER - -feature -- Execute - - execute (req: WSF_REQUEST; res: WSF_RESPONSE) - -- Execute request handler - do - execute_methods (req, res) + WSF_RESOURCE_HANDLER_HELPER + rename + execute_options as helper_execute_options, + handle_internal_server_error as helper_handle_internal_server_error end +create + + make_with_router + feature -- API DOC - api_doc : STRING = "URI:/order METHOD: POST%N URI:/order/{orderid} METHOD: GET, PUT, DELETE%N" + api_doc : STRING = "URI:/order METHOD: POST%N URI:/order/{orderid} METHOD: GET, HEAD, PUT, DELETE%N" +feature -- Access -feature -- Documentation - - mapping_documentation (m: WSF_ROUTER_MAPPING; a_request_methods: detachable WSF_REQUEST_METHODS): WSF_ROUTER_MAPPING_DOCUMENTATION + is_chunking (req: WSF_REQUEST): BOOLEAN + -- Will the response to `req' using chunked transfer encoding? do - create Result.make (m) - if a_request_methods /= Void then - if a_request_methods.has_method_post then - Result.add_description ("URI:/order METHOD: POST") - elseif - a_request_methods.has_method_get - or a_request_methods.has_method_put - or a_request_methods.has_method_delete - then - Result.add_description ("URI:/order/{orderid} METHOD: GET, PUT, DELETE") + -- No. + end + + includes_response_entity (req: WSF_REQUEST): BOOLEAN + -- Does the response to `req' include an entity? + -- Method will be DELETE, POST, PUT or an extension method. + do + Result := False + -- At present, there is no support for this except for DELETE. + end + + conneg (req: WSF_REQUEST): CONNEG_SERVER_SIDE + -- Content negotiatior for all requests + once + create Result.make ({HTTP_MIME_TYPES}.application_json, "en", "UTF-8", "identity") + end + + mime_types_supported (req: WSF_REQUEST): LIST [STRING] + -- All values for Accept header that `Current' can serve + do + create {ARRAYED_LIST [STRING]} Result.make_from_array (<<{HTTP_MIME_TYPES}.application_json>>) + Result.compare_objects + end + + languages_supported (req: WSF_REQUEST): LIST [STRING] + -- All values for Accept-Language header that `Current' can serve + do + create {ARRAYED_LIST [STRING]} Result.make_from_array (<<"en">>) + Result.compare_objects + end + + charsets_supported (req: WSF_REQUEST): LIST [STRING] + -- All values for Accept-Charset header that `Current' can serve + do + create {ARRAYED_LIST [STRING]} Result.make_from_array (<<"UTF-8">>) + Result.compare_objects + end + + encodings_supported (req: WSF_REQUEST): LIST [STRING] + -- All values for Accept-Encoding header that `Current' can serve + do + create {ARRAYED_LIST [STRING]} Result.make_from_array (<<"identity">>) + Result.compare_objects + end + + previous_location (req: WSF_REQUEST): LIST [URI] + -- Previous location(s) for resource named by `req'; + do + -- precondition is never met but we need a non-void Result to satisfy the compiler in Void-safe mode: + create {LINKED_LIST [URI]} Result.make + end + + 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. + do + -- All our responses are considered stale. + 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. + do + -- definitely not! + 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. + do + create {ARRAYED_LIST [READABLE_STRING_8]} Result.make (0) + 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. + do + create {ARRAYED_LIST [READABLE_STRING_8]} Result.make (0) + 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. + do + Result := True + -- since it's commercial data. + 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. + local + l_id: STRING + l_etag_util: ETAG_UTILS + do + l_id := order_id_from_request (req) + if db_access.orders.has_key (l_id) then + check attached db_access.orders.item (l_id) as l_order then + -- postcondition of `has_key' + create l_etag_util + Result := a_etag.same_string (l_etag_util.md5_digest (l_order.out).as_string_32) end end 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 + local + l_etag_utils: ETAG_UTILS + do + create l_etag_utils + if attached {ORDER} req.execution_variable ("ORDER") as l_order then + Result := l_etag_utils.md5_digest (l_order.out) + end + end + + modified_since (req: WSF_REQUEST; a_date_time: DATE_TIME): BOOLEAN + -- Has resource requested in `req' been modified since `a_date_time' (UTC)? + do + -- We don't track this information. It is safe to always say yes. + Result := True + end + +feature -- Measurement + + content_length (req: WSF_REQUEST): NATURAL + -- Length of entity-body of the response to `req' + do + check attached {READABLE_STRING_8} req.execution_variable ("GENERATED_CONTENT") as l_response then + -- postcondition generated_content_set_for_get_head of `ensure_content_available' + -- We only call this for GET/HEAD in this example. + Result := l_response.count.as_natural_32 + end + 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? + do + -- No. + end + +feature -- Status report + + finished (req: WSF_REQUEST): BOOLEAN + -- Has the last chunk been generated for `req'? + do + -- precondition is never met + end + +feature -- Execution + + check_resource_exists (req: WSF_REQUEST; a_helper: WSF_METHOD_HELPER) + -- Call `a_helper.set_resource_exists' to indicate that `req.path_translated' + -- is the name of an existing resource. + -- We also put the order into `req.execution_variable ("ORDER")' for GET or HEAD responses. + local + l_id: STRING + do + if req.is_post_request_method then + a_helper.set_resource_exists + -- because only /order is defined to this handler for POST + else + -- the request is of the form /order/{orderid} + l_id := order_id_from_request (req) + if db_access.orders.has_key (l_id) then + a_helper.set_resource_exists + if req.is_get_head_request_method then + check attached db_access.orders.item (l_id) as l_order then + -- postcondition `item_if_found' of `has_key' + req.set_execution_variable ("ORDER", l_order) + end + end + end + end + ensure then + order_saved_only_for_get_head: req.is_get_head_request_method = + attached {ORDER} req.execution_variable ("ORDER") + 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. + -- We save the text in `req.execution_variable ("GENERATED_CONTENT")' + do + check attached {ORDER} req.execution_variable ("ORDER") as l_order then + -- precondition get_or_head and postcondition order_saved_only_for_get_head of `check_resource_exists' and + if attached {JSON_VALUE} json.value (l_order) as jv then + req.set_execution_variable ("GENERATED_CONTENT", jv.representation) + else + req.set_execution_variable ("GENERATED_CONTENT", "") + end + end + ensure then + generated_content_set_for_get_head: req.is_get_head_request_method implies + attached {READABLE_STRING_8} req.execution_variable ("GENERATED_CONTENT") + 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'; + -- We only call this for GET/HEAD in this example. + do + check attached {READABLE_STRING_8} req.execution_variable ("GENERATED_CONTENT") as l_response then + -- postcondition generated_content_set_for_get_head of `ensure_content_available' + Result := l_response + end + 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. + do + -- precondition `is_chunking' is never met, but we need a dummy `Result' + -- to satisfy the compiler in void-safe mode + Result := ["", Void] + 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. + do + -- precondition `is_chunking' is never met + end + +feature -- DELETE + + delete (req: WSF_REQUEST) + -- Delete resource named in `req' or set an error on `req.error_handler'. + local + l_id: STRING + do + l_id := order_id_from_request (req) + if db_access.orders.has_key (l_id) then + if is_valid_to_delete (l_id) then + delete_order (l_id) + else + req.error_handler.add_custom_error ({HTTP_STATUS_CODE}.method_not_allowed, "DELETE not valid", + "There is conflict while trying to delete the order, the order could not be deleted in the current state") + end + else + req.error_handler.add_custom_error ({HTTP_STATUS_CODE}.not_found, "DELETE not valid", + "There is no such order to delete") + end + end + + deleted (req: WSF_REQUEST): BOOLEAN + -- Has resource named by `req' been deleted? + do + if not req.error_handler.has_error then + Result := True + end + end + + delete_queued (req: WSF_REQUEST): BOOLEAN + -- Has resource named by `req' been queued for deletion? + do + -- No + end + + +feature -- PUT/POST + + is_entity_too_large (req: WSF_REQUEST): BOOLEAN + -- Is the entity stored in `req.execution_variable ("REQUEST_ENTITY")' too large for the application? + do + -- No. We don't care for this example. + 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. + do + -- We don't bother for this example. Note that this is equivalent to setting zero. + 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. + do + -- We don't support creating a new resource with PUT. But this can't happen + -- with our router mappings, so we don't bother to set a 500 response. + 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. + do + if attached {ORDER} req.execution_variable ("EXTRACTED_ORDER") as l_order then + save_order (l_order) + compute_response_post (req, res, l_order) + else + handle_bad_request_response ("Not a valid order", req, res) + end + 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'. + do + if attached {ORDER} req.execution_variable ("EXTRACTED_ORDER") as l_order then + if not is_valid_to_update (l_order) then + req.set_execution_variable ("CONFLICT_CHECK_CODE", {NATURAL} 409) + --| FIXME: Here we need to define the Allow methods + handle_resource_conflict_response (l_order.out +"%N There is conflict while trying to update the order, the order could not be update in the current state", req, res) + end + else + req.set_execution_variable ("CONFLICT_CHECK_CODE", {NATURAL} 409) + --| FIXME: Here we need to define the Allow methods + --| This ought to be a 500, as if attached should probably be check attached. But as yet I lack a proof. TODO. + handle_resource_conflict_response ("There is conflict while trying to update the order, the order could not be update in the current state", req, res) + 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'. + local + l_order: detachable ORDER + l_id: STRING + do + if attached {READABLE_STRING_8} req.execution_variable ("REQUEST_ENTITY") as l_request then + l_order := extract_order_request (l_request) + if req.is_put_request_method then + l_id := order_id_from_request (req) + if l_order /= Void and then db_access.orders.has_key (l_id) then + l_order.set_id (l_id) + req.set_execution_variable ("REQUEST_CHECK_CODE", {NATURAL} 0) + req.set_execution_variable ("EXTRACTED_ORDER", l_order) + else + req.set_execution_variable ("REQUEST_CHECK_CODE", {NATURAL} 400) + handle_bad_request_response (l_request +"%N is not a valid ORDER, maybe the order does not exist in the system", req, res) + end + else + req.set_execution_variable ("REQUEST_CHECK_CODE", {NATURAL} 0) + req.set_execution_variable ("EXTRACTED_ORDER", l_order) + end + else + req.set_execution_variable ("REQUEST_CHECK_CODE", {NATURAL} 400) + handle_bad_request_response ("Request is not a valid ORDER", req, res) + 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. + do + if attached {ORDER} req.execution_variable ("EXTRACTED_ORDER") as l_order then + update_order (l_order) + compute_response_put (req, res, l_order) + else + handle_internal_server_error (res) + end + end + feature -- HTTP Methods - do_get (req: WSF_REQUEST; res: WSF_RESPONSE) - -- - local - id: STRING - do - if attached req.orig_path_info as orig_path then - id := get_order_id_from_path (orig_path) - if attached retrieve_order (id) as l_order then - if is_conditional_get (req, l_order) then - handle_resource_not_modified_response ("The resource" + orig_path + "does not change", req, res) - else - compute_response_get (req, res, l_order) - end - else - handle_resource_not_found_response ("The following resource" + orig_path + " is not found ", req, res) - end - end - end - - is_conditional_get (req : WSF_REQUEST; l_order : ORDER) : BOOLEAN - -- Check if If-None-Match is present and then if there is a representation that has that etag - -- if the representation hasn't changed, we return TRUE - -- then the response is a 304 with no entity body returned. - local - etag_util : ETAG_UTILS - do - if attached req.meta_string_variable ("HTTP_IF_NONE_MATCH") as if_none_match then - create etag_util - if if_none_match.same_string (etag_util.md5_digest (l_order.out).as_string_32) then - Result := True - end - end - end - - compute_response_get (req: WSF_REQUEST; res: WSF_RESPONSE; l_order: ORDER) - local - h: HTTP_HEADER - l_msg : STRING - etag_utils : ETAG_UTILS - do - create h.make - create etag_utils - h.put_content_type_application_json - if attached {JSON_VALUE} json.value (l_order) as jv then - l_msg := jv.representation - h.put_content_length (l_msg.count) - if attached req.request_time as time then - h.add_header ("Date:" + time.formatted_out ("ddd,[0]dd mmm yyyy [0]hh:[0]mi:[0]ss.ff2") + " GMT") - end - h.add_header ("etag:" + etag_utils.md5_digest (l_order.out)) - res.set_status_code ({HTTP_STATUS_CODE}.ok) - res.put_header_text (h.string) - res.put_string (l_msg) - end - end - - do_put (req: WSF_REQUEST; res: WSF_RESPONSE) - -- Updating a resource with PUT - -- A successful PUT request will not create a new resource, instead it will - -- change the state of the resource identified by the current uri. - -- If success we response with 200 and the updated order. - -- 404 if the order is not found - -- 400 in case of a bad request - -- 500 internal server error - -- If the request is a Conditional PUT, and it does not mat we response - -- 415, precondition failed. - local - l_put: STRING - l_order : detachable ORDER - id : STRING - do - if attached req.orig_path_info as orig_path then - id := get_order_id_from_path (orig_path) - l_put := retrieve_data (req) - l_order := extract_order_request(l_put) - if l_order /= Void and then db_access.orders.has_key (id) then - l_order.set_id (id) - if is_valid_to_update(l_order) then - if is_conditional_put (req, l_order) then - update_order( l_order) - compute_response_put (req, res, l_order) - else - handle_precondition_fail_response ("", req, res) - end - else - --| FIXME: Here we need to define the Allow methods - handle_resource_conflict_response (l_put +"%N There is conflict while trying to update the order, the order could not be update in the current state", req, res) - end - else - handle_bad_request_response (l_put +"%N is not a valid ORDER, maybe the order does not exist in the system", req, res) - end - end - end - - is_conditional_put (req : WSF_REQUEST; order : ORDER) : BOOLEAN - -- Check if If-Match is present and then if there is a representation that has that etag - -- if the representation hasn't changed, we return TRUE - local - etag_util : ETAG_UTILS - do - if attached retrieve_order (order.id) as l_order then - if attached req.meta_string_variable ("HTTP_IF_MATCH") as if_match then - create etag_util - if if_match.same_string (etag_util.md5_digest (l_order.out).as_string_32) then - Result := True - end - else - Result := True - end - end - end - - compute_response_put (req: WSF_REQUEST; res: WSF_RESPONSE; l_order : ORDER) local h: HTTP_HEADER @@ -198,67 +431,6 @@ feature -- HTTP Methods end end - - do_delete (req: WSF_REQUEST; res: WSF_RESPONSE) - -- Here we use DELETE to cancel an order, if that order is in state where - -- it can still be canceled. - -- 200 if is ok - -- 404 Resource not found - -- 405 if consumer and service's view of the resouce state is inconsisent - -- 500 if we have an internal server error - local - id: STRING - do - if attached req.orig_path_info as orig_path then - id := get_order_id_from_path (orig_path) - if db_access.orders.has_key (id) then - if is_valid_to_delete (id) then - delete_order( id) - compute_response_delete (req, res) - else - --| FIXME: Here we need to define the Allow methods - handle_method_not_allowed_response (orig_path + "%N There is conflict while trying to delete the order, the order could not be deleted in the current state", req, res) - end - else - handle_resource_not_found_response (orig_path + " not found in this server", req, res) - end - end - end - - compute_response_delete (req: WSF_REQUEST; res: WSF_RESPONSE) - local - h : HTTP_HEADER - do - create h.make - h.put_content_type_application_json - if attached req.request_time as time then - h.put_utc_date (time) - end - res.set_status_code ({HTTP_STATUS_CODE}.no_content) - res.put_header_text (h.string) - end - - do_post (req: WSF_REQUEST; res: WSF_RESPONSE) - -- Here the convention is the following. - -- POST is used for creation and the server determines the URI - -- of the created resource. - -- If the request post is SUCCESS, the server will create the order and will response with - -- HTTP_RESPONSE 201 CREATED, the Location header will contains the newly created order's URI - -- if the request post is not SUCCESS, the server will response with - -- HTTP_RESPONSE 400 BAD REQUEST, the client send a bad request - -- HTTP_RESPONSE 500 INTERNAL_SERVER_ERROR, when the server can deliver the request - local - l_post: STRING - do - l_post := retrieve_data (req) - if attached extract_order_request (l_post) as l_order then - save_order (l_order) - compute_response_post (req, res, l_order) - else - handle_bad_request_response (l_post +"%N is not a valid ORDER", req, res) - end - end - compute_response_post (req: WSF_REQUEST; res: WSF_RESPONSE; l_order : ORDER) local h: HTTP_HEADER @@ -290,9 +462,16 @@ feature -- HTTP Methods feature {NONE} -- URI helper methods - get_order_id_from_path (a_path: READABLE_STRING_32) : STRING + order_id_from_request (req: WSF_REQUEST): STRING + -- Value of "orderid" template URI variable in `req' + require + req_attached: req /= Void do - Result := a_path.split ('/').at (3) + if attached {WSF_VALUE} req.path_parameter ("orderid") as l_value then + Result := l_value.as_string.value.as_string_8 + else + Result := "" + end end feature {NONE} -- Implementation Repository Layer diff --git a/examples/restbucksCRUD/src/restbucks_server.e b/examples/restbucksCRUD/src/restbucks_server.e index 81008346..588b2057 100644 --- a/examples/restbucksCRUD/src/restbucks_server.e +++ b/examples/restbucksCRUD/src/restbucks_server.e @@ -37,7 +37,7 @@ feature {NONE} -- Initialization order_handler: ORDER_HANDLER doc: WSF_ROUTER_SELF_DOCUMENTATION_HANDLER do - create order_handler + create order_handler.make_with_router (router) router.handle_with_request_methods ("/order", order_handler, router.methods_POST) router.handle_with_request_methods ("/order/{orderid}", order_handler, router.methods_GET + router.methods_DELETE + router.methods_PUT) create doc.make_hidden (router) From 69da6c6d065f307b4609606b5c4f7cc8e8e491a9 Mon Sep 17 00:00:00 2001 From: Colin Adams Date: Wed, 7 Aug 2013 11:03:22 +0100 Subject: [PATCH 09/24] Fixes as picked up by code review --- examples/restbucksCRUD/restbucks-safe.ecf | 2 +- .../src/resource/order_handler.e | 47 ++-- .../network/protocol/http/src/http_header.e | 10 +- .../server/wsf/router/wsf_caching_policy.e | 28 +- library/server/wsf/router/wsf_delete_helper.e | 7 +- library/server/wsf/router/wsf_get_helper.e | 41 +++ library/server/wsf/router/wsf_method_helper.e | 263 +++++++++++++----- .../wsf/router/wsf_method_helper_factory.e | 14 +- library/server/wsf/router/wsf_post_helper.e | 45 +-- library/server/wsf/router/wsf_put_helper.e | 30 +- .../server/wsf/router/wsf_skeleton_handler.e | 23 +- library/server/wsf/src/wsf_request.e | 2 + 12 files changed, 363 insertions(+), 149 deletions(-) diff --git a/examples/restbucksCRUD/restbucks-safe.ecf b/examples/restbucksCRUD/restbucks-safe.ecf index ca7c3f18..08715841 100644 --- a/examples/restbucksCRUD/restbucks-safe.ecf +++ b/examples/restbucksCRUD/restbucks-safe.ecf @@ -18,7 +18,7 @@ - + diff --git a/examples/restbucksCRUD/src/resource/order_handler.e b/examples/restbucksCRUD/src/resource/order_handler.e index f1db9d1a..499e5695 100644 --- a/examples/restbucksCRUD/src/resource/order_handler.e +++ b/examples/restbucksCRUD/src/resource/order_handler.e @@ -9,7 +9,7 @@ class ORDER_HANDLER inherit WSF_SKELETON_HANDLER - + SHARED_DATABASE_API SHARED_EJSON @@ -28,9 +28,14 @@ create make_with_router -feature -- API DOC +feature -- Documentation - api_doc : STRING = "URI:/order METHOD: POST%N URI:/order/{orderid} METHOD: GET, HEAD, PUT, DELETE%N" + description: READABLE_STRING_GENERAL + -- General description for self-generated documentation; + -- The specific URI templates supported will be described automatically + do + Result := "Create, Read, Update or Delete an ORDER." + end feature -- Access @@ -93,7 +98,7 @@ feature -- Access -- 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. + -- Return Never_expires to indicate never expires. do -- All our responses are considered stale. end @@ -135,7 +140,7 @@ feature -- Access -- If `a_strong' then the strong comparison function must be used. local l_id: STRING - l_etag_util: ETAG_UTILS + l_etag_util: ETAG_UTILS do l_id := order_id_from_request (req) if db_access.orders.has_key (l_id) then @@ -155,8 +160,8 @@ feature -- Access create l_etag_utils if attached {ORDER} req.execution_variable ("ORDER") as l_order then Result := l_etag_utils.md5_digest (l_order.out) - end - end + end + end modified_since (req: WSF_REQUEST; a_date_time: DATE_TIME): BOOLEAN -- Has resource requested in `req' been modified since `a_date_time' (UTC)? @@ -164,7 +169,7 @@ feature -- Access -- We don't track this information. It is safe to always say yes. Result := True end - + feature -- Measurement content_length (req: WSF_REQUEST): NATURAL @@ -220,7 +225,7 @@ feature -- Execution order_saved_only_for_get_head: req.is_get_head_request_method = attached {ORDER} req.execution_variable ("ORDER") end - + feature -- GET/HEAD content ensure_content_available (req: WSF_REQUEST; @@ -254,7 +259,7 @@ feature -- GET/HEAD content Result := l_response end 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. @@ -270,7 +275,7 @@ feature -- GET/HEAD content do -- precondition `is_chunking' is never met end - + feature -- DELETE delete (req: WSF_REQUEST) @@ -315,7 +320,7 @@ feature -- PUT/POST -- No. We don't care for this example. end - check_content_headers (req: WSF_REQUEST) + 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. do @@ -334,14 +339,14 @@ feature -- PUT/POST -- 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. do - if attached {ORDER} req.execution_variable ("EXTRACTED_ORDER") as l_order then + if attached {ORDER} req.execution_variable ("EXTRACTED_ORDER") as l_order then save_order (l_order) compute_response_post (req, res, l_order) else handle_bad_request_response ("Not a valid order", req, res) end 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. @@ -350,17 +355,15 @@ feature -- PUT/POST if attached {ORDER} req.execution_variable ("EXTRACTED_ORDER") as l_order then if not is_valid_to_update (l_order) then req.set_execution_variable ("CONFLICT_CHECK_CODE", {NATURAL} 409) - --| FIXME: Here we need to define the Allow methods handle_resource_conflict_response (l_order.out +"%N There is conflict while trying to update the order, the order could not be update in the current state", req, res) end else req.set_execution_variable ("CONFLICT_CHECK_CODE", {NATURAL} 409) - --| FIXME: Here we need to define the Allow methods - --| This ought to be a 500, as if attached should probably be check attached. But as yet I lack a proof. TODO. + --| This ought to be a 500, as if attached should probably be check attached. But as yet I lack a proof. handle_resource_conflict_response ("There is conflict while trying to update the order, the order could not be update in the current state", req, res) 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")'. @@ -384,7 +387,7 @@ feature -- PUT/POST end else req.set_execution_variable ("REQUEST_CHECK_CODE", {NATURAL} 0) - req.set_execution_variable ("EXTRACTED_ORDER", l_order) + req.set_execution_variable ("EXTRACTED_ORDER", l_order) end else req.set_execution_variable ("REQUEST_CHECK_CODE", {NATURAL} 400) @@ -396,14 +399,14 @@ feature -- PUT/POST -- Perform the update requested in `req'. -- Write a response to `res' with a code of 204 or 500. do - if attached {ORDER} req.execution_variable ("EXTRACTED_ORDER") as l_order then + if attached {ORDER} req.execution_variable ("EXTRACTED_ORDER") as l_order then update_order (l_order) compute_response_put (req, res, l_order) else handle_internal_server_error (res) end end - + feature -- HTTP Methods compute_response_put (req: WSF_REQUEST; res: WSF_RESPONSE; l_order : ORDER) @@ -553,6 +556,6 @@ feature {NONE} -- Implementation Repository Layer end note - copyright: "2011-2012, Javier Velilla and others" + copyright: "2011-2013, Javier Velilla and others" license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)" end diff --git a/library/network/protocol/http/src/http_header.e b/library/network/protocol/http/src/http_header.e index 72e6fd37..d70ac062 100644 --- a/library/network/protocol/http/src/http_header.e +++ b/library/network/protocol/http/src/http_header.e @@ -350,7 +350,9 @@ feature -- Header change: general end add_header_key_value (k,v: READABLE_STRING_8) - -- Add header `k:v', or replace existing header of same header name/key + -- Add header `k:v'. + -- If it already exists, there will be multiple header with same name + -- which can also be valid local s: STRING_8 do @@ -509,10 +511,10 @@ feature -- Content related header put_header_key_value ({HTTP_HEADER_NAMES}.header_content_transfer_encoding, a_mechanism) end - put_content_language (a_enc: READABLE_STRING_8) - -- Put "Content-Language" header of value `a_enc'. + put_content_language (a_lang: READABLE_STRING_8) + -- Put "Content-Language" header of value `a_lang'. do - put_header_key_value ({HTTP_HEADER_NAMES}.header_content_language, a_enc) + put_header_key_value ({HTTP_HEADER_NAMES}.header_content_language, a_lang) end put_content_encoding (a_enc: READABLE_STRING_8) diff --git a/library/server/wsf/router/wsf_caching_policy.e b/library/server/wsf/router/wsf_caching_policy.e index 4c780b19..bc9a4687 100644 --- a/library/server/wsf/router/wsf_caching_policy.e +++ b/library/server/wsf/router/wsf_caching_policy.e @@ -10,16 +10,20 @@ deferred class WSF_CACHING_POLICY feature -- Access + Never_expires: NATURAL = 525600 + -- 525600 = 365 * 24 * 60 * 60 = (almost) 1 year; + -- See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.21 for an explanation of why this means never expire + 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. + -- Return `Never_expires' to indicate never expires. require req_attached: req /= Void deferred ensure - not_more_than_1_year: Result <= (365 * 1440).as_natural_32 + not_more_than_1_year: Result <= Never_expires end shared_age (req: WSF_REQUEST): NATURAL @@ -27,13 +31,13 @@ feature -- Access -- 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. + -- Return `Never_expires' 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 + not_more_than_1_year: Result <= Never_expires end http_1_0_age (req: WSF_REQUEST): NATURAL @@ -41,7 +45,7 @@ feature -- Access -- 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 + -- Return `Never_expires' 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 @@ -49,7 +53,7 @@ feature -- Access do Result := age (req) ensure - not_more_than_1_year: Result <= (365 * 1440).as_natural_32 + not_more_than_1_year: Result <= Never_expires end is_freely_cacheable (req: WSF_REQUEST): BOOLEAN @@ -81,7 +85,6 @@ feature -- Access -- 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? @@ -92,7 +95,6 @@ feature -- Access -- 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. @@ -122,4 +124,14 @@ feature -- Access deferred 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/router/wsf_delete_helper.e b/library/server/wsf/router/wsf_delete_helper.e index 6bc0e646..a8633d4b 100644 --- a/library/server/wsf/router/wsf_delete_helper.e +++ b/library/server/wsf/router/wsf_delete_helper.e @@ -11,15 +11,10 @@ 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; + send_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 deletion of resource named by `req' into `res' in accordance with `a_media_type' etc. local diff --git a/library/server/wsf/router/wsf_get_helper.e b/library/server/wsf/router/wsf_get_helper.e index e20dd50b..6085b979 100644 --- a/library/server/wsf/router/wsf_get_helper.e +++ b/library/server/wsf/router/wsf_get_helper.e @@ -12,4 +12,45 @@ inherit WSF_METHOD_HELPER +feature {NONE} -- Implementation + + send_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. + 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 + end diff --git a/library/server/wsf/router/wsf_method_helper.e b/library/server/wsf/router/wsf_method_helper.e index 6b23ebec..a37e9c3b 100644 --- a/library/server/wsf/router/wsf_method_helper.e +++ b/library/server/wsf/router/wsf_method_helper.e @@ -2,12 +2,19 @@ note description: "[ Policy-driven helpers to implement a method. - This implementation is suitable for GET and HEAD. + ]" date: "$Date$" revision: "$Revision$" -class WSF_METHOD_HELPER +deferred class WSF_METHOD_HELPER + +inherit + + HTTP_STATUS_CODE_MESSAGES + + SHARED_HTML_ENCODER + export {NONE} all end feature -- Access @@ -42,17 +49,17 @@ feature -- Basic operations if a_handler.resource_previously_existed (req) then if a_handler.resource_moved_permanently (req) then l_locs := a_handler.previous_location (req) - -- TODO 301 Moved permanently response + handle_redirection_error (req, res, l_locs, {HTTP_STATUS_CODE}.moved_permanently) elseif a_handler.resource_moved_temporarily (req) then l_locs := a_handler.previous_location (req) - -- TODO := 302 Found response + handle_redirection_error (req, res, l_locs, {HTTP_STATUS_CODE}.found) else 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) + res.put_header_lines (h) end else create h.make @@ -60,7 +67,7 @@ feature -- Basic operations h.put_current_date h.put_content_length (0) res.set_status_code ({HTTP_STATUS_CODE}.not_found) - res.put_header_text (h.string) + res.put_header_lines (h) end end @@ -78,7 +85,7 @@ feature -- Basic operations l_date: HTTP_DATE do if attached req.http_if_match as l_if_match then - -- TODO - also if-range when we add support for range requests + -- 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_failed := not across l_etags as i_etags some a_handler.matching_etag (req, i_etags.item, True) end @@ -105,14 +112,14 @@ feature -- Basic operations 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) + handle_if_none_match_failed (req, res, a_handler) 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) + handle_not_modified (req, res, a_handler) l_failed := True end end @@ -121,13 +128,13 @@ feature -- Basic operations end if not l_failed then handle_content_negotiation (req, res, a_handler, False) - end + end end end end 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'. @@ -163,7 +170,7 @@ feature {NONE} -- Implementation 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) + handle_not_acceptable ("None of the requested ContentTypes were acceptable", l_mime_types, req, res) else l_langs := a_handler.languages_supported (req) l_lang := l_conneg.language_preference (l_langs, req.http_accept_language) @@ -171,7 +178,7 @@ feature {NONE} -- Implementation 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) + handle_not_acceptable ("None of the requested languages were acceptable", l_langs, req, res) else if attached l_lang.language_type as l_language_type then h.put_content_language (l_language_type) @@ -182,7 +189,7 @@ feature {NONE} -- Implementation 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) + handle_not_acceptable ("None of the requested character encodings were acceptable", l_charsets, 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) @@ -193,13 +200,13 @@ feature {NONE} -- Implementation 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) + handle_not_acceptable ("None of the requested transfer encodings were acceptable", l_encodings, 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, + send_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 @@ -207,52 +214,19 @@ feature {NONE} -- Implementation end end - send_get_response (req: WSF_REQUEST; res: WSF_RESPONSE; a_handler: WSF_SKELETON_HANDLER; a_header: HTTP_HEADER; + send_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_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 + deferred end send_chunked_response (req: WSF_REQUEST; res: WSF_RESPONSE; a_handler: WSF_SKELETON_HANDLER; a_header: HTTP_HEADER; @@ -265,7 +239,7 @@ feature {NONE} -- Implementation 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 + a_compression_type_attached: a_compression_type /= Void local l_chunk: TUPLE [a_chunk: READABLE_STRING_8; a_extension: detachable READABLE_STRING_8] do @@ -290,11 +264,11 @@ feature {NONE} -- Implementation end end if a_handler.finished (req) then - -- TODO - support for trailers + -- In future, add 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 @@ -343,7 +317,7 @@ feature {NONE} -- Implementation 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 end append_field_name (a_field_names: STRING; a_fields: LIST [READABLE_STRING_8]) @@ -362,36 +336,139 @@ feature {NONE} -- Implementation feature -- Basic operations - handle_not_accepted (a_message: READABLE_STRING_8; req: WSF_REQUEST; res: WSF_RESPONSE) - -- Write a Not Accepted response to `res'. + handle_redirection_error (req: WSF_REQUEST; res: WSF_RESPONSE; a_locations: LIST [URI]; a_status_code: INTEGER) + -- Write `a_status_code' error to `res'. + -- Include all of `a_locations' in the headers, and hyperlink to the first one in the body. + require + res_attached: res /= Void + req_attached: req /= Void + a_locations_attached: a_locations /= Void + a_location_not_empty: not a_locations.is_empty + a_status_code_code: is_valid_http_status_code (a_status_code) + local + h: HTTP_HEADER + s: STRING + do + if attached http_status_code_message (a_status_code) as l_msg then + create h.make + across a_locations as i_location loop + h.add_header_key_value ({HTTP_HEADER_NAMES}.header_location, i_location.item.string) + end + if req.is_content_type_accepted ({HTTP_MIME_TYPES}.text_html) then + s := "" + s.append ("") + s.append (html_encoder.encoded_string (req.request_uri)) + s.append ("Error " + a_status_code.out + " (" + l_msg + ")") + s.append ("%N") + s.append ("[ + + + + ]") + s.append ("
Error " + a_status_code.out + " (" + l_msg + ")
") + s.append ("
") + s.append ("
") + s.append ("
") + s.append ("
") + s.append ("
") + s.append ("The current location for this resource is here") + s.append ("Error " + a_status_code.out + " (" + l_msg + ")
") + s.append ("
Error " + a_status_code.out + " (" + l_msg + "): " + html_encoder.encoded_string (req.request_uri) + "
") + s.append ("
") + s.append ("%N") + s.append ("%N") + + h.put_content_type_text_html + else + s := "Error " + a_status_code.out + " (" + l_msg + "): " + s.append (req.request_uri) + s.append_character ('%N') + s.append ("The current location for this resource is " + a_locations.first.string) + h.put_content_type_text_plain + end + h.put_content_length (s.count) + res.put_header_lines (h) + res.put_string (s) + res.flush + end + end + + handle_not_acceptable (a_message: READABLE_STRING_8; a_supported: LIST [STRING]; req: WSF_REQUEST; res: WSF_RESPONSE) + -- Write a Not Acceptable response to `res'. require req_attached: req /= Void res_attached: res /= Void a_message_attached: a_message /= Void + a_supported_attached: a_supported /= Void + local + h: HTTP_HEADER + s: STRING do - -- TODO: flag this if it gets to code review. + create h.make + h.put_content_type_text_plain + h.put_current_date + s := a_message + s.append_character ('%N') + s.append_character ('%N') + s.append_string ("We accept the following:%N%N") + across a_supported as i_supported loop + s.append_string (i_supported.item) + s.append_character ('%N') + end + h.put_content_length (s.count) + res.set_status_code ({HTTP_STATUS_CODE}.not_acceptable) + res.put_header_lines (h) + res.put_string (s) + res.flush end - handle_if_none_match_failed (req: WSF_REQUEST; res: WSF_RESPONSE) - -- Write a Precondition Failed response to `res'. + handle_if_none_match_failed (req: WSF_REQUEST; res: WSF_RESPONSE; a_handler: WSF_SKELETON_HANDLER) + -- Write a Not Modified response to `res'. require req_attached: req /= Void res_attached: res /= Void + a_handler_attached: a_handler /= Void do - -- TODO: flag this if it gets to code review. Why not just handle_precondition_failed? + handle_not_modified (req, res, a_handler) end - handle_not_modified (req: WSF_REQUEST; res: WSF_RESPONSE) - -- Write a Precondition Failed response to `res'. + handle_not_modified (req: WSF_REQUEST; res: WSF_RESPONSE; a_handler: WSF_SKELETON_HANDLER) + -- Write a Not Modified response to `res'. require req_attached: req /= Void res_attached: res /= Void + a_handler_attached: a_handler /= Void + local + h: HTTP_HEADER do - -- TODO: flag this if it gets to code review. Why not just handle_precondition_failed? + create h.make + h.put_content_type_text_plain + h.put_content_length (0) + if attached a_handler.etag (req, "", "", "", "") as l_etag then + -- FIXME: We aren't strictly conformant here, as we have not conducted content negotiation yet, + -- so we might not get an identical etag as for a successful (200 OK) request. + -- So we should conduct content negotiation at this point (and if not acceptable, we don't include an etag). + -- Add add any Vary header that might result. + -- Also, when we add support for the Content-Location header in responses, we need to send that here too. + h.put_header_key_value ({HTTP_HEADER_NAMES}.header_etag, l_etag) + end + generate_cache_headers (req, a_handler, h, create {DATE_TIME}.make_now_utc) + res.set_status_code ({HTTP_STATUS_CODE}.not_modified) + res.put_header_lines (h) end handle_precondition_failed (req: WSF_REQUEST; res: WSF_RESPONSE) - -- Write a precondition failed response for `req' to `res'. + -- Write a Precondition Failed response for `req' to `res'. require req_attached: req /= Void res_attached: res /= Void @@ -403,7 +480,59 @@ feature -- Basic operations h.put_current_date h.put_content_length (0) res.set_status_code ({HTTP_STATUS_CODE}.precondition_failed) - res.put_header_text (h.string) + res.put_header_lines (h) + end + + handle_unsupported_media_type (req: WSF_REQUEST; res: WSF_RESPONSE) + -- Write a Unsupported Media Type 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}.unsupported_media_type) + res.put_header_lines (h) + end + + handle_not_implemented (req: WSF_REQUEST; res: WSF_RESPONSE) + -- Write a Not Implemented 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}.not_implemented) + res.put_header_lines (h) + end + + handle_request_entity_too_large (req: WSF_REQUEST; res: WSF_RESPONSE; a_handler: WSF_SKELETON_HANDLER) + -- Write a Request Entity Too Large response for `req' to `res'. + require + req_attached: req /= Void + res_attached: res /= Void + a_handler_attached: a_handler /= 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}.request_entity_too_large) + res.put_header_lines (h) + -- FIXME: Need to check if condition is temporary. This needs a new query + -- on the handler. For now we can claim compliance by saying the condition + -- is always permenent :-) - author's might not like this though. end note diff --git a/library/server/wsf/router/wsf_method_helper_factory.e b/library/server/wsf/router/wsf_method_helper_factory.e index c41a6e3d..48f773c4 100644 --- a/library/server/wsf/router/wsf_method_helper_factory.e +++ b/library/server/wsf/router/wsf_method_helper_factory.e @@ -18,7 +18,7 @@ feature -- Factory do 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 - create Result + create {WSF_GET_HELPER} 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 @@ -27,5 +27,15 @@ feature -- Factory create {WSF_DELETE_HELPER} Result 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 diff --git a/library/server/wsf/router/wsf_post_helper.e b/library/server/wsf/router/wsf_post_helper.e index b05a40a7..41c54a64 100644 --- a/library/server/wsf/router/wsf_post_helper.e +++ b/library/server/wsf/router/wsf_post_helper.e @@ -11,11 +11,8 @@ class WSF_POST_HELPER inherit WSF_METHOD_HELPER - rename - send_get_response as do_post redefine - execute_new_resource, - do_post + execute_new_resource end feature -- Basic operations @@ -23,48 +20,54 @@ 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 + res.send (create {WSF_NOT_FOUND_RESPONSE}.make(req)) end end - + feature {NONE} -- Implementation - do_post (req: WSF_REQUEST; res: WSF_RESPONSE; a_handler: WSF_SKELETON_HANDLER; a_header: HTTP_HEADER; + send_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. as a new URI. + local + l_code: NATURAL 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 + l_code := a_handler.content_check_code (req) + if l_code /= 0 then + if l_code = 415 then + handle_unsupported_media_type (req, res) + else + handle_not_implemented (req, res) + end 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 + -- FIXME: 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 - +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/router/wsf_put_helper.e b/library/server/wsf/router/wsf_put_helper.e index 67f123b8..6adedbbf 100644 --- a/library/server/wsf/router/wsf_put_helper.e +++ b/library/server/wsf/router/wsf_put_helper.e @@ -11,11 +11,8 @@ class WSF_PUT_HELPER inherit WSF_METHOD_HELPER - rename - send_get_response as do_put redefine - execute_new_resource, - do_put + execute_new_resource end feature -- Basic operations @@ -25,7 +22,7 @@ feature -- Basic operations -- Policy routines are available in `a_handler'. do if a_handler.treat_as_moved_permanently (req) then - -- TODO 301 Moved permanently response (single location) + handle_redirection_error (req, res, a_handler.previous_location (req), {HTTP_STATUS_CODE}.moved_permanently) else handle_content_negotiation (req, res, a_handler, True) end @@ -34,17 +31,24 @@ feature -- Basic operations feature {NONE} -- Implementation - do_put (req: WSF_REQUEST; res: WSF_RESPONSE; a_handler: WSF_SKELETON_HANDLER; a_header: HTTP_HEADER; + send_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. as a new URI. + local + l_code: NATURAL 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 + l_code := a_handler.content_check_code (req) + if l_code /= 0 then + if l_code = 415 then + handle_unsupported_media_type (req, res) + else + handle_not_implemented (req, res) + end else a_handler.check_request (req, res) if a_handler.request_check_code (req) = 0 then @@ -56,20 +60,12 @@ feature {NONE} -- Implementation 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 + -- FIXME: 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 diff --git a/library/server/wsf/router/wsf_skeleton_handler.e b/library/server/wsf/router/wsf_skeleton_handler.e index aa8d01c3..cd7bc6d0 100644 --- a/library/server/wsf/router/wsf_skeleton_handler.e +++ b/library/server/wsf/router/wsf_skeleton_handler.e @@ -24,6 +24,8 @@ inherit WSF_METHOD_HELPER_FACTORY + WSF_SELF_DOCUMENTED_HANDLER + feature -- Access is_chunking (req: WSF_REQUEST): BOOLEAN @@ -42,7 +44,7 @@ feature -- Access end conneg (req: WSF_REQUEST): CONNEG_SERVER_SIDE - -- Content negotiatior for `req'; + -- Content negotiation for `req'; -- This would normally be a once object, ignoring `req'. require req_attached: req /= Void @@ -161,6 +163,25 @@ feature -- Status report deferred end + +feature -- Documentation + + mapping_documentation (m: WSF_ROUTER_MAPPING; a_request_methods: detachable WSF_REQUEST_METHODS): WSF_ROUTER_MAPPING_DOCUMENTATION + -- Documentation associated with Current handler, in the context of the mapping `m' and methods `a_request_methods' + --| `m' and `a_request_methods' are useful to produce specific documentation when the handler is used for multiple mapping. + do + create Result.make (m) + Result.add_description (description) + end + + description: READABLE_STRING_GENERAL + -- General description for self-generated documentation; + -- The specific URI templates supported will be described automatically + deferred + ensure + description_attached: Result /= Void + end + feature -- DELETE delete (req: WSF_REQUEST) diff --git a/library/server/wsf/src/wsf_request.e b/library/server/wsf/src/wsf_request.e index 572d5a28..61a098e9 100644 --- a/library/server/wsf/src/wsf_request.e +++ b/library/server/wsf/src/wsf_request.e @@ -376,6 +376,8 @@ feature -- Helper end end Result := l_accept.has_substring (a_content_type) + else + Result := True end end From 3249c377f1204d6710010bf43d5750f58819de90 Mon Sep 17 00:00:00 2001 From: Colin Adams Date: Thu, 8 Aug 2013 07:31:43 +0100 Subject: [PATCH 10/24] made deleted into an effective routine --- examples/restbucksCRUD/src/resource/order_handler.e | 8 -------- library/server/wsf/router/wsf_skeleton_handler.e | 7 ++++++- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/examples/restbucksCRUD/src/resource/order_handler.e b/examples/restbucksCRUD/src/resource/order_handler.e index 499e5695..8a24693f 100644 --- a/examples/restbucksCRUD/src/resource/order_handler.e +++ b/examples/restbucksCRUD/src/resource/order_handler.e @@ -297,14 +297,6 @@ feature -- DELETE end end - deleted (req: WSF_REQUEST): BOOLEAN - -- Has resource named by `req' been deleted? - do - if not req.error_handler.has_error then - Result := True - end - end - delete_queued (req: WSF_REQUEST): BOOLEAN -- Has resource named by `req' been queued for deletion? do diff --git a/library/server/wsf/router/wsf_skeleton_handler.e b/library/server/wsf/router/wsf_skeleton_handler.e index cd7bc6d0..b7ad0b39 100644 --- a/library/server/wsf/router/wsf_skeleton_handler.e +++ b/library/server/wsf/router/wsf_skeleton_handler.e @@ -197,7 +197,12 @@ feature -- DELETE -- Has resource named by `req' been deleted? require req_attached: req /= Void - deferred + do + if not req.error_handler.has_error then + Result := True + end + ensure + negative_implication: not Result implies req.error_handler.has_error end delete_queued (req: WSF_REQUEST): BOOLEAN From eade6d584c847ccc27ac5ac7189bcdb558266573 Mon Sep 17 00:00:00 2001 From: Colin Adams Date: Thu, 8 Aug 2013 09:33:21 +0100 Subject: [PATCH 11/24] Errors corrected that were discovered in the course of writing the tutorial --- library/server/wsf/router/wsf_skeleton_handler.e | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/server/wsf/router/wsf_skeleton_handler.e b/library/server/wsf/router/wsf_skeleton_handler.e index b7ad0b39..a69a6296 100644 --- a/library/server/wsf/router/wsf_skeleton_handler.e +++ b/library/server/wsf/router/wsf_skeleton_handler.e @@ -225,7 +225,7 @@ feature -- GET/HEAD content -- 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 + get_or_head_or_delete: req.is_get_head_request_method or req.is_delete_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 @@ -347,7 +347,7 @@ feature -- PUT/POST end check_conflict (req: WSF_REQUEST; res: WSF_RESPONSE) - -- Check we can support all content headers on request entity. + -- Check to see if updating the resource is problematic due to the current state of the resource. -- 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 From 4c901c31308991c5a701d57ff58e2a798ef5e643 Mon Sep 17 00:00:00 2001 From: Colin Adams Date: Thu, 8 Aug 2013 10:39:46 +0100 Subject: [PATCH 12/24] Implemented remaining error response calls --- library/server/wsf/router/wsf_delete_helper.e | 3 +- library/server/wsf/router/wsf_method_helper.e | 30 +++++++++++++++---- .../server/wsf/router/wsf_skeleton_handler.e | 1 + .../utility/general/error/src/error_handler.e | 10 +++++++ 4 files changed, 37 insertions(+), 7 deletions(-) diff --git a/library/server/wsf/router/wsf_delete_helper.e b/library/server/wsf/router/wsf_delete_helper.e index a8633d4b..77baf03d 100644 --- a/library/server/wsf/router/wsf_delete_helper.e +++ b/library/server/wsf/router/wsf_delete_helper.e @@ -47,8 +47,7 @@ feature {NONE} -- Implementation 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) + write_error_response (req, res) end end diff --git a/library/server/wsf/router/wsf_method_helper.e b/library/server/wsf/router/wsf_method_helper.e index a37e9c3b..ca527161 100644 --- a/library/server/wsf/router/wsf_method_helper.e +++ b/library/server/wsf/router/wsf_method_helper.e @@ -248,8 +248,7 @@ feature {NONE} -- Implementation 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) + write_error_response (req, res) end until a_handler.finished (req) or not a_handler.response_ok (req) @@ -259,8 +258,7 @@ feature {NONE} -- Implementation 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) + write_error_response (req, res) end end if a_handler.finished (req) then @@ -334,7 +332,29 @@ feature {NONE} -- Implementation end end -feature -- Basic operations +feature -- Errors + + +feature -- Error reporting + + write_error_response (req: WSF_REQUEST; res: WSF_RESPONSE) + -- Write an error response to `res' using `req.error_handler'. + require + req_attached: req /= Void + res_attached: res /= Void + req_has_error: req.has_error + local + h: HTTP_HEADER + m: READABLE_STRING_8 + do + m := req.error_handler.as_string_representation + create h.make + h.put_content_type_text_plain + h.put_content_length (m.count) + res.set_status_code (req.error_handler.primary_error_code) + res.put_header_lines (h) + res.put_string (m) + end handle_redirection_error (req: WSF_REQUEST; res: WSF_RESPONSE; a_locations: LIST [URI]; a_status_code: INTEGER) -- Write `a_status_code' error to `res'. diff --git a/library/server/wsf/router/wsf_skeleton_handler.e b/library/server/wsf/router/wsf_skeleton_handler.e index a69a6296..33a71d51 100644 --- a/library/server/wsf/router/wsf_skeleton_handler.e +++ b/library/server/wsf/router/wsf_skeleton_handler.e @@ -464,6 +464,7 @@ feature {NONE} -- Implementation h.put_content_length (m.count) h.put_current_date res.set_status_code ({HTTP_STATUS_CODE}.internal_server_error) + res.put_header_lines (h) res.put_string (m) ensure response_status_is_set: res.status_is_set diff --git a/library/utility/general/error/src/error_handler.e b/library/utility/general/error/src/error_handler.e index e6ff72fa..f112a0c5 100644 --- a/library/utility/general/error/src/error_handler.e +++ b/library/utility/general/error/src/error_handler.e @@ -25,6 +25,16 @@ feature {NONE} -- Initialization create error_added_actions end +feature -- Access + + primary_error_code: INTEGER + -- Code of first error in `errors' + require + at_least_one_error: has_error + do + Result := errors.first.code + end + feature -- Status has_error: BOOLEAN From f82456f3526aff7309c1dc87e74ef28c40a8a213 Mon Sep 17 00:00:00 2001 From: Colin Adams Date: Thu, 8 Aug 2013 17:13:38 +0100 Subject: [PATCH 13/24] Found another TODO - write_error_response in GET processing --- library/server/wsf/router/wsf_get_helper.e | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/library/server/wsf/router/wsf_get_helper.e b/library/server/wsf/router/wsf_get_helper.e index 6085b979..7200f6f1 100644 --- a/library/server/wsf/router/wsf_get_helper.e +++ b/library/server/wsf/router/wsf_get_helper.e @@ -37,8 +37,7 @@ feature {NONE} -- Implementation 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) + write_error_response (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) @@ -52,5 +51,15 @@ feature {NONE} -- Implementation end 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 From 2ed362f5d39ec2278ad3e87fced982ec11b60eb2 Mon Sep 17 00:00:00 2001 From: Colin Adams Date: Mon, 12 Aug 2013 09:27:00 +0100 Subject: [PATCH 14/24] refactored to allow etags to work properly when multiple representations are available --- .../src/resource/order_handler.e | 12 +- library/server/wsf/router/wsf_delete_helper.e | 16 ++- library/server/wsf/router/wsf_get_helper.e | 16 ++- library/server/wsf/router/wsf_method_helper.e | 122 +++++++++++------- library/server/wsf/router/wsf_post_helper.e | 13 +- library/server/wsf/router/wsf_put_helper.e | 9 +- .../server/wsf/router/wsf_skeleton_handler.e | 89 ++++++++----- 7 files changed, 172 insertions(+), 105 deletions(-) diff --git a/examples/restbucksCRUD/src/resource/order_handler.e b/examples/restbucksCRUD/src/resource/order_handler.e index 8a24693f..ece4c63e 100644 --- a/examples/restbucksCRUD/src/resource/order_handler.e +++ b/examples/restbucksCRUD/src/resource/order_handler.e @@ -152,7 +152,7 @@ feature -- Access end end - etag (req: WSF_REQUEST; a_media_type, a_language_type, a_character_type, a_compression_type: READABLE_STRING_8): detachable READABLE_STRING_8 + etag (req: WSF_REQUEST): detachable READABLE_STRING_8 -- Optional Etag for `req' in the requested variant local l_etag_utils: ETAG_UTILS @@ -228,14 +228,14 @@ feature -- Execution 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) + ensure_content_available (req: WSF_REQUEST) -- 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. -- We save the text in `req.execution_variable ("GENERATED_CONTENT")' + -- We ignore the results of content negotiation, as there is only one possible combination. do check attached {ORDER} req.execution_variable ("ORDER") as l_order then -- precondition get_or_head and postcondition order_saved_only_for_get_head of `check_resource_exists' and @@ -250,7 +250,7 @@ feature -- GET/HEAD content attached {READABLE_STRING_8} req.execution_variable ("GENERATED_CONTENT") end - content (req: WSF_REQUEST; a_media_type, a_language_type, a_character_type, a_compression_type: READABLE_STRING_8): READABLE_STRING_8 + content (req: WSF_REQUEST): READABLE_STRING_8 -- Non-chunked entity body in response to `req'; -- We only call this for GET/HEAD in this example. do @@ -260,7 +260,7 @@ feature -- GET/HEAD content end 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 (req: WSF_REQUEST): TUPLE [a_chunk: 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. do @@ -269,7 +269,7 @@ feature -- GET/HEAD content Result := ["", Void] end - generate_next_chunk (req: WSF_REQUEST; a_media_type, a_language_type, a_character_type, a_compression_type: READABLE_STRING_8) + generate_next_chunk (req: WSF_REQUEST) -- Prepare next chunk (including optional chunk extension) of entity body in response to `req'. -- This is not called for the first chunk. do diff --git a/library/server/wsf/router/wsf_delete_helper.e b/library/server/wsf/router/wsf_delete_helper.e index 77baf03d..751e0adf 100644 --- a/library/server/wsf/router/wsf_delete_helper.e +++ b/library/server/wsf/router/wsf_delete_helper.e @@ -14,15 +14,19 @@ inherit feature {NONE} -- Implementation - send_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 deletion of resource named by `req' into `res' in accordance with `a_media_type' etc. + send_response (req: WSF_REQUEST; res: WSF_RESPONSE; a_handler: WSF_SKELETON_HANDLER; a_header: HTTP_HEADER; a_new_resource: BOOLEAN) + -- Write response to deletion of resource named by `req' into `res'. + -- Upto four execution variables may be set on `req': + -- "NEGOTIATED_MEDIA_TYPE" + -- "NEGOTIATED_LANGUAGE" + -- "NEGOTIATED_CHARSET" + -- "NEGOTIATED_ENCODING" 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_handler.ensure_content_available (req) a_header.put_content_length (a_handler.content_length (req).as_integer_32) -- we don't bother supporting chunked responses for DELETE. else @@ -36,12 +40,12 @@ feature {NONE} -- Implementation 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)) + res.put_string (a_handler.content (req)) 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)) + res.put_string (a_handler.content (req)) else res.set_status_code ({HTTP_STATUS_CODE}.no_content) res.put_header_text (a_header.string) diff --git a/library/server/wsf/router/wsf_get_helper.e b/library/server/wsf/router/wsf_get_helper.e index 7200f6f1..c5e7a8cb 100644 --- a/library/server/wsf/router/wsf_get_helper.e +++ b/library/server/wsf/router/wsf_get_helper.e @@ -14,14 +14,18 @@ inherit feature {NONE} -- Implementation - send_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) + send_response (req: WSF_REQUEST; res: WSF_RESPONSE; a_handler: WSF_SKELETON_HANDLER; a_header: HTTP_HEADER; a_new_resource: BOOLEAN) -- Write response to `req' into `res' in accordance with `a_media_type' etc. + -- Upto four execution variables may be set on `req': + -- "NEGOTIATED_MEDIA_TYPE" + -- "NEGOTIATED_LANGUAGE" + -- "NEGOTIATED_CHARSET" + -- "NEGOTIATED_ENCODING" 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) + a_handler.ensure_content_available (req) l_chunked := a_handler.is_chunking (req) if l_chunked then a_header.put_transfer_encoding_chunked @@ -39,15 +43,15 @@ feature {NONE} -- Implementation else write_error_response (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 + if attached a_handler.etag (req) 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) + send_chunked_response (req, res, a_handler, a_header) else - res.put_string (a_handler.content (req, a_media_type, a_language_type, a_character_type, a_compression_type)) + res.put_string (a_handler.content (req)) end end end diff --git a/library/server/wsf/router/wsf_method_helper.e b/library/server/wsf/router/wsf_method_helper.e index ca527161..899e28b0 100644 --- a/library/server/wsf/router/wsf_method_helper.e +++ b/library/server/wsf/router/wsf_method_helper.e @@ -36,15 +36,19 @@ 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'. - -- This default implementation does not apply for PUT requests. - -- The behaviour for POST requests depends upon a policy. + -- Upto four execution variables may be set on `req': + -- "NEGOTIATED_MEDIA_TYPE" + -- "NEGOTIATED_LANGUAGE" + -- "NEGOTIATED_CHARSET" + -- "NEGOTIATED_ENCODING" + -- An HTTP_HEADER is also available as the execution variable "NEGOTIATED_HTTP_HEADER". + -- It includes the Vary header (if any) require req_attached: req /= Void res_attached: res /= Void a_handler_attached: a_handler /= Void local l_locs: LIST [URI] - h: HTTP_HEADER do if a_handler.resource_previously_existed (req) then if a_handler.resource_moved_permanently (req) then @@ -54,26 +58,35 @@ feature -- Basic operations l_locs := a_handler.previous_location (req) handle_redirection_error (req, res, l_locs, {HTTP_STATUS_CODE}.found) else - create h.make + check attached {HTTP_HEADER} req.execution_variable ("NEGOTIATED_HTTP_HEADER") as h then + -- postcondition header_attached of `handle_content_negotiation' + 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_lines (h) + end + end + else + check attached {HTTP_HEADER} req.execution_variable ("NEGOTIATED_HTTP_HEADER") as h then + -- postcondition header_attached of `handle_content_negotiation' h.put_content_type_text_plain h.put_current_date h.put_content_length (0) - res.set_status_code ({HTTP_STATUS_CODE}.gone) + res.set_status_code ({HTTP_STATUS_CODE}.not_found) res.put_header_lines (h) end - else - 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_lines (h) end end execute_existing_resource (req: WSF_REQUEST; res: WSF_RESPONSE; a_handler: WSF_SKELETON_HANDLER) -- Write response to existing resource requested by `req' into `res'. -- Policy routines are available in `a_handler'. + -- Upto four execution variables may be set on `req': + -- "NEGOTIATED_MEDIA_TYPE" + -- "NEGOTIATED_LANGUAGE" + -- "NEGOTIATED_CHARSET" + -- "NEGOTIATED_ENCODING" require req_attached: req /= Void res_attached: res /= Void @@ -127,19 +140,28 @@ feature -- Basic operations end end if not l_failed then - handle_content_negotiation (req, res, a_handler, False) + check attached {HTTP_HEADER} req.execution_variable ("NEGOTIATED_HTTP_HEADER") as h then + -- postcondition header_attached of `handle_content_negotiation' + send_response (req, res, a_handler, h, False) + end end end end end -feature {NONE} -- Implementation +feature -- Content negotiation - handle_content_negotiation (req: WSF_REQUEST; res: WSF_RESPONSE; - a_handler: WSF_SKELETON_HANDLER; a_new_resource: BOOLEAN) + handle_content_negotiation (req: WSF_REQUEST; res: WSF_RESPONSE; a_handler: WSF_SKELETON_HANDLER) -- 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. + -- + -- Either a 406 Not Acceptable error is sent, or upto four execution variables may be set on `req': + -- "NEGOTIATED_MEDIA_TYPE" + -- "NEGOTIATED_LANGUAGE" + -- "NEGOTIATED_CHARSET" + -- "NEGOTIATED_ENCODING" + -- An HTTP_HEADER is also saved as the execution variable "NEGOTIATED_HTTP_HEADER". + -- It includes the Vary header (if any) require req_attached: req /= Void res_attached: res /= Void @@ -182,6 +204,7 @@ feature {NONE} -- Implementation else if attached l_lang.language_type as l_language_type then h.put_content_language (l_language_type) + req.set_execution_variable ("NEGOTIATED_LANGUAGE", l_language_type) end l_charsets := a_handler.charsets_supported (req) l_charset := l_conneg.charset_preference (l_charsets, req.http_accept_charset) @@ -191,8 +214,14 @@ feature {NONE} -- Implementation if not l_charset.is_acceptable then handle_not_acceptable ("None of the requested character encodings were acceptable", l_charsets, 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) + if attached l_media.media_type as l_media_type then + if attached l_charset.character_type as l_character_type then + h.put_content_type (l_media_type + "; charset=" + l_character_type) + req.set_execution_variable ("NEGOTIATED_CHARSET", l_charset) + else + h.put_content_type (l_media_type) + end + req.set_execution_variable ("NEGOTIATED_MEDIA_TYPE", l_media_type) end l_encodings := a_handler.encodings_supported (req) l_encoding := l_conneg.encoding_preference (l_encodings, req.http_accept_encoding) @@ -204,48 +233,51 @@ feature {NONE} -- Implementation else if attached l_encoding.compression_type as l_compression_type then h.put_content_encoding (l_compression_type) + req.set_execution_variable ("NEGOTIATED_ENCODING", l_compression_type) end - -- We do not support multiple choices, so - send_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 + req.set_execution_variable ("NEGOTIATED_HTTP_HEADER", h) + ensure + header_attached: attached {HTTP_HEADER} req.execution_variable ("NEGOTIATED_HTTP_HEADER") end - send_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. +feature {NONE} -- Implementation + + send_response (req: WSF_REQUEST; res: WSF_RESPONSE; a_handler: WSF_SKELETON_HANDLER; a_header: HTTP_HEADER; a_new_resource: BOOLEAN) + -- Write response to `req' into `res'. + -- Upto four execution variables may be set on `req': + -- "NEGOTIATED_MEDIA_TYPE" + -- "NEGOTIATED_LANGUAGE" + -- "NEGOTIATED_CHARSET" + -- "NEGOTIATED_ENCODING" 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 deferred 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. + send_chunked_response (req: WSF_REQUEST; res: WSF_RESPONSE; a_handler: WSF_SKELETON_HANDLER; a_header: HTTP_HEADER) + -- Write response in chunks to `req'. + -- Upto four execution variables may be set on `req': + -- "NEGOTIATED_MEDIA_TYPE" + -- "NEGOTIATED_LANGUAGE" + -- "NEGOTIATED_CHARSET" + -- "NEGOTIATED_ENCODING" 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) + l_chunk := a_handler.next_chunk (req) res.put_chunk (l_chunk.a_chunk, l_chunk.a_extension) else write_error_response (req, res) @@ -253,9 +285,9 @@ feature {NONE} -- Implementation 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) + a_handler.generate_next_chunk (req) 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) + l_chunk := a_handler.next_chunk (req) res.put_chunk (l_chunk.a_chunk, l_chunk.a_extension) else write_error_response (req, res) @@ -464,6 +496,11 @@ feature -- Error reporting handle_not_modified (req: WSF_REQUEST; res: WSF_RESPONSE; a_handler: WSF_SKELETON_HANDLER) -- Write a Not Modified response to `res'. + -- Upto four execution variables may be set on `req': + -- "NEGOTIATED_MEDIA_TYPE" + -- "NEGOTIATED_LANGUAGE" + -- "NEGOTIATED_CHARSET" + -- "NEGOTIATED_ENCODING" require req_attached: req /= Void res_attached: res /= Void @@ -474,12 +511,7 @@ feature -- Error reporting create h.make h.put_content_type_text_plain h.put_content_length (0) - if attached a_handler.etag (req, "", "", "", "") as l_etag then - -- FIXME: We aren't strictly conformant here, as we have not conducted content negotiation yet, - -- so we might not get an identical etag as for a successful (200 OK) request. - -- So we should conduct content negotiation at this point (and if not acceptable, we don't include an etag). - -- Add add any Vary header that might result. - -- Also, when we add support for the Content-Location header in responses, we need to send that here too. + if attached a_handler.etag (req) as l_etag then h.put_header_key_value ({HTTP_HEADER_NAMES}.header_etag, l_etag) end generate_cache_headers (req, a_handler, h, create {DATE_TIME}.make_now_utc) diff --git a/library/server/wsf/router/wsf_post_helper.e b/library/server/wsf/router/wsf_post_helper.e index 41c54a64..f3d4d44c 100644 --- a/library/server/wsf/router/wsf_post_helper.e +++ b/library/server/wsf/router/wsf_post_helper.e @@ -22,7 +22,10 @@ feature -- Basic operations -- Policy routines are available in `a_handler'. do if a_handler.allow_post_to_missing_resource (req) then - handle_content_negotiation (req, res, a_handler, True) + check attached {HTTP_HEADER} req.execution_variable ("NEGOTIATED_HTTP_HEADER") as h then + -- postcondition header_attached of `handle_content_negotiation' + send_response (req, res, a_handler, h, True) + end else res.send (create {WSF_NOT_FOUND_RESPONSE}.make(req)) end @@ -31,9 +34,13 @@ feature -- Basic operations feature {NONE} -- Implementation - send_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) + send_response (req: WSF_REQUEST; res: WSF_RESPONSE; a_handler: WSF_SKELETON_HANDLER; a_header: HTTP_HEADER; a_new_resource: BOOLEAN) -- Write response to `req' into `res' in accordance with `a_media_type' etc. as a new URI. + -- Upto four execution variables may be set on `req': + -- "NEGOTIATED_MEDIA_TYPE" + -- "NEGOTIATED_LANGUAGE" + -- "NEGOTIATED_CHARSET" + -- "NEGOTIATED_ENCODING" local l_code: NATURAL do diff --git a/library/server/wsf/router/wsf_put_helper.e b/library/server/wsf/router/wsf_put_helper.e index 6adedbbf..02b26f0d 100644 --- a/library/server/wsf/router/wsf_put_helper.e +++ b/library/server/wsf/router/wsf_put_helper.e @@ -24,15 +24,16 @@ feature -- Basic operations if a_handler.treat_as_moved_permanently (req) then handle_redirection_error (req, res, a_handler.previous_location (req), {HTTP_STATUS_CODE}.moved_permanently) else - handle_content_negotiation (req, res, a_handler, True) + check attached {HTTP_HEADER} req.execution_variable ("NEGOTIATED_HTTP_HEADER") as h then + -- postcondition header_attached of `handle_content_negotiation' + send_response (req, res, a_handler, h, True) + end end end - feature {NONE} -- Implementation - send_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) + send_response (req: WSF_REQUEST; res: WSF_RESPONSE; a_handler: WSF_SKELETON_HANDLER; a_header: HTTP_HEADER; a_new_resource: BOOLEAN) -- Write response to `req' into `res' in accordance with `a_media_type' etc. as a new URI. local l_code: NATURAL diff --git a/library/server/wsf/router/wsf_skeleton_handler.e b/library/server/wsf/router/wsf_skeleton_handler.e index 33a71d51..b246af4c 100644 --- a/library/server/wsf/router/wsf_skeleton_handler.e +++ b/library/server/wsf/router/wsf_skeleton_handler.e @@ -109,14 +109,15 @@ feature -- Access 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 + etag (req: WSF_REQUEST): detachable READABLE_STRING_8 + -- Optional Etag for response entity to `req'; + -- Upto four execution variables may be set on `req': + -- "NEGOTIATED_MEDIA_TYPE" + -- "NEGOTIATED_LANGUAGE" + -- "NEGOTIATED_CHARSET" + -- "NEGOTIATED_ENCODING" 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 @@ -216,20 +217,23 @@ feature -- DELETE 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) + ensure_content_available (req: WSF_REQUEST) -- 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. + -- Upto four execution variables may be set on `req': + -- "NEGOTIATED_MEDIA_TYPE" + -- "NEGOTIATED_LANGUAGE" + -- "NEGOTIATED_CHARSET" + -- "NEGOTIATED_ENCODING" + -- If you support etags, and you have more than one possible representation + -- for the resource (so that your etag depends upon the particular representation), + -- then you will probably have already created the response entity in `check_resource_exists'. require req_attached: req /= Void get_or_head_or_delete: req.is_get_head_request_method or req.is_delete_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 @@ -243,45 +247,48 @@ feature -- GET/HEAD content 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' + content (req: WSF_REQUEST): READABLE_STRING_8 + -- Non-chunked entity body in response to `req'; + -- Upto four execution variables may be set on `req': + -- "NEGOTIATED_MEDIA_TYPE" + -- "NEGOTIATED_LANGUAGE" + -- "NEGOTIATED_CHARSET" + -- "NEGOTIATED_ENCODING" 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) + generate_next_chunk (req: WSF_REQUEST) -- Prepare next chunk (including optional chunk extension) of entity body in response to `req'. -- This is not called for the first chunk. + -- Upto four execution variables may be set on `req': + -- "NEGOTIATED_MEDIA_TYPE" + -- "NEGOTIATED_LANGUAGE" + -- "NEGOTIATED_CHARSET" + -- "NEGOTIATED_ENCODING" 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 (req: WSF_REQUEST): 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. + -- Four execution variables are set on `req': + -- "NEGOTIATED_MEDIA_TYPE" + -- "NEGOTIATED_LANGUAGE" + -- "NEGOTIATED_CHARSET" + -- "NEGOTIATED_ENCODING" 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 @@ -427,14 +434,17 @@ feature -- Execution res_attached: res /= Void a_helper_attached: a_helper /= Void do - check_resource_exists (req, a_helper) - if a_helper.resource_exists then - a_helper.execute_existing_resource (req, res, Current) - else - if attached req.http_if_match as l_if_match and then l_if_match.same_string ("*") then - a_helper.handle_precondition_failed (req, res) + a_helper.handle_content_negotiation (req, res, Current) + if not res.status_is_set or else res.status_code /= {HTTP_STATUS_CODE}.Not_acceptable then + check_resource_exists (req, a_helper) + if a_helper.resource_exists then + a_helper.execute_existing_resource (req, res, Current) else - a_helper.execute_new_resource (req, res, Current) + if attached req.http_if_match as l_if_match and then l_if_match.same_string ("*") then + a_helper.handle_precondition_failed (req, res) + else + a_helper.execute_new_resource (req, res, Current) + end end end end @@ -442,6 +452,15 @@ feature -- Execution check_resource_exists (req: WSF_REQUEST; a_helper: WSF_METHOD_HELPER) -- Call `a_helper.set_resource_exists' to indicate that `req.path_translated' -- is the name of an existing resource. + -- Upto four execution variables may be set on `req': + -- "NEGOTIATED_MEDIA_TYPE" + -- "NEGOTIATED_LANGUAGE" + -- "NEGOTIATED_CHARSET" + -- "NEGOTIATED_ENCODING" + -- If you support etags, and you have more than one possible representation + -- for the resource (so that your etag depends upon the particular representation), + -- then you will probably need to create the response entity at this point, rather + -- than in `ensure_content_available'. require req_attached: req /= Void a_helper_attached: a_helper /= Void From b074570e994b5856f1fe2848aeadf5cc5a7e7dc1 Mon Sep 17 00:00:00 2001 From: Colin Adams Date: Mon, 12 Aug 2013 16:45:47 +0100 Subject: [PATCH 15/24] Added some checks for custom erros being set. --- library/server/wsf/router/wsf_delete_helper.e | 52 +++++++++++-------- library/server/wsf/router/wsf_get_helper.e | 25 +++++---- 2 files changed, 44 insertions(+), 33 deletions(-) diff --git a/library/server/wsf/router/wsf_delete_helper.e b/library/server/wsf/router/wsf_delete_helper.e index 751e0adf..15f08204 100644 --- a/library/server/wsf/router/wsf_delete_helper.e +++ b/library/server/wsf/router/wsf_delete_helper.e @@ -23,34 +23,42 @@ feature {NONE} -- Implementation -- "NEGOTIATED_ENCODING" local l_dt: STRING + l_ok: BOOLEAN do a_handler.delete (req) - if a_handler.includes_response_entity (req) then - a_handler.ensure_content_available (req) - 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)) - elseif a_handler.deleted (req) then + l_ok := a_handler.response_ok (req) + if l_ok then if a_handler.includes_response_entity (req) then - res.set_status_code ({HTTP_STATUS_CODE}.ok) + a_handler.ensure_content_available (req) + l_ok := a_handler.response_ok (req) + if l_ok then + a_header.put_content_length (a_handler.content_length (req).as_integer_32) + end + -- 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)) - else - res.set_status_code ({HTTP_STATUS_CODE}.no_content) - res.put_header_text (a_header.string) + 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)) + else + res.set_status_code ({HTTP_STATUS_CODE}.no_content) + res.put_header_text (a_header.string) + end end - else + end + if not l_ok then write_error_response (req, res) end end diff --git a/library/server/wsf/router/wsf_get_helper.e b/library/server/wsf/router/wsf_get_helper.e index c5e7a8cb..666518e2 100644 --- a/library/server/wsf/router/wsf_get_helper.e +++ b/library/server/wsf/router/wsf_get_helper.e @@ -26,18 +26,21 @@ feature {NONE} -- Implementation l_dt: STRING do a_handler.ensure_content_available (req) - 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 + 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) + end if l_ok then res.set_status_code ({HTTP_STATUS_CODE}.ok) else From c93e50a7e25a341460a1b9ea474d97a3060f2e53 Mon Sep 17 00:00:00 2001 From: Colin Adams Date: Tue, 13 Aug 2013 15:47:59 +0100 Subject: [PATCH 16/24] Gave symbolic names to execution variables used by the framework --- library/server/wsf/router/wsf_method_helper.e | 12 +++++----- .../server/wsf/router/wsf_skeleton_handler.e | 24 +++++++++++++++++-- 2 files changed, 28 insertions(+), 8 deletions(-) diff --git a/library/server/wsf/router/wsf_method_helper.e b/library/server/wsf/router/wsf_method_helper.e index 899e28b0..61783af8 100644 --- a/library/server/wsf/router/wsf_method_helper.e +++ b/library/server/wsf/router/wsf_method_helper.e @@ -204,7 +204,7 @@ feature -- Content negotiation else if attached l_lang.language_type as l_language_type then h.put_content_language (l_language_type) - req.set_execution_variable ("NEGOTIATED_LANGUAGE", l_language_type) + req.set_execution_variable (a_handler.Negotiated_language_execution_variable, l_language_type) end l_charsets := a_handler.charsets_supported (req) l_charset := l_conneg.charset_preference (l_charsets, req.http_accept_charset) @@ -217,11 +217,11 @@ feature -- Content negotiation if attached l_media.media_type as l_media_type then if attached l_charset.character_type as l_character_type then h.put_content_type (l_media_type + "; charset=" + l_character_type) - req.set_execution_variable ("NEGOTIATED_CHARSET", l_charset) + req.set_execution_variable (a_handler.Negotiated_charset_execution_variable, l_charset) else h.put_content_type (l_media_type) end - req.set_execution_variable ("NEGOTIATED_MEDIA_TYPE", l_media_type) + req.set_execution_variable (a_handler.Negotiated_media_type_execution_variable, l_media_type) end l_encodings := a_handler.encodings_supported (req) l_encoding := l_conneg.encoding_preference (l_encodings, req.http_accept_encoding) @@ -233,15 +233,15 @@ feature -- Content negotiation else if attached l_encoding.compression_type as l_compression_type then h.put_content_encoding (l_compression_type) - req.set_execution_variable ("NEGOTIATED_ENCODING", l_compression_type) + req.set_execution_variable (a_handler.Negotiated_encoding_execution_variable, l_compression_type) end end end end end - req.set_execution_variable ("NEGOTIATED_HTTP_HEADER", h) + req.set_execution_variable (a_handler.Negotiated_http_header_execution_variable, h) ensure - header_attached: attached {HTTP_HEADER} req.execution_variable ("NEGOTIATED_HTTP_HEADER") + header_attached: attached {HTTP_HEADER} req.execution_variable (a_handler.Negotiated_http_header_execution_variable) end feature {NONE} -- Implementation diff --git a/library/server/wsf/router/wsf_skeleton_handler.e b/library/server/wsf/router/wsf_skeleton_handler.e index b246af4c..780034ee 100644 --- a/library/server/wsf/router/wsf_skeleton_handler.e +++ b/library/server/wsf/router/wsf_skeleton_handler.e @@ -26,6 +26,26 @@ inherit WSF_SELF_DOCUMENTED_HANDLER +feature -- Execution variables + + Negotiated_language_execution_variable: STRING = "NEGOTIATED_LANGUAGE" + -- Execution variable set by framework + + Negotiated_charset_execution_variable: STRING = "NEGOTIATED_CHARSET" + -- Execution variable set by framework + + Negotiated_media_type_execution_variable: STRING = "NEGOTIATED_MEDIA_TYPE" + -- Execution variable set by framework + + Negotiated_encoding_execution_variable: STRING = "NEGOTIATED_ENCODING" + -- Execution variable set by framework + + Negotiated_http_header_execution_variable: STRING = "NEGOTIATED_HTTP_HEADER" + -- Execution variable set by framework + + Request_entity_execution_variable: STRING = "REQUEST_ENTITY" + -- Execution variable set by framework + feature -- Access is_chunking (req: WSF_REQUEST): BOOLEAN @@ -295,7 +315,7 @@ feature -- GET/HEAD content feature -- PUT/POST read_entity (req: WSF_REQUEST) - -- Read request body and set as `req.execution_variable ("REQUEST_ENTITY")'. + -- Read request body and set as `req.execution_variable (Request_entity_execution_variable)'. require req_attached: req /= Void local @@ -304,7 +324,7 @@ feature -- PUT/POST 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) + req.set_execution_variable (Request_entity_execution_variable, l_body) end end From 275c26b55b6e9239895d25f842325c281ec2ce63 Mon Sep 17 00:00:00 2001 From: Colin Adams Date: Wed, 14 Aug 2013 09:22:35 +0100 Subject: [PATCH 17/24] Further use of constants for execution variables --- .../src/resource/order_handler.e | 75 ++++++++++--------- library/server/wsf/router/wsf_method_helper.e | 18 ++--- library/server/wsf/router/wsf_post_helper.e | 2 +- .../server/wsf/router/wsf_previous_policy.e | 5 +- library/server/wsf/router/wsf_put_helper.e | 14 +++- .../server/wsf/router/wsf_skeleton_handler.e | 27 ++++--- 6 files changed, 83 insertions(+), 58 deletions(-) diff --git a/examples/restbucksCRUD/src/resource/order_handler.e b/examples/restbucksCRUD/src/resource/order_handler.e index ece4c63e..1ea6f836 100644 --- a/examples/restbucksCRUD/src/resource/order_handler.e +++ b/examples/restbucksCRUD/src/resource/order_handler.e @@ -28,6 +28,18 @@ create make_with_router + +feature -- Execution variables + + Order_execution_variable: STRING = "ORDER" + -- Execution variable used by application + + Generated_content_execution_variable: STRING = "GENERATED_CONTENT" + -- Execution variable used by application + + Extracted_order_execution_variable: STRING = "EXTRACTED_ORDER" + -- Execution variable used by application + feature -- Documentation description: READABLE_STRING_GENERAL @@ -87,13 +99,6 @@ feature -- Access Result.compare_objects end - previous_location (req: WSF_REQUEST): LIST [URI] - -- Previous location(s) for resource named by `req'; - do - -- precondition is never met but we need a non-void Result to satisfy the compiler in Void-safe mode: - create {LINKED_LIST [URI]} Result.make - end - 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. @@ -158,7 +163,7 @@ feature -- Access l_etag_utils: ETAG_UTILS do create l_etag_utils - if attached {ORDER} req.execution_variable ("ORDER") as l_order then + if attached {ORDER} req.execution_variable (Order_execution_variable) as l_order then Result := l_etag_utils.md5_digest (l_order.out) end end @@ -175,7 +180,7 @@ feature -- Measurement content_length (req: WSF_REQUEST): NATURAL -- Length of entity-body of the response to `req' do - check attached {READABLE_STRING_8} req.execution_variable ("GENERATED_CONTENT") as l_response then + check attached {READABLE_STRING_8} req.execution_variable (Generated_content_execution_variable) as l_response then -- postcondition generated_content_set_for_get_head of `ensure_content_available' -- We only call this for GET/HEAD in this example. Result := l_response.count.as_natural_32 @@ -201,7 +206,7 @@ feature -- Execution check_resource_exists (req: WSF_REQUEST; a_helper: WSF_METHOD_HELPER) -- Call `a_helper.set_resource_exists' to indicate that `req.path_translated' -- is the name of an existing resource. - -- We also put the order into `req.execution_variable ("ORDER")' for GET or HEAD responses. + -- We also put the order into `req.execution_variable (Order_execution_variable)' for GET or HEAD responses. local l_id: STRING do @@ -216,14 +221,14 @@ feature -- Execution if req.is_get_head_request_method then check attached db_access.orders.item (l_id) as l_order then -- postcondition `item_if_found' of `has_key' - req.set_execution_variable ("ORDER", l_order) + req.set_execution_variable (Order_execution_variable, l_order) end end end end ensure then order_saved_only_for_get_head: req.is_get_head_request_method = - attached {ORDER} req.execution_variable ("ORDER") + attached {ORDER} req.execution_variable (Order_execution_variable) end feature -- GET/HEAD content @@ -234,27 +239,27 @@ feature -- GET/HEAD content -- 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. - -- We save the text in `req.execution_variable ("GENERATED_CONTENT")' + -- We save the text in `req.execution_variable (Generated_content_execution_variable)' -- We ignore the results of content negotiation, as there is only one possible combination. do - check attached {ORDER} req.execution_variable ("ORDER") as l_order then + check attached {ORDER} req.execution_variable (Order_execution_variable) as l_order then -- precondition get_or_head and postcondition order_saved_only_for_get_head of `check_resource_exists' and if attached {JSON_VALUE} json.value (l_order) as jv then - req.set_execution_variable ("GENERATED_CONTENT", jv.representation) + req.set_execution_variable (Generated_content_execution_variable, jv.representation) else - req.set_execution_variable ("GENERATED_CONTENT", "") + req.set_execution_variable (Generated_content_execution_variable, "") end end ensure then generated_content_set_for_get_head: req.is_get_head_request_method implies - attached {READABLE_STRING_8} req.execution_variable ("GENERATED_CONTENT") + attached {READABLE_STRING_8} req.execution_variable (Generated_content_execution_variable) end content (req: WSF_REQUEST): READABLE_STRING_8 -- Non-chunked entity body in response to `req'; -- We only call this for GET/HEAD in this example. do - check attached {READABLE_STRING_8} req.execution_variable ("GENERATED_CONTENT") as l_response then + check attached {READABLE_STRING_8} req.execution_variable (Generated_content_execution_variable) as l_response then -- postcondition generated_content_set_for_get_head of `ensure_content_available' Result := l_response end @@ -307,14 +312,14 @@ feature -- DELETE feature -- PUT/POST is_entity_too_large (req: WSF_REQUEST): BOOLEAN - -- Is the entity stored in `req.execution_variable ("REQUEST_ENTITY")' too large for the application? + -- Is the entity stored in `req.execution_variable (Request_entity_execution_variable)' too large for the application? do -- No. We don't care for this example. 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. + -- Set `req.execution_variable (Content_check_code_execution_variable)' to {NATURAL} zero if OK, or 415 or 501 if not. do -- We don't bother for this example. Note that this is equivalent to setting zero. end @@ -331,7 +336,7 @@ feature -- PUT/POST -- 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. do - if attached {ORDER} req.execution_variable ("EXTRACTED_ORDER") as l_order then + if attached {ORDER} req.execution_variable (Extracted_order_execution_variable) as l_order then save_order (l_order) compute_response_post (req, res, l_order) else @@ -341,16 +346,16 @@ feature -- PUT/POST 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. + -- Set `req.execution_variable (Conflict_check_code_execution_variable)' to {NATURAL} zero if OK, or 409 if not. -- In the latter case, write the full error response to `res'. do - if attached {ORDER} req.execution_variable ("EXTRACTED_ORDER") as l_order then + if attached {ORDER} req.execution_variable (Extracted_order_execution_variable) as l_order then if not is_valid_to_update (l_order) then - req.set_execution_variable ("CONFLICT_CHECK_CODE", {NATURAL} 409) + req.set_execution_variable (Conflict_check_code_execution_variable, {NATURAL} 409) handle_resource_conflict_response (l_order.out +"%N There is conflict while trying to update the order, the order could not be update in the current state", req, res) end else - req.set_execution_variable ("CONFLICT_CHECK_CODE", {NATURAL} 409) + req.set_execution_variable (Conflict_check_code_execution_variable, {NATURAL} 409) --| This ought to be a 500, as if attached should probably be check attached. But as yet I lack a proof. handle_resource_conflict_response ("There is conflict while trying to update the order, the order could not be update in the current state", req, res) end @@ -358,31 +363,31 @@ feature -- PUT/POST 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. + -- The entity is available as `req.execution_variable (Conflict_check_code_execution_variable)'. + -- Set `req.execution_variable (Request_check_code_execution_variable)' to {NATURAL} zero if OK, or 400 if not. -- In the latter case, write the full error response to `res'. local l_order: detachable ORDER l_id: STRING do - if attached {READABLE_STRING_8} req.execution_variable ("REQUEST_ENTITY") as l_request then + if attached {READABLE_STRING_8} req.execution_variable (Request_entity_execution_variable) as l_request then l_order := extract_order_request (l_request) if req.is_put_request_method then l_id := order_id_from_request (req) if l_order /= Void and then db_access.orders.has_key (l_id) then l_order.set_id (l_id) - req.set_execution_variable ("REQUEST_CHECK_CODE", {NATURAL} 0) - req.set_execution_variable ("EXTRACTED_ORDER", l_order) + req.set_execution_variable (Request_check_code_execution_variable, {NATURAL} 0) + req.set_execution_variable (Extracted_order_execution_variable, l_order) else - req.set_execution_variable ("REQUEST_CHECK_CODE", {NATURAL} 400) + req.set_execution_variable (Request_check_code_execution_variable, {NATURAL} 400) handle_bad_request_response (l_request +"%N is not a valid ORDER, maybe the order does not exist in the system", req, res) end else - req.set_execution_variable ("REQUEST_CHECK_CODE", {NATURAL} 0) - req.set_execution_variable ("EXTRACTED_ORDER", l_order) + req.set_execution_variable (Request_check_code_execution_variable, {NATURAL} 0) + req.set_execution_variable (Extracted_order_execution_variable, l_order) end else - req.set_execution_variable ("REQUEST_CHECK_CODE", {NATURAL} 400) + req.set_execution_variable (Request_check_code_execution_variable, {NATURAL} 400) handle_bad_request_response ("Request is not a valid ORDER", req, res) end end @@ -391,7 +396,7 @@ feature -- PUT/POST -- Perform the update requested in `req'. -- Write a response to `res' with a code of 204 or 500. do - if attached {ORDER} req.execution_variable ("EXTRACTED_ORDER") as l_order then + if attached {ORDER} req.execution_variable (Extracted_order_execution_variable) as l_order then update_order (l_order) compute_response_put (req, res, l_order) else diff --git a/library/server/wsf/router/wsf_method_helper.e b/library/server/wsf/router/wsf_method_helper.e index 61783af8..c64f1484 100644 --- a/library/server/wsf/router/wsf_method_helper.e +++ b/library/server/wsf/router/wsf_method_helper.e @@ -12,7 +12,7 @@ deferred class WSF_METHOD_HELPER inherit HTTP_STATUS_CODE_MESSAGES - + SHARED_HTML_ENCODER export {NONE} all end @@ -58,7 +58,7 @@ feature -- Basic operations l_locs := a_handler.previous_location (req) handle_redirection_error (req, res, l_locs, {HTTP_STATUS_CODE}.found) else - check attached {HTTP_HEADER} req.execution_variable ("NEGOTIATED_HTTP_HEADER") as h then + check attached {HTTP_HEADER} req.execution_variable (a_handler.Negotiated_http_header_execution_variable) as h then -- postcondition header_attached of `handle_content_negotiation' h.put_content_type_text_plain h.put_current_date @@ -68,7 +68,7 @@ feature -- Basic operations end end else - check attached {HTTP_HEADER} req.execution_variable ("NEGOTIATED_HTTP_HEADER") as h then + check attached {HTTP_HEADER} req.execution_variable (a_handler.Negotiated_http_header_execution_variable) as h then -- postcondition header_attached of `handle_content_negotiation' h.put_content_type_text_plain h.put_current_date @@ -140,7 +140,7 @@ feature -- Basic operations end end if not l_failed then - check attached {HTTP_HEADER} req.execution_variable ("NEGOTIATED_HTTP_HEADER") as h then + check attached {HTTP_HEADER} req.execution_variable (a_handler.Negotiated_http_header_execution_variable) as h then -- postcondition header_attached of `handle_content_negotiation' send_response (req, res, a_handler, h, False) end @@ -154,7 +154,7 @@ feature -- Content negotiation handle_content_negotiation (req: WSF_REQUEST; res: WSF_RESPONSE; a_handler: WSF_SKELETON_HANDLER) -- Negotiate acceptable content for, then write, response requested by `req' into `res'. -- Policy routines are available in `a_handler'. - -- + -- -- Either a 406 Not Acceptable error is sent, or upto four execution variables may be set on `req': -- "NEGOTIATED_MEDIA_TYPE" -- "NEGOTIATED_LANGUAGE" @@ -290,7 +290,7 @@ feature {NONE} -- Implementation l_chunk := a_handler.next_chunk (req) res.put_chunk (l_chunk.a_chunk, l_chunk.a_extension) else - write_error_response (req, res) + write_error_response (req, res) end end if a_handler.finished (req) then @@ -366,7 +366,7 @@ feature {NONE} -- Implementation feature -- Errors - + feature -- Error reporting write_error_response (req: WSF_REQUEST; res: WSF_RESPONSE) @@ -439,7 +439,7 @@ feature -- Error reporting s.append ("
") s.append ("%N") s.append ("%N") - + h.put_content_type_text_html else s := "Error " + a_status_code.out + " (" + l_msg + "): " @@ -566,7 +566,7 @@ feature -- Error reporting res.set_status_code ({HTTP_STATUS_CODE}.not_implemented) res.put_header_lines (h) end - + handle_request_entity_too_large (req: WSF_REQUEST; res: WSF_RESPONSE; a_handler: WSF_SKELETON_HANDLER) -- Write a Request Entity Too Large response for `req' to `res'. require diff --git a/library/server/wsf/router/wsf_post_helper.e b/library/server/wsf/router/wsf_post_helper.e index f3d4d44c..96ae57a4 100644 --- a/library/server/wsf/router/wsf_post_helper.e +++ b/library/server/wsf/router/wsf_post_helper.e @@ -22,7 +22,7 @@ feature -- Basic operations -- Policy routines are available in `a_handler'. do if a_handler.allow_post_to_missing_resource (req) then - check attached {HTTP_HEADER} req.execution_variable ("NEGOTIATED_HTTP_HEADER") as h then + check attached {HTTP_HEADER} req.execution_variable (a_handler.Negotiated_http_header_execution_variable) as h then -- postcondition header_attached of `handle_content_negotiation' send_response (req, res, a_handler, h, True) end diff --git a/library/server/wsf/router/wsf_previous_policy.e b/library/server/wsf/router/wsf_previous_policy.e index b64c0450..c77f0746 100644 --- a/library/server/wsf/router/wsf_previous_policy.e +++ b/library/server/wsf/router/wsf_previous_policy.e @@ -43,10 +43,11 @@ feature -- Access req_attached: req /= Void previously_existed: resource_previously_existed (req) moved: resource_moved_permanently (req) or resource_moved_temporarily (req) - deferred + do + create {LINKED_LIST [URI]} Result.make ensure previous_location_attached: Result /= Void - non_empty_list: not Result.empty + non_empty_list: not Result.is_empty end note diff --git a/library/server/wsf/router/wsf_put_helper.e b/library/server/wsf/router/wsf_put_helper.e index 02b26f0d..41095b67 100644 --- a/library/server/wsf/router/wsf_put_helper.e +++ b/library/server/wsf/router/wsf_put_helper.e @@ -24,7 +24,7 @@ feature -- Basic operations if a_handler.treat_as_moved_permanently (req) then handle_redirection_error (req, res, a_handler.previous_location (req), {HTTP_STATUS_CODE}.moved_permanently) else - check attached {HTTP_HEADER} req.execution_variable ("NEGOTIATED_HTTP_HEADER") as h then + check attached {HTTP_HEADER} req.execution_variable (a_handler.Negotiated_http_header_execution_variable) as h then -- postcondition header_attached of `handle_content_negotiation' send_response (req, res, a_handler, h, True) end @@ -68,5 +68,15 @@ feature {NONE} -- Implementation end 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 diff --git a/library/server/wsf/router/wsf_skeleton_handler.e b/library/server/wsf/router/wsf_skeleton_handler.e index 780034ee..511ec692 100644 --- a/library/server/wsf/router/wsf_skeleton_handler.e +++ b/library/server/wsf/router/wsf_skeleton_handler.e @@ -30,7 +30,7 @@ feature -- Execution variables Negotiated_language_execution_variable: STRING = "NEGOTIATED_LANGUAGE" -- Execution variable set by framework - + Negotiated_charset_execution_variable: STRING = "NEGOTIATED_CHARSET" -- Execution variable set by framework @@ -46,6 +46,15 @@ feature -- Execution variables Request_entity_execution_variable: STRING = "REQUEST_ENTITY" -- Execution variable set by framework + Conflict_check_code_execution_variable: STRING = "CONFLICT_CHECK_CODE" + -- Execution variable set by framework + + Content_check_code_execution_variable: STRING = "CONTENT_CHECK_CODE" + -- Execution variable set by framework + + Request_check_code_execution_variable: STRING = "REQUEST_CHECK_CODE" + -- Execution variable set by framework + feature -- Access is_chunking (req: WSF_REQUEST): BOOLEAN @@ -329,7 +338,7 @@ feature -- PUT/POST end is_entity_too_large (req: WSF_REQUEST): BOOLEAN - -- Is the entity stored in `req.execution_variable ("REQUEST_ENTITY")' too large for the application? + -- Is the entity stored in `req.execution_variable (Request_entity_execution_variable)' too large for the application? require req_attached: req /= Void deferred @@ -337,7 +346,7 @@ feature -- PUT/POST 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. + -- Set `req.execution_variable (Content_check_code_execution_variable)' to {NATURAL} zero if OK, or 415 or 501 if not. require req_attached: req /= Void deferred @@ -348,7 +357,7 @@ feature -- PUT/POST require req_attached: req /= Void do - if attached {NATURAL} req.execution_variable ("CONTENT_CHECK_CODE") as l_code then + if attached {NATURAL} req.execution_variable (Content_check_code_execution_variable) as l_code then Result := l_code end end @@ -375,7 +384,7 @@ feature -- PUT/POST check_conflict (req: WSF_REQUEST; res: WSF_RESPONSE) -- Check to see if updating the resource is problematic due to the current state of the resource. - -- Set `req.execution_variable ("CONFLICT_CHECK_CODE")' to {NATURAL} zero if OK, or 409 if not. + -- Set `req.execution_variable (Conflict_check_code_execution_variable)' 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 @@ -388,15 +397,15 @@ feature -- PUT/POST require req_attached: req /= Void do - if attached {NATURAL} req.execution_variable ("CONFLICT_CHECK_CODE") as l_code then + if attached {NATURAL} req.execution_variable (Conflict_check_code_execution_variable) 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. + -- The entity is available as `req.execution_variable (Conflict_check_code_execution_variable)'. + -- Set `req.execution_variable (Request_check_code_execution_variable)' 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 @@ -410,7 +419,7 @@ feature -- PUT/POST require req_attached: req /= Void do - if attached {NATURAL} req.execution_variable ("REQUEST_CHECK_CODE") as l_code then + if attached {NATURAL} req.execution_variable (Request_check_code_execution_variable) as l_code then Result := l_code end end From 143608fd8590cae60ad3b20f0912ac9c5bcd1d05 Mon Sep 17 00:00:00 2001 From: Colin Adams Date: Wed, 14 Aug 2013 11:32:27 +0100 Subject: [PATCH 18/24] Fixed recursion on router bug --- .../server/wsf/router/wsf_skeleton_handler.e | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/library/server/wsf/router/wsf_skeleton_handler.e b/library/server/wsf/router/wsf_skeleton_handler.e index 511ec692..b7c645be 100644 --- a/library/server/wsf/router/wsf_skeleton_handler.e +++ b/library/server/wsf/router/wsf_skeleton_handler.e @@ -11,7 +11,7 @@ deferred class WSF_SKELETON_HANDLER inherit - WSF_URI_TEMPLATE_ROUTING_HANDLER + WSF_URI_TEMPLATE_HANDLER redefine execute end @@ -26,6 +26,23 @@ inherit WSF_SELF_DOCUMENTED_HANDLER +feature {NONE} -- Initialization + + make_with_router (a_router: WSF_ROUTER) + -- Initialize `router'. + require + a_router_attached: a_router /= Void + do + router := a_router + ensure + router_aliased: router = a_router + end + +feature -- Router + + router: WSF_ROUTER + -- So that WSF_OPTIONS_POLICY can find the allowed methods + feature -- Execution variables Negotiated_language_execution_variable: STRING = "NEGOTIATED_LANGUAGE" From b5957d0f591f433fae74edaf45d44c9c229f1c71 Mon Sep 17 00:00:00 2001 From: Colin Adams Date: Thu, 15 Aug 2013 08:57:54 +0100 Subject: [PATCH 19/24] Removed empty feature clause --- library/server/wsf/router/wsf_method_helper.e | 2 -- 1 file changed, 2 deletions(-) diff --git a/library/server/wsf/router/wsf_method_helper.e b/library/server/wsf/router/wsf_method_helper.e index c64f1484..165a4111 100644 --- a/library/server/wsf/router/wsf_method_helper.e +++ b/library/server/wsf/router/wsf_method_helper.e @@ -364,8 +364,6 @@ feature {NONE} -- Implementation end end -feature -- Errors - feature -- Error reporting From 0755e2d2bca1b85432711bdbd0fae377e6b0e6a8 Mon Sep 17 00:00:00 2001 From: Colin Adams Date: Thu, 15 Aug 2013 10:30:47 +0100 Subject: [PATCH 20/24] Improved comment to ensure_content_exists --- library/server/wsf/router/wsf_skeleton_handler.e | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/server/wsf/router/wsf_skeleton_handler.e b/library/server/wsf/router/wsf_skeleton_handler.e index b7c645be..f63bcd64 100644 --- a/library/server/wsf/router/wsf_skeleton_handler.e +++ b/library/server/wsf/router/wsf_skeleton_handler.e @@ -264,7 +264,7 @@ feature -- DELETE feature -- GET/HEAD content ensure_content_available (req: WSF_REQUEST) - -- Commence generation of response text (entity-body). + -- Commence generation of response text (entity-body) (if not already done in `check_resource_exists'. -- 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 From 2903a1d3cddd075f9a14f4ed39507bb62b0343b8 Mon Sep 17 00:00:00 2001 From: Colin Adams Date: Thu, 15 Aug 2013 10:31:40 +0100 Subject: [PATCH 21/24] Improved comment to ensure_content_exists - take 2 --- library/server/wsf/router/wsf_skeleton_handler.e | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/server/wsf/router/wsf_skeleton_handler.e b/library/server/wsf/router/wsf_skeleton_handler.e index f63bcd64..e9ba25f1 100644 --- a/library/server/wsf/router/wsf_skeleton_handler.e +++ b/library/server/wsf/router/wsf_skeleton_handler.e @@ -264,7 +264,7 @@ feature -- DELETE feature -- GET/HEAD content ensure_content_available (req: WSF_REQUEST) - -- Commence generation of response text (entity-body) (if not already done in `check_resource_exists'. + -- Commence generation of response text (entity-body) (if not already done in `check_resource_exists'). -- 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 From eefe54755391721cc09d98df5944eb29b62e93f0 Mon Sep 17 00:00:00 2001 From: Colin Adams Date: Thu, 15 Aug 2013 14:58:58 +0100 Subject: [PATCH 22/24] Changed comment on execute to check assertion --- library/server/wsf/router/wsf_skeleton_handler.e | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/library/server/wsf/router/wsf_skeleton_handler.e b/library/server/wsf/router/wsf_skeleton_handler.e index e9ba25f1..60ebf617 100644 --- a/library/server/wsf/router/wsf_skeleton_handler.e +++ b/library/server/wsf/router/wsf_skeleton_handler.e @@ -457,10 +457,9 @@ feature -- Execution -- do check - known_method: True -- Can't be done until WSF_METHOD_NOT_ALLOWED_RESPONSE - -- is refactored. - -- Then maybe this can become a precondition. But we will still (?) - -- need a check that it isn't CONNECT or TRACE (it MIGHT be HEAD). + known_method: router.allowed_methods_for_request (req).has (req.request_method) + not_trace: not req.is_request_method ({HTTP_REQUEST_METHODS}.method_trace) + not_connect: not req.is_request_method ({HTTP_REQUEST_METHODS}.method_connect) end if req.is_request_method ({HTTP_REQUEST_METHODS}.method_options) then execute_options (req, res, router) From 3ae898476f56690320e9fed6362a17aa2cc7d3aa Mon Sep 17 00:00:00 2001 From: Colin Adams Date: Fri, 16 Aug 2013 04:50:48 +0100 Subject: [PATCH 23/24] Changed age to max_age --- examples/restbucksCRUD/src/resource/order_handler.e | 2 +- library/server/wsf/router/wsf_caching_policy.e | 6 +++--- library/server/wsf/router/wsf_method_helper.e | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/restbucksCRUD/src/resource/order_handler.e b/examples/restbucksCRUD/src/resource/order_handler.e index 1ea6f836..91d1b9ae 100644 --- a/examples/restbucksCRUD/src/resource/order_handler.e +++ b/examples/restbucksCRUD/src/resource/order_handler.e @@ -99,7 +99,7 @@ feature -- Access Result.compare_objects end - age (req: WSF_REQUEST): NATURAL + max_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. diff --git a/library/server/wsf/router/wsf_caching_policy.e b/library/server/wsf/router/wsf_caching_policy.e index bc9a4687..35f104de 100644 --- a/library/server/wsf/router/wsf_caching_policy.e +++ b/library/server/wsf/router/wsf_caching_policy.e @@ -14,7 +14,7 @@ feature -- Access -- 525600 = 365 * 24 * 60 * 60 = (almost) 1 year; -- See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.21 for an explanation of why this means never expire - age (req: WSF_REQUEST): NATURAL + max_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. @@ -35,7 +35,7 @@ feature -- Access require req_attached: req /= Void do - Result := age (req) + Result := max_age (req) ensure not_more_than_1_year: Result <= Never_expires end @@ -51,7 +51,7 @@ feature -- Access require req_attached: req /= Void do - Result := age (req) + Result := max_age (req) ensure not_more_than_1_year: Result <= Never_expires end diff --git a/library/server/wsf/router/wsf_method_helper.e b/library/server/wsf/router/wsf_method_helper.e index 165a4111..3f3037e8 100644 --- a/library/server/wsf/router/wsf_method_helper.e +++ b/library/server/wsf/router/wsf_method_helper.e @@ -315,7 +315,7 @@ feature {NONE} -- Implementation 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) + l_age_1 := a_handler.max_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 From 37b94bbf0d3e3836c023f9ee95442bc2aa98fd4e Mon Sep 17 00:00:00 2001 From: Colin Adams Date: Mon, 19 Aug 2013 11:48:49 +0100 Subject: [PATCH 24/24] Added header comment about redefining for extension methods --- library/server/wsf/router/wsf_method_helper_factory.e | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/library/server/wsf/router/wsf_method_helper_factory.e b/library/server/wsf/router/wsf_method_helper_factory.e index 48f773c4..633599b6 100644 --- a/library/server/wsf/router/wsf_method_helper_factory.e +++ b/library/server/wsf/router/wsf_method_helper_factory.e @@ -12,7 +12,8 @@ class WSF_METHOD_HELPER_FACTORY feature -- Factory new_method_helper (a_method: READABLE_STRING_8): detachable WSF_METHOD_HELPER - -- New object for processing `a_method' + -- New object for processing `a_method'; + -- Redefine this routine to implement extension methods. require a_method_attached: a_method /= Void do