diff options
author | Marc Abramowitz <marc@marc-abramowitz.com> | 2015-04-30 16:42:17 -0700 |
---|---|---|
committer | Marc Abramowitz <marc@marc-abramowitz.com> | 2015-04-30 16:42:17 -0700 |
commit | 12a3f1f4cfa7f88478dc1b0e949fcc095b9fc804 (patch) | |
tree | ddb8079523d846f0b074437fc33fa5e28b508183 /paste/cascade.py | |
download | paste-git-eliminate_cgi_parse_qsl.tar.gz |
Replace cgi.parse_qsl w/ six.moves.urllib.parse.parse_sqleliminate_cgi_parse_qsl_2eliminate_cgi_parse_qsl
because `cgi.parse_qsl` is deprecated, according to
https://docs.python.org/2/library/cgi.html#cgi.parse_qsl
Diffstat (limited to 'paste/cascade.py')
-rw-r--r-- | paste/cascade.py | 133 |
1 files changed, 133 insertions, 0 deletions
diff --git a/paste/cascade.py b/paste/cascade.py new file mode 100644 index 0000000..8207ae3 --- /dev/null +++ b/paste/cascade.py @@ -0,0 +1,133 @@ +# (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 |