0001# By Allan Saddi
0002import select
0003import struct
0004import socket
0005import errno
0006
0007__all__ = ['FCGIApp']
0008
0009# Constants from the spec.
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    # Set non-zero to write debug output to a file.
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: # EOF
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    # Construct FCGI_GET_VALUES record
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        #sock = self._getConnection()
0308        #print _fcgiGetValues(sock, ['FCGI_MAX_CONNS', 'FCGI_MAX_REQS', 'FCGI_MPXS_CONNS'])
0309        #sock.close()
0310
0311    def __call__(self, environ, start_response):
0312        #print environ
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        #print params
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        # Parse response headers
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        #print headers
0384        #print len(result)
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()