From 1b49445077938665e68ba71600e8beb4079f4e50 Mon Sep 17 00:00:00 2001 From: Jocelyn Fiat Date: Fri, 29 Jul 2011 10:51:22 +0200 Subject: [PATCH] Added first draft for a URI and/or URI-template base request router. --- library/server/request/router/doc/README.txt | 14 + library/server/request/router/license.lic | 10 + library/server/request/router/router-safe.ecf | 20 ++ library/server/request/router/router.ecf | 20 ++ .../src/context/request_handler_context.e | 71 ++++ .../src/context/request_uri_handler_context.e | 45 +++ .../request_uri_template_handler_context.e | 57 ++++ .../src/handler/request_agent_handler.e | 45 +++ .../router/src/handler/request_handler.e | 310 ++++++++++++++++++ .../router/src/router/request_router.e | 87 +++++ .../router/src/router/request_uri_router.e | 169 ++++++++++ .../src/router/request_uri_template_router.e | 138 ++++++++ 12 files changed, 986 insertions(+) create mode 100644 library/server/request/router/doc/README.txt create mode 100644 library/server/request/router/license.lic create mode 100644 library/server/request/router/router-safe.ecf create mode 100644 library/server/request/router/router.ecf create mode 100644 library/server/request/router/src/context/request_handler_context.e create mode 100644 library/server/request/router/src/context/request_uri_handler_context.e create mode 100644 library/server/request/router/src/context/request_uri_template_handler_context.e create mode 100644 library/server/request/router/src/handler/request_agent_handler.e create mode 100644 library/server/request/router/src/handler/request_handler.e create mode 100644 library/server/request/router/src/router/request_router.e create mode 100644 library/server/request/router/src/router/request_uri_router.e create mode 100644 library/server/request/router/src/router/request_uri_template_router.e diff --git a/library/server/request/router/doc/README.txt b/library/server/request/router/doc/README.txt new file mode 100644 index 00000000..78339059 --- /dev/null +++ b/library/server/request/router/doc/README.txt @@ -0,0 +1,14 @@ +This library introduce the notion of router and handler +The Router manages the association between URI,URI template and Handler +The Router is in charge to route/dispatch a request to one of the Handler according to the URI and how the Handler is mapped in the Router. + +Common usage + +router: REQUEST_ROUTER +hello_handler: REQUEST_HANDLER + +create {REQUEST_URI_TEMPLATE_ROUTER} router.make +create {REQUEST_AGENT_HANDLER} hello_handler.make (agent handle_hello) + +router.map ("/hello/{name}", hello_handler) +router.map_agent ("/hello/{name}", agent handle_hello) \ No newline at end of file diff --git a/library/server/request/router/license.lic b/library/server/request/router/license.lic new file mode 100644 index 00000000..cf2d1ed9 --- /dev/null +++ b/library/server/request/router/license.lic @@ -0,0 +1,10 @@ +${NOTE_KEYWORD} + copyright: "2011-${YEAR}, 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 + ]" diff --git a/library/server/request/router/router-safe.ecf b/library/server/request/router/router-safe.ecf new file mode 100644 index 00000000..48d0b68b --- /dev/null +++ b/library/server/request/router/router-safe.ecf @@ -0,0 +1,20 @@ + + + + + + /.git$ + /EIFGENs$ + /.svn$ + + + + + + + + + + diff --git a/library/server/request/router/router.ecf b/library/server/request/router/router.ecf new file mode 100644 index 00000000..48d0b68b --- /dev/null +++ b/library/server/request/router/router.ecf @@ -0,0 +1,20 @@ + + + + + + /.git$ + /EIFGENs$ + /.svn$ + + + + + + + + + + diff --git a/library/server/request/router/src/context/request_handler_context.e b/library/server/request/router/src/context/request_handler_context.e new file mode 100644 index 00000000..97f404d8 --- /dev/null +++ b/library/server/request/router/src/context/request_handler_context.e @@ -0,0 +1,71 @@ +note + description: "Summary description for {REQUEST_HANDLER_CONTEXT}." + author: "" + date: "$Date$" + revision: "$Revision$" + +deferred class + REQUEST_HANDLER_CONTEXT + +feature -- Access + + request: EWSGI_REQUEST + -- Associated request + + path: STRING + -- ??? + + request_format: detachable STRING + -- Request format based on `Content-Type'. + local + s: detachable STRING + do + s := request.environment.content_type + if s = Void then + elseif s.same_string ({HTTP_CONSTANTS}.json_text) then + Result := {HTTP_FORMAT_CONSTANTS}.json_name + elseif s.same_string ({HTTP_CONSTANTS}.json_app) then + Result := {HTTP_FORMAT_CONSTANTS}.json_name + elseif s.same_string ({HTTP_CONSTANTS}.xml_text) then + Result := {HTTP_FORMAT_CONSTANTS}.xml_name + elseif s.same_string ({HTTP_CONSTANTS}.html_text) then + Result := {HTTP_FORMAT_CONSTANTS}.html_name + elseif s.same_string ({HTTP_CONSTANTS}.plain_text) then + Result := {HTTP_FORMAT_CONSTANTS}.text_name + end + end + +feature -- Query + + path_parameter (a_name: STRING): detachable STRING_32 + -- Parameter value for path variable `a_name' + deferred + end + + query_parameter (a_name: STRING): detachable STRING_32 + -- Parameter value for query variable `a_name' + --| i.e after the ? character + deferred + end + + parameter (a_name: STRING): detachable STRING_32 + -- Any parameter value for variable `a_name' + -- URI template parameter and query parameters + do + Result := query_parameter (a_name) + if Result = Void then + Result := path_parameter (a_name) + end + end + +;note + copyright: "2011-2011, 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/request/router/src/context/request_uri_handler_context.e b/library/server/request/router/src/context/request_uri_handler_context.e new file mode 100644 index 00000000..1cbecbce --- /dev/null +++ b/library/server/request/router/src/context/request_uri_handler_context.e @@ -0,0 +1,45 @@ +note + description: "Summary description for {REQUEST_URI_HANDLER_CONTEXT}." + author: "" + date: "$Date$" + revision: "$Revision$" + +class + REQUEST_URI_HANDLER_CONTEXT + +inherit + REQUEST_HANDLER_CONTEXT + +create + make + +feature {NONE} -- Initialization + + make (req: EWSGI_REQUEST; p: like path) + do + request := req + path := p + end + +feature -- Query + + path_parameter (a_name: STRING): detachable STRING_32 + do + end + + query_parameter (a_name: STRING): detachable STRING_32 + do + Result := request.parameter (a_name) + end + +note + copyright: "2011-2011, 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/request/router/src/context/request_uri_template_handler_context.e b/library/server/request/router/src/context/request_uri_template_handler_context.e new file mode 100644 index 00000000..678e35b8 --- /dev/null +++ b/library/server/request/router/src/context/request_uri_template_handler_context.e @@ -0,0 +1,57 @@ +note + description: "Summary description for {REQUEST_URI_TEMPLATE_HANDLER_CONTEXT}." + author: "" + date: "$Date$" + revision: "$Revision$" + +class + REQUEST_URI_TEMPLATE_HANDLER_CONTEXT + +inherit + REQUEST_HANDLER_CONTEXT + +create + make + +feature {NONE} -- Initialization + + make (req: EWSGI_REQUEST; tpl: URI_TEMPLATE; tpl_res: URI_TEMPLATE_MATCH_RESULT; p: like path) + do + request := req + uri_template := tpl + uri_template_match := tpl_res + path := p + end + +feature -- Access + + uri_template: URI_TEMPLATE + + uri_template_match: URI_TEMPLATE_MATCH_RESULT + +feature -- Query + + path_parameter (a_name: STRING): detachable STRING_32 + do + Result := uri_template_match.url_decoded_path_variable (a_name) + end + + query_parameter (a_name: STRING): detachable STRING_32 + do + Result := uri_template_match.url_decoded_query_variable (a_name) + if Result = Void then + Result := request.parameter (a_name) + end + end + +note + copyright: "2011-2011, 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/request/router/src/handler/request_agent_handler.e b/library/server/request/router/src/handler/request_agent_handler.e new file mode 100644 index 00000000..f95f0b8c --- /dev/null +++ b/library/server/request/router/src/handler/request_agent_handler.e @@ -0,0 +1,45 @@ +note + description: "Summary description for REQUEST_AGENT_HANDLER." + author: "" + date: "$Date$" + revision: "$Revision$" + +class + REQUEST_AGENT_HANDLER + +inherit + REQUEST_HANDLER + +create + make + +feature -- Initialization + + make (act: like action) + do + action := act + initialize + end + +feature -- Access + + action: PROCEDURE [ANY, TUPLE [ctx: REQUEST_HANDLER_CONTEXT; req: EWSGI_REQUEST; res: EWSGI_RESPONSE_STREAM]] + +feature -- Execution + + execute_application (ctx: REQUEST_HANDLER_CONTEXT; req: EWSGI_REQUEST; res: EWSGI_RESPONSE_STREAM) + do + action.call ([ctx, req, res]) + end + +note + copyright: "2011-2011, 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/request/router/src/handler/request_handler.e b/library/server/request/router/src/handler/request_handler.e new file mode 100644 index 00000000..fe4525bd --- /dev/null +++ b/library/server/request/router/src/handler/request_handler.e @@ -0,0 +1,310 @@ +note + description: "Summary description for {REQUEST_HANDLER}." + author: "" + date: "$Date$" + revision: "$Revision$" + +deferred class + REQUEST_HANDLER + +feature {NONE} -- Initialization + + initialize + -- Initialize various attributes + do + end + +feature -- Access + + description: detachable STRING + -- Optional descriptiong + +feature -- Status report + + is_valid_context (req: EWSGI_REQUEST): BOOLEAN + -- Is `req' valid context for current handler? + do + Result := request_method_name_supported (req.environment.request_method) + end + +feature -- Execution + + execute (a_hdl_context: REQUEST_HANDLER_CONTEXT; req: EWSGI_REQUEST; res: EWSGI_RESPONSE_STREAM) + -- Execute request handler + require + is_valid_context: is_valid_context (req) + local + rescued: BOOLEAN + do + if not rescued then + if request_method_name_supported (req.environment.request_method) then + pre_execute (req) + execute_application (a_hdl_context, req, res) + post_execute (req, res) + else + execute_method_not_allowed (a_hdl_context, req, res) + end + else + rescue_execute (req, res) + end + rescue + rescued := True + retry + end + + execute_method_not_allowed (a_hdl_context: REQUEST_HANDLER_CONTEXT; req: EWSGI_REQUEST; res: EWSGI_RESPONSE_STREAM) + 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: EWSGI_REQUEST; res: EWSGI_RESPONSE_STREAM) + -- Execute request handler + deferred + end + + pre_execute (req: EWSGI_REQUEST) + -- Operation processed before `execute' + do + --| To be redefined if needed + end + + post_execute (req: EWSGI_REQUEST; res: EWSGI_RESPONSE_STREAM) + -- Operation processed after `execute' + do + --| To be redefined if needed + end + + rescue_execute (req: EWSGI_REQUEST; res: EWSGI_RESPONSE_STREAM) + -- Operation processed after a rescue + do + --| To be redefined if needed + post_execute (req, res) + end + +feature -- Execution: report + +-- execution_information (req: EWSGI_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: EWSGI_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: EWSGI_REQUEST; args: detachable STRING; abs: BOOLEAN): STRING + -- Associated url based on `path' and `args' + -- if `abs' then return absolute url + local + s: detachable STRING + do + s := args + if s /= Void and then s.count > 0 then + if s[1] /= '/' then + s := req.environment.request_uri + "/" + s + else + s := req.environment.request_uri + s + end + else + s := req.environment.request_uri + end + if abs then + Result := req.absolute_script_url (s) + else + Result := req.script_url (s) + end + ensure + result_attached: Result /= Void + end + +feature -- Element change + + set_description (s: like description) + -- Set `description' to `s' + do + description := s + end + +feature {NONE} -- Implementation + + supported_request_methods: INTEGER + -- Support request method such as GET, POST, ... + +feature {NONE} -- Status report + + request_method_id_supported (a_id: INTEGER): BOOLEAN + do + Result := (supported_request_methods & a_id) = a_id + end + + request_method_name_supported (n: STRING): BOOLEAN + -- Is request method `n' supported? + do + Result := request_method_id_supported (request_method_constants.method_id (n)) + end + + request_method_constants: HTTP_REQUEST_METHOD_CONSTANTS + once + create Result + end + +feature -- Status report + + supported_request_method_names: LIST [STRING] + -- Support request method such as GET, POST, ... + do + create {LINKED_LIST [STRING]} Result.make + if method_get_supported then + Result.extend (request_method_constants.method_get_name) + end + if method_post_supported then + Result.extend (request_method_constants.method_post_name) + end + if method_put_supported then + Result.extend (request_method_constants.method_put_name) + end + if method_delete_supported then + Result.extend (request_method_constants.method_delete_name) + end + if method_head_supported then + Result.extend (request_method_constants.method_head_name) + end + end + + method_get_supported: BOOLEAN + do + Result := request_method_id_supported ({HTTP_REQUEST_METHOD_CONSTANTS}.method_get) + end + + method_post_supported: BOOLEAN + do + Result := request_method_id_supported ({HTTP_REQUEST_METHOD_CONSTANTS}.method_post) + end + + method_put_supported: BOOLEAN + do + Result := request_method_id_supported ({HTTP_REQUEST_METHOD_CONSTANTS}.method_put) + end + + method_delete_supported: BOOLEAN + do + Result := request_method_id_supported ({HTTP_REQUEST_METHOD_CONSTANTS}.method_delete) + end + + method_head_supported: BOOLEAN + do + Result := request_method_id_supported ({HTTP_REQUEST_METHOD_CONSTANTS}.method_head) + end + +feature -- Element change: request methods + + reset_supported_request_methods + do + supported_request_methods := 0 + end + + enable_request_method_get + do + enable_request_method ({HTTP_REQUEST_METHOD_CONSTANTS}.method_get) + end + + enable_request_method_post + do + enable_request_method ({HTTP_REQUEST_METHOD_CONSTANTS}.method_post) + end + + enable_request_method_put + do + enable_request_method ({HTTP_REQUEST_METHOD_CONSTANTS}.method_put) + end + + enable_request_method_delete + do + enable_request_method ({HTTP_REQUEST_METHOD_CONSTANTS}.method_delete) + end + + enable_request_method_head + do + enable_request_method ({HTTP_REQUEST_METHOD_CONSTANTS}.method_head) + end + + enable_request_method (m: INTEGER) + do + supported_request_methods := supported_request_methods | m + end + +note + copyright: "2011-2011, 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/request/router/src/router/request_router.e b/library/server/request/router/src/router/request_router.e new file mode 100644 index 00000000..eef95ac6 --- /dev/null +++ b/library/server/request/router/src/router/request_router.e @@ -0,0 +1,87 @@ +note + description: "Summary description for {REQUEST_ROUTER}." + author: "" + date: "$Date$" + revision: "$Revision$" + +deferred class + REQUEST_ROUTER + +feature -- Registration + + map_default (r: like default_handler) + -- Map default handler + -- If no route/handler is found, + -- then use `default_handler' as default if not Void + do + default_handler := r + end + + map (a_id: STRING; h: REQUEST_HANDLER) + -- Map handler `h' with `a_id' + deferred + end + + map_agent (a_id: STRING; a_action: like {REQUEST_AGENT_HANDLER}.action) + local + h: REQUEST_AGENT_HANDLER + do + create h.make (a_action) + map (a_id, h) + end + +feature -- Execution + + dispatch (req: EWSGI_REQUEST; res: EWSGI_RESPONSE_STREAM): detachable REQUEST_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 + do + d := handler (req) + if d /= Void then + Result := d.handler + 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, "/") + end + Result.execute (ctx, req, res) + end + ensure + result_void_implie_no_default: Result = Void implies default_handler = Void + end + +feature {NONE} -- Access: Implementation + + handler (req: EWSGI_REQUEST): detachable TUPLE [handler: REQUEST_HANDLER; context: REQUEST_HANDLER_CONTEXT] + -- Handler whose map matched with `req' + require + req_valid: req /= Void and then req.environment.path_info /= Void + deferred + ensure + req_path_info_unchanged: req.environment.path_info.same_string (old req.environment.path_info) + end + +feature {NONE} -- Implementation + + default_handler: detachable REQUEST_HANDLER + -- Default handler + +;note + copyright: "2011-2011, 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/request/router/src/router/request_uri_router.e b/library/server/request/router/src/router/request_uri_router.e new file mode 100644 index 00000000..b4080739 --- /dev/null +++ b/library/server/request/router/src/router/request_uri_router.e @@ -0,0 +1,169 @@ +note + description: "Summary description for {REQUEST_URI_ROUTER}." + author: "" + date: "$Date$" + revision: "$Revision$" + +class + REQUEST_URI_ROUTER + +inherit + REQUEST_ROUTER + + ITERABLE [REQUEST_HANDLER] + redefine + new_cursor + end + +create + make + +feature -- Initialization + + make (n: INTEGER) + do + create handlers.make (n) + handlers.compare_objects + end + +feature -- Registration + + map (p: STRING; h: REQUEST_HANDLER) + do + handlers.force (h, p) + end + +feature {NONE} -- Access: Implementation + + handler (req: EWSGI_REQUEST): detachable TUPLE [handler: REQUEST_HANDLER; context: REQUEST_HANDLER_CONTEXT] + local + h: detachable REQUEST_HANDLER + ctx: detachable REQUEST_HANDLER_CONTEXT + do + h := handler_by_path (req.environment.path_info) + if h = Void then + if attached smart_handler_by_path (req.environment.path_info) as info then + h := info.handler + ctx := handler_context (info.path, req) + end + end + if h /= Void then + if h.is_valid_context (req) then + if ctx = Void then + ctx := handler_context (Void, req) + end + Result := [h, ctx] + else + Result := Void + end + end + end + + smart_handler (req: EWSGI_REQUEST): detachable TUPLE [path: STRING; handler: REQUEST_HANDLER] + require + req_valid: req /= Void and then req.environment.path_info /= Void + do + Result := smart_handler_by_path (req.environment.path_info) + ensure + req_path_info_unchanged: req.environment.path_info.same_string (old req.environment.path_info) + end + + handler_by_path (a_path: STRING): detachable REQUEST_HANDLER + require + a_path_valid: a_path /= Void + do + Result := handlers.item (context_path (a_path)) + ensure + a_path_unchanged: a_path.same_string (old a_path) + end + + smart_handler_by_path (a_path: STRING): detachable TUPLE [path: STRING; handler: REQUEST_HANDLER] + require + a_path_valid: a_path /= Void + local + p: INTEGER + l_context_path, l_path: STRING + h: detachable REQUEST_HANDLER + do + l_context_path := context_path (a_path) + from + p := l_context_path.count + 1 + until + p <= 1 or Result /= Void + loop + l_path := l_context_path.substring (1, p - 1) + h := handler_by_path (l_path) + if h /= Void then + Result := [l_path, h] + else + p := l_context_path.last_index_of ('/', p - 1) + end + variant + p + end + ensure + a_path_unchanged: a_path.same_string (old a_path) + end + +feature -- Context factory + + handler_context (p: detachable STRING; req: EWSGI_REQUEST): REQUEST_URI_HANDLER_CONTEXT + do + if p /= Void then + create Result.make (req, p) + else + create Result.make (req, req.environment.path_info) + end + end + +feature -- Access + + new_cursor: HASH_TABLE_ITERATION_CURSOR [REQUEST_HANDLER, STRING] + -- Fresh cursor associated with current structure + do + Result := handlers.new_cursor + end + + item (a_path: STRING): detachable REQUEST_HANDLER + do + Result := handler_by_path (a_path) + end + +feature {NONE} -- Implementation + + handlers: HASH_TABLE [REQUEST_HANDLER, STRING] + -- Handlers + + context_path (a_path: STRING): STRING + -- Prepared path from context which match requirement + -- i.e: not empty, starting with '/' + local + p: INTEGER + do + Result := a_path + if Result.is_empty then + Result := "/" + else + if Result[1] /= '/' then + Result := "/" + Result + end + p := Result.index_of ('.', 1) + if p > 0 then + Result := Result.substring (1, p - 1) + end + end + ensure + result_not_empty: not Result.is_empty + end + +;note + copyright: "2011-2011, 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/request/router/src/router/request_uri_template_router.e b/library/server/request/router/src/router/request_uri_template_router.e new file mode 100644 index 00000000..d5a43d33 --- /dev/null +++ b/library/server/request/router/src/router/request_uri_template_router.e @@ -0,0 +1,138 @@ +note + description: "Summary description for {REQUEST_URI_TEMPLATE_ROUTER}." + author: "" + date: "$Date$" + revision: "$Revision$" + +class + REQUEST_URI_TEMPLATE_ROUTER + +inherit + REQUEST_ROUTER + + ITERABLE [REQUEST_HANDLER] + redefine + new_cursor + end + +create + make + +feature -- Initialization + + make (n: INTEGER) + do + create handlers.make (n) + create templates.make (n) + handlers.compare_objects + end + +feature -- Registration + + map_with_uri_template (uri: URI_TEMPLATE; h: REQUEST_HANDLER) + do + handlers.force (h, uri.template) + templates.force (uri, uri.template) + end + + map (tpl: STRING; h: REQUEST_HANDLER) + local + uri: URI_TEMPLATE + do + create uri.make (tpl) + map_with_uri_template (uri, h) + end + +feature {NONE} -- Access: Implementation + + handler (req: EWSGI_REQUEST): detachable TUPLE [handler: REQUEST_HANDLER; context: REQUEST_HANDLER_CONTEXT] + local + ctx: detachable REQUEST_URI_TEMPLATE_HANDLER_CONTEXT + l_handlers: like handlers + t: STRING + p: STRING + do + p := req.environment.request_uri + from + l_handlers := handlers + l_handlers.start + until + l_handlers.after or Result /= Void + loop + t := l_handlers.key_for_iteration + 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_handlers.item_for_iteration, ctx] + end + l_handlers.forth + end + end + +feature -- Context factory + + handler_context (p: detachable STRING; req: EWSGI_REQUEST; tpl: URI_TEMPLATE; tpl_res: URI_TEMPLATE_MATCH_RESULT): REQUEST_URI_TEMPLATE_HANDLER_CONTEXT + do + if p /= Void then + create Result.make (req, tpl, tpl_res, p) + else + create Result.make (req, tpl, tpl_res, req.environment.path_info) + end + end + +feature -- Access + + new_cursor: HASH_TABLE_ITERATION_CURSOR [REQUEST_HANDLER, STRING] + -- Fresh cursor associated with current structure + do + Result := handlers.new_cursor + end + + item (a_path: STRING): detachable REQUEST_HANDLER + do + Result := handlers.item (a_path) + end + +feature {NONE} -- Implementation + + handlers: HASH_TABLE [REQUEST_HANDLER, STRING] + -- Handlers indexed by the template expression + -- see `templates' + + templates: HASH_TABLE [URI_TEMPLATE, STRING] + -- URI Template indexed by the template expression + + context_path (a_path: STRING): STRING + -- Prepared path from context which match requirement + -- i.e: not empty, starting with '/' + local + p: INTEGER + do + Result := a_path + if Result.is_empty then + Result := "/" + else + if Result[1] /= '/' then + Result := "/" + Result + end + p := Result.index_of ('.', 1) + if p > 0 then + Result := Result.substring (1, p - 1) + end + end + ensure + result_not_empty: not Result.is_empty + end + +;note + copyright: "2011-2011, 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