Passenger WSGI
| The instructions provided in this article or section are considered advanced. You are expected to be knowledgeable in the UNIX shell. |
Note that Passenger's WSGI support is currently considered a "proof of concept"[1]. It currently seems to work reasonably well; nevertheless, you should probably have a backup plan ready (such as Python FastCGI) in case you run into problems.
Contents |
Setting up Passenger WSGI
To start an example Python site using Passenger WSGI, your first step should be to configure the domain to use Passenger in the Manage Domains section of the web panel. Note that the document root must end in "/public" for a Passenger application - this directory will be used to serve static media.
Once you have set the domain to use Passenger, create a file called passenger_wsgi.py in the folder above the document root (i.e, if you set your document root to /home/username/example.com/public, you'd put this file at /home/username/example.com/passenger_wsgi.py). This file must export a WSGI server with the name application. Here's a minimal example:
def application(environ, start_response):
start_response('200 OK', [('Content-type', 'text/plain')])
return ["Hello, world!"]
This application will return a text file with the content "Hello, world!" for any request.
Passenger WSGI and Django
- See Django for instructions on how to configure Passenger to run Django.
Passenger WSGI and virtualenv
As Passenger loads your passenger_wsgi.py into a special wrapper (currently /dh/passenger/lib/phusion_passenger/wsgi/request_handler.py, although this may change), you cannot directly select what Python interpreter is used to run your application. However, you can switch interpreters at runtime by adding the following code to the beginning of your passenger_wsgi.py:
import sys, os INTERP = "/home/<username>/local/bin/python" #INTERP is present twice so that the new python interpreter knows the actual executable path if sys.executable != INTERP: os.execl(INTERP, INTERP, *sys.argv)
Set INTERP to the Python interpreter which you wish to use instead of the default.
Passenger WSGI and Pylons/Pyramid
If you're using a Pyramid-framework supported site, the following should work for your passenger_wsgi.py, assuming you've setup the python virtual environment at INTERP:
import sys, os
INTERP = "/home/<username>/local/bin/python"
#INTERP is present twice so that the new python interpreter knows the actual executable path
if sys.executable != INTERP: os.execl(INTERP, INTERP, *sys.argv)
from paste.deploy import loadapp
application = loadapp('config:/home/path/to/site/production.ini')
Note that if you're using a site created from one of the Pyramid starter templates, the development.ini config file wraps your site in the ErrorMiddleware layer, similar to what's done in the next section. However, ErrorMiddleware does not support environments where wsgi.multiprocess is True, so you must use the production config, or modify environ to set wsgi.multiprocess to False. (Note: This may cause problems if you manually override the settings)
500 Errors with Passenger WSGI Workaround
Passenger WSGI at the moment has trouble dealing with errors. Namely, if your WSGI application (for example - but not limited to - Django) raises an uncaught exception Passenger will die, a 500 page will be displayed in the browser, and the error message will not be recorded in the /home/username/logs/sitename/http/error.log file. This makes debugging really tricky.
One solution is to use Python Paste as a WSGI middleware between passenger and your application (django for me). Here is how I did it:
- Grab Paste from here: http://pypi.python.org/pypi/Paste . Unzip, all you need is the "paste" directory; put it into your application directory (for example, /home/username/sitename/myapp/paste)
- Edit your passenger_wsgi.py file to include that directory in the python path, and load paste up. Here is what your passenger_wsgi.py file might look like:
import sys, os
cwd = os.getcwd()
myapp_directory = cwd + '/myapp'
sys.stdout = sys.stderr
sys.path.insert(0,myapp_directory)
sys.path.append(os.getcwd())
os.environ['DJANGO_SETTINGS_MODULE'] = "myapp.settings"
from paste.exceptions.errormiddleware import ErrorMiddleware
import django.core.handlers.wsgi
application = django.core.handlers.wsgi.WSGIHandler()
# To cut django out of the loop, comment the above application = ... line ,
# and remove "test" from the below function definition.
def testapplication(environ, start_response):
status = '200 OK'
output = 'Hello World! Running Python version ' + sys.version + '\n\n'
response_headers = [('Content-type', 'text/plain'),
('Content-Length', str(len(output)))]
# to test paste's error catching prowess, uncomment the following line
# while this function is the "application"
#raise("error")
start_response(status, response_headers)
return [output]
application = ErrorMiddleware(application, debug=True)
Local Logging Alternative
import os, sys
log = file('/home/<username>/passengerwsgi.log', 'a')
print >>log, "Running %s" % (sys.executable)
INTERP = "/home/moatra/local/bin/python"
if sys.executable != INTERP:
print >>log, "Detected wrong interpreter location, swapping to %s" % (INTERP)
#swapping interpreters will not flush any files
log.flush()
log.close()
os.execl(INTERP, INTERP, *sys.argv)
#Should resume execution from the top of the file
from paste.deploy import loadapp
def application(environ, start_response):
print >>log, "Application called:"
print >>log, "environ: %s" % str(environ)
results = []
try:
app = loadapp('config:/home/path/to/site/production.ini')
print >>log, "App loaded, attempting to run"
log.flush()
results = app(environ, start_response)
print >>log, "App executed successfully"
except Exception, inst:
print >>log, "Error: %s" % str(type(inst))
print >>log, inst.args
log.flush()
finally:
log.close()
return results
Slightly More Robust Local Logging Alternative
This example uses Python's logging module. It does not contain the alternative interpreter bit or loading the ini file; it does show how to load local modules using getcwd().
Apart from the name myappmodule and myappmodule.application, this doesn't make any assumptions about your application.
import os
import sys
import logging
# append current dir to module path
cwd = os.getcwd()
sys.path.append(cwd)
# assuming this module is in the same dir as passenger_wsgi, this now works!
import myappmodule
# create a logfile in the current directory
logfilename = os.path.join(cwd, 'passenger_wsgi.log')
# configure the logging
logging.basicConfig(filename=logfilename, level=logging.DEBUG)
logging.info("Running %s", sys.executable)
def application(environ, start_response):
logging.info("Application called:")
logging.info("environ: %s", str(environ))
results = []
try:
results = myappmodule.application(environ, start_response)
logging.info("App executed successfully")
except Exception, inst:
logging.exception("Error: %s", str(type(inst)))
logging.info("Application call done")
return results
Another solution
*NOTE* This is not allowed on shared servers, only PS.
Another solution that worked for me was to first start a development server by executing./manage.py runserverNext, I opened another ssh shell and executed
lynx localhost:8000
This opens your application in the lynx web browser, bypassing Passenger by using the Django development server. If you're lucky, it will return some helpful feedback. Note that this solution won't help if the problem is with your Passenger configuration, since this method bypasses Passenger entirely.
Tips and Tricks
Try running your passenger_wsgi.py from your command line. That should help point out any normal python errors that you'd swear weren't there
Passenger seems to use a persistent python session. After updating passenger_wsgi.py, make sure to run pkill python to reset the session and force the server to use your new changes.
See also
- Passenger
- Python
- Python FastCGI (an alternative approach)