How to set up Python on FastCGI on IIS
Here's how to set up Python on FastCGI IIS 7+ with opens the way for a decent DJango setup
... and be able to hook a debugger into the process enabling you to step through your Python code
This example doesn't use the IIS management console but lists the contents of the resulting configuration files
Install Python + a good debugger (this example uses WingIDE which I found an excellent tool)
This example assumes folder c:\python27
Create a web folder, eg on localhost c:\inetpub\wwwroot\mypythonfolder and put the following web.config file in it:
Note the | pipe character in the scriptProcessor directive. This is used by IIS to map the script to a fastCgi application (step 3).
It should match character by character the fullpath + pipe character + arguments settings from step 3 below.
In the applicationHost.config file in c:\windows\system32\inetsrc\config folder place the following in the
<application fullPath="c:\python27\python.exe" arguments="c:\python27\lib\mylib\myfcgi.py" monitorChangesTo="C:\Python27\Lib\r4a\r4afcgi.py" stderrMode="ReturnStdErrIn500" maxInstances="4" idleTimeout="300" activityTimeout="300" requestTimeout="90" instanceMaxRequests="200" protocol="NamedPipe" queueLength="1000" flushNamedPipe="true" rapidFailsPerMinute="10" />
In c:\python27\lib\mylib\myfcgi.py put the following code:
import os, io, sys
ret = "environment:\r\n"
for param in os.environ.keys():
ret = ret + "%s=%s\r\n" % (param,os.environ[param])
ret = ret + "\r\nArgs:"
for arg in sys.argv:
ret = ret + arg
handle = io.open("c:\temp\myfcgi.log", 'wb')
Make sure IUSR has rights to write into your c:\temp folder
Put wingdbstub.py and wingdebugpw into your c:\python27\lib\mylib\ folder. This will enable debugging in wingide. These files are provided with your wing installation.
Note: if Python also needs to compile your code into wingstub.pyc, IUSR needs write rights on that folder since the python process will be launched under that account by IIS
Open wingdb and set a breakpoint on the 'import os, io, sys' line
Hit in your browser http://localhost/mypythonfolder
If everything works correct, wingide should now be triggered to display the running code at your breakpoint. If not:
- either there's a firewall issue. The python process communicates with the WingIDE interface through a tcp connection
- or there's an issue with security within wingide. It needs the proper version of the wingdebugpw file, which basically contains a password or token that validates access against your wingide installation. If this were not the case, anybody with tcp access to your pc could debug against your code.
Verify that in c:\temp the logfile is created. This should also work if you can't get step 7 going
Note that this page triggers the debugger but does not return any page to the webbrowser. Some background: the webserver communicates fastcgi through so called 'records'. This means that each single user-request comes into your application packed in multiple separate 'records'. Each record is a data structure that indicates the beginning of a request, the querystring, post variables etc.
The un-packing of these records to a single request is kind of cumbersome, it follows the fastcgi specification of
As the content of c:\python27\lib\mylib\myfcgi.py I just dropped in a copy of zoofcgi.py provided by helicontech.
This python file is able to decode these records and serve a page and is quite interesting to debug.
Also note that helicontech optionally provides a dll that sits in between IIS and and zoofcgi.py but this dll is not strictly necessary.
I believe it implements a slightly improved and generic version of the fastcgi implementation that msft provides. However when you use their dll, when you want to step through your code the process is terminated rather quicly and IIS/the DLL kills your python process when it concludes no response is coming back within a sec or 2.
That's it. In principle the communication between IIS and your python code is done with named pipes. You should be able to set it up using tcp sockets but I wasn't able to figure out which port is used (I believe the stdin should be transformed into the port which then can be select() ed but I didn't give that any attempt)