From 244fdf1b024f349525803a6cad34a57180d84f12 Mon Sep 17 00:00:00 2001 From: Jocelyn Fiat Date: Wed, 7 Sep 2011 12:14:03 +0200 Subject: [PATCH] Added request methods criteria for the router component. Now one can decide map_agent_with_request_methods ("/foo/bar/{bar_id}", agent handle_foo_bar, <<"GET">>) (and similar for non agent way) This might be useful in pure RESTful environment. --- .../src/hello_routed_world.e | 34 ++++++++++ .../src/context/request_handler_context.e | 4 +- .../router/src/router/request_router.e | 62 ++++++++++++++++++- .../router/src/router/request_uri_router.e | 61 ++++++++++-------- .../src/router/request_uri_template_router.e | 47 +++++++------- 5 files changed, 154 insertions(+), 54 deletions(-) diff --git a/examples/hello_routed_world/src/hello_routed_world.e b/examples/hello_routed_world/src/hello_routed_world.e index 2cba0ce3..a178b03a 100644 --- a/examples/hello_routed_world/src/hello_routed_world.e +++ b/examples/hello_routed_world/src/hello_routed_world.e @@ -29,6 +29,11 @@ feature {NONE} -- Initialization create_router do + debug + create {REQUEST_URI_ROUTER} router.make (5) + create {REQUEST_URI_TEMPLATE_ROUTER} router.make (5) + end + -- create {REQUEST_URI_ROUTER} router.make (5) create {REQUEST_URI_TEMPLATE_ROUTER} router.make (5) end @@ -47,6 +52,12 @@ feature {NONE} -- Initialization create ra.make (agent handle_anonymous_hello) router.map ("/hello", ra) router.map ("/hello.{format}", ra) + + router.map_agent_with_request_methods ("/method/any", agent handle_method_any, Void) + router.map_agent_with_request_methods ("/method/guess", agent handle_method_get_or_post, <<"GET", "POST">>) + router.map_agent_with_request_methods ("/method/custom", agent handle_method_get, <<"GET">>) + router.map_agent_with_request_methods ("/method/custom", agent handle_method_post, <<"POST">>) + end feature -- Execution @@ -170,6 +181,29 @@ feature -- Execution execute_hello (req, res, ctx.parameter ("name"), ctx) end + handle_method_any (ctx: REQUEST_HANDLER_CONTEXT; req: WGI_REQUEST; res: WGI_RESPONSE_BUFFER) + do + execute_hello (req, res, req.request_method, ctx) + end + + handle_method_get (ctx: REQUEST_HANDLER_CONTEXT; req: WGI_REQUEST; res: WGI_RESPONSE_BUFFER) + do + execute_hello (req, res, "GET", ctx) + end + + + handle_method_post (ctx: REQUEST_HANDLER_CONTEXT; req: WGI_REQUEST; res: WGI_RESPONSE_BUFFER) + do + execute_hello (req, res, "POST", ctx) + end + + handle_method_get_or_post (ctx: REQUEST_HANDLER_CONTEXT; req: WGI_REQUEST; res: WGI_RESPONSE_BUFFER) + do + execute_hello (req, res, "GET or POST", ctx) + 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/context/request_handler_context.e b/library/server/request/router/src/context/request_handler_context.e index c249e281..9fa5f0bc 100644 --- a/library/server/request/router/src/context/request_handler_context.e +++ b/library/server/request/router/src/context/request_handler_context.e @@ -20,8 +20,8 @@ feature -- Access request: WGI_REQUEST -- Associated request - path: STRING - -- ??? + path: READABLE_STRING_GENERAL + -- Associated path request_content_type (content_type_supported: detachable ARRAY [STRING]): detachable READABLE_STRING_8 local diff --git a/library/server/request/router/src/router/request_router.e b/library/server/request/router/src/router/request_router.e index 6617023a..78bce332 100644 --- a/library/server/request/router/src/router/request_router.e +++ b/library/server/request/router/src/router/request_router.e @@ -7,6 +7,9 @@ note deferred class REQUEST_ROUTER +inherit + ITERABLE [TUPLE [handler: REQUEST_HANDLER; id: READABLE_STRING_8; request_methods: detachable ARRAY [READABLE_STRING_8]]] + feature -- Registration map_default (r: like default_handler) @@ -17,17 +20,28 @@ feature -- Registration default_handler := r end - map (a_id: STRING; h: REQUEST_HANDLER) + map (a_id: READABLE_STRING_8; h: REQUEST_HANDLER) -- Map handler `h' with `a_id' + do + map_with_request_methods (a_id, h, Void) + end + + map_with_request_methods (a_id: READABLE_STRING_8; h: REQUEST_HANDLER; rqst_methods: detachable ARRAY [READABLE_STRING_8]) + -- Map handler `h' with `a_id' and `rqst_methods' deferred end - map_agent (a_id: STRING; a_action: like {REQUEST_AGENT_HANDLER}.action) + map_agent (a_id: READABLE_STRING_8; a_action: like {REQUEST_AGENT_HANDLER}.action) + do + map_agent_with_request_methods (a_id, a_action, Void) + end + + map_agent_with_request_methods (a_id: READABLE_STRING_8; a_action: like {REQUEST_AGENT_HANDLER}.action; rqst_methods: detachable ARRAY [READABLE_STRING_8]) local h: REQUEST_AGENT_HANDLER do create h.make (a_action) - map (a_id, h) + map_with_request_methods (a_id, h, rqst_methods) end feature -- Execution @@ -69,6 +83,48 @@ feature {NONE} -- Access: Implementation req_path_info_unchanged: req.path_info.same_string (old req.path_info) end + is_matching_request_methods (a_request_method: READABLE_STRING_GENERAL; rqst_methods: like formatted_request_methods): BOOLEAN + -- `a_request_method' is matching `rqst_methods' contents + local + i,n: INTEGER + m: READABLE_STRING_GENERAL + do + if rqst_methods /= Void and then not rqst_methods.is_empty then + m := a_request_method + from + i := rqst_methods.lower + n := rqst_methods.upper + until + i > n or Result + loop + Result := m.same_string (rqst_methods[i]) + i := i + 1 + end + else + Result := True + end + end + + formatted_request_methods (rqst_methods: like formatted_request_methods): detachable ARRAY [READABLE_STRING_8] + -- Formatted request methods values + local + i,l,u: INTEGER + do + if rqst_methods /= Void and then not rqst_methods.is_empty then + l := rqst_methods.lower + u := rqst_methods.upper + create Result.make_filled (rqst_methods[l], l, u) + from + i := l + 1 + until + i > u + loop + Result[i] := rqst_methods[i].as_string_8.as_upper + i := i + 1 + end + end + end + feature {NONE} -- Implementation default_handler: detachable REQUEST_HANDLER 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 c12f82af..9b5fa7fe 100644 --- a/library/server/request/router/src/router/request_uri_router.e +++ b/library/server/request/router/src/router/request_uri_router.e @@ -10,11 +10,6 @@ class inherit REQUEST_ROUTER - ITERABLE [REQUEST_HANDLER] - redefine - new_cursor - end - create make @@ -28,9 +23,9 @@ feature -- Initialization feature -- Registration - map (p: STRING; h: REQUEST_HANDLER) + map_with_request_methods (p: READABLE_STRING_8; h: REQUEST_HANDLER; rqst_methods: detachable ARRAY [READABLE_STRING_8]) do - handlers.force (h, p) + handlers.force ([h, p, formatted_request_methods (rqst_methods)]) end feature {NONE} -- Access: Implementation @@ -40,9 +35,9 @@ feature {NONE} -- Access: Implementation h: detachable REQUEST_HANDLER ctx: detachable REQUEST_HANDLER_CONTEXT do - h := handler_by_path (req.path_info) + h := handler_by_path (req.path_info, req.request_method) if h = Void then - if attached smart_handler_by_path (req.path_info) as info then + if attached smart_handler_by_path (req.path_info, req.request_method) as info then h := info.handler ctx := handler_context (info.path, req) end @@ -59,30 +54,45 @@ feature {NONE} -- Access: Implementation end end - smart_handler (req: WGI_REQUEST): detachable TUPLE [path: STRING; handler: REQUEST_HANDLER] + smart_handler (req: WGI_REQUEST): detachable TUPLE [path: READABLE_STRING_8; handler: REQUEST_HANDLER] require req_valid: req /= Void and then req.path_info /= Void do - Result := smart_handler_by_path (req.path_info) + Result := smart_handler_by_path (req.path_info, req.request_method) ensure req_path_info_unchanged: req.path_info.same_string (old req.path_info) end - handler_by_path (a_path: STRING): detachable REQUEST_HANDLER + handler_by_path (a_path: READABLE_STRING_GENERAL; rqst_method: READABLE_STRING_GENERAL): detachable REQUEST_HANDLER require a_path_valid: a_path /= Void + local + l_handlers: like handlers + l_item: like handlers.item do - Result := handlers.item (context_path (a_path)) + l_handlers := handlers + from + l_handlers.start + until + l_handlers.after or Result /= Void + loop + l_item := l_handlers.item + if is_matching_request_methods (rqst_method, l_item.request_methods) and a_path.same_string (l_item.uri) then + Result := l_item.handler + end + l_handlers.forth + end +-- 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] + smart_handler_by_path (a_path: READABLE_STRING_8; rqst_method: READABLE_STRING_GENERAL): detachable TUPLE [path: READABLE_STRING_8; handler: REQUEST_HANDLER] require a_path_valid: a_path /= Void local p: INTEGER - l_context_path, l_path: STRING + l_context_path, l_path: READABLE_STRING_8 h: detachable REQUEST_HANDLER do l_context_path := context_path (a_path) @@ -92,7 +102,7 @@ feature {NONE} -- Access: Implementation p <= 1 or Result /= Void loop l_path := l_context_path.substring (1, p - 1) - h := handler_by_path (l_path) + h := handler_by_path (l_path, rqst_method) if h /= Void then Result := [l_path, h] else @@ -118,34 +128,33 @@ feature -- Context factory feature -- Access - new_cursor: HASH_TABLE_ITERATION_CURSOR [REQUEST_HANDLER, STRING] + new_cursor: ITERATION_CURSOR [TUPLE [handler: REQUEST_HANDLER; id: READABLE_STRING_8; request_methods: detachable ARRAY [READABLE_STRING_8]]] -- 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 + handlers: ARRAYED_LIST [TUPLE [handler: REQUEST_HANDLER; uri: READABLE_STRING_8; request_methods: detachable ARRAY [READABLE_STRING_8]]] + -- Handlers indexed by the template expression + -- see `templates' - context_path (a_path: STRING): STRING + context_path (a_path: READABLE_STRING_8): READABLE_STRING_8 -- Prepared path from context which match requirement -- i.e: not empty, starting with '/' local p: INTEGER + s: STRING_8 do Result := a_path if Result.is_empty then Result := "/" else if Result[1] /= '/' then - Result := "/" + Result + create s.make_from_string (Result) + s.prepend_character ('/') + Result := s end p := Result.index_of ('.', 1) if p > 0 then 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 91e56b82..3fa801d6 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 @@ -10,11 +10,6 @@ class inherit REQUEST_ROUTER - ITERABLE [REQUEST_HANDLER] - redefine - new_cursor - end - create make @@ -31,16 +26,21 @@ feature -- Registration map_with_uri_template (uri: URI_TEMPLATE; h: REQUEST_HANDLER) do - handlers.force (h, uri.template) + map_with_uri_template_and_request_methods (uri, h, Void) + end + + map_with_uri_template_and_request_methods (uri: URI_TEMPLATE; h: REQUEST_HANDLER; rqst_methods: detachable ARRAY [READABLE_STRING_8]) + do + handlers.force ([h, uri.template, formatted_request_methods (rqst_methods)]) templates.force (uri, uri.template) end - map (tpl: STRING; h: REQUEST_HANDLER) + map_with_request_methods (tpl: READABLE_STRING_8; h: REQUEST_HANDLER; rqst_methods: detachable ARRAY [READABLE_STRING_8]) local uri: URI_TEMPLATE do create uri.make (tpl) - map_with_uri_template (uri, h) + map_with_uri_template_and_request_methods (uri, h, rqst_methods) end feature {NONE} -- Access: Implementation @@ -51,20 +51,26 @@ feature {NONE} -- Access: Implementation l_handlers: like handlers t: STRING p: STRING + l_req_method: READABLE_STRING_GENERAL do p := req.request_uri from + l_req_method := req.request_method 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] + if attached l_handlers.item as l_info then + if is_matching_request_methods (l_req_method, l_info.request_methods) then + t := l_info.template + 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] + end + end end l_handlers.forth end @@ -81,26 +87,21 @@ feature -- Context factory end end -feature -- Access +feature -- Access: ITERABLE - new_cursor: HASH_TABLE_ITERATION_CURSOR [REQUEST_HANDLER, STRING] + new_cursor: ITERATION_CURSOR [TUPLE [handler: REQUEST_HANDLER; template: READABLE_STRING_8; request_methods: detachable ARRAY [READABLE_STRING_8]]] -- 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: ARRAYED_LIST [TUPLE [handler: REQUEST_HANDLER; template: READABLE_STRING_8; request_methods: detachable ARRAY [READABLE_STRING_8]]] -- Handlers indexed by the template expression -- see `templates' - templates: HASH_TABLE [URI_TEMPLATE, STRING] + templates: HASH_TABLE [URI_TEMPLATE, READABLE_STRING_8] -- URI Template indexed by the template expression context_path (a_path: STRING): STRING