From 11286eeeef299a5f4b9a35ac4a3da379e5913257 Mon Sep 17 00:00:00 2001 From: Jocelyn Fiat Date: Fri, 9 Sep 2011 14:10:54 +0200 Subject: [PATCH] make router more easy to inherit from and specialized --- .../src/hello_routed_world.e | 2 +- library/server/request/router/router-safe.ecf | 8 +- .../src/application/routed_application.e | 7 +- .../src/context/request_handler_context.e | 47 +++++++- .../router/src/handler/request_handler.e | 104 ++++-------------- .../src/helper}/routed_application_helper.e | 62 ++--------- .../router/src/router/request_router.e | 52 +++++++-- .../router/src/router/request_uri_router.e | 29 +++-- .../src/router/request_uri_template_router.e | 28 +++-- 9 files changed, 164 insertions(+), 175 deletions(-) rename {examples/hello_routed_world/src/framework => library/server/request/router/src/helper}/routed_application_helper.e (55%) diff --git a/examples/hello_routed_world/src/hello_routed_world.e b/examples/hello_routed_world/src/hello_routed_world.e index a178b03a..ad5487c7 100644 --- a/examples/hello_routed_world/src/hello_routed_world.e +++ b/examples/hello_routed_world/src/hello_routed_world.e @@ -140,7 +140,7 @@ feature -- Execution msg := "Hello anonymous visitor !%N" end content_type_supported := <<{HTTP_CONSTANTS}.json_app, {HTTP_CONSTANTS}.html_text, {HTTP_CONSTANTS}.xml_text, {HTTP_CONSTANTS}.plain_text>> - inspect request_format_id (ctx, "format", content_type_supported) + inspect ctx.request_format_id ("format", content_type_supported) when {HTTP_FORMAT_CONSTANTS}.json then l_response_content_type := {HTTP_CONSTANTS}.json_app msg := "{%N%"application%": %"/hello%",%N %"message%": %"" + msg + "%" %N}" diff --git a/library/server/request/router/router-safe.ecf b/library/server/request/router/router-safe.ecf index f655ffe4..040eb070 100644 --- a/library/server/request/router/router-safe.ecf +++ b/library/server/request/router/router-safe.ecf @@ -7,15 +7,15 @@ /EIFGENs$ /.svn$ - - + @@ -32,8 +32,8 @@ - + - + diff --git a/library/server/request/router/src/application/routed_application.e b/library/server/request/router/src/application/routed_application.e index c22e6080..5cb24525 100644 --- a/library/server/request/router/src/application/routed_application.e +++ b/library/server/request/router/src/application/routed_application.e @@ -36,10 +36,11 @@ feature -- Setup feature -- Execution execute (req: WGI_REQUEST; res: WGI_RESPONSE_BUFFER) + local + l_handled: BOOLEAN do - if attached router.dispatch (req, res) as r then - --| done - else + l_handled := router.dispatch (req, res) + if not l_handled then execute_default (req, res) end end diff --git a/library/server/request/router/src/context/request_handler_context.e b/library/server/request/router/src/context/request_handler_context.e index 9fa5f0bc..666ff7ca 100644 --- a/library/server/request/router/src/context/request_handler_context.e +++ b/library/server/request/router/src/context/request_handler_context.e @@ -15,14 +15,59 @@ inherit {NONE} all end + HTTP_FORMAT_CONSTANTS + export + {NONE} all + end + feature -- Access request: WGI_REQUEST -- Associated request - path: READABLE_STRING_GENERAL + path: READABLE_STRING_8 -- Associated path + request_format (a_format_variable_name: detachable STRING; content_type_supported: detachable ARRAY [STRING]): detachable READABLE_STRING_8 + -- Format id for the request based on {HTTP_FORMAT_CONSTANTS} + local + do + if a_format_variable_name /= Void and then attached parameter (a_format_variable_name) as ctx_format then + Result := ctx_format.as_string_8 + else + Result := content_type_to_request_format (request_content_type (content_type_supported)) + end + end + + + request_format_id (a_format_variable_name: detachable STRING; content_type_supported: detachable ARRAY [STRING]): INTEGER + -- Format id for the request based on {HTTP_FORMAT_CONSTANTS} + do + if attached request_format (a_format_variable_name, content_type_supported) as l_format then + Result := format_id (l_format) + else + Result := 0 + end + end + + content_type_to_request_format (a_content_type: detachable READABLE_STRING_8): detachable READABLE_STRING_8 + -- `a_content_type' converted into a request format name + do + if a_content_type /= Void then + if a_content_type.same_string ({HTTP_CONSTANTS}.json_text) then + Result := {HTTP_FORMAT_CONSTANTS}.json_name + elseif a_content_type.same_string ({HTTP_CONSTANTS}.json_app) then + Result := {HTTP_FORMAT_CONSTANTS}.json_name + elseif a_content_type.same_string ({HTTP_CONSTANTS}.xml_text) then + Result := {HTTP_FORMAT_CONSTANTS}.xml_name + elseif a_content_type.same_string ({HTTP_CONSTANTS}.html_text) then + Result := {HTTP_FORMAT_CONSTANTS}.html_name + elseif a_content_type.same_string ({HTTP_CONSTANTS}.plain_text) then + Result := {HTTP_FORMAT_CONSTANTS}.text_name + end + end + end + request_content_type (content_type_supported: detachable ARRAY [STRING]): detachable READABLE_STRING_8 local s: detachable READABLE_STRING_32 diff --git a/library/server/request/router/src/handler/request_handler.e b/library/server/request/router/src/handler/request_handler.e index 7c4cd294..9c816624 100644 --- a/library/server/request/router/src/handler/request_handler.e +++ b/library/server/request/router/src/handler/request_handler.e @@ -7,6 +7,14 @@ note deferred class REQUEST_HANDLER +inherit + ANY + + ROUTED_APPLICATION_HELPER + export + {NONE} all + end + feature {NONE} -- Initialization initialize @@ -42,7 +50,7 @@ feature -- Execution execute_application (a_hdl_context, req, res) post_execute (req, res) else - execute_method_not_allowed (a_hdl_context, req, res) + execute_request_method_not_allowed (req, res, supported_request_method_names) end else rescue_execute (req, res) @@ -52,29 +60,6 @@ feature -- Execution retry end - execute_method_not_allowed (a_hdl_context: REQUEST_HANDLER_CONTEXT; req: WGI_REQUEST; res: WGI_RESPONSE_BUFFER) - local - s: STRING - lst: LIST [STRING] - do - res.set_status_code ({HTTP_STATUS_CODE}.method_not_allowed) - create s.make (25) - from - lst := supported_request_method_names - lst.start - until - lst.after - loop - s.append_string (lst.item) - if not lst.islast then - s.append_character (',') - s.append_character (' ') - end - lst.forth - end - res.write_header ({HTTP_STATUS_CODE}.method_not_allowed, <<["Allow", s]>>) - end - execute_application (a_hdl_context: REQUEST_HANDLER_CONTEXT; req: WGI_REQUEST; res: WGI_RESPONSE_BUFFER) -- Execute request handler deferred @@ -101,76 +86,27 @@ feature -- Execution feature -- Execution: report --- execution_information (req: WGI_REQUEST): detachable REQUEST_HANDLER_CONTEXT --- -- Execution information related to the request --- do --- if attached path_information (req, req.environment.path_info) as info then --- create Result.make (req.environment.path_info) --- end --- end - --- path_information (req: WGI_REQUEST; a_rq_path: STRING): detachable TUPLE [format: detachable STRING; arguments: detachable STRING] --- -- Information related to `a_path' --- local --- l_rq_path: STRING --- i,p,n: INTEGER --- l_format, l_args: detachable STRING --- do --- l_rq_path := a_rq_path --- if l_rq_path.count > 0 and then l_rq_path[1] /= '/' then --- l_rq_path := "/" + l_rq_path --- end --- n := l_rq_path.count --- i := req.environment.path_info.count + 1 - --- if format_located_before_parameters then --- --| path = app-path{.format}/parameters - --- if l_rq_path.valid_index (i) and then l_rq_path[i] = '.' then --- p := l_rq_path.index_of ('/', i + 1) --- if p = 0 then --- p := n + 1 --- else --- l_args := l_rq_path.substring (p + 1, n) --- end --- l_format := l_rq_path.substring (i + 1, p - 1) --- elseif n > i then --- check l_rq_path[i] = '/' end --- l_args := l_rq_path.substring (i + 1, n) --- end --- elseif format_located_after_parameters then --- --| path = app-path/parameters{.format} - --- p := l_rq_path.last_index_of ('.', n) --- if p > i then --- l_format := l_rq_path.substring (p + 1, n) --- l_args := l_rq_path.substring (i + 1, p - 1) --- elseif n > i then --- check l_rq_path[i] = '/' end --- l_format := Void --- l_args := l_rq_path.substring (i + 1, n) --- end --- end --- if l_format /= Void or l_args /= Void then --- Result := [l_format, l_args] --- end --- end - - url (req: WGI_REQUEST; args: detachable STRING; abs: BOOLEAN): STRING - -- Associated url based on `path' and `args' + url (req: WGI_REQUEST; a_base: detachable READABLE_STRING_8; args: detachable STRING; abs: BOOLEAN): STRING + -- Associated url based on `a_base' and `args' -- if `abs' then return absolute url local s: detachable STRING + l_base: STRING do + if a_base /= Void then + l_base := a_base + else + l_base := req.request_uri + end s := args if s /= Void and then s.count > 0 then if s[1] /= '/' then - s := req.request_uri + "/" + s + s := l_base + "/" + s else - s := req.request_uri + s + s := l_base + s end else - s := req.request_uri + s := l_base end if abs then Result := req.absolute_script_url (s) diff --git a/examples/hello_routed_world/src/framework/routed_application_helper.e b/library/server/request/router/src/helper/routed_application_helper.e similarity index 55% rename from examples/hello_routed_world/src/framework/routed_application_helper.e rename to library/server/request/router/src/helper/routed_application_helper.e index 92349a8b..0cce19bf 100644 --- a/examples/hello_routed_world/src/framework/routed_application_helper.e +++ b/library/server/request/router/src/helper/routed_application_helper.e @@ -10,11 +10,6 @@ class inherit ANY - HTTP_FORMAT_CONSTANTS - export - {NONE} all - end - feature -- Helper execute_content_type_not_allowed (req: WGI_REQUEST; res: WGI_RESPONSE_BUFFER; a_content_types: detachable ARRAY [STRING]; a_uri_formats: detachable ARRAY [STRING]) @@ -70,26 +65,22 @@ feature -- Helper end end - execute_method_not_allowed (req: WGI_REQUEST; res: WGI_RESPONSE_BUFFER; a_methods: ARRAY [STRING]) + execute_request_method_not_allowed (req: WGI_REQUEST; res: WGI_RESPONSE_BUFFER; a_methods: ITERABLE [STRING]) local s: STRING - i, n: INTEGER do - create s.make (10) - from - i := a_methods.lower - n := a_methods.upper - until - i > n + res.set_status_code ({HTTP_STATUS_CODE}.method_not_allowed) + create s.make (25) + across + a_methods as c loop - s.append_string (a_methods[i]) - if i < n then + if not s.is_empty then s.append_character (',') s.append_character (' ') end - i := i + 1 + s.append_string (c.item) end - + res.set_status_code ({HTTP_STATUS_CODE}.method_not_allowed) res.write_header ({HTTP_STATUS_CODE}.method_not_allowed, << ["Content-Type", {HTTP_CONSTANTS}.plain_text], ["Allow", s] @@ -97,43 +88,6 @@ feature -- Helper res.write_string ("Unsupported request method, Allow: " + s + "%N") end -feature -- Context helper - - request_format_id (ctx: REQUEST_HANDLER_CONTEXT; a_format_variable_name: detachable STRING; content_type_supported: detachable ARRAY [STRING]): INTEGER - -- Format id for the request based on {HTTP_FORMAT_CONSTANTS} - local - l_format: detachable STRING_8 - do - if a_format_variable_name /= Void and then attached ctx.parameter (a_format_variable_name) as ctx_format then - l_format := ctx_format.as_string_8 - else - l_format := content_type_to_request_format (ctx.request_content_type (content_type_supported)) - end - if l_format /= Void then - Result := format_id (l_format) - else - Result := 0 - end - end - - content_type_to_request_format (a_content_type: detachable READABLE_STRING_8): detachable STRING - -- `a_content_type' converted into a request format name - do - if a_content_type /= Void then - if a_content_type.same_string ({HTTP_CONSTANTS}.json_text) then - Result := {HTTP_FORMAT_CONSTANTS}.json_name - elseif a_content_type.same_string ({HTTP_CONSTANTS}.json_app) then - Result := {HTTP_FORMAT_CONSTANTS}.json_name - elseif a_content_type.same_string ({HTTP_CONSTANTS}.xml_text) then - Result := {HTTP_FORMAT_CONSTANTS}.xml_name - elseif a_content_type.same_string ({HTTP_CONSTANTS}.html_text) then - Result := {HTTP_FORMAT_CONSTANTS}.html_name - elseif a_content_type.same_string ({HTTP_CONSTANTS}.plain_text) then - Result := {HTTP_FORMAT_CONSTANTS}.text_name - end - end - end - note copyright: "2011-2011, Eiffel Software and others" license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)" diff --git a/library/server/request/router/src/router/request_router.e b/library/server/request/router/src/router/request_router.e index b5f0e414..a16c9cd7 100644 --- a/library/server/request/router/src/router/request_router.e +++ b/library/server/request/router/src/router/request_router.e @@ -7,9 +7,6 @@ note deferred class REQUEST_ROUTER -inherit - ITERABLE [TUPLE [handler: REQUEST_HANDLER; resource: READABLE_STRING_8; request_methods: detachable ARRAY [READABLE_STRING_8]]] - feature -- Registration map_default (r: like default_handler) @@ -17,7 +14,7 @@ feature -- Registration -- If no route/handler is found, -- then use `default_handler' as default if not Void do - default_handler := r + set_default_handler (r) end map (a_id: READABLE_STRING_8; h: REQUEST_HANDLER) @@ -46,13 +43,20 @@ feature -- Registration feature -- Execution - dispatch (req: WGI_REQUEST; res: WGI_RESPONSE_BUFFER): detachable REQUEST_HANDLER + dispatch (req: WGI_REQUEST; res: WGI_RESPONSE_BUFFER): BOOLEAN + -- Dispatch `req, res' to the associated handler + -- And return True is handled, otherwise False + do + Result := dispatch_and_return_handler (req, res) /= Void + end + + dispatch_and_return_handler (req: WGI_REQUEST; res: WGI_RESPONSE_BUFFER): like default_handler -- Dispatch `req, res' to the associated handler -- And return this handler -- If Result is Void, this means no handler was found. local d: like handler - ctx: detachable REQUEST_HANDLER_CONTEXT + ctx: detachable like default_handler_context do d := handler (req) if d /= Void then @@ -60,21 +64,29 @@ feature -- Execution ctx := d.context else Result := default_handler - end - if Result /= Void then - if ctx = Void then - check is_default_handler: Result = default_handler end - create {REQUEST_URI_HANDLER_CONTEXT} ctx.make (req, "/") + if Result /= Void then + ctx := default_handler_context (req) end + end + if Result /= Void and ctx /= Void then Result.execute (ctx, req, res) end ensure result_void_implie_no_default: Result = Void implies default_handler = Void end +feature -- Traversing + + new_cursor: ITERATION_CURSOR [TUPLE [handler: REQUEST_HANDLER; resource: READABLE_STRING_8; request_methods: detachable ARRAY [READABLE_STRING_8]]] + -- Fresh cursor associated with current structure + deferred + ensure + result_attached: Result /= Void + end + feature {NONE} -- Access: Implementation - handler (req: WGI_REQUEST): detachable TUPLE [handler: REQUEST_HANDLER; context: REQUEST_HANDLER_CONTEXT] + handler (req: WGI_REQUEST): detachable TUPLE [handler: attached like default_handler; context: like default_handler_context] -- Handler whose map matched with `req' require req_valid: req /= Void and then req.path_info /= Void @@ -127,8 +139,24 @@ feature {NONE} -- Access: Implementation feature {NONE} -- Implementation + set_default_handler (h: like default_handler) + -- Set `default_handler' to `h' + deferred + ensure + default_handler_set: h = default_handler + end + default_handler: detachable REQUEST_HANDLER -- Default handler + deferred + end + + default_handler_context (req: WGI_REQUEST): REQUEST_HANDLER_CONTEXT + -- Default handler context associated with `default_handler' + require + has_default_handler: default_handler /= Void + deferred + end ;note copyright: "2011-2011, Eiffel Software and others" diff --git a/library/server/request/router/src/router/request_uri_router.e b/library/server/request/router/src/router/request_uri_router.e index 303a1c3c..4480a354 100644 --- a/library/server/request/router/src/router/request_uri_router.e +++ b/library/server/request/router/src/router/request_uri_router.e @@ -30,10 +30,10 @@ feature -- Registration feature {NONE} -- Access: Implementation - handler (req: WGI_REQUEST): detachable TUPLE [handler: REQUEST_HANDLER; context: REQUEST_HANDLER_CONTEXT] + handler (req: WGI_REQUEST): detachable TUPLE [handler: REQUEST_HANDLER; context: like default_handler_context] local h: detachable REQUEST_HANDLER - ctx: detachable REQUEST_HANDLER_CONTEXT + ctx: detachable like default_handler_context do h := handler_by_path (req.path_info, req.request_method) if h = Void then @@ -82,7 +82,6 @@ feature {NONE} -- Access: Implementation end l_handlers.forth end --- Result := handlers.item (context_path (a_path)) ensure a_path_unchanged: a_path.same_string (old a_path) end @@ -115,14 +114,14 @@ feature {NONE} -- Access: Implementation a_path_unchanged: a_path.same_string (old a_path) end -feature -- Context factory +feature {NONE} -- Context factory - handler_context (p: detachable STRING; req: WGI_REQUEST): REQUEST_URI_HANDLER_CONTEXT + handler_context (p: detachable STRING; req: WGI_REQUEST): like default_handler_context do if p /= Void then - create Result.make (req, p) + create {REQUEST_URI_HANDLER_CONTEXT} Result.make (req, p) else - create Result.make (req, req.path_info) + create {REQUEST_URI_HANDLER_CONTEXT} Result.make (req, req.path_info) end end @@ -165,7 +164,21 @@ feature {NONE} -- Implementation result_not_empty: not Result.is_empty end -;note +feature {NONE} -- Default: implementation + + default_handler: detachable REQUEST_HANDLER + + set_default_handler (h: like default_handler) + do + default_handler := h + end + + default_handler_context (req: WGI_REQUEST): REQUEST_HANDLER_CONTEXT + do + Result := handler_context (Void, req) + end + +note copyright: "2011-2011, Eiffel Software and others" license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)" source: "[ diff --git a/library/server/request/router/src/router/request_uri_template_router.e b/library/server/request/router/src/router/request_uri_template_router.e index d4554320..e73e3d11 100644 --- a/library/server/request/router/src/router/request_uri_template_router.e +++ b/library/server/request/router/src/router/request_uri_template_router.e @@ -45,9 +45,8 @@ feature -- Registration feature {NONE} -- Access: Implementation - handler (req: WGI_REQUEST): detachable TUPLE [handler: REQUEST_HANDLER; context: REQUEST_HANDLER_CONTEXT] + handler (req: WGI_REQUEST): detachable TUPLE [handler: attached like default_handler; context: like default_handler_context] local - ctx: detachable REQUEST_URI_TEMPLATE_HANDLER_CONTEXT l_handlers: like handlers t: STRING p: STRING @@ -67,8 +66,7 @@ feature {NONE} -- Access: Implementation if attached templates.item (t) as tpl and then attached tpl.match (p) as res then - ctx := handler_context (p, req, tpl, res) - Result := [l_info.handler, ctx] + Result := [l_info.handler, handler_context (p, req, tpl, res)] end end end @@ -76,14 +74,14 @@ feature {NONE} -- Access: Implementation end end -feature -- Context factory +feature {NONE} -- Context factory - handler_context (p: detachable STRING; req: WGI_REQUEST; tpl: URI_TEMPLATE; tpl_res: URI_TEMPLATE_MATCH_RESULT): REQUEST_URI_TEMPLATE_HANDLER_CONTEXT + handler_context (p: detachable STRING; req: WGI_REQUEST; tpl: URI_TEMPLATE; tpl_res: URI_TEMPLATE_MATCH_RESULT): like default_handler_context do if p /= Void then - create Result.make (req, tpl, tpl_res, p) + create {REQUEST_URI_TEMPLATE_HANDLER_CONTEXT} Result.make (req, tpl, tpl_res, p) else - create Result.make (req, tpl, tpl_res, req.path_info) + create {REQUEST_URI_TEMPLATE_HANDLER_CONTEXT} Result.make (req, tpl, tpl_res, req.path_info) end end @@ -126,6 +124,20 @@ feature {NONE} -- Implementation result_not_empty: not Result.is_empty end +feature {NONE} -- Default: implementation + + default_handler: detachable REQUEST_HANDLER + + set_default_handler (h: like default_handler) + do + default_handler := h + end + + default_handler_context (req: WGI_REQUEST): REQUEST_HANDLER_CONTEXT + do + create {REQUEST_URI_HANDLER_CONTEXT} Result.make (req, "/") + end + ;note copyright: "2011-2011, Eiffel Software and others" license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"