~ubuntu-branches/ubuntu/natty/moin/natty-updates

« back to all changes in this revision

Viewing changes to MoinMoin/support/thfcgi.py

  • Committer: Bazaar Package Importer
  • Author(s): Jonas Smedegaard
  • Date: 2008-06-22 21:17:13 UTC
  • mto: This revision was merged to the branch mainline in revision 18.
  • Revision ID: james.westby@ubuntu.com-20080622211713-inlv5k4eifxckelr
ImportĀ upstreamĀ versionĀ 1.7.0

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# -*- coding: iso-8859-1 -*-
 
2
"""
 
3
    thfcgi.py - FastCGI communication with thread support
 
4
 
 
5
    Copyright Peter ļæ½strand <astrand@lysator.liu.se> 2001
 
6
 
 
7
    Modified for MoinMoin by Oliver Graf <ograf@bitart.de> 2003
 
8
    
 
9
    Added "external application" support, refactored code
 
10
        by Alexander Schremmer <alex AT alexanderweb DOT de>
 
11
 
 
12
    Cleanup, fixed typos, PEP-8, support for limiting creation of threads,
 
13
    limited number of requests lifetime, configurable backlog for socket
 
14
    .listen() by MoinMoin:ThomasWaldmann.
 
15
 
 
16
    2007 Support for Python's logging module by MoinMoin:ThomasWaldmann.
 
17
 
 
18
    For code base see:
 
19
    http://cvs.lysator.liu.se/viewcvs/viewcvs.cgi/webkom/thfcgi.py?cvsroot=webkom
 
20
 
 
21
    This program is free software; you can redistribute it and/or modify
 
22
    it under the terms of the GNU General Public License as published by
 
23
    the Free Software Foundation; version 2 of the License. 
 
24
 
 
25
    This program is distributed in the hope that it will be useful,
 
26
    but WITHOUT ANY WARRANTY; without even the implied warranty of
 
27
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
28
    GNU General Public License for more details.
 
29
 
 
30
    You should have received a copy of the GNU General Public License
 
31
    along with this program; if not, write to the Free Software
 
32
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 
33
"""
 
34
 
 
35
# TODO: Compare compare the number of bytes received on FCGI_STDIN with
 
36
#       CONTENT_LENGTH and abort the update if the two numbers are not equal.
 
37
 
 
38
import logging
 
39
log = logging.getLogger(__name__)
 
40
 
 
41
import os
 
42
import sys
 
43
import select
 
44
import socket
 
45
import errno
 
46
import cgi
 
47
from cStringIO import StringIO
 
48
import struct
 
49
 
 
50
try:
 
51
    import threading as _threading
 
52
except ImportError:
 
53
    import dummy_threading as _threading
 
54
 
 
55
# Maximum number of requests that can be handled
 
56
FCGI_MAX_REQS = 50
 
57
FCGI_MAX_CONNS = 50
 
58
FCGI_VERSION_1 = 1
 
59
 
 
60
# Can this application multiplex connections?
 
61
FCGI_MPXS_CONNS = 0
 
62
 
 
63
# Record types
 
64
FCGI_BEGIN_REQUEST = 1
 
65
FCGI_ABORT_REQUEST = 2
 
66
FCGI_END_REQUEST = 3
 
67
FCGI_PARAMS = 4
 
68
FCGI_STDIN = 5
 
69
FCGI_STDOUT = 6
 
70
FCGI_STDERR = 7
 
71
FCGI_DATA = 8
 
72
FCGI_GET_VALUES = 9
 
73
FCGI_GET_VALUES_RESULT = 10
 
74
FCGI_UNKNOWN_TYPE = 11
 
75
FCGI_MAXTYPE = FCGI_UNKNOWN_TYPE
 
76
 
 
77
# Types of management records
 
78
KNOWN_MANAGEMENT_TYPES = [FCGI_GET_VALUES]
 
79
 
 
80
FCGI_NULL_REQUEST_ID = 0
 
81
 
 
82
# Masks for flags component of FCGI_BEGIN_REQUEST
 
83
FCGI_KEEP_CONN = 1
 
84
 
 
85
# Values for role component of FCGI_BEGIN_REQUEST
 
86
FCGI_RESPONDER = 1
 
87
FCGI_AUTHORIZER = 2
 
88
FCGI_FILTER = 3
 
