I've added java servlet-style filters to Webware, and would like to contribute the code to the project, if people are interested.

I've attached files and patches -- it's a relatively small amount of code. A few patches to Application.py and Transaction.py, and then four new files. (Note: I *think* I got all the tabs right, but I'm not sure ;-)

I've been working with filters for a while (I wrote the feature against 0.8.1, and just found time to port the patches to 0.9.x), and have found them to be enormously useful.

Here's the basic idea:

- In Application.config, the user can specify a sequence of (regex, Filter) pairs, like so:

FilterMappings = [ (r'/',           'filters.Authorizer'),
                   (r'/admin/',     'filters.admin'),
                   (r'/customers/', 'filters.customers'),
                   (r'/users/',     'filters.users') ]


Each Filter will be a subclass of WebKit.Filter and must define a filter(trans, next) method.


- When the Application receives a new request, it constructs a 'chain' of Filters which match the URL

E.g. in the example above, for a request to /users/3/view, that chain would be: [ filters.Authorizer(), filters.users() ]


- The Application then calls the filter(trans, next) method of the first Filter in the chain

After examining and possibly modifying the transaction, each filter can pass control on to the succeeding one by calling next(trans). Or, if it can terminate the chain by just returning.


- If all the filters execute and pass control to next(), control returns to the Application, which will lookup and execute a servlet as usual



Some uses:

 - Path-based Authorization

An Authorizer filter can look in the session for credentials, and, if none are found, can redirect to a login page (after storing the originally request URL in the session, possibly)


 - Move parameters from the URL to the Request (REST-style)

Given a request of form:

/users/3/<serv>

a filter can modify the URL to be /users/<serv>, and store user_id=3 in the request.


 - Logging

 - Compression

Many others.

Basically, filters provide the user a clean, simple way to construct their own request-handling pipeline.

If people are interested, I can write up some documentation.

-Dan Milstein


Attachment: Application.py.patch
Description: Binary data

Attachment: Transaction.py.patch
Description: Binary data

from Common import *

import re

class FilterMapping(Object):

	""" Determine if a given filter should be applied to a specific
	request.  Instances of this class: are configured at start up; held by
	the Application; used to create the FilterChain for each request.

	The patterns passed in are regular expressions, to be matched using
	'match' (meaning, implicitly anchored at the beginning).
	"""

	def __init__(self, pattern, filterClassName):
		Object.__init__(self)
		self._pattern = re.compile(pattern)
		# Assume Filter has same name as module it's in
		mod = __import__(filterClassName)
		pkgs = filterClassName.split(".")	 
		for comp in pkgs[1:]:
			mod = getattr(mod, comp)
		self._klass = getattr(mod, pkgs[-1])

	def matches(self, path):
		# No globs, nothing fancy
		# print "Path to match: %s" % path
		return self._pattern.match(path)

	def filter(self):
		# Just create a new instance for now
		return self._klass()
from Common import *

from Filter import Filter

class FilterSink(Filter):

	""" A Sink for the Chain of Filters which returns control to the
	Application so that it can find and run a servlet."""

	## Init ##

	def __init__(self, application):
		Filter.__init__(self)
		self._application = application

	def filter(self, transaction, next):
		self._application.runServletInTransaction(transaction)
		# Ignore next, since the chain is complete
		return
from Common import *


class FilterChain(Object):
	"""

	A FilterChain holds the specific, ordered list of Filters which are
	found to apply to a given request.	They participate in transactions.
	
	XXX NOT IMPLEMENTED YET XXX
	It is intended that FilterChains can be created once, then used and
	destroyed, or they may be reused several times over (it's up to the
	server). 

	Objects that participate in a transaction include:
	
	* Application
	* Request
	* Transaction
	* Session
	* Servlet
	* Response
	* FilterChain

	The awake(), respond() and sleep() methods form a message
	sandwich. Each is passed an instance of Transaction which gives further
	access to all the objects involved.
	"""

	## Init ##

	def __init__(self):
	""" Subclasses must invoke super. """
	Object.__init__(self)
	self._serverSidePath = None
	self._filters = []


	## Access ##
	
	def name(self):
	""" Returns the name which is simple the name of the class.
	Subclasses should *not* override this method. It is used for
	logging and debugging. """
	return self.__class__.__name__


	## Filter Creation ##
	def addFilter(self, filter):
	self._filters.append(filter)

	## Request-response cycles ##

	def awake(self, trans):
	""" This message is sent to all objects that participate in the
	request-response cycle in a top-down fashion, prior to respond()."""
	self._transaction = trans
	for f in self._filters:
		f.awake(trans)

	def respond(self, trans):
	filters = self._filters
	next = Next(filters)
	first = filters[0]
	first.filter(trans, next)

	def sleep(self, trans):
	for f in self._filters:
		f.sleep(trans)
	#del self._filters


	## Abilities ##

	def canBeThreaded(self):
	""" Returns 0 or 1 to indicate if the servlet can be multithreaded. This value should not change during the lifetime of the object. The default implementation returns 0. Note: This is not currently used. """
	return 0

	def canBeReused(self):
	""" Returns 0 or 1 to indicate if a single servlet instance can be reused. The default is 1, but subclasses con override to return 0. Keep in mind that performance may seriously be degraded if instances can't be reused. Also, there's no known good reasons not to reuse and instance. Remember the awake() and sleep() methods are invoked for every transaction. But just in case, your servlet can refuse to be reused. """
	return 1


	## Server side filesystem ##

	def serverSidePath(self, path=None):
	""" Returns the filesystem path of the page on the server. """
	if self._serverSidePath is None:
		if hasattr(self, "_request") and self._request is not None:
		self._serverSidePath = self._request.serverSidePath()
		else:
		self._serverSidePath = self._transaction.request().serverSidePath()
	if path:
		return os.path.normpath(os.path.join(os.path.dirname(self._serverSidePath), path))
	else:
		return self._serverSidePath


	## Cleanup ##
	
	def clearTransaction(self):
	del self._transaction



class Next(Object):

	def __init__(self, chain, curIdx = 0):
	Object.__init__(self)
	self._chain = chain
	self._curIdx = curIdx

	def __call__(self, trans):
	nextIdx = self._curIdx + 1
	chain = self._chain
	if nextIdx > len(chain):
		raise Exception("next called past end of FilterChain: %s" % chain)
	nextFilter = chain[nextIdx]
	nextNext = Next(chain, nextIdx)
	nextFilter.filter(trans, nextNext)
	
from Common import *


class Filter(Object):
	"""
	
	A Filter is called as part of the request-handling cycle for a whole
	class of servlets, based on path matching.	This provides a means to
	implement a variety of sophisticated features in a clean, simple
	manner, such as:

	  * Authentication/Authorization
	  * Path-rewriting / manipulation
	  * Logging
	  * Compression

	This abstract class defines Filters at a high level.

	For a given request-handling loop, a set of Filters will be found to
	match the path.	 They will be grouped together into a FilterChain
	object.	 The key method of a Filte is filter(trans, next).	The 'next'
	argument can be called with the transaction if the filter wishes to
	pass it on to further stages of request handling.

	XXX NOT IMPLEMENTED YET XXX
	It is intended that Filters can be created once, then used and
	destroyed, or they may be reused several times over (it's up to the
	server). Therefore, filter developers should take the proper actions
	in awake() and sleep() so that reuse can occur.

	Objects that participate in a transaction include:
	
		* Application
		* Request
		* Transaction
		* Session
		* Servlet
		* Response
		* FilterChain

	The awake(), filter() and sleep() methods form a message
	sandwich. Each is passed an instance of Transaction which gives further
	access to all the objects involved.

	"""

	## Init ##

	def __init__(self):
		""" Subclasses must invoke super. """
		Object.__init__(self)
		self._serverSidePath = None


	## Access ##

	def name(self):
		""" Returns the name which is simple the name of the class.
		Subclasses should *not* override this method. It is used for
		logging and debugging. """
		return self.__class__.__name__


	## Request-response cycles ##

	def awake(self, trans):
		""" This message is sent to all objects that participate in the
		request-response cycle in a top-down fashion, prior to respond().
		Subclasses must invoke super. """
		self._transaction = trans

	def filter(self, trans, next):

		"""Subclasses should override this method toerform whatever actions
		are desired for their filtering action.	 When they are done, if
		they wish to continue request processing, they should call
		next(trans). """
		raise AbstractError, self.__class__

	def sleep(self, trans):
		pass


	## Log ##

	def log(self, message):
		""" This can be invoked to print messages concerning the servlet. This is often used by self to relay important information back to developers. """
		print '[%s] [msg] %s' % (asclocaltime(), message)


	## Abilities ##

	def canBeThreaded(self):
		""" Returns 0 or 1 to indicate if the servlet can be multithreaded. This value should not change during the lifetime of the object. The default implementation returns 0. Note: This is not currently used. """
		return 0

	def canBeReused(self):
		""" Returns 0 or 1 to indicate if a single servlet instance can be reused. The default is 1, but subclasses con override to return 0. Keep in mind that performance may seriously be degraded if instances can't be reused. Also, there's no known good reasons not to reuse and instance. Remember the awake() and sleep() methods are invoked for every transaction. But just in case, your servlet can refuse to be reused. """
		return 1


	## Server side filesystem ##

	def serverSidePath(self, path=None):
		""" Returns the filesystem path of the page on the server. """
		if self._serverSidePath is None:
			if hasattr(self, "_request") and self._request is not None:
				self._serverSidePath = self._request.serverSidePath()
			else:
				self._serverSidePath = self._transaction.request().serverSidePath()
		if path:
			return os.path.normpath(os.path.join(os.path.dirname(self._serverSidePath), path))
		else:
			return self._serverSidePath

-------------------------------------------------------------------------
Using Tomcat but need to do more? Need to support web services, security?
Get stuff done quickly with pre-integrated technology to make your job easier
Download IBM WebSphere Application Server v.1.0.1 based on Apache Geronimo
http://sel.as-us.falkag.net/sel?cmd=lnk&kid=120709&bid=263057&dat=121642
_______________________________________________
Webware-devel mailing list
Webware-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/webware-devel

Reply via email to