Files
EWF/library/server/wsf/router/wsf_router.e
Jocelyn Fiat aa65c16957 Breaking changes:
added `a_request_methods' argument to WSF_ROUTER_SELF_DOCUMENTATION_HANDLER.mapping_documentation
 added similar argument to WSF_ROUTER_SELF_DOCUMENTATION_ROUTER_MAPPING.documentation
Renamed WSF_ROUTER_METHODS as WSF_REQUEST_METHODS
Enhanced WSF_REQUEST_METHODS with new has_... function
Added WSF_ROUTER_VISITOR and WSF_ROUTER_ITERATOR that may be useful to iterate inside the router.
   we may improve the implementation of the router using those visitors in the future.
Improved the WSF_DEFAULT_RESPONSE to embedded suggested items (typically based on pseudo self documented router)
2012-12-19 16:42:26 +01:00

425 lines
12 KiB
Plaintext

note
description: "[
URL dispatching of request
Map a route to an handler according to the request method and path
]"
date: "$Date$"
revision: "$Revision$"
class
WSF_ROUTER
inherit
ITERABLE [WSF_ROUTER_ITEM]
WSF_REQUEST_EXPORTER
create
make,
make_with_base_url
feature {NONE} -- Initialization
make (n: INTEGER)
-- Create the router with a capacity of `n' mappings
do
create mappings.make (n)
initialize (n)
end
make_with_base_url (n: INTEGER; a_base_url: like base_url)
-- Make router allocated for at least `n' maps,
-- and use `a_base_url' as `base_url'
--| This avoids prefixing all the resource string.
do
make (n)
set_base_url (a_base_url)
end
initialize (n: INTEGER)
-- Initialize router
do
create mappings.make (n)
create pre_execution_actions
end
mappings: ARRAYED_LIST [WSF_ROUTER_ITEM]
-- Existing mappings
feature -- Mapping
map (a_mapping: WSF_ROUTER_MAPPING)
-- Map `a_mapping'
do
map_with_request_methods (a_mapping, Void)
end
map_with_request_methods (a_mapping: WSF_ROUTER_MAPPING; rqst_methods: detachable WSF_REQUEST_METHODS)
-- Map `a_mapping' for request methods `rqst_methods'
do
debug ("router")
-- Display conflict in mapping
if has_item_associated_with_resource (a_mapping.associated_resource, rqst_methods) then
io.error.put_string ("Mapping: " + a_mapping.debug_output + ": conflict with existing mapping")
if attached item_associated_with_resource (a_mapping.associated_resource, rqst_methods) as l_conflicted then
io.error.put_string (": " + l_conflicted.debug_output)
end
io.error.put_string ("%N")
end
end
mappings.extend (create {WSF_ROUTER_ITEM}.make_with_request_methods (a_mapping, rqst_methods))
a_mapping.handler.on_mapped (a_mapping, rqst_methods)
end
feature -- Mapping handler
handle (a_resource: READABLE_STRING_8; f: WSF_ROUTER_MAPPING_FACTORY)
-- Map the mapping created by factory `f' for resource `a_resource'
do
handle_with_request_methods (a_resource, f, Void)
end
handle_with_request_methods (a_resource: READABLE_STRING_8; f: WSF_ROUTER_MAPPING_FACTORY; rqst_methods: detachable WSF_REQUEST_METHODS)
-- Map the mapping created by factory `f' for resource `a_resource'
-- and only for request methods `rqst_methods'
do
map_with_request_methods (f.new_mapping (a_resource), rqst_methods)
end
feature -- Access
is_dispatched: BOOLEAN
-- `dispatch' set `is_dispatched' to True
-- if mapping was found, and associated handler executed
dispatch (req: WSF_REQUEST; res: WSF_RESPONSE)
-- Dispatch request `req' among the `mappings'
-- Set `is_dispatched' if the request were dispatched
do
if attached dispatch_and_return_handler (req, res) then
check is_dispatched: is_dispatched end
end
end
dispatch_and_return_handler (req: WSF_REQUEST; res: WSF_RESPONSE): detachable WSF_HANDLER
-- Dispatch request `req' among the `mappings'
-- And return the associated handler if mapping found and handler executed.
local
l_req_method: READABLE_STRING_8
head_res: WSF_HEAD_RESPONSE_WRAPPER
do
l_req_method := request_method (req)
is_dispatched := False
Result := dispatch_and_return_handler_for_request_method (req, res, l_req_method)
if Result = Void and l_req_method = {HTTP_REQUEST_METHODS}.method_head then
check is_not_dispatched: not is_dispatched end
create head_res.make_from_response (res)
req.set_request_method ({HTTP_REQUEST_METHODS}.method_GET)
Result := dispatch_and_return_handler_for_request_method (req, head_res, {HTTP_REQUEST_METHODS}.method_GET)
end
end
feature {NONE} -- Dispatch implementation
dispatch_and_return_handler_for_request_method (req: WSF_REQUEST; res: WSF_RESPONSE; a_request_method: READABLE_STRING_8): detachable WSF_HANDLER
-- Dispatch request `req' among the `mappings'
-- And return the associated handler if mapping found and handler executed.
local
m: WSF_ROUTER_MAPPING
do
is_dispatched := False
across
mappings as c
until
Result /= Void
loop
if attached c.item as l_info then
if is_matching_request_methods (a_request_method, l_info.request_methods) then
m := l_info.mapping
if attached m.routed_handler (req, res, Current) as r then
is_dispatched := True
Result := r
end
end
end
end
end
feature -- Status report
has_item_associated_with_resource (a_resource: READABLE_STRING_8; rqst_methods: detachable WSF_REQUEST_METHODS): BOOLEAN
local
m: WSF_ROUTER_MAPPING
ok: BOOLEAN
do
across
mappings as c
loop
m := c.item.mapping
ok := True
if rqst_methods /= Void then
if attached c.item.request_methods as l_item_rqst_methods then
ok := across rqst_methods as mtd some is_matching_request_methods (mtd.item, l_item_rqst_methods) end
else
ok := True
end
end
if ok then
if attached {WSF_ROUTING_HANDLER} m.handler as l_routing then
Result := l_routing.router.has_item_associated_with_resource (a_resource, rqst_methods)
elseif m.associated_resource.same_string (a_resource) then
Result := True
end
end
end
end
item_associated_with_resource (a_resource: READABLE_STRING_8; rqst_methods: detachable WSF_REQUEST_METHODS): detachable WSF_ROUTER_ITEM
local
m: WSF_ROUTER_MAPPING
ok: BOOLEAN
do
across
mappings as c
until
Result /= Void
loop
m := c.item.mapping
ok := True
if rqst_methods /= Void then
if attached c.item.request_methods as l_item_rqst_methods then
ok := across rqst_methods as mtd some is_matching_request_methods (mtd.item, l_item_rqst_methods) end
else
ok := True
end
end
if ok then
if attached {WSF_ROUTING_HANDLER} m.handler as l_routing then
Result := l_routing.router.item_associated_with_resource (a_resource, rqst_methods)
elseif m.associated_resource.same_string (a_resource) then
Result := c.item
end
end
end
end
allowed_methods_for_request (req: WSF_REQUEST): WSF_REQUEST_METHODS
-- Allowed methods for `req'
local
m: WSF_ROUTER_MAPPING
l_rqsmethods: detachable WSF_REQUEST_METHODS
do
create Result
across
mappings as c
loop
m := c.item.mapping
if attached {WSF_ROUTING_HANDLER} m.handler as l_routing then
l_rqsmethods := l_routing.router.allowed_methods_for_request (req)
elseif m.is_mapping (req, Current) then
l_rqsmethods := c.item.request_methods
else
l_rqsmethods := Void
end
if l_rqsmethods /= Void then
Result := Result + l_rqsmethods
end
end
end
feature -- Hook
execute_before (a_mapping: WSF_ROUTER_MAPPING)
-- Execute before the handler associated with the matching mapping is executed
do
pre_execution_actions.call ([a_mapping])
end
execute_after (a_mapping: WSF_ROUTER_MAPPING)
-- Execute after the handler associated with the matching mapping is executed
--| Could be redefined to add specific hook.
do
end
pre_execution_actions: ACTION_SEQUENCE [TUPLE [WSF_ROUTER_MAPPING]]
-- Action triggered before a route is execute
--| Could be used for tracing, logging
feature -- Base url
count: INTEGER
-- Number of mappings registered
do
Result := mappings.count
end
base_url: detachable READABLE_STRING_8
-- Common start of any route url
feature -- Element change
set_base_url (a_base_url: like base_url)
-- Set `base_url' to `a_base_url'
-- make sure no map is already added (i.e: count = 0)
require
a_valid_base_url: (a_base_url /= Void and then a_base_url.is_empty) implies (a_base_url.starts_with ("/") and not a_base_url.ends_with ("/"))
no_handler_set: count = 0
do
if a_base_url = Void or else a_base_url.is_empty then
base_url := Void
else
base_url := a_base_url
end
end
feature -- Traversing
new_cursor: ITERATION_CURSOR [WSF_ROUTER_ITEM]
-- Fresh cursor associated with current structure
do
Result := mappings.new_cursor
end
feature -- Request methods helper
methods_head: WSF_REQUEST_METHODS
once ("THREAD")
create Result
Result.enable_head
Result.lock
end
methods_options: WSF_REQUEST_METHODS
once ("THREAD")
create Result
Result.enable_options
Result.lock
end
methods_get: WSF_REQUEST_METHODS
once ("THREAD")
create Result
Result.enable_get
Result.lock
end
methods_post: WSF_REQUEST_METHODS
once ("THREAD")
create Result
Result.enable_post
Result.lock
end
methods_put: WSF_REQUEST_METHODS
once ("THREAD")
create Result
Result.enable_put
Result.lock
end
methods_delete: WSF_REQUEST_METHODS
once ("THREAD")
create Result
Result.enable_delete
Result.lock
end
methods_head_get_post: WSF_REQUEST_METHODS
once ("THREAD")
create Result.make (3)
Result.enable_head
Result.enable_get
Result.enable_post
Result.lock
end
methods_get_put_delete: WSF_REQUEST_METHODS
once ("THREAD")
create Result.make (3)
Result.enable_get
Result.enable_put
Result.enable_delete
Result.lock
end
methods_head_get: WSF_REQUEST_METHODS
once ("THREAD")
create Result.make (2)
Result.enable_head
Result.enable_get
Result.lock
end
methods_get_post: WSF_REQUEST_METHODS
once ("THREAD")
create Result.make (2)
Result.enable_get
Result.enable_post
Result.lock
end
methods_put_post: WSF_REQUEST_METHODS
once ("THREAD")
create Result.make (2)
Result.enable_put
Result.enable_post
Result.lock
end
feature {NONE} -- Access: Implementation
request_method (req: WSF_REQUEST): READABLE_STRING_8
-- Request method from `req' to be used in the router implementation.
local
m: READABLE_STRING_8
do
m := req.request_method
if m.is_case_insensitive_equal ({HTTP_REQUEST_METHODS}.method_get) then
Result := {HTTP_REQUEST_METHODS}.method_get
elseif m.is_case_insensitive_equal ({HTTP_REQUEST_METHODS}.method_post) then
Result := {HTTP_REQUEST_METHODS}.method_post
elseif m.is_case_insensitive_equal ({HTTP_REQUEST_METHODS}.method_head) then
Result := {HTTP_REQUEST_METHODS}.method_head
elseif m.is_case_insensitive_equal ({HTTP_REQUEST_METHODS}.method_trace) then
Result := {HTTP_REQUEST_METHODS}.method_trace
elseif m.is_case_insensitive_equal ({HTTP_REQUEST_METHODS}.method_options) then
Result := {HTTP_REQUEST_METHODS}.method_options
elseif m.is_case_insensitive_equal ({HTTP_REQUEST_METHODS}.method_put) then
Result := {HTTP_REQUEST_METHODS}.method_put
elseif m.is_case_insensitive_equal ({HTTP_REQUEST_METHODS}.method_delete) then
Result := {HTTP_REQUEST_METHODS}.method_delete
elseif m.is_case_insensitive_equal ({HTTP_REQUEST_METHODS}.method_connect) then
Result := {HTTP_REQUEST_METHODS}.method_connect
elseif m.is_case_insensitive_equal ({HTTP_REQUEST_METHODS}.method_patch) then
Result := {HTTP_REQUEST_METHODS}.method_patch
else
Result := m.as_upper
end
end
is_matching_request_methods (a_request_method: READABLE_STRING_8; a_rqst_methods: detachable WSF_REQUEST_METHODS): BOOLEAN
-- `a_request_method' is matching `a_rqst_methods' contents
do
if a_rqst_methods /= Void and then not a_rqst_methods.is_empty then
Result := a_rqst_methods.has (a_request_method)
else
Result := True
end
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)"
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