From d56f4e6c7dc6dc904193cfe1cf98978e11bae6aa Mon Sep 17 00:00:00 2001 From: Colin Adams Date: Mon, 18 Mar 2013 14:21:53 +0000 Subject: [PATCH] prior to refactoring for WSF_ROUTED_SKELETON_SERVICE --- examples/restbucksCRUD/src/restbucks_server.e | 5 ++ .../server/wsf/router/wsf_no_proxy_policy.e | 35 ++++++++ .../server/wsf/router/wsf_proxy_use_policy.e | 76 +++++++++++++++++ .../server/wsf/router/wsf_routed_service.e | 61 +++++++++----- library/server/wsf/router/wsf_router.e | 20 +++++ .../wsf_method_not_allowed_response.e | 82 +++++++++++++++++-- 6 files changed, 251 insertions(+), 28 deletions(-) create mode 100644 library/server/wsf/router/wsf_no_proxy_policy.e create mode 100644 library/server/wsf/router/wsf_proxy_use_policy.e diff --git a/examples/restbucksCRUD/src/restbucks_server.e b/examples/restbucksCRUD/src/restbucks_server.e index 56409c57..d9ebead2 100644 --- a/examples/restbucksCRUD/src/restbucks_server.e +++ b/examples/restbucksCRUD/src/restbucks_server.e @@ -10,11 +10,16 @@ inherit ANY WSF_URI_TEMPLATE_ROUTED_SERVICE + undefine + requires_proxy + end WSF_HANDLER_HELPER WSF_DEFAULT_SERVICE + WSF_NO_PROXY_POLICY + create make diff --git a/library/server/wsf/router/wsf_no_proxy_policy.e b/library/server/wsf/router/wsf_no_proxy_policy.e new file mode 100644 index 00000000..3397b30f --- /dev/null +++ b/library/server/wsf/router/wsf_no_proxy_policy.e @@ -0,0 +1,35 @@ +note + + description: "[ + Policy that no client ever need use a proxy. + + Users of this policy cannot safely use chunked transfer-encoding, or any + HTTP/1.1-specific features. So best used only for examples. + ]" + + date: "$Date$" + revision: "$Revision$" + +class WSF_NO_PROXY_POLICY + +inherit + + WSF_PROXY_USE_POLICY + redefine + requires_proxy + end + +feature -- Access + + requires_proxy (req: WSF_REQUEST): BOOLEAN + -- Does `req' require use of `proxy_server'? + do + end + + proxy_server (req: WSF_REQUEST): READABLE_STRING_8 -- We can't currently use UT_URI + -- Absolute URI of proxy server which `req' must use + do + Result := "" -- doesn't meet the postcondition, but the precondition is never true. + end + +end diff --git a/library/server/wsf/router/wsf_proxy_use_policy.e b/library/server/wsf/router/wsf_proxy_use_policy.e new file mode 100644 index 00000000..7c1c046c --- /dev/null +++ b/library/server/wsf/router/wsf_proxy_use_policy.e @@ -0,0 +1,76 @@ +note + + description: "[ + Policies that determine if the client must use a proxy server + to access the resource. + + The default policy implemented here is to require + use of the proxy for HTTP/1.0 clients (only) for all requests. + ]" + + date: "$Date$" + revision: "$Revision$" + +deferred class WSF_PROXY_USE_POLICY + +feature -- Access + + requires_proxy (req: WSF_REQUEST): BOOLEAN + -- Does `req' require use of `proxy_server'? + require + req_attached: req /= Void + do + if is_http_1_0 (req) then + Result := True + end + end + + proxy_server (req: WSF_REQUEST): READABLE_STRING_8 -- We can't currently use UT_URI + -- Absolute URI of proxy server which `req' must use + --| An alternative design would allow a relative URI (relative to the server host), + --| which will only work if both the server and the proxy use the default ports. + require + req_attached: req /= Void + proxy_required: requires_proxy (req) + deferred + ensure + absolute_uri: True -- We can't currently use UT_URI to check this. Have we got another class? + end + + is_http_1_0 (req: WSF_REQUEST): BOOLEAN + -- Does `req' come from an HTTP/1.0 client? + require + req_attached: req /= Void + local + l_protocol: READABLE_STRING_8 + l_tokens: LIST [READABLE_STRING_8] + l_protocol_name, l_protocol_version, l_major, l_minor: STRING_8 + do + l_protocol := req.server_protocol + l_tokens := l_protocol.split ('/') + if l_tokens.count = 2 then + l_protocol_name := l_tokens [1].as_string_8 + l_protocol_name.left_adjust + l_protocol_name.right_adjust + if l_protocol_name.is_case_insensitive_equal ({HTTP_CONSTANTS}.http_version_1_0.substring (1, 4)) then + l_protocol_version := l_tokens [2].as_string_8 + l_protocol_version.left_adjust + l_protocol_version.right_adjust + l_tokens := l_protocol_version.split ('.') + if l_tokens.count = 2 then + l_major := l_tokens [1].as_string_8 + l_major.left_adjust + l_major.right_adjust + l_minor := l_tokens [2].as_string_8 + l_minor.left_adjust + l_minor.right_adjust + if l_major.is_integer and then l_major.to_integer = 1 and then + l_minor.is_integer and then l_minor.to_integer = 0 then + Result := True + end + end + end + end + end + +end diff --git a/library/server/wsf/router/wsf_routed_service.e b/library/server/wsf/router/wsf_routed_service.e index b83c9dc7..bca46767 100644 --- a/library/server/wsf/router/wsf_routed_service.e +++ b/library/server/wsf/router/wsf_routed_service.e @@ -10,6 +10,8 @@ inherit WSF_SYSTEM_OPTIONS_ACCESS_POLICY + WSF_PROXY_USE_POLICY + feature -- Initialization initialize_router @@ -50,9 +52,11 @@ feature -- Execution --| which is implemented in WSF_REQUEST.make_from_wgi (when it calls `analyze'). if unavailable then 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 then handle_request_uri_too_long (res) - elseif req.request_method.as_upper.same_string ({HTTP_REQUEST_METHODS}.method_options) and then + elseif req.is_request_method ({HTTP_REQUEST_METHODS}.method_options) and then req.request_uri.same_string ("*") then handle_server_options (req, res) elseif attached router.dispatch_and_return_handler (req, res) as p then @@ -73,7 +77,6 @@ feature -- Execution local msg: WSF_DEFAULT_ROUTER_RESPONSE do - --| TODO (colin-adams): update this to distinguish between 501, 403 and 404 results. create msg.make_with_router (req, router) msg.set_documentation_included (True) res.send (msg) @@ -152,26 +155,23 @@ feature {NONE} -- Implementation handle_unavailable (res: WSF_RESPONSE) -- Write "Service unavailable" response to `res'. require - unavailable: unavailable = True + unavailable: unavailable res_attached: res /= Void local h: HTTP_HEADER do create h.make h.put_content_type_text_plain - check attached {READABLE_STRING_8} unavailablity_message as m then - -- invariant + check attached unavailablity_message as m then + -- invariant plus precondition h.put_content_length (m.count) h.put_current_date res.set_status_code ({HTTP_STATUS_CODE}.service_unavailable) if unavailability_duration > 0 then h.put_header_key_value ({HTTP_HEADER_NAMES}.header_retry_after, unavailability_duration.out) - elseif unavailable_until /= Void then - check attached {DATE_TIME} unavailable_until as u then - -- $£#$%#! compiler! + elseif attached unavailable_until as u then h.put_header_key_value ({HTTP_HEADER_NAMES}.header_retry_after, h.date_to_rfc1123_http_date_format (u)) - end end res.put_header_text (h.string) res.put_string (m) @@ -210,7 +210,7 @@ feature {NONE} -- Implementation require req_attached: req /= Void res_attached: res /= Void - method_is_options: req.request_method.as_upper.same_string ({HTTP_REQUEST_METHODS}.method_options) + method_is_options: req.is_request_method ({HTTP_REQUEST_METHODS}.method_options) server_options_requested: req.request_uri.same_string ("*") do --| First check if forbidden. @@ -233,7 +233,7 @@ feature {NONE} -- Implementation require req_attached: req /= Void res_attached: res /= Void - method_is_options: req.request_method.as_upper.same_string ({HTTP_REQUEST_METHODS}.method_options) + method_is_options: req.is_request_method ({HTTP_REQUEST_METHODS}.method_options) server_options_requested: req.request_uri.same_string ("*") local m: detachable READABLE_STRING_8 @@ -270,18 +270,18 @@ feature {NONE} -- Implementation require req_attached: req /= Void res_attached: res /= Void - method_is_options: req.request_method.as_upper.same_string ({HTTP_REQUEST_METHODS}.method_options) + method_is_options: req.is_request_method ({HTTP_REQUEST_METHODS}.method_options) server_options_requested: req.request_uri.same_string ("*") local h: HTTP_HEADER do - create h.make - h.put_content_type_text_plain - h.put_current_date - --| TODO - add Allow header for all permitted methods. - h.put_content_length (0) - res.set_status_code ({HTTP_STATUS_CODE}.ok) - res.put_header_text (h.string) + create h.make + h.put_content_type_text_plain + h.put_current_date + h.put_allow (router.all_allowed_methods) + h.put_content_length (0) + res.set_status_code ({HTTP_STATUS_CODE}.ok) + res.put_header_text (h.string) ensure response_status_is_set: res.status_is_set response_code_ok: res.status_code = {HTTP_STATUS_CODE}.ok @@ -289,9 +289,30 @@ feature {NONE} -- Implementation empty_body: res.transfered_content_length = 0 end + frozen handle_use_proxy (req: WSF_REQUEST; res: WSF_RESPONSE) + -- Write response to OPTIONS * into `res'. + require + res_attached: res /= Void + req_attached: req /= Void + proxy_required: requires_proxy (req) + local + h: HTTP_HEADER + do + create h.make + h.put_content_type_text_plain + h.put_current_date + h.put_location (proxy_server (req)) + h.put_content_length (0) + res.set_status_code ({HTTP_STATUS_CODE}.use_proxy) + ensure + response_status_is_set: res.status_is_set + response_code_use_proxy: res.status_code = {HTTP_STATUS_CODE}.use_proxy + header_sent: res.header_committed and res.message_committed + end + invariant - unavailability_message_attached: unavailable implies attached {READABLE_STRING_8} unavailablity_message as m and then + unavailability_message_attached: unavailable implies attached unavailablity_message as m and then m.count > 0 unavailability_duration_xor_unavailable_until: unavailability_duration > 0 implies unavailable_until = Void diff --git a/library/server/wsf/router/wsf_router.e b/library/server/wsf/router/wsf_router.e index 08a9c965..30785eb7 100644 --- a/library/server/wsf/router/wsf_router.e +++ b/library/server/wsf/router/wsf_router.e @@ -277,6 +277,26 @@ feature -- Status report end end + all_allowed_methods: WSF_REQUEST_METHODS + -- Methods allowed for ALL requests handled by `Current' + local + l_mapping: WSF_ROUTER_MAPPING + do + create Result + across + mappings as c + loop + if attached c.item.request_methods as m then + Result := Result + m + end + l_mapping := c.item.mapping + if attached {WSF_ROUTING_HANDLER} l_mapping.handler as l_routing then + Result := Result + l_routing.router.all_allowed_methods + end + --| not sure if that covers everything - Jocelyn, please comment + end + end + feature -- Hook execute_before (a_mapping: WSF_ROUTER_MAPPING) diff --git a/library/server/wsf/src/response/wsf_method_not_allowed_response.e b/library/server/wsf/src/response/wsf_method_not_allowed_response.e index 5e647c63..17478ccd 100644 --- a/library/server/wsf/src/response/wsf_method_not_allowed_response.e +++ b/library/server/wsf/src/response/wsf_method_not_allowed_response.e @@ -76,25 +76,44 @@ feature {WSF_RESPONSE} -- Output send_to (res: WSF_RESPONSE) local - s: STRING + s, l_html_error_code_text: STRING l_text: detachable READABLE_STRING_GENERAL l_loc: detachable READABLE_STRING_8 h: like header + l_recognized: BOOLEAN + l_messages: HTTP_STATUS_CODE_MESSAGES do + create l_messages h := header - res.set_status_code ({HTTP_STATUS_CODE}.method_not_allowed) + l_recognized := recognized_methods.has (request.request_method.as_upper) + if l_recognized then + res.set_status_code (l_messages.method_not_allowed) + else + res.set_status_code (l_messages.not_implemented) + end if attached suggested_methods as lst and then not lst.is_empty then h.put_allow (lst) end - s := "Not allowed" + if attached l_messages.http_status_code_message (res.status_code) as l_msg then + s := l_msg + else + check + impossible: False + -- as res.status_code is set to one of the codes that will produce + -- a non-void response, even though there is no postcondition to prove it + end + s := "Bug in server" + end + l_html_error_code_text := html_error_code_text (l_messages, l_recognized) + if request.is_content_type_accepted ({HTTP_MIME_TYPES}.text_html) then s := "" s.append ("") s.append (html_encoder.encoded_string (request.request_uri)) - s.append ("Error 405 (Method Not Allowed)!!") + s.append (l_html_error_code_text + "!!") s.append ("%N") s.append ( "[ @@ -111,15 +130,15 @@ feature {WSF_RESPONSE} -- Output - + ]") s.append ("
") s.append ("
") s.append ("
") s.append ("
") s.append ("
") - s.append ("Error 405 (Method Not Allowed)
") - s.append ("
Error 405 (Method Not Allowed): the request method ") + s.append (l_html_error_code_text + "
") + s.append ("
" + l_html_error_code_text + ": the request method ") s.append (request.request_method) s.append (" is inappropriate for the URL for " + html_encoder.encoded_string (request.request_uri) + ".
") if attached suggested_methods as lst and then not lst.is_empty then @@ -180,7 +199,7 @@ feature {WSF_RESPONSE} -- Output h.put_content_type_text_html else - s := "Error 405 (Method Not Allowed): the request method " + s := l_html_error_code_text + ": the request method " s.append (request.request_method) s.append (" is inappropriate for the URL for '" + html_encoder.encoded_string (request.request_uri) + "'.%N") if attached suggested_methods as lst and then not lst.is_empty then @@ -239,6 +258,53 @@ feature {WSF_RESPONSE} -- Output res.flush end +feature {NONE} -- Implementation + + recognized_methods: WSF_REQUEST_METHODS + -- All methods defined in HTTP/1.1 specification + --| Should this include CONNECT? It probably shouldn't be recognized by an origin server, + --| We will need a way to extend this for additional methods that the server implements. E.g. PATCH. + do + create Result.make_from_iterable (<< + {HTTP_REQUEST_METHODS}.method_head, + {HTTP_REQUEST_METHODS}.method_get, + {HTTP_REQUEST_METHODS}.method_trace, + {HTTP_REQUEST_METHODS}.method_options, + {HTTP_REQUEST_METHODS}.method_post, + {HTTP_REQUEST_METHODS}.method_put, + {HTTP_REQUEST_METHODS}.method_delete + >>) + ensure + recognized_methods_not_void: Result /= Void + end + + html_error_code_text (a_messages: HTTP_STATUS_CODE_MESSAGES; a_recognized: BOOLEAN): READABLE_STRING_8 + -- Message for including in HTML error text according to `a_recognized' + require + a_messages_attached: a_messages /= Void + local + l_code: INTEGER + do + if a_recognized then + l_code := a_messages.method_not_allowed + else + l_code := a_messages.not_implemented + end + if attached a_messages.http_status_code_message (l_code) as l_msg then + Result := "Error " + l_code.out + " (" + l_msg + ")" + else + check + impossible: False + -- as res.status_code is set to one of the codes that will produce + -- a non-void response, even though there is no postcondition to prove it. + -- The postcondition wouldn't be needed if there was a precondition using is_valid_http_status_code + end + Result := "Bug in server" + end + ensure + html_error_code_text_attached: Result /= Void + end + note copyright: "2011-2012, Jocelyn Fiat, Javier Velilla, Olivier Ligot, Eiffel Software and others" license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"