~certify-web-dev/twisted/certify-trunk

« back to all changes in this revision

Viewing changes to twisted/web2/twscgi.py

  • Committer: Bazaar Package Importer
  • Author(s): Matthias Klose
  • Date: 2007-01-17 14:52:35 UTC
  • mfrom: (1.1.5 upstream) (2.1.2 etch)
  • Revision ID: james.westby@ubuntu.com-20070117145235-btmig6qfmqfen0om
Tags: 2.5.0-0ubuntu1
New upstream version, compatible with python2.5.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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']