2 #------------------------------------------------------------------------
3 # Copyright (c) 1998 by Total Control Software
5 #------------------------------------------------------------------------
9 # Description: Handles communication with the FastCGI module of the
10 # web server without using the FastCGI developers kit, but
11 # will also work in a non-FastCGI environment, (straight CGI.)
12 # This module was originally fetched from someplace on the
13 # Net (I don't remember where and I can't find it now...) and
14 # has been significantly modified to fix several bugs, be more
15 # readable, more robust at handling large CGI data and return
16 # document sizes, and also to fit the model that we had previously
19 # WARNING: If you don't know what you are doing, don't tinker with this
22 # Creation Date: 1/30/98 2:59:04PM
24 # License: This is free software. You may use this software for any
25 # purpose including modification/redistribution, so long as
26 # this header remains intact and that you do not claim any
27 # rights of ownership or authorship of this software. This
28 # software has been tested, but no warranty is expressed or
31 #------------------------------------------------------------------------
34 import os
, sys
, string
, socket
, errno
35 from cStringIO
import StringIO
38 #---------------------------------------------------------------------------
40 # Set various FastCGI constants
41 # Maximum number of requests that can be handled
45 # Supported version of the FastCGI protocol
48 # Boolean: can this application multiplex connections?
52 FCGI_BEGIN_REQUEST
= 1 ; FCGI_ABORT_REQUEST
= 2 ; FCGI_END_REQUEST
= 3
53 FCGI_PARAMS
= 4 ; FCGI_STDIN
= 5 ; FCGI_STDOUT
= 6
54 FCGI_STDERR
= 7 ; FCGI_DATA
= 8 ; FCGI_GET_VALUES
= 9
55 FCGI_GET_VALUES_RESULT
= 10
56 FCGI_UNKNOWN_TYPE
= 11
57 FCGI_MAXTYPE
= FCGI_UNKNOWN_TYPE
59 # Types of management records
60 ManagementTypes
= [FCGI_GET_VALUES
]
62 FCGI_NULL_REQUEST_ID
=0
64 # Masks for flags component of FCGI_BEGIN_REQUEST
67 # Values for role component of FCGI_BEGIN_REQUEST
68 FCGI_RESPONDER
= 1 ; FCGI_AUTHORIZER
= 2 ; FCGI_FILTER
= 3
70 # Values for protocolStatus component of FCGI_END_REQUEST
71 FCGI_REQUEST_COMPLETE
= 0 # Request completed nicely
72 FCGI_CANT_MPX_CONN
= 1 # This app can't multiplex
73 FCGI_OVERLOADED
= 2 # New request rejected; too busy
74 FCGI_UNKNOWN_ROLE
= 3 # Role value not known
80 #---------------------------------------------------------------------------
82 # The following function is used during debugging; it isn't called
83 # anywhere at the moment
86 "Append a string to /tmp/err"
87 errf
=open('/tmp/err', 'a+')
91 #---------------------------------------------------------------------------
94 "Class representing FastCGI records"
96 self
.version
= FCGI_VERSION_1
97 self
.recType
= FCGI_UNKNOWN_TYPE
98 self
.reqId
= FCGI_NULL_REQUEST_ID
101 #----------------------------------------
102 def readRecord(self
, sock
):
103 s
= map(ord, sock
.recv(8))
104 self
.version
, self
.recType
, paddingLength
= s
[0], s
[1], s
[6]
105 self
.reqId
, contentLength
= (s
[2]<<8)+s
[3], (s
[4]<<8)+s
[5]
107 while len(self
.content
) < contentLength
:
108 data
= sock
.recv(contentLength
- len(self
.content
))
109 self
.content
= self
.content
+ data
110 if paddingLength
!= 0:
111 padding
= sock
.recv(paddingLength
)
113 # Parse the content information
115 if self
.recType
== FCGI_BEGIN_REQUEST
:
116 self
.role
= (ord(c
[0])<<8) + ord(c
[1])
117 self
.flags
= ord(c
[2])
119 elif self
.recType
== FCGI_UNKNOWN_TYPE
:
120 self
.unknownType
= ord(c
[0])
122 elif self
.recType
== FCGI_GET_VALUES
or self
.recType
== FCGI_PARAMS
:
126 name
, value
, pos
= readPair(c
, pos
)
127 self
.values
[name
] = value
128 elif self
.recType
== FCGI_END_REQUEST
:
130 self
.appStatus
= (b
[0]<<24) + (b
[1]<<16) + (b
[2]<<8) + b
[3]
131 self
.protocolStatus
= ord(c
[4])
133 #----------------------------------------
134 def writeRecord(self
, sock
):
135 content
= self
.content
136 if self
.recType
== FCGI_BEGIN_REQUEST
:
137 content
= chr(self
.role
>>8) + chr(self
.role
& 255) + chr(self
.flags
) + 5*'\000'
139 elif self
.recType
== FCGI_UNKNOWN_TYPE
:
140 content
= chr(self
.unknownType
) + 7*'\000'
142 elif self
.recType
==FCGI_GET_VALUES
or self
.recType
==FCGI_PARAMS
:
144 for i
in self
.values
.keys():
145 content
= content
+ writePair(i
, self
.values
[i
])
147 elif self
.recType
==FCGI_END_REQUEST
:
149 content
= chr((v
>>24)&255) + chr((v
>>16)&255) + chr((v
>>8)&255) + chr(v
&255)
150 content
= content
+ chr(self
.protocolStatus
) + 3*'\000'
153 eLen
= (cLen
+ 7) & (0xFFFF - 7) # align to an 8-byte boundary
156 hdr
= [ self
.version
,
164 hdr
= string
.joinfields(map(chr, hdr
), '')
166 sock
.send(hdr
+ content
+ padLen
*'\000')
168 #---------------------------------------------------------------------------
170 def readPair(s
, pos
):
171 nameLen
=ord(s
[pos
]) ; pos
=pos
+1
173 b
=map(ord, s
[pos
:pos
+3]) ; pos
=pos
+3
174 nameLen
=((nameLen
&127)<<24) + (b
[0]<<16) + (b
[1]<<8) + b
[2]
175 valueLen
=ord(s
[pos
]) ; pos
=pos
+1
177 b
=map(ord, s
[pos
:pos
+3]) ; pos
=pos
+3
178 valueLen
=((valueLen
&127)<<24) + (b
[0]<<16) + (b
[1]<<8) + b
[2]
179 return ( s
[pos
:pos
+nameLen
], s
[pos
+nameLen
:pos
+nameLen
+valueLen
],
180 pos
+nameLen
+valueLen
)
182 #---------------------------------------------------------------------------
184 def writePair(name
, value
):
188 s
=chr(128|
(l
>>24)&255) + chr((l
>>16)&255) + chr((l
>>8)&255) + chr(l
&255)
192 s
=s
+chr(128|
(l
>>24)&255) + chr((l
>>16)&255) + chr((l
>>8)&255) + chr(l
&255)
193 return s
+ name
+ value
195 #---------------------------------------------------------------------------
197 def HandleManTypes(r
, conn
):
198 if r
.recType
== FCGI_GET_VALUES
:
199 r
.recType
= FCGI_GET_VALUES_RESULT
201 vars={'FCGI_MAX_CONNS' : FCGI_MAX_CONNS
,
202 'FCGI_MAX_REQS' : FCGI_MAX_REQS
,
203 'FCGI_MPXS_CONNS': FCGI_MPXS_CONNS
}
204 for i
in r
.values
.keys():
205 if vars.has_key(i
): v
[i
]=vars[i
]
209 #---------------------------------------------------------------------------
210 #---------------------------------------------------------------------------
213 _isFCGI
= 1 # assume it is until we find out for sure
221 #---------------------------------------------------------------------------
229 self
.haveFinished
= 0
233 self
.haveFinished
= 1
234 self
.inp
, self
.out
, self
.err
, self
.env
= \
235 sys
.stdin
, sys
.stdout
, sys
.stderr
, os
.environ
238 if os
.environ
.has_key('FCGI_WEB_SERVER_ADDRS'):
239 good_addrs
=string
.split(os
.environ
['FCGI_WEB_SERVER_ADDRS'], ',')
240 good_addrs
=map(string
.strip(good_addrs
)) # Remove whitespace
244 self
.conn
, addr
=_sock
.accept()
250 # Check if the connection is from a legal address
251 if good_addrs
!=None and addr
not in good_addrs
:
252 raise error
, 'Connection from invalid server!'
255 r
=record(); r
.readRecord(self
.conn
)
257 if r
.recType
in ManagementTypes
:
258 HandleManTypes(r
, self
.conn
)
261 # Oh, poopy. It's a management record of an unknown
262 # type. Signal the error.
264 r2
.recType
=FCGI_UNKNOWN_TYPE
; r2
.unknownType
=r
.recType
265 r2
.writeRecord(self
.conn
)
266 continue # Charge onwards
268 # Ignore requests that aren't active
269 elif r
.reqId
!= self
.requestId
and r
.recType
!= FCGI_BEGIN_REQUEST
:
272 # If we're already doing a request, ignore further BEGIN_REQUESTs
273 elif r
.recType
== FCGI_BEGIN_REQUEST
and self
.requestId
!= 0:
276 # Begin a new request
277 if r
.recType
== FCGI_BEGIN_REQUEST
:
278 self
.requestId
= r
.reqId
279 if r
.role
== FCGI_AUTHORIZER
: remaining
=1
280 elif r
.role
== FCGI_RESPONDER
: remaining
=2
281 elif r
.role
== FCGI_FILTER
: remaining
=3
283 elif r
.recType
== FCGI_PARAMS
:
285 remaining
=remaining
-1
287 for i
in r
.values
.keys():
288 self
.env
[i
] = r
.values
[i
]
290 elif r
.recType
== FCGI_STDIN
:
292 remaining
=remaining
-1
294 stdin
=stdin
+r
.content
296 elif r
.recType
==FCGI_DATA
:
298 remaining
=remaining
-1
301 # end of while remaining:
303 self
.inp
= sys
.stdin
= StringIO(stdin
)
304 self
.err
= sys
.stderr
= StringIO()
305 self
.out
= sys
.stdout
= StringIO()
306 self
.data
= StringIO(data
)
311 def Finish(self
, status
=0):
312 if not self
.haveFinished
:
313 self
.haveFinished
= 1
319 r
.recType
= FCGI_STDERR
320 r
.reqId
= self
.requestId
321 data
= self
.err
.read()
324 chunk
, data
= self
.getNextChunk(data
)
326 r
.writeRecord(self
.conn
)
327 r
.content
="" ; r
.writeRecord(self
.conn
) # Terminate stream
329 r
.recType
= FCGI_STDOUT
330 data
= self
.out
.read()
332 chunk
, data
= self
.getNextChunk(data
)
334 r
.writeRecord(self
.conn
)
335 r
.content
="" ; r
.writeRecord(self
.conn
) # Terminate stream
338 r
.recType
=FCGI_END_REQUEST
339 r
.reqId
=self
.requestId
341 r
.protocolStatus
=FCGI_REQUEST_COMPLETE
342 r
.writeRecord(self
.conn
)
346 def getFieldStorage(self
):
348 if self
.env
.has_key('REQUEST_METHOD'):
349 method
= string
.upper(self
.env
['REQUEST_METHOD'])
351 return cgi
.FieldStorage(environ
=self
.env
, keep_blank_values
=1)
353 return cgi
.FieldStorage(fp
=self
.inp
, environ
=self
.env
, keep_blank_values
=1)
355 def getNextChunk(self
, data
):
361 Accept
= FCGI
# alias for backwards compatibility
362 #---------------------------------------------------------------------------
368 s
=socket
.fromfd(sys
.stdin
.fileno(), socket
.AF_INET
,
371 except socket
.error
, (err
, errmsg
):
372 if err
!=errno
.ENOTCONN
: # must be a non-fastCGI environment
381 #---------------------------------------------------------------------------
391 fs
= req
.getFieldStorage()
392 size
= string
.atoi(fs
['size'].value
)
395 doc
= ['<HTML><HEAD><TITLE>FCGI TestApp</TITLE></HEAD>\n<BODY>\n']
396 doc
.append('<H2>FCGI TestApp</H2><P>')
397 doc
.append('<b>request count</b> = %d<br>' % counter
)
398 # doc.append('<b>pid</b> = %s<br>' % os.getpid())
399 # if req.env.has_key('CONTENT_LENGTH'):
400 # cl = string.atoi(req.env['CONTENT_LENGTH'])
401 # doc.append('<br><b>POST data (%s):</b><br><pre>' % cl)
406 # if type(val) == type([]):
407 # doc.append(' <b>%-15s :</b> %s\n' % (k, val))
409 # doc.append(' <b>%-15s :</b> %s\n' % (k, val.value))
410 # doc.append('</pre>')
413 # doc.append('<P><HR><P><pre>')
414 # keys = req.env.keys()
417 # doc.append('<b>%-20s :</b> %s\n' % (k, req.env[k]))
418 # doc.append('\n</pre><P><HR>\n')
419 doc
.append('</BODY></HTML>\n')
422 doc
= string
.join(doc
, '')
423 req
.out
.write('Content-length: %s\r\n'
424 'Content-type: text/html\r\n'
425 'Cache-Control: no-cache\r\n'
433 f
= open('traceback', 'w')
434 traceback
.print_exc( file = f
)
435 # f.write('%s' % doc)
437 if __name__
=='__main__':