| « Does your bender have a Lucy Liu? | Newsradio DVD just shipped!!! » |
Update: I forgot to address buffering.
As I mentioned, I threw together an WSGI wrapper (gateway) for ASP. Here it is. Feedback welcome.
It blanks out SCRIPT_NAME to behave more like Apache. It also handles URL-rewriting, since that's pretty much the only sane way to use ASP with WSGI (or any framework, for that matter).
"""
WSGI wrapper for ASP.
Example Global.asa for a CherryPy app called "mcontrol":
<script language=Python runat=Server>
def Application_OnStart():
Application.Contents("multiprocess") = False
Application.Contents("multithread") = True
from mcontrol import chpy
</script>
Example handler.asp:
<%@Language=Python%>
<%
from wsgiref.asp_gateway import handler
from cherrypy.wsgiapp import wsgiApp
handler(Application, Request, Response).run(wsgiApp)
%>
"""
import sys
from wsgiref.handlers import BaseCGIHandler
class ASPInputWrapper(object):
def __init__(self, Request):
self.stream = Request.BinaryRead
size = Request.ServerVariables('CONTENT_LENGTH')
self.remainder = self.size = int(size)
def read(self, size=-1):
if size lt; 0:
size = self.remainder
content, size = self.stream(size)
self.remainder -= size
return content
def readline(self):
output = []
while True:
# Use an internal buffer instead? Still have to check for \n
char = self.read(1)
if not char:
break
output.append(char)
if char in ('\n', '\r'):
break
return ''.join(output)
def readlines(self, hint=-1):
lines = []
while True:
line = self.readline()
if not line:
break
lines.append(line)
return lines
def __iter__(self):
line = self.readline()
while line:
yield line
# Notice this won't prefetch the next line; it only
# gets called if the generator is resumed.
line = self.readline()
class handler(BaseCGIHandler):
def __init__(self, Application, Request, Response, buffering=True):
# If you set buffering to False, you must not "Enable Buffering" in
# the current Virtual Directory, NOR in any of its parent containers
# (directory, site, or server). IIS 5 and 6 buffer by default.
# See http://support.microsoft.com/default.aspx?scid=kb;en-us;Q306805
# and http://www.aspfaq.com/show.asp?id=2262
Response.Buffer = buffering
env = {}
for name in Request.ServerVariables:
try:
# names and values are both probably unicode. coerce them.
env[str(name)] = str(Request.ServerVariables(name))
except UnicodeEncodeError, x:
# There's a potential problem lurking here, since some ASP
# server var's which are required by WSGI may be high ASCII.
x.args += ((u"Server Variable '%s'" % name),)
raise x
multiprocess = str(Application.Contents("multiprocess"))
multithread = str(Application.Contents("multithread"))
# You will probably need *some* form of rewriter to use ASP
# with WSGI, since ASP requires one physical .asp file
# per requestable-URL; so far, we support one:
# Handle URL rewriting done by ISAPI_Rewrite Lite.
# http://www.isapirewrite.com/
# Note that PATH_TRANSLATED is also rewritten, but we
# don't make any provision for unmunging that.
old_path = env.get("HTTP_X_REWRITE_URL", None)
if old_path:
# Tear off any params.
env["PATH_INFO"] = old_path.split("?")[0]
# ASP puts the same values in SCRIPT_NAME and PATH_INFO,
# for some odd reason. Empty one of them.
env["SCRIPT_NAME"] = ""
BaseCGIHandler.__init__(self,
stdin=ASPInputWrapper(Request),
stdout=None,
stderr=sys.stderr,
environ=env,
multiprocess=multiprocess,
multithread=multithread
)
self.Response = Response
self._write = Response.Write
def _flush(self):
self.Response.Flush()
def send_headers(self):
self.cleanup_headers()
self.headers_sent = True
for key, val in self.headers.items():
self.Response.AddHeader(key, val)
I'm not up on the technicalities, but does this module work with Mark Ree's ISAPI-WSGI toolkit?
http://isapi-wsgi.python-hosting.com/
Hi, Andy! I've been reading your blog for quite a while.
As I mentioned earlier, I don't think Mark's ISAPI is ready for my needs yet (no threads yet). The non-WSGI ISAPI that Phil Frantz put into pywin32 (build 203+) seems a bit further along right now--could be helpful to finish Mark's.
Of course, the ASP wrapper (above) doesn't really have anything to do with any ISAPI implementation, except that they compete in roughly the same space.
Yes, isapi_wsgi doesn't have thread support yet. Have been working on the next release to use IO Completion ports but have a few problems to sort out. Also a lack of spare time is slowing down development :-)
How does global.asa find mcontrol? Where does the cherrypy app live?
Sean,
global.asa finds "mcontrol" in the same manner that any other Python script finds a module. In my particular case, I have a package named "mcontrol" in my site-packages directory; "mcontrol" is the name of my cherrypy application, and it contains a module named "chpy" which initializes the CherryPy config and server.
I'm revisiting using this for running cherrypy behind IIS. It's mostly working at this point, after running into a swath of small problems. The last problem I encountered is that in CherryPy, it expects the result of a cgiFieldStorage read to return a string (or at least an object with a .strip method). The object returned at line 43, however, returns a buffer object which does not meet this interface.
This causes an error 'buffer object has no attribute strip' in cgi.py when a POST request is sent through CherryPy.
I corrected the problem with a quick fix by explicitly transforming the result to a string:
def read(self, size=-1):
if size < 0:
size = self.remainder
content, size = self.stream(size)
self.remainder -= size
return str( content )
Thanks, Jason! I've incorporated your change at the permanent home of asp_gateway: http://projects.amor.org/misc/browser/asp_gateway.py