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 AuthorizationAn 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
Application.py.patch
Description: Binary data
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