[Web-SIG] Re: Latest WSGI Draft (Phillip J. Eby)
Phillip J. Eby
pje at telecommunity.com
Tue Aug 24 19:26:47 CEST 2004
At 11:26 AM 8/24/04 -0500, Ian Bicking wrote:
>Peter Hunt wrote:
>>Is there a "Hello, world!" type of middleware that I could take a look at?
>
>I haven't tested this (but I'll try to tonight), but here's perhaps a more
>realistic middleware. This compresses (with gzip) the response from the
>application (if it is allowed to):
>
>import gzip
>from cStringIO import StringIO
>
>class gzip_middleware(object):
>
> def __init__(self, application, compress_level=5):
> self.application = application
> self.compress_level = compress_level
>
> def __call__(self, environ, start_response):
> if 'gzip' not in environ.get('HTTP_ACCEPT'):
> # nothing for us to do, so this middleware will
> # be a no-op:
> return application(environ, start_response)
> response = GzipResponse(start_response, self.compress_level)
> app_iter = self.application(environ,
> response.gzip_start_response)
> response.finish_response(app_iter)
> return None
>
>class GzipResponse(object):
>
> def __iter__(self, start_response, compress_level):
I think you meant '__init__' here.
> self.start_response = start_response
> self.compress_level = compress_level
> self.gzip_fileobj = None
>
> def gzip_start_response(self, status, headers):
> # This isn't part of the spec yet:
> if headers.has_key('content-encoding'):
> # we won't double-encode
> return self.start_response(status, headers)
>
> headers['content-encoding'] = 'gzip'
> raw_writer = self.start_response(status, headers)
> dummy_fileobj = object()
> dummy_fileobj.write = raw_writer
> self.gzip_fileobj = GzipFile('', 'wb', self.compress_level,
> dummy_fileobj)
> return self.gzip_fileobj.write
>
> def finish_response(self, app_iter):
> try:
> for s in app_iter:
> self.gzip_fileobj.write(s)
> finally:
> if hasattr(app_iter, 'close'):
> app_iter.close()
> self.gzip_fileobj.close()
>
>
>
>
>Hmm... For a very simple filter, I actually found that surprisingly
>difficult to write.
Maybe because you used classes unnecessarily?
class GzipOutput(object):
pass
def gzip_middleware(application, compress_level=5):
def do_gzip(environ, start_response):
writer = []
if 'gzip' not in environ.get('HTTP_ACCEPT'):
# nothing for us to do, so this middleware will
# be a no-op:
return application(environ, start_response)
def gzip_start_response(status, headers):
if 'content-encoding' in headers:
writer.append(start_response(status,headers))
else:
headers['content-encoding'] = gzip
raw_writer = start_response(status,headers)
dummy_fileobj = GzipOutput()
dummy_fileobj.write = raw_writer
gzip_file = GzipFile('','wb',compress_level,dummy_fileobj)
writer.append(gzip_file.write)
return writer[0]
app_iter = application(environ,gzip_start_response)
if app_iter and writer:
try:
map(writer[0],app_iter)
finally:
if hasattr(app_iter,'close'):
app_iter.close()
else:
return app_iter
return do_gzip
Hm. That's only slightly less complicated. Still, the only "excise" is
handling the try/finally for the close -- virtually everything else is
directly connected to the required functionality. (By the way, your
implementation tries to iterate even if the app returns None, and you can't
set arbitrary attributes on 'object' instances.)
It may be that the PEP should contain a list of suggested utility
functions, like this one:
def finish_response(write_func,app_return):
if app_return:
try:
map(write_func,app_return)
finally:
if hasattr(app_return,'close'):
app_return.close()
Such a routine would come in handy for response-munging middleware.
> And I think it should take advantage of its server's iteration, but
> currently it only uses the "push" (write function) aspect of the
> server. But I'm not sure how exactly I would do that, especially so that
> the iteration actually had any beneficial properties.
For the given application, it's not important. Gzipping a server push
stream probably doesn't make a lot of sense. :)
If you *really* want to support it, you could do something like:
def iter_response(transformer, queue, app_return):
for data in app_return:
transformer(data)
if queue:
yield ''.join(queue)
queue[:] = []
Where "queue" is a list appended to by the 'transformer'. For your
example, you could set it up like this:
queue = []
dummy_fileobj.write = queue.append
I'll leave the rest as an exercise for the reader. :)
More information about the Web-SIG
mailing list