From b4ab4875fc5ebc5dacc4dc6868fa56dd5de59089 Mon Sep 17 00:00:00 2001 From: Colin Adams Date: Sat, 13 Apr 2013 14:48:28 +0100 Subject: [PATCH 01/68] If-Match implemented in skeleton handler --- .../specification/request/wgi_meta_names.e | 12 ++++++ .../ewsgi/specification/request/wgi_request.e | 32 +++++++++++++++- .../implementation/wgi_request_from_table.e | 38 ++++++++++++++++++- library/server/wsf/src/wsf_request.e | 36 ++++++++++++++++++ 4 files changed, 116 insertions(+), 2 deletions(-) diff --git a/library/server/ewsgi/specification/request/wgi_meta_names.e b/library/server/ewsgi/specification/request/wgi_meta_names.e index a4578a6b..c4f9ee81 100644 --- a/library/server/ewsgi/specification/request/wgi_meta_names.e +++ b/library/server/ewsgi/specification/request/wgi_meta_names.e @@ -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" diff --git a/library/server/ewsgi/specification/request/wgi_request.e b/library/server/ewsgi/specification/request/wgi_request.e index d8b77a4c..d860b4e8 100644 --- a/library/server/ewsgi/specification/request/wgi_request.e +++ b/library/server/ewsgi/specification/request/wgi_request.e @@ -605,7 +605,37 @@ feature -- HTTP_* end http_if_match: detachable READABLE_STRING_8 - -- Existance check on resource + -- Existence check on resource + 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 diff --git a/library/server/ewsgi/src/implementation/wgi_request_from_table.e b/library/server/ewsgi/src/implementation/wgi_request_from_table.e index 9f2fdec4..934f5e10 100644 --- a/library/server/ewsgi/src/implementation/wgi_request_from_table.e +++ b/library/server/ewsgi/src/implementation/wgi_request_from_table.e @@ -249,10 +249,46 @@ feature -- Access: HTTP_* CGI meta parameters - 1.1 end http_if_match: detachable READABLE_STRING_8 - -- Existance check on resource + -- Existence check on resource 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 diff --git a/library/server/wsf/src/wsf_request.e b/library/server/wsf/src/wsf_request.e index f460180a..cf8eaa73 100644 --- a/library/server/wsf/src/wsf_request.e +++ b/library/server/wsf/src/wsf_request.e @@ -1048,6 +1048,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 From 98ad77a57d10d897ebe52bab59a9170bbf0aab52 Mon Sep 17 00:00:00 2001 From: Colin Adams Date: Sat, 13 Apr 2013 14:49:03 +0100 Subject: [PATCH 02/68] If-Match implemented in skeleton handler --- library/server/wsf/router/wsf_get_helper.e | 15 +++ library/server/wsf/router/wsf_method_helper.e | 80 +++++++++++++++ .../wsf/router/wsf_method_helper_factory.e | 25 +++++ .../server/wsf/router/wsf_options_policy.e | 37 +++++++ .../server/wsf/router/wsf_previous_policy.e | 40 ++++++++ .../server/wsf/router/wsf_skeleton_handler.e | 98 +++++++++++++++++++ 6 files changed, 295 insertions(+) create mode 100644 library/server/wsf/router/wsf_get_helper.e create mode 100644 library/server/wsf/router/wsf_method_helper.e create mode 100644 library/server/wsf/router/wsf_method_helper_factory.e create mode 100644 library/server/wsf/router/wsf_options_policy.e create mode 100644 library/server/wsf/router/wsf_previous_policy.e create mode 100644 library/server/wsf/router/wsf_skeleton_handler.e diff --git a/library/server/wsf/router/wsf_get_helper.e b/library/server/wsf/router/wsf_get_helper.e new file mode 100644 index 00000000..e20dd50b --- /dev/null +++ b/library/server/wsf/router/wsf_get_helper.e @@ -0,0 +1,15 @@ +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 + +end diff --git a/library/server/wsf/router/wsf_method_helper.e b/library/server/wsf/router/wsf_method_helper.e new file mode 100644 index 00000000..135876c5 --- /dev/null +++ b/library/server/wsf/router/wsf_method_helper.e @@ -0,0 +1,80 @@ +note + + description: "[ + Policy-driven helpers to implement a method. + ]" + date: "$Date$" + revision: "$Revision$" + +deferred class WSF_METHOD_HELPER + +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'. + -- This default implementation does not apply for PUT requests. + -- The behaviour for POST requests depends upon a policy. + require + req_attached: req /= Void + res_attached: res /= Void + a_handler_attached: a_handler /= Void + do + if a_handler.resource_previously_existed (req) then + --| TODO - should we be passing the entire request, or should we further + --| simplify the programmer's task by passing `req.path_translated'? + if a_handler.resource_moved_permanently (req) then + -- TODO 301 Moved Permanently + elseif a_handler.resource_moved_temporarily (req) then + -- TODO 302 Found + else + -- TODO 410 Gone + end + else + -- TODO 404 Not Found + 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'. + 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_32] + do + if attached req.http_if_match as l_if_match then + l_etags := l_if_match.split (',') + + else + -- TODO: check_if_unmodified_since (req, res, a_handler) + end + end + + handle_precondition_failed (req: WSF_REQUEST; res: WSF_RESPONSE) + -- + require + req_attached: req /= Void + res_attached: res /= Void + do + end + +end diff --git a/library/server/wsf/router/wsf_method_helper_factory.e b/library/server/wsf/router/wsf_method_helper_factory.e new file mode 100644 index 00000000..a309b2ff --- /dev/null +++ b/library/server/wsf/router/wsf_method_helper_factory.e @@ -0,0 +1,25 @@ +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' + 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 + end + end + +end diff --git a/library/server/wsf/router/wsf_options_policy.e b/library/server/wsf/router/wsf_options_policy.e new file mode 100644 index 00000000..418ea12b --- /dev/null +++ b/library/server/wsf/router/wsf_options_policy.e @@ -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 diff --git a/library/server/wsf/router/wsf_previous_policy.e b/library/server/wsf/router/wsf_previous_policy.e new file mode 100644 index 00000000..9de00906 --- /dev/null +++ b/library/server/wsf/router/wsf_previous_policy.e @@ -0,0 +1,40 @@ +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$" + +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 + +end diff --git a/library/server/wsf/router/wsf_skeleton_handler.e b/library/server/wsf/router/wsf_skeleton_handler.e new file mode 100644 index 00000000..a6389ee8 --- /dev/null +++ b/library/server/wsf/router/wsf_skeleton_handler.e @@ -0,0 +1,98 @@ +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_ROUTING_HANDLER + redefine + execute + end + + WSF_OPTIONS_POLICY + + WSF_PREVIOUS_POLICY + + WSF_METHOD_HELPER_FACTORY + +feature -- Execution + + execute (req: WSF_REQUEST; res: WSF_RESPONSE) + -- + do + check + known_method: True -- Can't be done until WSF_METHOD_NOT_ALLOWED_RESPONSE + -- is refactored. + -- Then maybe this can become a precondition. But we will still (?) + -- need a check that it isn't CONNECT or TRACE (it MIGHT be HEAD). + 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 + 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 + + 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. + -- Optionally, also call `req.set_server_data', if this is now available as a by-product + -- of the existence check. + require + req_attached: req /= Void + a_helper_attached: a_helper /= Void + deferred + end + + 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_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 + +end From ba11e84ed6e9ce8fb1b0cb0fa89c82b5d3f2c8cb Mon Sep 17 00:00:00 2001 From: Colin Adams Date: Mon, 8 Jul 2013 10:17:04 +0100 Subject: [PATCH 03/68] prior to merging --- .../wsf/src/response/wsf_precondition_failed_message.e | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/library/server/wsf/src/response/wsf_precondition_failed_message.e b/library/server/wsf/src/response/wsf_precondition_failed_message.e index da0ac71f..8ec3273a 100644 --- a/library/server/wsf/src/response/wsf_precondition_failed_message.e +++ b/library/server/wsf/src/response/wsf_precondition_failed_message.e @@ -53,8 +53,6 @@ feature {WSF_RESPONSE} -- Output send_to (res: WSF_RESPONSE) local s: STRING - l_text: detachable READABLE_STRING_GENERAL - l_loc: detachable READABLE_STRING_8 h: like header do h := header @@ -117,8 +115,8 @@ feature {WSF_RESPONSE} -- Output end note - - copyright: "2013, 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)" source: "[ Eiffel Software From 0fca8daeb1a507426417a61b189fac5d8f9c4951 Mon Sep 17 00:00:00 2001 From: Olivier Ligot Date: Thu, 11 Jul 2013 16:46:38 +0200 Subject: [PATCH 04/68] Tests compile again (fixes #63) --- tests/all-safe.ecf | 2 -- tests/all-stable-safe.ecf | 3 --- 2 files changed, 5 deletions(-) diff --git a/tests/all-safe.ecf b/tests/all-safe.ecf index 2fcb5f33..8115909e 100644 --- a/tests/all-safe.ecf +++ b/tests/all-safe.ecf @@ -12,7 +12,6 @@ - @@ -22,7 +21,6 @@ - diff --git a/tests/all-stable-safe.ecf b/tests/all-stable-safe.ecf index 3af09a8a..eb3bf311 100644 --- a/tests/all-stable-safe.ecf +++ b/tests/all-stable-safe.ecf @@ -10,8 +10,6 @@ - - @@ -21,7 +19,6 @@ - From e8c66fa769d036a99510d6093c2cabda7186b955 Mon Sep 17 00:00:00 2001 From: Olivier Ligot Date: Fri, 19 Jul 2013 18:51:31 +0200 Subject: [PATCH 05/68] Fix C compilation when using libfcgi connector on OS X (#65) --- library/server/libfcgi/Clib/README.md | 13 +- library/server/libfcgi/README.md | 13 +- .../libfcgi/implementation/mac/fcgi_c_api.e | 144 ++++++++++++++ .../libfcgi/implementation/mac/fcgi_imp.e | 179 ++++++++++++++++++ library/server/libfcgi/libfcgi-safe.ecf | 19 +- library/server/libfcgi/libfcgi.ecf | 19 +- 6 files changed, 374 insertions(+), 13 deletions(-) create mode 100644 library/server/libfcgi/implementation/mac/fcgi_c_api.e create mode 100644 library/server/libfcgi/implementation/mac/fcgi_imp.e diff --git a/library/server/libfcgi/Clib/README.md b/library/server/libfcgi/Clib/README.md index 6348592c..224d63c3 100644 --- a/library/server/libfcgi/Clib/README.md +++ b/library/server/libfcgi/Clib/README.md @@ -1,6 +1,6 @@ -== libFCGI for Eiffel libFCGI wrapper library == +# libFCGI for Eiffel libFCGI wrapper library -=== On Windows === +## On Windows The Eiffel libFCGI wrapper needs a modified version of libFCGI (provided by http://www.fastcgi.com/devkit/libfcgi/) @@ -12,10 +12,15 @@ And then to build the needed .dll and .lib file, use either: build_win32.bat or build_win64.bat -=== On other platorms === +## On other platorms You can use the original version of libfcgi -For instance, on Ubuntu (or any debian): +### Debian based system (Ubuntu, ...) +On Ubuntu (or any Debian based system): > sudo apt-get install libfcgi-dev + +### Mac OS X +On Mac OS X: +> sudo port install fcgi diff --git a/library/server/libfcgi/README.md b/library/server/libfcgi/README.md index 09f769b8..c0d26f43 100644 --- a/library/server/libfcgi/README.md +++ b/library/server/libfcgi/README.md @@ -1,13 +1,16 @@ -== Eiffel libFCGI wrapper library == +# Eiffel libFCGI wrapper library This Eiffel library wraps the libFCGI devkit from http://www.fastcgi.com/devkit/libfcgi/ -=== Windows === +## Windows To compile your own binaries .lib and .dll on __Windows__: [read more](Clib/README.md) -=== Others === +## Others -==== Debian based system (ubuntu, ...) ==== -To install the fcgi lib on debian, you can use +### Debian based system (ubuntu, ...) +To install the fcgi lib on Ubuntu (or any Debian based system), you can use > sudo apt-get install libfcgi-dev +### Mac OS X +To install the fcgi lib on Mac OS X, you can use [MacPorts](http://www.macports.org/) +> sudo port install fcgi diff --git a/library/server/libfcgi/implementation/mac/fcgi_c_api.e b/library/server/libfcgi/implementation/mac/fcgi_c_api.e new file mode 100644 index 00000000..144abb9e --- /dev/null +++ b/library/server/libfcgi/implementation/mac/fcgi_c_api.e @@ -0,0 +1,144 @@ +note + description: "Wrappers around FastCGI C API." + legal: "See notice at end of class." + status: "See notice at end of class." + date: "$Date$" + revision: "$Revision$" + +class + FCGI_C_API + +feature -- Connection + + accept: INTEGER + -- Accept a Fast CGI connection. + -- Return 0 for successful calls, -1 otherwise. + external + "C inline use %"fcgi_stdio.h%"" + alias + "return FCGI_Accept();" + end + + environ: POINTER + -- Get the (char**) environ variable from the DLL. + external + "C inline use %"crt_externs.h%"" + alias + "return (char*) _NSGetEnviron();" + end + + finish + -- Finished current request from HTTP server started from + -- the most recent call to `fcgi_accept'. + external + "C inline use %"fcgi_stdio.h%"" + alias + "FCGI_Finish();" + end + + set_exit_status (v: INTEGER) + -- Set the exit status for the most recent request + external + "C inline use %"fcgi_stdio.h%"" + alias + "FCGI_SetExitStatus($v);" + end + +feature -- Input + + read_content_into (a_buffer: POINTER; a_length: INTEGER): INTEGER + -- Read content stream into `a_buffer' but no more than `a_length' character. + external + "C inline use %"fcgi_stdio.h%"" + alias + "[ + { + size_t n; + if (! FCGI_feof(FCGI_stdin)) { + n = FCGI_fread($a_buffer, 1, $a_length, FCGI_stdin); + } else { + n = 0; + } + return n; + } + ]" + end + +feature {FCGI_IMP} -- Internal + + feof (v: POINTER): INTEGER + -- FCGI_feof() + external + "C inline use %"fcgi_stdio.h%"" + alias + "FCGI_feof" + end + +feature {NONE} -- Input + + gets (s: POINTER): POINTER + -- gets() reads a line from stdin into the buffer pointed to + -- by s until either a terminating newline or EOF, which it + -- replaces with '\0' + -- No check for buffer overrun is performed + external + "C inline use %"fcgi_stdio.h%"" + alias + "return FCGI_gets($s);" + end + +feature -- Output + + put_string (v: POINTER; n: INTEGER) + external + "C inline use %"fcgi_stdio.h%"" + alias + "FCGI_fwrite($v, 1, $n, FCGI_stdout);" + end + +feature -- Error + + put_error (v: POINTER; n: INTEGER) + external + "C inline use %"fcgi_stdio.h%"" + alias + "FCGI_fwrite($v, 1, $n, FCGI_stderr);" + end + +feature -- Access + + stdout: POINTER + -- FCGI_stdout() return pointer on output FCGI_FILE + external + "C inline use %"fcgi_stdio.h%"" + alias + "FCGI_stdout" + end + + stdin: POINTER + -- FCGI_stdin() return pointer on input FCGI_FILE + external + "C inline use %"fcgi_stdio.h%"" + alias + "FCGI_stdin" + end + + stderr: POINTER + -- FCGI_stderr() return pointer on error FCGI_FILE + external + "C inline use %"fcgi_stdio.h%"" + alias + "FCGI_stderr" + end + +note + copyright: "Copyright (c) 1984-2011, Eiffel Software and others" + license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)" + source: "[ + Eiffel Software + 5949 Hollister Ave., Goleta, CA 93117 USA + Telephone 805-685-1006, Fax 805-685-6869 + Website http://www.eiffel.com + Customer support http://support.eiffel.com + ]" +end diff --git a/library/server/libfcgi/implementation/mac/fcgi_imp.e b/library/server/libfcgi/implementation/mac/fcgi_imp.e new file mode 100644 index 00000000..7f41b318 --- /dev/null +++ b/library/server/libfcgi/implementation/mac/fcgi_imp.e @@ -0,0 +1,179 @@ +note + description: "Implementation for the FCGI_I interface" + legal: "See notice at end of class." + status: "See notice at end of class." + date: "$Date$" + revision: "$Revision$" + +deferred class FCGI_IMP + +inherit + FCGI_I + STRING_HANDLER + +feature {NONE} -- Initialization + + make + -- Initialize FCGI interface + do + create fcgi + end + + fcgi: FCGI_C_API + -- FastCGI C API primitives + + +feature -- Access + + fcgi_environ: POINTER + do + Result := fcgi.environ + end + + fcgi_end_of_input: BOOLEAN + do + Result := fcgi.feof (fcgi.stdin) = 0 + end + +feature -- FCGI Connection + + fcgi_listen: INTEGER + -- Listen to the FCGI input stream + -- Return 0 for successful calls, -1 otherwise. + do + Result := {FCGI_C_API}.accept + end + + fcgi_finish + -- Finish current request from HTTP server started from + -- the most recent call to `fcgi_accept'. + do + {FCGI_C_API}.finish + end + + set_fcgi_exit_status (v: INTEGER) + do + {FCGI_C_API}.set_exit_status (-2) + end + +feature -- FCGI output + + put_string (a_str: READABLE_STRING_8) + -- Put `a_str' on the FastCGI stdout. + local + l_c_str: C_STRING + do + l_c_str := c_buffer + l_c_str.set_string (a_str) + {FCGI_C_API}.put_string (l_c_str.item, l_c_str.count) + end + +feature -- Error + + put_error (a_message: READABLE_STRING_8) + -- Put error message `a_message' on the FastCGI stderr + local + l_c_str: C_STRING + do + l_c_str := c_buffer + l_c_str.set_string (a_message) + fcgi.put_error (l_c_str.item, l_c_str.count) + end + +feature -- FCGI Input + + copy_from_stdin (n: INTEGER; tf: FILE) + -- Read up to n bytes from stdin and write to given file + local + l_c_str: C_STRING + num, readsize, writecount: INTEGER + done: BOOLEAN + do + readsize := n.min (K_input_bufsize) + l_c_str := c_buffer + from + until done or writecount >= n + loop + num := {FCGI_C_API}.read_content_into (l_c_str.item, readsize) + if num = 0 then + -- EOF + done := True + else + tf.put_managed_pointer (c_buffer.managed_data, 0, num) + writecount := writecount + num + end + end + end + +feature {NONE} -- Implementation: FCGI Input + + fill_pointer_from_stdin (p: POINTER; n: INTEGER): INTEGER + -- Read up to `n' bytes from stdin and store in pointer `p' + -- and return number of bytes read. + do + Result := {FCGI_C_API}.read_content_into (p, n) + end + +feature -- I/O Routines + +--RFO read_stdin_into (a_str: STRING) +--RFO -- Read a string from the `stdin' into `a_str'. +--RFO require +--RFO a_str_not_void: a_str /= Void +--RFO local +--RFO l_c_str: C_STRING +--RFO n: INTEGER +--RFO do +--RFO l_c_str := c_buffer +--RFO n := {FCGI_C_API}.read_content_into (l_c_str.item, l_c_str.capacity) +--RFO a_str.set_count (n) +--RFO l_c_str.read_substring_into (a_str, 1, n) +--RFO end + +--RFO read_string_into (a_str: STRING) +--RFO require +--RFO exists: a_str /= Void +--RFO local +--RFO l_c_str: C_STRING +--RFO p: POINTER +--RFO do +--RFO create l_c_str.make_empty (1024) +--RFO p := {FCGI_C_API}.gets (l_c_str.item) +--RFO-- if p /= default_pointer and p = l_c_str.item then +--RFO a_str.resize (l_c_str.count) +--RFO l_c_str.read_string_into (a_str) +--RFO-- else +--RFO-- put_error_line ("Bad pointer from gets") +--RFO-- end +--RFO end + +--RFO read_line +--RFO -- Read up to the next end of line, or end of input +--RFO -- Leave result in last_string +--RFO -- Newline character is absent from result +--RFO do +--RFO if last_string = Void then +--RFO create Result.make (K_input_bufsize) +--RFO else +--RFO last_string.wipe_out +--RFO end +--RFO-- if input_filename /= Void then +--RFO-- io.read_line +--RFO-- last_string.append (io.last_string) +--RFO-- else +--RFO read_string_into (last_string) +--RFO-- end +--RFO end + +note + copyright: "Copyright (c) 1984-2011, Eiffel Software and others" + license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)" + source: "[ + Eiffel Software + 5949 Hollister Ave., Goleta, CA 93117 USA + Telephone 805-685-1006, Fax 805-685-6869 + Website http://www.eiffel.com + Customer support http://support.eiffel.com + ]" + +end diff --git a/library/server/libfcgi/libfcgi-safe.ecf b/library/server/libfcgi/libfcgi-safe.ecf index fe797bfe..525ce69e 100644 --- a/library/server/libfcgi/libfcgi-safe.ecf +++ b/library/server/libfcgi/libfcgi-safe.ecf @@ -26,7 +26,12 @@ - + + + + + + @@ -34,6 +39,7 @@ /linux$ /fake$ + /mac$ @@ -41,8 +47,17 @@ /fake$ /windows$ + /mac$ - + + + + + /fake$ + /windows$ + /linux$ + + diff --git a/library/server/libfcgi/libfcgi.ecf b/library/server/libfcgi/libfcgi.ecf index f03336d3..1b0dbdc3 100644 --- a/library/server/libfcgi/libfcgi.ecf +++ b/library/server/libfcgi/libfcgi.ecf @@ -27,7 +27,12 @@ - + + + + + + @@ -36,6 +41,7 @@ /linux$ /fake$ + /mac$ @@ -43,8 +49,17 @@ /windows$ /fake$ + /mac$ - + + + + + /fake$ + /windows$ + /linux$ + + From 2c3d5b6f596f5729fff877286d6676452def85dc Mon Sep 17 00:00:00 2001 From: Jocelyn Fiat Date: Tue, 30 Jul 2013 16:06:06 +0200 Subject: [PATCH 06/68] Update README.md --- README.md | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index e52fc08a..282d281e 100644 --- a/README.md +++ b/README.md @@ -15,25 +15,15 @@ For download, check * https://github.com/EiffelWebFramework/EWF/downloads ## Requirements -* Compiling from EiffelStudio 7.0 -* Developped using EiffelStudio 7.1 (on Windows, Linux) -* Tested using EiffelStudio 7.1 with "jenkins" CI server (not anymore compatible with 6.8 due to use of `TABLE_ITERABLE') +* Compiling from EiffelStudio 7.2 +* Developped using EiffelStudio 7.3 (on Windows, Linux) +* Tested using EiffelStudio 7.2 with "jenkins" CI server (not anymore compatible with 6.8 due to use of `TABLE_ITERABLE') * The code have to allow __void-safe__ compilation and non void-safe system (see [more about void-safety](http://docs.eiffel.com/book/method/void-safe-programming-eiffel) ) ## How to get the source code? -Using git version >= 1.6.5 -* git clone --recursive https://github.com/EiffelWebFramework/EWF.git - -Otherwise, try +Using git * git clone https://github.com/EiffelWebFramework/EWF.git -* cd Eiffel-Web-Framework -* git submodule update --init -* git submodule foreach --recursive git checkout master - -An alternative to the last 2 instructions is to use the script from tools folder: -* cd tools -* update_git_working_copy * And to build the required and related Clibs * cd contrib/ise_library/cURL From 4b87a00637999db1ae8cfeb138d0f6d995fa16ac Mon Sep 17 00:00:00 2001 From: Berend de Boer Date: Sat, 3 Aug 2013 20:09:16 +1200 Subject: [PATCH 07/68] Remove invariant violation. --- library/server/wsf/src/wsf_request.e | 41 ++++++++++++++-------------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/library/server/wsf/src/wsf_request.e b/library/server/wsf/src/wsf_request.e index d7e843d0..aa1cd32b 100644 --- a/library/server/wsf/src/wsf_request.e +++ b/library/server/wsf/src/wsf_request.e @@ -1,18 +1,18 @@ note description: "[ Server request context of the httpd request - + It includes CGI interface and a few extra values that are usually valuable - meta_variable (a_name: READABLE_STRING_GENERAL): detachable WSF_STRING - meta_string_variable (a_name: READABLE_STRING_GENERAL): detachable READABLE_STRING_32 - + meta_variable (a_name: READABLE_STRING_GENERAL): detachable WSF_STRING + meta_string_variable (a_name: READABLE_STRING_GENERAL): detachable READABLE_STRING_32 + In addition it provides - + query_parameter (a_name: READABLE_STRING_GENERAL): detachable WSF_VALUE form_parameter (a_name: READABLE_STRING_GENERAL): detachable WSF_VALUE cookie (a_name: READABLE_STRING_GENERAL): detachable WSF_VALUE ... - + And also has execution_variable (a_name: READABLE_STRING_GENERAL): detachable ANY --| to keep value attached to the request @@ -140,7 +140,6 @@ feature -- Destroy end content_length_value := 0 - content_type := Void execution_variables_table.wipe_out internal_cookies_table := Void internal_form_data_parameters_table := Void @@ -208,7 +207,7 @@ feature -- Error handling error_handler: ERROR_HANDLER -- Error handler - -- By default initialized to new handler + -- By default initialized to new handler feature -- Access: Input @@ -219,7 +218,7 @@ feature -- Access: Input end is_chunked_input: BOOLEAN - -- Is request using chunked transfer-encoding? + -- Is request using chunked transfer-encoding? -- If True, the Content-Length has no meaning do Result := wgi_request.is_chunked_input @@ -375,7 +374,7 @@ feature -- Eiffel WGI access Result := wgi_request.wgi_connector end -feature {WSF_REQUEST_EXPORTER} -- Override value +feature {WSF_REQUEST_EXPORTER} -- Override value set_request_method (a_request_method: like request_method) -- Set `request_method' to `a_request_method' @@ -414,7 +413,7 @@ feature {NONE} -- Access: global variable end end -feature -- Access: global variables +feature -- Access: global variables items: ITERABLE [WSF_VALUE] do @@ -654,9 +653,9 @@ feature -- Access: CGI Meta variables feature {NONE} -- Access: CGI meta parameters meta_variables_table: STRING_TABLE [WSF_STRING] - -- CGI Environment parameters + -- CGI Environment parameters -feature -- Access: CGI meta parameters - 1.1 +feature -- Access: CGI meta parameters - 1.1 auth_type: detachable READABLE_STRING_8 -- This variable is specific to requests made via the "http" @@ -1128,7 +1127,7 @@ feature -- HTTP_* http_access_control_request_headers: detachable READABLE_STRING_8 -- Indicates which headers will be used in the actual request - -- as part of the preflight request + -- as part of the preflight request do Result := wgi_request.http_access_control_request_headers end @@ -1319,7 +1318,7 @@ feature -- Query parameters feature {NONE} -- Query parameters: implementation query_parameters_table: STRING_TABLE [WSF_VALUE] - -- Parameters extracted from QUERY_STRING + -- Parameters extracted from QUERY_STRING local vars: like internal_query_parameters_table p,e: INTEGER @@ -1496,7 +1495,7 @@ feature {NONE} -- Form fields and related uploaded_files_table: STRING_TABLE [WSF_UPLOADED_FILE] get_form_parameters - -- Variables sent by POST, ... request + -- Variables sent by POST, ... request local vars: like internal_form_data_parameters_table l_raw_data_cell: detachable CELL [detachable STRING_8] @@ -1528,7 +1527,7 @@ feature {NONE} -- Form fields and related end form_parameters_table: STRING_TABLE [WSF_VALUE] - -- Variables sent by POST request + -- Variables sent by POST request local vars: like internal_form_data_parameters_table do @@ -1541,7 +1540,7 @@ feature {NONE} -- Form fields and related Result := vars end -feature {NONE} -- Implementation: smart parameter identification +feature {NONE} -- Implementation: smart parameter identification add_value_to_table (a_name: READABLE_STRING_8; a_value: READABLE_STRING_8; a_table: STRING_TABLE [WSF_VALUE]) -- Add urlencoded parameter `a_name'=`a_value' to `a_table' @@ -1749,7 +1748,7 @@ feature {NONE} -- Implementation: URL Utility -- Server url internal_url_base: detachable STRING - -- URL base of potential script + -- URL base of potential script feature -- Element change @@ -1909,7 +1908,7 @@ feature {NONE} -- Implementation end end -feature {NONE} -- Implementation: utilities +feature {NONE} -- Implementation: utilities single_slash_starting_string (s: READABLE_STRING_32): STRING_32 -- Return the string `s' (or twin) with one and only one starting slash @@ -1939,7 +1938,7 @@ feature {NONE} -- Implementation: utilities check i >= 2 and i <= n end Result := s.substring (i - 1, s.count) else - --| starts with one '/' and only one + --| starts with one '/' and only one Result := s end elseif n = 1 then From 5753af3e43bb6988d21c688049462a4a1a91f589 Mon Sep 17 00:00:00 2001 From: Jocelyn Fiat Date: Mon, 5 Aug 2013 10:20:41 +0200 Subject: [PATCH 08/68] Cosmetic (removed commented line and fixed bad indentation) --- library/server/wsf/src/wsf_request.e | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/library/server/wsf/src/wsf_request.e b/library/server/wsf/src/wsf_request.e index aa1cd32b..36a38f19 100644 --- a/library/server/wsf/src/wsf_request.e +++ b/library/server/wsf/src/wsf_request.e @@ -156,7 +156,6 @@ feature -- Destroy raw_input_data_recorded := False request_method := empty_string_8 set_uploaded_file_path (Void) --- wgi_request end feature -- Status report @@ -1127,7 +1126,7 @@ feature -- HTTP_* http_access_control_request_headers: detachable READABLE_STRING_8 -- Indicates which headers will be used in the actual request - -- as part of the preflight request + -- as part of the preflight request do Result := wgi_request.http_access_control_request_headers end From 8d9dca1a947cecb1063a7eb8023e71c4c3da49f2 Mon Sep 17 00:00:00 2001 From: Jocelyn Fiat Date: Mon, 5 Aug 2013 10:21:24 +0200 Subject: [PATCH 09/68] removed building the Clib for Eiffel cUrl, since it is not anymore included in EWF. --- contrib/ise_library/build.eant | 1 - 1 file changed, 1 deletion(-) diff --git a/contrib/ise_library/build.eant b/contrib/ise_library/build.eant index 62ed34b6..82d1546f 100644 --- a/contrib/ise_library/build.eant +++ b/contrib/ise_library/build.eant @@ -29,7 +29,6 @@ - From e0bfdab1065f5af51a394365e5dbe6f1d9e0a13d Mon Sep 17 00:00:00 2001 From: Colin Adams Date: Tue, 6 Aug 2013 13:47:07 +0100 Subject: [PATCH 10/68] Add CONNEG to wsf*.ecf to support ploicy-driven framework --- library/server/wsf/wsf-safe.ecf | 1 + library/server/wsf/wsf.ecf | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/library/server/wsf/wsf-safe.ecf b/library/server/wsf/wsf-safe.ecf index 06b6121b..e47d827a 100644 --- a/library/server/wsf/wsf-safe.ecf +++ b/library/server/wsf/wsf-safe.ecf @@ -18,6 +18,7 @@ + diff --git a/library/server/wsf/wsf.ecf b/library/server/wsf/wsf.ecf index 3d19ec4d..b315cfb2 100644 --- a/library/server/wsf/wsf.ecf +++ b/library/server/wsf/wsf.ecf @@ -15,7 +15,9 @@ - + + From 8ab6dba1c8a9dcc2f7ec256cd67365ed6d1564eb Mon Sep 17 00:00:00 2001 From: Colin Adams Date: Tue, 6 Aug 2013 13:49:58 +0100 Subject: [PATCH 11/68] New routines added to HTTP_HEADER to support ploicy-driven framework --- library/network/protocol/http/src/http_header.e | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/library/network/protocol/http/src/http_header.e b/library/network/protocol/http/src/http_header.e index b1eee52d..72e6fd37 100644 --- a/library/network/protocol/http/src/http_header.e +++ b/library/network/protocol/http/src/http_header.e @@ -509,6 +509,18 @@ feature -- Content related header put_header_key_value ({HTTP_HEADER_NAMES}.header_content_transfer_encoding, a_mechanism) end + put_content_language (a_enc: READABLE_STRING_8) + -- Put "Content-Language" header of value `a_enc'. + do + put_header_key_value ({HTTP_HEADER_NAMES}.header_content_language, a_enc) + 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 From 0a9d2085295f1765b3684c76c821aeb543503559 Mon Sep 17 00:00:00 2001 From: Colin Adams Date: Tue, 6 Aug 2013 13:51:43 +0100 Subject: [PATCH 12/68] New routines added to WSF_REQUEST to support ploicy-driven framework --- library/server/wsf/src/wsf_request.e | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/library/server/wsf/src/wsf_request.e b/library/server/wsf/src/wsf_request.e index 33887bda..572d5a28 100644 --- a/library/server/wsf/src/wsf_request.e +++ b/library/server/wsf/src/wsf_request.e @@ -311,18 +311,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 From 8dbd24afd1dd7a75b71ce09e7661b6390775f856 Mon Sep 17 00:00:00 2001 From: Colin Adams Date: Tue, 6 Aug 2013 13:57:12 +0100 Subject: [PATCH 13/68] Policy-driven URI template handlers --- .../server/wsf/router/wsf_caching_policy.e | 125 ++++++ library/server/wsf/router/wsf_delete_helper.e | 60 +++ library/server/wsf/router/wsf_method_helper.e | 375 +++++++++++++++++- .../wsf/router/wsf_method_helper_factory.e | 8 +- library/server/wsf/router/wsf_post_helper.e | 70 ++++ .../server/wsf/router/wsf_previous_policy.e | 30 +- library/server/wsf/router/wsf_put_helper.e | 75 ++++ .../server/wsf/router/wsf_skeleton_handler.e | 367 ++++++++++++++++- 8 files changed, 1084 insertions(+), 26 deletions(-) create mode 100644 library/server/wsf/router/wsf_caching_policy.e create mode 100644 library/server/wsf/router/wsf_delete_helper.e create mode 100644 library/server/wsf/router/wsf_post_helper.e create mode 100644 library/server/wsf/router/wsf_put_helper.e diff --git a/library/server/wsf/router/wsf_caching_policy.e b/library/server/wsf/router/wsf_caching_policy.e new file mode 100644 index 00000000..4c780b19 --- /dev/null +++ b/library/server/wsf/router/wsf_caching_policy.e @@ -0,0 +1,125 @@ +note + + description: "[ + Policies for determing caching of responses. + ]" + date: "$Date$" + revision: "$Revision$" + +deferred class WSF_CACHING_POLICY + +feature -- Access + + 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 (365 * 1440 = 1 year) to indicate never expires. + require + req_attached: req /= Void + deferred + ensure + not_more_than_1_year: Result <= (365 * 1440).as_natural_32 + 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 (365 * 1440 = 1 year) to indicate never expires. + require + req_attached: req /= Void + do + Result := age (req) + ensure + not_more_than_1_year: Result <= (365 * 1440).as_natural_32 + 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 (365 * 1440 = 1 year) 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 := age (req) + ensure + not_more_than_1_year: Result <= (365 * 1440).as_natural_32 + 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 + +end diff --git a/library/server/wsf/router/wsf_delete_helper.e b/library/server/wsf/router/wsf_delete_helper.e new file mode 100644 index 00000000..6bc0e646 --- /dev/null +++ b/library/server/wsf/router/wsf_delete_helper.e @@ -0,0 +1,60 @@ +note + + description: "[ + Policy-driven helpers to implement the DELETE method. + ]" + date: "$Date$" + revision: "$Revision$" + +class WSF_DELETE_HELPER + +inherit + + WSF_METHOD_HELPER + rename + send_get_response as handle_delete + redefine + handle_delete + end + +feature {NONE} -- Implementation + + handle_delete (req: WSF_REQUEST; res: WSF_RESPONSE; a_handler: WSF_SKELETON_HANDLER; a_header: HTTP_HEADER; + a_media_type, a_language_type, a_character_type, a_compression_type: detachable READABLE_STRING_8; a_new_resource: BOOLEAN) + -- Write response to deletion of resource named by `req' into `res' in accordance with `a_media_type' etc. + local + l_dt: STRING + do + a_handler.delete (req) + if a_handler.includes_response_entity (req) then + a_handler.ensure_content_available (req, a_media_type, a_language_type, a_character_type, a_compression_type) + a_header.put_content_length (a_handler.content_length (req).as_integer_32) + -- 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, a_media_type, a_language_type, a_character_type, a_compression_type)) + 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, a_media_type, a_language_type, a_character_type, a_compression_type)) + else + res.set_status_code ({HTTP_STATUS_CODE}.no_content) + res.put_header_text (a_header.string) + end + else + -- TODO - req.error_handler.has_error = True + --handle_internal_server_error (a_handler.last_error (req), req, res) + end + end + +end diff --git a/library/server/wsf/router/wsf_method_helper.e b/library/server/wsf/router/wsf_method_helper.e index 135876c5..6b23ebec 100644 --- a/library/server/wsf/router/wsf_method_helper.e +++ b/library/server/wsf/router/wsf_method_helper.e @@ -2,11 +2,12 @@ note description: "[ Policy-driven helpers to implement a method. - ]" + This implementation is suitable for GET and HEAD. + ]" date: "$Date$" revision: "$Revision$" -deferred class WSF_METHOD_HELPER +class WSF_METHOD_HELPER feature -- Access @@ -34,24 +35,37 @@ feature -- Basic operations req_attached: req /= Void res_attached: res /= Void a_handler_attached: a_handler /= Void + local + l_locs: LIST [URI] + h: HTTP_HEADER do if a_handler.resource_previously_existed (req) then - --| TODO - should we be passing the entire request, or should we further - --| simplify the programmer's task by passing `req.path_translated'? if a_handler.resource_moved_permanently (req) then - -- TODO 301 Moved Permanently + l_locs := a_handler.previous_location (req) + -- TODO 301 Moved permanently response elseif a_handler.resource_moved_temporarily (req) then - -- TODO 302 Found + l_locs := a_handler.previous_location (req) + -- TODO := 302 Found response else - -- TODO 410 Gone + 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}.gone) + res.put_header_text (h.string) end else - -- TODO 404 Not Found + 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 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'. + + 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'. require req_attached: req /= Void @@ -59,22 +73,347 @@ feature -- Basic operations 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_32] + 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 - l_etags := l_if_match.split (',') - + -- TODO - 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 - -- TODO: check_if_unmodified_since (req, res, a_handler) + 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) + 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) + l_failed := True + end + end + end + end + end + if not l_failed then + handle_content_negotiation (req, res, a_handler, False) + end + end + end + end + +feature {NONE} -- Implementation + + handle_content_negotiation (req: WSF_REQUEST; res: WSF_RESPONSE; + a_handler: WSF_SKELETON_HANDLER; a_new_resource: BOOLEAN) + -- Negotiate acceptable content for, then write, response requested by `req' into `res'. + -- Policy routines are available in `a_handler'. + -- This default version applies to GET and HEAD. + 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_accepted ("None of the requested ContentTypes were acceptable", 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_accepted ("None of the requested languages were acceptable", req, res) + else + if attached l_lang.language_type as l_language_type then + h.put_content_language (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_accepted ("None of the requested character encodings were acceptable", req, res) + else + if attached l_media.media_type as l_media_type and attached l_charset.character_type as l_character_type then + h.put_content_type (l_media_type + "; charset=" + l_character_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_accepted ("None of the requested transfer encodings were acceptable", req, res) + else + if attached l_encoding.compression_type as l_compression_type then + h.put_content_encoding (l_compression_type) + end + -- We do not support multiple choices, so + send_get_response (req, res, a_handler, h, + l_media.media_type, l_lang.language_type, l_charset.character_type, l_encoding.compression_type, a_new_resource) + end + end + end + end + end + + send_get_response (req: WSF_REQUEST; res: WSF_RESPONSE; a_handler: WSF_SKELETON_HANDLER; a_header: HTTP_HEADER; + a_media_type, a_language_type, a_character_type, a_compression_type: detachable READABLE_STRING_8; a_new_resource: BOOLEAN) + -- Write response to `req' into `res' in accordance with `a_media_type' etc. + require + req_attached: req /= Void + res_attached: res /= Void + a_handler_attached: a_handler /= Void + a_header_attached: a_header /= Void + a_media_type_attached: a_media_type /= Void + a_language_type_attached: a_language_type /= Void + a_character_type_attached: a_character_type /= Void + a_compression_type_attached: a_compression_type /= Void + local + l_chunked, l_ok: BOOLEAN + l_dt: STRING + do + a_handler.ensure_content_available (req, a_media_type, a_language_type, a_character_type, a_compression_type) + 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) + if l_ok then + res.set_status_code ({HTTP_STATUS_CODE}.ok) + else + -- TODO - req.error_handler.has_error = True + --handle_internal_server_error (a_handler.last_error (req), req, res) + end + if attached a_handler.etag (req, a_media_type, a_language_type, a_character_type, a_compression_type) 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, a_media_type, a_language_type, a_character_type, a_compression_type) + else + res.put_string (a_handler.content (req, a_media_type, a_language_type, a_character_type, a_compression_type)) + end + end + end + + send_chunked_response (req: WSF_REQUEST; res: WSF_RESPONSE; a_handler: WSF_SKELETON_HANDLER; a_header: HTTP_HEADER; + a_media_type, a_language_type, a_character_type, a_compression_type: READABLE_STRING_8) + -- Write response in chunks to `req' into `res' in accordance with `a_media_type' etc. + require + req_attached: req /= Void + res_attached: res /= Void + a_handler_attached: a_handler /= Void + a_media_type_attached: a_media_type /= Void + a_language_type_attached: a_language_type /= Void + a_character_type_attached: a_character_type /= Void + a_compression_type_attached: a_compression_type /= 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, a_media_type, a_language_type, a_character_type, a_compression_type) + res.put_chunk (l_chunk.a_chunk, l_chunk.a_extension) + else + -- TODO - req.error_handler.has_error = True + -- handle_internal_server_error (a_handler.last_error (req), req, res) + end + until + a_handler.finished (req) or not a_handler.response_ok (req) + loop + a_handler.generate_next_chunk (req, a_media_type, a_language_type, a_character_type, a_compression_type) + if a_handler.response_ok (req) then + l_chunk := a_handler.next_chunk (req, a_media_type, a_language_type, a_character_type, a_compression_type) + res.put_chunk (l_chunk.a_chunk, l_chunk.a_extension) + else + -- TODO - req.error_handler.has_error = True + -- handle_internal_server_error (a_handler.last_error (req), req, res) + end + end + if a_handler.finished (req) then + -- TODO - support for trailers + res.put_chunk_end end end - handle_precondition_failed (req: WSF_REQUEST; res: WSF_RESPONSE) - -- + 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.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 -- Basic operations + + handle_not_accepted (a_message: READABLE_STRING_8; req: WSF_REQUEST; res: WSF_RESPONSE) + -- Write a Not Accepted response to `res'. + require + req_attached: req /= Void + res_attached: res /= Void + a_message_attached: a_message /= Void + do + -- TODO: flag this if it gets to code review. + end + + handle_if_none_match_failed (req: WSF_REQUEST; res: WSF_RESPONSE) + -- Write a Precondition Failed response to `res'. require req_attached: req /= Void res_attached: res /= Void do + -- TODO: flag this if it gets to code review. Why not just handle_precondition_failed? end - + + handle_not_modified (req: WSF_REQUEST; res: WSF_RESPONSE) + -- Write a Precondition Failed response to `res'. + require + req_attached: req /= Void + res_attached: res /= Void + do + -- TODO: flag this if it gets to code review. Why not just handle_precondition_failed? + 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_text (h.string) + 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 diff --git a/library/server/wsf/router/wsf_method_helper_factory.e b/library/server/wsf/router/wsf_method_helper_factory.e index a309b2ff..c41a6e3d 100644 --- a/library/server/wsf/router/wsf_method_helper_factory.e +++ b/library/server/wsf/router/wsf_method_helper_factory.e @@ -18,7 +18,13 @@ feature -- Factory 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 + create 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 diff --git a/library/server/wsf/router/wsf_post_helper.e b/library/server/wsf/router/wsf_post_helper.e new file mode 100644 index 00000000..b05a40a7 --- /dev/null +++ b/library/server/wsf/router/wsf_post_helper.e @@ -0,0 +1,70 @@ +note + + description: "[ + Policy-driven helpers to implement POST. + ]" + date: "$Date$" + revision: "$Revision$" + +class WSF_POST_HELPER + +inherit + + WSF_METHOD_HELPER + rename + send_get_response as do_post + redefine + execute_new_resource, + do_post + 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'. + local + l_etags: LIST [READABLE_STRING_32] + l_failed: BOOLEAN + do + if a_handler.allow_post_to_missing_resource (req) then + handle_content_negotiation (req, res, a_handler, True) + else + -- TODO 404 Not Found + end + end + + +feature {NONE} -- Implementation + + do_post (req: WSF_REQUEST; res: WSF_RESPONSE; a_handler: WSF_SKELETON_HANDLER; a_header: HTTP_HEADER; + a_media_type, a_language_type, a_character_type, a_compression_type: detachable READABLE_STRING_8; a_new_resource: BOOLEAN) + -- Write response to `req' into `res' in accordance with `a_media_type' etc. as a new URI. + 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) + if a_handler.content_check_code (req) /= 0 then + -- TODO - 415 or 501 + 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?) + -- TODO: more support, such as includes_response_entity + end + end + end + end + + handle_request_entity_too_large (req: WSF_REQUEST; res: WSF_RESPONSE; a_handler: WSF_SKELETON_HANDLER) + -- TODO. + require + -- TODO + do + -- Need to check if condition is temporary. + end + +end diff --git a/library/server/wsf/router/wsf_previous_policy.e b/library/server/wsf/router/wsf_previous_policy.e index 9de00906..b64c0450 100644 --- a/library/server/wsf/router/wsf_previous_policy.e +++ b/library/server/wsf/router/wsf_previous_policy.e @@ -7,11 +7,11 @@ note date: "$Date$" revision: "$Revision$" -class WSF_PREVIOUS_POLICY +deferred class WSF_PREVIOUS_POLICY feature -- Access - resource_previously_existed (req: WSF_REQUEST) : BOOLEAN + resource_previously_existed (req: WSF_REQUEST): BOOLEAN -- Did `req.path_translated' exist previously? require req_attached: req /= Void @@ -19,7 +19,7 @@ feature -- Access -- No. Override if this is not want you want. end - resource_moved_permanently (req: WSF_REQUEST) : BOOLEAN + resource_moved_permanently (req: WSF_REQUEST): BOOLEAN -- Was `req.path_translated' moved permanently? require req_attached: req /= Void @@ -28,7 +28,7 @@ feature -- Access -- No. Override if this is not want you want. end - resource_moved_temporarily (req: WSF_REQUEST) : BOOLEAN + resource_moved_temporarily (req: WSF_REQUEST): BOOLEAN -- Was `req.path_translated' moved temporarily? require req_attached: req /= Void @@ -37,4 +37,26 @@ feature -- Access -- 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) + deferred + ensure + previous_location_attached: Result /= Void + non_empty_list: not Result.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 diff --git a/library/server/wsf/router/wsf_put_helper.e b/library/server/wsf/router/wsf_put_helper.e new file mode 100644 index 00000000..67f123b8 --- /dev/null +++ b/library/server/wsf/router/wsf_put_helper.e @@ -0,0 +1,75 @@ +note + + description: "[ + Policy-driven helpers to implement PUT. + ]" + date: "$Date$" + revision: "$Revision$" + +class WSF_PUT_HELPER + +inherit + + WSF_METHOD_HELPER + rename + send_get_response as do_put + redefine + execute_new_resource, + do_put + 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 + -- TODO 301 Moved permanently response (single location) + else + handle_content_negotiation (req, res, a_handler, True) + end + end + + +feature {NONE} -- Implementation + + do_put (req: WSF_REQUEST; res: WSF_RESPONSE; a_handler: WSF_SKELETON_HANDLER; a_header: HTTP_HEADER; + a_media_type, a_language_type, a_character_type, a_compression_type: detachable READABLE_STRING_8; a_new_resource: BOOLEAN) + -- Write response to `req' into `res' in accordance with `a_media_type' etc. as a new URI. + 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) + if a_handler.content_check_code (req) /= 0 then + -- TODO - 415 or 501 + 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?) + -- TODO: more support, such as includes_response_entity + end + end + end + end + end + end + + handle_request_entity_too_large (req: WSF_REQUEST; res: WSF_RESPONSE; a_handler: WSF_SKELETON_HANDLER) + -- TODO. + require + -- TODO + do + -- Need to check if condition is temporary. + end + +end diff --git a/library/server/wsf/router/wsf_skeleton_handler.e b/library/server/wsf/router/wsf_skeleton_handler.e index a6389ee8..aa8d01c3 100644 --- a/library/server/wsf/router/wsf_skeleton_handler.e +++ b/library/server/wsf/router/wsf_skeleton_handler.e @@ -20,8 +20,358 @@ inherit WSF_PREVIOUS_POLICY + WSF_CACHING_POLICY + WSF_METHOD_HELPER_FACTORY +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 negotiatior 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; a_media_type, a_language_type, a_character_type, a_compression_type: READABLE_STRING_8): detachable READABLE_STRING_8 + -- Optional Etag for `req' in the requested variant + require + req_attached: req /= Void + a_media_type_attached: a_media_type /= Void + a_language_type_attached: a_language_type /= Void + a_character_type_attached: a_character_type /= Void + a_compression_type_attached: a_compression_type /= 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 -- 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 + deferred + 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; + a_media_type, a_language_type, a_character_type, a_compression_type: READABLE_STRING_8) + -- Commence generation of response text (entity-body). + -- 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. + require + req_attached: req /= Void + get_or_head: req.is_get_head_request_method + a_media_type_attached: a_media_type /= Void + a_language_type_attached: a_language_type /= Void + a_character_type_attached: a_character_type /= Void + a_compression_type_attached: a_compression_type /= Void + 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; a_media_type, a_language_type, a_character_type, a_compression_type: READABLE_STRING_8): READABLE_STRING_8 + -- Non-chunked entity body in response to `req' + 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) + a_media_type_attached: a_media_type /= Void + a_language_type_attached: a_language_type /= Void + a_character_type_attached: a_character_type /= Void + a_compression_type_attached: a_compression_type /= Void + deferred + end + + generate_next_chunk (req: WSF_REQUEST; a_media_type, a_language_type, a_character_type, a_compression_type: READABLE_STRING_8) + -- Prepare next chunk (including optional chunk extension) of entity body in response to `req'. + -- This is not called for the first chunk. + require + req_attached: req /= Void + no_error: response_ok (req) + chunked: is_chunking (req) + a_media_type_attached: a_media_type /= Void + a_language_type_attached: a_language_type /= Void + a_character_type_attached: a_character_type /= Void + a_compression_type_attached: a_compression_type /= Void + deferred + end + + next_chunk (req: WSF_REQUEST; a_media_type, a_language_type, a_character_type, a_compression_type: READABLE_STRING_8): 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. + require + req_attached: req /= Void + no_error: response_ok (req) + chunked: is_chunking (req) + a_media_type_attached: a_media_type /= Void + a_language_type_attached: a_language_type /= Void + a_character_type_attached: a_character_type /= Void + a_compression_type_attached: a_compression_type /= Void + deferred + end + +feature -- PUT/POST + + read_entity (req: WSF_REQUEST) + -- Read request body and set as `req.execution_variable ("REQUEST_ENTITY")'. + 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", l_body) + end + end + + is_entity_too_large (req: WSF_REQUEST): BOOLEAN + -- Is the entity stored in `req.execution_variable ("REQUEST_ENTITY")' 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")' 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") 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 we can support all content headers on request entity. + -- Set `req.execution_variable ("CONFLICT_CHECK_CODE")' 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") 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 ("REQUEST_ENTITY")'. + -- Set `req.execution_variable ("REQUEST_CHECK_CODE")' 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") 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) @@ -66,14 +416,14 @@ feature -- Execution 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. - -- Optionally, also call `req.set_server_data', if this is now available as a by-product - -- of the existence check. 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 @@ -94,5 +444,16 @@ feature -- Execution 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 From 277eb0b4b68b147646470e4974f432fe5e64493d Mon Sep 17 00:00:00 2001 From: Colin Adams Date: Tue, 6 Aug 2013 15:01:24 +0100 Subject: [PATCH 14/68] restbucksCRUD example changed to use policy-driven framework --- examples/restbucksCRUD/restbucks-safe.ecf | 5 +- .../src/resource/order_handler.e | 595 ++++++++++++------ examples/restbucksCRUD/src/restbucks_server.e | 2 +- 3 files changed, 392 insertions(+), 210 deletions(-) diff --git a/examples/restbucksCRUD/restbucks-safe.ecf b/examples/restbucksCRUD/restbucks-safe.ecf index 86dcda7f..ca7c3f18 100644 --- a/examples/restbucksCRUD/restbucks-safe.ecf +++ b/examples/restbucksCRUD/restbucks-safe.ecf @@ -1,5 +1,5 @@ - + @@ -18,11 +18,14 @@ + + + diff --git a/examples/restbucksCRUD/src/resource/order_handler.e b/examples/restbucksCRUD/src/resource/order_handler.e index 0bddd924..f1db9d1a 100644 --- a/examples/restbucksCRUD/src/resource/order_handler.e +++ b/examples/restbucksCRUD/src/resource/order_handler.e @@ -5,18 +5,11 @@ note revision: "$Revision$" class ORDER_HANDLER + inherit - WSF_URI_TEMPLATE_HANDLER - - WSF_RESOURCE_HANDLER_HELPER - redefine - do_get, - do_post, - do_put, - do_delete - end - + WSF_SKELETON_HANDLER + SHARED_DATABASE_API SHARED_EJSON @@ -25,154 +18,394 @@ inherit SHARED_ORDER_VALIDATION - WSF_SELF_DOCUMENTED_HANDLER - -feature -- Execute - - execute (req: WSF_REQUEST; res: WSF_RESPONSE) - -- Execute request handler - do - execute_methods (req, res) + WSF_RESOURCE_HANDLER_HELPER + rename + execute_options as helper_execute_options, + handle_internal_server_error as helper_handle_internal_server_error end +create + + make_with_router + feature -- API DOC - api_doc : STRING = "URI:/order METHOD: POST%N URI:/order/{orderid} METHOD: GET, PUT, DELETE%N" + api_doc : STRING = "URI:/order METHOD: POST%N URI:/order/{orderid} METHOD: GET, HEAD, PUT, DELETE%N" +feature -- Access -feature -- Documentation - - mapping_documentation (m: WSF_ROUTER_MAPPING; a_request_methods: detachable WSF_REQUEST_METHODS): WSF_ROUTER_MAPPING_DOCUMENTATION + is_chunking (req: WSF_REQUEST): BOOLEAN + -- Will the response to `req' using chunked transfer encoding? do - create Result.make (m) - if a_request_methods /= Void then - if a_request_methods.has_method_post then - Result.add_description ("URI:/order METHOD: POST") - elseif - a_request_methods.has_method_get - or a_request_methods.has_method_put - or a_request_methods.has_method_delete - then - Result.add_description ("URI:/order/{orderid} METHOD: GET, PUT, DELETE") + -- No. + end + + includes_response_entity (req: WSF_REQUEST): BOOLEAN + -- Does the response to `req' include an entity? + -- Method will be DELETE, POST, PUT or an extension method. + do + Result := False + -- At present, there is no support for this except for DELETE. + end + + conneg (req: WSF_REQUEST): CONNEG_SERVER_SIDE + -- Content negotiatior for all requests + once + create Result.make ({HTTP_MIME_TYPES}.application_json, "en", "UTF-8", "identity") + end + + mime_types_supported (req: WSF_REQUEST): LIST [STRING] + -- All values for Accept header that `Current' can serve + do + create {ARRAYED_LIST [STRING]} Result.make_from_array (<<{HTTP_MIME_TYPES}.application_json>>) + Result.compare_objects + end + + languages_supported (req: WSF_REQUEST): LIST [STRING] + -- All values for Accept-Language header that `Current' can serve + do + create {ARRAYED_LIST [STRING]} Result.make_from_array (<<"en">>) + Result.compare_objects + end + + charsets_supported (req: WSF_REQUEST): LIST [STRING] + -- All values for Accept-Charset header that `Current' can serve + do + create {ARRAYED_LIST [STRING]} Result.make_from_array (<<"UTF-8">>) + Result.compare_objects + end + + encodings_supported (req: WSF_REQUEST): LIST [STRING] + -- All values for Accept-Encoding header that `Current' can serve + do + create {ARRAYED_LIST [STRING]} Result.make_from_array (<<"identity">>) + Result.compare_objects + end + + previous_location (req: WSF_REQUEST): LIST [URI] + -- Previous location(s) for resource named by `req'; + do + -- precondition is never met but we need a non-void Result to satisfy the compiler in Void-safe mode: + create {LINKED_LIST [URI]} Result.make + end + + 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 (365 * 1440 = 1 year) to indicate never expires. + do + -- All our responses are considered stale. + 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. + do + -- definitely not! + 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. + do + create {ARRAYED_LIST [READABLE_STRING_8]} Result.make (0) + 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. + do + create {ARRAYED_LIST [READABLE_STRING_8]} Result.make (0) + 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. + do + Result := True + -- since it's commercial data. + 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. + local + l_id: STRING + l_etag_util: ETAG_UTILS + do + l_id := order_id_from_request (req) + if db_access.orders.has_key (l_id) then + check attached db_access.orders.item (l_id) as l_order then + -- postcondition of `has_key' + create l_etag_util + Result := a_etag.same_string (l_etag_util.md5_digest (l_order.out).as_string_32) end end end + etag (req: WSF_REQUEST; a_media_type, a_language_type, a_character_type, a_compression_type: READABLE_STRING_8): detachable READABLE_STRING_8 + -- Optional Etag for `req' in the requested variant + local + l_etag_utils: ETAG_UTILS + do + create l_etag_utils + if attached {ORDER} req.execution_variable ("ORDER") as l_order then + Result := l_etag_utils.md5_digest (l_order.out) + end + end + + modified_since (req: WSF_REQUEST; a_date_time: DATE_TIME): BOOLEAN + -- Has resource requested in `req' been modified since `a_date_time' (UTC)? + do + -- We don't track this information. It is safe to always say yes. + Result := True + end + +feature -- Measurement + + content_length (req: WSF_REQUEST): NATURAL + -- Length of entity-body of the response to `req' + do + check attached {READABLE_STRING_8} req.execution_variable ("GENERATED_CONTENT") as l_response then + -- postcondition generated_content_set_for_get_head of `ensure_content_available' + -- We only call this for GET/HEAD in this example. + Result := l_response.count.as_natural_32 + end + 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? + do + -- No. + end + +feature -- Status report + + finished (req: WSF_REQUEST): BOOLEAN + -- Has the last chunk been generated for `req'? + do + -- precondition is never met + end + +feature -- Execution + + 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. + -- We also put the order into `req.execution_variable ("ORDER")' for GET or HEAD responses. + local + l_id: STRING + do + if req.is_post_request_method then + a_helper.set_resource_exists + -- because only /order is defined to this handler for POST + else + -- the request is of the form /order/{orderid} + l_id := order_id_from_request (req) + if db_access.orders.has_key (l_id) then + a_helper.set_resource_exists + if req.is_get_head_request_method then + check attached db_access.orders.item (l_id) as l_order then + -- postcondition `item_if_found' of `has_key' + req.set_execution_variable ("ORDER", l_order) + end + end + end + end + ensure then + order_saved_only_for_get_head: req.is_get_head_request_method = + attached {ORDER} req.execution_variable ("ORDER") + end + +feature -- GET/HEAD content + + ensure_content_available (req: WSF_REQUEST; + a_media_type, a_language_type, a_character_type, a_compression_type: READABLE_STRING_8) + -- Commence generation of response text (entity-body). + -- 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. + -- We save the text in `req.execution_variable ("GENERATED_CONTENT")' + do + check attached {ORDER} req.execution_variable ("ORDER") as l_order then + -- precondition get_or_head and postcondition order_saved_only_for_get_head of `check_resource_exists' and + if attached {JSON_VALUE} json.value (l_order) as jv then + req.set_execution_variable ("GENERATED_CONTENT", jv.representation) + else + req.set_execution_variable ("GENERATED_CONTENT", "") + end + end + ensure then + generated_content_set_for_get_head: req.is_get_head_request_method implies + attached {READABLE_STRING_8} req.execution_variable ("GENERATED_CONTENT") + end + + content (req: WSF_REQUEST; a_media_type, a_language_type, a_character_type, a_compression_type: READABLE_STRING_8): READABLE_STRING_8 + -- Non-chunked entity body in response to `req'; + -- We only call this for GET/HEAD in this example. + do + check attached {READABLE_STRING_8} req.execution_variable ("GENERATED_CONTENT") as l_response then + -- postcondition generated_content_set_for_get_head of `ensure_content_available' + Result := l_response + end + end + + next_chunk (req: WSF_REQUEST; a_media_type, a_language_type, a_character_type, a_compression_type: READABLE_STRING_8): 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. + do + -- precondition `is_chunking' is never met, but we need a dummy `Result' + -- to satisfy the compiler in void-safe mode + Result := ["", Void] + end + + generate_next_chunk (req: WSF_REQUEST; a_media_type, a_language_type, a_character_type, a_compression_type: READABLE_STRING_8) + -- Prepare next chunk (including optional chunk extension) of entity body in response to `req'. + -- This is not called for the first chunk. + do + -- precondition `is_chunking' is never met + end + +feature -- DELETE + + delete (req: WSF_REQUEST) + -- Delete resource named in `req' or set an error on `req.error_handler'. + local + l_id: STRING + do + l_id := order_id_from_request (req) + if db_access.orders.has_key (l_id) then + if is_valid_to_delete (l_id) then + delete_order (l_id) + else + req.error_handler.add_custom_error ({HTTP_STATUS_CODE}.method_not_allowed, "DELETE not valid", + "There is conflict while trying to delete the order, the order could not be deleted in the current state") + end + else + req.error_handler.add_custom_error ({HTTP_STATUS_CODE}.not_found, "DELETE not valid", + "There is no such order to delete") + end + end + + deleted (req: WSF_REQUEST): BOOLEAN + -- Has resource named by `req' been deleted? + do + if not req.error_handler.has_error then + Result := True + end + end + + delete_queued (req: WSF_REQUEST): BOOLEAN + -- Has resource named by `req' been queued for deletion? + do + -- No + end + + +feature -- PUT/POST + + is_entity_too_large (req: WSF_REQUEST): BOOLEAN + -- Is the entity stored in `req.execution_variable ("REQUEST_ENTITY")' too large for the application? + do + -- No. We don't care for this example. + end + + check_content_headers (req: WSF_REQUEST) + -- Check we can support all content headers on request entity. + -- Set `req.execution_variable ("CONTENT_CHECK_CODE")' to {NATURAL} zero if OK, or 415 or 501 if not. + do + -- We don't bother for this example. Note that this is equivalent to setting zero. + 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. + do + -- We don't support creating a new resource with PUT. But this can't happen + -- with our router mappings, so we don't bother to set a 500 response. + 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. + do + if attached {ORDER} req.execution_variable ("EXTRACTED_ORDER") as l_order then + save_order (l_order) + compute_response_post (req, res, l_order) + else + handle_bad_request_response ("Not a valid order", req, res) + end + end + + check_conflict (req: WSF_REQUEST; res: WSF_RESPONSE) + -- Check we can support all content headers on request entity. + -- Set `req.execution_variable ("CONFLICT_CHECK_CODE")' to {NATURAL} zero if OK, or 409 if not. + -- In the latter case, write the full error response to `res'. + do + if attached {ORDER} req.execution_variable ("EXTRACTED_ORDER") as l_order then + if not is_valid_to_update (l_order) then + req.set_execution_variable ("CONFLICT_CHECK_CODE", {NATURAL} 409) + --| FIXME: Here we need to define the Allow methods + handle_resource_conflict_response (l_order.out +"%N There is conflict while trying to update the order, the order could not be update in the current state", req, res) + end + else + req.set_execution_variable ("CONFLICT_CHECK_CODE", {NATURAL} 409) + --| FIXME: Here we need to define the Allow methods + --| This ought to be a 500, as if attached should probably be check attached. But as yet I lack a proof. TODO. + handle_resource_conflict_response ("There is conflict while trying to update the order, the order could not be update in the current state", req, res) + 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 ("REQUEST_ENTITY")'. + -- Set `req.execution_variable ("REQUEST_CHECK_CODE")' to {NATURAL} zero if OK, or 400 if not. + -- In the latter case, write the full error response to `res'. + local + l_order: detachable ORDER + l_id: STRING + do + if attached {READABLE_STRING_8} req.execution_variable ("REQUEST_ENTITY") as l_request then + l_order := extract_order_request (l_request) + if req.is_put_request_method then + l_id := order_id_from_request (req) + if l_order /= Void and then db_access.orders.has_key (l_id) then + l_order.set_id (l_id) + req.set_execution_variable ("REQUEST_CHECK_CODE", {NATURAL} 0) + req.set_execution_variable ("EXTRACTED_ORDER", l_order) + else + req.set_execution_variable ("REQUEST_CHECK_CODE", {NATURAL} 400) + handle_bad_request_response (l_request +"%N is not a valid ORDER, maybe the order does not exist in the system", req, res) + end + else + req.set_execution_variable ("REQUEST_CHECK_CODE", {NATURAL} 0) + req.set_execution_variable ("EXTRACTED_ORDER", l_order) + end + else + req.set_execution_variable ("REQUEST_CHECK_CODE", {NATURAL} 400) + handle_bad_request_response ("Request is not a valid ORDER", req, res) + 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. + do + if attached {ORDER} req.execution_variable ("EXTRACTED_ORDER") as l_order then + update_order (l_order) + compute_response_put (req, res, l_order) + else + handle_internal_server_error (res) + end + end + feature -- HTTP Methods - do_get (req: WSF_REQUEST; res: WSF_RESPONSE) - -- - local - id: STRING - do - if attached req.orig_path_info as orig_path then - id := get_order_id_from_path (orig_path) - if attached retrieve_order (id) as l_order then - if is_conditional_get (req, l_order) then - handle_resource_not_modified_response ("The resource" + orig_path + "does not change", req, res) - else - compute_response_get (req, res, l_order) - end - else - handle_resource_not_found_response ("The following resource" + orig_path + " is not found ", req, res) - end - end - end - - is_conditional_get (req : WSF_REQUEST; l_order : ORDER) : BOOLEAN - -- Check if If-None-Match is present and then if there is a representation that has that etag - -- if the representation hasn't changed, we return TRUE - -- then the response is a 304 with no entity body returned. - local - etag_util : ETAG_UTILS - do - if attached req.meta_string_variable ("HTTP_IF_NONE_MATCH") as if_none_match then - create etag_util - if if_none_match.same_string (etag_util.md5_digest (l_order.out).as_string_32) then - Result := True - end - end - end - - compute_response_get (req: WSF_REQUEST; res: WSF_RESPONSE; l_order: ORDER) - local - h: HTTP_HEADER - l_msg : STRING - etag_utils : ETAG_UTILS - do - create h.make - create etag_utils - h.put_content_type_application_json - if attached {JSON_VALUE} json.value (l_order) as jv then - l_msg := jv.representation - h.put_content_length (l_msg.count) - if attached req.request_time as time then - h.add_header ("Date:" + time.formatted_out ("ddd,[0]dd mmm yyyy [0]hh:[0]mi:[0]ss.ff2") + " GMT") - end - h.add_header ("etag:" + etag_utils.md5_digest (l_order.out)) - res.set_status_code ({HTTP_STATUS_CODE}.ok) - res.put_header_text (h.string) - res.put_string (l_msg) - end - end - - do_put (req: WSF_REQUEST; res: WSF_RESPONSE) - -- Updating a resource with PUT - -- A successful PUT request will not create a new resource, instead it will - -- change the state of the resource identified by the current uri. - -- If success we response with 200 and the updated order. - -- 404 if the order is not found - -- 400 in case of a bad request - -- 500 internal server error - -- If the request is a Conditional PUT, and it does not mat we response - -- 415, precondition failed. - local - l_put: STRING - l_order : detachable ORDER - id : STRING - do - if attached req.orig_path_info as orig_path then - id := get_order_id_from_path (orig_path) - l_put := retrieve_data (req) - l_order := extract_order_request(l_put) - if l_order /= Void and then db_access.orders.has_key (id) then - l_order.set_id (id) - if is_valid_to_update(l_order) then - if is_conditional_put (req, l_order) then - update_order( l_order) - compute_response_put (req, res, l_order) - else - handle_precondition_fail_response ("", req, res) - end - else - --| FIXME: Here we need to define the Allow methods - handle_resource_conflict_response (l_put +"%N There is conflict while trying to update the order, the order could not be update in the current state", req, res) - end - else - handle_bad_request_response (l_put +"%N is not a valid ORDER, maybe the order does not exist in the system", req, res) - end - end - end - - is_conditional_put (req : WSF_REQUEST; order : ORDER) : BOOLEAN - -- Check if If-Match is present and then if there is a representation that has that etag - -- if the representation hasn't changed, we return TRUE - local - etag_util : ETAG_UTILS - do - if attached retrieve_order (order.id) as l_order then - if attached req.meta_string_variable ("HTTP_IF_MATCH") as if_match then - create etag_util - if if_match.same_string (etag_util.md5_digest (l_order.out).as_string_32) then - Result := True - end - else - Result := True - end - end - end - - compute_response_put (req: WSF_REQUEST; res: WSF_RESPONSE; l_order : ORDER) local h: HTTP_HEADER @@ -198,67 +431,6 @@ feature -- HTTP Methods end end - - do_delete (req: WSF_REQUEST; res: WSF_RESPONSE) - -- Here we use DELETE to cancel an order, if that order is in state where - -- it can still be canceled. - -- 200 if is ok - -- 404 Resource not found - -- 405 if consumer and service's view of the resouce state is inconsisent - -- 500 if we have an internal server error - local - id: STRING - do - if attached req.orig_path_info as orig_path then - id := get_order_id_from_path (orig_path) - if db_access.orders.has_key (id) then - if is_valid_to_delete (id) then - delete_order( id) - compute_response_delete (req, res) - else - --| FIXME: Here we need to define the Allow methods - handle_method_not_allowed_response (orig_path + "%N There is conflict while trying to delete the order, the order could not be deleted in the current state", req, res) - end - else - handle_resource_not_found_response (orig_path + " not found in this server", req, res) - end - end - end - - compute_response_delete (req: WSF_REQUEST; res: WSF_RESPONSE) - local - h : HTTP_HEADER - do - create h.make - h.put_content_type_application_json - if attached req.request_time as time then - h.put_utc_date (time) - end - res.set_status_code ({HTTP_STATUS_CODE}.no_content) - res.put_header_text (h.string) - end - - do_post (req: WSF_REQUEST; res: WSF_RESPONSE) - -- Here the convention is the following. - -- POST is used for creation and the server determines the URI - -- of the created resource. - -- If the request post is SUCCESS, the server will create the order and will response with - -- HTTP_RESPONSE 201 CREATED, the Location header will contains the newly created order's URI - -- if the request post is not SUCCESS, the server will response with - -- HTTP_RESPONSE 400 BAD REQUEST, the client send a bad request - -- HTTP_RESPONSE 500 INTERNAL_SERVER_ERROR, when the server can deliver the request - local - l_post: STRING - do - l_post := retrieve_data (req) - if attached extract_order_request (l_post) as l_order then - save_order (l_order) - compute_response_post (req, res, l_order) - else - handle_bad_request_response (l_post +"%N is not a valid ORDER", req, res) - end - end - compute_response_post (req: WSF_REQUEST; res: WSF_RESPONSE; l_order : ORDER) local h: HTTP_HEADER @@ -290,9 +462,16 @@ feature -- HTTP Methods feature {NONE} -- URI helper methods - get_order_id_from_path (a_path: READABLE_STRING_32) : STRING + order_id_from_request (req: WSF_REQUEST): STRING + -- Value of "orderid" template URI variable in `req' + require + req_attached: req /= Void do - Result := a_path.split ('/').at (3) + if attached {WSF_VALUE} req.path_parameter ("orderid") as l_value then + Result := l_value.as_string.value.as_string_8 + else + Result := "" + end end feature {NONE} -- Implementation Repository Layer diff --git a/examples/restbucksCRUD/src/restbucks_server.e b/examples/restbucksCRUD/src/restbucks_server.e index 81008346..588b2057 100644 --- a/examples/restbucksCRUD/src/restbucks_server.e +++ b/examples/restbucksCRUD/src/restbucks_server.e @@ -37,7 +37,7 @@ feature {NONE} -- Initialization order_handler: ORDER_HANDLER doc: WSF_ROUTER_SELF_DOCUMENTATION_HANDLER do - create order_handler + create order_handler.make_with_router (router) router.handle_with_request_methods ("/order", order_handler, router.methods_POST) router.handle_with_request_methods ("/order/{orderid}", order_handler, router.methods_GET + router.methods_DELETE + router.methods_PUT) create doc.make_hidden (router) From 69da6c6d065f307b4609606b5c4f7cc8e8e491a9 Mon Sep 17 00:00:00 2001 From: Colin Adams Date: Wed, 7 Aug 2013 11:03:22 +0100 Subject: [PATCH 15/68] Fixes as picked up by code review --- examples/restbucksCRUD/restbucks-safe.ecf | 2 +- .../src/resource/order_handler.e | 47 ++-- .../network/protocol/http/src/http_header.e | 10 +- .../server/wsf/router/wsf_caching_policy.e | 28 +- library/server/wsf/router/wsf_delete_helper.e | 7 +- library/server/wsf/router/wsf_get_helper.e | 41 +++ library/server/wsf/router/wsf_method_helper.e | 263 +++++++++++++----- .../wsf/router/wsf_method_helper_factory.e | 14 +- library/server/wsf/router/wsf_post_helper.e | 45 +-- library/server/wsf/router/wsf_put_helper.e | 30 +- .../server/wsf/router/wsf_skeleton_handler.e | 23 +- library/server/wsf/src/wsf_request.e | 2 + 12 files changed, 363 insertions(+), 149 deletions(-) diff --git a/examples/restbucksCRUD/restbucks-safe.ecf b/examples/restbucksCRUD/restbucks-safe.ecf index ca7c3f18..08715841 100644 --- a/examples/restbucksCRUD/restbucks-safe.ecf +++ b/examples/restbucksCRUD/restbucks-safe.ecf @@ -18,7 +18,7 @@ - + diff --git a/examples/restbucksCRUD/src/resource/order_handler.e b/examples/restbucksCRUD/src/resource/order_handler.e index f1db9d1a..499e5695 100644 --- a/examples/restbucksCRUD/src/resource/order_handler.e +++ b/examples/restbucksCRUD/src/resource/order_handler.e @@ -9,7 +9,7 @@ class ORDER_HANDLER inherit WSF_SKELETON_HANDLER - + SHARED_DATABASE_API SHARED_EJSON @@ -28,9 +28,14 @@ create make_with_router -feature -- API DOC +feature -- Documentation - api_doc : STRING = "URI:/order METHOD: POST%N URI:/order/{orderid} METHOD: GET, HEAD, PUT, DELETE%N" + description: READABLE_STRING_GENERAL + -- General description for self-generated documentation; + -- The specific URI templates supported will be described automatically + do + Result := "Create, Read, Update or Delete an ORDER." + end feature -- Access @@ -93,7 +98,7 @@ feature -- Access -- 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 (365 * 1440 = 1 year) to indicate never expires. + -- Return Never_expires to indicate never expires. do -- All our responses are considered stale. end @@ -135,7 +140,7 @@ feature -- Access -- If `a_strong' then the strong comparison function must be used. local l_id: STRING - l_etag_util: ETAG_UTILS + l_etag_util: ETAG_UTILS do l_id := order_id_from_request (req) if db_access.orders.has_key (l_id) then @@ -155,8 +160,8 @@ feature -- Access create l_etag_utils if attached {ORDER} req.execution_variable ("ORDER") as l_order then Result := l_etag_utils.md5_digest (l_order.out) - end - end + end + end modified_since (req: WSF_REQUEST; a_date_time: DATE_TIME): BOOLEAN -- Has resource requested in `req' been modified since `a_date_time' (UTC)? @@ -164,7 +169,7 @@ feature -- Access -- We don't track this information. It is safe to always say yes. Result := True end - + feature -- Measurement content_length (req: WSF_REQUEST): NATURAL @@ -220,7 +225,7 @@ feature -- Execution order_saved_only_for_get_head: req.is_get_head_request_method = attached {ORDER} req.execution_variable ("ORDER") end - + feature -- GET/HEAD content ensure_content_available (req: WSF_REQUEST; @@ -254,7 +259,7 @@ feature -- GET/HEAD content Result := l_response end end - + next_chunk (req: WSF_REQUEST; a_media_type, a_language_type, a_character_type, a_compression_type: READABLE_STRING_8): 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. @@ -270,7 +275,7 @@ feature -- GET/HEAD content do -- precondition `is_chunking' is never met end - + feature -- DELETE delete (req: WSF_REQUEST) @@ -315,7 +320,7 @@ feature -- PUT/POST -- No. We don't care for this example. end - check_content_headers (req: WSF_REQUEST) + check_content_headers (req: WSF_REQUEST) -- Check we can support all content headers on request entity. -- Set `req.execution_variable ("CONTENT_CHECK_CODE")' to {NATURAL} zero if OK, or 415 or 501 if not. do @@ -334,14 +339,14 @@ feature -- PUT/POST -- 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. do - if attached {ORDER} req.execution_variable ("EXTRACTED_ORDER") as l_order then + if attached {ORDER} req.execution_variable ("EXTRACTED_ORDER") as l_order then save_order (l_order) compute_response_post (req, res, l_order) else handle_bad_request_response ("Not a valid order", req, res) end end - + check_conflict (req: WSF_REQUEST; res: WSF_RESPONSE) -- Check we can support all content headers on request entity. -- Set `req.execution_variable ("CONFLICT_CHECK_CODE")' to {NATURAL} zero if OK, or 409 if not. @@ -350,17 +355,15 @@ feature -- PUT/POST if attached {ORDER} req.execution_variable ("EXTRACTED_ORDER") as l_order then if not is_valid_to_update (l_order) then req.set_execution_variable ("CONFLICT_CHECK_CODE", {NATURAL} 409) - --| FIXME: Here we need to define the Allow methods handle_resource_conflict_response (l_order.out +"%N There is conflict while trying to update the order, the order could not be update in the current state", req, res) end else req.set_execution_variable ("CONFLICT_CHECK_CODE", {NATURAL} 409) - --| FIXME: Here we need to define the Allow methods - --| This ought to be a 500, as if attached should probably be check attached. But as yet I lack a proof. TODO. + --| This ought to be a 500, as if attached should probably be check attached. But as yet I lack a proof. handle_resource_conflict_response ("There is conflict while trying to update the order, the order could not be update in the current state", req, res) 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 ("REQUEST_ENTITY")'. @@ -384,7 +387,7 @@ feature -- PUT/POST end else req.set_execution_variable ("REQUEST_CHECK_CODE", {NATURAL} 0) - req.set_execution_variable ("EXTRACTED_ORDER", l_order) + req.set_execution_variable ("EXTRACTED_ORDER", l_order) end else req.set_execution_variable ("REQUEST_CHECK_CODE", {NATURAL} 400) @@ -396,14 +399,14 @@ feature -- PUT/POST -- Perform the update requested in `req'. -- Write a response to `res' with a code of 204 or 500. do - if attached {ORDER} req.execution_variable ("EXTRACTED_ORDER") as l_order then + if attached {ORDER} req.execution_variable ("EXTRACTED_ORDER") as l_order then update_order (l_order) compute_response_put (req, res, l_order) else handle_internal_server_error (res) end end - + feature -- HTTP Methods compute_response_put (req: WSF_REQUEST; res: WSF_RESPONSE; l_order : ORDER) @@ -553,6 +556,6 @@ feature {NONE} -- Implementation Repository Layer end 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)" end diff --git a/library/network/protocol/http/src/http_header.e b/library/network/protocol/http/src/http_header.e index 72e6fd37..d70ac062 100644 --- a/library/network/protocol/http/src/http_header.e +++ b/library/network/protocol/http/src/http_header.e @@ -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,10 +511,10 @@ feature -- Content related header put_header_key_value ({HTTP_HEADER_NAMES}.header_content_transfer_encoding, a_mechanism) end - put_content_language (a_enc: READABLE_STRING_8) - -- Put "Content-Language" header of value `a_enc'. + 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_enc) + put_header_key_value ({HTTP_HEADER_NAMES}.header_content_language, a_lang) end put_content_encoding (a_enc: READABLE_STRING_8) diff --git a/library/server/wsf/router/wsf_caching_policy.e b/library/server/wsf/router/wsf_caching_policy.e index 4c780b19..bc9a4687 100644 --- a/library/server/wsf/router/wsf_caching_policy.e +++ b/library/server/wsf/router/wsf_caching_policy.e @@ -10,16 +10,20 @@ 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 + 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 (365 * 1440 = 1 year) to indicate never expires. + -- Return `Never_expires' to indicate never expires. require req_attached: req /= Void deferred ensure - not_more_than_1_year: Result <= (365 * 1440).as_natural_32 + not_more_than_1_year: Result <= Never_expires end shared_age (req: WSF_REQUEST): NATURAL @@ -27,13 +31,13 @@ feature -- Access -- 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 (365 * 1440 = 1 year) to indicate never expires. + -- Return `Never_expires' to indicate never expires. require req_attached: req /= Void do Result := age (req) ensure - not_more_than_1_year: Result <= (365 * 1440).as_natural_32 + not_more_than_1_year: Result <= Never_expires end http_1_0_age (req: WSF_REQUEST): NATURAL @@ -41,7 +45,7 @@ feature -- Access -- 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 (365 * 1440 = 1 year) to indicate never expires. Note this will + -- 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 @@ -49,7 +53,7 @@ feature -- Access do Result := age (req) ensure - not_more_than_1_year: Result <= (365 * 1440).as_natural_32 + not_more_than_1_year: Result <= Never_expires end is_freely_cacheable (req: WSF_REQUEST): BOOLEAN @@ -81,7 +85,6 @@ feature -- Access -- 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? @@ -92,7 +95,6 @@ feature -- Access -- 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. @@ -122,4 +124,14 @@ feature -- Access 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 diff --git a/library/server/wsf/router/wsf_delete_helper.e b/library/server/wsf/router/wsf_delete_helper.e index 6bc0e646..a8633d4b 100644 --- a/library/server/wsf/router/wsf_delete_helper.e +++ b/library/server/wsf/router/wsf_delete_helper.e @@ -11,15 +11,10 @@ class WSF_DELETE_HELPER inherit WSF_METHOD_HELPER - rename - send_get_response as handle_delete - redefine - handle_delete - end feature {NONE} -- Implementation - handle_delete (req: WSF_REQUEST; res: WSF_RESPONSE; a_handler: WSF_SKELETON_HANDLER; a_header: HTTP_HEADER; + send_response (req: WSF_REQUEST; res: WSF_RESPONSE; a_handler: WSF_SKELETON_HANDLER; a_header: HTTP_HEADER; a_media_type, a_language_type, a_character_type, a_compression_type: detachable READABLE_STRING_8; a_new_resource: BOOLEAN) -- Write response to deletion of resource named by `req' into `res' in accordance with `a_media_type' etc. local diff --git a/library/server/wsf/router/wsf_get_helper.e b/library/server/wsf/router/wsf_get_helper.e index e20dd50b..6085b979 100644 --- a/library/server/wsf/router/wsf_get_helper.e +++ b/library/server/wsf/router/wsf_get_helper.e @@ -12,4 +12,45 @@ 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_media_type, a_language_type, a_character_type, a_compression_type: detachable READABLE_STRING_8; a_new_resource: BOOLEAN) + -- Write response to `req' into `res' in accordance with `a_media_type' etc. + local + l_chunked, l_ok: BOOLEAN + l_dt: STRING + do + a_handler.ensure_content_available (req, a_media_type, a_language_type, a_character_type, a_compression_type) + 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) + if l_ok then + res.set_status_code ({HTTP_STATUS_CODE}.ok) + else + -- TODO - req.error_handler.has_error = True + --handle_internal_server_error (a_handler.last_error (req), req, res) + end + if attached a_handler.etag (req, a_media_type, a_language_type, a_character_type, a_compression_type) 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, a_media_type, a_language_type, a_character_type, a_compression_type) + else + res.put_string (a_handler.content (req, a_media_type, a_language_type, a_character_type, a_compression_type)) + end + end + end + end diff --git a/library/server/wsf/router/wsf_method_helper.e b/library/server/wsf/router/wsf_method_helper.e index 6b23ebec..a37e9c3b 100644 --- a/library/server/wsf/router/wsf_method_helper.e +++ b/library/server/wsf/router/wsf_method_helper.e @@ -2,12 +2,19 @@ note description: "[ Policy-driven helpers to implement a method. - This implementation is suitable for GET and HEAD. + ]" date: "$Date$" revision: "$Revision$" -class WSF_METHOD_HELPER +deferred class WSF_METHOD_HELPER + +inherit + + HTTP_STATUS_CODE_MESSAGES + + SHARED_HTML_ENCODER + export {NONE} all end feature -- Access @@ -42,17 +49,17 @@ feature -- Basic operations if a_handler.resource_previously_existed (req) then if a_handler.resource_moved_permanently (req) then l_locs := a_handler.previous_location (req) - -- TODO 301 Moved permanently response + 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) - -- TODO := 302 Found response + handle_redirection_error (req, res, l_locs, {HTTP_STATUS_CODE}.found) 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}.gone) - res.put_header_text (h.string) + res.put_header_lines (h) end else create h.make @@ -60,7 +67,7 @@ feature -- Basic operations h.put_current_date h.put_content_length (0) res.set_status_code ({HTTP_STATUS_CODE}.not_found) - res.put_header_text (h.string) + res.put_header_lines (h) end end @@ -78,7 +85,7 @@ feature -- Basic operations l_date: HTTP_DATE do if attached req.http_if_match as l_if_match then - -- TODO - also if-range when we add support for range requests + -- 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 @@ -105,14 +112,14 @@ feature -- Basic operations 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) + 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) + handle_not_modified (req, res, a_handler) l_failed := True end end @@ -121,13 +128,13 @@ feature -- Basic operations end if not l_failed then handle_content_negotiation (req, res, a_handler, False) - end + end end end end feature {NONE} -- Implementation - + handle_content_negotiation (req: WSF_REQUEST; res: WSF_RESPONSE; a_handler: WSF_SKELETON_HANDLER; a_new_resource: BOOLEAN) -- Negotiate acceptable content for, then write, response requested by `req' into `res'. @@ -163,7 +170,7 @@ feature {NONE} -- Implementation h.add_header_key_value ({HTTP_HEADER_NAMES}.header_vary, l_media_variant) end if not l_media.is_acceptable then - handle_not_accepted ("None of the requested ContentTypes were acceptable", req, res) + 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) @@ -171,7 +178,7 @@ feature {NONE} -- Implementation h.add_header_key_value ({HTTP_HEADER_NAMES}.header_vary, l_lang_variant) end if not l_lang.is_acceptable then - handle_not_accepted ("None of the requested languages were acceptable", req, res) + 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) @@ -182,7 +189,7 @@ feature {NONE} -- Implementation h.add_header_key_value ({HTTP_HEADER_NAMES}.header_vary, l_charset_variant) end if not l_charset.is_acceptable then - handle_not_accepted ("None of the requested character encodings were acceptable", req, res) + 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 and attached l_charset.character_type as l_character_type then h.put_content_type (l_media_type + "; charset=" + l_character_type) @@ -193,13 +200,13 @@ feature {NONE} -- Implementation h.add_header_key_value ({HTTP_HEADER_NAMES}.header_vary, l_encoding_variant) end if not l_encoding.is_acceptable then - handle_not_accepted ("None of the requested transfer encodings were acceptable", req, res) + 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) end -- We do not support multiple choices, so - send_get_response (req, res, a_handler, h, + send_response (req, res, a_handler, h, l_media.media_type, l_lang.language_type, l_charset.character_type, l_encoding.compression_type, a_new_resource) end end @@ -207,52 +214,19 @@ feature {NONE} -- Implementation end end - send_get_response (req: WSF_REQUEST; res: WSF_RESPONSE; a_handler: WSF_SKELETON_HANDLER; a_header: HTTP_HEADER; + send_response (req: WSF_REQUEST; res: WSF_RESPONSE; a_handler: WSF_SKELETON_HANDLER; a_header: HTTP_HEADER; a_media_type, a_language_type, a_character_type, a_compression_type: detachable READABLE_STRING_8; a_new_resource: BOOLEAN) -- Write response to `req' into `res' in accordance with `a_media_type' etc. require req_attached: req /= Void res_attached: res /= Void a_handler_attached: a_handler /= Void - a_header_attached: a_header /= Void + a_header_attached: a_header /= Void a_media_type_attached: a_media_type /= Void a_language_type_attached: a_language_type /= Void a_character_type_attached: a_character_type /= Void a_compression_type_attached: a_compression_type /= Void - local - l_chunked, l_ok: BOOLEAN - l_dt: STRING - do - a_handler.ensure_content_available (req, a_media_type, a_language_type, a_character_type, a_compression_type) - 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) - if l_ok then - res.set_status_code ({HTTP_STATUS_CODE}.ok) - else - -- TODO - req.error_handler.has_error = True - --handle_internal_server_error (a_handler.last_error (req), req, res) - end - if attached a_handler.etag (req, a_media_type, a_language_type, a_character_type, a_compression_type) 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, a_media_type, a_language_type, a_character_type, a_compression_type) - else - res.put_string (a_handler.content (req, a_media_type, a_language_type, a_character_type, a_compression_type)) - end - end + deferred end send_chunked_response (req: WSF_REQUEST; res: WSF_RESPONSE; a_handler: WSF_SKELETON_HANDLER; a_header: HTTP_HEADER; @@ -265,7 +239,7 @@ feature {NONE} -- Implementation a_media_type_attached: a_media_type /= Void a_language_type_attached: a_language_type /= Void a_character_type_attached: a_character_type /= Void - a_compression_type_attached: a_compression_type /= Void + a_compression_type_attached: a_compression_type /= Void local l_chunk: TUPLE [a_chunk: READABLE_STRING_8; a_extension: detachable READABLE_STRING_8] do @@ -290,11 +264,11 @@ feature {NONE} -- Implementation end end if a_handler.finished (req) then - -- TODO - support for trailers + -- 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 @@ -343,7 +317,7 @@ feature {NONE} -- Implementation 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 end append_field_name (a_field_names: STRING; a_fields: LIST [READABLE_STRING_8]) @@ -362,36 +336,139 @@ feature {NONE} -- Implementation feature -- Basic operations - handle_not_accepted (a_message: READABLE_STRING_8; req: WSF_REQUEST; res: WSF_RESPONSE) - -- Write a Not Accepted response to `res'. + 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 := "" + s.append ("") + s.append (html_encoder.encoded_string (req.request_uri)) + s.append ("Error " + a_status_code.out + " (" + l_msg + ")") + s.append ("%N") + s.append ("[ + + + + ]") + s.append ("
Error " + a_status_code.out + " (" + l_msg + ")
") + s.append ("
") + s.append ("
") + s.append ("
") + s.append ("
") + s.append ("
") + s.append ("The current location for this resource is here") + s.append ("Error " + a_status_code.out + " (" + l_msg + ")
") + s.append ("
Error " + a_status_code.out + " (" + l_msg + "): " + html_encoder.encoded_string (req.request_uri) + "
") + s.append ("
") + s.append ("%N") + s.append ("%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 - -- TODO: flag this if it gets to code review. + 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) - -- Write a Precondition Failed response to `res'. + 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 - -- TODO: flag this if it gets to code review. Why not just handle_precondition_failed? + handle_not_modified (req, res, a_handler) end - handle_not_modified (req: WSF_REQUEST; res: WSF_RESPONSE) - -- Write a Precondition Failed response to `res'. + handle_not_modified (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 + local + h: HTTP_HEADER do - -- TODO: flag this if it gets to code review. Why not just handle_precondition_failed? + create h.make + h.put_content_type_text_plain + h.put_content_length (0) + if attached a_handler.etag (req, "", "", "", "") as l_etag then + -- FIXME: We aren't strictly conformant here, as we have not conducted content negotiation yet, + -- so we might not get an identical etag as for a successful (200 OK) request. + -- So we should conduct content negotiation at this point (and if not acceptable, we don't include an etag). + -- Add add any Vary header that might result. + -- Also, when we add support for the Content-Location header in responses, we need to send that here too. + 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'. + -- Write a Precondition Failed response for `req' to `res'. require req_attached: req /= Void res_attached: res /= Void @@ -403,7 +480,59 @@ feature -- Basic operations h.put_current_date h.put_content_length (0) res.set_status_code ({HTTP_STATUS_CODE}.precondition_failed) - res.put_header_text (h.string) + 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 diff --git a/library/server/wsf/router/wsf_method_helper_factory.e b/library/server/wsf/router/wsf_method_helper_factory.e index c41a6e3d..48f773c4 100644 --- a/library/server/wsf/router/wsf_method_helper_factory.e +++ b/library/server/wsf/router/wsf_method_helper_factory.e @@ -18,7 +18,7 @@ feature -- Factory 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 Result + 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 @@ -27,5 +27,15 @@ feature -- Factory 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 diff --git a/library/server/wsf/router/wsf_post_helper.e b/library/server/wsf/router/wsf_post_helper.e index b05a40a7..41c54a64 100644 --- a/library/server/wsf/router/wsf_post_helper.e +++ b/library/server/wsf/router/wsf_post_helper.e @@ -11,11 +11,8 @@ class WSF_POST_HELPER inherit WSF_METHOD_HELPER - rename - send_get_response as do_post redefine - execute_new_resource, - do_post + execute_new_resource end feature -- Basic operations @@ -23,48 +20,54 @@ 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'. - local - l_etags: LIST [READABLE_STRING_32] - l_failed: BOOLEAN do if a_handler.allow_post_to_missing_resource (req) then handle_content_negotiation (req, res, a_handler, True) else - -- TODO 404 Not Found + res.send (create {WSF_NOT_FOUND_RESPONSE}.make(req)) end end - + feature {NONE} -- Implementation - do_post (req: WSF_REQUEST; res: WSF_RESPONSE; a_handler: WSF_SKELETON_HANDLER; a_header: HTTP_HEADER; + send_response (req: WSF_REQUEST; res: WSF_RESPONSE; a_handler: WSF_SKELETON_HANDLER; a_header: HTTP_HEADER; a_media_type, a_language_type, a_character_type, a_compression_type: detachable READABLE_STRING_8; 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) - if a_handler.content_check_code (req) /= 0 then - -- TODO - 415 or 501 + 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?) - -- TODO: more support, such as includes_response_entity + -- FIXME: more support, such as includes_response_entity end end end end - handle_request_entity_too_large (req: WSF_REQUEST; res: WSF_RESPONSE; a_handler: WSF_SKELETON_HANDLER) - -- TODO. - require - -- TODO - do - -- Need to check if condition is temporary. - 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 diff --git a/library/server/wsf/router/wsf_put_helper.e b/library/server/wsf/router/wsf_put_helper.e index 67f123b8..6adedbbf 100644 --- a/library/server/wsf/router/wsf_put_helper.e +++ b/library/server/wsf/router/wsf_put_helper.e @@ -11,11 +11,8 @@ class WSF_PUT_HELPER inherit WSF_METHOD_HELPER - rename - send_get_response as do_put redefine - execute_new_resource, - do_put + execute_new_resource end feature -- Basic operations @@ -25,7 +22,7 @@ feature -- Basic operations -- Policy routines are available in `a_handler'. do if a_handler.treat_as_moved_permanently (req) then - -- TODO 301 Moved permanently response (single location) + handle_redirection_error (req, res, a_handler.previous_location (req), {HTTP_STATUS_CODE}.moved_permanently) else handle_content_negotiation (req, res, a_handler, True) end @@ -34,17 +31,24 @@ feature -- Basic operations feature {NONE} -- Implementation - do_put (req: WSF_REQUEST; res: WSF_RESPONSE; a_handler: WSF_SKELETON_HANDLER; a_header: HTTP_HEADER; + send_response (req: WSF_REQUEST; res: WSF_RESPONSE; a_handler: WSF_SKELETON_HANDLER; a_header: HTTP_HEADER; a_media_type, a_language_type, a_character_type, a_compression_type: detachable READABLE_STRING_8; 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) - if a_handler.content_check_code (req) /= 0 then - -- TODO - 415 or 501 + 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 @@ -56,20 +60,12 @@ feature {NONE} -- Implementation if a_handler.conflict_check_code (req) = 0 then a_handler.update_resource (req, res) -- 204 or 500 (add support for this?) - -- TODO: more support, such as includes_response_entity + -- FIXME: more support, such as includes_response_entity end end end end end end - - handle_request_entity_too_large (req: WSF_REQUEST; res: WSF_RESPONSE; a_handler: WSF_SKELETON_HANDLER) - -- TODO. - require - -- TODO - do - -- Need to check if condition is temporary. - end end diff --git a/library/server/wsf/router/wsf_skeleton_handler.e b/library/server/wsf/router/wsf_skeleton_handler.e index aa8d01c3..cd7bc6d0 100644 --- a/library/server/wsf/router/wsf_skeleton_handler.e +++ b/library/server/wsf/router/wsf_skeleton_handler.e @@ -24,6 +24,8 @@ inherit WSF_METHOD_HELPER_FACTORY + WSF_SELF_DOCUMENTED_HANDLER + feature -- Access is_chunking (req: WSF_REQUEST): BOOLEAN @@ -42,7 +44,7 @@ feature -- Access end conneg (req: WSF_REQUEST): CONNEG_SERVER_SIDE - -- Content negotiatior for `req'; + -- Content negotiation for `req'; -- This would normally be a once object, ignoring `req'. require req_attached: req /= Void @@ -161,6 +163,25 @@ feature -- Status report 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) diff --git a/library/server/wsf/src/wsf_request.e b/library/server/wsf/src/wsf_request.e index 572d5a28..61a098e9 100644 --- a/library/server/wsf/src/wsf_request.e +++ b/library/server/wsf/src/wsf_request.e @@ -376,6 +376,8 @@ feature -- Helper end end Result := l_accept.has_substring (a_content_type) + else + Result := True end end From 78ff0134c75ca91bbd34ec02bec54eee3dd54e5d Mon Sep 17 00:00:00 2001 From: colin-adams Date: Wed, 7 Aug 2013 03:27:42 -0700 Subject: [PATCH 16/68] Created Using the policy driven framework (markdown) --- Using-the-policy-driven-framework.md | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 Using-the-policy-driven-framework.md diff --git a/Using-the-policy-driven-framework.md b/Using-the-policy-driven-framework.md new file mode 100644 index 00000000..a0e5836d --- /dev/null +++ b/Using-the-policy-driven-framework.md @@ -0,0 +1,9 @@ +# Using the policy driven framework + +## Introduction + +The aim of the policy-driven framework is to allow authors of web-servers to concentrate on the business logic (e.g., in the case of a GET request, generating the content), without having to worry about the details of the HTTP protocol (such as headers and response codes). However, there are so many possibilities in the HTTP protocol, that it is impossible to correctly guess what to do in all cases. Therefore the author has to supply policy decisions to the framework, in areas such as caching decisions. These are implemented as a set of deferred classes for which the author needs to provide effective implementations. + +## Mapping the URI space + +The authors first task is to decide which URIs the server will respond to (we do this using [URI templates](http://tools.ietf.org/html/rfc6570) ) and which methods are supported for each template.This is done in the class that that defines the service (which is often the root class for the application). This class must be a descendant of WSF_ROUTED_SKELETON_SERVICE. \ No newline at end of file From 7dd36014cc987575b1c964efd80effa42b05e9cb Mon Sep 17 00:00:00 2001 From: colin-adams Date: Wed, 7 Aug 2013 03:56:52 -0700 Subject: [PATCH 17/68] Updated Using the policy driven framework (markdown) --- Using-the-policy-driven-framework.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Using-the-policy-driven-framework.md b/Using-the-policy-driven-framework.md index a0e5836d..077ddfd2 100644 --- a/Using-the-policy-driven-framework.md +++ b/Using-the-policy-driven-framework.md @@ -4,6 +4,12 @@ The aim of the policy-driven framework is to allow authors of web-servers to concentrate on the business logic (e.g., in the case of a GET request, generating the content), without having to worry about the details of the HTTP protocol (such as headers and response codes). However, there are so many possibilities in the HTTP protocol, that it is impossible to correctly guess what to do in all cases. Therefore the author has to supply policy decisions to the framework, in areas such as caching decisions. These are implemented as a set of deferred classes for which the author needs to provide effective implementations. +We aim to provide unconditional compliance [See HTTP/1.1 specification](http://www.w3.org/Protocols/rfc2616/rfc2616-sec1.html#sec1) for you. + ## Mapping the URI space -The authors first task is to decide which URIs the server will respond to (we do this using [URI templates](http://tools.ietf.org/html/rfc6570) ) and which methods are supported for each template.This is done in the class that that defines the service (which is often the root class for the application). This class must be a descendant of WSF_ROUTED_SKELETON_SERVICE. \ No newline at end of file +The authors first task is to decide which URIs the server will respond to (we do this using [URI templates](http://tools.ietf.org/html/rfc6570) ) and which methods are supported for each template.This is done in the class that that defines the service (which is often the root class for the application). This class must be a descendant of WSF_ROUTED_SKELETON_SERVICE. Throughout this tutorial, we will refer to the restbucksCRUD example application, which can be found in the EWF distribution in the examples directory. It's root class, RESTBUCKS_SERVER, inherits from WSF_ROUTED_SKELETON_SERVICE, as well as WSF_DEFAULT_SERVICE. The latter class means that you must specify in the ECF which connector you will use by default.This means you can easily change connectors just by changing the ECF and recompiling. + +### Declaring your URI templates + +In order to map your URI space to handlers (which you will write), you need to implement the routine setup_router. You can see in the example that the ORDER_HANDLER handler is associated with two URI templates. The URI /order is associated with the POST method (only). Any requests to /order with the GET method (or any other method) will result in an automatically generated compliant response being sent on your behalf to the client. The other principle methods (you get compliant responses to the HEAD method for free whenever you allow the GET method) are associated with the URI template /order/{orderid}. Here, orderid is a template variable. It's value for any given request is provided to your application as {WSF_REQUEST}.path_parameter ("orderid"). If the client passes a URI of /order/21, then you will see the value 21. If the client passes /order/fred, you will see the value fred. But if the client passes /order/21/new, he will see a compliant error response generated by the framework. \ No newline at end of file From b55f3636515c6db7dc889b3ef36426d90d2816a6 Mon Sep 17 00:00:00 2001 From: colin-adams Date: Wed, 7 Aug 2013 05:30:02 -0700 Subject: [PATCH 18/68] Updated Using the policy driven framework (markdown) --- Using-the-policy-driven-framework.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/Using-the-policy-driven-framework.md b/Using-the-policy-driven-framework.md index 077ddfd2..115804e0 100644 --- a/Using-the-policy-driven-framework.md +++ b/Using-the-policy-driven-framework.md @@ -12,4 +12,13 @@ The authors first task is to decide which URIs the server will respond to (we do ### Declaring your URI templates -In order to map your URI space to handlers (which you will write), you need to implement the routine setup_router. You can see in the example that the ORDER_HANDLER handler is associated with two URI templates. The URI /order is associated with the POST method (only). Any requests to /order with the GET method (or any other method) will result in an automatically generated compliant response being sent on your behalf to the client. The other principle methods (you get compliant responses to the HEAD method for free whenever you allow the GET method) are associated with the URI template /order/{orderid}. Here, orderid is a template variable. It's value for any given request is provided to your application as {WSF_REQUEST}.path_parameter ("orderid"). If the client passes a URI of /order/21, then you will see the value 21. If the client passes /order/fred, you will see the value fred. But if the client passes /order/21/new, he will see a compliant error response generated by the framework. \ No newline at end of file +In order to map your URI space to handlers (which you will write), you need to implement the routine setup_router. You can see in the example that the ORDER_HANDLER handler is associated with two URI templates. The URI /order is associated with the POST method (only). Any requests to /order with the GET method (or any other method) will result in an automatically generated compliant response being sent on your behalf to the client. The other principle methods (you get compliant responses to the HEAD method for free whenever you allow the GET method) are associated with the URI template /order/{orderid}. Here, orderid is a template variable. It's value for any given request is provided to your application as {WSF_REQUEST}.path_parameter ("orderid"). If the client passes a URI of /order/21, then you will see the value 21. If the client passes /order/fred, you will see the value fred. But if the client passes /order/21/new, he will see a compliant error response generated by the framework. + +## Declaring your policy in responding to OPTIONS + +WSF_ROUTED_SKELETON_SERVICE inherits from WSF_SYSTEM_OPTIONS_ACCESS_POLICY. This policy declares that the framework will provide a compliant default response to OPTIONS * requests. If you prefer to not respond to OPTIONS * requests (and I am doubtful if it is fully compliant to make that choice), then you can redefine +is_system_options_forbidden. + +## Declaring your policy on requiring use of a proxy server + +WSF_ROUTED_SKELETON_SERVICE also inherits from WSF_PROXY_USE_POLICY. This determines if the server will require clients to use a proxy server. By default, it will do so for HTTP/1.0 clients. This is a sensible default, as the framework assumes an HTTP/1.1 client throughout. If you are sure that you will only ever have HTTP/1.1 clients, then you can instead inherit from WSF_NO_PROXY_POLICY, as RESTBUCKS_SERVER does. If not, then you need to implement proxy_server. From 0c4a410ac0d118a9596ec0dcb948959a3db89d8a Mon Sep 17 00:00:00 2001 From: colin-adams Date: Wed, 7 Aug 2013 05:51:11 -0700 Subject: [PATCH 19/68] Created Writing the handlers (markdown) --- Writing-the-handlers.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 Writing-the-handlers.md diff --git a/Writing-the-handlers.md b/Writing-the-handlers.md new file mode 100644 index 00000000..b59fbd38 --- /dev/null +++ b/Writing-the-handlers.md @@ -0,0 +1,3 @@ +# Writing the handlers + +Now you have to implement each handler. You need to inherit from WSF_SKELETON_HANDLER (as ORDER_HANDLER does). This involves implementing a lot of deferred routines. There are other routines for which default implementations are provided, which you might want to override. \ No newline at end of file From 84c3039806c5ae74b0856e7f9fc0ddc40b47d507 Mon Sep 17 00:00:00 2001 From: colin-adams Date: Wed, 7 Aug 2013 05:51:54 -0700 Subject: [PATCH 20/68] Updated Using the policy driven framework (markdown) --- Using-the-policy-driven-framework.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Using-the-policy-driven-framework.md b/Using-the-policy-driven-framework.md index 115804e0..2999ec9a 100644 --- a/Using-the-policy-driven-framework.md +++ b/Using-the-policy-driven-framework.md @@ -22,3 +22,5 @@ is_system_options_forbidden. ## Declaring your policy on requiring use of a proxy server WSF_ROUTED_SKELETON_SERVICE also inherits from WSF_PROXY_USE_POLICY. This determines if the server will require clients to use a proxy server. By default, it will do so for HTTP/1.0 clients. This is a sensible default, as the framework assumes an HTTP/1.1 client throughout. If you are sure that you will only ever have HTTP/1.1 clients, then you can instead inherit from WSF_NO_PROXY_POLICY, as RESTBUCKS_SERVER does. If not, then you need to implement proxy_server. + +Next you have to [write your handler(s)](./Writing-the-handlers) \ No newline at end of file From bf0a8e8efbb7a0f3dd83b52c79645f2ff01fefb8 Mon Sep 17 00:00:00 2001 From: colin-adams Date: Wed, 7 Aug 2013 05:57:49 -0700 Subject: [PATCH 21/68] Updated Writing the handlers (markdown) --- Writing-the-handlers.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Writing-the-handlers.md b/Writing-the-handlers.md index b59fbd38..d1fd9324 100644 --- a/Writing-the-handlers.md +++ b/Writing-the-handlers.md @@ -1,3 +1,11 @@ # Writing the handlers -Now you have to implement each handler. You need to inherit from WSF_SKELETON_HANDLER (as ORDER_HANDLER does). This involves implementing a lot of deferred routines. There are other routines for which default implementations are provided, which you might want to override. \ No newline at end of file +Now you have to implement each handler. You need to inherit from WSF_SKELETON_HANDLER (as ORDER_HANDLER does). This involves implementing a lot of deferred routines. There are other routines for which default implementations are provided, which you might want to override. This applies to both routines defined in this class, and those declared in the three policy classes from which it inherits. + +## Implementing the routines declared directly in WSF_SKELETON_HANDLER + +TODO + +## Implementing the policies + +TODO \ No newline at end of file From f3849679e896771fa5c531c2957925c5fdf6d301 Mon Sep 17 00:00:00 2001 From: colin-adams Date: Wed, 7 Aug 2013 06:00:37 -0700 Subject: [PATCH 22/68] Updated Writing the handlers (markdown) --- Writing-the-handlers.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Writing-the-handlers.md b/Writing-the-handlers.md index d1fd9324..61be5110 100644 --- a/Writing-the-handlers.md +++ b/Writing-the-handlers.md @@ -8,4 +8,8 @@ TODO ## Implementing the policies +* WSF_OPTIONS_POLICY +* WSF_PREVIOUS_POLICY +* WSF_CACHING_POLICY + TODO \ No newline at end of file From 45fd51b4b5de38887850ca28b4bc9842ab7496b2 Mon Sep 17 00:00:00 2001 From: colin-adams Date: Wed, 7 Aug 2013 06:02:24 -0700 Subject: [PATCH 23/68] Updated Writing the handlers (markdown) --- Writing-the-handlers.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Writing-the-handlers.md b/Writing-the-handlers.md index 61be5110..081c4a59 100644 --- a/Writing-the-handlers.md +++ b/Writing-the-handlers.md @@ -8,7 +8,7 @@ TODO ## Implementing the policies -* WSF_OPTIONS_POLICY +* [WSF_OPTIONS_POLICY](edit this) * WSF_PREVIOUS_POLICY * WSF_CACHING_POLICY From 7bc09bda8fb84d7d359b842246819b9b28e0ade5 Mon Sep 17 00:00:00 2001 From: colin-adams Date: Wed, 7 Aug 2013 06:02:42 -0700 Subject: [PATCH 24/68] Created WSF_OPTIONS_POLICY (markdown) --- WSF_OPTIONS_POLICY.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 WSF_OPTIONS_POLICY.md diff --git a/WSF_OPTIONS_POLICY.md b/WSF_OPTIONS_POLICY.md new file mode 100644 index 00000000..30404ce4 --- /dev/null +++ b/WSF_OPTIONS_POLICY.md @@ -0,0 +1 @@ +TODO \ No newline at end of file From 33d523e5bf2beb0ef52323df780a1fbe9745581e Mon Sep 17 00:00:00 2001 From: colin-adams Date: Wed, 7 Aug 2013 06:03:13 -0700 Subject: [PATCH 25/68] Updated Writing the handlers (markdown) --- Writing-the-handlers.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Writing-the-handlers.md b/Writing-the-handlers.md index 081c4a59..598a05fc 100644 --- a/Writing-the-handlers.md +++ b/Writing-the-handlers.md @@ -8,7 +8,7 @@ TODO ## Implementing the policies -* [WSF_OPTIONS_POLICY](edit this) +* [WSF_OPTIONS_POLICY](./WSF_OPTIONS_POLICY) * WSF_PREVIOUS_POLICY * WSF_CACHING_POLICY From 090e294f1060cd5f3db82fdd231f75e86058bbfa Mon Sep 17 00:00:00 2001 From: colin-adams Date: Wed, 7 Aug 2013 06:06:15 -0700 Subject: [PATCH 26/68] Updated WSF_OPTIONS_POLICY (markdown) --- WSF_OPTIONS_POLICY.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/WSF_OPTIONS_POLICY.md b/WSF_OPTIONS_POLICY.md index 30404ce4..2056c430 100644 --- a/WSF_OPTIONS_POLICY.md +++ b/WSF_OPTIONS_POLICY.md @@ -1 +1,3 @@ -TODO \ No newline at end of file +# Implementing routines in WSF_OPTIONS_POLICY + +This class provides a default response to OPTIONS requests other than OPTIONS *. So you don't have to do anything. The default response just includes the mandatory Allow headers for all the methods that are allowed for the request URI. if you want to include a body text, or additional header, then you should redefine this routine. \ No newline at end of file From 7815557f840514613ac37514e2349169989d788f Mon Sep 17 00:00:00 2001 From: colin-adams Date: Wed, 7 Aug 2013 06:14:24 -0700 Subject: [PATCH 27/68] Updated Writing the handlers (markdown) --- Writing-the-handlers.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Writing-the-handlers.md b/Writing-the-handlers.md index 598a05fc..2021f1ae 100644 --- a/Writing-the-handlers.md +++ b/Writing-the-handlers.md @@ -9,7 +9,7 @@ TODO ## Implementing the policies * [WSF_OPTIONS_POLICY](./WSF_OPTIONS_POLICY) -* WSF_PREVIOUS_POLICY +* [WSF_PREVIOUS_POLICY](./WSF_PREVIOUS_POLICY) * WSF_CACHING_POLICY TODO \ No newline at end of file From c261f02c8472d6217a9a17fe7a2a96ba9651b6f6 Mon Sep 17 00:00:00 2001 From: colin-adams Date: Wed, 7 Aug 2013 06:24:45 -0700 Subject: [PATCH 28/68] Created Wsf previous policy (markdown) --- Wsf-previous-policy.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 Wsf-previous-policy.md diff --git a/Wsf-previous-policy.md b/Wsf-previous-policy.md new file mode 100644 index 00000000..37c965c5 --- /dev/null +++ b/Wsf-previous-policy.md @@ -0,0 +1,20 @@ +# Implementing WSF_PREVIOUS_POLICY + +This class provides routines which enable the programmer to encode knowledge about resources that have moved (either temporarily, or permanently), or have been permanently removed. There are four routines, but only one is actually deferred. + +## resource_previously_existed + +By default, this routine says that currently doesn't exist, never has existed. You need to redefine this routine to return True for any URIs that you want to indicate used to exist, and either no longer do so, or have moved to another location. + +## resource_moved_permanently + +If you have indicated that a resource previously existed, then it may have moved permanently, temporarily, or just ceased to exist. In the first case, you need to redefine this routine to return True for such a resource. +## resource_moved_temporarily + +If you have indicated that a resource previously existed, then it may have moved permanently, temporarily, or just ceased to exist. In the second case, you need to redefine this routine to return True for such a resource. + +## previous_location + +You need to implement this routine. It should provide the locations where a resource has moved to. There must be at least one such location. If more than one is provided, then the first one is considered primary. + +If the preconditions for this routine are never met (as is the case by default), then just return an empty list. \ No newline at end of file From 3b517d3c53d1bd3836f84e25305c3c5c343d6d6f Mon Sep 17 00:00:00 2001 From: colin-adams Date: Wed, 7 Aug 2013 06:25:31 -0700 Subject: [PATCH 29/68] Updated Writing the handlers (markdown) --- Writing-the-handlers.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Writing-the-handlers.md b/Writing-the-handlers.md index 2021f1ae..256296e2 100644 --- a/Writing-the-handlers.md +++ b/Writing-the-handlers.md @@ -10,6 +10,4 @@ TODO * [WSF_OPTIONS_POLICY](./WSF_OPTIONS_POLICY) * [WSF_PREVIOUS_POLICY](./WSF_PREVIOUS_POLICY) -* WSF_CACHING_POLICY - -TODO \ No newline at end of file +* [WSF_CACHING_POLICY](./WSF_CACHING_POLICY) From 259815467c86758253c6ec3de84592910ea607dd Mon Sep 17 00:00:00 2001 From: colin-adams Date: Wed, 7 Aug 2013 07:05:24 -0700 Subject: [PATCH 30/68] Created Wsf caching policy (markdown) --- Wsf-caching-policy.md | 52 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 Wsf-caching-policy.md diff --git a/Wsf-caching-policy.md b/Wsf-caching-policy.md new file mode 100644 index 00000000..e2c5d6ae --- /dev/null +++ b/Wsf-caching-policy.md @@ -0,0 +1,52 @@ +# Implementing WSF_CACHING_POLICY + +This class contains a large number of routines, some of which have sensible defaults. + +## age + +This is used to generate a **Cache-Control: max-age** header. It says how old the response can before a cache will consider it stale (and therefore will need to revalidate with the server). Common values are zero (always consider it stale) and Never_expires (never always mean up to one year) and 1440 (one day). + +## shared_age + +This defaults to the same as age, so you only have to redefine it if you want a different value. If different from age, then we generate a **Cache-Control: s-max-age** header. This applies to shared caches only. Otherwise it has the same meaning as age. This overrides the value specified in age for shared caches. + +## http_1_0_age + +This generates an **Expires** header, and has the same meaning as age, but is understood by HTTP/1.0 caches. By default it has the same value as age. You only need to redefine this if you want to treat HTTP/1.0 caches differently (you might not trust them so well, so you might want to return 0 here). + +## is_freely_cacheable + +This routine says whether a shared cache can use this response for all client. If True, then it generates a **Cache-Control: public** header. If your data is at all sensitive, then you want to return False here. + +## is_transformable + +Non-transparent proxies are allowed to make some modifications to headers. If your application relies on this _not_ happening, then you want to return False here. This is the default, so you don't have to do anything. This means a **Cache-Control: no-transform** header will be generated. +But most applications can return True. + +## must_revalidate + +Some clients request that their private cache ignores server expiry times (and so freely reuse stale responses). If you want to force revalidation anyway in such circumstances, then redefine to return True. In which case, we generate a **Cache-Control: must-revalidate** header. + +## must_proxy_revalidate + +This is the same as must_revalidate, but only applies to shared caches that are configured to serve stale responses. If you redefine to return True, then we generate a **Cache-Control: proxy-revalidate** header. + +## private_headers + +This is used to indicate that parts (or all) of a response are considered private to a single user, and should not be freely served from a shared cache. You must implement this routine. Your choices are: + +1. Return Void. None of the response is considered private. +1. Return and empty list. All of the response is considered private. +1. Return a list of header names. + +If you don't return Void, then a **Cache-Control: private** header will be generated. + +## non_cacheable_headers + +This is similar to private_headers, and you have the same three choices. the difference is that it is a list of headers (or the whole response) that will not be sent from a cache without revalidation. + +If you don't return Void, then a **Cache-Control: no-cache** header will be generated. + +## is_sensitive + +Is the response to be considered of a sensitive nature? If so, then it will not be archived from a cache. We generate a **Cache-Control: no-store** header. \ No newline at end of file From ce04737d46b5eab52818125c2444d42328ed82c8 Mon Sep 17 00:00:00 2001 From: colin-adams Date: Wed, 7 Aug 2013 07:18:35 -0700 Subject: [PATCH 31/68] Updated Writing the handlers (markdown) --- Writing-the-handlers.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/Writing-the-handlers.md b/Writing-the-handlers.md index 256296e2..5834c9ed 100644 --- a/Writing-the-handlers.md +++ b/Writing-the-handlers.md @@ -4,7 +4,17 @@ Now you have to implement each handler. You need to inherit from WSF_SKELETON_HA ## Implementing the routines declared directly in WSF_SKELETON_HANDLER -TODO +### is_chunking + +HTTP/1.1 supports streaming responses (and providing you have configured your server to use a proxy server in WSF_PROXY_USE_POLICY, this framework guarantees you have an HTTP/1.1 client to deal with). It is up to you whether or not you choose to make use of it. If so, then you have to serve the response one chunk at a time (but you could generate it all at once, and slice it up as you go). In this routine you just say whether or not you will be doing this. So the framework n=knows which other routines to call. + +## includes_response_entity + +The response to a DELETE, PUT or POST will include HTTP headers. It may or may not include a body. It is up to you, and this is where you tell the framework. + +## conneg + +[The HTTP/1.1 specification](http://www.w3.org/Protocols/rfc2616/rfc2616-sec12.html#sec12.1) defines server-driven content negotiation. Based on the Accept* headers in the request, we can determine whether we have a format for the response entity that is acceptable to the client. You need to indicate what formats you support. The framework does the rest. Normally you will have the same options for all requests, in which case you can use a once object. ## Implementing the policies From 9395e31c5343b3fee2929e4520537d833c98e8b7 Mon Sep 17 00:00:00 2001 From: colin-adams Date: Wed, 7 Aug 2013 08:50:31 -0700 Subject: [PATCH 32/68] Updated Writing the handlers (markdown) --- Writing-the-handlers.md | 45 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/Writing-the-handlers.md b/Writing-the-handlers.md index 5834c9ed..8f958854 100644 --- a/Writing-the-handlers.md +++ b/Writing-the-handlers.md @@ -16,8 +16,51 @@ The response to a DELETE, PUT or POST will include HTTP headers. It may or may n [The HTTP/1.1 specification](http://www.w3.org/Protocols/rfc2616/rfc2616-sec12.html#sec12.1) defines server-driven content negotiation. Based on the Accept* headers in the request, we can determine whether we have a format for the response entity that is acceptable to the client. You need to indicate what formats you support. The framework does the rest. Normally you will have the same options for all requests, in which case you can use a once object. +## mime_types_supported + +Here you need to indicate which media types you support for responses. One of the entries must be passed to the creation routine for conneg. + +## languages_supported + +Here you need to indicate which languages you support for responses. One of the entries must be passed to the creation routine for conneg. + + +## charsets_supported + +Here you need to indicate which character sets you support for responses. One of the entries must be passed to the creation routine for conneg. + + +## encodings_supported + +Here you need to indicate which compression encodings you support for responses. One of the entries must be passed to the creation routine for conneg. + +## additional_variant_headers + +The framework will write a Vary header if conneg indicates that different formats are supported. This warns caches that they may not be able to use a cached response if the Accept* headers in the request differ. If the author knows that the response may be affected by other request headers in addition to these, then they must be indicated here, so they can be included in a Vary header with the response. + +## predictable_response + +If the response may vary in other ways not predictable from the request headers, then redefine this routine to return True. In that case we will generate a Vary: * header to inform the cache that the response is not necessarily repeatable. + +## matching_etag + +An **ETag** header is a kind of message digest. Clients can use etags to avoid re-fetching responses for unchanged resources, or to avoid updating a resource that may have changed since the client last updated it. +You must implement this routine to test for matches **if and only if** you return non-Void responses for the etag routine. + +## etag + +You are strongly encouraged to return non-Void for this routine. See [Validation Model](http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.3) for more details. + +## modified_since + +You need to implement this. If you do not have information about when a resource was last modified, then return True as a precaution. Of course, you return false for a static resource. + +## treat_as_moved_permanently + +This routine when a PUT request is made to a resource that does not exist. See [PUT](http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.6) in the HTTP/1.1 specification for why you might want to return zero. + ## Implementing the policies * [WSF_OPTIONS_POLICY](./WSF_OPTIONS_POLICY) * [WSF_PREVIOUS_POLICY](./WSF_PREVIOUS_POLICY) -* [WSF_CACHING_POLICY](./WSF_CACHING_POLICY) +* [WSF_CACHING_POLICY](./WSF_CACHING_POLICY) \ No newline at end of file From a552b8fcfa3fd27c82787df896e9811fb3a96633 Mon Sep 17 00:00:00 2001 From: colin-adams Date: Wed, 7 Aug 2013 09:13:56 -0700 Subject: [PATCH 33/68] Updated Writing the handlers (markdown) --- Writing-the-handlers.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/Writing-the-handlers.md b/Writing-the-handlers.md index 8f958854..e82961ec 100644 --- a/Writing-the-handlers.md +++ b/Writing-the-handlers.md @@ -8,54 +8,54 @@ Now you have to implement each handler. You need to inherit from WSF_SKELETON_HA HTTP/1.1 supports streaming responses (and providing you have configured your server to use a proxy server in WSF_PROXY_USE_POLICY, this framework guarantees you have an HTTP/1.1 client to deal with). It is up to you whether or not you choose to make use of it. If so, then you have to serve the response one chunk at a time (but you could generate it all at once, and slice it up as you go). In this routine you just say whether or not you will be doing this. So the framework n=knows which other routines to call. -## includes_response_entity +### includes_response_entity The response to a DELETE, PUT or POST will include HTTP headers. It may or may not include a body. It is up to you, and this is where you tell the framework. -## conneg +### conneg [The HTTP/1.1 specification](http://www.w3.org/Protocols/rfc2616/rfc2616-sec12.html#sec12.1) defines server-driven content negotiation. Based on the Accept* headers in the request, we can determine whether we have a format for the response entity that is acceptable to the client. You need to indicate what formats you support. The framework does the rest. Normally you will have the same options for all requests, in which case you can use a once object. -## mime_types_supported +### mime_types_supported Here you need to indicate which media types you support for responses. One of the entries must be passed to the creation routine for conneg. -## languages_supported +### languages_supported Here you need to indicate which languages you support for responses. One of the entries must be passed to the creation routine for conneg. -## charsets_supported +### charsets_supported Here you need to indicate which character sets you support for responses. One of the entries must be passed to the creation routine for conneg. -## encodings_supported +### encodings_supported Here you need to indicate which compression encodings you support for responses. One of the entries must be passed to the creation routine for conneg. -## additional_variant_headers +### additional_variant_headers The framework will write a Vary header if conneg indicates that different formats are supported. This warns caches that they may not be able to use a cached response if the Accept* headers in the request differ. If the author knows that the response may be affected by other request headers in addition to these, then they must be indicated here, so they can be included in a Vary header with the response. -## predictable_response +### predictable_response If the response may vary in other ways not predictable from the request headers, then redefine this routine to return True. In that case we will generate a Vary: * header to inform the cache that the response is not necessarily repeatable. -## matching_etag +### matching_etag An **ETag** header is a kind of message digest. Clients can use etags to avoid re-fetching responses for unchanged resources, or to avoid updating a resource that may have changed since the client last updated it. You must implement this routine to test for matches **if and only if** you return non-Void responses for the etag routine. -## etag +### etag You are strongly encouraged to return non-Void for this routine. See [Validation Model](http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.3) for more details. -## modified_since +### modified_since You need to implement this. If you do not have information about when a resource was last modified, then return True as a precaution. Of course, you return false for a static resource. -## treat_as_moved_permanently +### treat_as_moved_permanently This routine when a PUT request is made to a resource that does not exist. See [PUT](http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.6) in the HTTP/1.1 specification for why you might want to return zero. From 10caa4c1dfb17ffef608729b3428a7ae5b9f897f Mon Sep 17 00:00:00 2001 From: colin-adams Date: Wed, 7 Aug 2013 09:48:38 -0700 Subject: [PATCH 34/68] Updated Writing the handlers (markdown) --- Writing-the-handlers.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/Writing-the-handlers.md b/Writing-the-handlers.md index e82961ec..461e2a1b 100644 --- a/Writing-the-handlers.md +++ b/Writing-the-handlers.md @@ -7,6 +7,7 @@ Now you have to implement each handler. You need to inherit from WSF_SKELETON_HA ### is_chunking HTTP/1.1 supports streaming responses (and providing you have configured your server to use a proxy server in WSF_PROXY_USE_POLICY, this framework guarantees you have an HTTP/1.1 client to deal with). It is up to you whether or not you choose to make use of it. If so, then you have to serve the response one chunk at a time (but you could generate it all at once, and slice it up as you go). In this routine you just say whether or not you will be doing this. So the framework n=knows which other routines to call. +Currently we only support chunking for GET or HEAD routines. This might change in the future, so if you intend to return True, you should call req.is_get_head_request_method. ### includes_response_entity @@ -59,6 +60,33 @@ You need to implement this. If you do not have information about when a resource This routine when a PUT request is made to a resource that does not exist. See [PUT](http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.6) in the HTTP/1.1 specification for why you might want to return zero. +## allow_post_to_missing_resource + +POST requests are normally made to an existing entity. However it is possible to create new resources using a POST, if the server allows it. This is where you make that decision. + +If you return True, and the resource is created, a 201 Created response will be returned. + +## content_length + +If you are not streaming the result, the the HTTP protocol requires that the length of the entity is known. You need to implement this routine to provide that information. + +## finished + +If you are streaming the response, then you need to tell the framework when the last chunk has been sent. +To implement this routine, you will probably need to call req.set_execution_variable (some-name, True) in ensure_content_avaiable and generate_next_chunk, and call attached {BOOLEAN} req.execution_variable (some-name) in this routine. + +## description + +This is for the automatically generated documentation that the framework will generate in response to a request that you have not mapped into an handler. + +## delete + +This routine is for carrying out a DELETE request to a resource. If it is valid to delete the named resource, then you should either go ahead and do it, or queue a deletion request somewhere (if you do that then you will probably need to call req.set_execution_variable (some-name-or-other, True). Otherwise you should call req.error_handler.add_custom_error to explain why the DELETE could not proceed (you should also do this if the attempt to delete the resource fails). +Of course, if you have not mapped any DELETE requests to the URI space of this handler, then you can just do nothing. + +## delete_queued + +If in the delete routine, you elected to queue the request, then you need to return True here. You will probably need to check the execution variable you set in the delete routine. ## Implementing the policies * [WSF_OPTIONS_POLICY](./WSF_OPTIONS_POLICY) From 7e4f51a7ceb4e0a774bfc2e0a94871ba74dea8bf Mon Sep 17 00:00:00 2001 From: colin-adams Date: Wed, 7 Aug 2013 09:58:42 -0700 Subject: [PATCH 35/68] Updated Writing the handlers (markdown) --- Writing-the-handlers.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/Writing-the-handlers.md b/Writing-the-handlers.md index 461e2a1b..cd09a20d 100644 --- a/Writing-the-handlers.md +++ b/Writing-the-handlers.md @@ -60,33 +60,34 @@ You need to implement this. If you do not have information about when a resource This routine when a PUT request is made to a resource that does not exist. See [PUT](http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.6) in the HTTP/1.1 specification for why you might want to return zero. -## allow_post_to_missing_resource +### allow_post_to_missing_resource POST requests are normally made to an existing entity. However it is possible to create new resources using a POST, if the server allows it. This is where you make that decision. If you return True, and the resource is created, a 201 Created response will be returned. -## content_length +### content_length If you are not streaming the result, the the HTTP protocol requires that the length of the entity is known. You need to implement this routine to provide that information. -## finished +### finished If you are streaming the response, then you need to tell the framework when the last chunk has been sent. To implement this routine, you will probably need to call req.set_execution_variable (some-name, True) in ensure_content_avaiable and generate_next_chunk, and call attached {BOOLEAN} req.execution_variable (some-name) in this routine. -## description +### description This is for the automatically generated documentation that the framework will generate in response to a request that you have not mapped into an handler. -## delete +### delete This routine is for carrying out a DELETE request to a resource. If it is valid to delete the named resource, then you should either go ahead and do it, or queue a deletion request somewhere (if you do that then you will probably need to call req.set_execution_variable (some-name-or-other, True). Otherwise you should call req.error_handler.add_custom_error to explain why the DELETE could not proceed (you should also do this if the attempt to delete the resource fails). Of course, if you have not mapped any DELETE requests to the URI space of this handler, then you can just do nothing. -## delete_queued +### delete_queued If in the delete routine, you elected to queue the request, then you need to return True here. You will probably need to check the execution variable you set in the delete routine. + ## Implementing the policies * [WSF_OPTIONS_POLICY](./WSF_OPTIONS_POLICY) From 2415a57ab08d9eb3c2d85e10d233dcbc7310a73c Mon Sep 17 00:00:00 2001 From: colin-adams Date: Wed, 7 Aug 2013 23:30:49 -0700 Subject: [PATCH 36/68] Updated Writing the handlers (markdown) --- Writing-the-handlers.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Writing-the-handlers.md b/Writing-the-handlers.md index cd09a20d..ce845fd8 100644 --- a/Writing-the-handlers.md +++ b/Writing-the-handlers.md @@ -88,6 +88,10 @@ Of course, if you have not mapped any DELETE requests to the URI space of this h If in the delete routine, you elected to queue the request, then you need to return True here. You will probably need to check the execution variable you set in the delete routine. +### deleted + +If delete_queued returns False, then deleted needs to indicate whether or not the delete succeeded. A default implementation is provided that should be satisfactory. + ## Implementing the policies * [WSF_OPTIONS_POLICY](./WSF_OPTIONS_POLICY) From 3249c377f1204d6710010bf43d5750f58819de90 Mon Sep 17 00:00:00 2001 From: Colin Adams Date: Thu, 8 Aug 2013 07:31:43 +0100 Subject: [PATCH 37/68] made deleted into an effective routine --- examples/restbucksCRUD/src/resource/order_handler.e | 8 -------- library/server/wsf/router/wsf_skeleton_handler.e | 7 ++++++- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/examples/restbucksCRUD/src/resource/order_handler.e b/examples/restbucksCRUD/src/resource/order_handler.e index 499e5695..8a24693f 100644 --- a/examples/restbucksCRUD/src/resource/order_handler.e +++ b/examples/restbucksCRUD/src/resource/order_handler.e @@ -297,14 +297,6 @@ feature -- DELETE end end - deleted (req: WSF_REQUEST): BOOLEAN - -- Has resource named by `req' been deleted? - do - if not req.error_handler.has_error then - Result := True - end - end - delete_queued (req: WSF_REQUEST): BOOLEAN -- Has resource named by `req' been queued for deletion? do diff --git a/library/server/wsf/router/wsf_skeleton_handler.e b/library/server/wsf/router/wsf_skeleton_handler.e index cd7bc6d0..b7ad0b39 100644 --- a/library/server/wsf/router/wsf_skeleton_handler.e +++ b/library/server/wsf/router/wsf_skeleton_handler.e @@ -197,7 +197,12 @@ feature -- DELETE -- Has resource named by `req' been deleted? require req_attached: req /= Void - deferred + 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 From bc976c37b14bd2398e094151660c6dcf9f87c3b4 Mon Sep 17 00:00:00 2001 From: colin-adams Date: Thu, 8 Aug 2013 00:26:56 -0700 Subject: [PATCH 38/68] Updated Writing the handlers (markdown) --- Writing-the-handlers.md | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/Writing-the-handlers.md b/Writing-the-handlers.md index ce845fd8..be116a37 100644 --- a/Writing-the-handlers.md +++ b/Writing-the-handlers.md @@ -88,9 +88,37 @@ Of course, if you have not mapped any DELETE requests to the URI space of this h If in the delete routine, you elected to queue the request, then you need to return True here. You will probably need to check the execution variable you set in the delete routine. -### deleted +### ensure_content_available -If delete_queued returns False, then deleted needs to indicate whether or not the delete succeeded. A default implementation is provided that should be satisfactory. +This routine is called for GET and DELETE (when a entity is provided in the response) processing. It's purpose is to make the text of the entity (body of the response) available for future routines (if is_chunking is true, then only the first chunk needs to be made available, although if you only serve, as opposed to generate, the result in chunks, then you will make the entire entity available here). This is necessary so that we can compute the length before we start to serve the response. You would normally save it in an execution variable on the request object (as ORDER_HANDLER does). Note that this usage of execution variables ensures your routines can successfully cope with simultaneous requests. If you encounter a problem generating the content, then add an error to req.error_handler. + +As well as the request object, we provide the results of content negotiation, so you can generate the entity in the agreed format. If you only support one format (i.e. all of mime_types_supported, charsets_supported, encodings_supported and languages_supported are one-element lists), then you are guaranteed that this is what you are being asked for, and so you can ignore them. + +### content + +When not streaming, this routine provides the entity to the framework (for GET or DELETE). Normally you would just access the execution variable that you set in ensure_content_available. Again, the results of content negotiation are made available, but you probably don't need them at this stage. If you only stream responses (for GET), and if you don't support DELETE, then you don't need to do anything here. + +### generate_next_chunk + +When streaming the response, this routine is called to enable you to generate chunks beyond the first, so that you can incrementally generate the response entity. If you generated the entire response entity in +ensure_content_available, then you do nothing here. Otherwise, you will generate the next chunk, and save it in the same execution variable that you use in ensure_content_available (or add an error to req.error_handler). If you don't support streaming, then you don't need to do anything here. + +### next_chunk + +When streaming the response, the framework calls this routine to provides the contents of each generated chunk. If you generated the entire response entity in ensure_content_available, then you need to slice it in this routine (you will have to keep track of where you are with execution variables). If instead you generate the response incrementally, then your task is much easier - you just access the execution variable saved in ensure_content_available/generate_next_chunk. +As in all these content-serving routines, we provide the results of content negotiation. This might be necessary, for instance, if you were compressing an incrementally generated response (it might be more convenient to do the compression here rather than in both ensure_content_available and generate_next_chunk). + +### read_entity + +This is called for PUT and POST processing, to read the entity provided in the request. A default implementation is provided. This assumes that no decoding (e.g. decompression or character set conversion) is necessary. And it saves it in the execution variable REQUEST_ENTITY. + +Currently the framework provides very little support for PUT and POST requests (so you may well need to redefine this routine). There are several reasons for this: + +1. I personally don't have much experience with PUT and POST. +1. It has taken a long time to develop this framework, and to some extent I was working in the dark (I couldn't check what I was doing until the entire framework was written - it wouldn't even compile before then). +1. The idea for the framework came from a code review process on servers I had written for the company that I work for. I had acquired a lot of knowledge of the HTTP protocol in the process, and some of it showed in the code that I had written. It was thought that it would be a good idea if this knowledge were encapsulated in Eiffel, so other developers would be able to write servers without such knowledge. So this framework has been developed in company time. However, at present, we are only using GET requests. + +Experience with converting the restbucksCRUD example to use the framework, shows that it is certainly possible to do POST and PUT processing with it. But enhancements are needed, especially in the area of decoding the request entity. ## Implementing the policies From e9013e548b56c1e64db7027187b3a8b8f93c862b Mon Sep 17 00:00:00 2001 From: colin-adams Date: Thu, 8 Aug 2013 00:56:14 -0700 Subject: [PATCH 39/68] Updated Writing the handlers (markdown) --- Writing-the-handlers.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/Writing-the-handlers.md b/Writing-the-handlers.md index be116a37..ab456856 100644 --- a/Writing-the-handlers.md +++ b/Writing-the-handlers.md @@ -120,6 +120,34 @@ Currently the framework provides very little support for PUT and POST requests ( Experience with converting the restbucksCRUD example to use the framework, shows that it is certainly possible to do POST and PUT processing with it. But enhancements are needed, especially in the area of decoding the request entity. +### is_entity_too_large + +If your application has limits on the size of entities that it can store, then you implement them here. + +### check_content_headers + +This is called after is_entity_too_large returns False. You are supposed to check the following request headers, and take any appropriate actions (such as setting an error, decompression the entity, or converting it to a different character set): + +* Content-Encoding +* Content-Language +* Content-MD5 +* Content-Range +* Content-Type + +At the moment, your duty is to set the execution variable CONTENT_CHECK_CODE to zero, or an HTTP error status code. A future enhancement of the framework might be to provide more support for this. + +### content_check_code + +This simply accesses the execution variable CONTENT_CHECK_CODE set in check_content_headers. if you want to use some other mechanism, then you can redefine this routine. + +### create_resource + +This routine is called when a PUT request is made with a URI that refers to a resource that does not exist (PUT is normally used for updating an existing resource), and you have already decided to allow this. +In this routine you have the responsibilities of: + +1. Creating the resource using the entity in REQUEST_ENTITY (or some decoded version that you have stored elsewhere). +1. Writing the entire response yourself (as I said before, support for PUT and POST processing is poor at present), including setting the status code of 201 Created or 303 See Other or 500 Internal server error). + ## Implementing the policies * [WSF_OPTIONS_POLICY](./WSF_OPTIONS_POLICY) From 2dac1ff6c9f187a1d21ef8840a127b3937afdd9d Mon Sep 17 00:00:00 2001 From: colin-adams Date: Thu, 8 Aug 2013 01:25:29 -0700 Subject: [PATCH 40/68] Updated Writing the handlers (markdown) --- Writing-the-handlers.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/Writing-the-handlers.md b/Writing-the-handlers.md index ab456856..922ed6cf 100644 --- a/Writing-the-handlers.md +++ b/Writing-the-handlers.md @@ -148,6 +148,30 @@ In this routine you have the responsibilities of: 1. Creating the resource using the entity in REQUEST_ENTITY (or some decoded version that you have stored elsewhere). 1. Writing the entire response yourself (as I said before, support for PUT and POST processing is poor at present), including setting the status code of 201 Created or 303 See Other or 500 Internal server error). +### append_resource + +This routine is called for POST requests on an existing resource (normal usage). + +In this routine you have the responsibilities of: + +1. Storing the entity from REQUEST_ENTITY (or some decoded version that you have stored elsewhere), or whatever other action is appropriate for the semantics of POST requests to this URI. +1. Writing the entire response yourself (as I said before, support for PUT and POST processing is poor at present), including setting the status code of 200 OK, 204 No Content, 303 See Other or 500 Internal server error). + +### check_conflict + +This is called for a normal (updating) PUT request. You have to check to see if the current state of the resource makes updating impossible. If so, then you need to write the entire response with a status code of 409 Conflict, and set the execution variable CONFLICT_CHECK_CODE to 409. +Otherwise you just set the execution variable CONFLICT_CHECK_CODE to 0. + +See [the HTTP/1.1 specification](http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.10) for when you are allowed to use the 409 response, and what to write in the response entity. If this is not appropriate then a 500 Internal server error would be more appropriate (and set CONFLICT_CHECK_CODE to 500 - the framework only tests for non-zero). + +### conflict_check_code + +This is implemented to check CONFLICT_CHECK_CODE from the previous routine. If you choose to use a different mechanism, then you need to redefine this. + +### check_request + +This is called for PUT and POST requests. You need to check that the request entity (available in the execution variable REQUEST_ENTITY) is valid for the semantics of the request URI. You should set the execution variable REQUEST_CHECK_CODE to 0 if it is OK. If not, set it to 400 and write the full response, including a status code of 400 Bad Request. + ## Implementing the policies * [WSF_OPTIONS_POLICY](./WSF_OPTIONS_POLICY) From 9c8a034a041d6277e9570174ed4eb1f843bfce0a Mon Sep 17 00:00:00 2001 From: colin-adams Date: Thu, 8 Aug 2013 01:32:31 -0700 Subject: [PATCH 41/68] Updated Writing the handlers (markdown) --- Writing-the-handlers.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Writing-the-handlers.md b/Writing-the-handlers.md index 922ed6cf..252d6bdf 100644 --- a/Writing-the-handlers.md +++ b/Writing-the-handlers.md @@ -172,6 +172,14 @@ This is implemented to check CONFLICT_CHECK_CODE from the previous routine. If y This is called for PUT and POST requests. You need to check that the request entity (available in the execution variable REQUEST_ENTITY) is valid for the semantics of the request URI. You should set the execution variable REQUEST_CHECK_CODE to 0 if it is OK. If not, set it to 400 and write the full response, including a status code of 400 Bad Request. +### request_check_code + +This routine just checks REQUEST_CHECK_CODE. if you choose to use a different mechanism, then redefine it. + +### update_resource + +This routine is called for a normal (updating) PUT request. You have to update the state of the resource using the entity saved in the execution environment variable REQUEST_ENTITY (or more likely elsewhere - see what ORDER_HANDLER does). Then write the entire response including a status code of 204 No Content or 500 Internal server error. + ## Implementing the policies * [WSF_OPTIONS_POLICY](./WSF_OPTIONS_POLICY) From eade6d584c847ccc27ac5ac7189bcdb558266573 Mon Sep 17 00:00:00 2001 From: Colin Adams Date: Thu, 8 Aug 2013 09:33:21 +0100 Subject: [PATCH 42/68] Errors corrected that were discovered in the course of writing the tutorial --- library/server/wsf/router/wsf_skeleton_handler.e | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/server/wsf/router/wsf_skeleton_handler.e b/library/server/wsf/router/wsf_skeleton_handler.e index b7ad0b39..a69a6296 100644 --- a/library/server/wsf/router/wsf_skeleton_handler.e +++ b/library/server/wsf/router/wsf_skeleton_handler.e @@ -225,7 +225,7 @@ feature -- GET/HEAD content -- are used, then this will also generate the chunk extension for the first chunk. require req_attached: req /= Void - get_or_head: req.is_get_head_request_method + get_or_head_or_delete: req.is_get_head_request_method or req.is_delete_request_method a_media_type_attached: a_media_type /= Void a_language_type_attached: a_language_type /= Void a_character_type_attached: a_character_type /= Void @@ -347,7 +347,7 @@ feature -- PUT/POST end check_conflict (req: WSF_REQUEST; res: WSF_RESPONSE) - -- Check we can support all content headers on request entity. + -- Check to see if updating the resource is problematic due to the current state of the resource. -- Set `req.execution_variable ("CONFLICT_CHECK_CODE")' to {NATURAL} zero if OK, or 409 if not. -- In the latter case, write the full error response to `res'. require From 4c901c31308991c5a701d57ff58e2a798ef5e643 Mon Sep 17 00:00:00 2001 From: Colin Adams Date: Thu, 8 Aug 2013 10:39:46 +0100 Subject: [PATCH 43/68] Implemented remaining error response calls --- library/server/wsf/router/wsf_delete_helper.e | 3 +- library/server/wsf/router/wsf_method_helper.e | 30 +++++++++++++++---- .../server/wsf/router/wsf_skeleton_handler.e | 1 + .../utility/general/error/src/error_handler.e | 10 +++++++ 4 files changed, 37 insertions(+), 7 deletions(-) diff --git a/library/server/wsf/router/wsf_delete_helper.e b/library/server/wsf/router/wsf_delete_helper.e index a8633d4b..77baf03d 100644 --- a/library/server/wsf/router/wsf_delete_helper.e +++ b/library/server/wsf/router/wsf_delete_helper.e @@ -47,8 +47,7 @@ feature {NONE} -- Implementation res.put_header_text (a_header.string) end else - -- TODO - req.error_handler.has_error = True - --handle_internal_server_error (a_handler.last_error (req), req, res) + write_error_response (req, res) end end diff --git a/library/server/wsf/router/wsf_method_helper.e b/library/server/wsf/router/wsf_method_helper.e index a37e9c3b..ca527161 100644 --- a/library/server/wsf/router/wsf_method_helper.e +++ b/library/server/wsf/router/wsf_method_helper.e @@ -248,8 +248,7 @@ feature {NONE} -- Implementation l_chunk := a_handler.next_chunk (req, a_media_type, a_language_type, a_character_type, a_compression_type) res.put_chunk (l_chunk.a_chunk, l_chunk.a_extension) else - -- TODO - req.error_handler.has_error = True - -- handle_internal_server_error (a_handler.last_error (req), req, res) + write_error_response (req, res) end until a_handler.finished (req) or not a_handler.response_ok (req) @@ -259,8 +258,7 @@ feature {NONE} -- Implementation l_chunk := a_handler.next_chunk (req, a_media_type, a_language_type, a_character_type, a_compression_type) res.put_chunk (l_chunk.a_chunk, l_chunk.a_extension) else - -- TODO - req.error_handler.has_error = True - -- handle_internal_server_error (a_handler.last_error (req), req, res) + write_error_response (req, res) end end if a_handler.finished (req) then @@ -334,7 +332,29 @@ feature {NONE} -- Implementation end end -feature -- Basic operations +feature -- Errors + + +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'. diff --git a/library/server/wsf/router/wsf_skeleton_handler.e b/library/server/wsf/router/wsf_skeleton_handler.e index a69a6296..33a71d51 100644 --- a/library/server/wsf/router/wsf_skeleton_handler.e +++ b/library/server/wsf/router/wsf_skeleton_handler.e @@ -464,6 +464,7 @@ feature {NONE} -- Implementation 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 diff --git a/library/utility/general/error/src/error_handler.e b/library/utility/general/error/src/error_handler.e index e6ff72fa..f112a0c5 100644 --- a/library/utility/general/error/src/error_handler.e +++ b/library/utility/general/error/src/error_handler.e @@ -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 From bbbf958d7d10d73901b838c6cd9ed620ac314f2d Mon Sep 17 00:00:00 2001 From: colin-adams Date: Thu, 8 Aug 2013 02:41:27 -0700 Subject: [PATCH 44/68] Updated Using the policy driven framework (markdown) --- Using-the-policy-driven-framework.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Using-the-policy-driven-framework.md b/Using-the-policy-driven-framework.md index 2999ec9a..af97acde 100644 --- a/Using-the-policy-driven-framework.md +++ b/Using-the-policy-driven-framework.md @@ -1,5 +1,7 @@ # Using the policy driven framework +**This describes a new facility that is not yet in the EWF release** + ## Introduction The aim of the policy-driven framework is to allow authors of web-servers to concentrate on the business logic (e.g., in the case of a GET request, generating the content), without having to worry about the details of the HTTP protocol (such as headers and response codes). However, there are so many possibilities in the HTTP protocol, that it is impossible to correctly guess what to do in all cases. Therefore the author has to supply policy decisions to the framework, in areas such as caching decisions. These are implemented as a set of deferred classes for which the author needs to provide effective implementations. From f82456f3526aff7309c1dc87e74ef28c40a8a213 Mon Sep 17 00:00:00 2001 From: Colin Adams Date: Thu, 8 Aug 2013 17:13:38 +0100 Subject: [PATCH 45/68] Found another TODO - write_error_response in GET processing --- library/server/wsf/router/wsf_get_helper.e | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/library/server/wsf/router/wsf_get_helper.e b/library/server/wsf/router/wsf_get_helper.e index 6085b979..7200f6f1 100644 --- a/library/server/wsf/router/wsf_get_helper.e +++ b/library/server/wsf/router/wsf_get_helper.e @@ -37,8 +37,7 @@ feature {NONE} -- Implementation if l_ok then res.set_status_code ({HTTP_STATUS_CODE}.ok) else - -- TODO - req.error_handler.has_error = True - --handle_internal_server_error (a_handler.last_error (req), req, res) + write_error_response (req, res) end if attached a_handler.etag (req, a_media_type, a_language_type, a_character_type, a_compression_type) as l_etag then a_header.put_header_key_value ({HTTP_HEADER_NAMES}.header_etag, l_etag) @@ -52,5 +51,15 @@ feature {NONE} -- Implementation 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 From fe971d07ec6760bce9c7e5214228c39c1c67b05b Mon Sep 17 00:00:00 2001 From: colin-adams Date: Sun, 11 Aug 2013 23:55:50 -0700 Subject: [PATCH 46/68] Updated Using the policy driven framework (markdown) --- Using-the-policy-driven-framework.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Using-the-policy-driven-framework.md b/Using-the-policy-driven-framework.md index af97acde..bf85c9ef 100644 --- a/Using-the-policy-driven-framework.md +++ b/Using-the-policy-driven-framework.md @@ -6,7 +6,7 @@ The aim of the policy-driven framework is to allow authors of web-servers to concentrate on the business logic (e.g., in the case of a GET request, generating the content), without having to worry about the details of the HTTP protocol (such as headers and response codes). However, there are so many possibilities in the HTTP protocol, that it is impossible to correctly guess what to do in all cases. Therefore the author has to supply policy decisions to the framework, in areas such as caching decisions. These are implemented as a set of deferred classes for which the author needs to provide effective implementations. -We aim to provide unconditional compliance [See HTTP/1.1 specification](http://www.w3.org/Protocols/rfc2616/rfc2616-sec1.html#sec1) for you. +We aim to provide unconditional compliance [See HTTP/1.1 specification](http://www.w3.org/Protocols/rfc2616/rfc2616-sec1.html#sec1) for you. Note that byte-ranges are not yet supported. ## Mapping the URI space From 2ed362f5d39ec2278ad3e87fced982ec11b60eb2 Mon Sep 17 00:00:00 2001 From: Colin Adams Date: Mon, 12 Aug 2013 09:27:00 +0100 Subject: [PATCH 47/68] refactored to allow etags to work properly when multiple representations are available --- .../src/resource/order_handler.e | 12 +- library/server/wsf/router/wsf_delete_helper.e | 16 ++- library/server/wsf/router/wsf_get_helper.e | 16 ++- library/server/wsf/router/wsf_method_helper.e | 122 +++++++++++------- library/server/wsf/router/wsf_post_helper.e | 13 +- library/server/wsf/router/wsf_put_helper.e | 9 +- .../server/wsf/router/wsf_skeleton_handler.e | 89 ++++++++----- 7 files changed, 172 insertions(+), 105 deletions(-) diff --git a/examples/restbucksCRUD/src/resource/order_handler.e b/examples/restbucksCRUD/src/resource/order_handler.e index 8a24693f..ece4c63e 100644 --- a/examples/restbucksCRUD/src/resource/order_handler.e +++ b/examples/restbucksCRUD/src/resource/order_handler.e @@ -152,7 +152,7 @@ feature -- Access end end - etag (req: WSF_REQUEST; a_media_type, a_language_type, a_character_type, a_compression_type: READABLE_STRING_8): detachable READABLE_STRING_8 + etag (req: WSF_REQUEST): detachable READABLE_STRING_8 -- Optional Etag for `req' in the requested variant local l_etag_utils: ETAG_UTILS @@ -228,14 +228,14 @@ feature -- Execution feature -- GET/HEAD content - ensure_content_available (req: WSF_REQUEST; - a_media_type, a_language_type, a_character_type, a_compression_type: READABLE_STRING_8) + ensure_content_available (req: WSF_REQUEST) -- Commence generation of response text (entity-body). -- 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. -- We save the text in `req.execution_variable ("GENERATED_CONTENT")' + -- We ignore the results of content negotiation, as there is only one possible combination. do check attached {ORDER} req.execution_variable ("ORDER") as l_order then -- precondition get_or_head and postcondition order_saved_only_for_get_head of `check_resource_exists' and @@ -250,7 +250,7 @@ feature -- GET/HEAD content attached {READABLE_STRING_8} req.execution_variable ("GENERATED_CONTENT") end - content (req: WSF_REQUEST; a_media_type, a_language_type, a_character_type, a_compression_type: READABLE_STRING_8): READABLE_STRING_8 + content (req: WSF_REQUEST): READABLE_STRING_8 -- Non-chunked entity body in response to `req'; -- We only call this for GET/HEAD in this example. do @@ -260,7 +260,7 @@ feature -- GET/HEAD content end end - next_chunk (req: WSF_REQUEST; a_media_type, a_language_type, a_character_type, a_compression_type: READABLE_STRING_8): TUPLE [a_check: READABLE_STRING_8; a_extension: detachable READABLE_STRING_8] + next_chunk (req: WSF_REQUEST): TUPLE [a_chunk: 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. do @@ -269,7 +269,7 @@ feature -- GET/HEAD content Result := ["", Void] end - generate_next_chunk (req: WSF_REQUEST; a_media_type, a_language_type, a_character_type, a_compression_type: READABLE_STRING_8) + 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. do diff --git a/library/server/wsf/router/wsf_delete_helper.e b/library/server/wsf/router/wsf_delete_helper.e index 77baf03d..751e0adf 100644 --- a/library/server/wsf/router/wsf_delete_helper.e +++ b/library/server/wsf/router/wsf_delete_helper.e @@ -14,15 +14,19 @@ inherit feature {NONE} -- Implementation - send_response (req: WSF_REQUEST; res: WSF_RESPONSE; a_handler: WSF_SKELETON_HANDLER; a_header: HTTP_HEADER; - a_media_type, a_language_type, a_character_type, a_compression_type: detachable READABLE_STRING_8; a_new_resource: BOOLEAN) - -- Write response to deletion of resource named by `req' into `res' in accordance with `a_media_type' etc. + 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 do a_handler.delete (req) if a_handler.includes_response_entity (req) then - a_handler.ensure_content_available (req, a_media_type, a_language_type, a_character_type, a_compression_type) + a_handler.ensure_content_available (req) a_header.put_content_length (a_handler.content_length (req).as_integer_32) -- we don't bother supporting chunked responses for DELETE. else @@ -36,12 +40,12 @@ feature {NONE} -- Implementation 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, a_media_type, a_language_type, a_character_type, a_compression_type)) + 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, a_media_type, a_language_type, a_character_type, a_compression_type)) + res.put_string (a_handler.content (req)) else res.set_status_code ({HTTP_STATUS_CODE}.no_content) res.put_header_text (a_header.string) diff --git a/library/server/wsf/router/wsf_get_helper.e b/library/server/wsf/router/wsf_get_helper.e index 7200f6f1..c5e7a8cb 100644 --- a/library/server/wsf/router/wsf_get_helper.e +++ b/library/server/wsf/router/wsf_get_helper.e @@ -14,14 +14,18 @@ inherit feature {NONE} -- Implementation - send_response (req: WSF_REQUEST; res: WSF_RESPONSE; a_handler: WSF_SKELETON_HANDLER; a_header: HTTP_HEADER; - a_media_type, a_language_type, a_character_type, a_compression_type: detachable READABLE_STRING_8; a_new_resource: BOOLEAN) + 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, a_media_type, a_language_type, a_character_type, a_compression_type) + a_handler.ensure_content_available (req) l_chunked := a_handler.is_chunking (req) if l_chunked then a_header.put_transfer_encoding_chunked @@ -39,15 +43,15 @@ feature {NONE} -- Implementation else write_error_response (req, res) end - if attached a_handler.etag (req, a_media_type, a_language_type, a_character_type, a_compression_type) as l_etag then + 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, a_media_type, a_language_type, a_character_type, a_compression_type) + send_chunked_response (req, res, a_handler, a_header) else - res.put_string (a_handler.content (req, a_media_type, a_language_type, a_character_type, a_compression_type)) + res.put_string (a_handler.content (req)) end end end diff --git a/library/server/wsf/router/wsf_method_helper.e b/library/server/wsf/router/wsf_method_helper.e index ca527161..899e28b0 100644 --- a/library/server/wsf/router/wsf_method_helper.e +++ b/library/server/wsf/router/wsf_method_helper.e @@ -36,15 +36,19 @@ 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'. - -- This default implementation does not apply for PUT requests. - -- The behaviour for POST requests depends upon a policy. + -- 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] - h: HTTP_HEADER do if a_handler.resource_previously_existed (req) then if a_handler.resource_moved_permanently (req) then @@ -54,26 +58,35 @@ feature -- Basic operations l_locs := a_handler.previous_location (req) handle_redirection_error (req, res, l_locs, {HTTP_STATUS_CODE}.found) else - create h.make + check attached {HTTP_HEADER} req.execution_variable ("NEGOTIATED_HTTP_HEADER") 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 ("NEGOTIATED_HTTP_HEADER") 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.set_status_code ({HTTP_STATUS_CODE}.not_found) res.put_header_lines (h) end - 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_lines (h) 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 @@ -127,19 +140,28 @@ feature -- Basic operations end end if not l_failed then - handle_content_negotiation (req, res, a_handler, False) + check attached {HTTP_HEADER} req.execution_variable ("NEGOTIATED_HTTP_HEADER") as h then + -- postcondition header_attached of `handle_content_negotiation' + send_response (req, res, a_handler, h, False) + end end end end end -feature {NONE} -- Implementation +feature -- Content negotiation - handle_content_negotiation (req: WSF_REQUEST; res: WSF_RESPONSE; - a_handler: WSF_SKELETON_HANDLER; a_new_resource: BOOLEAN) + 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'. - -- This default version applies to GET and HEAD. + -- + -- 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 @@ -182,6 +204,7 @@ feature {NONE} -- Implementation else if attached l_lang.language_type as l_language_type then h.put_content_language (l_language_type) + req.set_execution_variable ("NEGOTIATED_LANGUAGE", l_language_type) end l_charsets := a_handler.charsets_supported (req) l_charset := l_conneg.charset_preference (l_charsets, req.http_accept_charset) @@ -191,8 +214,14 @@ feature {NONE} -- Implementation 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 and attached l_charset.character_type as l_character_type then - h.put_content_type (l_media_type + "; charset=" + l_character_type) + 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 ("NEGOTIATED_CHARSET", l_charset) + else + h.put_content_type (l_media_type) + end + req.set_execution_variable ("NEGOTIATED_MEDIA_TYPE", l_media_type) end l_encodings := a_handler.encodings_supported (req) l_encoding := l_conneg.encoding_preference (l_encodings, req.http_accept_encoding) @@ -204,48 +233,51 @@ feature {NONE} -- Implementation else if attached l_encoding.compression_type as l_compression_type then h.put_content_encoding (l_compression_type) + req.set_execution_variable ("NEGOTIATED_ENCODING", l_compression_type) end - -- We do not support multiple choices, so - send_response (req, res, a_handler, h, - l_media.media_type, l_lang.language_type, l_charset.character_type, l_encoding.compression_type, a_new_resource) end end end end + req.set_execution_variable ("NEGOTIATED_HTTP_HEADER", h) + ensure + header_attached: attached {HTTP_HEADER} req.execution_variable ("NEGOTIATED_HTTP_HEADER") end - send_response (req: WSF_REQUEST; res: WSF_RESPONSE; a_handler: WSF_SKELETON_HANDLER; a_header: HTTP_HEADER; - a_media_type, a_language_type, a_character_type, a_compression_type: detachable READABLE_STRING_8; a_new_resource: BOOLEAN) - -- Write response to `req' into `res' in accordance with `a_media_type' etc. +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 - a_media_type_attached: a_media_type /= Void - a_language_type_attached: a_language_type /= Void - a_character_type_attached: a_character_type /= Void - a_compression_type_attached: a_compression_type /= Void deferred end - send_chunked_response (req: WSF_REQUEST; res: WSF_RESPONSE; a_handler: WSF_SKELETON_HANDLER; a_header: HTTP_HEADER; - a_media_type, a_language_type, a_character_type, a_compression_type: READABLE_STRING_8) - -- Write response in chunks to `req' into `res' in accordance with `a_media_type' etc. + 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 - a_media_type_attached: a_media_type /= Void - a_language_type_attached: a_language_type /= Void - a_character_type_attached: a_character_type /= Void - a_compression_type_attached: a_compression_type /= 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, a_media_type, a_language_type, a_character_type, a_compression_type) + l_chunk := a_handler.next_chunk (req) res.put_chunk (l_chunk.a_chunk, l_chunk.a_extension) else write_error_response (req, res) @@ -253,9 +285,9 @@ feature {NONE} -- Implementation until a_handler.finished (req) or not a_handler.response_ok (req) loop - a_handler.generate_next_chunk (req, a_media_type, a_language_type, a_character_type, a_compression_type) + a_handler.generate_next_chunk (req) if a_handler.response_ok (req) then - l_chunk := a_handler.next_chunk (req, a_media_type, a_language_type, a_character_type, a_compression_type) + l_chunk := a_handler.next_chunk (req) res.put_chunk (l_chunk.a_chunk, l_chunk.a_extension) else write_error_response (req, res) @@ -464,6 +496,11 @@ feature -- Error reporting 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 @@ -474,12 +511,7 @@ feature -- Error reporting create h.make h.put_content_type_text_plain h.put_content_length (0) - if attached a_handler.etag (req, "", "", "", "") as l_etag then - -- FIXME: We aren't strictly conformant here, as we have not conducted content negotiation yet, - -- so we might not get an identical etag as for a successful (200 OK) request. - -- So we should conduct content negotiation at this point (and if not acceptable, we don't include an etag). - -- Add add any Vary header that might result. - -- Also, when we add support for the Content-Location header in responses, we need to send that here too. + 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) diff --git a/library/server/wsf/router/wsf_post_helper.e b/library/server/wsf/router/wsf_post_helper.e index 41c54a64..f3d4d44c 100644 --- a/library/server/wsf/router/wsf_post_helper.e +++ b/library/server/wsf/router/wsf_post_helper.e @@ -22,7 +22,10 @@ feature -- Basic operations -- Policy routines are available in `a_handler'. do if a_handler.allow_post_to_missing_resource (req) then - handle_content_negotiation (req, res, a_handler, True) + check attached {HTTP_HEADER} req.execution_variable ("NEGOTIATED_HTTP_HEADER") 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 @@ -31,9 +34,13 @@ feature -- Basic operations feature {NONE} -- Implementation - send_response (req: WSF_REQUEST; res: WSF_RESPONSE; a_handler: WSF_SKELETON_HANDLER; a_header: HTTP_HEADER; - a_media_type, a_language_type, a_character_type, a_compression_type: detachable READABLE_STRING_8; a_new_resource: BOOLEAN) + 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 diff --git a/library/server/wsf/router/wsf_put_helper.e b/library/server/wsf/router/wsf_put_helper.e index 6adedbbf..02b26f0d 100644 --- a/library/server/wsf/router/wsf_put_helper.e +++ b/library/server/wsf/router/wsf_put_helper.e @@ -24,15 +24,16 @@ feature -- Basic operations 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 - handle_content_negotiation (req, res, a_handler, True) + check attached {HTTP_HEADER} req.execution_variable ("NEGOTIATED_HTTP_HEADER") 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_media_type, a_language_type, a_character_type, a_compression_type: detachable READABLE_STRING_8; a_new_resource: BOOLEAN) + 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 diff --git a/library/server/wsf/router/wsf_skeleton_handler.e b/library/server/wsf/router/wsf_skeleton_handler.e index 33a71d51..b246af4c 100644 --- a/library/server/wsf/router/wsf_skeleton_handler.e +++ b/library/server/wsf/router/wsf_skeleton_handler.e @@ -109,14 +109,15 @@ feature -- Access deferred end - etag (req: WSF_REQUEST; a_media_type, a_language_type, a_character_type, a_compression_type: READABLE_STRING_8): detachable READABLE_STRING_8 - -- Optional Etag for `req' in the requested variant + 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 - a_media_type_attached: a_media_type /= Void - a_language_type_attached: a_language_type /= Void - a_character_type_attached: a_character_type /= Void - a_compression_type_attached: a_compression_type /= Void deferred end @@ -216,20 +217,23 @@ feature -- DELETE feature -- GET/HEAD content - ensure_content_available (req: WSF_REQUEST; - a_media_type, a_language_type, a_character_type, a_compression_type: READABLE_STRING_8) + ensure_content_available (req: WSF_REQUEST) -- Commence generation of response text (entity-body). -- 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 - a_media_type_attached: a_media_type /= Void - a_language_type_attached: a_language_type /= Void - a_character_type_attached: a_character_type /= Void - a_compression_type_attached: a_compression_type /= Void deferred end @@ -243,45 +247,48 @@ feature -- GET/HEAD content last_error_set: Result = not req.error_handler.has_error end - content (req: WSF_REQUEST; a_media_type, a_language_type, a_character_type, a_compression_type: READABLE_STRING_8): READABLE_STRING_8 - -- Non-chunked entity body in response to `req' + 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) - a_media_type_attached: a_media_type /= Void - a_language_type_attached: a_language_type /= Void - a_character_type_attached: a_character_type /= Void - a_compression_type_attached: a_compression_type /= Void deferred end - generate_next_chunk (req: WSF_REQUEST; a_media_type, a_language_type, a_character_type, a_compression_type: READABLE_STRING_8) + 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) - a_media_type_attached: a_media_type /= Void - a_language_type_attached: a_language_type /= Void - a_character_type_attached: a_character_type /= Void - a_compression_type_attached: a_compression_type /= Void deferred end - next_chunk (req: WSF_REQUEST; a_media_type, a_language_type, a_character_type, a_compression_type: READABLE_STRING_8): TUPLE [a_check: READABLE_STRING_8; a_extension: detachable READABLE_STRING_8] + 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) - a_media_type_attached: a_media_type /= Void - a_language_type_attached: a_language_type /= Void - a_character_type_attached: a_character_type /= Void - a_compression_type_attached: a_compression_type /= Void deferred end @@ -427,14 +434,17 @@ feature -- Execution res_attached: res /= Void a_helper_attached: a_helper /= Void do - 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) + 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 - a_helper.execute_new_resource (req, res, Current) + 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 @@ -442,6 +452,15 @@ feature -- Execution 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 From 35224b1b171bfe06a43441feb5dfa9ae8b6d062f Mon Sep 17 00:00:00 2001 From: colin-adams Date: Mon, 12 Aug 2013 01:45:58 -0700 Subject: [PATCH 48/68] Updated Writing the handlers (markdown) --- Writing-the-handlers.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Writing-the-handlers.md b/Writing-the-handlers.md index 252d6bdf..933c3a09 100644 --- a/Writing-the-handlers.md +++ b/Writing-the-handlers.md @@ -8,6 +8,7 @@ Now you have to implement each handler. You need to inherit from WSF_SKELETON_HA HTTP/1.1 supports streaming responses (and providing you have configured your server to use a proxy server in WSF_PROXY_USE_POLICY, this framework guarantees you have an HTTP/1.1 client to deal with). It is up to you whether or not you choose to make use of it. If so, then you have to serve the response one chunk at a time (but you could generate it all at once, and slice it up as you go). In this routine you just say whether or not you will be doing this. So the framework n=knows which other routines to call. Currently we only support chunking for GET or HEAD routines. This might change in the future, so if you intend to return True, you should call req.is_get_head_request_method. +Note that currently this framework does not support writing a trailer. ### includes_response_entity @@ -47,10 +48,14 @@ If the response may vary in other ways not predictable from the request headers, An **ETag** header is a kind of message digest. Clients can use etags to avoid re-fetching responses for unchanged resources, or to avoid updating a resource that may have changed since the client last updated it. You must implement this routine to test for matches **if and only if** you return non-Void responses for the etag routine. +Note that if you support multiple representations through content negotiation, then etags are dependent upon +the selected variant. Therefore you will need to have the response entity available for this routine. This can be done in check_resource_exists. ### etag You are strongly encouraged to return non-Void for this routine. See [Validation Model](http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.3) for more details. +Note that if you support multiple representations through content negotiation, then etags are dependent upon +the selected variant. Therefore you will need to have the response entity available for this routine. This can be done in check_resource_exists. ### modified_since @@ -94,6 +99,9 @@ This routine is called for GET and DELETE (when a entity is provided in the resp As well as the request object, we provide the results of content negotiation, so you can generate the entity in the agreed format. If you only support one format (i.e. all of mime_types_supported, charsets_supported, encodings_supported and languages_supported are one-element lists), then you are guaranteed that this is what you are being asked for, and so you can ignore them. +Note that if you support multiple representations through content negotiation, then etags are dependent upon +the selected variant. Therefore you will need to have the response entity available for this routine. In such cases, this will have to be done in check_resource_exists, rather than here, as this routine is called later on. + ### content When not streaming, this routine provides the entity to the framework (for GET or DELETE). Normally you would just access the execution variable that you set in ensure_content_available. Again, the results of content negotiation are made available, but you probably don't need them at this stage. If you only stream responses (for GET), and if you don't support DELETE, then you don't need to do anything here. From bf5bae803d8c8c30c772e4bf8e8fa3daabaa2398 Mon Sep 17 00:00:00 2001 From: colin-adams Date: Mon, 12 Aug 2013 01:49:11 -0700 Subject: [PATCH 49/68] Updated Writing the handlers (markdown) --- Writing-the-handlers.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Writing-the-handlers.md b/Writing-the-handlers.md index 933c3a09..99c557c1 100644 --- a/Writing-the-handlers.md +++ b/Writing-the-handlers.md @@ -4,6 +4,12 @@ Now you have to implement each handler. You need to inherit from WSF_SKELETON_HA ## Implementing the routines declared directly in WSF_SKELETON_HANDLER +### check_resource_exists + +Here you check for the existence of the resource named by the request URI. If it does, then you need to call set_resource_exists on the helper argument. +Note that if you support multiple representations through content negotiation, then etags are dependent upon +the selected variant. If you support etags, then you will need to make the response entity available at this point, rather than in ensure_content_available. + ### is_chunking HTTP/1.1 supports streaming responses (and providing you have configured your server to use a proxy server in WSF_PROXY_USE_POLICY, this framework guarantees you have an HTTP/1.1 client to deal with). It is up to you whether or not you choose to make use of it. If so, then you have to serve the response one chunk at a time (but you could generate it all at once, and slice it up as you go). In this routine you just say whether or not you will be doing this. So the framework n=knows which other routines to call. From b074570e994b5856f1fe2848aeadf5cc5a7e7dc1 Mon Sep 17 00:00:00 2001 From: Colin Adams Date: Mon, 12 Aug 2013 16:45:47 +0100 Subject: [PATCH 50/68] Added some checks for custom erros being set. --- library/server/wsf/router/wsf_delete_helper.e | 52 +++++++++++-------- library/server/wsf/router/wsf_get_helper.e | 25 +++++---- 2 files changed, 44 insertions(+), 33 deletions(-) diff --git a/library/server/wsf/router/wsf_delete_helper.e b/library/server/wsf/router/wsf_delete_helper.e index 751e0adf..15f08204 100644 --- a/library/server/wsf/router/wsf_delete_helper.e +++ b/library/server/wsf/router/wsf_delete_helper.e @@ -23,34 +23,42 @@ feature {NONE} -- Implementation -- "NEGOTIATED_ENCODING" local l_dt: STRING + l_ok: BOOLEAN do a_handler.delete (req) - if a_handler.includes_response_entity (req) then - a_handler.ensure_content_available (req) - a_header.put_content_length (a_handler.content_length (req).as_integer_32) - -- 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 + l_ok := a_handler.response_ok (req) + if l_ok then if a_handler.includes_response_entity (req) then - res.set_status_code ({HTTP_STATUS_CODE}.ok) + 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)) - else - res.set_status_code ({HTTP_STATUS_CODE}.no_content) - res.put_header_text (a_header.string) + 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 - else + end + if not l_ok then write_error_response (req, res) end end diff --git a/library/server/wsf/router/wsf_get_helper.e b/library/server/wsf/router/wsf_get_helper.e index c5e7a8cb..666518e2 100644 --- a/library/server/wsf/router/wsf_get_helper.e +++ b/library/server/wsf/router/wsf_get_helper.e @@ -26,18 +26,21 @@ feature {NONE} -- Implementation l_dt: STRING do a_handler.ensure_content_available (req) - 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) + 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 From 9c8bc59224de4318558a92d66f54b47631459654 Mon Sep 17 00:00:00 2001 From: colin-adams Date: Tue, 13 Aug 2013 00:54:45 -0700 Subject: [PATCH 51/68] Updated Writing the handlers (markdown) --- Writing-the-handlers.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/Writing-the-handlers.md b/Writing-the-handlers.md index 99c557c1..6af6d39f 100644 --- a/Writing-the-handlers.md +++ b/Writing-the-handlers.md @@ -2,6 +2,22 @@ Now you have to implement each handler. You need to inherit from WSF_SKELETON_HANDLER (as ORDER_HANDLER does). This involves implementing a lot of deferred routines. There are other routines for which default implementations are provided, which you might want to override. This applies to both routines defined in this class, and those declared in the three policy classes from which it inherits. +## Communicating between routines + +Depending upon the connector (Nino, CGI, FastCGI etc.) that you are using, your handler may be invoked concurrently for multiple requests. Therefore it is unsafe to save state in normal attributes. WSF_REQUEST has a pair of getter/setter routines, execution_variable/set_execution_variable, which you can use for this purpose. +Internally, the framework uses the following execution variable names, so you must avoid them: + +1. REQUEST_ENTITY +1. NEGOTIATED_LANGUAGE +1. NEGOTIATED_CHARSET +1. NEGOTIATED_MEDIA_TYPE +1. NEGOTIATED_ENCODING +1. NEGOTIATED_HTTP_HEADER + +The first one makes the request entity from a PULL or POST request available to your routines. + +The next four make the results of content negotiation available to your routines. The last one makes an HTTP_HEADER available to your routines. You should use this rather than create your own, as it may contain a **Vary** header as a by-product of content negotiation. + ## Implementing the routines declared directly in WSF_SKELETON_HANDLER ### check_resource_exists From c93e50a7e25a341460a1b9ea474d97a3060f2e53 Mon Sep 17 00:00:00 2001 From: Colin Adams Date: Tue, 13 Aug 2013 15:47:59 +0100 Subject: [PATCH 52/68] Gave symbolic names to execution variables used by the framework --- library/server/wsf/router/wsf_method_helper.e | 12 +++++----- .../server/wsf/router/wsf_skeleton_handler.e | 24 +++++++++++++++++-- 2 files changed, 28 insertions(+), 8 deletions(-) diff --git a/library/server/wsf/router/wsf_method_helper.e b/library/server/wsf/router/wsf_method_helper.e index 899e28b0..61783af8 100644 --- a/library/server/wsf/router/wsf_method_helper.e +++ b/library/server/wsf/router/wsf_method_helper.e @@ -204,7 +204,7 @@ feature -- Content negotiation else if attached l_lang.language_type as l_language_type then h.put_content_language (l_language_type) - req.set_execution_variable ("NEGOTIATED_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) @@ -217,11 +217,11 @@ feature -- Content negotiation 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 ("NEGOTIATED_CHARSET", l_charset) + 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 ("NEGOTIATED_MEDIA_TYPE", l_media_type) + 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) @@ -233,15 +233,15 @@ feature -- Content negotiation else if attached l_encoding.compression_type as l_compression_type then h.put_content_encoding (l_compression_type) - req.set_execution_variable ("NEGOTIATED_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 ("NEGOTIATED_HTTP_HEADER", h) + req.set_execution_variable (a_handler.Negotiated_http_header_execution_variable, h) ensure - header_attached: attached {HTTP_HEADER} req.execution_variable ("NEGOTIATED_HTTP_HEADER") + header_attached: attached {HTTP_HEADER} req.execution_variable (a_handler.Negotiated_http_header_execution_variable) end feature {NONE} -- Implementation diff --git a/library/server/wsf/router/wsf_skeleton_handler.e b/library/server/wsf/router/wsf_skeleton_handler.e index b246af4c..780034ee 100644 --- a/library/server/wsf/router/wsf_skeleton_handler.e +++ b/library/server/wsf/router/wsf_skeleton_handler.e @@ -26,6 +26,26 @@ inherit WSF_SELF_DOCUMENTED_HANDLER +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 + feature -- Access is_chunking (req: WSF_REQUEST): BOOLEAN @@ -295,7 +315,7 @@ feature -- GET/HEAD content feature -- PUT/POST read_entity (req: WSF_REQUEST) - -- Read request body and set as `req.execution_variable ("REQUEST_ENTITY")'. + -- Read request body and set as `req.execution_variable (Request_entity_execution_variable)'. require req_attached: req /= Void local @@ -304,7 +324,7 @@ feature -- PUT/POST 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", l_body) + req.set_execution_variable (Request_entity_execution_variable, l_body) end end From 123fc8252e62bf6edcbf3921be2d0e828c8dfa8c Mon Sep 17 00:00:00 2001 From: colin-adams Date: Tue, 13 Aug 2013 08:24:05 -0700 Subject: [PATCH 53/68] Updated Writing the handlers (markdown) --- Writing-the-handlers.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Writing-the-handlers.md b/Writing-the-handlers.md index 6af6d39f..6bc01145 100644 --- a/Writing-the-handlers.md +++ b/Writing-the-handlers.md @@ -18,6 +18,8 @@ The first one makes the request entity from a PULL or POST request available to The next four make the results of content negotiation available to your routines. The last one makes an HTTP_HEADER available to your routines. You should use this rather than create your own, as it may contain a **Vary** header as a by-product of content negotiation. +All six names are defined as constants in WSF_SKELETON_HANDLER, to make it easier for you to refer to them. + ## Implementing the routines declared directly in WSF_SKELETON_HANDLER ### check_resource_exists From 275c26b55b6e9239895d25f842325c281ec2ce63 Mon Sep 17 00:00:00 2001 From: Colin Adams Date: Wed, 14 Aug 2013 09:22:35 +0100 Subject: [PATCH 54/68] Further use of constants for execution variables --- .../src/resource/order_handler.e | 75 ++++++++++--------- library/server/wsf/router/wsf_method_helper.e | 18 ++--- library/server/wsf/router/wsf_post_helper.e | 2 +- .../server/wsf/router/wsf_previous_policy.e | 5 +- library/server/wsf/router/wsf_put_helper.e | 14 +++- .../server/wsf/router/wsf_skeleton_handler.e | 27 ++++--- 6 files changed, 83 insertions(+), 58 deletions(-) diff --git a/examples/restbucksCRUD/src/resource/order_handler.e b/examples/restbucksCRUD/src/resource/order_handler.e index ece4c63e..1ea6f836 100644 --- a/examples/restbucksCRUD/src/resource/order_handler.e +++ b/examples/restbucksCRUD/src/resource/order_handler.e @@ -28,6 +28,18 @@ create make_with_router + +feature -- Execution variables + + Order_execution_variable: STRING = "ORDER" + -- Execution variable used by application + + Generated_content_execution_variable: STRING = "GENERATED_CONTENT" + -- Execution variable used by application + + Extracted_order_execution_variable: STRING = "EXTRACTED_ORDER" + -- Execution variable used by application + feature -- Documentation description: READABLE_STRING_GENERAL @@ -87,13 +99,6 @@ feature -- Access Result.compare_objects end - previous_location (req: WSF_REQUEST): LIST [URI] - -- Previous location(s) for resource named by `req'; - do - -- precondition is never met but we need a non-void Result to satisfy the compiler in Void-safe mode: - create {LINKED_LIST [URI]} Result.make - end - 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. @@ -158,7 +163,7 @@ feature -- Access l_etag_utils: ETAG_UTILS do create l_etag_utils - if attached {ORDER} req.execution_variable ("ORDER") as l_order then + if attached {ORDER} req.execution_variable (Order_execution_variable) as l_order then Result := l_etag_utils.md5_digest (l_order.out) end end @@ -175,7 +180,7 @@ feature -- Measurement content_length (req: WSF_REQUEST): NATURAL -- Length of entity-body of the response to `req' do - check attached {READABLE_STRING_8} req.execution_variable ("GENERATED_CONTENT") as l_response then + check attached {READABLE_STRING_8} req.execution_variable (Generated_content_execution_variable) as l_response then -- postcondition generated_content_set_for_get_head of `ensure_content_available' -- We only call this for GET/HEAD in this example. Result := l_response.count.as_natural_32 @@ -201,7 +206,7 @@ feature -- Execution 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. - -- We also put the order into `req.execution_variable ("ORDER")' for GET or HEAD responses. + -- We also put the order into `req.execution_variable (Order_execution_variable)' for GET or HEAD responses. local l_id: STRING do @@ -216,14 +221,14 @@ feature -- Execution if req.is_get_head_request_method then check attached db_access.orders.item (l_id) as l_order then -- postcondition `item_if_found' of `has_key' - req.set_execution_variable ("ORDER", l_order) + req.set_execution_variable (Order_execution_variable, l_order) end end end end ensure then order_saved_only_for_get_head: req.is_get_head_request_method = - attached {ORDER} req.execution_variable ("ORDER") + attached {ORDER} req.execution_variable (Order_execution_variable) end feature -- GET/HEAD content @@ -234,27 +239,27 @@ feature -- GET/HEAD content -- 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. - -- We save the text in `req.execution_variable ("GENERATED_CONTENT")' + -- We save the text in `req.execution_variable (Generated_content_execution_variable)' -- We ignore the results of content negotiation, as there is only one possible combination. do - check attached {ORDER} req.execution_variable ("ORDER") as l_order then + check attached {ORDER} req.execution_variable (Order_execution_variable) as l_order then -- precondition get_or_head and postcondition order_saved_only_for_get_head of `check_resource_exists' and if attached {JSON_VALUE} json.value (l_order) as jv then - req.set_execution_variable ("GENERATED_CONTENT", jv.representation) + req.set_execution_variable (Generated_content_execution_variable, jv.representation) else - req.set_execution_variable ("GENERATED_CONTENT", "") + req.set_execution_variable (Generated_content_execution_variable, "") end end ensure then generated_content_set_for_get_head: req.is_get_head_request_method implies - attached {READABLE_STRING_8} req.execution_variable ("GENERATED_CONTENT") + attached {READABLE_STRING_8} req.execution_variable (Generated_content_execution_variable) end content (req: WSF_REQUEST): READABLE_STRING_8 -- Non-chunked entity body in response to `req'; -- We only call this for GET/HEAD in this example. do - check attached {READABLE_STRING_8} req.execution_variable ("GENERATED_CONTENT") as l_response then + check attached {READABLE_STRING_8} req.execution_variable (Generated_content_execution_variable) as l_response then -- postcondition generated_content_set_for_get_head of `ensure_content_available' Result := l_response end @@ -307,14 +312,14 @@ feature -- DELETE feature -- PUT/POST is_entity_too_large (req: WSF_REQUEST): BOOLEAN - -- Is the entity stored in `req.execution_variable ("REQUEST_ENTITY")' too large for the application? + -- Is the entity stored in `req.execution_variable (Request_entity_execution_variable)' too large for the application? do -- No. We don't care for this example. end check_content_headers (req: WSF_REQUEST) -- Check we can support all content headers on request entity. - -- Set `req.execution_variable ("CONTENT_CHECK_CODE")' to {NATURAL} zero if OK, or 415 or 501 if not. + -- Set `req.execution_variable (Content_check_code_execution_variable)' to {NATURAL} zero if OK, or 415 or 501 if not. do -- We don't bother for this example. Note that this is equivalent to setting zero. end @@ -331,7 +336,7 @@ feature -- PUT/POST -- 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. do - if attached {ORDER} req.execution_variable ("EXTRACTED_ORDER") as l_order then + if attached {ORDER} req.execution_variable (Extracted_order_execution_variable) as l_order then save_order (l_order) compute_response_post (req, res, l_order) else @@ -341,16 +346,16 @@ feature -- PUT/POST check_conflict (req: WSF_REQUEST; res: WSF_RESPONSE) -- Check we can support all content headers on request entity. - -- Set `req.execution_variable ("CONFLICT_CHECK_CODE")' to {NATURAL} zero if OK, or 409 if not. + -- 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'. do - if attached {ORDER} req.execution_variable ("EXTRACTED_ORDER") as l_order then + if attached {ORDER} req.execution_variable (Extracted_order_execution_variable) as l_order then if not is_valid_to_update (l_order) then - req.set_execution_variable ("CONFLICT_CHECK_CODE", {NATURAL} 409) + req.set_execution_variable (Conflict_check_code_execution_variable, {NATURAL} 409) handle_resource_conflict_response (l_order.out +"%N There is conflict while trying to update the order, the order could not be update in the current state", req, res) end else - req.set_execution_variable ("CONFLICT_CHECK_CODE", {NATURAL} 409) + req.set_execution_variable (Conflict_check_code_execution_variable, {NATURAL} 409) --| This ought to be a 500, as if attached should probably be check attached. But as yet I lack a proof. handle_resource_conflict_response ("There is conflict while trying to update the order, the order could not be update in the current state", req, res) end @@ -358,31 +363,31 @@ feature -- PUT/POST 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 ("REQUEST_ENTITY")'. - -- Set `req.execution_variable ("REQUEST_CHECK_CODE")' to {NATURAL} zero if OK, or 400 if not. + -- 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'. local l_order: detachable ORDER l_id: STRING do - if attached {READABLE_STRING_8} req.execution_variable ("REQUEST_ENTITY") as l_request then + if attached {READABLE_STRING_8} req.execution_variable (Request_entity_execution_variable) as l_request then l_order := extract_order_request (l_request) if req.is_put_request_method then l_id := order_id_from_request (req) if l_order /= Void and then db_access.orders.has_key (l_id) then l_order.set_id (l_id) - req.set_execution_variable ("REQUEST_CHECK_CODE", {NATURAL} 0) - req.set_execution_variable ("EXTRACTED_ORDER", l_order) + req.set_execution_variable (Request_check_code_execution_variable, {NATURAL} 0) + req.set_execution_variable (Extracted_order_execution_variable, l_order) else - req.set_execution_variable ("REQUEST_CHECK_CODE", {NATURAL} 400) + req.set_execution_variable (Request_check_code_execution_variable, {NATURAL} 400) handle_bad_request_response (l_request +"%N is not a valid ORDER, maybe the order does not exist in the system", req, res) end else - req.set_execution_variable ("REQUEST_CHECK_CODE", {NATURAL} 0) - req.set_execution_variable ("EXTRACTED_ORDER", l_order) + req.set_execution_variable (Request_check_code_execution_variable, {NATURAL} 0) + req.set_execution_variable (Extracted_order_execution_variable, l_order) end else - req.set_execution_variable ("REQUEST_CHECK_CODE", {NATURAL} 400) + req.set_execution_variable (Request_check_code_execution_variable, {NATURAL} 400) handle_bad_request_response ("Request is not a valid ORDER", req, res) end end @@ -391,7 +396,7 @@ feature -- PUT/POST -- Perform the update requested in `req'. -- Write a response to `res' with a code of 204 or 500. do - if attached {ORDER} req.execution_variable ("EXTRACTED_ORDER") as l_order then + if attached {ORDER} req.execution_variable (Extracted_order_execution_variable) as l_order then update_order (l_order) compute_response_put (req, res, l_order) else diff --git a/library/server/wsf/router/wsf_method_helper.e b/library/server/wsf/router/wsf_method_helper.e index 61783af8..c64f1484 100644 --- a/library/server/wsf/router/wsf_method_helper.e +++ b/library/server/wsf/router/wsf_method_helper.e @@ -12,7 +12,7 @@ deferred class WSF_METHOD_HELPER inherit HTTP_STATUS_CODE_MESSAGES - + SHARED_HTML_ENCODER export {NONE} all end @@ -58,7 +58,7 @@ feature -- Basic operations 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 ("NEGOTIATED_HTTP_HEADER") as h 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' h.put_content_type_text_plain h.put_current_date @@ -68,7 +68,7 @@ feature -- Basic operations end end else - check attached {HTTP_HEADER} req.execution_variable ("NEGOTIATED_HTTP_HEADER") as h 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' h.put_content_type_text_plain h.put_current_date @@ -140,7 +140,7 @@ feature -- Basic operations end end if not l_failed then - check attached {HTTP_HEADER} req.execution_variable ("NEGOTIATED_HTTP_HEADER") as h 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 @@ -154,7 +154,7 @@ 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" @@ -290,7 +290,7 @@ feature {NONE} -- Implementation l_chunk := a_handler.next_chunk (req) res.put_chunk (l_chunk.a_chunk, l_chunk.a_extension) else - write_error_response (req, res) + write_error_response (req, res) end end if a_handler.finished (req) then @@ -366,7 +366,7 @@ feature {NONE} -- Implementation feature -- Errors - + feature -- Error reporting write_error_response (req: WSF_REQUEST; res: WSF_RESPONSE) @@ -439,7 +439,7 @@ feature -- Error reporting s.append ("
") s.append ("%N") s.append ("%N") - + h.put_content_type_text_html else s := "Error " + a_status_code.out + " (" + l_msg + "): " @@ -566,7 +566,7 @@ feature -- Error reporting 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 diff --git a/library/server/wsf/router/wsf_post_helper.e b/library/server/wsf/router/wsf_post_helper.e index f3d4d44c..96ae57a4 100644 --- a/library/server/wsf/router/wsf_post_helper.e +++ b/library/server/wsf/router/wsf_post_helper.e @@ -22,7 +22,7 @@ feature -- Basic operations -- 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 ("NEGOTIATED_HTTP_HEADER") as h 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 diff --git a/library/server/wsf/router/wsf_previous_policy.e b/library/server/wsf/router/wsf_previous_policy.e index b64c0450..c77f0746 100644 --- a/library/server/wsf/router/wsf_previous_policy.e +++ b/library/server/wsf/router/wsf_previous_policy.e @@ -43,10 +43,11 @@ feature -- Access req_attached: req /= Void previously_existed: resource_previously_existed (req) moved: resource_moved_permanently (req) or resource_moved_temporarily (req) - deferred + do + create {LINKED_LIST [URI]} Result.make ensure previous_location_attached: Result /= Void - non_empty_list: not Result.empty + non_empty_list: not Result.is_empty end note diff --git a/library/server/wsf/router/wsf_put_helper.e b/library/server/wsf/router/wsf_put_helper.e index 02b26f0d..41095b67 100644 --- a/library/server/wsf/router/wsf_put_helper.e +++ b/library/server/wsf/router/wsf_put_helper.e @@ -24,7 +24,7 @@ feature -- Basic operations 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 ("NEGOTIATED_HTTP_HEADER") as h 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 @@ -68,5 +68,15 @@ feature {NONE} -- Implementation 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 diff --git a/library/server/wsf/router/wsf_skeleton_handler.e b/library/server/wsf/router/wsf_skeleton_handler.e index 780034ee..511ec692 100644 --- a/library/server/wsf/router/wsf_skeleton_handler.e +++ b/library/server/wsf/router/wsf_skeleton_handler.e @@ -30,7 +30,7 @@ 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 @@ -46,6 +46,15 @@ feature -- Execution variables 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 @@ -329,7 +338,7 @@ feature -- PUT/POST end is_entity_too_large (req: WSF_REQUEST): BOOLEAN - -- Is the entity stored in `req.execution_variable ("REQUEST_ENTITY")' too large for the application? + -- Is the entity stored in `req.execution_variable (Request_entity_execution_variable)' too large for the application? require req_attached: req /= Void deferred @@ -337,7 +346,7 @@ feature -- PUT/POST check_content_headers (req: WSF_REQUEST) -- Check we can support all content headers on request entity. - -- Set `req.execution_variable ("CONTENT_CHECK_CODE")' to {NATURAL} zero if OK, or 415 or 501 if not. + -- 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 @@ -348,7 +357,7 @@ feature -- PUT/POST require req_attached: req /= Void do - if attached {NATURAL} req.execution_variable ("CONTENT_CHECK_CODE") as l_code then + if attached {NATURAL} req.execution_variable (Content_check_code_execution_variable) as l_code then Result := l_code end end @@ -375,7 +384,7 @@ feature -- PUT/POST 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")' to {NATURAL} zero if OK, or 409 if not. + -- 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 @@ -388,15 +397,15 @@ feature -- PUT/POST require req_attached: req /= Void do - if attached {NATURAL} req.execution_variable ("CONFLICT_CHECK_CODE") as l_code then + 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 ("REQUEST_ENTITY")'. - -- Set `req.execution_variable ("REQUEST_CHECK_CODE")' to {NATURAL} zero if OK, or 400 if not. + -- 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 @@ -410,7 +419,7 @@ feature -- PUT/POST require req_attached: req /= Void do - if attached {NATURAL} req.execution_variable ("REQUEST_CHECK_CODE") as l_code then + if attached {NATURAL} req.execution_variable (Request_check_code_execution_variable) as l_code then Result := l_code end end From 5e62d82e9ce92acc15993d26b96129115c1794b1 Mon Sep 17 00:00:00 2001 From: colin-adams Date: Wed, 14 Aug 2013 02:22:22 -0700 Subject: [PATCH 55/68] Updated Wsf previous policy (markdown) --- Wsf-previous-policy.md | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/Wsf-previous-policy.md b/Wsf-previous-policy.md index 37c965c5..1ba251d0 100644 --- a/Wsf-previous-policy.md +++ b/Wsf-previous-policy.md @@ -1,20 +1,19 @@ -# Implementing WSF_PREVIOUS_POLICY +# WSF_PREVIOUS_POLICY -This class provides routines which enable the programmer to encode knowledge about resources that have moved (either temporarily, or permanently), or have been permanently removed. There are four routines, but only one is actually deferred. +This class deals with resources that have moved or gone. The default assumes no such resources. It exists as a separate class, rather than have the routines directly in WSF_SKELETON_HANDLER, as sub-classing it may be convenient for an organisation. ## resource_previously_existed -By default, this routine says that currently doesn't exist, never has existed. You need to redefine this routine to return True for any URIs that you want to indicate used to exist, and either no longer do so, or have moved to another location. +Redefining this routine is always necessary if you want to deal with any previous resources. ## resource_moved_permanently -If you have indicated that a resource previously existed, then it may have moved permanently, temporarily, or just ceased to exist. In the first case, you need to redefine this routine to return True for such a resource. +Redefine this routine for any resources that have permanently changed location. The framework will generate a 301 Moved Permanently response, and the user agent will automatically redirect the request to (one of) the new location(s) you provide. The user agent will use the new URI for future requests. + ## resource_moved_temporarily -If you have indicated that a resource previously existed, then it may have moved permanently, temporarily, or just ceased to exist. In the second case, you need to redefine this routine to return True for such a resource. +This is for resource that have only been moved for a short period. The framework will generate a 302 Found response. The only substantial difference between this and resource_moved_permanently, is that the agent will use the old URI for future requests. ## previous_location -You need to implement this routine. It should provide the locations where a resource has moved to. There must be at least one such location. If more than one is provided, then the first one is considered primary. - -If the preconditions for this routine are never met (as is the case by default), then just return an empty list. \ No newline at end of file +When you redefine resource_moved_permanently or resource_moved_temporarily, the framework will generate a Location header for the new URI, and a hypertext document to the new URI(s). You **must** redefine this routine to provide those locations (the first one you provide will be in the location header). \ No newline at end of file From bcdfcdd468bd948453b1c83369edf46be477de90 Mon Sep 17 00:00:00 2001 From: colin-adams Date: Wed, 14 Aug 2013 02:23:07 -0700 Subject: [PATCH 56/68] Updated Writing the handlers (markdown) --- Writing-the-handlers.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Writing-the-handlers.md b/Writing-the-handlers.md index 6bc01145..37c43630 100644 --- a/Writing-the-handlers.md +++ b/Writing-the-handlers.md @@ -215,5 +215,5 @@ This routine is called for a normal (updating) PUT request. You have to update t ## Implementing the policies * [WSF_OPTIONS_POLICY](./WSF_OPTIONS_POLICY) -* [WSF_PREVIOUS_POLICY](./WSF_PREVIOUS_POLICY) +* [WSF_PREVIOUS_POLICY](./Wsf-previous-policy) * [WSF_CACHING_POLICY](./WSF_CACHING_POLICY) \ No newline at end of file From aff7948c65088d58eb208ddf91bffd04cb39d284 Mon Sep 17 00:00:00 2001 From: colin-adams Date: Wed, 14 Aug 2013 02:23:53 -0700 Subject: [PATCH 57/68] Updated Writing the handlers (markdown) --- Writing-the-handlers.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Writing-the-handlers.md b/Writing-the-handlers.md index 37c43630..50770637 100644 --- a/Writing-the-handlers.md +++ b/Writing-the-handlers.md @@ -216,4 +216,4 @@ This routine is called for a normal (updating) PUT request. You have to update t * [WSF_OPTIONS_POLICY](./WSF_OPTIONS_POLICY) * [WSF_PREVIOUS_POLICY](./Wsf-previous-policy) -* [WSF_CACHING_POLICY](./WSF_CACHING_POLICY) \ No newline at end of file +* [WSF_CACHING_POLICY](./Wsf-caching-policy) \ No newline at end of file From b2d9fe1a4b7e9d8ff60b9a2ea58a649de17857c8 Mon Sep 17 00:00:00 2001 From: colin-adams Date: Wed, 14 Aug 2013 02:47:12 -0700 Subject: [PATCH 58/68] Updated Writing the handlers (markdown) --- Writing-the-handlers.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Writing-the-handlers.md b/Writing-the-handlers.md index 50770637..87e68a2c 100644 --- a/Writing-the-handlers.md +++ b/Writing-the-handlers.md @@ -13,12 +13,16 @@ Internally, the framework uses the following execution variable names, so you mu 1. NEGOTIATED_MEDIA_TYPE 1. NEGOTIATED_ENCODING 1. NEGOTIATED_HTTP_HEADER +1. CONFLICT_CHECK_CODE +1. CONTENT_CHECK_CODE +1. REQUEST_CHECK_CODE The first one makes the request entity from a PULL or POST request available to your routines. -The next four make the results of content negotiation available to your routines. The last one makes an HTTP_HEADER available to your routines. You should use this rather than create your own, as it may contain a **Vary** header as a by-product of content negotiation. +The next four make the results of content negotiation available to your routines. The sixth one makes an HTTP_HEADER available to your routines. You should use this rather than create your own, as it may contain a **Vary** header as a by-product of content negotiation. +The last three are for reporting the result from check_conflict, check_content and check_request. -All six names are defined as constants in WSF_SKELETON_HANDLER, to make it easier for you to refer to them. +All names are defined as constants in WSF_SKELETON_HANDLER, to make it easier for you to refer to them. ## Implementing the routines declared directly in WSF_SKELETON_HANDLER From 143608fd8590cae60ad3b20f0912ac9c5bcd1d05 Mon Sep 17 00:00:00 2001 From: Colin Adams Date: Wed, 14 Aug 2013 11:32:27 +0100 Subject: [PATCH 59/68] Fixed recursion on router bug --- .../server/wsf/router/wsf_skeleton_handler.e | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/library/server/wsf/router/wsf_skeleton_handler.e b/library/server/wsf/router/wsf_skeleton_handler.e index 511ec692..b7c645be 100644 --- a/library/server/wsf/router/wsf_skeleton_handler.e +++ b/library/server/wsf/router/wsf_skeleton_handler.e @@ -11,7 +11,7 @@ deferred class WSF_SKELETON_HANDLER inherit - WSF_URI_TEMPLATE_ROUTING_HANDLER + WSF_URI_TEMPLATE_HANDLER redefine execute end @@ -26,6 +26,23 @@ inherit 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" From b5957d0f591f433fae74edaf45d44c9c229f1c71 Mon Sep 17 00:00:00 2001 From: Colin Adams Date: Thu, 15 Aug 2013 08:57:54 +0100 Subject: [PATCH 60/68] Removed empty feature clause --- library/server/wsf/router/wsf_method_helper.e | 2 -- 1 file changed, 2 deletions(-) diff --git a/library/server/wsf/router/wsf_method_helper.e b/library/server/wsf/router/wsf_method_helper.e index c64f1484..165a4111 100644 --- a/library/server/wsf/router/wsf_method_helper.e +++ b/library/server/wsf/router/wsf_method_helper.e @@ -364,8 +364,6 @@ feature {NONE} -- Implementation end end -feature -- Errors - feature -- Error reporting From 0755e2d2bca1b85432711bdbd0fae377e6b0e6a8 Mon Sep 17 00:00:00 2001 From: Colin Adams Date: Thu, 15 Aug 2013 10:30:47 +0100 Subject: [PATCH 61/68] Improved comment to ensure_content_exists --- library/server/wsf/router/wsf_skeleton_handler.e | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/server/wsf/router/wsf_skeleton_handler.e b/library/server/wsf/router/wsf_skeleton_handler.e index b7c645be..f63bcd64 100644 --- a/library/server/wsf/router/wsf_skeleton_handler.e +++ b/library/server/wsf/router/wsf_skeleton_handler.e @@ -264,7 +264,7 @@ feature -- DELETE feature -- GET/HEAD content ensure_content_available (req: WSF_REQUEST) - -- Commence generation of response text (entity-body). + -- 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 From 2903a1d3cddd075f9a14f4ed39507bb62b0343b8 Mon Sep 17 00:00:00 2001 From: Colin Adams Date: Thu, 15 Aug 2013 10:31:40 +0100 Subject: [PATCH 62/68] Improved comment to ensure_content_exists - take 2 --- library/server/wsf/router/wsf_skeleton_handler.e | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/server/wsf/router/wsf_skeleton_handler.e b/library/server/wsf/router/wsf_skeleton_handler.e index f63bcd64..e9ba25f1 100644 --- a/library/server/wsf/router/wsf_skeleton_handler.e +++ b/library/server/wsf/router/wsf_skeleton_handler.e @@ -264,7 +264,7 @@ feature -- DELETE 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'. + -- 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 From eefe54755391721cc09d98df5944eb29b62e93f0 Mon Sep 17 00:00:00 2001 From: Colin Adams Date: Thu, 15 Aug 2013 14:58:58 +0100 Subject: [PATCH 63/68] Changed comment on execute to check assertion --- library/server/wsf/router/wsf_skeleton_handler.e | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/library/server/wsf/router/wsf_skeleton_handler.e b/library/server/wsf/router/wsf_skeleton_handler.e index e9ba25f1..60ebf617 100644 --- a/library/server/wsf/router/wsf_skeleton_handler.e +++ b/library/server/wsf/router/wsf_skeleton_handler.e @@ -457,10 +457,9 @@ feature -- Execution -- do check - known_method: True -- Can't be done until WSF_METHOD_NOT_ALLOWED_RESPONSE - -- is refactored. - -- Then maybe this can become a precondition. But we will still (?) - -- need a check that it isn't CONNECT or TRACE (it MIGHT be HEAD). + 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) From 3ae898476f56690320e9fed6362a17aa2cc7d3aa Mon Sep 17 00:00:00 2001 From: Colin Adams Date: Fri, 16 Aug 2013 04:50:48 +0100 Subject: [PATCH 64/68] Changed age to max_age --- examples/restbucksCRUD/src/resource/order_handler.e | 2 +- library/server/wsf/router/wsf_caching_policy.e | 6 +++--- library/server/wsf/router/wsf_method_helper.e | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/restbucksCRUD/src/resource/order_handler.e b/examples/restbucksCRUD/src/resource/order_handler.e index 1ea6f836..91d1b9ae 100644 --- a/examples/restbucksCRUD/src/resource/order_handler.e +++ b/examples/restbucksCRUD/src/resource/order_handler.e @@ -99,7 +99,7 @@ feature -- Access Result.compare_objects end - age (req: WSF_REQUEST): NATURAL + 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. diff --git a/library/server/wsf/router/wsf_caching_policy.e b/library/server/wsf/router/wsf_caching_policy.e index bc9a4687..35f104de 100644 --- a/library/server/wsf/router/wsf_caching_policy.e +++ b/library/server/wsf/router/wsf_caching_policy.e @@ -14,7 +14,7 @@ feature -- Access -- 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 - age (req: WSF_REQUEST): NATURAL + 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. @@ -35,7 +35,7 @@ feature -- Access require req_attached: req /= Void do - Result := age (req) + Result := max_age (req) ensure not_more_than_1_year: Result <= Never_expires end @@ -51,7 +51,7 @@ feature -- Access require req_attached: req /= Void do - Result := age (req) + Result := max_age (req) ensure not_more_than_1_year: Result <= Never_expires end diff --git a/library/server/wsf/router/wsf_method_helper.e b/library/server/wsf/router/wsf_method_helper.e index 165a4111..3f3037e8 100644 --- a/library/server/wsf/router/wsf_method_helper.e +++ b/library/server/wsf/router/wsf_method_helper.e @@ -315,7 +315,7 @@ feature {NONE} -- Implementation 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.age (req) + 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 From 37b94bbf0d3e3836c023f9ee95442bc2aa98fd4e Mon Sep 17 00:00:00 2001 From: Colin Adams Date: Mon, 19 Aug 2013 11:48:49 +0100 Subject: [PATCH 65/68] Added header comment about redefining for extension methods --- library/server/wsf/router/wsf_method_helper_factory.e | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/library/server/wsf/router/wsf_method_helper_factory.e b/library/server/wsf/router/wsf_method_helper_factory.e index 48f773c4..633599b6 100644 --- a/library/server/wsf/router/wsf_method_helper_factory.e +++ b/library/server/wsf/router/wsf_method_helper_factory.e @@ -12,7 +12,8 @@ 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' + -- New object for processing `a_method'; + -- Redefine this routine to implement extension methods. require a_method_attached: a_method /= Void do From 9958bb27a1dd55f637e542b97c3b62ca7368968a Mon Sep 17 00:00:00 2001 From: Jocelyn Fiat Date: Tue, 20 Aug 2013 13:15:37 +0200 Subject: [PATCH 66/68] Removed WSF_ROUTING_HANDLER.make_with_router (a_router) It was not used in existing code, and potentially dangerous, if coder reuses router by accident. --- .../starts_with/helpers/wsf_starts_with_routing_handler.e | 5 ++--- .../router/support/uri/helpers/wsf_uri_routing_handler.e | 5 ++--- .../helpers/wsf_uri_template_routing_handler.e | 5 ++--- library/server/wsf/router/wsf_routing_handler.e | 7 +------ .../helpers/wsf_starts_with_routing_context_handler.e | 5 ++--- .../support/uri/helpers/wsf_uri_routing_context_handler.e | 5 ++--- .../helpers/wsf_uri_template_routing_context_handler.e | 5 ++--- .../wsf/router_context/wsf_routing_context_handler.e | 5 ++--- 8 files changed, 15 insertions(+), 27 deletions(-) diff --git a/library/server/wsf/router/support/starts_with/helpers/wsf_starts_with_routing_handler.e b/library/server/wsf/router/support/starts_with/helpers/wsf_starts_with_routing_handler.e index b247c358..088ff44b 100644 --- a/library/server/wsf/router/support/starts_with/helpers/wsf_starts_with_routing_handler.e +++ b/library/server/wsf/router/support/starts_with/helpers/wsf_starts_with_routing_handler.e @@ -16,8 +16,7 @@ inherit end create - make, - make_with_router + make feature -- Execution @@ -29,7 +28,7 @@ feature -- Execution end 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)" source: "[ Eiffel Software diff --git a/library/server/wsf/router/support/uri/helpers/wsf_uri_routing_handler.e b/library/server/wsf/router/support/uri/helpers/wsf_uri_routing_handler.e index 27798b6e..3cb787e0 100644 --- a/library/server/wsf/router/support/uri/helpers/wsf_uri_routing_handler.e +++ b/library/server/wsf/router/support/uri/helpers/wsf_uri_routing_handler.e @@ -13,11 +13,10 @@ inherit WSF_URI_HANDLER create - make, - make_with_router + make 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)" source: "[ Eiffel Software diff --git a/library/server/wsf/router/support/uri_template/helpers/wsf_uri_template_routing_handler.e b/library/server/wsf/router/support/uri_template/helpers/wsf_uri_template_routing_handler.e index 48e401a6..adc7b374 100644 --- a/library/server/wsf/router/support/uri_template/helpers/wsf_uri_template_routing_handler.e +++ b/library/server/wsf/router/support/uri_template/helpers/wsf_uri_template_routing_handler.e @@ -13,11 +13,10 @@ inherit WSF_URI_TEMPLATE_HANDLER create - make, - make_with_router + make 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)" source: "[ Eiffel Software diff --git a/library/server/wsf/router/wsf_routing_handler.e b/library/server/wsf/router/wsf_routing_handler.e index 5aa855e5..33ae7cfb 100644 --- a/library/server/wsf/router/wsf_routing_handler.e +++ b/library/server/wsf/router/wsf_routing_handler.e @@ -12,14 +12,9 @@ inherit feature {NONE} -- Initialization - make_with_router (a_router: like router) - do - router := a_router - end - make (n: INTEGER) do - make_with_router (create {like router}.make (n)) + create router.make (n) end feature -- Access diff --git a/library/server/wsf/router_context/support/starts_with/helpers/wsf_starts_with_routing_context_handler.e b/library/server/wsf/router_context/support/starts_with/helpers/wsf_starts_with_routing_context_handler.e index 74965647..064ddac8 100644 --- a/library/server/wsf/router_context/support/starts_with/helpers/wsf_starts_with_routing_context_handler.e +++ b/library/server/wsf/router_context/support/starts_with/helpers/wsf_starts_with_routing_context_handler.e @@ -16,8 +16,7 @@ inherit end create - make, - make_with_router + make feature -- Execution @@ -27,7 +26,7 @@ feature -- Execution end 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)" source: "[ Eiffel Software diff --git a/library/server/wsf/router_context/support/uri/helpers/wsf_uri_routing_context_handler.e b/library/server/wsf/router_context/support/uri/helpers/wsf_uri_routing_context_handler.e index f76caf41..5a084d81 100644 --- a/library/server/wsf/router_context/support/uri/helpers/wsf_uri_routing_context_handler.e +++ b/library/server/wsf/router_context/support/uri/helpers/wsf_uri_routing_context_handler.e @@ -18,8 +18,7 @@ inherit end create - make, - make_with_router + make feature -- Execution @@ -31,7 +30,7 @@ feature -- Execution end 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)" source: "[ Eiffel Software diff --git a/library/server/wsf/router_context/support/uri_template/helpers/wsf_uri_template_routing_context_handler.e b/library/server/wsf/router_context/support/uri_template/helpers/wsf_uri_template_routing_context_handler.e index 6ddb157c..bdf7ff6b 100644 --- a/library/server/wsf/router_context/support/uri_template/helpers/wsf_uri_template_routing_context_handler.e +++ b/library/server/wsf/router_context/support/uri_template/helpers/wsf_uri_template_routing_context_handler.e @@ -18,8 +18,7 @@ inherit end create - make, - make_with_router + make feature -- Execution @@ -31,7 +30,7 @@ feature -- Execution end 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)" source: "[ Eiffel Software diff --git a/library/server/wsf/router_context/wsf_routing_context_handler.e b/library/server/wsf/router_context/wsf_routing_context_handler.e index 25e784da..214a542d 100644 --- a/library/server/wsf/router_context/wsf_routing_context_handler.e +++ b/library/server/wsf/router_context/wsf_routing_context_handler.e @@ -15,8 +15,7 @@ inherit end create - make, - make_with_router + make feature -- Execution @@ -26,7 +25,7 @@ feature -- Execution end 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)" source: "[ Eiffel Software From 07f71dfc4e970a44578efdf6f7cd38f1df3aeb3f Mon Sep 17 00:00:00 2001 From: Jocelyn Fiat Date: Tue, 20 Aug 2013 13:26:55 +0200 Subject: [PATCH 67/68] Moved recent policy-driven classes into "policy" sub folder --- library/server/wsf/router/{ => policy}/wsf_caching_policy.e | 0 library/server/wsf/router/{ => policy}/wsf_delete_helper.e | 0 library/server/wsf/router/{ => policy}/wsf_get_helper.e | 0 library/server/wsf/router/{ => policy}/wsf_method_helper.e | 0 .../server/wsf/router/{ => policy}/wsf_method_helper_factory.e | 0 library/server/wsf/router/{ => policy}/wsf_no_proxy_policy.e | 0 library/server/wsf/router/{ => policy}/wsf_options_policy.e | 0 library/server/wsf/router/{ => policy}/wsf_post_helper.e | 0 library/server/wsf/router/{ => policy}/wsf_previous_policy.e | 0 library/server/wsf/router/{ => policy}/wsf_proxy_use_policy.e | 0 library/server/wsf/router/{ => policy}/wsf_put_helper.e | 0 .../server/wsf/router/{ => policy}/wsf_routed_skeleton_service.e | 0 library/server/wsf/router/{ => policy}/wsf_skeleton_handler.e | 0 .../wsf/router/{ => policy}/wsf_system_options_access_policy.e | 0 14 files changed, 0 insertions(+), 0 deletions(-) rename library/server/wsf/router/{ => policy}/wsf_caching_policy.e (100%) rename library/server/wsf/router/{ => policy}/wsf_delete_helper.e (100%) rename library/server/wsf/router/{ => policy}/wsf_get_helper.e (100%) rename library/server/wsf/router/{ => policy}/wsf_method_helper.e (100%) rename library/server/wsf/router/{ => policy}/wsf_method_helper_factory.e (100%) rename library/server/wsf/router/{ => policy}/wsf_no_proxy_policy.e (100%) rename library/server/wsf/router/{ => policy}/wsf_options_policy.e (100%) rename library/server/wsf/router/{ => policy}/wsf_post_helper.e (100%) rename library/server/wsf/router/{ => policy}/wsf_previous_policy.e (100%) rename library/server/wsf/router/{ => policy}/wsf_proxy_use_policy.e (100%) rename library/server/wsf/router/{ => policy}/wsf_put_helper.e (100%) rename library/server/wsf/router/{ => policy}/wsf_routed_skeleton_service.e (100%) rename library/server/wsf/router/{ => policy}/wsf_skeleton_handler.e (100%) rename library/server/wsf/router/{ => policy}/wsf_system_options_access_policy.e (100%) diff --git a/library/server/wsf/router/wsf_caching_policy.e b/library/server/wsf/router/policy/wsf_caching_policy.e similarity index 100% rename from library/server/wsf/router/wsf_caching_policy.e rename to library/server/wsf/router/policy/wsf_caching_policy.e diff --git a/library/server/wsf/router/wsf_delete_helper.e b/library/server/wsf/router/policy/wsf_delete_helper.e similarity index 100% rename from library/server/wsf/router/wsf_delete_helper.e rename to library/server/wsf/router/policy/wsf_delete_helper.e diff --git a/library/server/wsf/router/wsf_get_helper.e b/library/server/wsf/router/policy/wsf_get_helper.e similarity index 100% rename from library/server/wsf/router/wsf_get_helper.e rename to library/server/wsf/router/policy/wsf_get_helper.e diff --git a/library/server/wsf/router/wsf_method_helper.e b/library/server/wsf/router/policy/wsf_method_helper.e similarity index 100% rename from library/server/wsf/router/wsf_method_helper.e rename to library/server/wsf/router/policy/wsf_method_helper.e diff --git a/library/server/wsf/router/wsf_method_helper_factory.e b/library/server/wsf/router/policy/wsf_method_helper_factory.e similarity index 100% rename from library/server/wsf/router/wsf_method_helper_factory.e rename to library/server/wsf/router/policy/wsf_method_helper_factory.e diff --git a/library/server/wsf/router/wsf_no_proxy_policy.e b/library/server/wsf/router/policy/wsf_no_proxy_policy.e similarity index 100% rename from library/server/wsf/router/wsf_no_proxy_policy.e rename to library/server/wsf/router/policy/wsf_no_proxy_policy.e diff --git a/library/server/wsf/router/wsf_options_policy.e b/library/server/wsf/router/policy/wsf_options_policy.e similarity index 100% rename from library/server/wsf/router/wsf_options_policy.e rename to library/server/wsf/router/policy/wsf_options_policy.e diff --git a/library/server/wsf/router/wsf_post_helper.e b/library/server/wsf/router/policy/wsf_post_helper.e similarity index 100% rename from library/server/wsf/router/wsf_post_helper.e rename to library/server/wsf/router/policy/wsf_post_helper.e diff --git a/library/server/wsf/router/wsf_previous_policy.e b/library/server/wsf/router/policy/wsf_previous_policy.e similarity index 100% rename from library/server/wsf/router/wsf_previous_policy.e rename to library/server/wsf/router/policy/wsf_previous_policy.e diff --git a/library/server/wsf/router/wsf_proxy_use_policy.e b/library/server/wsf/router/policy/wsf_proxy_use_policy.e similarity index 100% rename from library/server/wsf/router/wsf_proxy_use_policy.e rename to library/server/wsf/router/policy/wsf_proxy_use_policy.e diff --git a/library/server/wsf/router/wsf_put_helper.e b/library/server/wsf/router/policy/wsf_put_helper.e similarity index 100% rename from library/server/wsf/router/wsf_put_helper.e rename to library/server/wsf/router/policy/wsf_put_helper.e diff --git a/library/server/wsf/router/wsf_routed_skeleton_service.e b/library/server/wsf/router/policy/wsf_routed_skeleton_service.e similarity index 100% rename from library/server/wsf/router/wsf_routed_skeleton_service.e rename to library/server/wsf/router/policy/wsf_routed_skeleton_service.e diff --git a/library/server/wsf/router/wsf_skeleton_handler.e b/library/server/wsf/router/policy/wsf_skeleton_handler.e similarity index 100% rename from library/server/wsf/router/wsf_skeleton_handler.e rename to library/server/wsf/router/policy/wsf_skeleton_handler.e diff --git a/library/server/wsf/router/wsf_system_options_access_policy.e b/library/server/wsf/router/policy/wsf_system_options_access_policy.e similarity index 100% rename from library/server/wsf/router/wsf_system_options_access_policy.e rename to library/server/wsf/router/policy/wsf_system_options_access_policy.e From 265117129441aa8ec18386e50ff829e771d1e6f0 Mon Sep 17 00:00:00 2001 From: Jocelyn Fiat Date: Tue, 20 Aug 2013 17:27:57 +0200 Subject: [PATCH 68/68] Extracted the policy driven classes into their own library for now "wsf_policy_driven.ecf" Updated the restbucksCRUD example to demonstrate both approaches. --- examples/restbucksCRUD/README-compilation.txt | 3 + examples/restbucksCRUD/restbucks-safe.ecf | 33 +- .../policy_driven_resource/order_handler.e | 558 ++++++++++++++++ .../src/resource/order_handler.e | 603 +++++++----------- .../wsf_delete_helper.e | 0 .../policy => policy_driven}/wsf_get_helper.e | 0 .../wsf_method_helper.e | 0 .../wsf_method_helper_factory.e | 0 .../wsf_post_helper.e | 0 .../policy => policy_driven}/wsf_put_helper.e | 0 .../wsf_skeleton_handler.e | 0 .../wsf_routed_skeleton_service.e | 12 +- library/server/wsf/wsf-safe.ecf | 7 +- library/server/wsf/wsf.ecf | 7 +- library/server/wsf/wsf_policy_driven-safe.ecf | 22 + library/server/wsf/wsf_policy_driven.ecf | 22 + tests/all-safe.ecf | 1 + 17 files changed, 870 insertions(+), 398 deletions(-) create mode 100644 examples/restbucksCRUD/README-compilation.txt create mode 100644 examples/restbucksCRUD/src/policy_driven_resource/order_handler.e rename library/server/wsf/{router/policy => policy_driven}/wsf_delete_helper.e (100%) rename library/server/wsf/{router/policy => policy_driven}/wsf_get_helper.e (100%) rename library/server/wsf/{router/policy => policy_driven}/wsf_method_helper.e (100%) rename library/server/wsf/{router/policy => policy_driven}/wsf_method_helper_factory.e (100%) rename library/server/wsf/{router/policy => policy_driven}/wsf_post_helper.e (100%) rename library/server/wsf/{router/policy => policy_driven}/wsf_put_helper.e (100%) rename library/server/wsf/{router/policy => policy_driven}/wsf_skeleton_handler.e (100%) rename library/server/wsf/router/policy/{ => service}/wsf_routed_skeleton_service.e (97%) create mode 100644 library/server/wsf/wsf_policy_driven-safe.ecf create mode 100644 library/server/wsf/wsf_policy_driven.ecf diff --git a/examples/restbucksCRUD/README-compilation.txt b/examples/restbucksCRUD/README-compilation.txt new file mode 100644 index 00000000..6e3dfa50 --- /dev/null +++ b/examples/restbucksCRUD/README-compilation.txt @@ -0,0 +1,3 @@ +The current example has a main target for the server: "restbucks" +But we also provide "policy_driven_restbucks" target which is using the +policy-driven framework than help coder fulfill HTTP expectations. diff --git a/examples/restbucksCRUD/restbucks-safe.ecf b/examples/restbucksCRUD/restbucks-safe.ecf index 08715841..d25369fa 100644 --- a/examples/restbucksCRUD/restbucks-safe.ecf +++ b/examples/restbucksCRUD/restbucks-safe.ecf @@ -1,16 +1,11 @@ - - + /EIFGENs$ /\.git$ /\.svn$ - @@ -29,6 +24,30 @@ - + + + + + + + /policy_driven_resource$ + + + + + + + + + + /resource$ + + diff --git a/examples/restbucksCRUD/src/policy_driven_resource/order_handler.e b/examples/restbucksCRUD/src/policy_driven_resource/order_handler.e new file mode 100644 index 00000000..91d1b9ae --- /dev/null +++ b/examples/restbucksCRUD/src/policy_driven_resource/order_handler.e @@ -0,0 +1,558 @@ +note + description: "{ORDER_HANDLER} handle the resources that we want to expose" + author: "" + date: "$Date$" + revision: "$Revision$" + +class ORDER_HANDLER + +inherit + + WSF_SKELETON_HANDLER + + SHARED_DATABASE_API + + SHARED_EJSON + + REFACTORING_HELPER + + SHARED_ORDER_VALIDATION + + WSF_RESOURCE_HANDLER_HELPER + rename + execute_options as helper_execute_options, + handle_internal_server_error as helper_handle_internal_server_error + end + +create + + make_with_router + + +feature -- Execution variables + + Order_execution_variable: STRING = "ORDER" + -- Execution variable used by application + + Generated_content_execution_variable: STRING = "GENERATED_CONTENT" + -- Execution variable used by application + + Extracted_order_execution_variable: STRING = "EXTRACTED_ORDER" + -- Execution variable used by application + +feature -- Documentation + + description: READABLE_STRING_GENERAL + -- General description for self-generated documentation; + -- The specific URI templates supported will be described automatically + do + Result := "Create, Read, Update or Delete an ORDER." + end + +feature -- Access + + is_chunking (req: WSF_REQUEST): BOOLEAN + -- Will the response to `req' using chunked transfer encoding? + do + -- No. + end + + includes_response_entity (req: WSF_REQUEST): BOOLEAN + -- Does the response to `req' include an entity? + -- Method will be DELETE, POST, PUT or an extension method. + do + Result := False + -- At present, there is no support for this except for DELETE. + end + + conneg (req: WSF_REQUEST): CONNEG_SERVER_SIDE + -- Content negotiatior for all requests + once + create Result.make ({HTTP_MIME_TYPES}.application_json, "en", "UTF-8", "identity") + end + + mime_types_supported (req: WSF_REQUEST): LIST [STRING] + -- All values for Accept header that `Current' can serve + do + create {ARRAYED_LIST [STRING]} Result.make_from_array (<<{HTTP_MIME_TYPES}.application_json>>) + Result.compare_objects + end + + languages_supported (req: WSF_REQUEST): LIST [STRING] + -- All values for Accept-Language header that `Current' can serve + do + create {ARRAYED_LIST [STRING]} Result.make_from_array (<<"en">>) + Result.compare_objects + end + + charsets_supported (req: WSF_REQUEST): LIST [STRING] + -- All values for Accept-Charset header that `Current' can serve + do + create {ARRAYED_LIST [STRING]} Result.make_from_array (<<"UTF-8">>) + Result.compare_objects + end + + encodings_supported (req: WSF_REQUEST): LIST [STRING] + -- All values for Accept-Encoding header that `Current' can serve + do + create {ARRAYED_LIST [STRING]} Result.make_from_array (<<"identity">>) + Result.compare_objects + end + + 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. + do + -- All our responses are considered stale. + 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. + do + -- definitely not! + 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. + do + create {ARRAYED_LIST [READABLE_STRING_8]} Result.make (0) + 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. + do + create {ARRAYED_LIST [READABLE_STRING_8]} Result.make (0) + 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. + do + Result := True + -- since it's commercial data. + 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. + local + l_id: STRING + l_etag_util: ETAG_UTILS + do + l_id := order_id_from_request (req) + if db_access.orders.has_key (l_id) then + check attached db_access.orders.item (l_id) as l_order then + -- postcondition of `has_key' + create l_etag_util + Result := a_etag.same_string (l_etag_util.md5_digest (l_order.out).as_string_32) + end + end + end + + etag (req: WSF_REQUEST): detachable READABLE_STRING_8 + -- Optional Etag for `req' in the requested variant + local + l_etag_utils: ETAG_UTILS + do + create l_etag_utils + if attached {ORDER} req.execution_variable (Order_execution_variable) as l_order then + Result := l_etag_utils.md5_digest (l_order.out) + end + end + + modified_since (req: WSF_REQUEST; a_date_time: DATE_TIME): BOOLEAN + -- Has resource requested in `req' been modified since `a_date_time' (UTC)? + do + -- We don't track this information. It is safe to always say yes. + Result := True + end + +feature -- Measurement + + content_length (req: WSF_REQUEST): NATURAL + -- Length of entity-body of the response to `req' + do + check attached {READABLE_STRING_8} req.execution_variable (Generated_content_execution_variable) as l_response then + -- postcondition generated_content_set_for_get_head of `ensure_content_available' + -- We only call this for GET/HEAD in this example. + Result := l_response.count.as_natural_32 + end + 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? + do + -- No. + end + +feature -- Status report + + finished (req: WSF_REQUEST): BOOLEAN + -- Has the last chunk been generated for `req'? + do + -- precondition is never met + end + +feature -- Execution + + 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. + -- We also put the order into `req.execution_variable (Order_execution_variable)' for GET or HEAD responses. + local + l_id: STRING + do + if req.is_post_request_method then + a_helper.set_resource_exists + -- because only /order is defined to this handler for POST + else + -- the request is of the form /order/{orderid} + l_id := order_id_from_request (req) + if db_access.orders.has_key (l_id) then + a_helper.set_resource_exists + if req.is_get_head_request_method then + check attached db_access.orders.item (l_id) as l_order then + -- postcondition `item_if_found' of `has_key' + req.set_execution_variable (Order_execution_variable, l_order) + end + end + end + end + ensure then + order_saved_only_for_get_head: req.is_get_head_request_method = + attached {ORDER} req.execution_variable (Order_execution_variable) + end + +feature -- GET/HEAD content + + ensure_content_available (req: WSF_REQUEST) + -- Commence generation of response text (entity-body). + -- 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. + -- We save the text in `req.execution_variable (Generated_content_execution_variable)' + -- We ignore the results of content negotiation, as there is only one possible combination. + do + check attached {ORDER} req.execution_variable (Order_execution_variable) as l_order then + -- precondition get_or_head and postcondition order_saved_only_for_get_head of `check_resource_exists' and + if attached {JSON_VALUE} json.value (l_order) as jv then + req.set_execution_variable (Generated_content_execution_variable, jv.representation) + else + req.set_execution_variable (Generated_content_execution_variable, "") + end + end + ensure then + generated_content_set_for_get_head: req.is_get_head_request_method implies + attached {READABLE_STRING_8} req.execution_variable (Generated_content_execution_variable) + end + + content (req: WSF_REQUEST): READABLE_STRING_8 + -- Non-chunked entity body in response to `req'; + -- We only call this for GET/HEAD in this example. + do + check attached {READABLE_STRING_8} req.execution_variable (Generated_content_execution_variable) as l_response then + -- postcondition generated_content_set_for_get_head of `ensure_content_available' + Result := l_response + end + end + + next_chunk (req: WSF_REQUEST): TUPLE [a_chunk: 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. + do + -- precondition `is_chunking' is never met, but we need a dummy `Result' + -- to satisfy the compiler in void-safe mode + Result := ["", Void] + 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. + do + -- precondition `is_chunking' is never met + end + +feature -- DELETE + + delete (req: WSF_REQUEST) + -- Delete resource named in `req' or set an error on `req.error_handler'. + local + l_id: STRING + do + l_id := order_id_from_request (req) + if db_access.orders.has_key (l_id) then + if is_valid_to_delete (l_id) then + delete_order (l_id) + else + req.error_handler.add_custom_error ({HTTP_STATUS_CODE}.method_not_allowed, "DELETE not valid", + "There is conflict while trying to delete the order, the order could not be deleted in the current state") + end + else + req.error_handler.add_custom_error ({HTTP_STATUS_CODE}.not_found, "DELETE not valid", + "There is no such order to delete") + end + end + + delete_queued (req: WSF_REQUEST): BOOLEAN + -- Has resource named by `req' been queued for deletion? + do + -- No + end + + +feature -- PUT/POST + + 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? + do + -- No. We don't care for this example. + 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. + do + -- We don't bother for this example. Note that this is equivalent to setting zero. + 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. + do + -- We don't support creating a new resource with PUT. But this can't happen + -- with our router mappings, so we don't bother to set a 500 response. + 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. + do + if attached {ORDER} req.execution_variable (Extracted_order_execution_variable) as l_order then + save_order (l_order) + compute_response_post (req, res, l_order) + else + handle_bad_request_response ("Not a valid order", req, res) + end + end + + check_conflict (req: WSF_REQUEST; res: WSF_RESPONSE) + -- Check we can support all content headers on request entity. + -- 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'. + do + if attached {ORDER} req.execution_variable (Extracted_order_execution_variable) as l_order then + if not is_valid_to_update (l_order) then + req.set_execution_variable (Conflict_check_code_execution_variable, {NATURAL} 409) + handle_resource_conflict_response (l_order.out +"%N There is conflict while trying to update the order, the order could not be update in the current state", req, res) + end + else + req.set_execution_variable (Conflict_check_code_execution_variable, {NATURAL} 409) + --| This ought to be a 500, as if attached should probably be check attached. But as yet I lack a proof. + handle_resource_conflict_response ("There is conflict while trying to update the order, the order could not be update in the current state", req, res) + 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'. + local + l_order: detachable ORDER + l_id: STRING + do + if attached {READABLE_STRING_8} req.execution_variable (Request_entity_execution_variable) as l_request then + l_order := extract_order_request (l_request) + if req.is_put_request_method then + l_id := order_id_from_request (req) + if l_order /= Void and then db_access.orders.has_key (l_id) then + l_order.set_id (l_id) + req.set_execution_variable (Request_check_code_execution_variable, {NATURAL} 0) + req.set_execution_variable (Extracted_order_execution_variable, l_order) + else + req.set_execution_variable (Request_check_code_execution_variable, {NATURAL} 400) + handle_bad_request_response (l_request +"%N is not a valid ORDER, maybe the order does not exist in the system", req, res) + end + else + req.set_execution_variable (Request_check_code_execution_variable, {NATURAL} 0) + req.set_execution_variable (Extracted_order_execution_variable, l_order) + end + else + req.set_execution_variable (Request_check_code_execution_variable, {NATURAL} 400) + handle_bad_request_response ("Request is not a valid ORDER", req, res) + 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. + do + if attached {ORDER} req.execution_variable (Extracted_order_execution_variable) as l_order then + update_order (l_order) + compute_response_put (req, res, l_order) + else + handle_internal_server_error (res) + end + end + +feature -- HTTP Methods + + compute_response_put (req: WSF_REQUEST; res: WSF_RESPONSE; l_order : ORDER) + local + h: HTTP_HEADER + joc : JSON_ORDER_CONVERTER + etag_utils : ETAG_UTILS + do + create h.make + create joc.make + create etag_utils + json.add_converter(joc) + + create h.make + h.put_content_type_application_json + if attached req.request_time as time then + h.add_header ("Date:" +time.formatted_out ("ddd,[0]dd mmm yyyy [0]hh:[0]mi:[0]ss.ff2") + " GMT") + end + h.add_header ("etag:" + etag_utils.md5_digest (l_order.out)) + if attached {JSON_VALUE} json.value (l_order) as jv then + h.put_content_length (jv.representation.count) + res.set_status_code ({HTTP_STATUS_CODE}.ok) + res.put_header_text (h.string) + res.put_string (jv.representation) + end + end + + compute_response_post (req: WSF_REQUEST; res: WSF_RESPONSE; l_order : ORDER) + local + h: HTTP_HEADER + l_msg : STRING + l_location : STRING + joc : JSON_ORDER_CONVERTER + do + create h.make + + create joc.make + json.add_converter(joc) + + h.put_content_type_application_json + if attached {JSON_VALUE} json.value (l_order) as jv then + l_msg := jv.representation + h.put_content_length (l_msg.count) + if attached req.http_host as host then + l_location := "http://" + host + req.request_uri + "/" + l_order.id + h.put_location (l_location) + end + if attached req.request_time as time then + h.put_utc_date (time) + end + res.set_status_code ({HTTP_STATUS_CODE}.created) + res.put_header_text (h.string) + res.put_string (l_msg) + end + end + +feature {NONE} -- URI helper methods + + order_id_from_request (req: WSF_REQUEST): STRING + -- Value of "orderid" template URI variable in `req' + require + req_attached: req /= Void + do + if attached {WSF_VALUE} req.path_parameter ("orderid") as l_value then + Result := l_value.as_string.value.as_string_8 + else + Result := "" + end + end + +feature {NONE} -- Implementation Repository Layer + + retrieve_order ( id : STRING) : detachable ORDER + -- get the order by id if it exist, in other case, Void + do + Result := db_access.orders.item (id) + end + + save_order (an_order: ORDER) + -- save the order to the repository + local + i : INTEGER + do + from + i := 1 + until + not db_access.orders.has_key ((db_access.orders.count + i).out) + loop + i := i + 1 + end + an_order.set_id ((db_access.orders.count + i).out) + an_order.set_status ("submitted") + an_order.add_revision + db_access.orders.force (an_order, an_order.id) + end + + + is_valid_to_delete ( an_id : STRING) : BOOLEAN + -- Is the order identified by `an_id' in a state whre it can still be deleted? + do + if attached retrieve_order (an_id) as l_order then + if order_validation.is_state_valid_to_update (l_order.status) then + Result := True + end + end + end + + is_valid_to_update (an_order: ORDER) : BOOLEAN + -- Check if there is a conflict while trying to update the order + do + if attached retrieve_order (an_order.id) as l_order then + if order_validation.is_state_valid_to_update (l_order.status) and then order_validation.is_valid_status_state (an_order.status) and then + order_validation.is_valid_transition (l_order, an_order.status) then + Result := True + end + end + end + + update_order (an_order: ORDER) + -- update the order to the repository + do + an_order.add_revision + db_access.orders.force (an_order, an_order.id) + end + + delete_order (an_order: STRING) + -- update the order to the repository + do + db_access.orders.remove (an_order) + end + + extract_order_request (l_post : STRING) : detachable ORDER + -- extract an object Order from the request, or Void + -- if the request is invalid + local + parser : JSON_PARSER + joc : JSON_ORDER_CONVERTER + do + create joc.make + json.add_converter(joc) + create parser.make_parser (l_post) + if attached parser.parse as jv and parser.is_parsed then + if attached {like extract_order_request} json.object (jv, "ORDER") as res then + Result := res + end + end + end + +note + copyright: "2011-2013, Javier Velilla and others" + license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)" +end diff --git a/examples/restbucksCRUD/src/resource/order_handler.e b/examples/restbucksCRUD/src/resource/order_handler.e index 91d1b9ae..c7b6693e 100644 --- a/examples/restbucksCRUD/src/resource/order_handler.e +++ b/examples/restbucksCRUD/src/resource/order_handler.e @@ -5,10 +5,17 @@ note revision: "$Revision$" class ORDER_HANDLER - inherit - WSF_SKELETON_HANDLER + WSF_URI_TEMPLATE_HANDLER + + WSF_RESOURCE_HANDLER_HELPER + redefine + do_get, + do_post, + do_put, + do_delete + end SHARED_DATABASE_API @@ -18,394 +25,174 @@ inherit SHARED_ORDER_VALIDATION - WSF_RESOURCE_HANDLER_HELPER - rename - execute_options as helper_execute_options, - handle_internal_server_error as helper_handle_internal_server_error - end + WSF_SELF_DOCUMENTED_HANDLER create - make_with_router +feature {NONE} -- Initialization -feature -- Execution variables + 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 - Order_execution_variable: STRING = "ORDER" - -- Execution variable used by application +feature -- Router - Generated_content_execution_variable: STRING = "GENERATED_CONTENT" - -- Execution variable used by application + router: WSF_ROUTER + -- Associated router that could be used for advanced strategy + +feature -- Execute + + execute (req: WSF_REQUEST; res: WSF_RESPONSE) + -- Execute request handler + do + execute_methods (req, res) + end + +feature -- API DOC + + api_doc : STRING = "URI:/order METHOD: POST%N URI:/order/{orderid} METHOD: GET, PUT, DELETE%N" - Extracted_order_execution_variable: STRING = "EXTRACTED_ORDER" - -- Execution variable used by application feature -- Documentation - description: READABLE_STRING_GENERAL - -- General description for self-generated documentation; - -- The specific URI templates supported will be described automatically + mapping_documentation (m: WSF_ROUTER_MAPPING; a_request_methods: detachable WSF_REQUEST_METHODS): WSF_ROUTER_MAPPING_DOCUMENTATION do - Result := "Create, Read, Update or Delete an ORDER." - end - -feature -- Access - - is_chunking (req: WSF_REQUEST): BOOLEAN - -- Will the response to `req' using chunked transfer encoding? - do - -- No. - end - - includes_response_entity (req: WSF_REQUEST): BOOLEAN - -- Does the response to `req' include an entity? - -- Method will be DELETE, POST, PUT or an extension method. - do - Result := False - -- At present, there is no support for this except for DELETE. - end - - conneg (req: WSF_REQUEST): CONNEG_SERVER_SIDE - -- Content negotiatior for all requests - once - create Result.make ({HTTP_MIME_TYPES}.application_json, "en", "UTF-8", "identity") - end - - mime_types_supported (req: WSF_REQUEST): LIST [STRING] - -- All values for Accept header that `Current' can serve - do - create {ARRAYED_LIST [STRING]} Result.make_from_array (<<{HTTP_MIME_TYPES}.application_json>>) - Result.compare_objects - end - - languages_supported (req: WSF_REQUEST): LIST [STRING] - -- All values for Accept-Language header that `Current' can serve - do - create {ARRAYED_LIST [STRING]} Result.make_from_array (<<"en">>) - Result.compare_objects - end - - charsets_supported (req: WSF_REQUEST): LIST [STRING] - -- All values for Accept-Charset header that `Current' can serve - do - create {ARRAYED_LIST [STRING]} Result.make_from_array (<<"UTF-8">>) - Result.compare_objects - end - - encodings_supported (req: WSF_REQUEST): LIST [STRING] - -- All values for Accept-Encoding header that `Current' can serve - do - create {ARRAYED_LIST [STRING]} Result.make_from_array (<<"identity">>) - Result.compare_objects - end - - 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. - do - -- All our responses are considered stale. - 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. - do - -- definitely not! - 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. - do - create {ARRAYED_LIST [READABLE_STRING_8]} Result.make (0) - 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. - do - create {ARRAYED_LIST [READABLE_STRING_8]} Result.make (0) - 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. - do - Result := True - -- since it's commercial data. - 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. - local - l_id: STRING - l_etag_util: ETAG_UTILS - do - l_id := order_id_from_request (req) - if db_access.orders.has_key (l_id) then - check attached db_access.orders.item (l_id) as l_order then - -- postcondition of `has_key' - create l_etag_util - Result := a_etag.same_string (l_etag_util.md5_digest (l_order.out).as_string_32) + create Result.make (m) + if a_request_methods /= Void then + if a_request_methods.has_method_post then + Result.add_description ("URI:/order METHOD: POST") + elseif + a_request_methods.has_method_get + or a_request_methods.has_method_put + or a_request_methods.has_method_delete + then + Result.add_description ("URI:/order/{orderid} METHOD: GET, PUT, DELETE") end end end - etag (req: WSF_REQUEST): detachable READABLE_STRING_8 - -- Optional Etag for `req' in the requested variant - local - l_etag_utils: ETAG_UTILS - do - create l_etag_utils - if attached {ORDER} req.execution_variable (Order_execution_variable) as l_order then - Result := l_etag_utils.md5_digest (l_order.out) - end - end - - modified_since (req: WSF_REQUEST; a_date_time: DATE_TIME): BOOLEAN - -- Has resource requested in `req' been modified since `a_date_time' (UTC)? - do - -- We don't track this information. It is safe to always say yes. - Result := True - end - -feature -- Measurement - - content_length (req: WSF_REQUEST): NATURAL - -- Length of entity-body of the response to `req' - do - check attached {READABLE_STRING_8} req.execution_variable (Generated_content_execution_variable) as l_response then - -- postcondition generated_content_set_for_get_head of `ensure_content_available' - -- We only call this for GET/HEAD in this example. - Result := l_response.count.as_natural_32 - end - 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? - do - -- No. - end - -feature -- Status report - - finished (req: WSF_REQUEST): BOOLEAN - -- Has the last chunk been generated for `req'? - do - -- precondition is never met - end - -feature -- Execution - - 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. - -- We also put the order into `req.execution_variable (Order_execution_variable)' for GET or HEAD responses. - local - l_id: STRING - do - if req.is_post_request_method then - a_helper.set_resource_exists - -- because only /order is defined to this handler for POST - else - -- the request is of the form /order/{orderid} - l_id := order_id_from_request (req) - if db_access.orders.has_key (l_id) then - a_helper.set_resource_exists - if req.is_get_head_request_method then - check attached db_access.orders.item (l_id) as l_order then - -- postcondition `item_if_found' of `has_key' - req.set_execution_variable (Order_execution_variable, l_order) - end - end - end - end - ensure then - order_saved_only_for_get_head: req.is_get_head_request_method = - attached {ORDER} req.execution_variable (Order_execution_variable) - end - -feature -- GET/HEAD content - - ensure_content_available (req: WSF_REQUEST) - -- Commence generation of response text (entity-body). - -- 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. - -- We save the text in `req.execution_variable (Generated_content_execution_variable)' - -- We ignore the results of content negotiation, as there is only one possible combination. - do - check attached {ORDER} req.execution_variable (Order_execution_variable) as l_order then - -- precondition get_or_head and postcondition order_saved_only_for_get_head of `check_resource_exists' and - if attached {JSON_VALUE} json.value (l_order) as jv then - req.set_execution_variable (Generated_content_execution_variable, jv.representation) - else - req.set_execution_variable (Generated_content_execution_variable, "") - end - end - ensure then - generated_content_set_for_get_head: req.is_get_head_request_method implies - attached {READABLE_STRING_8} req.execution_variable (Generated_content_execution_variable) - end - - content (req: WSF_REQUEST): READABLE_STRING_8 - -- Non-chunked entity body in response to `req'; - -- We only call this for GET/HEAD in this example. - do - check attached {READABLE_STRING_8} req.execution_variable (Generated_content_execution_variable) as l_response then - -- postcondition generated_content_set_for_get_head of `ensure_content_available' - Result := l_response - end - end - - next_chunk (req: WSF_REQUEST): TUPLE [a_chunk: 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. - do - -- precondition `is_chunking' is never met, but we need a dummy `Result' - -- to satisfy the compiler in void-safe mode - Result := ["", Void] - 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. - do - -- precondition `is_chunking' is never met - end - -feature -- DELETE - - delete (req: WSF_REQUEST) - -- Delete resource named in `req' or set an error on `req.error_handler'. - local - l_id: STRING - do - l_id := order_id_from_request (req) - if db_access.orders.has_key (l_id) then - if is_valid_to_delete (l_id) then - delete_order (l_id) - else - req.error_handler.add_custom_error ({HTTP_STATUS_CODE}.method_not_allowed, "DELETE not valid", - "There is conflict while trying to delete the order, the order could not be deleted in the current state") - end - else - req.error_handler.add_custom_error ({HTTP_STATUS_CODE}.not_found, "DELETE not valid", - "There is no such order to delete") - end - end - - delete_queued (req: WSF_REQUEST): BOOLEAN - -- Has resource named by `req' been queued for deletion? - do - -- No - end - - -feature -- PUT/POST - - 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? - do - -- No. We don't care for this example. - 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. - do - -- We don't bother for this example. Note that this is equivalent to setting zero. - 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. - do - -- We don't support creating a new resource with PUT. But this can't happen - -- with our router mappings, so we don't bother to set a 500 response. - 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. - do - if attached {ORDER} req.execution_variable (Extracted_order_execution_variable) as l_order then - save_order (l_order) - compute_response_post (req, res, l_order) - else - handle_bad_request_response ("Not a valid order", req, res) - end - end - - check_conflict (req: WSF_REQUEST; res: WSF_RESPONSE) - -- Check we can support all content headers on request entity. - -- 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'. - do - if attached {ORDER} req.execution_variable (Extracted_order_execution_variable) as l_order then - if not is_valid_to_update (l_order) then - req.set_execution_variable (Conflict_check_code_execution_variable, {NATURAL} 409) - handle_resource_conflict_response (l_order.out +"%N There is conflict while trying to update the order, the order could not be update in the current state", req, res) - end - else - req.set_execution_variable (Conflict_check_code_execution_variable, {NATURAL} 409) - --| This ought to be a 500, as if attached should probably be check attached. But as yet I lack a proof. - handle_resource_conflict_response ("There is conflict while trying to update the order, the order could not be update in the current state", req, res) - 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'. - local - l_order: detachable ORDER - l_id: STRING - do - if attached {READABLE_STRING_8} req.execution_variable (Request_entity_execution_variable) as l_request then - l_order := extract_order_request (l_request) - if req.is_put_request_method then - l_id := order_id_from_request (req) - if l_order /= Void and then db_access.orders.has_key (l_id) then - l_order.set_id (l_id) - req.set_execution_variable (Request_check_code_execution_variable, {NATURAL} 0) - req.set_execution_variable (Extracted_order_execution_variable, l_order) - else - req.set_execution_variable (Request_check_code_execution_variable, {NATURAL} 400) - handle_bad_request_response (l_request +"%N is not a valid ORDER, maybe the order does not exist in the system", req, res) - end - else - req.set_execution_variable (Request_check_code_execution_variable, {NATURAL} 0) - req.set_execution_variable (Extracted_order_execution_variable, l_order) - end - else - req.set_execution_variable (Request_check_code_execution_variable, {NATURAL} 400) - handle_bad_request_response ("Request is not a valid ORDER", req, res) - 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. - do - if attached {ORDER} req.execution_variable (Extracted_order_execution_variable) as l_order then - update_order (l_order) - compute_response_put (req, res, l_order) - else - handle_internal_server_error (res) - end - end - feature -- HTTP Methods + do_get (req: WSF_REQUEST; res: WSF_RESPONSE) + -- + local + id: STRING + do + if attached req.path_info as l_path_info then + id := get_order_id_from_path (l_path_info) + if attached retrieve_order (id) as l_order then + if is_conditional_get (req, l_order) then + handle_resource_not_modified_response ("The resource" + l_path_info + "does not change", req, res) + else + compute_response_get (req, res, l_order) + end + else + handle_resource_not_found_response ("The following resource" + l_path_info + " is not found ", req, res) + end + end + end + + is_conditional_get (req : WSF_REQUEST; l_order : ORDER) : BOOLEAN + -- Check if If-None-Match is present and then if there is a representation that has that etag + -- if the representation hasn't changed, we return TRUE + -- then the response is a 304 with no entity body returned. + local + etag_util : ETAG_UTILS + do + if attached req.meta_string_variable ("HTTP_IF_NONE_MATCH") as if_none_match then + create etag_util + if if_none_match.same_string (etag_util.md5_digest (l_order.out).as_string_32) then + Result := True + end + end + end + + compute_response_get (req: WSF_REQUEST; res: WSF_RESPONSE; l_order: ORDER) + local + h: HTTP_HEADER + l_msg : STRING + etag_utils : ETAG_UTILS + do + create h.make + create etag_utils + h.put_content_type_application_json + if attached {JSON_VALUE} json.value (l_order) as jv then + l_msg := jv.representation + h.put_content_length (l_msg.count) + if attached req.request_time as time then + h.add_header ("Date:" + time.formatted_out ("ddd,[0]dd mmm yyyy [0]hh:[0]mi:[0]ss.ff2") + " GMT") + end + h.add_header ("etag:" + etag_utils.md5_digest (l_order.out)) + res.set_status_code ({HTTP_STATUS_CODE}.ok) + res.put_header_text (h.string) + res.put_string (l_msg) + end + end + + do_put (req: WSF_REQUEST; res: WSF_RESPONSE) + -- Updating a resource with PUT + -- A successful PUT request will not create a new resource, instead it will + -- change the state of the resource identified by the current uri. + -- If success we response with 200 and the updated order. + -- 404 if the order is not found + -- 400 in case of a bad request + -- 500 internal server error + -- If the request is a Conditional PUT, and it does not mat we response + -- 415, precondition failed. + local + l_put: STRING + l_order : detachable ORDER + id : STRING + do + if attached req.path_info as l_path_info then + id := get_order_id_from_path (l_path_info) + l_put := retrieve_data (req) + l_order := extract_order_request(l_put) + if l_order /= Void and then db_access.orders.has_key (id) then + l_order.set_id (id) + if is_valid_to_update(l_order) then + if is_conditional_put (req, l_order) then + update_order( l_order) + compute_response_put (req, res, l_order) + else + handle_precondition_fail_response ("", req, res) + end + else + --| FIXME: Here we need to define the Allow methods + handle_resource_conflict_response (l_put +"%N There is conflict while trying to update the order, the order could not be update in the current state", req, res) + end + else + handle_bad_request_response (l_put +"%N is not a valid ORDER, maybe the order does not exist in the system", req, res) + end + end + end + + is_conditional_put (req : WSF_REQUEST; order : ORDER) : BOOLEAN + -- Check if If-Match is present and then if there is a representation that has that etag + -- if the representation hasn't changed, we return TRUE + local + etag_util : ETAG_UTILS + do + if attached retrieve_order (order.id) as l_order then + if attached req.meta_string_variable ("HTTP_IF_MATCH") as if_match then + create etag_util + if if_match.same_string (etag_util.md5_digest (l_order.out).as_string_32) then + Result := True + end + else + Result := True + end + end + end + + compute_response_put (req: WSF_REQUEST; res: WSF_RESPONSE; l_order : ORDER) local h: HTTP_HEADER @@ -431,6 +218,67 @@ feature -- HTTP Methods end end + + do_delete (req: WSF_REQUEST; res: WSF_RESPONSE) + -- Here we use DELETE to cancel an order, if that order is in state where + -- it can still be canceled. + -- 200 if is ok + -- 404 Resource not found + -- 405 if consumer and service's view of the resouce state is inconsisent + -- 500 if we have an internal server error + local + id: STRING + do + if attached req.path_info as l_path_info then + id := get_order_id_from_path (l_path_info) + if db_access.orders.has_key (id) then + if is_valid_to_delete (id) then + delete_order( id) + compute_response_delete (req, res) + else + --| FIXME: Here we need to define the Allow methods + handle_method_not_allowed_response (l_path_info + "%N There is conflict while trying to delete the order, the order could not be deleted in the current state", req, res) + end + else + handle_resource_not_found_response (l_path_info + " not found in this server", req, res) + end + end + end + + compute_response_delete (req: WSF_REQUEST; res: WSF_RESPONSE) + local + h : HTTP_HEADER + do + create h.make + h.put_content_type_application_json + if attached req.request_time as time then + h.put_utc_date (time) + end + res.set_status_code ({HTTP_STATUS_CODE}.no_content) + res.put_header_text (h.string) + end + + do_post (req: WSF_REQUEST; res: WSF_RESPONSE) + -- Here the convention is the following. + -- POST is used for creation and the server determines the URI + -- of the created resource. + -- If the request post is SUCCESS, the server will create the order and will response with + -- HTTP_RESPONSE 201 CREATED, the Location header will contains the newly created order's URI + -- if the request post is not SUCCESS, the server will response with + -- HTTP_RESPONSE 400 BAD REQUEST, the client send a bad request + -- HTTP_RESPONSE 500 INTERNAL_SERVER_ERROR, when the server can deliver the request + local + l_post: STRING + do + l_post := retrieve_data (req) + if attached extract_order_request (l_post) as l_order then + save_order (l_order) + compute_response_post (req, res, l_order) + else + handle_bad_request_response (l_post +"%N is not a valid ORDER", req, res) + end + end + compute_response_post (req: WSF_REQUEST; res: WSF_RESPONSE; l_order : ORDER) local h: HTTP_HEADER @@ -462,16 +310,9 @@ feature -- HTTP Methods feature {NONE} -- URI helper methods - order_id_from_request (req: WSF_REQUEST): STRING - -- Value of "orderid" template URI variable in `req' - require - req_attached: req /= Void + get_order_id_from_path (a_path: READABLE_STRING_32) : STRING do - if attached {WSF_VALUE} req.path_parameter ("orderid") as l_value then - Result := l_value.as_string.value.as_string_8 - else - Result := "" - end + Result := a_path.split ('/').at (3) end feature {NONE} -- Implementation Repository Layer diff --git a/library/server/wsf/router/policy/wsf_delete_helper.e b/library/server/wsf/policy_driven/wsf_delete_helper.e similarity index 100% rename from library/server/wsf/router/policy/wsf_delete_helper.e rename to library/server/wsf/policy_driven/wsf_delete_helper.e diff --git a/library/server/wsf/router/policy/wsf_get_helper.e b/library/server/wsf/policy_driven/wsf_get_helper.e similarity index 100% rename from library/server/wsf/router/policy/wsf_get_helper.e rename to library/server/wsf/policy_driven/wsf_get_helper.e diff --git a/library/server/wsf/router/policy/wsf_method_helper.e b/library/server/wsf/policy_driven/wsf_method_helper.e similarity index 100% rename from library/server/wsf/router/policy/wsf_method_helper.e rename to library/server/wsf/policy_driven/wsf_method_helper.e diff --git a/library/server/wsf/router/policy/wsf_method_helper_factory.e b/library/server/wsf/policy_driven/wsf_method_helper_factory.e similarity index 100% rename from library/server/wsf/router/policy/wsf_method_helper_factory.e rename to library/server/wsf/policy_driven/wsf_method_helper_factory.e diff --git a/library/server/wsf/router/policy/wsf_post_helper.e b/library/server/wsf/policy_driven/wsf_post_helper.e similarity index 100% rename from library/server/wsf/router/policy/wsf_post_helper.e rename to library/server/wsf/policy_driven/wsf_post_helper.e diff --git a/library/server/wsf/router/policy/wsf_put_helper.e b/library/server/wsf/policy_driven/wsf_put_helper.e similarity index 100% rename from library/server/wsf/router/policy/wsf_put_helper.e rename to library/server/wsf/policy_driven/wsf_put_helper.e diff --git a/library/server/wsf/router/policy/wsf_skeleton_handler.e b/library/server/wsf/policy_driven/wsf_skeleton_handler.e similarity index 100% rename from library/server/wsf/router/policy/wsf_skeleton_handler.e rename to library/server/wsf/policy_driven/wsf_skeleton_handler.e diff --git a/library/server/wsf/router/policy/wsf_routed_skeleton_service.e b/library/server/wsf/router/policy/service/wsf_routed_skeleton_service.e similarity index 97% rename from library/server/wsf/router/policy/wsf_routed_skeleton_service.e rename to library/server/wsf/router/policy/service/wsf_routed_skeleton_service.e index 45fd8975..e9eddb59 100644 --- a/library/server/wsf/router/policy/wsf_routed_skeleton_service.e +++ b/library/server/wsf/router/policy/service/wsf_routed_skeleton_service.e @@ -30,14 +30,14 @@ feature -- Execution 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 + 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 ("*") + elseif + req.is_request_method ({HTTP_REQUEST_METHODS}.method_options) and then + req.request_uri.same_string ("*") then handle_server_options (req, res) else diff --git a/library/server/wsf/wsf-safe.ecf b/library/server/wsf/wsf-safe.ecf index e47d827a..efeb0034 100644 --- a/library/server/wsf/wsf-safe.ecf +++ b/library/server/wsf/wsf-safe.ecf @@ -18,8 +18,11 @@ - - + + + /policy_driven$ + + diff --git a/library/server/wsf/wsf.ecf b/library/server/wsf/wsf.ecf index b315cfb2..b8088db3 100644 --- a/library/server/wsf/wsf.ecf +++ b/library/server/wsf/wsf.ecf @@ -17,11 +17,14 @@ - - + + + /policy_driven$ + + diff --git a/library/server/wsf/wsf_policy_driven-safe.ecf b/library/server/wsf/wsf_policy_driven-safe.ecf new file mode 100644 index 00000000..f5b81277 --- /dev/null +++ b/library/server/wsf/wsf_policy_driven-safe.ecf @@ -0,0 +1,22 @@ + + + + + + /.git$ + /EIFGENs$ + /.svn$ + + + + + + + + + + + + + diff --git a/library/server/wsf/wsf_policy_driven.ecf b/library/server/wsf/wsf_policy_driven.ecf new file mode 100644 index 00000000..4e9cd61f --- /dev/null +++ b/library/server/wsf/wsf_policy_driven.ecf @@ -0,0 +1,22 @@ + + + + + + /.git$ + /EIFGENs$ + /.svn$ + + + + + + + + + + + + + diff --git a/tests/all-safe.ecf b/tests/all-safe.ecf index 8115909e..cb24fecd 100644 --- a/tests/all-safe.ecf +++ b/tests/all-safe.ecf @@ -46,6 +46,7 @@ +