TurboGears + ISAPI-WSGI + IIS

July 10, 2008 at 12:18 PM | categories: Programming | View Comments

On June 19, 2008, Louis wrote to the isapi_wsgi-dev Google Group asking about how to get CherryPy to work with ISAPI-WSGI. Since ISAPI-WSGI was how I was going to connect my Turbo Gears app up to IIS, I recording what I did here for posterity.

The first caveat to this is that you will not get this to work with IIS in 64 bit mode unless you can get a build of PyWin32 for x64. If you running a 64bit Windows architecture you will need to set IIS to 32bit mode and only run 32bit ISAPI dlls. On IIS6 (Windows 2003/XP) this will mean you can only run 32bit DLLs. If you are using IIS7 (Windows 2008), you will, apparently, be able to have 64bit and 32bit process pools. This situation could get better in Python 2.6 since PyWin32 seems to have a x64 build for the 2.6 alphas.

Okay, on to the explanation. The first thing to do in your DLL Python file, is to include these lines:

import sys
if hasattr(sys, "isapidllhandle"):
    import win32traceutil

This checks that we are running as an ISAPI DLL and imports win32traceutil, which redirects stdin and stdout so that you can view them with the win32traceutil message collector (just run the module on its own: python -m win32traceutil). If things go wrong, at least you will have a way of knowing what is going wrong.

My __ExtensionFactory__() looks like this:

def __ExtensionFactory__():
    # Do some pre import setup
    import os

    app_dir = os.path.join(os.path.dirname(__file__), '..', 'app')
    app_dir = os.path.normpath(app_dir)
    sys.path.append(app_dir)
    os.chdir(app_dir)

    # import my app creator
    import wsgi_myapp
    return ISAPIThreadPoolHandler(wsgi_myapp.wsgiApp)

In the do some pre-import setup, we add the application dir to the import path and change directories so that the current working dir is where the TurboGears app is (start-yourproject.py).

Then I have the module that sets up the TurboGears/CherryPy WSGI app. I started with the TurboGears start-yourapp.py file and made modifications that were appropriate for making it work properly with WSGI:

#!c:\Python25\python.exe
from turbogears import config, update_config
import cherrypy
cherrypy.lowercase_api = True

# first look on the command line for a desired config file,
# if it's not on the command line, then
# look for setup.py in this directory. If it's not there, this script is
# probably installed
update_config(configfile="prod.cfg",modulename="yourapp.config")
config.update(dict(package="yourapp"))

from yourapp.controllers import Root

cherrypy.root = Root()
cherrypy.server.start(initOnly=True, serverClass=None)

from cherrypy._cpwsgi import wsgiApp

The CherryPy critical components (for CherryPy 2.2) are:

# Grab your Root object
from yourapp.controllers import Root

# set the root object and initialize the server
cherrypy.root = Root()
cherrypy.server.start(initOnly=True, serverClass=None)

# expose the wsgiApp to be imported by the isapidll.py file above.
from cherrypy._cpwsgi import wsgiApp

Louis is using CherryPy 3 which changes things slightly from what I have done. CherryPy 3 is all WSGI all the time so we need to do less fiddling. Here is what he had for __ExtensionFactory__():

def __ExtensionFactory__():
    try:
        #cpwsgiapp = cherrypy.Application(HelloWorld(),'F:\python-work')
        app = cherrypy.tree.mount(HelloWorld())
        cherrypy.engine.start(blocking=False)
        #wsgi_apps = [('/blog', blog), ('/forum', forum)]

        #server = wsgiserver.CherryPyWSGIServer(('localhost', 8080), HelloWorld(), server_name='localhost')

        return isapi_wsgi.ISAPISimpleHandler(app)
    finally:
        # This ensures that any left-over threads are stopped as well.
        cherrypy.engine.stop()

I left in his extra comments because they show how he got to where he is. This looks like the basic start a server code shown in the tutorial. I think the key issues here are the cherrypy.engine calls. The CherryPy WSGI Wiki page does not mention the engine at all. I would guess that what he actually needs is:

def __ExtensionFactory__():
    app = cherrypy.tree.mount(HelloWorld())
    return isapi_wsgi.ISAPISimpleHandler(app)

That said, I have not used CherryPy 3, so I have no direct experience.

Update: Don't use autoreload with ISAPI-WSGI. It won't work and if you don't use the win32traceutil you won't know why.

Also, I have added more background detail on how to use ISAPI with Python and the ISAPI-WSGI package here.

blog comments powered by Disqus