summaryrefslogtreecommitdiff
path: root/wsgitools/filters.py
diff options
context:
space:
mode:
authorHelmut Grohne <helmut@subdivi.de>2007-04-14 22:37:26 +0200
committerHelmut Grohne <helmut@subdivi.de>2007-04-14 22:37:26 +0200
commit2435f82361f6bc4dcd51e1305905ecbbb5757f50 (patch)
treedf738a4ffdbd212383b6d8df22cb3b74e1fc83f9 /wsgitools/filters.py
downloadwsgitools-2435f82361f6bc4dcd51e1305905ecbbb5757f50.tar.gz
initial tree
Diffstat (limited to 'wsgitools/filters.py')
-rw-r--r--wsgitools/filters.py218
1 files changed, 218 insertions, 0 deletions
diff --git a/wsgitools/filters.py b/wsgitools/filters.py
new file mode 100644
index 0000000..1d6154f
--- /dev/null
+++ b/wsgitools/filters.py
@@ -0,0 +1,218 @@
+__all__ = []
+
+import sys
+import time
+
+__all__.append("CloseableIterator")
+class CloseableIterator:
+ """Concatenating iterator with close attribute."""
+ def __init__(self, close_function, *iterators):
+ """If close_function is not None, it will be the close attribute of
+ the created iterator object. Further parameters specify iterators
+ that are to be concatenated."""
+ if close_function is not None:
+ self.close = close_function
+ self.iterators = map(iter, iterators)
+ def __iter__(self):
+ """iterator interface"""
+ return self
+ def next(self):
+ """iterator interface"""
+ if not self.iterators:
+ raise StopIteration
+ try:
+ return self.iterators[0].next()
+ except StopIteration:
+ self.iterators.pop(0)
+ return self.next()
+
+__all__.append("CloseableList")
+class CloseableList(list):
+ """A list with a close attribute."""
+ def __init__(self, close_function, *args):
+ """If close_function is not None, it will be the close attribute of
+ the created list object. Other parameters are passed to the list
+ constructor."""
+ if close_function is not None:
+ self.close = close_function
+ list.__init__(self, *args)
+ def __iter__(self):
+ return CloseableIterator(getattr(self, "close", None),
+ list.__iter__(self))
+
+__all__.append("BaseWSGIFilter")
+class BaseWSGIFilter:
+ """Generic WSGI filter class to be used with WSGIFilterMiddleware.
+
+ For each request a filter object gets created.
+ The environment is then passed through filter_environ.
+ Possible exceptions are filtered by filter_exc_info.
+ After that for each (header, value) tuple filter_header is used.
+ The resulting list is filtered through filter_headers.
+ Any data is filtered through filter_data.
+ In order to possibly append data the append_data method is invoked.
+ When the request has finished handle_close is invoked.
+
+ All methods do not modify the passed data by default. Passing the
+ BaseWSGIFilter class to a WSGIFilterMiddleware will result in not modifying
+ the request at all.
+ """
+ def __init__(self):
+ """This constructor does nothing and can safely be overwritten. It is
+ only listed here to document that it must be callable without additional
+ parameters."""
+ pass
+ def filter_environ(self, environ):
+ """Receives a dict with the environment passed to the wsgi application
+ and a dict must be returned. The default is to return the same dict."""
+ return environ
+ def filter_exc_info(self, exc_info):
+ """Receives either None or a tuple passed as third argument to
+ start_response from the wrapped wsgi application. Either None or such a
+ tuple must be returned."""
+ return exc_info
+ def filter_status(self, status):
+ """Receives a status string passed as first argument to start_response
+ from the wrapped wsgi application. A valid HTTP status string must be
+ returned."""
+ return status
+ def filter_header(self, headername, headervalue):
+ """This function is invoked for each (headername, headervalue) tuple in
+ the second argument to the start_response from the wrapped wsgi
+ application. Such a value or None for discarding the header must be
+ returned."""
+ return (headername, headervalue)
+ def filter_headers(self, headers):
+ """A list of headers passed as the second argument to the start_response
+ from the wrapped wsgi application is passed to this function and such a
+ list must also be returned."""
+ return headers
+ def filter_data(self, data):
+ """For each string that is either written by the write callable or
+ returned from the wrapped wsgi application this method is invoked. It
+ must return a string."""
+ return data
+ def append_data(self):
+ """This function can be used to append data to the response. A list of
+ strings or some kind of iterable yielding strings has to be returned.
+ The default is to return an empty list.
+ """
+ return []
+ def handle_close(self):
+ """This method is invoked after the request has finished."""
+ pass
+
+__all__.append("WSGIFilterMiddleware")
+class WSGIFilterMiddleware:
+ """This wsgi middleware can be used with specialized BaseWSGIFilters to
+ modify wsgi requests and/or reponses."""
+ def __init__(self, app, filterclass):
+ """app is a wsgi application.
+ filterclass is a subclass of BaseWSGIFilter or some class that
+ implements the interface."""
+ self.app = app
+ self.filterclass = filterclass
+ def __call__(self, environ, start_response):
+ """wsgi interface"""
+ filter = self.filterclass()
+ environ = filter.filter_environ(environ)
+
+ def modified_start_response(status, headers, exc_info=None):
+ exc_info = filter.filter_exc_info(exc_info)
+ status = filter.filter_status(status)
+ headers = (filter.filter_header(h, v) for h, v in headers)
+ headers = [h for h in headers if h is not None]
+ headers = filter.filter_headers(headers)
+ write = start_response(status, headers, exc_info)
+ def modified_write(data):
+ write(filter.filter_data(data))
+ return modified_write
+
+ ret = self.app(environ, modified_start_response)
+
+ def modified_close():
+ filter.handle_close()
+ getattr(ret, "close", lambda:0)()
+
+ if isinstance(ret, list):
+ return CloseableList(modified_close,
+ [filter.filter_data(data) for data in ret]
+ + list(filter.append_data()))
+ ret = iter(ret)
+ return CloseableIterator(modified_close,
+ (filter.filter_data(data) for data in ret),
+ filter.append_data())
+
+__all__.append("RequestLogWSGIFilter")
+class RequestLogWSGIFilter(BaseWSGIFilter):
+ """This filter logs all requests in the apache log file format."""
+ @classmethod
+ def creator(cls, log):
+ return lambda:cls(log)
+ def __init__(self, log=sys.stdout):
+ self.log = log
+ self.time = time.strftime("%d/%b/%Y:%T %z")
+ self.length = 0
+ def filter_environ(self, environ):
+ """BaseWSGIFilter interface"""
+ self.remote = environ.get("REMOTE_ADDR", "?")
+ self.reqmethod = environ["REQUEST_METHOD"]
+ self.path = environ["SCRIPT_NAME"] + environ["PATH_INFO"]
+ self.proto = environ.get("SERVER_PROTOCOL", None)
+ self.referrer = environ.get("HTTP_REFERER", "-")
+ self.useragent = environ.get("HTTP_USER_AGENT", "-")
+ return environ
+ def filter_status(self, status):
+ """BaseWSGIFilter interface"""
+ self.status = status.split()[0]
+ return status
+ def filter_data(self, data):
+ """BaseWSGIFilter interface"""
+ self.length += len(data)
+ return data
+ def handle_close(self):
+ """BaseWSGIFilter interface"""
+ line = '%s - - [%s] "%s' % (self.remote, self.time, self.reqmethod)
+ line = '%s %s' % (line, self.path)
+ if self.proto is not None:
+ line = "%s %s" % (line, self.proto)
+ line = '%s" %s %d' % (line, self.status, self.length)
+ if self.referrer is not None:
+ line = '%s "%s"' % (line, self.referrer)
+ else:
+ line += " -"
+ if self.useragent is not None:
+ line = '%s "%s"' % (line, self.useragent)
+ else:
+ line += " -"
+ print >> self.log, line
+
+__all__.append("TimerWSGIFilter")
+class TimerWSGIFilter(BaseWSGIFilter):
+ @classmethod
+ def creator(cls, pattern):
+ return lambda:cls(pattern)
+ def __init__(self, pattern="?GenTime"):
+ self.pattern = pattern
+ self.start = time.time()
+ def filter_data(self, data):
+ """BaseWSGIFilter interface"""
+ if data == self.pattern:
+ return "%8.3g" % (time.time() - self.start)
+ return data
+
+__all__.append("EncodeWSGIFilter")
+class EncodeWSGIFilter(BaseWSGIFilter):
+ """Encodes all body data (no headers) with given charset."""
+ @classmethod
+ def creator(cls, charset):
+ return lambda:cls(charset)
+ def __init__(self, charset="utf-8"):
+ self.charset = charset
+ def filter_data(self, data):
+ """BaseWSGIFilter interface"""
+ return data.encode(self.charset)
+ def filter_header(self, header, value):
+ if header.lower() != "content-type":
+ return (header, value)
+ return (header, "%s; charset=%s" % (value, self.charset))