89
 
 
90
# Values for protocolStatus component of FCGI_END_REQUEST
 
91
FCGI_REQUEST_COMPLETE = 0     # Request completed ok
 
92
FCGI_CANT_MPX_CONN = 1        # This app cannot multiplex
 
93
FCGI_OVERLOADED = 2           # Too busy
 
94
FCGI_UNKNOWN_ROLE = 3         # Role value not known
 
95
 
 
96
# Struct format types
 
97
FCGI_BeginRequestBody = "!HB5x"
 
98
FCGI_Record_header = "!BBHHBx"
 
99
FCGI_UnknownTypeBody = "!B7x"
 
100
FCGI_EndRequestBody = "!IB3x"
 
101
 
 
102
 
 
103
def log(s):
 
104
    logging.debug(s)
 
105
 
 
106
class SocketErrorOnWrite:
 
107
    """Is raised if a write fails in the socket code."""
 
108
    pass
 
109
 
 
110
class Record:
 
111
    """Class representing FastCGI records"""
 
112
 
 
113
    def __init__(self):
 
114
        """Initialize FastCGI record"""
 
115
        self.version = FCGI_VERSION_1
 
116
        self.rec_type = FCGI_UNKNOWN_TYPE
 
117
        self.req_id   = FCGI_NULL_REQUEST_ID
 
118
        self.content = ""
 
119
 
 
120
        # Only in FCGI_BEGIN_REQUEST
 
121
        self.role = None
 
122
        self.flags = None
 
123
        self.keep_conn = 0
 
124
 
 
125
        # Only in FCGI_UNKNOWN_TYPE
 
126
        self.unknownType = None
 
127
 
 
128
        # Only in FCGI_END_REQUEST
 
129
        self.appStatus = None
 
130
        self.protocolStatus = None
 
131
 
 
132
    def read_pair(self, data, pos):
 
133
        """Read a FastCGI key-value pair from the server."""
 
134
        namelen = struct.unpack("!B", data[pos])[0]
 
135
        if namelen & 128:
 
136
            # 4-byte name length
 
137
            namelen = struct.unpack("!I", data[pos:pos+4])[0] & 0x7fffffff
 
138
            pos += 4
 
139
        else:
 
140
            pos += 1
 
141
 
 
142
        valuelen = struct.unpack("!B", data[pos])[0]
 
143
        if valuelen & 128:
 
144
            # 4-byte value length
 
145
            valuelen = struct.unpack("!I", data[pos:pos+4])[0] & 0x7fffffff
 
146
            pos += 4
 
147
        else:
 
148
            pos += 1
 
149
 
 
150
        name = data[pos:pos+namelen]
 
151
        pos += namelen
 
152
        value = data[pos:pos+valuelen]
 
153
        pos += valuelen
 
154
 
 
155
        return name, value, pos
 
156
 
 
157
    def write_pair(self, name, value):
 
158
        """Write a FastCGI key-value pair to the server."""
 
159
        namelen = len(name)
 
160
        if namelen < 128:
 
161
            data = struct.pack("!B", namelen)
 
162
        else:
 
163
            # 4-byte name length
 
164
            data = struct.pack("!I", namelen | 0x80000000L)
 
165
 
 
166
        valuelen = len(value)
 
167
        if valuelen < 128:
 
168
            data += struct.pack("!B", value)
 
169
        else:
 
170
            # 4-byte value length
 
171
            data += struct.pack("!I", value | 0x80000000L)
 
172
 
 
173
        return data + name + value
 
174
 
 
175
    def readRecord(self, sock):
 
176
        """Read a FastCGI record from the server."""
 
177
        data = sock.recv(8)
 
178
        if not data:
 
179
            # No data received. This means EOF. 
 
180
            return None
 
181
 
 
182
        self.version, self.rec_type, self.req_id, contentLength, paddingLength = \
 
183
            struct.unpack(FCGI_Record_header, data)
 
184
 
 
185
        self.content = ""
 
186
        while len(self.content) < contentLength:
 
187
            data = sock.recv(contentLength - len(self.content))
 
188
            self.content = self.content + data
 
189
        if paddingLength != 0:
 
190
            sock.recv(paddingLength)
 
191
 
 
192
        # Parse the content information
 
193
        if self.rec_type == FCGI_BEGIN_REQUEST:
 
194
            self.role, self.flags = struct.unpack(FCGI_BeginRequestBody, self.content)
 
