The problem with your solution is that your HTTP server can only pass requests off to the SCGI server. It would be nice if the same Twisted Web server could handle static content itself and pass requests for dynamic content off to the SCGI server, as Apache can. My solution makes this possible, though my preliminary test script doesn't configure Twisted Web this way. I've attached two files to this message (hoping Mailman isn't configured to disallow attachments). twscgi.py is the module that contains the SCGI client implementation. I placed this file in $HOME/scgi-1.2/scgi so it would go in /usr/lib/python2.3/site-packages/scgi when I installed the scgi package using distutils. I'm not sure if the scgi package is the right place for this module, but it'll work for now. test-twscgi.py is a little script that sets up a Twisted Web server on port 8000, which delegates all requests to the SCGI server on port 1984 (the port used for SCGI in Toboso's dist.py script). Again, with Twisted Web, more complicated configurations are possible, since the SCGIResource class in twscgi is a subclass of twisted.web.resource.Resource. In my test script, the root resource is an SCGIResource, but it doesn't have to be. I hope this helps. -- Matt Campbell Lead Programmer Serotek Corporation www.freedombox.info
# twscgi.py: SCGI client for Twisted Web # by Matt Campbell, July 31, 2004 # Uses substantial chunks of code from twcgi.py # # Twisted, the Framework of Your Internet # Copyright (C) 2001 Matthew W. Lefkowitz # # This library is free software; you can redistribute it and/or # modify it under the terms of version 2.1 of the GNU Lesser General Public # License as published by the Free Software Foundation. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA """I hold resource classes and helper classes that deal with SCGI servers. """ # System Imports import string import os import sys import urllib # Twisted Imports from twisted.protocols import http, basic from twisted.internet import reactor, protocol from twisted.spread import pb from twisted.python import log, filepath from twisted.web import server, error, html, resource, static, twcgi from twisted.web.server import NOT_DONE_YET SCGI_PROTOCOL_VERSION = "1" class SCGIResource(twcgi.CGIScript): """I represent a resource provided by an SCGI server. """ def __init__(self, host="127.0.0.1", port=4000, registry=None): self.host = host self.port = port twcgi.CGIScript.__init__(self, filename="") def runProcess(self, env, request, qargs=[]): env["SCRIPT_NAME"] = request.path if "PATH_INFO" in env: # make it like Apache mod_scgi del env["PATH_INFO"] factory = SCGIClientFactory(env, request) reactor.connectTCP(self.host, self.port, factory) class SCGIClient(basic.LineReceiver, pb.Viewable): handling_headers = 1 # Remotely relay producer interface. def view_resumeProducing(self, issuer): self.resumeProducing() def view_pauseProducing(self, issuer): self.pauseProducing() def view_stopProducing(self, issuer): self.stopProducing() def resumeProducing(self): self.transport.resumeProducing() def pauseProducing(self): self.transport.pauseProducing() def stopProducing(self): self.transport.loseConnection() def connectionMade(self): env, request = self.factory.env, self.factory.request contentLength = "0" if env.get("CONTENT_LENGTH"): contentLength = env["CONTENT_LENGTH"] print "content-length:", contentLength del env["CONTENT_LENGTH"] envstr = "CONTENT_LENGTH\x00%s\x00" % contentLength env["SCGI"] = SCGI_PROTOCOL_VERSION if "HTTP_COOKIE" in env: print "cookie:", env["HTTP_COOKIE"] envstr += "".join(["%s\x00%s\x00" % (key, value) for key, value in env.items()]) self.transport.write("%d:%s," % (len(envstr), envstr)) request.registerProducer(self, 1) request.content.seek(0, 0) content = request.content.read() if content: print "content:", content self.transport.write(content) def lineReceived(self, line): # This is so much simpler than CGIProcessProtocol! line = line.replace("\r", "") if line == "": self.handling_headers = 0 self.setRawMode() else: request = self.factory.request header = line br = string.find(header,': ') if br == -1: log.msg( 'ignoring malformed SCGI header: %s' % header ) else: headerName = string.lower(header[:br]) headerText = header[br+2:] if headerName == 'location': request.setResponseCode(http.FOUND) if headerName == 'status': try: statusNum = int(headerText[:3]) #"XXX " sometimes happens. except: log.msg( "malformed status header" ) else: request.setResponseCode(statusNum) elif headerName == "set-cookie": # HTTPRequest.addCookie() puts the cookie together given # the key, value, etc. Here we just want to pass it on # as we received it. request.cookies.append(headerText) else: request.setHeader(headerName,headerText) def rawDataReceived(self, output): self.factory.request.write(output) def connectionLost(self, reason): request = self.factory.request if self.handling_headers: log.msg("Premature end of headers in %s" % (request.uri,)) request.write( error.ErrorPage(http.INTERNAL_SERVER_ERROR, "CGI Script Error", "Premature end of script headers.").render(request)) request.unregisterProducer() request.finish() class SCGIClientFactory(protocol.ReconnectingClientFactory): # XXX Currently this class will keep trying to connect indefinitely. # But it does use exponential backoff. protocol = SCGIClient def __init__(self, env, request): self.env = env self.request = request self.request.registerProducer(self, 1) def resumeProducing(self): pass def pauseProducing(self): pass def stopProducing(self): self.stopTrying() def buildProtocol(self, addr): self.request.unregisterProducer() return protocol.ReconnectingClientFactory.buildProtocol(self, addr) def clientConnectionLost(self, connector, reason): pass
from twisted.internet import reactor from twisted.web import server from scgi import twscgi scgi = twscgi.SCGIResource("localhost", 1984) site = server.Site(scgi) reactor.listenTCP(8000, site) reactor.run()