[Twisted-Python] SCGIChannel: SCGI protocol support
Neil Blakey-Milner
nbm at mithrandr.moria.org
Fri Dec 20 16:40:43 EST 2002
Hi all,
SCGI (http://www.mems-exchange.org/software/scgi/) is a replacement for
the CGI protocol, in a similar manner to FCGI. Through mod_scgi on
apache, you can delegate sections of a web site served by apache to be
handled via SCGI:
<Location "/jobsearch">
SCGIServer 127.0.0.1 4000
SCGIHandler On
</Location>
SCGIChannel serves as a replacement to HTTPChannel, performing the
request with the information given over SCGI, and returning the reply.
A couple of changes are necessary to make use of inheritance of
HTTPChannel and server.Site to work as expected with its protocol.
These are in scgi-changes.patch.
I work around the lack of these changes in the scgichannel.py attached.
Use the above apache directives and this file to see what it does.
Opinions requested.
Neil
--
Neil Blakey-Milner
nbm at mithrandr.moria.org
-------------- next part --------------
Index: protocols/http.py
===================================================================
RCS file: /cvs/Twisted/twisted/protocols/http.py,v
retrieving revision 1.64
diff -u -r1.64 http.py
--- protocols/http.py 3 Dec 2002 06:05:07 -0000 1.64
+++ protocols/http.py 20 Dec 2002 21:09:16 -0000
@@ -840,9 +840,9 @@
self.transport.loseConnection()
return
command, request, version = parts
- self.__command = command
- self.__path = request
- self.__version = version
+ self._command = command
+ self._path = request
+ self._version = version
if version == 'HTTP/0.9':
self.allHeadersReceived()
self.allContentReceived()
@@ -873,15 +873,15 @@
self.requests[-1].received_headers[header] = data
def allContentReceived(self):
- command = self.__command
- path = self.__path
- version = self.__version
+ command = self._command
+ path = self._path
+ version = self._version
# reset ALL state variables, so we don't interfere with next request
self.length = 0
- self.__header = ''
+ self._header = ''
self.__first_line = 1
- del self.__command, self.__path, self.__version
+ del self._command, self._path, self._version
req = self.requests[-1]
req.requestReceived(command, path, version)
@@ -899,7 +899,7 @@
def allHeadersReceived(self):
req = self.requests[-1]
req.parseCookies()
- self.persistent = self.checkPersistence(req, self.__version)
+ self.persistent = self.checkPersistence(req, self._version)
req.gotLength(self.length)
def checkPersistence(self, request, version):
Index: web/server.py
===================================================================
RCS file: /cvs/Twisted/twisted/web/server.py,v
retrieving revision 1.84
diff -u -r1.84 server.py
--- web/server.py 3 Dec 2002 17:24:27 -0000 1.84
+++ web/server.py 20 Dec 2002 21:09:18 -0000
@@ -416,7 +416,7 @@
def buildProtocol(self, addr):
"""Generate a channel attached to this site.
"""
- channel = http.HTTPChannel()
+ channel = self.protocol()
channel.requestFactory = Request
channel.site = self
channel.factory = self
-------------- next part --------------
#!/usr/local/bin/python
#
# Copyright (c) 2002 Neil Blakey-Milner
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
# SUCH DAMAGE.
#
from twisted.protocols import http
from twisted.web import server
class SCGITransport:
def __init__(self, realtransport):
self.realtransport = realtransport
self.firstline = 1
def write(self, data):
if self.firstline:
data = data.replace("HTTP/1.1", "Status:")
self.firstline = 0
self.realtransport.write(data)
def writeSequence(self, data):
for entry in data:
self.write(entry)
def setPeer(self, peer):
self.peer = peer
def setHost(self, host):
self.host = host
def getPeer(self):
return self.peer
def getHost(self):
return self.host
def loseConnection(self):
self.realtransport.loseConnection()
class SCGIChannel(http.HTTPChannel):
def __init__(self):
http.HTTPChannel.__init__(self)
self._buffer = ""
self._maxbufferlength = None
self.prefix = ""
def dataReceived(self, data):
self._buffer = self._buffer + data
if not self._maxbufferlength:
idx = self._buffer.index(":")
if not idx:
return
self._maxbufferlength = long(self._buffer[:idx])
if len(self._buffer) - (idx + 2) == self._maxbufferlength:
scgistring = self._buffer[idx + 1:]
items = scgistring.split("\0")
items = items[:-1]
assert len(items) % 2 == 0, "malformed headers"
env = {}
for i in range(0, len(items), 2):
env[items[i]] = items[i+1]
request = self.requestFactory(self, len(self.requests))
self.requests.append(request)
if env.has_key("HTTP_USER_AGENT"):
request.received_headers["User-Agent"] = env["HTTP_USER_AGENT"]
if env.has_key("HTTP_ACCEPT"):
request.received_headers["Accept"] = env["HTTP_ACCEPT"]
if env.has_key("SERVER_NAME"):
request.received_headers["host"] = env["SERVER_NAME"]
if env.has_key("CONTENT_LENGTH"):
request.received_headers["Content-Length"] = env["CONTENT_LENGTH"]
# XXX: working around use of __version, &c. in HTTPChannel
self._HTTPChannel__command = env["REQUEST_METHOD"]
self._HTTPChannel__path = env["REQUEST_URI"][len(self.prefix):]
self._HTTPChannel__version = env["SERVER_PROTOCOL"]
self.transport.setPeer(("INET", env["REMOTE_ADDR"], int(env["REMOTE_PORT"])))
self.transport.setHost(("INET", env["SERVER_NAME"], int(env["SERVER_PORT"])))
self.allHeadersReceived()
self.allContentReceived()
def requestDone(self, request):
self.transport.loseConnection()
def connectionMade(self):
self.transport = SCGITransport(self.transport)
class SCGISite(server.Site):
def __init__(self, resource, prefix = ""):
server.Site.__init__(self, resource)
self.protocol = SCGIChannel
self.prefix = prefix
# XXX: Fix when twisted.web.server.Site starts using self.protocol()
def buildProtocol(self, addr):
"""Generate a channel attached to this site.
"""
channel = self.protocol()
channel.requestFactory = server.Request
channel.site = self
channel.factory = self
channel.prefix = self.prefix
return channel
def main():
from twisted.internet import reactor
from twisted.web import resource, widgets
class IndexDisplay(widgets.Presentation):
template = """<h1>Index</h1>"""
gdgt = widgets.Gadget()
gdgt.putWidget('', IndexDisplay())
site = SCGISite(gdgt, "/jobsearch")
site.protocol = SCGIChannel
reactor.listenTCP(4000, site)
reactor.run()
if __name__ == "__main__":
main()
More information about the Twisted-Python
mailing list