CherryPy / cherrypy / _cpwsgi.py

"""A WSGI application interface (see PEP 333)."""

import sys
import cherrypy
from cherrypy import _cputil, _cpwsgiserver
from cherrypy.lib import httptools


def requestLine(environ):
    """Rebuild first line of the request (e.g. "GET /path HTTP/1.0")."""
    
    resource = environ.get('SCRIPT_NAME', '') + environ.get('PATH_INFO', '')
    if not (resource == "*" or resource.startswith("/")):
        resource = "/" + resource
    
    qString = environ.get('QUERY_STRING')
    if qString:
        resource += '?' + qString
    
    resource = resource.replace(" ", "%20")
    
    return ('%s %s %s' % (environ['REQUEST_METHOD'],
                          resource or '/',
                          environ['SERVER_PROTOCOL']
                          )
            )

headerNames = {'HTTP_CGI_AUTHORIZATION': 'Authorization',
               'CONTENT_LENGTH': 'Content-Length',
               'CONTENT_TYPE': 'Content-Type',
               'REMOTE_HOST': 'Remote-Host',
               'REMOTE_ADDR': 'Remote-Addr',
               }

def translate_headers(environ):
    """Translate CGI-environ header names to HTTP header names."""
    for cgiName in environ:
        translatedHeader = headerNames.get(cgiName.upper())
        if translatedHeader:
            yield translatedHeader, environ[cgiName]
        elif cgiName.upper().startswith("HTTP_"):
            # Hackish attempt at recovering original header names.
            translatedHeader = cgiName[5:].replace("_", "-")
            yield translatedHeader, environ[cgiName]


class NullWriter(object):
    
    def write(self, data):
        pass


def wsgiApp(environ, start_response):
    """The WSGI 'application object' for CherryPy."""
    
    # Trap screen output from BaseHTTPRequestHandler.log_message()
    if not cherrypy.config.get('server.log_to_screen'):
        sys.stderr = NullWriter()
    
    request = None
    try:
        # 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.
        env = environ.get
        clientAddr = (env('REMOTE_ADDR', ''), int(env('REMOTE_PORT', -1)))
        request = cherrypy.server.request(clientAddr, env('REMOTE_ADDR', ''),
                                          environ['wsgi.url_scheme'])
        request.login = (env('LOGON_USER') or env('REMOTE_USER') or None)
        request.multithread = environ['wsgi.multithread']
        request.multiprocess = environ['wsgi.multiprocess']
        response = request.run(requestLine(environ),
                               translate_headers(environ),
                               environ['wsgi.input'])
        s, h, b = response.status, response.header_list, response.body
        exc = None
    except (KeyboardInterrupt, SystemExit):
        raise
    except:
        if cherrypy.config.get("server.throw_errors", False):
            raise
        tb = _cputil.formatExc()
        cherrypy.log(tb)
        if not cherrypy.config.get("server.show_tracebacks", False):
            tb = ""
        s, h, b = _cputil.bareError(tb)
        exc = sys.exc_info()
    
    try:
        start_response(s, h, exc)
        for chunk in b:
            # WSGI requires all data to be of type "str". This coercion should
            # not take any time at all if chunk is already of type "str".
            # If it's unicode, it could be a big performance hit (x ~500).
            chunk = str(chunk)
            yield chunk
        if request:
            request.close()
    except (KeyboardInterrupt, SystemExit), ex:
        try:
            if request:
                request.close()
        except:
            cherrypy.log(traceback=True)
        raise ex
    except:
        cherrypy.log(traceback=True)
        try:
            if request:
                request.close()
        except:
            cherrypy.log(traceback=True)
        s, h, b = _cputil.bareError()
        # CherryPy test suite expects bareError body to be output,
        # so don't call start_response (which, according to PEP 333,
        # may raise its own error at that point).
        for chunk in b:
            yield str(chunk)


# Server components.


class CPHTTPRequest(_cpwsgiserver.HTTPRequest):
    
    def __init__(self, socket, addr, server):
        _cpwsgiserver.HTTPRequest.__init__(self, socket, addr, server)
        mhs = int(cherrypy.config.get('server.max_request_header_size',
                                      500 * 1024))
        self.rfile = httptools.SizeCheckWrapper(self.rfile, mhs)
    
    def parse_request(self):
        try:
            _cpwsgiserver.HTTPRequest.parse_request(self)
        except httptools.MaxSizeExceeded:
            msg = "Request Entity Too Large"
            proto = self.environ.get("SERVER_PROTOCOL", "HTTP/1.0")
            self.wfile.write("%s 413 %s\r\n" % (proto, msg))
            self.wfile.write("Content-Length: %s\r\n\r\n" % len(msg))
            self.wfile.write(msg)
            self.wfile.flush()
            self.ready = False
            
            cherrypy.log(traceback=True)
        else:
            if self.ready:
                # Request header is parsed
                # We prepare the SizeCheckWrapper for the request body
                self.rfile.bytes_read = 0
                path = self.environ["SCRIPT_NAME"]
                if path == "*":
                    path = "global"
                else:
                    path = "/" + path
                mbs = int(cherrypy.config.get('server.max_request_body_size',
                                              100 * 1024 * 1024, path=path))
                self.rfile.maxlen = mbs


class WSGIServer(_cpwsgiserver.CherryPyWSGIServer):
    
    """Wrapper for _cpwsgiserver.CherryPyWSGIServer.
    
    _cpwsgiserver has been designed to not reference CherryPy in any way,
    so that it can be used in other frameworks and applications. Therefore,
    we wrap it here.
    
    """
    
    RequestHandlerClass = CPHTTPRequest
    
    def __init__(self, app=wsgiApp):
        conf = cherrypy.config.get
        
        sockFile = cherrypy.config.get('server.socket_file')
        if sockFile:
            bind_addr = sockFile
        else:
            bind_addr = (conf("server.socket_host"), conf("server.socket_port"))
        
        s = _cpwsgiserver.CherryPyWSGIServer
        s.__init__(self, bind_addr, app,
                   conf("server.thread_pool"),
                   conf("server.socket_host"),
                   request_queue_size = conf('server.socket_queue_size'),
                   )
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.