1
"""SCGI client resource and protocols.
5
# * Handle scgi server death, half way through a resonse.
8
from zope.interface import implements
9
from twisted.internet import defer, protocol, reactor
10
from twisted.protocols import basic
11
from twisted.web2 import http, iweb, resource, responsecode, stream, twcgi
14
class SCGIClientResource(resource.LeafResource):
15
"""A resource that connects to an SCGI server and relays the server's
16
response to the browser.
18
This resource connects to a SCGI server on a known host ('localhost', by
19
default) and port. It has no responsibility for starting the SCGI server.
21
If the server is not running when a client connects then a BAD_GATEWAY
22
response will be returned immediately.
25
def __init__(self, port, host='localhost'):
26
"""Initialise a SCGI client resource
28
resource.LeafResource.__init__(self)
32
def renderHTTP(self, request):
33
return doSCGI(request, self.host, self.port)
35
def doSCGI(request, host, port):
36
if request.stream.length is None:
37
return http.Response(responsecode.LENGTH_REQUIRED)
38
factory = SCGIClientProtocolFactory(request)
39
reactor.connectTCP(host, port, factory)
40
return factory.deferred
42
class SCGIClientProtocol(basic.LineReceiver):
43
"""Protocol for talking to a SCGI server.
46
def __init__(self, request, deferred):
47
self.request = request
48
self.deferred = deferred
49
self.stream = stream.ProducerStream()
50
self.response = http.Response(stream=self.stream)
52
def connectionMade(self):
53
# Ooh, look someone did all the hard work for me :).
54
env = twcgi.createCGIEnvironment(self.request)
55
# Send the headers. The Content-Length header should always be sent
56
# first and must be 0 if not present.
57
# The whole lot is sent as one big netstring with each name and value
58
# separated by a '\0'.
59
contentLength = str(env.pop('CONTENT_LENGTH', 0))
62
scgiHeaders.append('%s\x00%s\x00'%('CONTENT_LENGTH', str(contentLength)))
63
scgiHeaders.append('SCGI\x001\x00')
64
for name, value in env.iteritems():
65
if name in ('CONTENT_LENGTH', 'SCGI'):
67
scgiHeaders.append('%s\x00%s\x00'%(name,value))
68
scgiHeaders = ''.join(scgiHeaders)
69
self.transport.write('%d:%s,' % (len(scgiHeaders), scgiHeaders))
70
stream.StreamProducer(self.request.stream).beginProducing(self.transport)
72
def lineReceived(self, line):
73
# Look for end of headers
75
# Switch into raw mode to recieve data and callback the deferred
76
# with the response instance. The data will be streamed as it
77
# arrives. Callback the deferred and set self.response to None,
78
# because there are no promises that the response will not be
79
# mutated by a resource higher in the tree, such as
80
# log.LogWrapperResource
82
self.deferred.callback(self.response)
86
# Split the header into name and value. The 'Status' header is handled
87
# specially; all other headers are simply passed onto the response I'm
89
name, value = line.split(':',1)
91
if name.lower() == 'status':
92
value = value.split(None,1)[0]
93
self.response.code = int(value)
95
self.response.headers.addRawHeader(name, value)
97
def rawDataReceived(self, data):
98
self.stream.write(data)
100
def connectionLost(self, reason):
101
# The connection is closed and all data has been streamed via the
102
# response. Tell the response stream it's over.
106
class SCGIClientProtocolFactory(protocol.ClientFactory):
107
"""SCGI client protocol factory.
109
I am created by a SCGIClientResource to connect to an SCGI server. When I
110
connect I create a SCGIClientProtocol instance to do all the talking with
113
The ``deferred`` attribute is passed on to the protocol and is fired with
114
the HTTP response from the server once it has been recieved.
116
protocol = SCGIClientProtocol
117
noisy = False # Make Factory shut up
119
def __init__(self, request):
120
self.request = request
121
self.deferred = defer.Deferred()
123
def buildProtocol(self, addr):
124
return self.protocol(self.request, self.deferred)
126
def clientConnectionFailed(self, connector, reason):
127
self.sendFailureResponse(reason)
129
def sendFailureResponse(self, reason):
130
response = http.Response(code=responsecode.BAD_GATEWAY, stream=str(reason.value))
131
self.deferred.callback(response)
133
__all__ = ['SCGIClientResource']