CherryPy / cherrypy / test / modwsgi.py

"""Wrapper for mod_wsgi, for use as a CherryPy HTTP server.

To autostart modwsgi, the "apache" executable or script must be
on your system path, or you must override the global APACHE_PATH.
On some platforms, "apache" may be called "apachectl" or "apache2ctl"--
create a symlink to them if needed.


KNOWN BUGS
==========

##1. Apache processes Range headers automatically; CherryPy's truncated
##    output is then truncated again by Apache. See test_core.testRanges.
##    This was worked around in http://www.cherrypy.org/changeset/1319.
2. Apache does not allow custom HTTP methods like CONNECT as per the spec.
    See test_core.testHTTPMethods.
3. Max request header and body settings do not work with Apache.
##4. Apache replaces status "reason phrases" automatically. For example,
##    CherryPy may set "304 Not modified" but Apache will write out
##    "304 Not Modified" (capital "M").
##5. Apache does not allow custom error codes as per the spec.
##6. Apache (or perhaps modpython, or modpython_gateway) unquotes %xx in the
##    Request-URI too early.
7. mod_wsgi will not read request bodies which use the "chunked"
    transfer-coding (it passes REQUEST_CHUNKED_ERROR to ap_setup_client_block
    instead of REQUEST_CHUNKED_DECHUNK, see Apache2's http_protocol.c and
    mod_python's requestobject.c).
8. When responding with 204 No Content, mod_wsgi adds a Content-Length
    header for you.
9. When an error is raised, mod_wsgi has no facility for printing a
    traceback as the response content (it's sent to the Apache log instead).
10. Startup and shutdown of Apache when running mod_wsgi seems slow.
"""

import os
curdir = os.path.abspath(os.path.dirname(__file__))
import re
import sys
import time

import cherrypy
from cherrypy.test import helper, webtest


def read_process(cmd, args=""):
    pipein, pipeout = os.popen4("%s %s" % (cmd, args))
    try:
        firstline = pipeout.readline()
        if (re.search(r"(not recognized|No such file|not found)", firstline,
                      re.IGNORECASE)):
            raise IOError('%s must be on your system path.' % cmd)
        output = firstline + pipeout.read()
    finally:
        pipeout.close()
    return output


if sys.platform == 'win32':
    APACHE_PATH = "httpd"
else:
    APACHE_PATH = "apache"

CONF_PATH = "test_mw.conf"

conf_modwsgi = r"""
# Apache2 server conf file for testing CherryPy with modpython_gateway.

ServerName 127.0.0.1
DocumentRoot "/"
Listen %(port)s

AllowEncodedSlashes On
LoadModule rewrite_module modules/mod_rewrite.so
RewriteEngine on
RewriteMap escaping int:escape

LoadModule log_config_module modules/mod_log_config.so
LogFormat "%%h %%l %%u %%t \"%%r\" %%>s %%b \"%%{Referer}i\" \"%%{User-agent}i\"" combined
CustomLog "%(curdir)s/apache.access.log" combined
ErrorLog "%(curdir)s/apache.error.log"
LogLevel debug

LoadModule wsgi_module modules/mod_wsgi.so
LoadModule env_module modules/mod_env.so

WSGIScriptAlias / "%(curdir)s/modwsgi.py"
SetEnv testmod %(testmod)s
"""


class ModWSGISupervisor(helper.Supervisor):
    """Server Controller for ModWSGI and CherryPy."""

    using_apache = True
    using_wsgi = True
    template=conf_modwsgi

    def __str__(self):
        return "ModWSGI Server on %s:%s" % (self.host, self.port)

    def start(self, modulename):
        mpconf = CONF_PATH
        if not os.path.isabs(mpconf):
            mpconf = os.path.join(curdir, mpconf)

        f = open(mpconf, 'wb')
        try:
            output = (self.template %
                      {'port': self.port, 'testmod': modulename,
                       'curdir': curdir})
            f.write(output)
        finally:
            f.close()

        result = read_process(APACHE_PATH, "-k start -f %s" % mpconf)
        if result:
            print(result)

        # Make a request so mod_wsgi starts up our app.
        # If we don't, concurrent initial requests will 404.
        cherrypy._cpserver.wait_for_occupied_port("127.0.0.1", self.port)
        webtest.openURL('/ihopetheresnodefault', port=self.port)
        time.sleep(1)

    def stop(self):
        """Gracefully shutdown a server that is serving forever."""
        read_process(APACHE_PATH, "-k stop")


loaded = False
def application(environ, start_response):
    import cherrypy
    global loaded
    if not loaded:
        loaded = True
        modname = "cherrypy.test." + environ['testmod']
        mod = __import__(modname, globals(), locals(), [''])
        mod.setup_server()

        cherrypy.config.update({
            "log.error_file": os.path.join(curdir, "test.error.log"),
            "log.access_file": os.path.join(curdir, "test.access.log"),
            "environment": "test_suite",
            "engine.SIGHUP": None,
            "engine.SIGTERM": None,
            })
    return cherrypy.tree(environ, start_response)
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.