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_HANDLER redefine execute end WSF_OPTIONS_POLICY WSF_PREVIOUS_POLICY WSF_CACHING_POLICY WSF_METHOD_HELPER_FACTORY 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" -- 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 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 -- 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 negotiation 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): 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 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 -- 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) -- 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 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 -- 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) -- 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 -- 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 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): 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) deferred 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. -- 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) deferred end 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) deferred end feature -- PUT/POST read_entity (req: WSF_REQUEST) -- Read request body and set as `req.execution_variable (Request_entity_execution_variable)'. 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_execution_variable, l_body) end end 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? 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_execution_variable)' 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_execution_variable) 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 to see if updating the resource is problematic due to the current state of the resource. -- 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 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_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 (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 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_execution_variable) 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) -- do check 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) 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 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) elseif req.error_handler.has_error then a_helper.write_error_response (req, res) 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 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. -- 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 deferred end feature {NONE} -- Implementation 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_header_lines (h) 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 note copyright: "2011-2013, Colin Adams, 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