| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133 |
- # (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
- # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
- """
- Cascades through several applications, so long as applications
- return ``404 Not Found``.
- """
- from paste import httpexceptions
- from paste.util import converters
- import tempfile
- from cStringIO import StringIO
- __all__ = ['Cascade']
- def make_cascade(loader, global_conf, catch='404', **local_conf):
- """
- Entry point for Paste Deploy configuration
- Expects configuration like::
- [composit:cascade]
- use = egg:Paste#cascade
- # all start with 'app' and are sorted alphabetically
- app1 = foo
- app2 = bar
- ...
- catch = 404 500 ...
- """
- catch = map(int, converters.aslist(catch))
- apps = []
- for name, value in local_conf.items():
- if not name.startswith('app'):
- raise ValueError(
- "Bad configuration key %r (=%r); all configuration keys "
- "must start with 'app'"
- % (name, value))
- app = loader.get_app(value, global_conf=global_conf)
- apps.append((name, app))
- apps.sort()
- apps = [app for name, app in apps]
- return Cascade(apps, catch=catch)
- class Cascade(object):
- """
- Passed a list of applications, ``Cascade`` will try each of them
- in turn. If one returns a status code listed in ``catch`` (by
- default just ``404 Not Found``) then the next application is
- tried.
- If all applications fail, then the last application's failure
- response is used.
- Instances of this class are WSGI applications.
- """
- def __init__(self, applications, catch=(404,)):
- self.apps = applications
- self.catch_codes = {}
- self.catch_exceptions = []
- for error in catch:
- if isinstance(error, str):
- error = int(error.split(None, 1)[0])
- if isinstance(error, httpexceptions.HTTPException):
- exc = error
- code = error.code
- else:
- exc = httpexceptions.get_exception(error)
- code = error
- self.catch_codes[code] = exc
- self.catch_exceptions.append(exc)
- self.catch_exceptions = tuple(self.catch_exceptions)
- def __call__(self, environ, start_response):
- """
- WSGI application interface
- """
- failed = []
- def repl_start_response(status, headers, exc_info=None):
- code = int(status.split(None, 1)[0])
- if code in self.catch_codes:
- failed.append(None)
- return _consuming_writer
- return start_response(status, headers, exc_info)
- try:
- length = int(environ.get('CONTENT_LENGTH', 0) or 0)
- except ValueError:
- length = 0
- if length > 0:
- # We have to copy wsgi.input
- copy_wsgi_input = True
- if length > 4096 or length < 0:
- f = tempfile.TemporaryFile()
- if length < 0:
- f.write(environ['wsgi.input'].read())
- else:
- copy_len = length
- while copy_len > 0:
- chunk = environ['wsgi.input'].read(min(copy_len, 4096))
- if not chunk:
- raise IOError("Request body truncated")
- f.write(chunk)
- copy_len -= len(chunk)
- f.seek(0)
- else:
- f = StringIO(environ['wsgi.input'].read(length))
- environ['wsgi.input'] = f
- else:
- copy_wsgi_input = False
- for app in self.apps[:-1]:
- environ_copy = environ.copy()
- if copy_wsgi_input:
- environ_copy['wsgi.input'].seek(0)
- failed = []
- try:
- v = app(environ_copy, repl_start_response)
- if not failed:
- return v
- else:
- if hasattr(v, 'close'):
- # Exhaust the iterator first:
- list(v)
- # then close:
- v.close()
- except self.catch_exceptions:
- pass
- if copy_wsgi_input:
- environ['wsgi.input'].seek(0)
- return self.apps[-1](environ, start_response)
- def _consuming_writer(s):
- pass
|