195
            self.keep_conn = self.flags & FCGI_KEEP_CONN
 
196
 
 
197
        elif self.rec_type == FCGI_UNKNOWN_TYPE:
 
198
            self.unknownType = struct.unpack(FCGI_UnknownTypeBody, self.content)
 
199
 
 
200
        elif self.rec_type == FCGI_GET_VALUES or self.rec_type == FCGI_PARAMS:
 
201
            self.values = {}
 
202
            pos = 0
 
203
            while pos < len(self.content):
 
204
                name, value, pos = self.read_pair(self.content, pos)
 
205
                self.values[name] = value
 
206
 
 
207
        elif self.rec_type == FCGI_END_REQUEST:
 
208
            self.appStatus, self.protocolStatus = struct.unpack(FCGI_EndRequestBody, self.content)
 
209
 
 
210
        return 1
 
211
 
 
212
    def writeRecord(self, sock):
 
213
        """Write a FastCGI record to the server."""
 
214
        content = self.content
 
215
        if self.rec_type == FCGI_BEGIN_REQUEST:
 
216
            content = struct.pack(FCGI_BeginRequestBody, self.role, self.flags)
 
217
 
 
218
        elif self.rec_type == FCGI_UNKNOWN_TYPE:
 
219
            content = struct.pack(FCGI_UnknownTypeBody, self.unknownType)
 
220
 
 
221
        elif self.rec_type == FCGI_GET_VALUES or self.rec_type == FCGI_PARAMS:
 
222
            content = ""
 
223
            for i in self.values:
 
224
                content = content + self.write_pair(i, self.values[i])
 
225
 
 
226
        elif self.rec_type == FCGI_END_REQUEST:
 
227
            content = struct.pack(FCGI_EndRequestBody, self.appStatus, self.protocolStatus)
 
228
 
 
229
        # Align to 8-byte boundary
 
230
        clen = len(content)
 
231
        padlen = ((clen + 7) & 0xfff8) - clen
 
232
 
 
233
        hdr = struct.pack(FCGI_Record_header, self.version, self.rec_type, self.req_id, clen, padlen)
 
234
 
 
235
        try:
 
236
            sock.sendall(hdr + content + padlen*"\x00")
 
237
        except socket.error:
 
238
            # Write error, probably broken pipe. Exit. 
 
239
            raise SocketErrorOnWrite
 
240
 
 
241
 
 
242
class Request:
 
243
    """A request, corresponding to an accept():ed connection and
 
244
    a FCGI request."""
 
245
 
 
246
    def __init__(self, conn, req_handler, inthread=False):
 
247
        """Initialize Request container."""
 
248
        self.conn = conn
 
249
        self.req_handler = req_handler
 
250
        self.inthread = inthread
 
251
 
 
252
        self.keep_conn = 0
 
253
        self.req_id = None
 
254
 
 
255
        # Input
 
256
        self.env = {}
 
257
        self.env_complete = 0
 
258
        self.stdin = StringIO()
 
259
        self.stdin_complete = 0
 
260
        self.data = StringIO()
 
261
        self.data_complete = 0
 
262
 
 
263
        # Output
 
264
        self.out = StringIO()
 
265
        self.err = StringIO()
 
266
 
 
267
        self.have_finished = 0
 
268
 
 
269
    def run(self):
 
270
        """Read records for this request and handle them through the
 
271
        request handler."""
 
272
        while 1:
 
273
            try:
 
274
                if self.conn.fileno() < 1:
 
275
                    # Connection lost
 
276
                    raise Exception("Connection lost")
 
277
            except:
 
278
                return
 
279
 
 
280
            select.select([self.conn], [], [])
 
281
            rec = Record()
 
282
            if rec.readRecord(self.conn):
 
283
                self._handle_record(rec)
 
284
            else:
 
285
                # EOF, connection closed. Break loop, end thread. 
 
286
                return
 
287
 
 
288
    def getFieldStorage(self):
 
289
        """Return a cgi FieldStorage constructed from the stdin and
 
290
        environ read from the server for this request."""
 
291
        self.stdin.reset()
 
292
        # cgi.FieldStorage will eat the input here...
 
293
        r = cgi.FieldStorage(fp=self.stdin, environ=self.env, keep_blank_values=1)
 
294
        # hence, we reset here so we can obtain
 
