aboutsummaryrefslogtreecommitdiff
path: root/cgi/fcgi.py
diff options
context:
space:
mode:
Diffstat (limited to 'cgi/fcgi.py')
-rw-r--r--cgi/fcgi.py137
1 files changed, 83 insertions, 54 deletions
diff --git a/cgi/fcgi.py b/cgi/fcgi.py
index e59f8c8..08af980 100644
--- a/cgi/fcgi.py
+++ b/cgi/fcgi.py
@@ -137,11 +137,13 @@ if __debug__:
except:
pass
+
class InputStream(object):
"""
File-like object representing FastCGI input streams (FCGI_STDIN and
FCGI_DATA). Supports the minimum methods required by WSGI spec.
"""
+
def __init__(self, conn):
self._conn = conn
@@ -150,10 +152,10 @@ class InputStream(object):
self._buf = ''
self._bufList = []
- self._pos = 0 # Current read position.
- self._avail = 0 # Number of bytes currently available.
+ self._pos = 0 # Current read position.
+ self._avail = 0 # Number of bytes currently available.
- self._eof = False # True when server has sent EOF notification.
+ self._eof = False # True when server has sent EOF notification.
def _shrinkBuffer(self):
"""Gets rid of already read data (since we can't rewind)."""
@@ -253,12 +255,14 @@ class InputStream(object):
self._bufList.append(data)
self._avail += len(data)
+
class MultiplexedInputStream(InputStream):
"""
A version of InputStream meant to be used with MultiplexedConnections.
Assumes the MultiplexedConnection (the producer) and the Request
(the consumer) are running in different threads.
"""
+
def __init__(self, conn):
super(MultiplexedInputStream, self).__init__(conn)
@@ -295,18 +299,20 @@ class MultiplexedInputStream(InputStream):
finally:
self._lock.release()
+
class OutputStream(object):
"""
FastCGI output stream (FCGI_STDOUT/FCGI_STDERR). By default, calls to
write() or writelines() immediately result in Records being sent back
to the server. Buffering should be done in a higher level!
"""
+
def __init__(self, conn, req, type, buffered=False):
self._conn = conn
self._req = req
self._type = type
self._buffered = buffered
- self._bufList = [] # Used if buffered is True
+ self._bufList = [] # Used if buffered is True
self.dataWritten = False
self.closed = False
@@ -358,11 +364,13 @@ class OutputStream(object):
self._conn.writeRecord(rec)
self.closed = True
+
class TeeOutputStream(object):
"""
Simple wrapper around two or more output file-like objects that copies
written data to all streams.
"""
+
def __init__(self, streamList):
self._streamList = streamList
@@ -378,10 +386,12 @@ class TeeOutputStream(object):
for f in self._streamList:
f.flush()
+
class StdoutWrapper(object):
"""
Wrapper for sys.stdout so we know if data has actually been written.
"""
+
def __init__(self, stdout):
self._file = stdout
self.dataWritten = False
@@ -398,6 +408,7 @@ class StdoutWrapper(object):
def __getattr__(self, name):
return getattr(self._file, name)
+
def decode_pair(s, pos=0):
"""
Decodes a name/value pair.
@@ -426,6 +437,7 @@ def decode_pair(s, pos=0):
return (pos, (name, value))
+
def encode_pair(name, value):
"""
Encodes a name/value pair.
@@ -445,13 +457,15 @@ def encode_pair(name, value):
s += struct.pack('!L', valueLength | 0x80000000L)
return s + name + value
-
+
+
class Record(object):
"""
A FastCGI Record.
Used for encoding/decoding records.
"""
+
def __init__(self, type=FCGI_UNKNOWN_TYPE, requestId=FCGI_NULL_REQUEST_ID):
self.version = FCGI_VERSION_1
self.type = type
@@ -476,7 +490,7 @@ class Record(object):
continue
else:
raise
- if not data: # EOF
+ if not data: # EOF
break
dataList.append(data)
dataLen = len(data)
@@ -494,15 +508,16 @@ class Record(object):
if length < FCGI_HEADER_LEN:
raise EOFError
-
+
self.version, self.type, self.requestId, self.contentLength, \
- self.paddingLength = struct.unpack(FCGI_Header, header)
+ self.paddingLength = struct.unpack(FCGI_Header, header)
+
+ if __debug__:
+ _debug(9, 'read: fd = %d, type = %d, requestId = %d, '
+ 'contentLength = %d' %
+ (sock.fileno(), self.type, self.requestId,
+ self.contentLength))
- if __debug__: _debug(9, 'read: fd = %d, type = %d, requestId = %d, '
- 'contentLength = %d' %
- (sock.fileno(), self.type, self.requestId,
- self.contentLength))
-
if self.contentLength:
try:
self.contentData, length = self._recvall(sock,
@@ -541,10 +556,11 @@ class Record(object):
"""Encode and write a Record to a socket."""
self.paddingLength = -self.contentLength & 7
- if __debug__: _debug(9, 'write: fd = %d, type = %d, requestId = %d, '
- 'contentLength = %d' %
- (sock.fileno(), self.type, self.requestId,
- self.contentLength))
+ if __debug__:
+ _debug(9, 'write: fd = %d, type = %d, requestId = %d, '
+ 'contentLength = %d' %
+ (sock.fileno(), self.type, self.requestId,
+ self.contentLength))
header = struct.pack(FCGI_Header, self.version, self.type,
self.requestId, self.contentLength,
@@ -554,7 +570,8 @@ class Record(object):
self._sendall(sock, self.contentData)
if self.paddingLength:
self._sendall(sock, '\x00'*self.paddingLength)
-
+
+
class Request(object):
"""
Represents a single FastCGI request.
@@ -564,6 +581,7 @@ class Request(object):
be called by your handler. However, server, params, stdin, stdout,
stderr, and data are free for your handler's use.
"""
+
def __init__(self, conn, inputStreamClass):
self._conn = conn
@@ -586,35 +604,38 @@ class Request(object):
protocolStatus, appStatus = FCGI_REQUEST_COMPLETE, 0
- if __debug__: _debug(1, 'protocolStatus = %d, appStatus = %d' %
- (protocolStatus, appStatus))
+ if __debug__:
+ _debug(1, 'protocolStatus = %d, appStatus = %d' %
+ (protocolStatus, appStatus))
self._flush()
self._end(appStatus, protocolStatus)
def _end(self, appStatus=0L, protocolStatus=FCGI_REQUEST_COMPLETE):
self._conn.end_request(self, appStatus, protocolStatus)
-
+
def _flush(self):
self.stdout.close()
self.stderr.close()
+
class CGIRequest(Request):
"""A normal CGI request disguised as a FastCGI request."""
+
def __init__(self, server):
# These are normally filled in by Connection.
self.requestId = 1
self.role = FCGI_RESPONDER
self.flags = 0
self.aborted = False
-
+
self.server = server
self.params = dict(os.environ)
self.stdin = sys.stdin
- self.stdout = StdoutWrapper(sys.stdout) # Oh, the humanity!
+ self.stdout = StdoutWrapper(sys.stdout) # Oh, the humanity!
self.stderr = sys.stderr
self.data = StringIO.StringIO()
-
+
def _end(self, appStatus=0L, protocolStatus=FCGI_REQUEST_COMPLETE):
sys.exit(appStatus)
@@ -622,6 +643,7 @@ class CGIRequest(Request):
# Not buffered, do nothing.
pass
+
class Connection(object):
"""
A Connection with the web server.
@@ -655,7 +677,7 @@ class Connection(object):
except:
pass
self._sock.close()
-
+
def run(self):
"""Begin processing data from the socket."""
self._keepGoing = True
@@ -665,7 +687,7 @@ class Connection(object):
except EOFError:
break
except (select.error, socket.error), e:
- if e[0] == errno.EBADF: # Socket was closed by Request.
+ if e[0] == errno.EBADF: # Socket was closed by Request.
break
raise
@@ -684,7 +706,8 @@ class Connection(object):
# Sigh. ValueError gets thrown sometimes when passing select
# a closed socket.
raise EOFError
- if r: break
+ if r:
+ break
if not self._keepGoing:
return
rec = Record()
@@ -733,7 +756,8 @@ class Connection(object):
if remove:
del self._requests[req.requestId]
- if __debug__: _debug(2, 'end_request: flags = %d' % req.flags)
+ if __debug__:
+ _debug(2, 'end_request: flags = %d' % req.flags)
if not (req.flags & FCGI_KEEP_CONN) and not self._requests:
self._cleanupSocket()
@@ -816,7 +840,8 @@ class Connection(object):
outrec.contentData = struct.pack(FCGI_UnknownTypeBody, inrec.type)
outrec.contentLength = FCGI_UnknownTypeBody_LEN
self.writeRecord(rec)
-
+
+
class MultiplexedConnection(Connection):
"""
A version of Connection capable of handling multiple requests
@@ -843,7 +868,7 @@ class MultiplexedConnection(Connection):
self._lock.release()
super(MultiplexedConnection, self)._cleanupSocket()
-
+
def writeRecord(self, rec):
# Must use locking to prevent intermingling of Records from different
# threads.
@@ -902,7 +927,8 @@ class MultiplexedConnection(Connection):
super(MultiplexedConnection, self)._do_data(inrec)
finally:
self._lock.release()
-
+
+
class Server(object):
"""
The FastCGI server.
@@ -959,18 +985,18 @@ class Server(object):
# from the OS.
maxConns = resource.getrlimit(resource.RLIMIT_NOFILE)[0]
except ImportError:
- maxConns = 100 # Just some made up number.
+ maxConns = 100 # Just some made up number.
maxReqs = maxConns
if multiplexed:
self._connectionClass = MultiplexedConnection
- maxReqs *= 5 # Another made up number.
+ maxReqs *= 5 # Another made up number.
else:
self._connectionClass = Connection
self.capability = {
FCGI_MAX_CONNS: maxConns,
FCGI_MAX_REQS: maxReqs,
FCGI_MPXS_CONNS: multiplexed and 1 or 0
- }
+ }
else:
self._connectionClass = Connection
self.capability = {
@@ -978,18 +1004,18 @@ class Server(object):
FCGI_MAX_CONNS: 1,
FCGI_MAX_REQS: 1,
FCGI_MPXS_CONNS: 0
- }
+ }
self._bindAddress = bindAddress
self._umask = umask
def _setupSocket(self):
- if self._bindAddress is None: # Run as a normal FastCGI?
+ if self._bindAddress is None: # Run as a normal FastCGI?
isFCGI = True
if isFCGI:
try:
sock = socket.fromfd(FCGI_LISTENSOCK_FILENO, socket.AF_INET,
- socket.SOCK_STREAM)
+ socket.SOCK_STREAM)
sock.getpeername()
except AttributeError:
isFCGI = False
@@ -1042,16 +1068,16 @@ class Server(object):
sock.close()
def _installSignalHandlers(self):
- self._oldSIGs = [(x,signal.getsignal(x)) for x in
+ self._oldSIGs = [(x, signal.getsignal(x)) for x in
(signal.SIGHUP, signal.SIGINT, signal.SIGTERM)]
signal.signal(signal.SIGHUP, self._hupHandler)
signal.signal(signal.SIGINT, self._intHandler)
signal.signal(signal.SIGTERM, self._intHandler)
def _restoreSignalHandlers(self):
- for signum,handler in self._oldSIGs:
+ for signum, handler in self._oldSIGs:
signal.signal(signum, handler)
-
+
def _hupHandler(self, signum, frame):
self._hupReceived = True
self._keepGoing = False
@@ -1094,7 +1120,7 @@ class Server(object):
raise
if web_server_addrs and \
- (len(addr) != 2 or addr[0] not in web_server_addrs):
+ (len(addr) != 2 or addr[0] not in web_server_addrs):
clientSock.close()
continue
@@ -1145,11 +1171,13 @@ class Server(object):
req.stdout.write('Content-Type: text/html\r\n\r\n' +
cgitb.html(sys.exc_info()))
+
class WSGIServer(Server):
"""
FastCGI server that supports the Web Server Gateway Interface. See
<http://www.python.org/peps/pep-0333.html>.
"""
+
def __init__(self, application, environ=None, umask=None,
multithreaded=True, **kw):
"""
@@ -1160,7 +1188,7 @@ class WSGIServer(Server):
Set multithreaded to False if your application is not MT-safe.
"""
if kw.has_key('handler'):
- del kw['handler'] # Doesn't make sense to let this through
+ del kw['handler'] # Doesn't make sense to let this through
super(WSGIServer, self).__init__(**kw)
if environ is None:
@@ -1182,7 +1210,7 @@ class WSGIServer(Server):
environ = req.params
environ.update(self.environ)
- environ['wsgi.version'] = (1,0)
+ environ['wsgi.version'] = (1, 0)
environ['wsgi.input'] = req.stdin
if self._bindAddress is None:
stderr = req.stderr
@@ -1190,7 +1218,7 @@ class WSGIServer(Server):
stderr = TeeOutputStream((sys.stderr, req.stderr))
environ['wsgi.errors'] = stderr
environ['wsgi.multithread'] = not isinstance(req, CGIRequest) and \
- thread_available and self.multithreaded
+ thread_available and self.multithreaded
# Rationale for the following: If started by the web server
# (self._bindAddress is None) in either FastCGI or CGI mode, the
# possibility of being spawned multiple times simultaneously is quite
@@ -1218,7 +1246,7 @@ class WSGIServer(Server):
if not headers_sent:
status, responseHeaders = headers_sent[:] = headers_set
found = False
- for header,value in responseHeaders:
+ for header, value in responseHeaders:
if header.lower() == 'content-length':
found = True
break
@@ -1245,7 +1273,7 @@ class WSGIServer(Server):
# Re-raise if too late
raise exc_info[0], exc_info[1], exc_info[2]
finally:
- exc_info = None # avoid dangling circular ref
+ exc_info = None # avoid dangling circular ref
else:
assert not headers_set, 'Headers already set!'
@@ -1255,7 +1283,7 @@ class WSGIServer(Server):
assert status[3] == ' ', 'Status must have a space after code'
assert type(response_headers) is list, 'Headers must be a list'
if __debug__:
- for name,val in response_headers:
+ for name, val in response_headers:
assert type(name) is str, 'Header names must be strings'
assert type(val) is str, 'Header values must be strings'
@@ -1272,13 +1300,13 @@ class WSGIServer(Server):
if data:
write(data)
if not headers_sent:
- write('') # in case body was empty
+ write('') # in case body was empty
finally:
if hasattr(result, 'close'):
result.close()
except socket.error, e:
if e[0] != errno.EPIPE:
- raise # Don't let EPIPE propagate beyond server
+ raise # Don't let EPIPE propagate beyond server
finally:
if not self.multithreaded:
self._app_lock.release()
@@ -1294,16 +1322,17 @@ class WSGIServer(Server):
# If any of these are missing, it probably signifies a broken
# server...
- for name,default in [('REQUEST_METHOD', 'GET'),
- ('SERVER_NAME', 'localhost'),
- ('SERVER_PORT', '80'),
- ('SERVER_PROTOCOL', 'HTTP/1.0')]:
+ for name, default in [('REQUEST_METHOD', 'GET'),
+ ('SERVER_NAME', 'localhost'),
+ ('SERVER_PORT', '80'),
+ ('SERVER_PROTOCOL', 'HTTP/1.0')]:
if not environ.has_key(name):
environ['wsgi.errors'].write('%s: missing FastCGI param %s '
'required by WSGI!\n' %
(self.__class__.__name__, name))
environ[name] = default
-
+
+
if __name__ == '__main__':
def test_app(environ, start_response):
"""Probably not the most efficient example."""