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