diff --git a/examples/filter/filter-safe.ecf b/examples/filter/filter-safe.ecf new file mode 100644 index 00000000..024d575d --- /dev/null +++ b/examples/filter/filter-safe.ecf @@ -0,0 +1,33 @@ + + + + + + /EIFGENs$ + /\.git$ + /\.svn$ + + + + + + + + + + + + + + + + + + + + diff --git a/examples/filter/readme.md b/examples/filter/readme.md new file mode 100644 index 00000000..b1a43c03 --- /dev/null +++ b/examples/filter/readme.md @@ -0,0 +1,4 @@ +Filter example + +To test the example, you can just run in a terminal: +> curl -u foo:bar http://localhost:9090/user/1 -v diff --git a/examples/filter/src/database/database_api.e b/examples/filter/src/database/database_api.e new file mode 100644 index 00000000..fb97a5d5 --- /dev/null +++ b/examples/filter/src/database/database_api.e @@ -0,0 +1,30 @@ +note + description: "Summary description for {DATABASE_API}." + author: "" + date: "$Date$" + revision: "$Revision$" + +class + DATABASE_API +create + make + +feature -- Initialization + + make + local + l_user: USER + do + create users.make (10) + create l_user.make (1, "foo", "bar") + users.put (l_user, l_user.id) + end + +feature -- Access + + users: HASH_TABLE [USER, INTEGER] + +;note + copyright: "2011-2011, Javier Velilla and others" + license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)" +end diff --git a/examples/filter/src/database/shared_database_api.e b/examples/filter/src/database/shared_database_api.e new file mode 100644 index 00000000..658b4bd5 --- /dev/null +++ b/examples/filter/src/database/shared_database_api.e @@ -0,0 +1,20 @@ +note + description: "Summary description for {SHARED_DATABASE_API}." + author: "" + date: "$Date$" + revision: "$Revision$" + +class + SHARED_DATABASE_API + +feature -- Access + + db_access: DATABASE_API + once + create Result.make + end + +note + copyright: "2011-2011, Javier Velilla and others" + license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)" +end diff --git a/examples/filter/src/domain/json_user_converter.e b/examples/filter/src/domain/json_user_converter.e new file mode 100644 index 00000000..d137330b --- /dev/null +++ b/examples/filter/src/domain/json_user_converter.e @@ -0,0 +1,52 @@ +note + description: "JSON user converter." + author: "Olivier Ligot" + date: "$Date$" + revision: "$Revision$" + +class + JSON_USER_CONVERTER + +inherit + JSON_CONVERTER + +create + make + +feature {NONE} -- Initialization + + make + do + create object.make (0, "", "") + end + +feature -- Access + + object: USER + + value: detachable JSON_OBJECT + +feature -- Conversion + + from_json (j: attached like value): detachable like object + -- Convert from JSON value. + do + end + + to_json (o: like object): like value + -- Convert to JSON value. + do + create Result.make + Result.put (json.value (o.id), id_key) + Result.put (json.value (o.name), name_key) + end + + feature {NONE} -- Implementation + + id_key: STRING = "id" + name_key: STRING = "name" + +note + copyright: "2011-2011, Javier Velilla and others" + license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)" +end diff --git a/examples/filter/src/domain/user.e b/examples/filter/src/domain/user.e new file mode 100644 index 00000000..4d5756c4 --- /dev/null +++ b/examples/filter/src/domain/user.e @@ -0,0 +1,40 @@ +note + description: "User." + author: "" + date: "$Date$" + revision: "$Revision$" + +class + USER + +create + make + +feature {NONE} -- Initialization + + make (an_id: INTEGER; a_name, a_password: STRING) + do + id := an_id + name := a_name + password := a_password + ensure + id_set: id = an_id + name_set: name = a_name + password_set: password = a_password + end + +feature -- Access + + id: INTEGER + -- Identifier + + name: STRING + -- Name + + password: STRING + -- Password + +;note + copyright: "2011-2011, Javier Velilla and others" + license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)" +end diff --git a/examples/filter/src/filter/authentication_filter.e b/examples/filter/src/filter/authentication_filter.e new file mode 100644 index 00000000..3f4809bc --- /dev/null +++ b/examples/filter/src/filter/authentication_filter.e @@ -0,0 +1,50 @@ +note + description: "Authentication filter." + author: "Olivier Ligot" + date: "$Date$" + revision: "$Revision$" + +class + AUTHENTICATION_FILTER [C -> WSF_URI_TEMPLATE_HANDLER_CONTEXT] + +inherit + WSF_FILTER_HANDLER [C] + + SHARED_DATABASE_API + +feature -- Basic operations + + execute (ctx: C; req: WSF_REQUEST; res: WSF_RESPONSE) + -- Execute the filter + local + l_auth: HTTP_AUTHORIZATION + do + create l_auth.make (req.http_authorization) + if (attached l_auth.type as l_auth_type and then l_auth_type.is_equal ("basic")) and + attached Db_access.users.item (1) as l_user and then + (attached l_auth.login as l_auth_login and then l_auth_login.is_equal (l_user.name) + and attached l_auth.password as l_auth_password and then l_auth_password.is_equal (l_user.password)) then + execute_next (ctx, req, res) + else + handle_unauthorized ("Unauthorized", ctx, req, res) + end + end + +feature {NONE} -- Implementation + + handle_unauthorized (a_description: STRING; ctx: C; req: WSF_REQUEST; res: WSF_RESPONSE) + -- Handle forbidden. + local + h: HTTP_HEADER + do + create h.make + h.put_content_type_text_plain + h.put_content_length (a_description.count) + h.put_current_date + h.put_header_key_value ({HTTP_HEADER_NAMES}.header_www_authenticate, "Basic realm=%"User%"") + res.set_status_code ({HTTP_STATUS_CODE}.unauthorized) + res.put_header_text (h.string) + res.put_string (a_description) + end + +end diff --git a/examples/filter/src/filter/logging_filter.e b/examples/filter/src/filter/logging_filter.e new file mode 100644 index 00000000..330012c4 --- /dev/null +++ b/examples/filter/src/filter/logging_filter.e @@ -0,0 +1,40 @@ +note + description: "Logging filter." + author: "Olivier Ligot" + date: "$Date$" + revision: "$Revision$" + +class + LOGGING_FILTER + +inherit + WSF_FILTER + +feature -- Basic operations + + execute (req: WSF_REQUEST; res: WSF_RESPONSE) + -- Execute the filter + local + l_user_agent: STRING + l_date: DATE_TIME + do + if attached req.http_user_agent as ua then + l_user_agent := ua.as_string_8 + else + l_user_agent := "-" + end + create l_date.make_now + io.put_string ("[" + l_date.formatted_out (Date_time_format) + "] %"" + req.request_method + " " + req.request_uri + + " " + {HTTP_CONSTANTS}.http_version_1_1 + "%" " + res.status_code.out + " " + l_user_agent) + io.put_new_line + execute_next (req, res) + end + +feature -- Constants + + Date_time_format: STRING = "yyyy/[0]mm/[0]dd [0]hh:[0]mi:[0]ss.ff3" + +note + copyright: "2011-2012, Javier Velilla and others" + license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)" +end diff --git a/examples/filter/src/filter_server.e b/examples/filter/src/filter_server.e new file mode 100644 index 00000000..b956fc16 --- /dev/null +++ b/examples/filter/src/filter_server.e @@ -0,0 +1,109 @@ +note + description : "Filter example." + author : "Olivier Ligot" + date : "$Date$" + revision : "$Revision$" + +class + FILTER_SERVER + +inherit + ANY + + WSF_URI_TEMPLATE_FILTERED_SERVICE + + WSF_HANDLER_HELPER + + WSF_DEFAULT_SERVICE + + SHARED_EJSON + +create + make + +feature {NONE} -- Initialization + + make + do + initialize_filter + initialize_json + set_service_option ("port", 9090) + make_and_launch + end + + create_filter + -- Create `filter' + local + l_router: WSF_URI_TEMPLATE_ROUTER + l_authentication_filter: AUTHENTICATION_FILTER [WSF_URI_TEMPLATE_HANDLER_CONTEXT] + l_user_filter: USER_HANDLER [WSF_URI_TEMPLATE_HANDLER_CONTEXT] + l_user_handler: WSF_HANDLER [WSF_URI_TEMPLATE_HANDLER_CONTEXT] + l_routing_filter: WSF_ROUTING_FILTER [WSF_HANDLER [WSF_URI_TEMPLATE_HANDLER_CONTEXT], WSF_URI_TEMPLATE_HANDLER_CONTEXT] + do + create l_router.make (1) + create l_authentication_filter + create l_user_filter + l_authentication_filter.set_next (l_user_filter) + l_user_handler := l_authentication_filter + l_router.map_with_request_methods ("/user/{userid}", l_user_handler, << "GET" >>) + create l_routing_filter.make (l_router) + l_routing_filter.set_execute_default_action (agent execute_default) + filter := l_routing_filter + end + + setup_filter + -- Setup `filter' + local + l_logging_filter: LOGGING_FILTER + do + create l_logging_filter + filter.set_next (l_logging_filter) + end + + initialize_json + -- Initialize `json'. + do + json.add_converter (create {JSON_USER_CONVERTER}.make) + end + +feature -- Basic operations + + execute (req: WSF_REQUEST; res: WSF_RESPONSE) + do + filter.execute (req, res) + end + + execute_default (req: WSF_REQUEST; res: WSF_RESPONSE) + -- I'm using this method to handle the method not allowed response + -- in the case that the given uri does not have a corresponding http method + -- to handle it. + local + h : HTTP_HEADER + l_description : STRING + l_api_doc : STRING + do + if req.content_length_value > 0 then + req.input.read_string (req.content_length_value.as_integer_32) + end + create h.make + h.put_content_type_text_plain + l_api_doc := "%NPlease check the API%NURI:/user/{userid} METHOD: GET%N" + l_description := req.request_method + req.request_uri + " is not allowed" + "%N" + l_api_doc + h.put_content_length (l_description.count) + h.put_current_date + res.set_status_code ({HTTP_STATUS_CODE}.method_not_allowed) + res.put_header_text (h.string) + res.put_string (l_description) + end + +note + copyright: "2011-2012, Javier Velilla 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/examples/filter/src/resource/user_handler.e b/examples/filter/src/resource/user_handler.e new file mode 100644 index 00000000..f6e9a7c1 --- /dev/null +++ b/examples/filter/src/resource/user_handler.e @@ -0,0 +1,86 @@ +note + description: "User handler." + author: "Olivier Ligot" + date: "$Date$" + revision: "$Revision$" + +class + USER_HANDLER [C -> WSF_HANDLER_CONTEXT] + +inherit + WSF_FILTER_HANDLER [C] + + WSF_RESOURCE_HANDLER_HELPER [C] + redefine + do_get + end + + SHARED_DATABASE_API + + SHARED_EJSON + +feature -- Basic operations + + execute (ctx: C; req: WSF_REQUEST; res: WSF_RESPONSE) + -- Execute request handler + do + execute_methods (ctx, req, res) + end + + do_get (ctx: C; req: WSF_REQUEST; res: WSF_RESPONSE) + -- Using GET to retrieve resource information. + -- If the GET request is SUCCESS, we response with + -- 200 OK, and a representation of the user + -- If the GET request is not SUCCESS, we response with + -- 404 Resource not found + local + id : STRING + do + if attached req.orig_path_info as orig_path then + id := get_user_id_from_path (orig_path) + if attached retrieve_user (id) as l_user then + compute_response_get (ctx, req, res, l_user) + else + handle_resource_not_found_response ("The following resource " + orig_path + " is not found ", ctx, req, res) + end + end + end + +feature {NONE} -- Implementation + + compute_response_get (ctx: C; req: WSF_REQUEST; res: WSF_RESPONSE; l_user : USER) + local + h: HTTP_HEADER + l_msg : STRING + do + create h.make + h.put_content_type_application_json + if attached {JSON_VALUE} json.value (l_user) 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 + res.set_status_code ({HTTP_STATUS_CODE}.ok) + res.put_header_text (h.string) + res.put_string (l_msg) + end + end + + get_user_id_from_path (a_path: READABLE_STRING_32) : STRING + do + Result := a_path.split ('/').at (3) + end + + retrieve_user (id: STRING) : detachable USER + -- Retrieve the user by id if it exist, in other case, Void + do + if id.is_integer and then Db_access.users.has (id.to_integer) then + Result := db_access.users.item (id.to_integer) + end + end + +note + copyright: "2011-2012, Javier Velilla and others" + license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)" +end diff --git a/library/server/wsf/src/filter/README.md b/library/server/wsf/src/filter/README.md new file mode 100644 index 00000000..cf7d6703 --- /dev/null +++ b/library/server/wsf/src/filter/README.md @@ -0,0 +1,27 @@ +# Introduction + +The basic idea of a filter is to pre-process incoming data and post-process outgoing data. +Filters are part of a filter chain, thus following the [chain of responsability design pattern](http://en.wikipedia.org/wiki/Chain-of-responsibility_pattern). + +Each filter decides to call the next filter or not. + +# Levels + +In EWF, there are two levels of filters. + +## WSF_FILTER + +Typical examples of such filters are: logging, compression, routing (WSF_ROUTING_FILTER), ... + +## WSF_FILTER_HANDLER + +Handler that can also play the role of a filter. + +Typical examples of such filters are: authentication, ... + +# References + +Filters (also called middelwares) in other environments: +* in Python: http://www.wsgi.org/en/latest/libraries.html +* in Node.js: http://expressjs.com/guide.html#middleware +* in Apache: http://httpd.apache.org/docs/2.2/en/filter.html diff --git a/library/server/wsf/src/filter/wsf_filter.e b/library/server/wsf/src/filter/wsf_filter.e new file mode 100644 index 00000000..141e097c --- /dev/null +++ b/library/server/wsf/src/filter/wsf_filter.e @@ -0,0 +1,52 @@ +note + description: "Objects than can pre-process incoming data and post-process outgoing data." + author: "Olivier Ligot" + date: "$Date$" + revision: "$Revision$" + +deferred class + WSF_FILTER + +feature -- Access + + next: detachable WSF_FILTER + -- Next filter + +feature -- Element change + + set_next (a_next: WSF_FILTER) + -- Set `next' to `a_next' + do + next := a_next + ensure + next_set: next = a_next + end + +feature -- Basic operations + + execute (req: WSF_REQUEST; res: WSF_RESPONSE) + -- Execute the filter. + deferred + end + +feature {NONE} -- Implementation + + execute_next (req: WSF_REQUEST; res: WSF_RESPONSE) + -- Execute the `next' filter. + do + if attached next as n then + n.execute (req, res) + end + end + +note + copyright: "2011-2012, Jocelyn Fiat, Javier Velilla, 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/src/filter/wsf_filter_handler.e b/library/server/wsf/src/filter/wsf_filter_handler.e new file mode 100644 index 00000000..f06f0082 --- /dev/null +++ b/library/server/wsf/src/filter/wsf_filter_handler.e @@ -0,0 +1,51 @@ +note + description: "[ + Handler that can also play the role of a filter, i.e. + than can pre-process incoming data and post-process outgoing data. + ]" + author: "" + date: "$Date$" + revision: "$Revision$" + +deferred class + WSF_FILTER_HANDLER [C -> WSF_HANDLER_CONTEXT] + +inherit + WSF_HANDLER [C] + +feature -- Access + + next: detachable WSF_FILTER_HANDLER [C] + -- Next filter + +feature -- Element change + + set_next (a_next: WSF_FILTER_HANDLER [C]) + -- Set `next' to `a_next' + do + next := a_next + ensure + next_set: next = a_next + end + +feature {NONE} -- Implementation + + execute_next (ctx: C; req: WSF_REQUEST; res: WSF_RESPONSE) + -- Execute the `next' filter. + do + if attached next as n then + n.execute (ctx, req, res) + end + end + +note + copyright: "2011-2012, Jocelyn Fiat, Javier Velilla, 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/src/filter/wsf_routing_filter.e b/library/server/wsf/src/filter/wsf_routing_filter.e new file mode 100644 index 00000000..9b42cbf7 --- /dev/null +++ b/library/server/wsf/src/filter/wsf_routing_filter.e @@ -0,0 +1,75 @@ +note + description: "Routing filter." + author: "Olivier Ligot" + date: "$Date$" + revision: "$Revision$" + +class + WSF_ROUTING_FILTER [H -> WSF_HANDLER [C], C -> WSF_HANDLER_CONTEXT] + +inherit + WSF_FILTER + +create + make + +feature {NONE} -- Initialization + + make (a_router: WSF_ROUTER [H, C]) + do + router := a_router + ensure + router_set: router = a_router + end + +feature -- Access + + router: WSF_ROUTER [H, C] + -- Router + + execute_default_action: detachable PROCEDURE [ANY, TUPLE [req: WSF_REQUEST; res: WSF_RESPONSE]] + -- `execute_default' action + +feature -- Element change + + set_execute_default_action (an_action: like execute_default_action) + -- Set `execute_default_action' to `an_action' + do + execute_default_action := an_action + ensure + execute_default_action_set: execute_default_action = an_action + end + +feature -- Basic operations + + execute (req: WSF_REQUEST; res: WSF_RESPONSE) + -- Execute the filter + do + if attached router.route (req) as r then + router.execute_route (r, req, res) + else + execute_default (req, res) + end + execute_next (req, res) + end + + execute_default (req: WSF_REQUEST; res: WSF_RESPONSE) + do + if attached execute_default_action as action then + action.call ([req, res]) + else + do_nothing + end + end + +note + copyright: "2011-2012, Jocelyn Fiat, Javier Velilla, 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/src/filter/wsf_uri_template_filtered_service.e b/library/server/wsf/src/filter/wsf_uri_template_filtered_service.e new file mode 100644 index 00000000..cc8a859b --- /dev/null +++ b/library/server/wsf/src/filter/wsf_uri_template_filtered_service.e @@ -0,0 +1,48 @@ +note + description: "Summary description for {WSF_URI_TEMPLATE_FILTERED_SERVICE}." + author: "" + date: "$Date$" + revision: "$Revision$" + +deferred class + WSF_URI_TEMPLATE_FILTERED_SERVICE + +feature {NONE} -- Initialization + + initialize_filter + -- Initialize `filter' + do + create_filter + setup_filter + end + + create_filter + -- Create `filter' + deferred + ensure + filter_created: filter /= Void + end + + setup_filter + -- Setup `filter' + require + filter_created: filter /= Void + deferred + end + +feature -- Access + + filter: WSF_FILTER + -- Filter + +;note + copyright: "2011-2012, Jocelyn Fiat, Javier Velilla, 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