Merge pull request #46 from colin-adams/skeleton_router
Skeleton router note : - wsf_method_not_allowed_response.e needs deeper review - tidy some helper classes
This commit is contained in:
@@ -4,22 +4,10 @@ note
|
|||||||
date: "$Date$"
|
date: "$Date$"
|
||||||
revision: "$Revision$"
|
revision: "$Revision$"
|
||||||
|
|
||||||
class
|
class ORDER_HANDLER
|
||||||
ORDER_HANDLER
|
|
||||||
inherit
|
inherit
|
||||||
WSF_URI_HANDLER
|
|
||||||
rename
|
|
||||||
execute as uri_execute,
|
|
||||||
new_mapping as new_uri_mapping
|
|
||||||
end
|
|
||||||
|
|
||||||
WSF_URI_TEMPLATE_HANDLER
|
WSF_URI_TEMPLATE_HANDLER
|
||||||
rename
|
|
||||||
execute as uri_template_execute,
|
|
||||||
new_mapping as new_uri_template_mapping
|
|
||||||
select
|
|
||||||
new_uri_template_mapping
|
|
||||||
end
|
|
||||||
|
|
||||||
WSF_RESOURCE_HANDLER_HELPER
|
WSF_RESOURCE_HANDLER_HELPER
|
||||||
redefine
|
redefine
|
||||||
@@ -28,22 +16,20 @@ inherit
|
|||||||
do_put,
|
do_put,
|
||||||
do_delete
|
do_delete
|
||||||
end
|
end
|
||||||
|
|
||||||
SHARED_DATABASE_API
|
SHARED_DATABASE_API
|
||||||
|
|
||||||
SHARED_EJSON
|
SHARED_EJSON
|
||||||
|
|
||||||
REFACTORING_HELPER
|
REFACTORING_HELPER
|
||||||
|
|
||||||
SHARED_ORDER_VALIDATION
|
SHARED_ORDER_VALIDATION
|
||||||
|
|
||||||
WSF_SELF_DOCUMENTED_HANDLER
|
WSF_SELF_DOCUMENTED_HANDLER
|
||||||
|
|
||||||
feature -- execute
|
feature -- Execute
|
||||||
|
|
||||||
uri_execute (req: WSF_REQUEST; res: WSF_RESPONSE)
|
execute (req: WSF_REQUEST; res: WSF_RESPONSE)
|
||||||
-- Execute request handler
|
|
||||||
do
|
|
||||||
execute_methods (req, res)
|
|
||||||
end
|
|
||||||
|
|
||||||
uri_template_execute (req: WSF_REQUEST; res: WSF_RESPONSE)
|
|
||||||
-- Execute request handler
|
-- Execute request handler
|
||||||
do
|
do
|
||||||
execute_methods (req, res)
|
execute_methods (req, res)
|
||||||
|
|||||||
@@ -3,21 +3,23 @@ note
|
|||||||
date : "$Date$"
|
date : "$Date$"
|
||||||
revision : "$Revision$"
|
revision : "$Revision$"
|
||||||
|
|
||||||
class
|
class RESTBUCKS_SERVER
|
||||||
RESTBUCKS_SERVER
|
|
||||||
|
|
||||||
inherit
|
inherit
|
||||||
ANY
|
|
||||||
|
|
||||||
WSF_URI_TEMPLATE_ROUTED_SERVICE
|
WSF_ROUTED_SKELETON_SERVICE
|
||||||
redefine
|
undefine
|
||||||
execute_default
|
requires_proxy
|
||||||
end
|
end
|
||||||
|
|
||||||
|
WSF_URI_TEMPLATE_HELPER_FOR_ROUTED_SERVICE
|
||||||
|
|
||||||
WSF_HANDLER_HELPER
|
WSF_HANDLER_HELPER
|
||||||
|
|
||||||
WSF_DEFAULT_SERVICE
|
WSF_DEFAULT_SERVICE
|
||||||
|
|
||||||
|
WSF_NO_PROXY_POLICY
|
||||||
|
|
||||||
create
|
create
|
||||||
make
|
make
|
||||||
|
|
||||||
@@ -42,18 +44,9 @@ feature {NONE} -- Initialization
|
|||||||
router.handle_with_request_methods ("/api/doc", doc, router.methods_GET)
|
router.handle_with_request_methods ("/api/doc", doc, router.methods_GET)
|
||||||
end
|
end
|
||||||
|
|
||||||
feature -- Execution
|
|
||||||
|
|
||||||
execute_default (req: WSF_REQUEST; res: WSF_RESPONSE)
|
|
||||||
-- I'm using this method to handle the method not allowed response
|
|
||||||
-- in the case that the given uri does not have a corresponding http method
|
|
||||||
-- to handle it.
|
|
||||||
do
|
|
||||||
Precursor (req, res)
|
|
||||||
end
|
|
||||||
|
|
||||||
note
|
note
|
||||||
copyright: "2011-2012, Javier Velilla and others"
|
copyright: "2011-2013, Javier Velilla and others"
|
||||||
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
|
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
|
||||||
source: "[
|
source: "[
|
||||||
Eiffel Software
|
Eiffel Software
|
||||||
|
|||||||
@@ -10,11 +10,14 @@ class
|
|||||||
inherit
|
inherit
|
||||||
ANY
|
ANY
|
||||||
|
|
||||||
WSF_ROUTED_SERVICE
|
WSF_ROUTED_SKELETON_SERVICE
|
||||||
|
undefine
|
||||||
|
requires_proxy
|
||||||
|
end
|
||||||
|
|
||||||
WSF_URI_TEMPLATE_ROUTED_SERVICE
|
WSF_URI_TEMPLATE_HELPER_FOR_ROUTED_SERVICE
|
||||||
|
|
||||||
WSF_URI_ROUTED_SERVICE
|
WSF_NO_PROXY_POLICY
|
||||||
|
|
||||||
WSF_DEFAULT_SERVICE
|
WSF_DEFAULT_SERVICE
|
||||||
|
|
||||||
|
|||||||
@@ -871,6 +871,8 @@ feature {NONE} -- Implementation
|
|||||||
h.append_character ('%N')
|
h.append_character ('%N')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
feature -- Access
|
||||||
|
|
||||||
date_to_rfc1123_http_date_format (dt: DATE_TIME): STRING_8
|
date_to_rfc1123_http_date_format (dt: DATE_TIME): STRING_8
|
||||||
-- String representation of `dt' using the RFC 1123
|
-- String representation of `dt' using the RFC 1123
|
||||||
local
|
local
|
||||||
|
|||||||
@@ -7,10 +7,18 @@ class
|
|||||||
APPLICATION
|
APPLICATION
|
||||||
|
|
||||||
inherit
|
inherit
|
||||||
WSF_URI_TEMPLATE_ROUTED_SERVICE
|
|
||||||
|
WSF_ROUTED_SKELETON_SERVICE
|
||||||
|
undefine
|
||||||
|
requires_proxy
|
||||||
|
end
|
||||||
|
|
||||||
|
WSF_URI_TEMPLATE_HELPER_FOR_ROUTED_SERVICE
|
||||||
|
|
||||||
WSF_SERVICE
|
WSF_SERVICE
|
||||||
|
|
||||||
|
WSF_NO_PROXY_POLICY
|
||||||
|
|
||||||
create
|
create
|
||||||
make_and_launch
|
make_and_launch
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
note
|
||||||
|
|
||||||
|
description: "Facilities inheritance to add URI template-base routing to a routed service"
|
||||||
|
|
||||||
|
date: "$Date$"
|
||||||
|
revision: "$Revision$"
|
||||||
|
|
||||||
|
deferred class WSF_URI_TEMPLATE_HELPER_FOR_ROUTED_SERVICE
|
||||||
|
|
||||||
|
feature -- Access
|
||||||
|
|
||||||
|
router: WSF_ROUTER
|
||||||
|
-- Router used to dispatch the request according to the WSF_REQUEST object
|
||||||
|
-- and associated request methods;
|
||||||
|
-- This should not be implemented by descendants. Instead, you gain an effective
|
||||||
|
-- version by also inheriting from WSF_ROUTED_SERVICE, or one of it's descendants.
|
||||||
|
deferred
|
||||||
|
ensure
|
||||||
|
router_not_void: Result /= Void
|
||||||
|
end
|
||||||
|
|
||||||
|
feature -- Mapping helper: uri
|
||||||
|
|
||||||
|
map_uri_template (a_tpl: STRING; h: WSF_URI_TEMPLATE_HANDLER)
|
||||||
|
-- Map `h' as handler for `a_tpl'
|
||||||
|
require
|
||||||
|
a_tpl_attached: a_tpl /= Void
|
||||||
|
h_attached: h /= Void
|
||||||
|
do
|
||||||
|
map_uri_template_with_request_methods (a_tpl, h, Void)
|
||||||
|
end
|
||||||
|
|
||||||
|
map_uri_template_with_request_methods (a_tpl: READABLE_STRING_8; h: WSF_URI_TEMPLATE_HANDLER; rqst_methods: detachable WSF_REQUEST_METHODS)
|
||||||
|
-- Map `h' as handler for `a_tpl' for request methods `rqst_methods'.
|
||||||
|
require
|
||||||
|
a_tpl_attached: a_tpl /= Void
|
||||||
|
h_attached: h /= Void
|
||||||
|
do
|
||||||
|
router.map_with_request_methods (create {WSF_URI_TEMPLATE_MAPPING}.make (a_tpl, h), rqst_methods)
|
||||||
|
end
|
||||||
|
|
||||||
|
feature -- Mapping helper: uri agent
|
||||||
|
|
||||||
|
map_uri_template_agent (a_tpl: READABLE_STRING_8; proc: PROCEDURE [ANY, TUPLE [req: WSF_REQUEST; res: WSF_RESPONSE]])
|
||||||
|
-- Map `proc' as handler for `a_tpl'
|
||||||
|
require
|
||||||
|
a_tpl_attached: a_tpl /= Void
|
||||||
|
proc_attached: proc /= Void
|
||||||
|
do
|
||||||
|
map_uri_template_agent_with_request_methods (a_tpl, proc, Void)
|
||||||
|
end
|
||||||
|
|
||||||
|
map_uri_template_agent_with_request_methods (a_tpl: READABLE_STRING_8; proc: PROCEDURE [ANY, TUPLE [req: WSF_REQUEST; res: WSF_RESPONSE]]; rqst_methods: detachable WSF_REQUEST_METHODS)
|
||||||
|
-- Map `proc' as handler for `a_tpl' for request methods `rqst_methods'.
|
||||||
|
require
|
||||||
|
a_tpl_attached: a_tpl /= Void
|
||||||
|
proc_attached: proc /= Void
|
||||||
|
do
|
||||||
|
map_uri_template_with_request_methods (a_tpl, create {WSF_URI_TEMPLATE_AGENT_HANDLER}.make (proc), rqst_methods)
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
@@ -7,6 +7,8 @@ note
|
|||||||
deferred class
|
deferred class
|
||||||
WSF_URI_TEMPLATE_ROUTER_HELPER
|
WSF_URI_TEMPLATE_ROUTER_HELPER
|
||||||
|
|
||||||
|
obsolete "Use class WSF_URI_TEMPLATE_HELPER_FOR_ROUTED_SERVICE in conjunction with WSF_ROUTED_SKELETON_SERVICE"
|
||||||
|
|
||||||
feature -- Access
|
feature -- Access
|
||||||
|
|
||||||
router: WSF_ROUTER
|
router: WSF_ROUTER
|
||||||
|
|||||||
36
library/server/wsf/router/wsf_no_proxy_policy.e
Normal file
36
library/server/wsf/router/wsf_no_proxy_policy.e
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
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): URI
|
||||||
|
-- Absolute URI of proxy server which `req' must use
|
||||||
|
do
|
||||||
|
create Result.make_from_string ("")
|
||||||
|
-- doesn't meet the postcondition, but the precondition is never true.
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
88
library/server/wsf/router/wsf_proxy_use_policy.e
Normal file
88
library/server/wsf/router/wsf_proxy_use_policy.e
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
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): URI
|
||||||
|
-- Absolute URI of proxy server which `req' must use
|
||||||
|
require
|
||||||
|
req_attached: req /= Void
|
||||||
|
proxy_required: requires_proxy (req)
|
||||||
|
deferred
|
||||||
|
ensure
|
||||||
|
proxy_server_attached: Result /= Void
|
||||||
|
valid_uri: Result.is_valid
|
||||||
|
absolute_uri: not Result.scheme.is_empty
|
||||||
|
http_or_https: Result.scheme.is_case_insensitive_equal ("http") or
|
||||||
|
Result.scheme.is_case_insensitive_equal ("https")
|
||||||
|
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
|
||||||
|
|
||||||
|
note
|
||||||
|
copyright: "2011-2013, 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
|
||||||
@@ -48,6 +48,9 @@ feature -- Execution
|
|||||||
if not sess.dispatched then
|
if not sess.dispatched then
|
||||||
execute_default (req, res)
|
execute_default (req, res)
|
||||||
end
|
end
|
||||||
|
ensure
|
||||||
|
response_status_is_set: res.status_is_set
|
||||||
|
header_sent: res.header_committed
|
||||||
end
|
end
|
||||||
|
|
||||||
execute_default (req: WSF_REQUEST; res: WSF_RESPONSE)
|
execute_default (req: WSF_REQUEST; res: WSF_RESPONSE)
|
||||||
|
|||||||
283
library/server/wsf/router/wsf_routed_skeleton_service.e
Normal file
283
library/server/wsf/router/wsf_routed_skeleton_service.e
Normal file
@@ -0,0 +1,283 @@
|
|||||||
|
note
|
||||||
|
description: "Summary description for {WSF_ROUTED_SERVICE}."
|
||||||
|
author: ""
|
||||||
|
date: "$Date$"
|
||||||
|
revision: "$Revision$"
|
||||||
|
|
||||||
|
deferred class WSF_ROUTED_SKELETON_SERVICE
|
||||||
|
|
||||||
|
inherit
|
||||||
|
|
||||||
|
WSF_ROUTED_SERVICE
|
||||||
|
redefine
|
||||||
|
execute
|
||||||
|
end
|
||||||
|
|
||||||
|
WSF_SYSTEM_OPTIONS_ACCESS_POLICY
|
||||||
|
|
||||||
|
WSF_PROXY_USE_POLICY
|
||||||
|
|
||||||
|
feature -- Execution
|
||||||
|
|
||||||
|
execute (req: WSF_REQUEST; res: WSF_RESPONSE)
|
||||||
|
-- If the service is available, and request URI is not too long, dispatch the request
|
||||||
|
-- and if handler is not found, execute the default procedure `execute_default'.
|
||||||
|
local
|
||||||
|
l_sess: WSF_ROUTER_SESSION
|
||||||
|
do
|
||||||
|
--| When we reach here, the request has already passed check for 400 (Bad request),
|
||||||
|
--| 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.is_request_method ({HTTP_REQUEST_METHODS}.method_options) and then
|
||||||
|
req.request_uri.same_string ("*") then
|
||||||
|
handle_server_options (req, res)
|
||||||
|
else
|
||||||
|
create l_sess
|
||||||
|
router.dispatch (req, res, l_sess)
|
||||||
|
if not l_sess.dispatched then
|
||||||
|
execute_default (req, res)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
feature -- Measurement
|
||||||
|
|
||||||
|
maximum_uri_length: NATURAL
|
||||||
|
-- Maximum length in characters (or zero for no limit) permitted
|
||||||
|
-- for {WSF_REQUEST}.request_uri
|
||||||
|
|
||||||
|
feature -- Status report
|
||||||
|
|
||||||
|
unavailable: BOOLEAN
|
||||||
|
-- Is service currently unavailable?
|
||||||
|
|
||||||
|
unavailablity_message: detachable READABLE_STRING_8
|
||||||
|
-- Message to be included as text of response body for {HTTP_STATUS_CODE}.service_unavailable
|
||||||
|
|
||||||
|
unavailability_duration: NATURAL
|
||||||
|
-- Delta seconds for service unavailability (0 if not known)
|
||||||
|
|
||||||
|
unavailable_until: detachable DATE_TIME
|
||||||
|
-- Time at which service becomes available again (if known)
|
||||||
|
|
||||||
|
feature -- Status setting
|
||||||
|
|
||||||
|
set_available
|
||||||
|
-- Set `unavailable' to `False'.
|
||||||
|
do
|
||||||
|
unavailable := False
|
||||||
|
unavailablity_message := Void
|
||||||
|
unavailable_until := Void
|
||||||
|
ensure
|
||||||
|
available: unavailable = False
|
||||||
|
unavailablity_message_detached: unavailablity_message = Void
|
||||||
|
unavailable_until_detached: unavailable_until = Void
|
||||||
|
end
|
||||||
|
|
||||||
|
set_unavailable (a_message: READABLE_STRING_8; a_duration: NATURAL; a_until: detachable DATE_TIME)
|
||||||
|
-- Set `unavailable' to `True'.
|
||||||
|
require
|
||||||
|
a_message_attached: a_message /= Void
|
||||||
|
a_duration_xor_a_until: a_duration > 0 implies a_until = Void
|
||||||
|
do
|
||||||
|
unavailable := True
|
||||||
|
unavailablity_message := a_message
|
||||||
|
unavailability_duration := a_duration
|
||||||
|
ensure
|
||||||
|
unavailable: unavailable = True
|
||||||
|
unavailablity_message_aliased: unavailablity_message = a_message
|
||||||
|
unavailability_duration_set: unavailability_duration = a_duration
|
||||||
|
unavailable_until_aliased: unavailable_until = a_until
|
||||||
|
end
|
||||||
|
|
||||||
|
set_maximum_uri_length (a_len: NATURAL)
|
||||||
|
-- Set `maximum_uri_length' to `a_len'.
|
||||||
|
-- Can pass zero to mean no restrictions.
|
||||||
|
do
|
||||||
|
maximum_uri_length := a_len
|
||||||
|
ensure
|
||||||
|
maximum_uri_length_set: maximum_uri_length = a_len
|
||||||
|
end
|
||||||
|
|
||||||
|
feature {NONE} -- Implementation
|
||||||
|
|
||||||
|
handle_unavailable (res: WSF_RESPONSE)
|
||||||
|
-- Write "Service unavailable" response to `res'.
|
||||||
|
require
|
||||||
|
unavailable: unavailable
|
||||||
|
res_attached: res /= Void
|
||||||
|
local
|
||||||
|
h: HTTP_HEADER
|
||||||
|
do
|
||||||
|
create h.make
|
||||||
|
h.put_content_type_text_plain
|
||||||
|
check attached unavailablity_message as m then
|
||||||
|
-- invariant `unavailability_message_attached' plus precondition `unavailable'
|
||||||
|
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 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
|
||||||
|
res.put_header_text (h.string)
|
||||||
|
res.put_string (m)
|
||||||
|
end
|
||||||
|
ensure
|
||||||
|
response_status_is_set: res.status_is_set
|
||||||
|
status_is_service_unavailable: res.status_code = {HTTP_STATUS_CODE}.service_unavailable
|
||||||
|
body_sent: res.message_committed and then res.transfered_content_length > 0
|
||||||
|
body_content_was_unavailablity_message: True -- doesn't seem to be any way to check
|
||||||
|
end
|
||||||
|
|
||||||
|
handle_request_uri_too_long (res: WSF_RESPONSE)
|
||||||
|
-- Write "Request URI too long" response into `res'.
|
||||||
|
require
|
||||||
|
res_attached: res /= Void
|
||||||
|
local
|
||||||
|
h: HTTP_HEADER
|
||||||
|
m: READABLE_STRING_8
|
||||||
|
do
|
||||||
|
create h.make
|
||||||
|
h.put_content_type_text_plain
|
||||||
|
h.put_current_date
|
||||||
|
m := "Maximum permitted length for request URI is " + maximum_uri_length.out + " characters"
|
||||||
|
h.put_content_length (m.count)
|
||||||
|
res.set_status_code ({HTTP_STATUS_CODE}.request_uri_too_long)
|
||||||
|
res.put_header_text (h.string)
|
||||||
|
res.put_string (m)
|
||||||
|
ensure
|
||||||
|
response_status_is_set: res.status_is_set
|
||||||
|
status_is_request_uri_too_long: res.status_code = {HTTP_STATUS_CODE}.request_uri_too_long
|
||||||
|
body_sent: res.message_committed and then res.transfered_content_length > 0
|
||||||
|
end
|
||||||
|
|
||||||
|
frozen handle_server_options (req: WSF_REQUEST; res: WSF_RESPONSE)
|
||||||
|
-- Write response to OPTIONS * into `res'.
|
||||||
|
require
|
||||||
|
req_attached: req /= Void
|
||||||
|
res_attached: res /= Void
|
||||||
|
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.
|
||||||
|
--| (N.B. authentication requires an absoluteURI (RFC3617 page 3), and so cannot be used for OPTIONS *.
|
||||||
|
--| Otherwise construct an Allow response automatically from the router.
|
||||||
|
if is_system_options_forbidden (req) then
|
||||||
|
handle_system_options_forbidden (req, res)
|
||||||
|
else
|
||||||
|
handle_system_options (req, res)
|
||||||
|
end
|
||||||
|
ensure
|
||||||
|
response_status_is_set: res.status_is_set
|
||||||
|
valid_response_code: res.status_code = {HTTP_STATUS_CODE}.forbidden or
|
||||||
|
res.status_code = {HTTP_STATUS_CODE}.not_found or res.status_code = {HTTP_STATUS_CODE}.ok
|
||||||
|
header_sent: res.header_committed and res.message_committed
|
||||||
|
end
|
||||||
|
|
||||||
|
frozen handle_system_options_forbidden (req: WSF_REQUEST; res: WSF_RESPONSE)
|
||||||
|
-- Write a 403 Forbidden or a 404 Not found response into `res'.
|
||||||
|
require
|
||||||
|
req_attached: req /= Void
|
||||||
|
res_attached: res /= Void
|
||||||
|
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
|
||||||
|
h: HTTP_HEADER
|
||||||
|
do
|
||||||
|
m := system_options_forbidden_text (req)
|
||||||
|
if attached {READABLE_STRING_8} m as l_msg then
|
||||||
|
create h.make
|
||||||
|
h.put_content_type_text_plain
|
||||||
|
h.put_current_date
|
||||||
|
h.put_content_length (l_msg.count)
|
||||||
|
res.set_status_code ({HTTP_STATUS_CODE}.forbidden)
|
||||||
|
res.put_header_text (h.string)
|
||||||
|
res.put_string (l_msg)
|
||||||
|
else
|
||||||
|
create h.make
|
||||||
|
h.put_content_type_text_plain
|
||||||
|
h.put_current_date
|
||||||
|
h.put_content_length (0)
|
||||||
|
res.set_status_code ({HTTP_STATUS_CODE}.not_found)
|
||||||
|
res.put_header_text (h.string)
|
||||||
|
end
|
||||||
|
ensure
|
||||||
|
response_status_is_set: res.status_is_set
|
||||||
|
valid_response_code: res.status_code = {HTTP_STATUS_CODE}.forbidden or
|
||||||
|
res.status_code = {HTTP_STATUS_CODE}.not_found
|
||||||
|
header_sent: res.header_committed
|
||||||
|
message_sent: res.message_committed
|
||||||
|
end
|
||||||
|
|
||||||
|
handle_system_options (req: WSF_REQUEST; res: WSF_RESPONSE)
|
||||||
|
-- Write response to OPTIONS * into `res'.
|
||||||
|
-- This may be redefined by the user, but normally this will not be necessary.
|
||||||
|
require
|
||||||
|
req_attached: req /= Void
|
||||||
|
res_attached: res /= Void
|
||||||
|
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
|
||||||
|
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
|
||||||
|
header_sent: res.header_committed and res.message_committed
|
||||||
|
empty_body: res.transfered_content_length = 0
|
||||||
|
end
|
||||||
|
|
||||||
|
frozen handle_use_proxy (req: WSF_REQUEST; res: WSF_RESPONSE)
|
||||||
|
-- Write Use Proxy response `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).string)
|
||||||
|
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 unavailablity_message as m and then
|
||||||
|
m.count > 0
|
||||||
|
unavailability_duration_xor_unavailable_until: unavailability_duration > 0 implies unavailable_until = Void
|
||||||
|
|
||||||
|
;note
|
||||||
|
copyright: "2011-2013, 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
|
||||||
@@ -317,6 +317,26 @@ feature -- Status report
|
|||||||
end
|
end
|
||||||
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
|
feature -- Hook
|
||||||
|
|
||||||
execute_before (a_mapping: WSF_ROUTER_MAPPING)
|
execute_before (a_mapping: WSF_ROUTER_MAPPING)
|
||||||
|
|||||||
40
library/server/wsf/router/wsf_system_options_access_policy.e
Normal file
40
library/server/wsf/router/wsf_system_options_access_policy.e
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
note
|
||||||
|
|
||||||
|
description: "[
|
||||||
|
Policy to decide if OPTIONS * is honoured.
|
||||||
|
Servers that wish to forbid OPTIONS * requests
|
||||||
|
can redefine `is_system_options_forbidden'.
|
||||||
|
|
||||||
|
Response 403 Forbidden is meant to be accompanied
|
||||||
|
by an entity body describing the reason for the refusal.
|
||||||
|
Since authentication cannot be used for OPTIONS *, there
|
||||||
|
are limited grounds for selective refusal (the IP address might
|
||||||
|
be used though), so we provide a convenient default for
|
||||||
|
`system_options_forbidden_text'.
|
||||||
|
]"
|
||||||
|
|
||||||
|
date: "$Date$"
|
||||||
|
revision: "$Revision$"
|
||||||
|
|
||||||
|
class WSF_SYSTEM_OPTIONS_ACCESS_POLICY
|
||||||
|
|
||||||
|
feature -- Access
|
||||||
|
|
||||||
|
is_system_options_forbidden (req: WSF_REQUEST): BOOLEAN
|
||||||
|
-- Should we return 403 Forbidden in response to OPTIONS * requests?
|
||||||
|
require
|
||||||
|
req_attached: req /= Void
|
||||||
|
do
|
||||||
|
-- by default, unconditionally no.
|
||||||
|
end
|
||||||
|
|
||||||
|
system_options_forbidden_text (req: WSF_REQUEST): detachable READABLE_STRING_8
|
||||||
|
-- Content of 403 Forbidden response;
|
||||||
|
-- Returning `Void' means instead respond with 403 Not found
|
||||||
|
require
|
||||||
|
req_attached: req /= Void
|
||||||
|
do
|
||||||
|
Result := "OPTIONS * is not permitted"
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
note
|
note
|
||||||
description: "[
|
description: "[
|
||||||
This class is used to report a 405 Method not allowed response
|
This class is used to report a 405 Method not allowed response,
|
||||||
|
or a 501 not implemented response, depending upon whether
|
||||||
|
the method is known to the server.
|
||||||
]"
|
]"
|
||||||
date: "$Date$"
|
date: "$Date$"
|
||||||
revision: "$Revision$"
|
revision: "$Revision$"
|
||||||
@@ -76,25 +78,45 @@ feature {WSF_RESPONSE} -- Output
|
|||||||
|
|
||||||
send_to (res: WSF_RESPONSE)
|
send_to (res: WSF_RESPONSE)
|
||||||
local
|
local
|
||||||
s: STRING
|
s, l_html_error_code_text: STRING
|
||||||
l_text: detachable READABLE_STRING_GENERAL
|
l_text: detachable READABLE_STRING_GENERAL
|
||||||
l_loc: detachable READABLE_STRING_8
|
l_loc: detachable READABLE_STRING_8
|
||||||
h: like header
|
h: like header
|
||||||
|
--l_recognized: BOOLEAN
|
||||||
|
l_messages: HTTP_STATUS_CODE_MESSAGES
|
||||||
do
|
do
|
||||||
|
create l_messages
|
||||||
h := header
|
h := header
|
||||||
res.set_status_code ({HTTP_STATUS_CODE}.method_not_allowed)
|
-- To be considered later
|
||||||
|
--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
|
if attached suggested_methods as lst and then not lst.is_empty then
|
||||||
h.put_allow (lst)
|
h.put_allow (lst)
|
||||||
end
|
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, True)
|
||||||
|
|
||||||
if request.is_content_type_accepted ({HTTP_MIME_TYPES}.text_html) then
|
if request.is_content_type_accepted ({HTTP_MIME_TYPES}.text_html) then
|
||||||
s := "<html lang=%"en%"><head>"
|
s := "<html lang=%"en%"><head>"
|
||||||
s.append ("<title>")
|
s.append ("<title>")
|
||||||
s.append (html_encoder.encoded_string (request.request_uri))
|
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 ("</title>%N")
|
s.append ("</title>%N")
|
||||||
s.append (
|
s.append (
|
||||||
"[
|
"[
|
||||||
@@ -111,15 +133,15 @@ feature {WSF_RESPONSE} -- Output
|
|||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="header">Error 405 (Method Not Allowed)!!</div>
|
<div id="header">]" + l_html_error_code_text + "[!!</div>
|
||||||
]")
|
]")
|
||||||
s.append ("<div id=%"logo%">")
|
s.append ("<div id=%"logo%">")
|
||||||
s.append ("<div class=%"outter%"> ")
|
s.append ("<div class=%"outter%"> ")
|
||||||
s.append ("<div class=%"inner1%"></div>")
|
s.append ("<div class=%"inner1%"></div>")
|
||||||
s.append ("<div class=%"inner2%"></div>")
|
s.append ("<div class=%"inner2%"></div>")
|
||||||
s.append ("</div>")
|
s.append ("</div>")
|
||||||
s.append ("Error 405 (Method Not Allowed)</div>")
|
s.append (l_html_error_code_text + "</div>")
|
||||||
s.append ("<div id=%"message%">Error 405 (Method Not Allowed): the request method <code>")
|
s.append ("<div id=%"message%">" + l_html_error_code_text + ": the request method <code>")
|
||||||
s.append (request.request_method)
|
s.append (request.request_method)
|
||||||
s.append ("</code> is inappropriate for the URL for <code>" + html_encoder.encoded_string (request.request_uri) + "</code>.</div>")
|
s.append ("</code> is inappropriate for the URL for <code>" + html_encoder.encoded_string (request.request_uri) + "</code>.</div>")
|
||||||
if attached suggested_methods as lst and then not lst.is_empty then
|
if attached suggested_methods as lst and then not lst.is_empty then
|
||||||
@@ -180,7 +202,7 @@ feature {WSF_RESPONSE} -- Output
|
|||||||
|
|
||||||
h.put_content_type_text_html
|
h.put_content_type_text_html
|
||||||
else
|
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 (request.request_method)
|
||||||
s.append (" is inappropriate for the URL for '" + html_encoder.encoded_string (request.request_uri) + "'.%N")
|
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
|
if attached suggested_methods as lst and then not lst.is_empty then
|
||||||
@@ -239,8 +261,56 @@ feature {WSF_RESPONSE} -- Output
|
|||||||
res.flush
|
res.flush
|
||||||
end
|
end
|
||||||
|
|
||||||
|
feature {NONE} -- Implementation
|
||||||
|
|
||||||
|
-- To be discussed later...
|
||||||
|
--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
|
note
|
||||||
copyright: "2011-2012, Jocelyn Fiat, Javier Velilla, Olivier Ligot, Eiffel Software and others"
|
copyright: "2011-2013, Jocelyn Fiat, Javier Velilla, Olivier Ligot, Eiffel Software and others"
|
||||||
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
|
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
|
||||||
source: "[
|
source: "[
|
||||||
Eiffel Software
|
Eiffel Software
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="ISO-8859-1"?>
|
<?xml version="1.0" encoding="ISO-8859-1"?>
|
||||||
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-11-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-11-0 http://www.eiffel.com/developers/xml/configuration-1-11-0.xsd" name="wsf" uuid="A37CE5AA-4D2A-4441-BC6A-0A1D7EC49647" library_target="wsf">
|
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-10-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-10-0 http://www.eiffel.com/developers/xml/configuration-1-10-0.xsd" name="wsf" uuid="A37CE5AA-4D2A-4441-BC6A-0A1D7EC49647" library_target="wsf">
|
||||||
<target name="wsf">
|
<target name="wsf">
|
||||||
<root all_classes="true"/>
|
<root all_classes="true"/>
|
||||||
<file_rule>
|
<file_rule>
|
||||||
@@ -16,6 +16,7 @@
|
|||||||
<library name="ewsgi" location="..\ewsgi\ewsgi-safe.ecf"/>
|
<library name="ewsgi" location="..\ewsgi\ewsgi-safe.ecf"/>
|
||||||
<library name="http" location="..\..\network\protocol\http\http-safe.ecf"/>
|
<library name="http" location="..\..\network\protocol\http\http-safe.ecf"/>
|
||||||
<library name="time" location="$ISE_LIBRARY\library\time\time-safe.ecf"/>
|
<library name="time" location="$ISE_LIBRARY\library\time\time-safe.ecf"/>
|
||||||
|
<library name="uri" location="$ISE_LIBRARY\library\text\uri\uri-safe.ecf"/>
|
||||||
<library name="uri_template" location="..\..\text\parser\uri_template\uri_template-safe.ecf"/>
|
<library name="uri_template" location="..\..\text\parser\uri_template\uri_template-safe.ecf"/>
|
||||||
<cluster name="router" location=".\router\" recursive="true"/>
|
<cluster name="router" location=".\router\" recursive="true"/>
|
||||||
<cluster name="src" location=".\src\" recursive="true"/>
|
<cluster name="src" location=".\src\" recursive="true"/>
|
||||||
|
|||||||
@@ -16,7 +16,9 @@
|
|||||||
<library name="error" location="../../utility/general/error/error.ecf"/>
|
<library name="error" location="../../utility/general/error/error.ecf"/>
|
||||||
<library name="http" location="../../network/protocol/http/http.ecf"/>
|
<library name="http" location="../../network/protocol/http/http.ecf"/>
|
||||||
<library name="uri_template" location="../../text/parser/uri_template/uri_template.ecf"/>
|
<library name="uri_template" location="../../text/parser/uri_template/uri_template.ecf"/>
|
||||||
<library name="encoder" location="..\..\text\encoder\encoder.ecf"/>
|
<library name="encoder"
|
||||||
|
location="..\..\text\encoder\encoder.ecf"/>
|
||||||
|
<library name="uri" location="$ISE_LIBRARY\library\text\uri\uri.ecf" readonly="true"/>
|
||||||
<cluster name="src" location=".\src" recursive="true"/>
|
<cluster name="src" location=".\src" recursive="true"/>
|
||||||
<cluster name="router" location=".\router" recursive="true"/>
|
<cluster name="router" location=".\router" recursive="true"/>
|
||||||
</target>
|
</target>
|
||||||
|
|||||||
Reference in New Issue
Block a user