| Index: third_party/cherrypy/_cpwsgi.py
|
| ===================================================================
|
| --- third_party/cherrypy/_cpwsgi.py (revision 0)
|
| +++ third_party/cherrypy/_cpwsgi.py (revision 0)
|
| @@ -0,0 +1,408 @@
|
| +"""WSGI interface (see PEP 333 and 3333).
|
| +
|
| +Note that WSGI environ keys and values are 'native strings'; that is,
|
| +whatever the type of "" is. For Python 2, that's a byte string; for Python 3,
|
| +it's a unicode string. But PEP 3333 says: "even if Python's str type is
|
| +actually Unicode "under the hood", the content of native strings must
|
| +still be translatable to bytes via the Latin-1 encoding!"
|
| +"""
|
| +
|
| +import sys as _sys
|
| +
|
| +import cherrypy as _cherrypy
|
| +from cherrypy._cpcompat import BytesIO, bytestr, ntob, ntou, py3k, unicodestr
|
| +from cherrypy import _cperror
|
| +from cherrypy.lib import httputil
|
| +
|
| +
|
| +def downgrade_wsgi_ux_to_1x(environ):
|
| + """Return a new environ dict for WSGI 1.x from the given WSGI u.x environ."""
|
| + env1x = {}
|
| +
|
| + url_encoding = environ[ntou('wsgi.url_encoding')]
|
| + for k, v in list(environ.items()):
|
| + if k in [ntou('PATH_INFO'), ntou('SCRIPT_NAME'), ntou('QUERY_STRING')]:
|
| + v = v.encode(url_encoding)
|
| + elif isinstance(v, unicodestr):
|
| + v = v.encode('ISO-8859-1')
|
| + env1x[k.encode('ISO-8859-1')] = v
|
| +
|
| + return env1x
|
| +
|
| +
|
| +class VirtualHost(object):
|
| + """Select a different WSGI application based on the Host header.
|
| +
|
| + This can be useful when running multiple sites within one CP server.
|
| + It allows several domains to point to different applications. For example::
|
| +
|
| + root = Root()
|
| + RootApp = cherrypy.Application(root)
|
| + Domain2App = cherrypy.Application(root)
|
| + SecureApp = cherrypy.Application(Secure())
|
| +
|
| + vhost = cherrypy._cpwsgi.VirtualHost(RootApp,
|
| + domains={'www.domain2.example': Domain2App,
|
| + 'www.domain2.example:443': SecureApp,
|
| + })
|
| +
|
| + cherrypy.tree.graft(vhost)
|
| + """
|
| + default = None
|
| + """Required. The default WSGI application."""
|
| +
|
| + use_x_forwarded_host = True
|
| + """If True (the default), any "X-Forwarded-Host"
|
| + request header will be used instead of the "Host" header. This
|
| + is commonly added by HTTP servers (such as Apache) when proxying."""
|
| +
|
| + domains = {}
|
| + """A dict of {host header value: application} pairs.
|
| + The incoming "Host" request header is looked up in this dict,
|
| + and, if a match is found, the corresponding WSGI application
|
| + will be called instead of the default. Note that you often need
|
| + separate entries for "example.com" and "www.example.com".
|
| + In addition, "Host" headers may contain the port number.
|
| + """
|
| +
|
| + def __init__(self, default, domains=None, use_x_forwarded_host=True):
|
| + self.default = default
|
| + self.domains = domains or {}
|
| + self.use_x_forwarded_host = use_x_forwarded_host
|
| +
|
| + def __call__(self, environ, start_response):
|
| + domain = environ.get('HTTP_HOST', '')
|
| + if self.use_x_forwarded_host:
|
| + domain = environ.get("HTTP_X_FORWARDED_HOST", domain)
|
| +
|
| + nextapp = self.domains.get(domain)
|
| + if nextapp is None:
|
| + nextapp = self.default
|
| + return nextapp(environ, start_response)
|
| +
|
| +
|
| +class InternalRedirector(object):
|
| + """WSGI middleware that handles raised cherrypy.InternalRedirect."""
|
| +
|
| + def __init__(self, nextapp, recursive=False):
|
| + self.nextapp = nextapp
|
| + self.recursive = recursive
|
| +
|
| + def __call__(self, environ, start_response):
|
| + redirections = []
|
| + while True:
|
| + environ = environ.copy()
|
| + try:
|
| + return self.nextapp(environ, start_response)
|
| + except _cherrypy.InternalRedirect:
|
| + ir = _sys.exc_info()[1]
|
| + sn = environ.get('SCRIPT_NAME', '')
|
| + path = environ.get('PATH_INFO', '')
|
| + qs = environ.get('QUERY_STRING', '')
|
| +
|
| + # Add the *previous* path_info + qs to redirections.
|
| + old_uri = sn + path
|
| + if qs:
|
| + old_uri += "?" + qs
|
| + redirections.append(old_uri)
|
| +
|
| + if not self.recursive:
|
| + # Check to see if the new URI has been redirected to already
|
| + new_uri = sn + ir.path
|
| + if ir.query_string:
|
| + new_uri += "?" + ir.query_string
|
| + if new_uri in redirections:
|
| + ir.request.close()
|
| + raise RuntimeError("InternalRedirector visited the "
|
| + "same URL twice: %r" % new_uri)
|
| +
|
| + # Munge the environment and try again.
|
| + environ['REQUEST_METHOD'] = "GET"
|
| + environ['PATH_INFO'] = ir.path
|
| + environ['QUERY_STRING'] = ir.query_string
|
| + environ['wsgi.input'] = BytesIO()
|
| + environ['CONTENT_LENGTH'] = "0"
|
| + environ['cherrypy.previous_request'] = ir.request
|
| +
|
| +
|
| +class ExceptionTrapper(object):
|
| + """WSGI middleware that traps exceptions."""
|
| +
|
| + def __init__(self, nextapp, throws=(KeyboardInterrupt, SystemExit)):
|
| + self.nextapp = nextapp
|
| + self.throws = throws
|
| +
|
| + def __call__(self, environ, start_response):
|
| + return _TrappedResponse(self.nextapp, environ, start_response, self.throws)
|
| +
|
| +
|
| +class _TrappedResponse(object):
|
| +
|
| + response = iter([])
|
| +
|
| + def __init__(self, nextapp, environ, start_response, throws):
|
| + self.nextapp = nextapp
|
| + self.environ = environ
|
| + self.start_response = start_response
|
| + self.throws = throws
|
| + self.started_response = False
|
| + self.response = self.trap(self.nextapp, self.environ, self.start_response)
|
| + self.iter_response = iter(self.response)
|
| +
|
| + def __iter__(self):
|
| + self.started_response = True
|
| + return self
|
| +
|
| + if py3k:
|
| + def __next__(self):
|
| + return self.trap(next, self.iter_response)
|
| + else:
|
| + def next(self):
|
| + return self.trap(self.iter_response.next)
|
| +
|
| + def close(self):
|
| + if hasattr(self.response, 'close'):
|
| + self.response.close()
|
| +
|
| + def trap(self, func, *args, **kwargs):
|
| + try:
|
| + return func(*args, **kwargs)
|
| + except self.throws:
|
| + raise
|
| + except StopIteration:
|
| + raise
|
| + except:
|
| + tb = _cperror.format_exc()
|
| + #print('trapped (started %s):' % self.started_response, tb)
|
| + _cherrypy.log(tb, severity=40)
|
| + if not _cherrypy.request.show_tracebacks:
|
| + tb = ""
|
| + s, h, b = _cperror.bare_error(tb)
|
| + if py3k:
|
| + # What fun.
|
| + s = s.decode('ISO-8859-1')
|
| + h = [(k.decode('ISO-8859-1'), v.decode('ISO-8859-1'))
|
| + for k, v in h]
|
| + if self.started_response:
|
| + # Empty our iterable (so future calls raise StopIteration)
|
| + self.iter_response = iter([])
|
| + else:
|
| + self.iter_response = iter(b)
|
| +
|
| + try:
|
| + self.start_response(s, h, _sys.exc_info())
|
| + except:
|
| + # "The application must not trap any exceptions raised by
|
| + # start_response, if it called start_response with exc_info.
|
| + # Instead, it should allow such exceptions to propagate
|
| + # back to the server or gateway."
|
| + # But we still log and call close() to clean up ourselves.
|
| + _cherrypy.log(traceback=True, severity=40)
|
| + raise
|
| +
|
| + if self.started_response:
|
| + return ntob("").join(b)
|
| + else:
|
| + return b
|
| +
|
| +
|
| +# WSGI-to-CP Adapter #
|
| +
|
| +
|
| +class AppResponse(object):
|
| + """WSGI response iterable for CherryPy applications."""
|
| +
|
| + def __init__(self, environ, start_response, cpapp):
|
| + self.cpapp = cpapp
|
| + try:
|
| + if not py3k:
|
| + if environ.get(ntou('wsgi.version')) == (ntou('u'), 0):
|
| + environ = downgrade_wsgi_ux_to_1x(environ)
|
| + self.environ = environ
|
| + self.run()
|
| +
|
| + r = _cherrypy.serving.response
|
| +
|
| + outstatus = r.output_status
|
| + if not isinstance(outstatus, bytestr):
|
| + raise TypeError("response.output_status is not a byte string.")
|
| +
|
| + outheaders = []
|
| + for k, v in r.header_list:
|
| + if not isinstance(k, bytestr):
|
| + raise TypeError("response.header_list key %r is not a byte string." % k)
|
| + if not isinstance(v, bytestr):
|
| + raise TypeError("response.header_list value %r is not a byte string." % v)
|
| + outheaders.append((k, v))
|
| +
|
| + if py3k:
|
| + # According to PEP 3333, when using Python 3, the response status
|
| + # and headers must be bytes masquerading as unicode; that is, they
|
| + # must be of type "str" but are restricted to code points in the
|
| + # "latin-1" set.
|
| + outstatus = outstatus.decode('ISO-8859-1')
|
| + outheaders = [(k.decode('ISO-8859-1'), v.decode('ISO-8859-1'))
|
| + for k, v in outheaders]
|
| +
|
| + self.iter_response = iter(r.body)
|
| + self.write = start_response(outstatus, outheaders)
|
| + except:
|
| + self.close()
|
| + raise
|
| +
|
| + def __iter__(self):
|
| + return self
|
| +
|
| + if py3k:
|
| + def __next__(self):
|
| + return next(self.iter_response)
|
| + else:
|
| + def next(self):
|
| + return self.iter_response.next()
|
| +
|
| + def close(self):
|
| + """Close and de-reference the current request and response. (Core)"""
|
| + self.cpapp.release_serving()
|
| +
|
| + def run(self):
|
| + """Create a Request object using environ."""
|
| + env = self.environ.get
|
| +
|
| + local = httputil.Host('', int(env('SERVER_PORT', 80)),
|
| + env('SERVER_NAME', ''))
|
| + remote = httputil.Host(env('REMOTE_ADDR', ''),
|
| + int(env('REMOTE_PORT', -1) or -1),
|
| + env('REMOTE_HOST', ''))
|
| + scheme = env('wsgi.url_scheme')
|
| + sproto = env('ACTUAL_SERVER_PROTOCOL', "HTTP/1.1")
|
| + request, resp = self.cpapp.get_serving(local, remote, scheme, sproto)
|
| +
|
| + # LOGON_USER is served by IIS, and is the name of the
|
| + # user after having been mapped to a local account.
|
| + # Both IIS and Apache set REMOTE_USER, when possible.
|
| + request.login = env('LOGON_USER') or env('REMOTE_USER') or None
|
| + request.multithread = self.environ['wsgi.multithread']
|
| + request.multiprocess = self.environ['wsgi.multiprocess']
|
| + request.wsgi_environ = self.environ
|
| + request.prev = env('cherrypy.previous_request', None)
|
| +
|
| + meth = self.environ['REQUEST_METHOD']
|
| +
|
| + path = httputil.urljoin(self.environ.get('SCRIPT_NAME', ''),
|
| + self.environ.get('PATH_INFO', ''))
|
| + qs = self.environ.get('QUERY_STRING', '')
|
| +
|
| + if py3k:
|
| + # This isn't perfect; if the given PATH_INFO is in the wrong encoding,
|
| + # it may fail to match the appropriate config section URI. But meh.
|
| + old_enc = self.environ.get('wsgi.url_encoding', 'ISO-8859-1')
|
| + new_enc = self.cpapp.find_config(self.environ.get('PATH_INFO', ''),
|
| + "request.uri_encoding", 'utf-8')
|
| + if new_enc.lower() != old_enc.lower():
|
| + # Even though the path and qs are unicode, the WSGI server is
|
| + # required by PEP 3333 to coerce them to ISO-8859-1 masquerading
|
| + # as unicode. So we have to encode back to bytes and then decode
|
| + # again using the "correct" encoding.
|
| + try:
|
| + u_path = path.encode(old_enc).decode(new_enc)
|
| + u_qs = qs.encode(old_enc).decode(new_enc)
|
| + except (UnicodeEncodeError, UnicodeDecodeError):
|
| + # Just pass them through without transcoding and hope.
|
| + pass
|
| + else:
|
| + # Only set transcoded values if they both succeed.
|
| + path = u_path
|
| + qs = u_qs
|
| +
|
| + rproto = self.environ.get('SERVER_PROTOCOL')
|
| + headers = self.translate_headers(self.environ)
|
| + rfile = self.environ['wsgi.input']
|
| + request.run(meth, path, qs, rproto, headers, rfile)
|
| +
|
| + headerNames = {'HTTP_CGI_AUTHORIZATION': 'Authorization',
|
| + 'CONTENT_LENGTH': 'Content-Length',
|
| + 'CONTENT_TYPE': 'Content-Type',
|
| + 'REMOTE_HOST': 'Remote-Host',
|
| + 'REMOTE_ADDR': 'Remote-Addr',
|
| + }
|
| +
|
| + def translate_headers(self, environ):
|
| + """Translate CGI-environ header names to HTTP header names."""
|
| + for cgiName in environ:
|
| + # We assume all incoming header keys are uppercase already.
|
| + if cgiName in self.headerNames:
|
| + yield self.headerNames[cgiName], environ[cgiName]
|
| + elif cgiName[:5] == "HTTP_":
|
| + # Hackish attempt at recovering original header names.
|
| + translatedHeader = cgiName[5:].replace("_", "-")
|
| + yield translatedHeader, environ[cgiName]
|
| +
|
| +
|
| +class CPWSGIApp(object):
|
| + """A WSGI application object for a CherryPy Application."""
|
| +
|
| + pipeline = [('ExceptionTrapper', ExceptionTrapper),
|
| + ('InternalRedirector', InternalRedirector),
|
| + ]
|
| + """A list of (name, wsgiapp) pairs. Each 'wsgiapp' MUST be a
|
| + constructor that takes an initial, positional 'nextapp' argument,
|
| + plus optional keyword arguments, and returns a WSGI application
|
| + (that takes environ and start_response arguments). The 'name' can
|
| + be any you choose, and will correspond to keys in self.config."""
|
| +
|
| + head = None
|
| + """Rather than nest all apps in the pipeline on each call, it's only
|
| + done the first time, and the result is memoized into self.head. Set
|
| + this to None again if you change self.pipeline after calling self."""
|
| +
|
| + config = {}
|
| + """A dict whose keys match names listed in the pipeline. Each
|
| + value is a further dict which will be passed to the corresponding
|
| + named WSGI callable (from the pipeline) as keyword arguments."""
|
| +
|
| + response_class = AppResponse
|
| + """The class to instantiate and return as the next app in the WSGI chain."""
|
| +
|
| + def __init__(self, cpapp, pipeline=None):
|
| + self.cpapp = cpapp
|
| + self.pipeline = self.pipeline[:]
|
| + if pipeline:
|
| + self.pipeline.extend(pipeline)
|
| + self.config = self.config.copy()
|
| +
|
| + def tail(self, environ, start_response):
|
| + """WSGI application callable for the actual CherryPy application.
|
| +
|
| + You probably shouldn't call this; call self.__call__ instead,
|
| + so that any WSGI middleware in self.pipeline can run first.
|
| + """
|
| + return self.response_class(environ, start_response, self.cpapp)
|
| +
|
| + def __call__(self, environ, start_response):
|
| + head = self.head
|
| + if head is None:
|
| + # Create and nest the WSGI apps in our pipeline (in reverse order).
|
| + # Then memoize the result in self.head.
|
| + head = self.tail
|
| + for name, callable in self.pipeline[::-1]:
|
| + conf = self.config.get(name, {})
|
| + head = callable(head, **conf)
|
| + self.head = head
|
| + return head(environ, start_response)
|
| +
|
| + def namespace_handler(self, k, v):
|
| + """Config handler for the 'wsgi' namespace."""
|
| + if k == "pipeline":
|
| + # Note this allows multiple 'wsgi.pipeline' config entries
|
| + # (but each entry will be processed in a 'random' order).
|
| + # It should also allow developers to set default middleware
|
| + # in code (passed to self.__init__) that deployers can add to
|
| + # (but not remove) via config.
|
| + self.pipeline.extend(v)
|
| + elif k == "response_class":
|
| + self.response_class = v
|
| + else:
|
| + name, arg = k.split(".", 1)
|
| + bucket = self.config.setdefault(name, {})
|
| + bucket[arg] = v
|
| +
|
|
|
| Property changes on: third_party/cherrypy/_cpwsgi.py
|
| ___________________________________________________________________
|
| Added: svn:eol-style
|
| + LF
|
|
|
|
|