root / trunk / twisted / web2 / channel / cgi.py

Revision 15709, 5.0 kB (checked in by foom, 3 years ago)

Clean up Content-Length handling. Instead of passing through the
(possibly incorrect) Content-Length from the client to the request, use the
value properly computed by the RFC rules. Thanks to robertc for pointing out
this error.

Also remove places where code was redundantly setting Content-Length on
the response, as it gets set automatically from the output stream's length.

Line 
1 import warnings
2 import os
3 import urllib
4 from zope.interface import implements
5
6 from twisted.internet import protocol, address
7 from twisted.internet import reactor, interfaces
8 from twisted.web2 import http, http_headers, server, responsecode
9
10 class BaseCGIChannelRequest(protocol.Protocol):
11     implements(interfaces.IHalfCloseableProtocol)
12    
13     finished = False
14     requestFactory = http.Request
15     request = None
16    
17     def makeRequest(self, vars):
18         headers = http_headers.Headers()
19         http_vers = http.parseVersion(vars['SERVER_PROTOCOL'])
20         if http_vers[0] != 'http' or http_vers[1] > 1:
21             _abortWithError(responsecode.INTERNAL_SERVER_ERROR, "Twisted.web CGITransport: Unknown HTTP version: " % vars['SERVER_PROTOCOL'])
22
23         secure = vars.get("HTTPS") in ("1", "on") # apache extension?
24         port = vars.get('SERVER_PORT') or 80
25         server_host = vars.get('SERVER_NAME') or vars.get('SERVER_ADDR') or 'localhost'
26        
27         self.hostinfo = address.IPv4Address('TCP', server_host, port), bool(secure)
28         self.remoteinfo = address.IPv4Address(
29             'TCP', vars.get('REMOTE_ADDR', ''), vars.get('REMOTE_PORT', 0))
30        
31         uri = vars.get('REQUEST_URI') # apache extension?
32         if not uri:
33             qstr = vars.get('QUERY_STRING', '')
34             if qstr:
35                 qstr = "?"+urllib.quote(qstr, safe="")
36             uri = urllib.quote(vars['SCRIPT_NAME'])+urllib.quote(vars.get('PATH_INFO'''))+qstr
37            
38         for name,val in vars.iteritems():
39             if name.startswith('HTTP_'):
40                 name = name[5:].replace('_', '-')
41             elif name == 'CONTENT_TYPE':
42                 name = 'content-type'
43             else:
44                 continue
45             headers.setRawHeaders(name, (val,))
46            
47         self._dataRemaining = int(vars.get('CONTENT_LENGTH', '0'))
48         self.request = self.requestFactory(self, vars['REQUEST_METHOD'], uri, http_vers[1:3], self._dataRemaining, headers, prepathuri=vars['SCRIPT_NAME'])
49        
50     def writeIntermediateResponse(self, code, headers=None):
51         """Ignore, CGI doesn't support."""
52         pass
53    
54     def write(self, data):
55         self.transport.write(data)
56    
57     def finish(self):
58         if self.finished:
59             warnings.warn("Warning! request.finish called twice.", stacklevel=2)
60             return
61         self.finished = True
62         self.transport.loseConnection()
63
64     def getHostInfo(self):
65         return self.hostinfo
66
67     def getRemoteHost(self):
68         return self.remoteinfo
69        
70     def abortConnection(self, closeWrite=True):
71         self.transport.loseConnection()
72    
73     def registerProducer(self, producer, streaming):
74         self.transport.registerProducer(producer, streaming)
75    
76     def unregisterProducer(self):
77         self.transport.unregisterProducer()
78
79     def writeConnectionLost(self):
80         self.loseConnection()
81        
82     def readConnectionLost(self):
83         if self._dataRemaining > 0:
84             # content-length was wrong, abort
85             self.loseConnection()
86    
87 class CGIChannelRequest(BaseCGIChannelRequest):
88     cgi_vers = (1, 0)
89    
90     def __init__(self, requestFactory, vars):
91         self.requestFactory=requestFactory
92         cgi_vers = http.parseVersion(vars['GATEWAY_INTERFACE'])
93         if cgi_vers[0] != 'cgi' or cgi_vers[1] != 1:
94             _abortWithError(responsecode.INTERNAL_SERVER_ERROR, "Twisted.web CGITransport: Unknown CGI version %s" % vars['GATEWAY_INTERFACE'])
95         self.makeRequest(vars)
96        
97     def writeHeaders(self, code, headers):
98         l = []
99         code_message = responsecode.RESPONSES.get(code, "Unknown Status")
100        
101         l.append("Status: %s %s\n" % (code, code_message))
102         if headers is not None:
103             for name, valuelist in headers.getAllRawHeaders():
104                 for value in valuelist:
105                     l.append("%s: %s\n" % (name, value))
106         l.append('\n')
107         self.transport.writeSequence(l)
108    
109     def dataReceived(self, data):
110         if self._dataRemaining <= 0:
111             return
112        
113         if self._dataRemaining < len(data):
114             data = data[:self._dataRemaining]
115         self._dataRemaining -= len(data)
116         self.request.handleContentChunk(data)
117         if self._dataRemaining == 0:
118             self.request.handleContentComplete()
119
120     def connectionMade(self):
121         self.request.process()
122         if self._dataRemaining == 0:
123             self.request.handleContentComplete()
124        
125     def connectionLost(self, reason):
126         if reactor.running:
127             reactor.stop()
128
129 def startCGI(site):
130     """Call this as the last thing in your CGI python script in order to
131     hook up your site object with the incoming request.
132
133     E.g.:
134     >>> from twisted.web2 import channel, server
135     >>> if __name__ == '__main__':
136     ...     channel.startCGI(server.Site(myToplevelResource))
137     
138     """
139     from twisted.internet.stdio import StandardIO
140     StandardIO(CGIChannelRequest(site, os.environ))
141     reactor.run()
142
143 __all__ = ['startCGI']
Note: See TracBrowser for help on using the browser.