root / trunk / twisted / web2 / twscgi.py

Revision 17142, 4.9 kB (checked in by dreid, 3 years ago)

Fix twscgi to write to the stream directly instead of to the stream on the response object it has returned.

Author: dreid
Reviewer: Jerub
Fixes #1755

Line 
1 """SCGI client resource and protocols.
2 """
3
4 # TODO:
5 #   * Handle scgi server death, half way through a resonse.
6
7
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
12
13
14 class SCGIClientResource(resource.LeafResource):
15     """A resource that connects to an SCGI server and relays the server's
16     response to the browser.
17     
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.
20     
21     If the server is not running when a client connects then a BAD_GATEWAY
22     response will be returned immediately.
23     """
24    
25     def __init__(self, port, host='localhost'):
26         """Initialise a SCGI client resource
27         """
28         resource.LeafResource.__init__(self)
29         self.host = host
30         self.port = port
31    
32     def renderHTTP(self, request):
33         return doSCGI(request, self.host, self.port)
34
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
41    
42 class SCGIClientProtocol(basic.LineReceiver):
43     """Protocol for talking to a SCGI server.
44     """
45    
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)
51
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))
60         env['SCGI'] = '1'
61         scgiHeaders = []
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'):
66                 continue
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)
71        
72     def lineReceived(self, line):
73         # Look for end of headers
74         if line == '':
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
81             self.setRawMode()
82             self.deferred.callback(self.response)
83             self.response = None
84             return
85
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
88         # building.
89         name, value = line.split(':',1)
90         value = value.strip()
91         if name.lower() == 'status':
92             value = value.split(None,1)[0]
93             self.response.code = int(value)
94         else:
95             self.response.headers.addRawHeader(name, value)
96        
97     def rawDataReceived(self, data):
98         self.stream.write(data)
99        
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.
103         self.stream.finish()
104    
105    
106 class SCGIClientProtocolFactory(protocol.ClientFactory):
107     """SCGI client protocol factory.
108     
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
111     the server.
112     
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.
115     """
116     protocol = SCGIClientProtocol
117     noisy = False # Make Factory shut up
118     
119     def __init__(self, request):
120         self.request = request
121         self.deferred = defer.Deferred()
122        
123     def buildProtocol(self, addr):
124         return self.protocol(self.request, self.deferred)
125        
126     def clientConnectionFailed(self, connector, reason):
127         self.sendFailureResponse(reason)
128        
129     def sendFailureResponse(self, reason):
130         response = http.Response(code=responsecode.BAD_GATEWAY, stream=str(reason.value))
131         self.deferred.callback(response)
132        
133 __all__ = ['SCGIClientResource']
Note: See TracBrowser for help on using the browser.