[ADD] Filter: pre-process incoming data and post-process outgoing data

Filters are part of a filter chain, thus following the chain of responsability
design pattern.
More information are available in library/server/wsf/src/filter/README.md
This commit is contained in:
Olivier Ligot
2012-08-10 10:09:59 +02:00
parent c62a4d2c49
commit 74334e665d
15 changed files with 717 additions and 0 deletions

View File

@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-9-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-9-0 http://www.eiffel.com/developers/xml/configuration-1-9-0.xsd" name="restbucks" uuid="7C9887BD-4AE4-47F2-A0AA-4BBB6736D433">
<target name="restbucks">
<root class="FILTER_SERVER" feature="make"/>
<file_rule>
<exclude>/EIFGENs$</exclude>
<exclude>/\.git$</exclude>
<exclude>/\.svn$</exclude>
</file_rule>
<option debug="true" warning="true" full_class_checking="true" is_attached_by_default="true" void_safety="all" syntax="provisional">
<debug name="nino" enabled="true"/>
<assertions precondition="true" postcondition="true" invariant="true" supplier_precondition="true"/>
</option>
<setting name="concurrency" value="thread"/>
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
<library name="connector_nino" location="..\..\library\server\ewsgi\connectors\nino\nino-safe.ecf" readonly="false">
<option debug="true">
<debug name="nino" enabled="true"/>
</option>
</library>
<library name="default_nino" location="..\..\library\server\wsf\default\nino-safe.ecf" readonly="false"/>
<library name="eel" location="..\..\library\crypto\eel\eel-safe.ecf" readonly="false"/>
<library name="encoder" location="..\..\library\text\encoder\encoder-safe.ecf" readonly="false"/>
<library name="http" location="..\..\library\protocol\http\http-safe.ecf" readonly="false"/>
<library name="json" location="..\..\contrib\library\text\parser\json\library\json-safe.ecf" readonly="false"/>
<library name="uri_template" location="..\..\library\protocol\uri_template\uri_template-safe.ecf" readonly="false"/>
<library name="wsf" location="..\..\library\server\wsf\wsf-safe.ecf" readonly="false"/>
<library name="wsf_extension" location="..\..\library\server\wsf_extension\wsf_extension-safe.ecf" readonly="false"/>
<library name="http_authorization" location="..\..\library\server\authentication\http_authorization\http_authorization-safe.ecf" readonly="false"/>
<library name="time" location="$ISE_LIBRARY\library\time\time-safe.ecf"/>
<cluster name="src" location="src\" recursive="true"/>
</target>
</system>

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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