Merge branch 'handler' of github.com:colin-adams/EWF into colin-adams-handler
This commit is contained in:
@@ -350,7 +350,9 @@ feature -- Header change: general
|
||||
end
|
||||
|
||||
add_header_key_value (k,v: READABLE_STRING_8)
|
||||
-- Add header `k:v', or replace existing header of same header name/key
|
||||
-- Add header `k:v'.
|
||||
-- If it already exists, there will be multiple header with same name
|
||||
-- which can also be valid
|
||||
local
|
||||
s: STRING_8
|
||||
do
|
||||
@@ -509,6 +511,18 @@ feature -- Content related header
|
||||
put_header_key_value ({HTTP_HEADER_NAMES}.header_content_transfer_encoding, a_mechanism)
|
||||
end
|
||||
|
||||
put_content_language (a_lang: READABLE_STRING_8)
|
||||
-- Put "Content-Language" header of value `a_lang'.
|
||||
do
|
||||
put_header_key_value ({HTTP_HEADER_NAMES}.header_content_language, a_lang)
|
||||
end
|
||||
|
||||
put_content_encoding (a_enc: READABLE_STRING_8)
|
||||
-- Put "Content-Encoding" header of value `a_enc'.
|
||||
do
|
||||
put_header_key_value ({HTTP_HEADER_NAMES}.header_content_encoding, a_enc)
|
||||
end
|
||||
|
||||
put_transfer_encoding (a_enc: READABLE_STRING_8)
|
||||
-- Put "Transfer-Encoding" header with for instance "chunked"
|
||||
do
|
||||
|
||||
@@ -54,6 +54,18 @@ feature -- Access
|
||||
|
||||
http_if_match: STRING = "HTTP_IF_MATCH"
|
||||
|
||||
http_if_modified_since: STRING = "HTTP_IF_MODIFIED_SINCE"
|
||||
|
||||
http_if_none_match: STRING = "HTTP_IF_NONE_MATCH"
|
||||
|
||||
http_if_range: STRING = "HTTP_IF_RANGE"
|
||||
|
||||
http_if_unmodified_since: STRING = "HTTP_IF_UNMODIFIED_SINCE"
|
||||
|
||||
http_last_modified: STRING = "HTTP_LAST_MODIFIED"
|
||||
|
||||
http_range: STRING = "HTTP_RANGE"
|
||||
|
||||
gateway_interface: STRING = "GATEWAY_INTERFACE"
|
||||
|
||||
auth_type: STRING = "AUTH_TYPE"
|
||||
|
||||
@@ -609,6 +609,36 @@ feature -- HTTP_*
|
||||
deferred
|
||||
end
|
||||
|
||||
http_if_modified_since: detachable READABLE_STRING_8
|
||||
-- Modification check on resource
|
||||
deferred
|
||||
end
|
||||
|
||||
http_if_none_match: detachable READABLE_STRING_8
|
||||
-- Existence check on resource
|
||||
deferred
|
||||
end
|
||||
|
||||
http_if_range: detachable READABLE_STRING_8
|
||||
-- Range check on resource
|
||||
deferred
|
||||
end
|
||||
|
||||
http_if_unmodified_since: detachable READABLE_STRING_8
|
||||
-- Modification check on resource
|
||||
deferred
|
||||
end
|
||||
|
||||
http_last_modified: detachable READABLE_STRING_8
|
||||
-- Modification time of resource
|
||||
deferred
|
||||
end
|
||||
|
||||
http_range: detachable READABLE_STRING_8
|
||||
-- Requested byte-range of resource
|
||||
deferred
|
||||
end
|
||||
|
||||
feature -- Extra CGI environment variables
|
||||
|
||||
request_uri: READABLE_STRING_8
|
||||
|
||||
@@ -253,6 +253,42 @@ feature -- Access: HTTP_* CGI meta parameters - 1.1
|
||||
do
|
||||
Result := meta_string_variable ({WGI_META_NAMES}.http_if_match)
|
||||
end
|
||||
|
||||
http_if_modified_since: detachable READABLE_STRING_8
|
||||
-- Modification check on resource
|
||||
do
|
||||
Result := meta_string_variable ({WGI_META_NAMES}.http_if_modified_since)
|
||||
end
|
||||
|
||||
http_if_none_match: detachable READABLE_STRING_8
|
||||
-- Existence check on resource
|
||||
do
|
||||
Result := meta_string_variable ({WGI_META_NAMES}.http_if_none_match)
|
||||
end
|
||||
|
||||
http_if_range: detachable READABLE_STRING_8
|
||||
-- Range check on resource
|
||||
do
|
||||
Result := meta_string_variable ({WGI_META_NAMES}.http_if_range)
|
||||
end
|
||||
|
||||
http_if_unmodified_since: detachable READABLE_STRING_8
|
||||
-- Modification check on resource
|
||||
do
|
||||
Result := meta_string_variable ({WGI_META_NAMES}.http_if_unmodified_since)
|
||||
end
|
||||
|
||||
http_last_modified: detachable READABLE_STRING_8
|
||||
-- Modification time of resource
|
||||
do
|
||||
Result := meta_string_variable ({WGI_META_NAMES}.http_last_modified)
|
||||
end
|
||||
|
||||
http_range: detachable READABLE_STRING_8
|
||||
-- Requested byte-range of resource
|
||||
do
|
||||
Result := meta_string_variable ({WGI_META_NAMES}.http_range)
|
||||
end
|
||||
|
||||
feature -- Access: Extension to CGI meta parameters - 1.1
|
||||
|
||||
|
||||
137
library/server/wsf/router/wsf_caching_policy.e
Normal file
137
library/server/wsf/router/wsf_caching_policy.e
Normal file
@@ -0,0 +1,137 @@
|
||||
note
|
||||
|
||||
description: "[
|
||||
Policies for determing caching of responses.
|
||||
]"
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
deferred class WSF_CACHING_POLICY
|
||||
|
||||
feature -- Access
|
||||
|
||||
Never_expires: NATURAL = 525600
|
||||
-- 525600 = 365 * 24 * 60 * 60 = (almost) 1 year;
|
||||
-- See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.21 for an explanation of why this means never expire
|
||||
|
||||
max_age (req: WSF_REQUEST): NATURAL
|
||||
-- Maximum age in seconds before response to `req` is considered stale;
|
||||
-- This is used to generate a Cache-Control: max-age header.
|
||||
-- Return 0 to indicate already expired.
|
||||
-- Return `Never_expires' to indicate never expires.
|
||||
require
|
||||
req_attached: req /= Void
|
||||
deferred
|
||||
ensure
|
||||
not_more_than_1_year: Result <= Never_expires
|
||||
end
|
||||
|
||||
shared_age (req: WSF_REQUEST): NATURAL
|
||||
-- Maximum age in seconds before response to `req` is considered stale in a shared cache;
|
||||
-- This is used to generate a Cache-Control: s-maxage header.
|
||||
-- If you wish to have different expiry ages for shared and provate caches, redefine this routine.
|
||||
-- Return 0 to indicate already expired.
|
||||
-- Return `Never_expires' to indicate never expires.
|
||||
require
|
||||
req_attached: req /= Void
|
||||
do
|
||||
Result := max_age (req)
|
||||
ensure
|
||||
not_more_than_1_year: Result <= Never_expires
|
||||
end
|
||||
|
||||
http_1_0_age (req: WSF_REQUEST): NATURAL
|
||||
-- Maximum age in seconds before response to `req` is considered stale;
|
||||
-- This is used to generate an Expires header, which HTTP/1.0 caches understand.
|
||||
-- If you wish to generate a different age for HTTP/1.0 caches, then redefine this routine.
|
||||
-- Return 0 to indicate already expired.
|
||||
-- Return `Never_expires' to indicate never expires. Note this will
|
||||
-- make a result cachecable that would not normally be cacheable (such as as response
|
||||
-- to a POST), unless overriden by cache-control headers, so be sure to check `req.request_method'.
|
||||
require
|
||||
req_attached: req /= Void
|
||||
do
|
||||
Result := max_age (req)
|
||||
ensure
|
||||
not_more_than_1_year: Result <= Never_expires
|
||||
end
|
||||
|
||||
is_freely_cacheable (req: WSF_REQUEST): BOOLEAN
|
||||
-- Should the response to `req' be freely cachable in shared caches?
|
||||
-- If `True', then a Cache-Control: public header will be generated.
|
||||
require
|
||||
req_attached: req /= Void
|
||||
deferred
|
||||
end
|
||||
|
||||
is_transformable (req: WSF_REQUEST): BOOLEAN
|
||||
-- Should a non-transparent proxy be allowed to modify headers of response to `req`?
|
||||
-- The headers concerned are listed in http://www.w3.org/Protocols/rfc2616-sec14.html#sec14,9.
|
||||
-- If `False' then a Cache-Control: no-transorm header will be generated.
|
||||
require
|
||||
req_attached: req /= Void
|
||||
do
|
||||
-- We choose a conservative default. But most applications can
|
||||
-- redefine to return `True'.
|
||||
end
|
||||
|
||||
must_revalidate (req: WSF_REQUEST): BOOLEAN
|
||||
-- If a client has requested, or a cache is configured, to ignore server's expiration time,
|
||||
-- should we force revalidation anyway?
|
||||
-- If `True' then a Cache-Control: must-revalidate header will be generated.
|
||||
require
|
||||
req_attached: req /= Void
|
||||
do
|
||||
-- Redefine to force revalidation.
|
||||
end
|
||||
|
||||
must_proxy_revalidate (req: WSF_REQUEST): BOOLEAN
|
||||
-- If a shared cache is configured to ignore server's expiration time,
|
||||
-- should we force revalidation anyway?
|
||||
-- If `True' then a Cache-Control: proxy-revalidate header will be generated.
|
||||
require
|
||||
req_attached: req /= Void
|
||||
do
|
||||
-- Redefine to force revalidation.
|
||||
end
|
||||
|
||||
private_headers (req: WSF_REQUEST): detachable LIST [READABLE_STRING_8]
|
||||
-- Header names intended for a single user.
|
||||
-- If non-Void, then a Cache-Control: private header will be generated.
|
||||
-- Returning an empty list prevents the entire response from being served from a shared cache.
|
||||
require
|
||||
req_attached: req /= Void
|
||||
deferred
|
||||
ensure
|
||||
not_freely_cacheable: Result /= Void implies not is_freely_cacheable (req)
|
||||
end
|
||||
|
||||
non_cacheable_headers (req: WSF_REQUEST): detachable LIST [READABLE_STRING_8]
|
||||
-- Header names that will not be sent from a cache without revalidation;
|
||||
-- If non-Void, then a Cache-Control: no-cache header will be generated.
|
||||
-- Returning an empty list prevents the response being served from a cache
|
||||
-- without revalidation.
|
||||
require
|
||||
req_attached: req /= Void
|
||||
deferred
|
||||
end
|
||||
|
||||
is_sensitive (req: WSF_REQUEST): BOOLEAN
|
||||
-- Is the response to `req' of a sensitive nature?
|
||||
-- If `True' then a Cache-Control: no-store header will be generated.
|
||||
require
|
||||
req_attached: req /= Void
|
||||
deferred
|
||||
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
|
||||
66
library/server/wsf/router/wsf_delete_helper.e
Normal file
66
library/server/wsf/router/wsf_delete_helper.e
Normal file
@@ -0,0 +1,66 @@
|
||||
note
|
||||
|
||||
description: "[
|
||||
Policy-driven helpers to implement the DELETE method.
|
||||
]"
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
class WSF_DELETE_HELPER
|
||||
|
||||
inherit
|
||||
|
||||
WSF_METHOD_HELPER
|
||||
|
||||
feature {NONE} -- Implementation
|
||||
|
||||
send_response (req: WSF_REQUEST; res: WSF_RESPONSE; a_handler: WSF_SKELETON_HANDLER; a_header: HTTP_HEADER; a_new_resource: BOOLEAN)
|
||||
-- Write response to deletion of resource named by `req' into `res'.
|
||||
-- Upto four execution variables may be set on `req':
|
||||
-- "NEGOTIATED_MEDIA_TYPE"
|
||||
-- "NEGOTIATED_LANGUAGE"
|
||||
-- "NEGOTIATED_CHARSET"
|
||||
-- "NEGOTIATED_ENCODING"
|
||||
local
|
||||
l_dt: STRING
|
||||
l_ok: BOOLEAN
|
||||
do
|
||||
a_handler.delete (req)
|
||||
l_ok := a_handler.response_ok (req)
|
||||
if l_ok then
|
||||
if a_handler.includes_response_entity (req) then
|
||||
a_handler.ensure_content_available (req)
|
||||
l_ok := a_handler.response_ok (req)
|
||||
if l_ok then
|
||||
a_header.put_content_length (a_handler.content_length (req).as_integer_32)
|
||||
end
|
||||
-- we don't bother supporting chunked responses for DELETE.
|
||||
else
|
||||
a_header.put_content_length (0)
|
||||
end
|
||||
if attached req.request_time as l_time then
|
||||
l_dt := (create {HTTP_DATE}.make_from_date_time (l_time)).rfc1123_string
|
||||
a_header.put_header_key_value ({HTTP_HEADER_NAMES}.header_date, l_dt)
|
||||
generate_cache_headers (req, a_handler, a_header, l_time)
|
||||
end
|
||||
if a_handler.delete_queued (req) then
|
||||
res.set_status_code ({HTTP_STATUS_CODE}.accepted)
|
||||
res.put_header_text (a_header.string)
|
||||
res.put_string (a_handler.content (req))
|
||||
elseif a_handler.deleted (req) then
|
||||
if a_handler.includes_response_entity (req) then
|
||||
res.set_status_code ({HTTP_STATUS_CODE}.ok)
|
||||
res.put_header_text (a_header.string)
|
||||
res.put_string (a_handler.content (req))
|
||||
else
|
||||
res.set_status_code ({HTTP_STATUS_CODE}.no_content)
|
||||
res.put_header_text (a_header.string)
|
||||
end
|
||||
end
|
||||
end
|
||||
if not l_ok then
|
||||
write_error_response (req, res)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
72
library/server/wsf/router/wsf_get_helper.e
Normal file
72
library/server/wsf/router/wsf_get_helper.e
Normal file
@@ -0,0 +1,72 @@
|
||||
note
|
||||
|
||||
description: "[
|
||||
Policy-driven helpers to implement processing of GET and HEAD requests.
|
||||
]"
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
class WSF_GET_HELPER
|
||||
|
||||
inherit
|
||||
|
||||
WSF_METHOD_HELPER
|
||||
|
||||
feature {NONE} -- Implementation
|
||||
|
||||
send_response (req: WSF_REQUEST; res: WSF_RESPONSE; a_handler: WSF_SKELETON_HANDLER; a_header: HTTP_HEADER; a_new_resource: BOOLEAN)
|
||||
-- Write response to `req' into `res' in accordance with `a_media_type' etc.
|
||||
-- Upto four execution variables may be set on `req':
|
||||
-- "NEGOTIATED_MEDIA_TYPE"
|
||||
-- "NEGOTIATED_LANGUAGE"
|
||||
-- "NEGOTIATED_CHARSET"
|
||||
-- "NEGOTIATED_ENCODING"
|
||||
local
|
||||
l_chunked, l_ok: BOOLEAN
|
||||
l_dt: STRING
|
||||
do
|
||||
a_handler.ensure_content_available (req)
|
||||
l_ok := a_handler.response_ok (req)
|
||||
if l_ok then
|
||||
l_chunked := a_handler.is_chunking (req)
|
||||
if l_chunked then
|
||||
a_header.put_transfer_encoding_chunked
|
||||
else
|
||||
a_header.put_content_length (a_handler.content_length (req).as_integer_32)
|
||||
end
|
||||
if attached req.request_time as l_time then
|
||||
l_dt := (create {HTTP_DATE}.make_from_date_time (l_time)).rfc1123_string
|
||||
a_header.put_header_key_value ({HTTP_HEADER_NAMES}.header_date, l_dt)
|
||||
generate_cache_headers (req, a_handler, a_header, l_time)
|
||||
end
|
||||
l_ok := a_handler.response_ok (req)
|
||||
end
|
||||
if l_ok then
|
||||
res.set_status_code ({HTTP_STATUS_CODE}.ok)
|
||||
else
|
||||
write_error_response (req, res)
|
||||
end
|
||||
if attached a_handler.etag (req) as l_etag then
|
||||
a_header.put_header_key_value ({HTTP_HEADER_NAMES}.header_etag, l_etag)
|
||||
end
|
||||
res.put_header_text (a_header.string)
|
||||
if l_ok then
|
||||
if l_chunked then
|
||||
send_chunked_response (req, res, a_handler, a_header)
|
||||
else
|
||||
res.put_string (a_handler.content (req))
|
||||
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
|
||||
598
library/server/wsf/router/wsf_method_helper.e
Normal file
598
library/server/wsf/router/wsf_method_helper.e
Normal file
@@ -0,0 +1,598 @@
|
||||
note
|
||||
|
||||
description: "[
|
||||
Policy-driven helpers to implement a method.
|
||||
|
||||
]"
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
deferred class WSF_METHOD_HELPER
|
||||
|
||||
inherit
|
||||
|
||||
HTTP_STATUS_CODE_MESSAGES
|
||||
|
||||
SHARED_HTML_ENCODER
|
||||
export {NONE} all end
|
||||
|
||||
feature -- Access
|
||||
|
||||
resource_exists: BOOLEAN
|
||||
-- Does the requested resource (request URI) exist?
|
||||
|
||||
feature -- Setting
|
||||
|
||||
set_resource_exists
|
||||
-- Set `resource_exists' to `True'.
|
||||
do
|
||||
resource_exists := True
|
||||
ensure
|
||||
set: resource_exists
|
||||
end
|
||||
|
||||
feature -- Basic operations
|
||||
|
||||
execute_new_resource (req: WSF_REQUEST; res: WSF_RESPONSE; a_handler: WSF_SKELETON_HANDLER)
|
||||
-- Write response to non-existing resource requested by `req.' into `res'.
|
||||
-- Policy routines are available in `a_handler'.
|
||||
-- Upto four execution variables may be set on `req':
|
||||
-- "NEGOTIATED_MEDIA_TYPE"
|
||||
-- "NEGOTIATED_LANGUAGE"
|
||||
-- "NEGOTIATED_CHARSET"
|
||||
-- "NEGOTIATED_ENCODING"
|
||||
-- An HTTP_HEADER is also available as the execution variable "NEGOTIATED_HTTP_HEADER".
|
||||
-- It includes the Vary header (if any)
|
||||
require
|
||||
req_attached: req /= Void
|
||||
res_attached: res /= Void
|
||||
a_handler_attached: a_handler /= Void
|
||||
local
|
||||
l_locs: LIST [URI]
|
||||
do
|
||||
if a_handler.resource_previously_existed (req) then
|
||||
if a_handler.resource_moved_permanently (req) then
|
||||
l_locs := a_handler.previous_location (req)
|
||||
handle_redirection_error (req, res, l_locs, {HTTP_STATUS_CODE}.moved_permanently)
|
||||
elseif a_handler.resource_moved_temporarily (req) then
|
||||
l_locs := a_handler.previous_location (req)
|
||||
handle_redirection_error (req, res, l_locs, {HTTP_STATUS_CODE}.found)
|
||||
else
|
||||
check attached {HTTP_HEADER} req.execution_variable (a_handler.Negotiated_http_header_execution_variable) as h then
|
||||
-- postcondition header_attached of `handle_content_negotiation'
|
||||
h.put_content_type_text_plain
|
||||
h.put_current_date
|
||||
h.put_content_length (0)
|
||||
res.set_status_code ({HTTP_STATUS_CODE}.gone)
|
||||
res.put_header_lines (h)
|
||||
end
|
||||
end
|
||||
else
|
||||
check attached {HTTP_HEADER} req.execution_variable (a_handler.Negotiated_http_header_execution_variable) as h then
|
||||
-- postcondition header_attached of `handle_content_negotiation'
|
||||
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_lines (h)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
execute_existing_resource (req: WSF_REQUEST; res: WSF_RESPONSE; a_handler: WSF_SKELETON_HANDLER)
|
||||
-- Write response to existing resource requested by `req' into `res'.
|
||||
-- Policy routines are available in `a_handler'.
|
||||
-- Upto four execution variables may be set on `req':
|
||||
-- "NEGOTIATED_MEDIA_TYPE"
|
||||
-- "NEGOTIATED_LANGUAGE"
|
||||
-- "NEGOTIATED_CHARSET"
|
||||
-- "NEGOTIATED_ENCODING"
|
||||
require
|
||||
req_attached: req /= Void
|
||||
res_attached: res /= Void
|
||||
a_handler_attached: a_handler /= Void
|
||||
not_if_match_star: attached req.http_if_match as l_if_match implies not l_if_match.same_string ("*")
|
||||
local
|
||||
l_etags: LIST [READABLE_STRING_8]
|
||||
l_failed: BOOLEAN
|
||||
l_date: HTTP_DATE
|
||||
do
|
||||
if attached req.http_if_match as l_if_match then
|
||||
-- also if-range when we add support for range requests
|
||||
if not l_if_match.same_string ("*") then
|
||||
l_etags := l_if_match.split (',')
|
||||
l_failed := not across l_etags as i_etags some a_handler.matching_etag (req, i_etags.item, True) end
|
||||
end
|
||||
end
|
||||
if l_failed then
|
||||
handle_precondition_failed (req, res)
|
||||
else
|
||||
if attached req.http_if_unmodified_since as l_if_unmodified_since then
|
||||
if l_if_unmodified_since.is_string_8 then
|
||||
create l_date.make_from_string (l_if_unmodified_since.as_string_8)
|
||||
if not l_date.has_error then
|
||||
if a_handler.modified_since (req, l_date.date_time) then
|
||||
handle_precondition_failed (req, res)
|
||||
l_failed := True
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
if not l_failed then
|
||||
if attached req.http_if_none_match as l_if_none_match then
|
||||
l_etags := l_if_none_match.split (',')
|
||||
l_failed := l_if_none_match.same_string ("*") or
|
||||
across l_etags as i_etags some a_handler.matching_etag (req, i_etags.item, False) end
|
||||
end
|
||||
if l_failed then
|
||||
handle_if_none_match_failed (req, res, a_handler)
|
||||
else
|
||||
if attached req.http_if_modified_since as l_if_modified_since then
|
||||
if l_if_modified_since.is_string_8 then
|
||||
create l_date.make_from_string (l_if_modified_since.as_string_8)
|
||||
if not l_date.has_error then
|
||||
if not a_handler.modified_since (req, l_date.date_time) then
|
||||
handle_not_modified (req, res, a_handler)
|
||||
l_failed := True
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
if not l_failed then
|
||||
check attached {HTTP_HEADER} req.execution_variable (a_handler.Negotiated_http_header_execution_variable) as h then
|
||||
-- postcondition header_attached of `handle_content_negotiation'
|
||||
send_response (req, res, a_handler, h, False)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
feature -- Content negotiation
|
||||
|
||||
handle_content_negotiation (req: WSF_REQUEST; res: WSF_RESPONSE; a_handler: WSF_SKELETON_HANDLER)
|
||||
-- Negotiate acceptable content for, then write, response requested by `req' into `res'.
|
||||
-- Policy routines are available in `a_handler'.
|
||||
--
|
||||
-- Either a 406 Not Acceptable error is sent, or upto four execution variables may be set on `req':
|
||||
-- "NEGOTIATED_MEDIA_TYPE"
|
||||
-- "NEGOTIATED_LANGUAGE"
|
||||
-- "NEGOTIATED_CHARSET"
|
||||
-- "NEGOTIATED_ENCODING"
|
||||
-- An HTTP_HEADER is also saved as the execution variable "NEGOTIATED_HTTP_HEADER".
|
||||
-- It includes the Vary header (if any)
|
||||
require
|
||||
req_attached: req /= Void
|
||||
res_attached: res /= Void
|
||||
a_handler_attached: a_handler /= Void
|
||||
local
|
||||
l_conneg: CONNEG_SERVER_SIDE
|
||||
h: HTTP_HEADER
|
||||
l_media: MEDIA_TYPE_VARIANT_RESULTS
|
||||
l_lang: LANGUAGE_VARIANT_RESULTS
|
||||
l_charset: CHARACTER_ENCODING_VARIANT_RESULTS
|
||||
l_encoding: COMPRESSION_VARIANT_RESULTS
|
||||
l_mime_types, l_langs, l_charsets, l_encodings: LIST [STRING]
|
||||
l_vary_star: BOOLEAN
|
||||
do
|
||||
create h.make
|
||||
l_vary_star := not a_handler.predictable_response (req)
|
||||
if l_vary_star then
|
||||
h.add_header_key_value ({HTTP_HEADER_NAMES}.header_vary, "*")
|
||||
elseif attached a_handler.additional_variant_headers (req) as l_additional then
|
||||
across l_additional as i_additional loop
|
||||
h.add_header_key_value ({HTTP_HEADER_NAMES}.header_vary, i_additional.item)
|
||||
end
|
||||
end
|
||||
l_conneg := a_handler.conneg (req)
|
||||
l_mime_types := a_handler.mime_types_supported (req)
|
||||
l_media := l_conneg.media_type_preference (l_mime_types, req.http_accept)
|
||||
if not l_vary_star and l_mime_types.count > 1 and attached l_media.variant_header as l_media_variant then
|
||||
h.add_header_key_value ({HTTP_HEADER_NAMES}.header_vary, l_media_variant)
|
||||
end
|
||||
if not l_media.is_acceptable then
|
||||
handle_not_acceptable ("None of the requested ContentTypes were acceptable", l_mime_types, req, res)
|
||||
else
|
||||
l_langs := a_handler.languages_supported (req)
|
||||
l_lang := l_conneg.language_preference (l_langs, req.http_accept_language)
|
||||
if not l_vary_star and l_langs.count > 1 and attached l_lang.variant_header as l_lang_variant then
|
||||
h.add_header_key_value ({HTTP_HEADER_NAMES}.header_vary, l_lang_variant)
|
||||
end
|
||||
if not l_lang.is_acceptable then
|
||||
handle_not_acceptable ("None of the requested languages were acceptable", l_langs, req, res)
|
||||
else
|
||||
if attached l_lang.language_type as l_language_type then
|
||||
h.put_content_language (l_language_type)
|
||||
req.set_execution_variable (a_handler.Negotiated_language_execution_variable, l_language_type)
|
||||
end
|
||||
l_charsets := a_handler.charsets_supported (req)
|
||||
l_charset := l_conneg.charset_preference (l_charsets, req.http_accept_charset)
|
||||
if not l_vary_star and l_charsets.count > 1 and attached l_charset.variant_header as l_charset_variant then
|
||||
h.add_header_key_value ({HTTP_HEADER_NAMES}.header_vary, l_charset_variant)
|
||||
end
|
||||
if not l_charset.is_acceptable then
|
||||
handle_not_acceptable ("None of the requested character encodings were acceptable", l_charsets, req, res)
|
||||
else
|
||||
if attached l_media.media_type as l_media_type then
|
||||
if attached l_charset.character_type as l_character_type then
|
||||
h.put_content_type (l_media_type + "; charset=" + l_character_type)
|
||||
req.set_execution_variable (a_handler.Negotiated_charset_execution_variable, l_charset)
|
||||
else
|
||||
h.put_content_type (l_media_type)
|
||||
end
|
||||
req.set_execution_variable (a_handler.Negotiated_media_type_execution_variable, l_media_type)
|
||||
end
|
||||
l_encodings := a_handler.encodings_supported (req)
|
||||
l_encoding := l_conneg.encoding_preference (l_encodings, req.http_accept_encoding)
|
||||
if not l_vary_star and l_encodings.count > 1 and attached l_encoding.variant_header as l_encoding_variant then
|
||||
h.add_header_key_value ({HTTP_HEADER_NAMES}.header_vary, l_encoding_variant)
|
||||
end
|
||||
if not l_encoding.is_acceptable then
|
||||
handle_not_acceptable ("None of the requested transfer encodings were acceptable", l_encodings, req, res)
|
||||
else
|
||||
if attached l_encoding.compression_type as l_compression_type then
|
||||
h.put_content_encoding (l_compression_type)
|
||||
req.set_execution_variable (a_handler.Negotiated_encoding_execution_variable, l_compression_type)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
req.set_execution_variable (a_handler.Negotiated_http_header_execution_variable, h)
|
||||
ensure
|
||||
header_attached: attached {HTTP_HEADER} req.execution_variable (a_handler.Negotiated_http_header_execution_variable)
|
||||
end
|
||||
|
||||
feature {NONE} -- Implementation
|
||||
|
||||
send_response (req: WSF_REQUEST; res: WSF_RESPONSE; a_handler: WSF_SKELETON_HANDLER; a_header: HTTP_HEADER; a_new_resource: BOOLEAN)
|
||||
-- Write response to `req' into `res'.
|
||||
-- Upto four execution variables may be set on `req':
|
||||
-- "NEGOTIATED_MEDIA_TYPE"
|
||||
-- "NEGOTIATED_LANGUAGE"
|
||||
-- "NEGOTIATED_CHARSET"
|
||||
-- "NEGOTIATED_ENCODING"
|
||||
require
|
||||
req_attached: req /= Void
|
||||
res_attached: res /= Void
|
||||
a_handler_attached: a_handler /= Void
|
||||
a_header_attached: a_header /= Void
|
||||
deferred
|
||||
end
|
||||
|
||||
send_chunked_response (req: WSF_REQUEST; res: WSF_RESPONSE; a_handler: WSF_SKELETON_HANDLER; a_header: HTTP_HEADER)
|
||||
-- Write response in chunks to `req'.
|
||||
-- Upto four execution variables may be set on `req':
|
||||
-- "NEGOTIATED_MEDIA_TYPE"
|
||||
-- "NEGOTIATED_LANGUAGE"
|
||||
-- "NEGOTIATED_CHARSET"
|
||||
-- "NEGOTIATED_ENCODING"
|
||||
require
|
||||
req_attached: req /= Void
|
||||
res_attached: res /= Void
|
||||
a_handler_attached: a_handler /= Void
|
||||
local
|
||||
l_chunk: TUPLE [a_chunk: READABLE_STRING_8; a_extension: detachable READABLE_STRING_8]
|
||||
do
|
||||
from
|
||||
if a_handler.response_ok (req) then
|
||||
l_chunk := a_handler.next_chunk (req)
|
||||
res.put_chunk (l_chunk.a_chunk, l_chunk.a_extension)
|
||||
else
|
||||
write_error_response (req, res)
|
||||
end
|
||||
until
|
||||
a_handler.finished (req) or not a_handler.response_ok (req)
|
||||
loop
|
||||
a_handler.generate_next_chunk (req)
|
||||
if a_handler.response_ok (req) then
|
||||
l_chunk := a_handler.next_chunk (req)
|
||||
res.put_chunk (l_chunk.a_chunk, l_chunk.a_extension)
|
||||
else
|
||||
write_error_response (req, res)
|
||||
end
|
||||
end
|
||||
if a_handler.finished (req) then
|
||||
-- In future, add support for trailers
|
||||
res.put_chunk_end
|
||||
end
|
||||
end
|
||||
|
||||
generate_cache_headers (req: WSF_REQUEST; a_handler: WSF_SKELETON_HANDLER; a_header: HTTP_HEADER; a_request_time: DATE_TIME)
|
||||
-- Write headers affecting caching for `req' into `a_header'.
|
||||
require
|
||||
req_attached: req /= Void
|
||||
a_handler_attached: a_handler /= Void
|
||||
a_header_attached: a_header /= Void
|
||||
a_request_time_attached: a_request_time /= Void
|
||||
local
|
||||
l_age, l_age_1, l_age_2: NATURAL
|
||||
l_dur: DATE_TIME_DURATION
|
||||
l_dt, l_field_names: STRING
|
||||
do
|
||||
l_age := a_handler.http_1_0_age (req)
|
||||
create l_dur.make (0, 0, 0, 0, 0, l_age.as_integer_32)
|
||||
l_dt := (create {HTTP_DATE}.make_from_date_time (a_request_time + l_dur)).rfc1123_string
|
||||
a_header.put_header_key_value ({HTTP_HEADER_NAMES}.header_expires, l_dt)
|
||||
l_age_1 := a_handler.max_age (req)
|
||||
if l_age_1 /= l_age then
|
||||
a_header.add_header_key_value ({HTTP_HEADER_NAMES}.header_cache_control, "max-age=" + l_age_1.out)
|
||||
end
|
||||
l_age_2 := a_handler.shared_age (req)
|
||||
if l_age_2 /= l_age_1 then
|
||||
a_header.add_header_key_value ({HTTP_HEADER_NAMES}.header_cache_control, "s-maxage=" + l_age_2.out)
|
||||
end
|
||||
if a_handler.is_freely_cacheable (req) then
|
||||
a_header.add_header_key_value ({HTTP_HEADER_NAMES}.header_cache_control, "public")
|
||||
elseif attached a_handler.private_headers (req) as l_fields then
|
||||
l_field_names := "="
|
||||
if not l_fields.is_empty then
|
||||
append_field_name (l_field_names, l_fields)
|
||||
end
|
||||
a_header.add_header_key_value ({HTTP_HEADER_NAMES}.header_cache_control, "private" + l_field_names)
|
||||
end
|
||||
if attached a_handler.non_cacheable_headers (req) as l_fields then
|
||||
l_field_names := "="
|
||||
if not l_fields.is_empty then
|
||||
append_field_name (l_field_names, l_fields)
|
||||
end
|
||||
a_header.add_header_key_value ({HTTP_HEADER_NAMES}.header_cache_control, "no-cache" + l_field_names)
|
||||
end
|
||||
if not a_handler.is_transformable (req) then
|
||||
a_header.add_header_key_value ({HTTP_HEADER_NAMES}.header_cache_control, "no-transform")
|
||||
end
|
||||
if a_handler.must_revalidate (req) then
|
||||
a_header.add_header_key_value ({HTTP_HEADER_NAMES}.header_cache_control, "must-revalidate")
|
||||
end
|
||||
if a_handler.must_proxy_revalidate (req) then
|
||||
a_header.add_header_key_value ({HTTP_HEADER_NAMES}.header_cache_control, "proxy-revalidate")
|
||||
end
|
||||
end
|
||||
|
||||
append_field_name (a_field_names: STRING; a_fields: LIST [READABLE_STRING_8])
|
||||
-- Append all of `a_fields' as a comma-separated list to `a_field_names'.
|
||||
require
|
||||
a_field_names_attached: a_field_names /= Void
|
||||
a_fields_attached: a_fields /= Void
|
||||
do
|
||||
across a_fields as i_fields loop
|
||||
a_field_names.append_string (i_fields.item)
|
||||
if not i_fields.is_last then
|
||||
a_field_names.append_character (',')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
feature -- Error reporting
|
||||
|
||||
write_error_response (req: WSF_REQUEST; res: WSF_RESPONSE)
|
||||
-- Write an error response to `res' using `req.error_handler'.
|
||||
require
|
||||
req_attached: req /= Void
|
||||
res_attached: res /= Void
|
||||
req_has_error: req.has_error
|
||||
local
|
||||
h: HTTP_HEADER
|
||||
m: READABLE_STRING_8
|
||||
do
|
||||
m := req.error_handler.as_string_representation
|
||||
create h.make
|
||||
h.put_content_type_text_plain
|
||||
h.put_content_length (m.count)
|
||||
res.set_status_code (req.error_handler.primary_error_code)
|
||||
res.put_header_lines (h)
|
||||
res.put_string (m)
|
||||
end
|
||||
|
||||
handle_redirection_error (req: WSF_REQUEST; res: WSF_RESPONSE; a_locations: LIST [URI]; a_status_code: INTEGER)
|
||||
-- Write `a_status_code' error to `res'.
|
||||
-- Include all of `a_locations' in the headers, and hyperlink to the first one in the body.
|
||||
require
|
||||
res_attached: res /= Void
|
||||
req_attached: req /= Void
|
||||
a_locations_attached: a_locations /= Void
|
||||
a_location_not_empty: not a_locations.is_empty
|
||||
a_status_code_code: is_valid_http_status_code (a_status_code)
|
||||
local
|
||||
h: HTTP_HEADER
|
||||
s: STRING
|
||||
do
|
||||
if attached http_status_code_message (a_status_code) as l_msg then
|
||||
create h.make
|
||||
across a_locations as i_location loop
|
||||
h.add_header_key_value ({HTTP_HEADER_NAMES}.header_location, i_location.item.string)
|
||||
end
|
||||
if req.is_content_type_accepted ({HTTP_MIME_TYPES}.text_html) then
|
||||
s := "<html lang=%"en%"><head>"
|
||||
s.append ("<title>")
|
||||
s.append (html_encoder.encoded_string (req.request_uri))
|
||||
s.append ("Error " + a_status_code.out + " (" + l_msg + ")")
|
||||
s.append ("</title>%N")
|
||||
s.append ("[
|
||||
<style type="text/css">
|
||||
div#header {color: #fff; background-color: #000; padding: 20px; text-align: center; font-size: 2em; font-weight: bold;}
|
||||
div#message { margin: 40px; text-align: center; font-size: 1.5em; }
|
||||
div#suggestions { margin: auto; width: 60%;}
|
||||
div#suggestions ul { }
|
||||
div#footer {color: #999; background-color: #eee; padding: 10px; text-align: center; }
|
||||
div#logo { float: right; margin: 20px; width: 60px height: auto; font-size: 0.8em; text-align: center; }
|
||||
div#logo div.outer { padding: 6px; width: 60px; border: solid 3px #500; background-color: #b00;}
|
||||
div#logo div.outer div.inner1 { display: block; margin: 10px 15px; width: 30px; height: 50px; color: #fff; background-color: #fff; border: solid 2px #900; }
|
||||
div#logo div.outer div.inner2 { margin: 10px 15px; width: 30px; height: 15px; color: #fff; background-color: #fff; border: solid 2px #900; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
]")
|
||||
s.append ("<div id=%"header%">Error " + a_status_code.out + " (" + l_msg + ")</div>")
|
||||
s.append ("<div id=%"logo%">")
|
||||
s.append ("<div class=%"outer%"> ")
|
||||
s.append ("<div class=%"inner1%"></div>")
|
||||
s.append ("<div class=%"inner2%"></div>")
|
||||
s.append ("</div>")
|
||||
s.append ("The current location for this resource is <a href=%"" + a_locations.first.string + "%">here</a>")
|
||||
s.append ("Error " + a_status_code.out + " (" + l_msg + ")</div>")
|
||||
s.append ("<div id=%"message%">Error " + a_status_code.out + " (" + l_msg + "): <code>" + html_encoder.encoded_string (req.request_uri) + "</code></div>")
|
||||
s.append ("<div id=%"footer%"></div>")
|
||||
s.append ("</body>%N")
|
||||
s.append ("</html>%N")
|
||||
|
||||
h.put_content_type_text_html
|
||||
else
|
||||
s := "Error " + a_status_code.out + " (" + l_msg + "): "
|
||||
s.append (req.request_uri)
|
||||
s.append_character ('%N')
|
||||
s.append ("The current location for this resource is " + a_locations.first.string)
|
||||
h.put_content_type_text_plain
|
||||
end
|
||||
h.put_content_length (s.count)
|
||||
res.put_header_lines (h)
|
||||
res.put_string (s)
|
||||
res.flush
|
||||
end
|
||||
end
|
||||
|
||||
handle_not_acceptable (a_message: READABLE_STRING_8; a_supported: LIST [STRING]; req: WSF_REQUEST; res: WSF_RESPONSE)
|
||||
-- Write a Not Acceptable response to `res'.
|
||||
require
|
||||
req_attached: req /= Void
|
||||
res_attached: res /= Void
|
||||
a_message_attached: a_message /= Void
|
||||
a_supported_attached: a_supported /= Void
|
||||
local
|
||||
h: HTTP_HEADER
|
||||
s: STRING
|
||||
do
|
||||
create h.make
|
||||
h.put_content_type_text_plain
|
||||
h.put_current_date
|
||||
s := a_message
|
||||
s.append_character ('%N')
|
||||
s.append_character ('%N')
|
||||
s.append_string ("We accept the following:%N%N")
|
||||
across a_supported as i_supported loop
|
||||
s.append_string (i_supported.item)
|
||||
s.append_character ('%N')
|
||||
end
|
||||
h.put_content_length (s.count)
|
||||
res.set_status_code ({HTTP_STATUS_CODE}.not_acceptable)
|
||||
res.put_header_lines (h)
|
||||
res.put_string (s)
|
||||
res.flush
|
||||
end
|
||||
|
||||
handle_if_none_match_failed (req: WSF_REQUEST; res: WSF_RESPONSE; a_handler: WSF_SKELETON_HANDLER)
|
||||
-- Write a Not Modified response to `res'.
|
||||
require
|
||||
req_attached: req /= Void
|
||||
res_attached: res /= Void
|
||||
a_handler_attached: a_handler /= Void
|
||||
do
|
||||
handle_not_modified (req, res, a_handler)
|
||||
end
|
||||
|
||||
handle_not_modified (req: WSF_REQUEST; res: WSF_RESPONSE; a_handler: WSF_SKELETON_HANDLER)
|
||||
-- Write a Not Modified response to `res'.
|
||||
-- Upto four execution variables may be set on `req':
|
||||
-- "NEGOTIATED_MEDIA_TYPE"
|
||||
-- "NEGOTIATED_LANGUAGE"
|
||||
-- "NEGOTIATED_CHARSET"
|
||||
-- "NEGOTIATED_ENCODING"
|
||||
require
|
||||
req_attached: req /= Void
|
||||
res_attached: res /= Void
|
||||
a_handler_attached: a_handler /= Void
|
||||
local
|
||||
h: HTTP_HEADER
|
||||
do
|
||||
create h.make
|
||||
h.put_content_type_text_plain
|
||||
h.put_content_length (0)
|
||||
if attached a_handler.etag (req) as l_etag then
|
||||
h.put_header_key_value ({HTTP_HEADER_NAMES}.header_etag, l_etag)
|
||||
end
|
||||
generate_cache_headers (req, a_handler, h, create {DATE_TIME}.make_now_utc)
|
||||
res.set_status_code ({HTTP_STATUS_CODE}.not_modified)
|
||||
res.put_header_lines (h)
|
||||
end
|
||||
|
||||
handle_precondition_failed (req: WSF_REQUEST; res: WSF_RESPONSE)
|
||||
-- Write a Precondition Failed response for `req' to `res'.
|
||||
require
|
||||
req_attached: req /= Void
|
||||
res_attached: res /= Void
|
||||
local
|
||||
h: HTTP_HEADER
|
||||
do
|
||||
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}.precondition_failed)
|
||||
res.put_header_lines (h)
|
||||
end
|
||||
|
||||
handle_unsupported_media_type (req: WSF_REQUEST; res: WSF_RESPONSE)
|
||||
-- Write a Unsupported Media Type response for `req' to `res'.
|
||||
require
|
||||
req_attached: req /= Void
|
||||
res_attached: res /= Void
|
||||
local
|
||||
h: HTTP_HEADER
|
||||
do
|
||||
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}.unsupported_media_type)
|
||||
res.put_header_lines (h)
|
||||
end
|
||||
|
||||
handle_not_implemented (req: WSF_REQUEST; res: WSF_RESPONSE)
|
||||
-- Write a Not Implemented response for `req' to `res'.
|
||||
require
|
||||
req_attached: req /= Void
|
||||
res_attached: res /= Void
|
||||
local
|
||||
h: HTTP_HEADER
|
||||
do
|
||||
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_implemented)
|
||||
res.put_header_lines (h)
|
||||
end
|
||||
|
||||
handle_request_entity_too_large (req: WSF_REQUEST; res: WSF_RESPONSE; a_handler: WSF_SKELETON_HANDLER)
|
||||
-- Write a Request Entity Too Large response for `req' to `res'.
|
||||
require
|
||||
req_attached: req /= Void
|
||||
res_attached: res /= Void
|
||||
a_handler_attached: a_handler /= Void
|
||||
local
|
||||
h: HTTP_HEADER
|
||||
do
|
||||
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}.request_entity_too_large)
|
||||
res.put_header_lines (h)
|
||||
-- FIXME: Need to check if condition is temporary. This needs a new query
|
||||
-- on the handler. For now we can claim compliance by saying the condition
|
||||
-- is always permenent :-) - author's might not like this though.
|
||||
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
|
||||
42
library/server/wsf/router/wsf_method_helper_factory.e
Normal file
42
library/server/wsf/router/wsf_method_helper_factory.e
Normal file
@@ -0,0 +1,42 @@
|
||||
note
|
||||
|
||||
description: "[
|
||||
Default factory for policy-driven method helpers.
|
||||
Extension methods can be implemented here.
|
||||
]"
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
class WSF_METHOD_HELPER_FACTORY
|
||||
|
||||
feature -- Factory
|
||||
|
||||
new_method_helper (a_method: READABLE_STRING_8): detachable WSF_METHOD_HELPER
|
||||
-- New object for processing `a_method';
|
||||
-- Redefine this routine to implement extension methods.
|
||||
require
|
||||
a_method_attached: a_method /= Void
|
||||
do
|
||||
if a_method.is_case_insensitive_equal ({HTTP_REQUEST_METHODS}.method_get) or
|
||||
a_method.is_case_insensitive_equal ({HTTP_REQUEST_METHODS}.method_head) then
|
||||
create {WSF_GET_HELPER} Result
|
||||
elseif a_method.is_case_insensitive_equal ({HTTP_REQUEST_METHODS}.method_put) then
|
||||
create {WSF_PUT_HELPER} Result
|
||||
elseif a_method.is_case_insensitive_equal ({HTTP_REQUEST_METHODS}.method_post) then
|
||||
create {WSF_POST_HELPER} Result
|
||||
elseif a_method.is_case_insensitive_equal ({HTTP_REQUEST_METHODS}.method_delete) then
|
||||
create {WSF_DELETE_HELPER} Result
|
||||
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
|
||||
37
library/server/wsf/router/wsf_options_policy.e
Normal file
37
library/server/wsf/router/wsf_options_policy.e
Normal file
@@ -0,0 +1,37 @@
|
||||
note
|
||||
|
||||
description: "[
|
||||
Default policy for responing to OPTIONS requests other than OPTIONS*
|
||||
By overriding `execute_options', clients can add a body, for example.
|
||||
]"
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
class WSF_OPTIONS_POLICY
|
||||
|
||||
feature -- Basic operations
|
||||
|
||||
execute_options (req: WSF_REQUEST; res: WSF_RESPONSE; a_router: WSF_ROUTER)
|
||||
-- Write response to `req' into `res'.
|
||||
require
|
||||
req_attached: req /= Void
|
||||
options_request: req.is_request_method ({HTTP_REQUEST_METHODS}.method_options)
|
||||
res_attached: res /= Void
|
||||
a_router_attached: a_router /= Void
|
||||
local
|
||||
l_methods: WSF_REQUEST_METHODS
|
||||
h: HTTP_HEADER
|
||||
do
|
||||
create h.make
|
||||
res.set_status_code ({HTTP_STATUS_CODE}.ok)
|
||||
h.put_content_type ({HTTP_MIME_TYPES}.text_plain)
|
||||
h.put_current_date
|
||||
h.put_content_length (0)
|
||||
l_methods := a_router.allowed_methods_for_request (req)
|
||||
if not l_methods.is_empty then
|
||||
h.put_allow (l_methods)
|
||||
end
|
||||
res.put_header_text (h.string)
|
||||
end
|
||||
|
||||
end
|
||||
80
library/server/wsf/router/wsf_post_helper.e
Normal file
80
library/server/wsf/router/wsf_post_helper.e
Normal file
@@ -0,0 +1,80 @@
|
||||
note
|
||||
|
||||
description: "[
|
||||
Policy-driven helpers to implement POST.
|
||||
]"
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
class WSF_POST_HELPER
|
||||
|
||||
inherit
|
||||
|
||||
WSF_METHOD_HELPER
|
||||
redefine
|
||||
execute_new_resource
|
||||
end
|
||||
|
||||
feature -- Basic operations
|
||||
|
||||
execute_new_resource (req: WSF_REQUEST; res: WSF_RESPONSE; a_handler: WSF_SKELETON_HANDLER)
|
||||
-- Write response to non-existing resource requested by `req.' into `res'.
|
||||
-- Policy routines are available in `a_handler'.
|
||||
do
|
||||
if a_handler.allow_post_to_missing_resource (req) then
|
||||
check attached {HTTP_HEADER} req.execution_variable (a_handler.Negotiated_http_header_execution_variable) as h then
|
||||
-- postcondition header_attached of `handle_content_negotiation'
|
||||
send_response (req, res, a_handler, h, True)
|
||||
end
|
||||
else
|
||||
res.send (create {WSF_NOT_FOUND_RESPONSE}.make(req))
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
feature {NONE} -- Implementation
|
||||
|
||||
send_response (req: WSF_REQUEST; res: WSF_RESPONSE; a_handler: WSF_SKELETON_HANDLER; a_header: HTTP_HEADER; a_new_resource: BOOLEAN)
|
||||
-- Write response to `req' into `res' in accordance with `a_media_type' etc. as a new URI.
|
||||
-- Upto four execution variables may be set on `req':
|
||||
-- "NEGOTIATED_MEDIA_TYPE"
|
||||
-- "NEGOTIATED_LANGUAGE"
|
||||
-- "NEGOTIATED_CHARSET"
|
||||
-- "NEGOTIATED_ENCODING"
|
||||
local
|
||||
l_code: NATURAL
|
||||
do
|
||||
a_handler.read_entity (req)
|
||||
if a_handler.is_entity_too_large (req) then
|
||||
handle_request_entity_too_large (req, res, a_handler)
|
||||
else
|
||||
a_handler.check_content_headers (req)
|
||||
l_code := a_handler.content_check_code (req)
|
||||
if l_code /= 0 then
|
||||
if l_code = 415 then
|
||||
handle_unsupported_media_type (req, res)
|
||||
else
|
||||
handle_not_implemented (req, res)
|
||||
end
|
||||
else
|
||||
a_handler.check_request (req, res)
|
||||
if a_handler.request_check_code (req) = 0 then
|
||||
a_handler.append_resource (req, res)
|
||||
-- 200 or 204 or 303 or 500 (add support for this?)
|
||||
-- FIXME: more support, such as includes_response_entity
|
||||
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
|
||||
63
library/server/wsf/router/wsf_previous_policy.e
Normal file
63
library/server/wsf/router/wsf_previous_policy.e
Normal file
@@ -0,0 +1,63 @@
|
||||
note
|
||||
|
||||
description: "[
|
||||
Policies for deciding if a resource that currently doesn't exist used to do so.
|
||||
This default implementation assumes that no resources used to exist.
|
||||
]"
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
deferred class WSF_PREVIOUS_POLICY
|
||||
|
||||
feature -- Access
|
||||
|
||||
resource_previously_existed (req: WSF_REQUEST): BOOLEAN
|
||||
-- Did `req.path_translated' exist previously?
|
||||
require
|
||||
req_attached: req /= Void
|
||||
do
|
||||
-- No. Override if this is not want you want.
|
||||
end
|
||||
|
||||
resource_moved_permanently (req: WSF_REQUEST): BOOLEAN
|
||||
-- Was `req.path_translated' moved permanently?
|
||||
require
|
||||
req_attached: req /= Void
|
||||
previously_existed: resource_previously_existed (req)
|
||||
do
|
||||
-- No. Override if this is not want you want.
|
||||
end
|
||||
|
||||
resource_moved_temporarily (req: WSF_REQUEST): BOOLEAN
|
||||
-- Was `req.path_translated' moved temporarily?
|
||||
require
|
||||
req_attached: req /= Void
|
||||
previously_existed: resource_previously_existed (req)
|
||||
do
|
||||
-- No. Override if this is not want you want.
|
||||
end
|
||||
|
||||
previous_location (req: WSF_REQUEST): LIST [URI]
|
||||
-- Previous location(s) for resource named by `req';
|
||||
require
|
||||
req_attached: req /= Void
|
||||
previously_existed: resource_previously_existed (req)
|
||||
moved: resource_moved_permanently (req) or resource_moved_temporarily (req)
|
||||
do
|
||||
create {LINKED_LIST [URI]} Result.make
|
||||
ensure
|
||||
previous_location_attached: Result /= Void
|
||||
non_empty_list: not Result.is_empty
|
||||
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
|
||||
82
library/server/wsf/router/wsf_put_helper.e
Normal file
82
library/server/wsf/router/wsf_put_helper.e
Normal file
@@ -0,0 +1,82 @@
|
||||
note
|
||||
|
||||
description: "[
|
||||
Policy-driven helpers to implement PUT.
|
||||
]"
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
class WSF_PUT_HELPER
|
||||
|
||||
inherit
|
||||
|
||||
WSF_METHOD_HELPER
|
||||
redefine
|
||||
execute_new_resource
|
||||
end
|
||||
|
||||
feature -- Basic operations
|
||||
|
||||
execute_new_resource (req: WSF_REQUEST; res: WSF_RESPONSE; a_handler: WSF_SKELETON_HANDLER)
|
||||
-- Write response to non-existing resource requested by `req.' into `res'.
|
||||
-- Policy routines are available in `a_handler'.
|
||||
do
|
||||
if a_handler.treat_as_moved_permanently (req) then
|
||||
handle_redirection_error (req, res, a_handler.previous_location (req), {HTTP_STATUS_CODE}.moved_permanently)
|
||||
else
|
||||
check attached {HTTP_HEADER} req.execution_variable (a_handler.Negotiated_http_header_execution_variable) as h then
|
||||
-- postcondition header_attached of `handle_content_negotiation'
|
||||
send_response (req, res, a_handler, h, True)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
feature {NONE} -- Implementation
|
||||
|
||||
send_response (req: WSF_REQUEST; res: WSF_RESPONSE; a_handler: WSF_SKELETON_HANDLER; a_header: HTTP_HEADER; a_new_resource: BOOLEAN)
|
||||
-- Write response to `req' into `res' in accordance with `a_media_type' etc. as a new URI.
|
||||
local
|
||||
l_code: NATURAL
|
||||
do
|
||||
a_handler.read_entity (req)
|
||||
if a_handler.is_entity_too_large (req) then
|
||||
handle_request_entity_too_large (req, res, a_handler)
|
||||
else
|
||||
a_handler.check_content_headers (req)
|
||||
l_code := a_handler.content_check_code (req)
|
||||
if l_code /= 0 then
|
||||
if l_code = 415 then
|
||||
handle_unsupported_media_type (req, res)
|
||||
else
|
||||
handle_not_implemented (req, res)
|
||||
end
|
||||
else
|
||||
a_handler.check_request (req, res)
|
||||
if a_handler.request_check_code (req) = 0 then
|
||||
if a_new_resource then
|
||||
a_handler.create_resource (req, res)
|
||||
-- 201 or 500 (add support for this?)
|
||||
else
|
||||
a_handler.check_conflict (req, res)
|
||||
if a_handler.conflict_check_code (req) = 0 then
|
||||
a_handler.update_resource (req, res)
|
||||
-- 204 or 500 (add support for this?)
|
||||
-- FIXME: more support, such as includes_response_entity
|
||||
end
|
||||
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
|
||||
550
library/server/wsf/router/wsf_skeleton_handler.e
Normal file
550
library/server/wsf/router/wsf_skeleton_handler.e
Normal file
@@ -0,0 +1,550 @@
|
||||
note
|
||||
|
||||
description: "[
|
||||
Policy-driven handlers.
|
||||
Implementers only need to concentrate on creating content.
|
||||
]"
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
deferred class WSF_SKELETON_HANDLER
|
||||
|
||||
inherit
|
||||
|
||||
WSF_URI_TEMPLATE_HANDLER
|
||||
redefine
|
||||
execute
|
||||
end
|
||||
|
||||
WSF_OPTIONS_POLICY
|
||||
|
||||
WSF_PREVIOUS_POLICY
|
||||
|
||||
WSF_CACHING_POLICY
|
||||
|
||||
WSF_METHOD_HELPER_FACTORY
|
||||
|
||||
WSF_SELF_DOCUMENTED_HANDLER
|
||||
|
||||
feature {NONE} -- Initialization
|
||||
|
||||
make_with_router (a_router: WSF_ROUTER)
|
||||
-- Initialize `router'.
|
||||
require
|
||||
a_router_attached: a_router /= Void
|
||||
do
|
||||
router := a_router
|
||||
ensure
|
||||
router_aliased: router = a_router
|
||||
end
|
||||
|
||||
feature -- Router
|
||||
|
||||
router: WSF_ROUTER
|
||||
-- So that WSF_OPTIONS_POLICY can find the allowed methods
|
||||
|
||||
feature -- Execution variables
|
||||
|
||||
Negotiated_language_execution_variable: STRING = "NEGOTIATED_LANGUAGE"
|
||||
-- Execution variable set by framework
|
||||
|
||||
Negotiated_charset_execution_variable: STRING = "NEGOTIATED_CHARSET"
|
||||
-- Execution variable set by framework
|
||||
|
||||
Negotiated_media_type_execution_variable: STRING = "NEGOTIATED_MEDIA_TYPE"
|
||||
-- Execution variable set by framework
|
||||
|
||||
Negotiated_encoding_execution_variable: STRING = "NEGOTIATED_ENCODING"
|
||||
-- Execution variable set by framework
|
||||
|
||||
Negotiated_http_header_execution_variable: STRING = "NEGOTIATED_HTTP_HEADER"
|
||||
-- Execution variable set by framework
|
||||
|
||||
Request_entity_execution_variable: STRING = "REQUEST_ENTITY"
|
||||
-- Execution variable set by framework
|
||||
|
||||
Conflict_check_code_execution_variable: STRING = "CONFLICT_CHECK_CODE"
|
||||
-- Execution variable set by framework
|
||||
|
||||
Content_check_code_execution_variable: STRING = "CONTENT_CHECK_CODE"
|
||||
-- Execution variable set by framework
|
||||
|
||||
Request_check_code_execution_variable: STRING = "REQUEST_CHECK_CODE"
|
||||
-- Execution variable set by framework
|
||||
|
||||
feature -- Access
|
||||
|
||||
is_chunking (req: WSF_REQUEST): BOOLEAN
|
||||
-- Will the response to `req' using chunked transfer encoding?
|
||||
require
|
||||
req_attached: req /= Void
|
||||
deferred
|
||||
end
|
||||
|
||||
includes_response_entity (req: WSF_REQUEST): BOOLEAN
|
||||
-- Does the response to `req' include an entity?
|
||||
-- Method will be DELETE, OUT, POST or an extension method.
|
||||
require
|
||||
req_attached: req /= Void
|
||||
deferred
|
||||
end
|
||||
|
||||
conneg (req: WSF_REQUEST): CONNEG_SERVER_SIDE
|
||||
-- Content negotiation for `req';
|
||||
-- This would normally be a once object, ignoring `req'.
|
||||
require
|
||||
req_attached: req /= Void
|
||||
deferred
|
||||
end
|
||||
|
||||
mime_types_supported (req: WSF_REQUEST): LIST [STRING]
|
||||
-- All values for Accept header that `Current' can serve
|
||||
require
|
||||
req_attached: req /= Void
|
||||
deferred
|
||||
ensure
|
||||
mime_types_supported_includes_default: Result.has (conneg (req).mime_default)
|
||||
end
|
||||
|
||||
languages_supported (req: WSF_REQUEST): LIST [STRING]
|
||||
-- All values for Accept-Language header that `Current' can serve
|
||||
require
|
||||
req_attached: req /= Void
|
||||
deferred
|
||||
ensure
|
||||
languages_supported_includes_default: Result.has (conneg (req).language_default)
|
||||
end
|
||||
|
||||
charsets_supported (req: WSF_REQUEST): LIST [STRING]
|
||||
-- All values for Accept-Charset header that `Current' can serve
|
||||
require
|
||||
req_attached: req /= Void
|
||||
deferred
|
||||
ensure
|
||||
charsets_supported_includes_default: Result.has (conneg (req).charset_default)
|
||||
end
|
||||
|
||||
encodings_supported (req: WSF_REQUEST): LIST [STRING]
|
||||
-- All values for Accept-Encoding header that `Current' can serve
|
||||
require
|
||||
req_attached: req /= Void
|
||||
deferred
|
||||
ensure
|
||||
encodings_supported_includes_default: Result.has (conneg (req).encoding_default)
|
||||
end
|
||||
|
||||
additional_variant_headers (req: WSF_REQUEST): detachable LIST [STRING]
|
||||
-- Header other than Accept, Accept-Language, Accept-Charset and Accept-Encoding,
|
||||
-- which might affect the response
|
||||
do
|
||||
end
|
||||
|
||||
predictable_response (req: WSF_REQUEST): BOOLEAN
|
||||
-- Does the response to `req' vary only on the dimensions of ContentType, Language, Charset and Transfer encoding,
|
||||
-- plus those named in `additional_variant_headers'?
|
||||
do
|
||||
Result := True
|
||||
-- redefine to return `False', so as to induce a Vary: * header
|
||||
end
|
||||
|
||||
matching_etag (req: WSF_REQUEST; a_etag: READABLE_STRING_32; a_strong: BOOLEAN): BOOLEAN
|
||||
-- Is `a_etag' a match for resource requested in `req'?
|
||||
-- If `a_strong' then the strong comparison function must be used.
|
||||
require
|
||||
req_attached: req /= Void
|
||||
deferred
|
||||
end
|
||||
|
||||
etag (req: WSF_REQUEST): detachable READABLE_STRING_8
|
||||
-- Optional Etag for response entity to `req';
|
||||
-- Upto four execution variables may be set on `req':
|
||||
-- "NEGOTIATED_MEDIA_TYPE"
|
||||
-- "NEGOTIATED_LANGUAGE"
|
||||
-- "NEGOTIATED_CHARSET"
|
||||
-- "NEGOTIATED_ENCODING"
|
||||
require
|
||||
req_attached: req /= Void
|
||||
deferred
|
||||
end
|
||||
|
||||
modified_since (req: WSF_REQUEST; a_date_time: DATE_TIME): BOOLEAN
|
||||
-- Has resource requested in `req' been modified since `a_date_time' (UTC)?
|
||||
require
|
||||
req_attached: req /= Void
|
||||
deferred
|
||||
end
|
||||
|
||||
treat_as_moved_permanently (req: WSF_REQUEST): BOOLEAN
|
||||
-- Rather than store as a new entity, do we treat it as an existing entity that has been moved?
|
||||
require
|
||||
req_attached: req /= Void
|
||||
put_request: req.is_request_method ({HTTP_REQUEST_METHODS}.method_put)
|
||||
do
|
||||
-- No. Redefine this if needed.
|
||||
end
|
||||
|
||||
allow_post_to_missing_resource (req: WSF_REQUEST): BOOLEAN
|
||||
-- The resource named in `req' does not exist, and this is a POST. Do we allow it?
|
||||
require
|
||||
req_attached: req /= Void
|
||||
deferred
|
||||
end
|
||||
|
||||
feature -- Measurement
|
||||
|
||||
content_length (req: WSF_REQUEST): NATURAL
|
||||
-- Length of entity-body of the response to `req'
|
||||
require
|
||||
req_attached: req /= Void
|
||||
not_chunked: not is_chunking (req)
|
||||
deferred
|
||||
end
|
||||
|
||||
feature -- Status report
|
||||
|
||||
finished (req: WSF_REQUEST): BOOLEAN
|
||||
-- Has the last chunk been generated for `req'?
|
||||
require
|
||||
req_attached: req /= Void
|
||||
chunked: is_chunking (req)
|
||||
deferred
|
||||
end
|
||||
|
||||
|
||||
feature -- Documentation
|
||||
|
||||
mapping_documentation (m: WSF_ROUTER_MAPPING; a_request_methods: detachable WSF_REQUEST_METHODS): WSF_ROUTER_MAPPING_DOCUMENTATION
|
||||
-- Documentation associated with Current handler, in the context of the mapping `m' and methods `a_request_methods'
|
||||
--| `m' and `a_request_methods' are useful to produce specific documentation when the handler is used for multiple mapping.
|
||||
do
|
||||
create Result.make (m)
|
||||
Result.add_description (description)
|
||||
end
|
||||
|
||||
description: READABLE_STRING_GENERAL
|
||||
-- General description for self-generated documentation;
|
||||
-- The specific URI templates supported will be described automatically
|
||||
deferred
|
||||
ensure
|
||||
description_attached: Result /= Void
|
||||
end
|
||||
|
||||
feature -- DELETE
|
||||
|
||||
delete (req: WSF_REQUEST)
|
||||
-- Delete resource named in `req' or set an error on `req.error_handler'.
|
||||
require
|
||||
req_attached: req /= Void
|
||||
deferred
|
||||
ensure
|
||||
error_or_queued_or_deleted: not req.error_handler.has_error implies (delete_queued (req) or deleted (req))
|
||||
end
|
||||
|
||||
deleted (req: WSF_REQUEST): BOOLEAN
|
||||
-- Has resource named by `req' been deleted?
|
||||
require
|
||||
req_attached: req /= Void
|
||||
do
|
||||
if not req.error_handler.has_error then
|
||||
Result := True
|
||||
end
|
||||
ensure
|
||||
negative_implication: not Result implies req.error_handler.has_error
|
||||
end
|
||||
|
||||
delete_queued (req: WSF_REQUEST): BOOLEAN
|
||||
-- Has resource named by `req' been queued for deletion?
|
||||
require
|
||||
req_attached: req /= Void
|
||||
deferred
|
||||
ensure
|
||||
entity_available: includes_response_entity (req)
|
||||
end
|
||||
|
||||
feature -- GET/HEAD content
|
||||
|
||||
ensure_content_available (req: WSF_REQUEST)
|
||||
-- Commence generation of response text (entity-body) (if not already done in `check_resource_exists').
|
||||
-- If not chunked, then this will create the entire entity-body so as to be available
|
||||
-- for a subsequent call to `content'.
|
||||
-- If chunked, only the first chunk will be made available to `next_chunk'. If chunk extensions
|
||||
-- are used, then this will also generate the chunk extension for the first chunk.
|
||||
-- Upto four execution variables may be set on `req':
|
||||
-- "NEGOTIATED_MEDIA_TYPE"
|
||||
-- "NEGOTIATED_LANGUAGE"
|
||||
-- "NEGOTIATED_CHARSET"
|
||||
-- "NEGOTIATED_ENCODING"
|
||||
-- If you support etags, and you have more than one possible representation
|
||||
-- for the resource (so that your etag depends upon the particular representation),
|
||||
-- then you will probably have already created the response entity in `check_resource_exists'.
|
||||
require
|
||||
req_attached: req /= Void
|
||||
get_or_head_or_delete: req.is_get_head_request_method or req.is_delete_request_method
|
||||
deferred
|
||||
end
|
||||
|
||||
response_ok (req: WSF_REQUEST): BOOLEAN
|
||||
-- Has generation of the response (so-far, if chunked) proceeded witout error?
|
||||
require
|
||||
req_attached: req /= Void
|
||||
do
|
||||
Result := not req.error_handler.has_error
|
||||
ensure
|
||||
last_error_set: Result = not req.error_handler.has_error
|
||||
end
|
||||
|
||||
content (req: WSF_REQUEST): READABLE_STRING_8
|
||||
-- Non-chunked entity body in response to `req';
|
||||
-- Upto four execution variables may be set on `req':
|
||||
-- "NEGOTIATED_MEDIA_TYPE"
|
||||
-- "NEGOTIATED_LANGUAGE"
|
||||
-- "NEGOTIATED_CHARSET"
|
||||
-- "NEGOTIATED_ENCODING"
|
||||
require
|
||||
req_attached: req /= Void
|
||||
head_get_or_delete: req.is_get_head_request_method or req.is_delete_request_method
|
||||
no_error: response_ok (req)
|
||||
not_chunked: not is_chunking (req)
|
||||
deferred
|
||||
end
|
||||
|
||||
generate_next_chunk (req: WSF_REQUEST)
|
||||
-- Prepare next chunk (including optional chunk extension) of entity body in response to `req'.
|
||||
-- This is not called for the first chunk.
|
||||
-- Upto four execution variables may be set on `req':
|
||||
-- "NEGOTIATED_MEDIA_TYPE"
|
||||
-- "NEGOTIATED_LANGUAGE"
|
||||
-- "NEGOTIATED_CHARSET"
|
||||
-- "NEGOTIATED_ENCODING"
|
||||
require
|
||||
req_attached: req /= Void
|
||||
no_error: response_ok (req)
|
||||
chunked: is_chunking (req)
|
||||
deferred
|
||||
end
|
||||
|
||||
next_chunk (req: WSF_REQUEST): TUPLE [a_check: READABLE_STRING_8; a_extension: detachable READABLE_STRING_8]
|
||||
-- Next chunk of entity body in response to `req';
|
||||
-- The second field of the result is an optional chunk extension.
|
||||
-- Four execution variables are set on `req':
|
||||
-- "NEGOTIATED_MEDIA_TYPE"
|
||||
-- "NEGOTIATED_LANGUAGE"
|
||||
-- "NEGOTIATED_CHARSET"
|
||||
-- "NEGOTIATED_ENCODING"
|
||||
require
|
||||
req_attached: req /= Void
|
||||
no_error: response_ok (req)
|
||||
chunked: is_chunking (req)
|
||||
deferred
|
||||
end
|
||||
|
||||
feature -- PUT/POST
|
||||
|
||||
read_entity (req: WSF_REQUEST)
|
||||
-- Read request body and set as `req.execution_variable (Request_entity_execution_variable)'.
|
||||
require
|
||||
req_attached: req /= Void
|
||||
local
|
||||
l_body: STRING
|
||||
do
|
||||
create l_body.make_empty
|
||||
req.read_input_data_into (l_body)
|
||||
if not l_body.is_empty then
|
||||
req.set_execution_variable (Request_entity_execution_variable, l_body)
|
||||
end
|
||||
end
|
||||
|
||||
is_entity_too_large (req: WSF_REQUEST): BOOLEAN
|
||||
-- Is the entity stored in `req.execution_variable (Request_entity_execution_variable)' too large for the application?
|
||||
require
|
||||
req_attached: req /= Void
|
||||
deferred
|
||||
end
|
||||
|
||||
check_content_headers (req: WSF_REQUEST)
|
||||
-- Check we can support all content headers on request entity.
|
||||
-- Set `req.execution_variable (Content_check_code_execution_variable)' to {NATURAL} zero if OK, or 415 or 501 if not.
|
||||
require
|
||||
req_attached: req /= Void
|
||||
deferred
|
||||
end
|
||||
|
||||
content_check_code (req: WSF_REQUEST): NATURAL
|
||||
-- Code set by `check_content_headers'.
|
||||
require
|
||||
req_attached: req /= Void
|
||||
do
|
||||
if attached {NATURAL} req.execution_variable (Content_check_code_execution_variable) as l_code then
|
||||
Result := l_code
|
||||
end
|
||||
end
|
||||
|
||||
create_resource (req: WSF_REQUEST; res: WSF_RESPONSE)
|
||||
-- Create new resource in response to a PUT request when `check_resource_exists' returns `False'.
|
||||
-- Implementor must set error code of 200 OK or 500 Server Error.
|
||||
require
|
||||
req_attached: req /= Void
|
||||
res_attached: res /= Void
|
||||
put_request: req.is_put_request_method
|
||||
deferred
|
||||
end
|
||||
|
||||
append_resource (req: WSF_REQUEST; res: WSF_RESPONSE)
|
||||
-- Create new resource in response to a POST request.
|
||||
-- Implementor must set error code of 200 OK or 204 No Content or 303 See Other or 500 Server Error.
|
||||
require
|
||||
req_attached: req /= Void
|
||||
res_attached: res /= Void
|
||||
post_request: req.is_post_request_method
|
||||
deferred
|
||||
end
|
||||
|
||||
check_conflict (req: WSF_REQUEST; res: WSF_RESPONSE)
|
||||
-- Check to see if updating the resource is problematic due to the current state of the resource.
|
||||
-- Set `req.execution_variable (Conflict_check_code_execution_variable)' to {NATURAL} zero if OK, or 409 if not.
|
||||
-- In the latter case, write the full error response to `res'.
|
||||
require
|
||||
req_attached: req /= Void
|
||||
res_attached: res /= Void
|
||||
deferred
|
||||
end
|
||||
|
||||
conflict_check_code (req: WSF_REQUEST): NATURAL
|
||||
-- Code set by `check_conflict'.
|
||||
require
|
||||
req_attached: req /= Void
|
||||
do
|
||||
if attached {NATURAL} req.execution_variable (Conflict_check_code_execution_variable) as l_code then
|
||||
Result := l_code
|
||||
end
|
||||
end
|
||||
|
||||
check_request (req: WSF_REQUEST; res: WSF_RESPONSE)
|
||||
-- Check that the request entity is a valid request.
|
||||
-- The entity is available as `req.execution_variable (Conflict_check_code_execution_variable)'.
|
||||
-- Set `req.execution_variable (Request_check_code_execution_variable)' to {NATURAL} zero if OK, or 400 if not.
|
||||
-- In the latter case, write the full error response to `res'.
|
||||
require
|
||||
req_attached: req /= Void
|
||||
res_attached: res /= Void
|
||||
put_or_post: req.is_put_post_request_method
|
||||
deferred
|
||||
end
|
||||
|
||||
request_check_code (req: WSF_REQUEST): NATURAL
|
||||
-- Code set by `check_request'.
|
||||
require
|
||||
req_attached: req /= Void
|
||||
do
|
||||
if attached {NATURAL} req.execution_variable (Request_check_code_execution_variable) as l_code then
|
||||
Result := l_code
|
||||
end
|
||||
end
|
||||
|
||||
update_resource (req: WSF_REQUEST; res: WSF_RESPONSE)
|
||||
-- Perform the update requested in `req'.
|
||||
-- Write a response to `res' with a code of 204 or 500.
|
||||
require
|
||||
req_attached: req /= Void
|
||||
res_attached: res /= Void
|
||||
put_request: req.is_put_request_method
|
||||
deferred
|
||||
end
|
||||
|
||||
feature -- Execution
|
||||
|
||||
execute (req: WSF_REQUEST; res: WSF_RESPONSE)
|
||||
-- <Precursor>
|
||||
do
|
||||
check
|
||||
known_method: router.allowed_methods_for_request (req).has (req.request_method)
|
||||
not_trace: not req.is_request_method ({HTTP_REQUEST_METHODS}.method_trace)
|
||||
not_connect: not req.is_request_method ({HTTP_REQUEST_METHODS}.method_connect)
|
||||
end
|
||||
if req.is_request_method ({HTTP_REQUEST_METHODS}.method_options) then
|
||||
execute_options (req, res, router)
|
||||
else
|
||||
if attached new_method_helper (req.request_method) as l_helper then
|
||||
execute_method (req, res, l_helper)
|
||||
else
|
||||
handle_internal_server_error (res)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
execute_method (req: WSF_REQUEST; res: WSF_RESPONSE; a_helper: WSF_METHOD_HELPER)
|
||||
-- Write response to `req' into `res', using `a_helper' as a logic helper.
|
||||
require
|
||||
req_attached: req /= Void
|
||||
res_attached: res /= Void
|
||||
a_helper_attached: a_helper /= Void
|
||||
do
|
||||
a_helper.handle_content_negotiation (req, res, Current)
|
||||
if not res.status_is_set or else res.status_code /= {HTTP_STATUS_CODE}.Not_acceptable then
|
||||
check_resource_exists (req, a_helper)
|
||||
if a_helper.resource_exists then
|
||||
a_helper.execute_existing_resource (req, res, Current)
|
||||
else
|
||||
if attached req.http_if_match as l_if_match and then l_if_match.same_string ("*") then
|
||||
a_helper.handle_precondition_failed (req, res)
|
||||
else
|
||||
a_helper.execute_new_resource (req, res, Current)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
check_resource_exists (req: WSF_REQUEST; a_helper: WSF_METHOD_HELPER)
|
||||
-- Call `a_helper.set_resource_exists' to indicate that `req.path_translated'
|
||||
-- is the name of an existing resource.
|
||||
-- Upto four execution variables may be set on `req':
|
||||
-- "NEGOTIATED_MEDIA_TYPE"
|
||||
-- "NEGOTIATED_LANGUAGE"
|
||||
-- "NEGOTIATED_CHARSET"
|
||||
-- "NEGOTIATED_ENCODING"
|
||||
-- If you support etags, and you have more than one possible representation
|
||||
-- for the resource (so that your etag depends upon the particular representation),
|
||||
-- then you will probably need to create the response entity at this point, rather
|
||||
-- than in `ensure_content_available'.
|
||||
require
|
||||
req_attached: req /= Void
|
||||
a_helper_attached: a_helper /= Void
|
||||
deferred
|
||||
end
|
||||
|
||||
feature {NONE} -- Implementation
|
||||
|
||||
handle_internal_server_error (res: WSF_RESPONSE)
|
||||
-- Write "Internal Server Error" response to `res'.
|
||||
require
|
||||
res_attached: res /= Void
|
||||
local
|
||||
h: HTTP_HEADER
|
||||
m: STRING_8
|
||||
do
|
||||
create h.make
|
||||
h.put_content_type_text_plain
|
||||
m := "Server failed to handle request properly"
|
||||
h.put_content_length (m.count)
|
||||
h.put_current_date
|
||||
res.set_status_code ({HTTP_STATUS_CODE}.internal_server_error)
|
||||
res.put_header_lines (h)
|
||||
res.put_string (m)
|
||||
ensure
|
||||
response_status_is_set: res.status_is_set
|
||||
status_is_service_unavailable: res.status_code = {HTTP_STATUS_CODE}.internal_server_error
|
||||
body_sent: res.message_committed and then res.transfered_content_length > 0
|
||||
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
|
||||
@@ -309,18 +309,44 @@ feature -- Helper
|
||||
Result := request_method.is_case_insensitive_equal (m.as_string_8)
|
||||
end
|
||||
|
||||
is_put_request_method: BOOLEAN
|
||||
-- Is Current a PUT request method?
|
||||
do
|
||||
Result := request_method.is_case_insensitive_equal ({HTTP_REQUEST_METHODS}.method_put)
|
||||
end
|
||||
|
||||
is_post_request_method: BOOLEAN
|
||||
-- Is Current a POST request method?
|
||||
do
|
||||
Result := request_method.is_case_insensitive_equal ({HTTP_REQUEST_METHODS}.method_post)
|
||||
end
|
||||
|
||||
is_delete_request_method: BOOLEAN
|
||||
-- Is Current a DELETE request method?
|
||||
do
|
||||
Result := request_method.is_case_insensitive_equal ({HTTP_REQUEST_METHODS}.method_delete)
|
||||
end
|
||||
|
||||
is_get_request_method: BOOLEAN
|
||||
-- Is Current a GET request method?
|
||||
do
|
||||
Result := request_method.is_case_insensitive_equal ({HTTP_REQUEST_METHODS}.method_get)
|
||||
end
|
||||
|
||||
is_get_head_request_method: BOOLEAN
|
||||
-- Is Current a GET or a HEAD request method?
|
||||
do
|
||||
Result := request_method.is_case_insensitive_equal ({HTTP_REQUEST_METHODS}.method_get) or
|
||||
request_method.is_case_insensitive_equal ({HTTP_REQUEST_METHODS}.method_head)
|
||||
end
|
||||
|
||||
is_put_post_request_method: BOOLEAN
|
||||
-- Is Current a PUT or a POST request method?
|
||||
do
|
||||
Result := request_method.is_case_insensitive_equal ({HTTP_REQUEST_METHODS}.method_put) or
|
||||
request_method.is_case_insensitive_equal ({HTTP_REQUEST_METHODS}.method_post)
|
||||
end
|
||||
|
||||
is_content_type_accepted (a_content_type: READABLE_STRING_GENERAL): BOOLEAN
|
||||
-- Does client accepts content_type for the response?
|
||||
--| Based on header "Accept:" that can be for instance
|
||||
@@ -348,6 +374,8 @@ feature -- Helper
|
||||
end
|
||||
end
|
||||
Result := l_accept.has_substring (a_content_type)
|
||||
else
|
||||
Result := True
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1137,6 +1165,42 @@ feature -- HTTP_*
|
||||
Result := wgi_request.http_if_match
|
||||
end
|
||||
|
||||
http_if_modified_since: detachable READABLE_STRING_8
|
||||
-- Modification check on resource
|
||||
do
|
||||
Result := wgi_request.http_if_modified_since
|
||||
end
|
||||
|
||||
http_if_none_match: detachable READABLE_STRING_8
|
||||
-- Existence check on resource
|
||||
do
|
||||
Result := wgi_request.http_if_none_match
|
||||
end
|
||||
|
||||
http_if_range: detachable READABLE_STRING_8
|
||||
-- Range check on resource
|
||||
do
|
||||
Result := wgi_request.http_if_range
|
||||
end
|
||||
|
||||
http_if_unmodified_since: detachable READABLE_STRING_8
|
||||
-- Modification check on resource
|
||||
do
|
||||
Result := wgi_request.http_if_unmodified_since
|
||||
end
|
||||
|
||||
http_last_modified: detachable READABLE_STRING_8
|
||||
-- Modification time of resource
|
||||
do
|
||||
Result := wgi_request.http_last_modified
|
||||
end
|
||||
|
||||
http_range: detachable READABLE_STRING_8
|
||||
-- Requested byte-range of resource
|
||||
do
|
||||
Result := wgi_request.http_range
|
||||
end
|
||||
|
||||
feature -- Extra CGI environment variables
|
||||
|
||||
request_uri: READABLE_STRING_8
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
<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="conneg" location="../../network/protocol/CONNEG/conneg-safe.ecf"/>
|
||||
<cluster name="router" location=".\router\" recursive="true"/>
|
||||
<cluster name="src" location=".\src\" recursive="true"/>
|
||||
</target>
|
||||
|
||||
@@ -15,7 +15,9 @@
|
||||
<library name="time" location="$ISE_LIBRARY\library\time\time.ecf"/>
|
||||
<library name="error" location="../../utility/general/error/error.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="conneg" location="../../network/protocol/CONNEG/conneg.ecf"/>
|
||||
<library name="encoder"
|
||||
location="..\..\text\encoder\encoder.ecf"/>
|
||||
<library name="uri" location="$ISE_LIBRARY\library\text\uri\uri.ecf" readonly="true"/>
|
||||
|
||||
@@ -25,6 +25,16 @@ feature {NONE} -- Initialization
|
||||
create error_added_actions
|
||||
end
|
||||
|
||||
feature -- Access
|
||||
|
||||
primary_error_code: INTEGER
|
||||
-- Code of first error in `errors'
|
||||
require
|
||||
at_least_one_error: has_error
|
||||
do
|
||||
Result := errors.first.code
|
||||
end
|
||||
|
||||
feature -- Status
|
||||
|
||||
has_error: BOOLEAN
|
||||
|
||||
Reference in New Issue
Block a user