295
        # the data again...
 
296
        self.stdin.reset()
 
297
        return r
 
298
 
 
299
    def _flush(self, stream):
 
300
        """Flush a stream of this request."""
 
301
        stream.reset()
 
302
 
 
303
        rec = Record()
 
304
        rec.rec_type = FCGI_STDOUT
 
305
        rec.req_id = self.req_id
 
306
        data = stream.read()
 
307
 
 
308
        if not data:
 
309
            # Writing zero bytes would mean stream termination
 
310
            return
 
311
 
 
312
        while data:
 
313
            chunk, data = self.getNextChunk(data)
 
314
            rec.content = chunk
 
315
            rec.writeRecord(self.conn)
 
316
        # Truncate
 
317
        stream.reset()
 
318
        stream.truncate()
 
319
 
 
320
    def flush_out(self):
 
321
        """Flush Requests stdout stream."""
 
322
        self._flush(self.out)
 
323
 
 
324
    def flush_err(self):
 
325
        """Flush Requests stderr stream."""
 
326
        self._flush(self.err)
 
327
 
 
328
    def finish(self, status=0):
 
329
        """Finish this Request, flushing all output and
 
330
        possible exiting this thread."""
 
331
        if self.have_finished:
 
332
            return
 
333
 
 
334
        self.have_finished = 1
 
335
 
 
336
        # stderr
 
337
        if self.err.tell(): # just send err record if there is data on the err stream
 
338
            self.err.reset()
 
339
            rec = Record()
 
340
            rec.rec_type = FCGI_STDERR
 
341
            rec.req_id = self.req_id
 
342
            data = self.err.read()
 
343
            while data:
 
344
                chunk, data = self.getNextChunk(data)
 
345
                rec.content = chunk
 
346
                rec.writeRecord(self.conn)
 
347
            rec.content = ""
 
348
            rec.writeRecord(self.conn)      # Terminate stream
 
349
 
 
350
        # stdout
 
351
        self.out.reset()
 
352
        rec = Record()
 
353
        rec.rec_type = FCGI_STDOUT
 
354
        rec.req_id = self.req_id
 
355
        data = self.out.read()
 
356
        while data:
 
357
            chunk, data = self.getNextChunk(data)
 
358
            rec.content = chunk
 
359
            rec.writeRecord(self.conn)
 
360
        rec.content = ""
 
361
        rec.writeRecord(self.conn)      # Terminate stream
 
362
 
 
363
        # end request
 
364
        rec = Record()
 
365
        rec.rec_type = FCGI_END_REQUEST
 
366
        rec.req_id = self.req_id
 
367
        rec.appStatus = status
 
368
        rec.protocolStatus = FCGI_REQUEST_COMPLETE
 
369
        rec.writeRecord(self.conn)
 
370
        if not self.keep_conn:
 
371
            self.conn.close()
 
372
            if self.inthread:
 
373
                raise SystemExit
 
374
 
 
375
    #
 
376
    # Record handlers
 
377
    #
 
378
    def _handle_record(self, rec):
 
379
        """Handle record."""
 
380
        if rec.req_id == FCGI_NULL_REQUEST_ID:
 
381
            # Management record            
 
382
            self._handle_man_record(rec)
 
383
        else:
 
384
            # Application record
 
385
            self._handle_app_record(rec)
 
386
 
 
387
    def _handle_man_record(self, rec):
 
388
        """Handle management record."""
 
389
        rec_type = rec.rec_type
 
390
        if rec_type in KNOWN_MANAGEMENT_TYPES:
 
391
            self._handle_known_man_types(rec)
 
392
        else:
 
393
            # It's a management record of an unknown type. Signal the error.
 
394
            rec = Record()
 
395
            rec.rec_type = FCGI_UNKNOWN_TYPE
 
396
            rec.unknownType = rec_type
 
397
            rec.writeRecord(self.conn)
 
398
 
 
399
    def _handle_known_man_types(self, rec):
 
400
        """Handle a known management record."""
 
401
        if rec.rec_type == FCGI_GET_VALUES:
 
402
            reply_rec = Record()
 
403
            reply_rec.rec_type = FCGI_GET_VALUES_RESULT
 
404
 
 
405
            params = {'FCGI_MAX_CONNS': FCGI_MAX_CONNS,
 
406
                      'FCGI_MAX_REQS': FCGI_MAX_REQS,
 
407
                      'FCGI_MPXS_CONNS': FCGI_MPXS_CONNS,
 
408
                     }
 
