##################################################
# SPYCE - Python-based HTML Scripting
# Copyright (c) 2002 Rimon Barr.
#
# Refer to spyce.py
# CVS: $Id: fcgi.py 915 2006-07-20 00:00:51Z ellisj $
##################################################
# Taken originally from: http://alldunn.com/python/fcgi.py
# Edited a fair bit. -- RB
__doc__ = 'Python Fast CGI implementation'
import os, sys, string, socket, errno, cgi
from cStringIO import StringIO
import spyceUtil
##################################################
# Constants
#
# Protol constants: record types
FCGI_BEGIN_REQUEST = 1
FCGI_ABORT_REQUEST = 2
FCGI_END_REQUEST = 3
FCGI_PARAMS = 4
FCGI_STDIN = 5
FCGI_STDOUT = 6
FCGI_STDERR = 7
FCGI_DATA = 8
FCGI_GET_VALUES = 9
FCGI_GET_VALUES_RESULT = 10
FCGI_UNKNOWN_TYPE = 11
FCGI_MAXTYPE = FCGI_UNKNOWN_TYPE
# Protocol constants: FCGI_BEGIN_REQUEST flag mask
FCGI_KEEP_CONN = 1
# Protocol constants: FCGI_BEGIN_REQUEST role
FCGI_RESPONDER = 1
FCGI_AUTHORIZER = 2
FCGI_FILTER = 3
# Protocol constants: FCGI_END_REQUEST protocolStatus
FCGI_REQUEST_COMPLETE = 0 # ok
FCGI_CANT_MPX_CONN = 1 # can not multiplex
FCGI_OVERLOADED = 2 # too busy
FCGI_UNKNOWN_ROLE = 3 # role unknown
# Protocol constants: management record types
FCGI_NULL_REQUEST_ID = 0
# Protocol setting: maximum number of requests
FCGI_MAX_REQS = 1
FCGI_MAX_CONNS = 1
# Protocol setting: can multiplex?
FCGI_MPXS_CONNS = 0
# Protocol setting: FastCGI protocol version
FCGI_VERSION_1 = 1
##################################################
# Protocol
#
class record:
def __init__(self):
self.version = FCGI_VERSION_1
self.recType = FCGI_UNKNOWN_TYPE
self.reqId = FCGI_NULL_REQUEST_ID
self.content = ""
def readRecord(self, sock):
# read content
hdr = map(ord, self.readExact(sock, 8))
self.version = hdr[0]
self.recType = hdr[1]
self.reqId = (hdr[2]<<8)+hdr[3]
contentLength = (hdr[4]<<8)+hdr[5]
paddingLength = hdr[6]
self.content = self.readExact(sock, contentLength)
self.readExact(sock, paddingLength)
# parse
c = self.content
if self.recType == FCGI_BEGIN_REQUEST:
self.role = (ord(c[0])<<8) + ord(c[1])
self.flags = ord(c[2])
elif self.recType == FCGI_UNKNOWN_TYPE:
self.unknownType = ord(c[0])
elif self.recType == FCGI_GET_VALUES or self.recType == FCGI_PARAMS:
self.values={}
pos=0
while pos < len(c):
name, value, pos = self.decodePair(c, pos)
self.values[name] = value
elif self.recType == FCGI_END_REQUEST:
b = map(ord, c[0:5])
self.appStatus = (b[0]<<24) + (b[1]<<16) + (b[2]<<8) + b[3]
self.protocolStatus = b[4]
def writeRecord(self, sock):
content = self.content
if self.recType == FCGI_BEGIN_REQUEST:
content = chr(self.role>>8) + chr(self.role & 255) + chr(self.flags) + 5*'\000'
elif self.recType == FCGI_UNKNOWN_TYPE:
content = chr(self.unknownType) + 7*'\000'
elif self.recType==FCGI_GET_VALUES or self.recType==FCGI_PARAMS:
content = ""
for i in self.values.keys():
content = content + self.encodePair(i, self.values[i])
elif self.recType==FCGI_END_REQUEST:
v = self.appStatus
content = chr((v>>24)&255) + chr((v>>16)&255) + chr((v>>8)&255) + chr(v&255)
content = content + chr(self.protocolStatus) + 3*'\000'
cLen = len(content)
eLen = (cLen + 7) & (0xFFFF - 7) # align to an 8-byte boundary
padLen = eLen - cLen
hdr = [ self.version, self.recType, self.reqId >> 8,
self.reqId & 255, cLen >> 8, cLen & 255, padLen, 0]
hdr = string.joinfields(map(chr, hdr), '')
sock.send(hdr + content + padLen*'\000')
def readExact(self, sock, amount):
data = ''
while amount and len(data) < amount:
data = data + sock.recv(amount-len(data))
return data
def decodePair(self, s, pos):
nameLen=ord(s[pos]) ; pos=pos+1
if nameLen & 128:
b=map(ord, s[pos:pos+3]) ; pos=pos+3
nameLen=((nameLen&127)<<24) + (b[0]<<16) + (b[1]<<8) + b[2]
valueLen=ord(s[pos]) ; pos=pos+1
if valueLen & 128:
b=map(ord, s[pos:pos+3]) ; pos=pos+3
valueLen=((valueLen&127)<<24) + (b[0]<<16) + (b[1]<<8) + b[2]
name = s[pos:pos+nameLen] ; pos = pos + nameLen
value = s[pos:pos+valueLen] ; pos = pos + valueLen
return name, value, pos
def encodePair(self, name, value):
l=len(name)
if l<128: s=chr(l)
else: s=chr(128|(l>>24)&255)+chr((l>>16)&255)+chr((l>>8)&255)+chr(l&255)
l=len(value)
if l<128: s=s+chr(l)
else: s=s+chr(128|(l>>24)&255)+chr((l>>16)&255)+chr((l>>8)&255)+chr(l&255)
return s + name + value
class FCGI:
def __init__(self, port=None):
# environment variables
try:
self.FCGI_PORT = int(os.environ['FCGI_PORT'])
except:
self.FCGI_PORT = None
if port: self.FCGI_PORT = port
self.FCGI_PORT = None
try:
self.FCGI_ALLOWED_ADDR = os.environ['FCGI_WEB_SERVER_ADDRS']
self.FCGI_ALLOWED_ADDR = map(string.strip, string.split(self.FCGI_ALLOWED_ADDR, ','))
except:
self.FCGI_ALLOWED_ADDR = None
self.firstCall = 1
self.clearState()
self.socket = None
self.createServerSocket()
def createServerSocket(self):
if self.FCGI_PORT:
s=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.set_reuse_addr()
s.bind(('127.0.0.1', self.FCGI_PORT))
else:
try:
s=socket.fromfd(sys.stdin.fileno(), socket.AF_INET, socket.SOCK_STREAM)
s.getpeername()
except socket.error, (err, errmsg):
if err!=errno.ENOTCONN:
return
except:
return
self.socket = s
def accept(self):
if not self.socket: # plain CGI
if self.firstCall:
result = sys.stdin, spyceUtil.NoCloseOut(sys.stdout), sys.stderr, os.environ
else:
return 0
else: # FCGI
result = self.recv()
self.firstCall = 0
return result
def finish(self):
if self.firstCall or not self.socket: return
self.send()
def clearState(self):
self.reqID = 0
self.connection = None
self.environ = {}
self.stdin = StringIO()
self.stderr = StringIO()
self.stdout = StringIO()
self.data = StringIO()
def send(self):
self.stderr.seek(0,0)
self.stdout.seek(0,0)
self.sendStream(FCGI_STDERR, self.stderr.read())
self.sendStream(FCGI_STDOUT, self.stdout.read())
r=record()
r.recType=FCGI_END_REQUEST
r.reqId=self.reqID
r.appStatus=0
r.protocolStatus=FCGI_REQUEST_COMPLETE
r.writeRecord(self.connection)
self.connection.close()
self.clearState()
def sendStream(self, streamType, streamData):
if not streamData:
return
r=record()
r.recType = streamType
r.reqId = self.reqID
data = streamData
while data:
r.content, data = data[:8192], data[8192:]
r.writeRecord(self.connection)
r.content='' ; r.writeRecord(self.connection)
def recv(self):
self.connection, address = self.socket.accept()
# rimtodo: filter to serve only allowed addresses
# if good_addrs!=None and addr not in good_addrs:
# raise 'Connection from invalid server!'
remaining=1
while remaining:
r=record(); r.readRecord(self.connection)
if r.recType in [FCGI_GET_VALUES]: # management records
if r.recType == FCGI_GET_VALUES:
r.recType = FCGI_GET_VALUES_RESULT
v={}
vars={'FCGI_MAX_CONNS' : FCGI_MAX_CONNS,
'FCGI_MAX_REQS' : FCGI_MAX_REQS,
'FCGI_MPXS_CONNS': FCGI_MPXS_CONNS}
for i in r.values.keys():
if vars.has_key(i): v[i]=vars[i]
r.values=vars
r.writeRecord(self.connection)
elif r.reqId == 0: # management record of unknown type
r2 = record()
r2.recType = FCGI_UNKNOWN_TYPE ; r2.unknownType = r.recType
r2.writeRecord(self.connection)
continue
elif r.reqId != self.reqID and r.recType != FCGI_BEGIN_REQUEST:
continue # ignore inactive requests
elif r.recType == FCGI_BEGIN_REQUEST and self.reqID != 0:
continue # ignore BEGIN_REQUESTs in the middle of request
if r.recType == FCGI_BEGIN_REQUEST: # begin request
self.reqID = r.reqId
if r.role == FCGI_AUTHORIZER: remaining=1
elif r.role == FCGI_RESPONDER: remaining=2
elif r.role == FCGI_FILTER: remaining=3
elif r.recType == FCGI_PARAMS: # environment
if r.content == '': remaining=remaining-1
else:
for i in r.values.keys():
self.environ[i] = r.values[i]
elif r.recType == FCGI_STDIN: # stdin
if r.content == '': remaining=remaining-1
else: self.stdin.write(r.content)
elif r.recType == FCGI_DATA: # data
if r.content == '': remaining=remaining-1
else: self.data.write(r.content)
# end while
self.stdin.seek(0,0)
self.data.seek(0,0)
# return CGI environment
return self.stdin, spyceUtil.NoCloseOut(self.stdout), self.stderr, self.environ
|