From 265117129441aa8ec18386e50ff829e771d1e6f0 Mon Sep 17 00:00:00 2001 From: Jocelyn Fiat Date: Tue, 20 Aug 2013 17:27:57 +0200 Subject: [PATCH] Extracted the policy driven classes into their own library for now "wsf_policy_driven.ecf" Updated the restbucksCRUD example to demonstrate both approaches. --- examples/restbucksCRUD/README-compilation.txt | 3 + examples/restbucksCRUD/restbucks-safe.ecf | 33 +- .../policy_driven_resource/order_handler.e | 558 ++++++++++++++++ .../src/resource/order_handler.e | 603 +++++++----------- .../wsf_delete_helper.e | 0 .../policy => policy_driven}/wsf_get_helper.e | 0 .../wsf_method_helper.e | 0 .../wsf_method_helper_factory.e | 0 .../wsf_post_helper.e | 0 .../policy => policy_driven}/wsf_put_helper.e | 0 .../wsf_skeleton_handler.e | 0 .../wsf_routed_skeleton_service.e | 12 +- library/server/wsf/wsf-safe.ecf | 7 +- library/server/wsf/wsf.ecf | 7 +- library/server/wsf/wsf_policy_driven-safe.ecf | 22 + library/server/wsf/wsf_policy_driven.ecf | 22 + tests/all-safe.ecf | 1 + 17 files changed, 870 insertions(+), 398 deletions(-) create mode 100644 examples/restbucksCRUD/README-compilation.txt create mode 100644 examples/restbucksCRUD/src/policy_driven_resource/order_handler.e rename library/server/wsf/{router/policy => policy_driven}/wsf_delete_helper.e (100%) rename library/server/wsf/{router/policy => policy_driven}/wsf_get_helper.e (100%) rename library/server/wsf/{router/policy => policy_driven}/wsf_method_helper.e (100%) rename library/server/wsf/{router/policy => policy_driven}/wsf_method_helper_factory.e (100%) rename library/server/wsf/{router/policy => policy_driven}/wsf_post_helper.e (100%) rename library/server/wsf/{router/policy => policy_driven}/wsf_put_helper.e (100%) rename library/server/wsf/{router/policy => policy_driven}/wsf_skeleton_handler.e (100%) rename library/server/wsf/router/policy/{ => service}/wsf_routed_skeleton_service.e (97%) create mode 100644 library/server/wsf/wsf_policy_driven-safe.ecf create mode 100644 library/server/wsf/wsf_policy_driven.ecf diff --git a/examples/restbucksCRUD/README-compilation.txt b/examples/restbucksCRUD/README-compilation.txt new file mode 100644 index 00000000..6e3dfa50 --- /dev/null +++ b/examples/restbucksCRUD/README-compilation.txt @@ -0,0 +1,3 @@ +The current example has a main target for the server: "restbucks" +But we also provide "policy_driven_restbucks" target which is using the +policy-driven framework than help coder fulfill HTTP expectations. diff --git a/examples/restbucksCRUD/restbucks-safe.ecf b/examples/restbucksCRUD/restbucks-safe.ecf index 08715841..d25369fa 100644 --- a/examples/restbucksCRUD/restbucks-safe.ecf +++ b/examples/restbucksCRUD/restbucks-safe.ecf @@ -1,16 +1,11 @@ - - + /EIFGENs$ /\.git$ /\.svn$ - @@ -29,6 +24,30 @@ - + + + + + + + /policy_driven_resource$ + + + + + + + + + + /resource$ + + diff --git a/examples/restbucksCRUD/src/policy_driven_resource/order_handler.e b/examples/restbucksCRUD/src/policy_driven_resource/order_handler.e new file mode 100644 index 00000000..91d1b9ae --- /dev/null +++ b/examples/restbucksCRUD/src/policy_driven_resource/order_handler.e @@ -0,0 +1,558 @@ +note + description: "{ORDER_HANDLER} handle the resources that we want to expose" + author: "" + date: "$Date$" + revision: "$Revision$" + +class ORDER_HANDLER + +inherit + + WSF_SKELETON_HANDLER + + SHARED_DATABASE_API + + SHARED_EJSON + + REFACTORING_HELPER + + SHARED_ORDER_VALIDATION + + 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 -- 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 + -- 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 + + is_chunking (req: WSF_REQUEST): BOOLEAN + -- Will the response to `req' using chunked transfer encoding? + do + -- 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 + + 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. + -- Return Never_expires 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): 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_execution_variable) 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_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 + 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_execution_variable)' 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_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_execution_variable) + end + +feature -- GET/HEAD content + + 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_execution_variable)' + -- We ignore the results of content negotiation, as there is only one possible combination. + do + 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_execution_variable, jv.representation) + else + 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_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_execution_variable) 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): 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 + -- 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) + -- 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 + + 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_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_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 + + 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_execution_variable) 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_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_execution_variable) as l_order then + if not is_valid_to_update (l_order) then + 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_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 + 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 (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_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_execution_variable, {NATURAL} 0) + req.set_execution_variable (Extracted_order_execution_variable, l_order) + else + 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_execution_variable, {NATURAL} 0) + req.set_execution_variable (Extracted_order_execution_variable, l_order) + end + else + 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 + + 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_execution_variable) 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) + local + h: HTTP_HEADER + joc : JSON_ORDER_CONVERTER + etag_utils : ETAG_UTILS + do + create h.make + create joc.make + create etag_utils + json.add_converter(joc) + + create h.make + h.put_content_type_application_json + 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)) + if attached {JSON_VALUE} json.value (l_order) as jv then + h.put_content_length (jv.representation.count) + res.set_status_code ({HTTP_STATUS_CODE}.ok) + res.put_header_text (h.string) + res.put_string (jv.representation) + end + end + + compute_response_post (req: WSF_REQUEST; res: WSF_RESPONSE; l_order : ORDER) + local + h: HTTP_HEADER + l_msg : STRING + l_location : STRING + joc : JSON_ORDER_CONVERTER + do + create h.make + + create joc.make + json.add_converter(joc) + + 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.http_host as host then + l_location := "http://" + host + req.request_uri + "/" + l_order.id + h.put_location (l_location) + end + if attached req.request_time as time then + h.put_utc_date (time) + end + res.set_status_code ({HTTP_STATUS_CODE}.created) + res.put_header_text (h.string) + res.put_string (l_msg) + end + end + +feature {NONE} -- URI helper methods + + order_id_from_request (req: WSF_REQUEST): STRING + -- Value of "orderid" template URI variable in `req' + require + req_attached: req /= Void + do + 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 + + retrieve_order ( id : STRING) : detachable ORDER + -- get the order by id if it exist, in other case, Void + do + Result := db_access.orders.item (id) + end + + save_order (an_order: ORDER) + -- save the order to the repository + local + i : INTEGER + do + from + i := 1 + until + not db_access.orders.has_key ((db_access.orders.count + i).out) + loop + i := i + 1 + end + an_order.set_id ((db_access.orders.count + i).out) + an_order.set_status ("submitted") + an_order.add_revision + db_access.orders.force (an_order, an_order.id) + end + + + is_valid_to_delete ( an_id : STRING) : BOOLEAN + -- Is the order identified by `an_id' in a state whre it can still be deleted? + do + if attached retrieve_order (an_id) as l_order then + if order_validation.is_state_valid_to_update (l_order.status) then + Result := True + end + end + end + + is_valid_to_update (an_order: ORDER) : BOOLEAN + -- Check if there is a conflict while trying to update the order + do + if attached retrieve_order (an_order.id) as l_order then + if order_validation.is_state_valid_to_update (l_order.status) and then order_validation.is_valid_status_state (an_order.status) and then + order_validation.is_valid_transition (l_order, an_order.status) then + Result := True + end + end + end + + update_order (an_order: ORDER) + -- update the order to the repository + do + an_order.add_revision + db_access.orders.force (an_order, an_order.id) + end + + delete_order (an_order: STRING) + -- update the order to the repository + do + db_access.orders.remove (an_order) + end + + extract_order_request (l_post : STRING) : detachable ORDER + -- extract an object Order from the request, or Void + -- if the request is invalid + local + parser : JSON_PARSER + joc : JSON_ORDER_CONVERTER + do + create joc.make + json.add_converter(joc) + create parser.make_parser (l_post) + if attached parser.parse as jv and parser.is_parsed then + if attached {like extract_order_request} json.object (jv, "ORDER") as res then + Result := res + end + end + end + +note + copyright: "2011-2013, Javier Velilla and others" + license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)" +end diff --git a/examples/restbucksCRUD/src/resource/order_handler.e b/examples/restbucksCRUD/src/resource/order_handler.e index 91d1b9ae..c7b6693e 100644 --- a/examples/restbucksCRUD/src/resource/order_handler.e +++ b/examples/restbucksCRUD/src/resource/order_handler.e @@ -5,10 +5,17 @@ note revision: "$Revision$" class ORDER_HANDLER - inherit - WSF_SKELETON_HANDLER + WSF_URI_TEMPLATE_HANDLER + + WSF_RESOURCE_HANDLER_HELPER + redefine + do_get, + do_post, + do_put, + do_delete + end SHARED_DATABASE_API @@ -18,394 +25,174 @@ inherit SHARED_ORDER_VALIDATION - WSF_RESOURCE_HANDLER_HELPER - rename - execute_options as helper_execute_options, - handle_internal_server_error as helper_handle_internal_server_error - end + WSF_SELF_DOCUMENTED_HANDLER create - make_with_router +feature {NONE} -- Initialization -feature -- Execution variables + 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 - Order_execution_variable: STRING = "ORDER" - -- Execution variable used by application +feature -- Router - Generated_content_execution_variable: STRING = "GENERATED_CONTENT" - -- Execution variable used by application + router: WSF_ROUTER + -- Associated router that could be used for advanced strategy + +feature -- Execute + + execute (req: WSF_REQUEST; res: WSF_RESPONSE) + -- Execute request handler + do + execute_methods (req, res) + end + +feature -- API DOC + + api_doc : STRING = "URI:/order METHOD: POST%N URI:/order/{orderid} METHOD: GET, PUT, DELETE%N" - Extracted_order_execution_variable: STRING = "EXTRACTED_ORDER" - -- Execution variable used by application feature -- Documentation - description: READABLE_STRING_GENERAL - -- General description for self-generated documentation; - -- The specific URI templates supported will be described automatically + mapping_documentation (m: WSF_ROUTER_MAPPING; a_request_methods: detachable WSF_REQUEST_METHODS): WSF_ROUTER_MAPPING_DOCUMENTATION do - Result := "Create, Read, Update or Delete an ORDER." - end - -feature -- Access - - is_chunking (req: WSF_REQUEST): BOOLEAN - -- Will the response to `req' using chunked transfer encoding? - do - -- 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 - - 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. - -- Return Never_expires 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) + 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") end end end - etag (req: WSF_REQUEST): 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_execution_variable) 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_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 - 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_execution_variable)' 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_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_execution_variable) - end - -feature -- GET/HEAD content - - 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_execution_variable)' - -- We ignore the results of content negotiation, as there is only one possible combination. - do - 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_execution_variable, jv.representation) - else - 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_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_execution_variable) 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): 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 - -- 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) - -- 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 - - 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_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_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 - - 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_execution_variable) 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_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_execution_variable) as l_order then - if not is_valid_to_update (l_order) then - 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_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 - 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 (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_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_execution_variable, {NATURAL} 0) - req.set_execution_variable (Extracted_order_execution_variable, l_order) - else - 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_execution_variable, {NATURAL} 0) - req.set_execution_variable (Extracted_order_execution_variable, l_order) - end - else - 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 - - 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_execution_variable) 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.path_info as l_path_info then + id := get_order_id_from_path (l_path_info) + if attached retrieve_order (id) as l_order then + if is_conditional_get (req, l_order) then + handle_resource_not_modified_response ("The resource" + l_path_info + "does not change", req, res) + else + compute_response_get (req, res, l_order) + end + else + handle_resource_not_found_response ("The following resource" + l_path_info + " 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.path_info as l_path_info then + id := get_order_id_from_path (l_path_info) + 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 @@ -431,6 +218,67 @@ 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.path_info as l_path_info then + id := get_order_id_from_path (l_path_info) + 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 (l_path_info + "%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 (l_path_info + " 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 @@ -462,16 +310,9 @@ feature -- HTTP Methods feature {NONE} -- URI helper methods - order_id_from_request (req: WSF_REQUEST): STRING - -- Value of "orderid" template URI variable in `req' - require - req_attached: req /= Void + get_order_id_from_path (a_path: READABLE_STRING_32) : STRING do - if attached {WSF_VALUE} req.path_parameter ("orderid") as l_value then - Result := l_value.as_string.value.as_string_8 - else - Result := "" - end + Result := a_path.split ('/').at (3) end feature {NONE} -- Implementation Repository Layer diff --git a/library/server/wsf/router/policy/wsf_delete_helper.e b/library/server/wsf/policy_driven/wsf_delete_helper.e similarity index 100% rename from library/server/wsf/router/policy/wsf_delete_helper.e rename to library/server/wsf/policy_driven/wsf_delete_helper.e diff --git a/library/server/wsf/router/policy/wsf_get_helper.e b/library/server/wsf/policy_driven/wsf_get_helper.e similarity index 100% rename from library/server/wsf/router/policy/wsf_get_helper.e rename to library/server/wsf/policy_driven/wsf_get_helper.e diff --git a/library/server/wsf/router/policy/wsf_method_helper.e b/library/server/wsf/policy_driven/wsf_method_helper.e similarity index 100% rename from library/server/wsf/router/policy/wsf_method_helper.e rename to library/server/wsf/policy_driven/wsf_method_helper.e diff --git a/library/server/wsf/router/policy/wsf_method_helper_factory.e b/library/server/wsf/policy_driven/wsf_method_helper_factory.e similarity index 100% rename from library/server/wsf/router/policy/wsf_method_helper_factory.e rename to library/server/wsf/policy_driven/wsf_method_helper_factory.e diff --git a/library/server/wsf/router/policy/wsf_post_helper.e b/library/server/wsf/policy_driven/wsf_post_helper.e similarity index 100% rename from library/server/wsf/router/policy/wsf_post_helper.e rename to library/server/wsf/policy_driven/wsf_post_helper.e diff --git a/library/server/wsf/router/policy/wsf_put_helper.e b/library/server/wsf/policy_driven/wsf_put_helper.e similarity index 100% rename from library/server/wsf/router/policy/wsf_put_helper.e rename to library/server/wsf/policy_driven/wsf_put_helper.e diff --git a/library/server/wsf/router/policy/wsf_skeleton_handler.e b/library/server/wsf/policy_driven/wsf_skeleton_handler.e similarity index 100% rename from library/server/wsf/router/policy/wsf_skeleton_handler.e rename to library/server/wsf/policy_driven/wsf_skeleton_handler.e diff --git a/library/server/wsf/router/policy/wsf_routed_skeleton_service.e b/library/server/wsf/router/policy/service/wsf_routed_skeleton_service.e similarity index 97% rename from library/server/wsf/router/policy/wsf_routed_skeleton_service.e rename to library/server/wsf/router/policy/service/wsf_routed_skeleton_service.e index 45fd8975..e9eddb59 100644 --- a/library/server/wsf/router/policy/wsf_routed_skeleton_service.e +++ b/library/server/wsf/router/policy/service/wsf_routed_skeleton_service.e @@ -30,14 +30,14 @@ feature -- Execution handle_unavailable (res) elseif requires_proxy (req) then handle_use_proxy (req, res) - elseif - maximum_uri_length > 0 and then - req.request_uri.count.to_natural_32 > maximum_uri_length + elseif + maximum_uri_length > 0 and then + req.request_uri.count.to_natural_32 > maximum_uri_length then handle_request_uri_too_long (res) - elseif - req.is_request_method ({HTTP_REQUEST_METHODS}.method_options) and then - req.request_uri.same_string ("*") + elseif + req.is_request_method ({HTTP_REQUEST_METHODS}.method_options) and then + req.request_uri.same_string ("*") then handle_server_options (req, res) else diff --git a/library/server/wsf/wsf-safe.ecf b/library/server/wsf/wsf-safe.ecf index e47d827a..efeb0034 100644 --- a/library/server/wsf/wsf-safe.ecf +++ b/library/server/wsf/wsf-safe.ecf @@ -18,8 +18,11 @@ - - + + + /policy_driven$ + + diff --git a/library/server/wsf/wsf.ecf b/library/server/wsf/wsf.ecf index b315cfb2..b8088db3 100644 --- a/library/server/wsf/wsf.ecf +++ b/library/server/wsf/wsf.ecf @@ -17,11 +17,14 @@ - - + + + /policy_driven$ + + diff --git a/library/server/wsf/wsf_policy_driven-safe.ecf b/library/server/wsf/wsf_policy_driven-safe.ecf new file mode 100644 index 00000000..f5b81277 --- /dev/null +++ b/library/server/wsf/wsf_policy_driven-safe.ecf @@ -0,0 +1,22 @@ + + + + + + /.git$ + /EIFGENs$ + /.svn$ + + + + + + + + + + + + + diff --git a/library/server/wsf/wsf_policy_driven.ecf b/library/server/wsf/wsf_policy_driven.ecf new file mode 100644 index 00000000..4e9cd61f --- /dev/null +++ b/library/server/wsf/wsf_policy_driven.ecf @@ -0,0 +1,22 @@ + + + + + + /.git$ + /EIFGENs$ + /.svn$ + + + + + + + + + + + + + diff --git a/tests/all-safe.ecf b/tests/all-safe.ecf index 8115909e..cb24fecd 100644 --- a/tests/all-safe.ecf +++ b/tests/all-safe.ecf @@ -46,6 +46,7 @@ +