409
 
 
410
            for name in rec.values:
 
411
                if name in params:
 
412
                    # We known this value, include in reply
 
413
                    reply_rec.values[name] = params[name]
 
414
 
 
415
            rec.writeRecord(self.conn)
 
416
 
 
417
    def _handle_app_record(self, rec):
 
418
        """Handle an application record. This calls the specified
 
419
        request_handler, if environ and stdin is complete."""
 
420
        if rec.rec_type == FCGI_BEGIN_REQUEST:
 
421
            # Discrete
 
422
            self._handle_begin_request(rec)
 
423
            return
 
424
        elif rec.req_id != self.req_id:
 
425
            log("Received unknown request ID %r" % rec.req_id)
 
426
            # Ignore requests that aren't active
 
427
            return
 
428
        if rec.rec_type == FCGI_ABORT_REQUEST:
 
429
            # Discrete
 
430
            rec.rec_type = FCGI_END_REQUEST
 
431
            rec.protocolStatus = FCGI_REQUEST_COMPLETE
 
432
            rec.appStatus = 0
 
433
            rec.writeRecord(self.conn)
 
434
            return
 
435
        elif rec.rec_type == FCGI_PARAMS:
 
436
            # Stream
 
437
            self._handle_params(rec)
 
438
        elif rec.rec_type == FCGI_STDIN:
 
439
            # Stream
 
440
            self._handle_stdin(rec)
 
441
        elif rec.rec_type == FCGI_DATA:
 
442
            # Stream
 
443
            self._handle_data(rec)
 
444
        else:
 
445
            # Should never happen. 
 
446
            log("Received unknown FCGI record type %r" % rec.rec_type)
 
447
            pass
 
448
 
 
449
        if self.env_complete and self.stdin_complete:
 
450
            # Call application request handler. 
 
451
            # The arguments sent to the request handler is:
 
452
            # self: us. 
 
453
            # req: The request.
 
454
            # env: The request environment
 
455
            # form: FieldStorage.
 
456
            self.req_handler(self, self.env, self.getFieldStorage())
 
457
 
 
458
    def _handle_begin_request(self, rec):
 
459
        """Handle begin request."""
 
460
        if rec.role != FCGI_RESPONDER:
 
461
            # Unknown role, signal error.
 
462
            rec.rec_type = FCGI_END_REQUEST
 
463
            rec.appStatus = 0
 
464
            rec.protocolStatus = FCGI_UNKNOWN_ROLE
 
465
            rec.writeRecord(self.conn)
 
466
            return
 
467
 
 
468
        self.req_id = rec.req_id
 
469
        self.keep_conn = rec.keep_conn
 
470
 
 
471
    def _handle_params(self, rec):
 
472
        """Handle environment."""
 
473
        if self.env_complete:
 
474
            # Should not happen
 
475
            log("Received FCGI_PARAMS more than once")
 
476
            return
 
477
 
 
478
        if not rec.content:
 
479
            self.env_complete = 1
 
480
 
 
481
        # Add all vars to our environment
 
482
        self.env.update(rec.values)
 
483
 
 
484
    def _handle_stdin(self, rec):
 
485
        """Handle stdin."""
 
486
        if self.stdin_complete:
 
487
            # Should not happen
 
488
            log("Received FCGI_STDIN more than once")
 
489
            return
 
490
 
 
491
        if not rec.content:
 
492
            self.stdin_complete = 1
 
493
            self.stdin.reset()
 
494
            return
 
495
 
 
496
        self.stdin.write(rec.content)
 
497
 
 
498
    def _handle_data(self, rec):
 
499
        """Handle data."""
 
500
        if self.data_complete:
 
501
            # Should not happen
 
502
            log("Received FCGI_DATA more than once")
 
503
            return
 
504
 
 
505
        if not rec.content:
 
506
            self.data_complete = 1
 
507
 
 
508
        self.data.write(rec.content)
 
509
 
 
510
    def getNextChunk(self, data):
 
511
        """Helper function which returns chunks of data."""
 
512
        chunk = data[:8192]
 
513
        data = data[8192:]
 
514
        return chunk, data
 
515
 
 
516
class FCGI:
 
517
    """FCGI requests"""
 
