0001
0002import select
0003import struct
0004import socket
0005import errno
0006
0007__all__ = ['FCGIApp']
0008
0009
0010FCGI_LISTENSOCK_FILENO = 0
0011
0012FCGI_HEADER_LEN = 8
0013
0014FCGI_VERSION_1 = 1
0015
0016FCGI_BEGIN_REQUEST = 1
0017FCGI_ABORT_REQUEST = 2
0018FCGI_END_REQUEST = 3
0019FCGI_PARAMS = 4
0020FCGI_STDIN = 5
0021FCGI_STDOUT = 6
0022FCGI_STDERR = 7
0023FCGI_DATA = 8
0024FCGI_GET_VALUES = 9
0025FCGI_GET_VALUES_RESULT = 10
0026FCGI_UNKNOWN_TYPE = 11
0027FCGI_MAXTYPE = FCGI_UNKNOWN_TYPE
0028
0029FCGI_NULL_REQUEST_ID = 0
0030
0031FCGI_KEEP_CONN = 1
0032
0033FCGI_RESPONDER = 1
0034FCGI_AUTHORIZER = 2
0035FCGI_FILTER = 3
0036
0037FCGI_REQUEST_COMPLETE = 0
0038FCGI_CANT_MPX_CONN = 1
0039FCGI_OVERLOADED = 2
0040FCGI_UNKNOWN_ROLE = 3
0041
0042FCGI_MAX_CONNS = 'FCGI_MAX_CONNS'
0043FCGI_MAX_REQS = 'FCGI_MAX_REQS'
0044FCGI_MPXS_CONNS = 'FCGI_MPXS_CONNS'
0045
0046FCGI_Header = '!BBHHBx'
0047FCGI_BeginRequestBody = '!HB5x'
0048FCGI_EndRequestBody = '!LB3x'
0049FCGI_UnknownTypeBody = '!B7x'
0050
0051FCGI_BeginRequestBody_LEN = struct.calcsize(FCGI_BeginRequestBody)
0052FCGI_EndRequestBody_LEN = struct.calcsize(FCGI_EndRequestBody)
0053FCGI_UnknownTypeBody_LEN = struct.calcsize(FCGI_UnknownTypeBody)
0054
0055if __debug__:
0056 import time
0057
0058
0059 DEBUG = 0
0060 DEBUGLOG = '/tmp/fcgi.log'
0061
0062 def _debug(level, msg):
0063 if DEBUG < level:
0064 return
0065
0066 try:
0067 f = open(DEBUGLOG, 'a')
0068 f.write('%sfcgi: %s\n' % (time.ctime()[4:-4], msg))
0069 f.close()
0070 except:
0071 pass
0072
0073def decode_pair(s, pos=0):
0074 """
0075 Decodes a name/value pair.
0076
0077 The number of bytes decoded as well as the name/value pair
0078 are returned.
0079 """
0080 nameLength = ord(s[pos])
0081 if nameLength & 128:
0082 nameLength = struct.unpack('!L', s[pos:pos+4])[0] & 0x7fffffff
0083 pos += 4
0084 else:
0085 pos += 1
0086
0087 valueLength = ord(s[pos])
0088 if valueLength & 128:
0089 valueLength = struct.unpack('!L', s[pos:pos+4])[0] & 0x7fffffff
0090 pos += 4
0091 else:
0092 pos += 1
0093
0094 name = s[pos:pos+nameLength]
0095 pos += nameLength
0096 value = s[pos:pos+valueLength]
0097 pos += valueLength
0098
0099 return (pos, (name, value))
0100
0101def encode_pair(name, value):
0102 """
0103 Encodes a name/value pair.
0104
0105 The encoded string is returned.
0106 """
0107 nameLength = len(name)
0108 if nameLength < 128:
0109 s = chr(nameLength)
0110 else:
0111 s = struct.pack('!L', nameLength | 0x80000000L)
0112
0113 valueLength = len(value)
0114 if valueLength < 128:
0115 s += chr(valueLength)
0116 else:
0117 s += struct.pack('!L', valueLength | 0x80000000L)
0118
0119 return s + name + value
0120
0121class Record(object):
0122 """
0123 A FastCGI Record.
0124
0125 Used for encoding/decoding records.
0126 """
0127 def __init__(self, type=FCGI_UNKNOWN_TYPE, requestId=FCGI_NULL_REQUEST_ID):
0128 self.version = FCGI_VERSION_1
0129 self.type = type
0130 self.requestId = requestId
0131 self.contentLength = 0
0132 self.paddingLength = 0
0133 self.contentData = ''
0134
0135 def _recvall(sock, length):
0136 """
0137 Attempts to receive length bytes from a socket, blocking if necessary.
0138 (Socket may be blocking or non-blocking.)
0139 """
0140 dataList = []
0141 recvLen = 0
0142 while length:
0143 try:
0144 data = sock.recv(length)
0145 except socket.error, e:
0146 if e[0] == errno.EAGAIN:
0147 select.select([sock], [], [])
0148 continue
0149 else:
0150 raise
0151 if not data:
0152 break
0153 dataList.append(data)
0154 dataLen = len(data)
0155 recvLen += dataLen
0156 length -= dataLen
0157 return ''.join(dataList), recvLen
0158 _recvall = staticmethod(_recvall)
0159
0160 def read(self, sock):
0161 """Read and decode a Record from a socket."""
0162 try:
0163 header, length = self._recvall(sock, FCGI_HEADER_LEN)
0164 except:
0165 raise EOFError
0166
0167 if length < FCGI_HEADER_LEN:
0168 raise EOFError
0169
0170 self.version, self.type, self.requestId, self.contentLength, self.paddingLength = struct.unpack(FCGI_Header, header)
0172
0173 if __debug__: _debug(9, 'read: fd = %d, type = %d, requestId = %d, '
0174 'contentLength = %d' %
0175 (sock.fileno(), self.type, self.requestId,
0176 self.contentLength))
0177
0178 if self.contentLength:
0179 try:
0180 self.contentData, length = self._recvall(sock,
0181 self.contentLength)
0182 except:
0183 raise EOFError
0184
0185 if length < self.contentLength:
0186 raise EOFError
0187
0188 if self.paddingLength:
0189 try:
0190 self._recvall(sock, self.paddingLength)
0191 except:
0192 raise EOFError
0193
0194 def _sendall(sock, data):
0195 """
0196 Writes data to a socket and does not return until all the data is sent.
0197 """
0198 length = len(data)
0199 while length:
0200 try:
0201 sent = sock.send(data)
0202 except socket.error, e:
0203 if e[0] == errno.EAGAIN:
0204 select.select([], [sock], [])
0205 continue
0206 else:
0207 raise
0208 data = data[sent:]
0209 length -= sent
0210 _sendall = staticmethod(_sendall)
0211
0212 def write(self, sock):
0213 """Encode and write a Record to a socket."""
0214 self.paddingLength = -self.contentLength & 7
0215
0216 if __debug__: _debug(9, 'write: fd = %d, type = %d, requestId = %d, '
0217 'contentLength = %d' %
0218 (sock.fileno(), self.type, self.requestId,
0219 self.contentLength))
0220
0221 header = struct.pack(FCGI_Header, self.version, self.type,
0222 self.requestId, self.contentLength,
0223 self.paddingLength)
0224 self._sendall(sock, header)
0225 if self.contentLength:
0226 self._sendall(sock, self.contentData)
0227 if self.paddingLength:
0228 self._sendall(sock, '\x00'*self.paddingLength)
0229
0230def _fcgiGetValues(sock, vars):
0231
0232 outrec = Record(FCGI_GET_VALUES)
0233 data = []
0234 for name in vars:
0235 data.append(encode_pair(name, ''))
0236 data = ''.join(data)
0237 outrec.contentData = data
0238 outrec.contentLength = len(data)
0239 outrec.write(sock)
0240
0241 inrec = Record()
0242 inrec.read(sock)
0243 result = {}
0244 if inrec.type == FCGI_GET_VALUES_RESULT:
0245 pos = 0
0246 while pos < inrec.contentLength:
0247 pos, (name, value) = decode_pair(inrec.contentData, pos)
0248 result[name] = value
0249 return result
0250
0251def _fcgiParams(sock, requestId, params):
0252 rec = Record(FCGI_PARAMS, requestId)
0253 data = []
0254 for name,value in params.items():
0255 data.append(encode_pair(name, value))
0256 data = ''.join(data)
0257 rec.contentData = data
0258 rec.contentLength = len(data)
0259 rec.write(sock)
0260
0261_environPrefixes = ['SERVER_', 'HTTP_', 'REQUEST_', 'REMOTE_', 'PATH_',
0262 'CONTENT_']
0263_environCopies = ['SCRIPT_NAME', 'QUERY_STRING', 'AUTH_TYPE']
0264_environRenames = {}
0265
0266def _filterEnviron(environ):
0267 result = {}
0268 for n in environ.keys():
0269 for p in _environPrefixes:
0270 if n.startswith(p):
0271 result[n] = environ[n]
0272 for c in _environCopies:
0273 if n == c:
0274 result[n] = environ[n]
0275 for oldname,newname in _environRenames.items():
0276 if n == oldname:
0277 result[newname] = environ[n]
0278 return result
0279
0280def _lightFilterEnviron(environ):
0281 result = {}
0282 for n in environ.keys():
0283 if n.upper() == n:
0284 result[n] = environ[n]
0285 return result
0286
0287class FCGIApp(object):
0288 def _getConnection(self):
0289 if self.connect is not None:
0290 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
0291 sock.connect(self.connect)
0292 return sock
0293
0294 def __init__(self, command=None, connect=None, host=None, port=None, filterEnviron=True):
0295 if host is not None:
0296 assert port is not None
0297 connect=(host, port)
0298
0299 assert (command is not None and connect is None) or (command is None and connect is not None)
0301
0302 self.command = command
0303 self.connect = connect
0304
0305 self.filterEnviron = filterEnviron
0306
0307
0308
0309
0310
0311 def __call__(self, environ, start_response):
0312
0313
0314 sock = self._getConnection()
0315
0316 rec = Record(FCGI_BEGIN_REQUEST, 1)
0317 rec.contentData = struct.pack(FCGI_BeginRequestBody, FCGI_RESPONDER, 0)
0318 rec.contentLength = FCGI_BeginRequestBody_LEN
0319 rec.write(sock)
0320
0321 if self.filterEnviron:
0322 params = _filterEnviron(environ)
0323 else:
0324 params = _lightFilterEnviron(environ)
0325
0326 _fcgiParams(sock, 1, params)
0327 _fcgiParams(sock, 1, {})
0328
0329 while True:
0330 s = environ['wsgi.input'].read(4096)
0331 rec = Record(FCGI_STDIN, 1)
0332 rec.contentData = s
0333 rec.contentLength = len(s)
0334 rec.write(sock)
0335
0336 if not s: break
0337
0338 rec = Record(FCGI_DATA, 1)
0339 rec.write(sock)
0340
0341 result = []
0342 while True:
0343 inrec = Record()
0344 inrec.read(sock)
0345 if inrec.type == FCGI_STDOUT:
0346 if inrec.contentData:
0347 result.append(inrec.contentData)
0348 elif inrec.type == FCGI_STDERR:
0349 environ['wsgi.errors'].write(inrec.contentData)
0350 elif inrec.type == FCGI_END_REQUEST:
0351 break
0352
0353 sock.close()
0354
0355 result = ''.join(result)
0356
0357
0358 status = '200 OK'
0359 headers = []
0360 pos = 0
0361 while True:
0362 eolpos = result.find('\n', pos)
0363 if eolpos < 0: break
0364 line = result[pos:eolpos-1]
0365 pos = eolpos + 1
0366
0367 line = line.strip()
0368 if not line: break
0369
0370 header, value = line.split(':')
0371 header = header.strip().lower()
0372 value = value.strip()
0373
0374 if header == 'status':
0375 status = value
0376 if status.find(' ') < 0:
0377 status += ' FCGIApp'
0378 else:
0379 headers.append((header, value))
0380
0381 result = result[pos:]
0382
0383
0384
0385
0386 start_response(status, headers)
0387 return [result]
0388
0389if __name__ == '__main__':
0390 from flup.server.ajp import WSGIServer
0391 app = FCGIApp(connect=('localhost', 4242))
0392 WSGIServer(app).run()