518
 
 
519
    def __init__(self, req_handler, fd=sys.stdin, port=None, max_requests=-1, backlog=5, max_threads=5):
 
520
        """Initialize main loop and set request_handler."""
 
521
        self.req_handler = req_handler
 
522
        self.fd = fd
 
523
        self.__port = port
 
524
        self._make_socket()
 
525
        # how many requests we have left before terminating this process, -1 means infinite lifetime:
 
526
        self.requests_left = max_requests
 
527
        # for socket.listen(backlog):
 
528
        self.backlog = backlog
 
529
        # how many threads we have at maximum (including the main program = 1. thread)
 
530
        self.max_threads = max_threads
 
531
 
 
532
    def accept_handler(self, conn, addr, inthread=False):
 
533
        """Construct Request and run() it."""
 
534
        self._check_good_addrs(addr)
 
535
        try:
 
536
            req = Request(conn, self.req_handler, inthread)
 
537
            req.run()
 
538
        except SocketErrorOnWrite:
 
539
            raise SystemExit
 
540
 
 
541
    def _make_socket(self):
 
542
        """Create socket and verify FCGI environment."""
 
543
        try:
 
544
            if self.__port:
 
545
                if isinstance(self.__port, str):
 
546
                    try:
 
547
                        os.unlink(self.__port)
 
548
                    except:
 
549
                        pass
 
550
                    s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
 
551
                    s.bind(self.__port)
 
552
                    # os.chmod(self.__port, 0660)
 
553
                else:
 
554
                    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 
555
                    s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
 
556
                    # bind to the localhost
 
557
                    s.bind(('127.0.0.1', self.__port))
 
558
                s.listen(1)
 
559
            else:
 
560
                if hasattr(socket, 'fromfd'):
 
561
                    s = socket.fromfd(self.fd.fileno(), socket.AF_INET, socket.SOCK_STREAM)
 
562
                    s.getpeername()
 
563
                else: # we do not run on posix, fire up an FCGI external process
 
564
                    raise ValueError("FastCGI port is not setup correctly")
 
565
        except socket.error, (err, errmsg):
 
566
            if err != errno.ENOTCONN:
 
567
                raise RuntimeError("No FastCGI environment: %s - %s" % (repr(err), errmsg))
 
568
 
 
569
        self.sock = s
 
570
 
 
571
    def _check_good_addrs(self, addr):
 
572
        """Check if request is done from the right server."""
 
573
        # Apaches mod_fastcgi seems not to use FCGI_WEB_SERVER_ADDRS. 
 
574
        if 'FCGI_WEB_SERVER_ADDRS' in os.environ:
 
575
            good_addrs = os.environ['FCGI_WEB_SERVER_ADDRS'].split(',')
 
576
            good_addrs = [addr.strip() for addr in good_addrs] # Remove whitespace
 
577
        else:
 
578
            good_addrs = None
 
579
 
 
580
        # Check if the connection is from a legal address
 
581
        if good_addrs is not None and addr not in good_addrs:
 
582
            raise RuntimeError("Connection from invalid server!")
 
583
 
 
584
    def run(self):
 
585
        """Wait & serve. Calls request_handler on every request."""
 
586
        self.sock.listen(self.backlog)
 
587
        pid = os.getpid()
 
588
        log("Starting Process (PID=%d)" % pid)
 
589
        running = True
 
590
        while running:
 
591
            if not self.requests_left:
 
592
                # self.sock.shutdown(RDWR) here does NOT help with backlog
 
593
                log("Maximum number of processed requests reached, terminating this worker process (PID=%d)..." % pid)
 
594
                running = False
 
595
            elif self.requests_left > 0:
 
596
                self.requests_left -= 1
 
597
            if running:
 
598
                conn, addr = self.sock.accept()
 
599
                threadcount = _threading.activeCount()
 
600
                if threadcount < self.max_threads:
 
601
                    log("Accepted connection, %d active threads, starting worker thread..." % threadcount)
 
602
                    t = _threading.Thread(target=self.accept_handler, args=(conn, addr, True))
 
603
                    t.start()
 
604
                else:
 
605
                    log("Accepted connection, %d active threads, running in main thread..." % threadcount)
 
606
                    self.accept_handler(conn, addr, False)
 
607
        self.sock.close()
 
608
        log("Ending Process (PID=%d)" % pid)
 
609