medusa.http_server   F
last analyzed

Complexity

Total Complexity 121

Size/Duplication

Total Lines 785
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 505
dl 0
loc 785
rs 2
c 0
b 0
f 0
wmc 121

44 Methods

Rating   Name   Duplication   Size   Complexity  
A http_server.readable() 0 2 1
A http_request.has_key() 0 2 1
A http_request.response() 0 4 1
A http_channel.log() 0 2 1
A http_server.remove_handler() 0 2 1
A http_channel.send() 0 4 1
A http_server.handle_connect() 0 2 1
A fifo.push() 0 2 1
A http_request.__getitem__() 0 2 1
A http_server.writable() 0 2 1
A fifo.push_front() 0 2 1
A fifo.first() 0 2 1
A http_channel.check_maintenance() 0 3 2
A http_server.handle_read() 0 2 1
A fifo.__len__() 0 2 1
A http_request.__setitem__() 0 2 1
A http_channel.maintenance() 0 2 1
A http_channel.writable() 0 3 1
F http_request.done() 0 75 14
A http_channel.recv() 0 13 2
A http_request.build_reply_header() 0 8 2
A http_channel.collect_incoming_data() 0 7 2
A http_server.install_handler() 0 5 2
A http_request.get_header_with_regex() 0 6 3
A http_request.collect_incoming_data() 0 7 2
A http_channel.__init__() 0 10 1
B http_server.__init__() 0 51 6
A http_request.split_uri() 0 8 3
A http_channel.kill_zombies() 0 6 4
A fifo.__init__() 0 5 2
A http_request.error() 0 12 1
A http_request.log_date_string() 0 5 1
A http_server.status() 0 26 2
C http_channel.found_terminator() 0 66 11
A fifo.pop() 0 7 2
A http_channel.handle_error() 0 6 2
A http_channel.__repr__() 0 6 1
A http_request.log() 0 9 1
A http_request.get_header() 0 15 4
A http_channel.writable_for_proxy() 0 15 4
A http_request.__init__() 0 14 1
A http_request.found_terminator() 0 7 2
A http_server.handle_accept() 0 20 3
A http_request.push() 0 5 2

7 Functions

Rating   Name   Duplication   Size   Complexity  
A maybe_status() 0 5 2
A join_headers() 0 8 3
A crack_request() 0 10 3
A get_header() 0 6 4
A get_header_match() 0 6 4
A profile_loop() 0 5 2
A compute_timezone_for_log() 0 16 4

How to fix   Complexity   

Complexity

Complex classes like medusa.http_server often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
#! /usr/local/bin/python
2
# -*- Mode: Python; tab-width: 4 -*-
3
#
4
#    Author: Sam Rushing <[email protected]>
5
#    Copyright 1996-2000 by Sam Rushing
6
#                         All Rights Reserved.
7
#
8
9
# python modules
10
import os
11
import re
12
import socket
13
import stat
14
import string
15
import sys
16
import time
17
18
# async modules
19
import asyncore
20
import asynchat
21
22
# medusa modules
23
import http_date
24
import producers
25
import status_handler
26
import logger
27
28
VERSION_STRING = "Outer Space"
29
30
from counter import counter
31
from urllib import unquote
32
33
# ===========================================================================
34
#                            Request Object
35
# ===========================================================================
36
37
class http_request:
38
39
    # default reply code
40
    reply_code = 200
41
42
    request_counter = counter()
43
44
    # Whether to automatically use chunked encoding when
45
    #
46
    #   HTTP version is 1.1
47
    #   Content-Length is not set
48
    #   Chunked encoding is not already in effect
49
    #
50
    # If your clients are having trouble, you might want to disable this.
51
    use_chunked = 1
52
53
    # by default, this request object ignores user data.
54
    collector = None
55
56
    def __init__ (self, *args):
57
        # unpack information about the request
58
        (self.channel, self.request,
59
         self.command, self.uri, self.version,
60
         self.header) = args
61
62
        self.outgoing = fifo()
63
        self.reply_headers = {
64
            'Server'    : 'Medusa/%s' % VERSION_STRING,
65
            'Date'        : http_date.build_http_date (time.time())
66
            }
67
        self.request_number = http_request.request_counter.increment()
68
        self._split_uri = None
69
        self._header_cache = {}
70
71
    # --------------------------------------------------
72
    # reply header management
73
    # --------------------------------------------------
74
    def __setitem__ (self, key, value):
75
        self.reply_headers[key] = value
76
77
    def __getitem__ (self, key):
78
        return self.reply_headers[key]
79
80
    def has_key (self, key):
81
        return self.reply_headers.has_key (key)
82
83
    def build_reply_header (self):
84
        return string.join (
85
            [self.response(self.reply_code)] + map (
86
                lambda x: '%s: %s' % x,
87
                self.reply_headers.items()
88
                ),
89
            '\r\n'
90
            ) + '\r\n\r\n'
91
92
    # --------------------------------------------------
93
    # split a uri
94
    # --------------------------------------------------
95
96
    # <path>;<params>?<query>#<fragment>
97
    path_regex = re.compile (
98
    #      path      params    query   fragment
99
        r'([^;?#]*)(;[^?#]*)?(\?[^#]*)?(#.*)?'
100
        )
101
102
    def split_uri (self):
103
        if self._split_uri is None:
104
            m = self.path_regex.match (self.uri)
105
            if m.end() != len(self.uri):
106
                raise ValueError, "Broken URI"
107
            else:
108
                self._split_uri = m.groups()
109
        return self._split_uri
110
111
    def get_header_with_regex (self, head_reg, group):
112
        for line in self.header:
113
            m = head_reg.match (line)
114
            if m.end() == len(line):
115
                return head_reg.group (group)
116
        return ''
117
118
    def get_header (self, header):
119
        header = string.lower (header)
120
        hc = self._header_cache
121
        if not hc.has_key (header):
122
            h = header + ': '
123
            hl = len(h)
124
            for line in self.header:
125
                if string.lower (line[:hl]) == h:
126
                    r = line[hl:]
127
                    hc[header] = r
128
                    return r
129
            hc[header] = None
130
            return None
131
        else:
132
            return hc[header]
133
134
    # --------------------------------------------------
135
    # user data
136
    # --------------------------------------------------
137
138
    def collect_incoming_data (self, data):
139
        if self.collector:
140
            self.collector.collect_incoming_data (data)
141
        else:
142
            self.log_info(
143
                'Dropping %d bytes of incoming request data' % len(data),
144
                'warning'
145
                )
146
147
    def found_terminator (self):
148
        if self.collector:
149
            self.collector.found_terminator()
150
        else:
151
            self.log_info (
152
                'Unexpected end-of-record for incoming request',
153
                'warning'
154
                )
155
156
    def push (self, thing):
157
        if type(thing) == type(''):
158
            self.outgoing.push (producers.simple_producer (thing))
159
        else:
160
            self.outgoing.push (thing)
161
162
    def response (self, code=200):
163
        message = self.responses[code]
164
        self.reply_code = code
165
        return 'HTTP/%s %d %s' % (self.version, code, message)
166
167
    def error (self, code):
168
        self.reply_code = code
169
        message = self.responses[code]
170
        s = self.DEFAULT_ERROR_MESSAGE % {
171
            'code': code,
172
            'message': message,
173
            }
174
        self['Content-Length'] = len(s)
175
        self['Content-Type'] = 'text/html'
176
        # make an error reply
177
        self.push (s)
178
        self.done()
179
180
    # can also be used for empty replies
181
    reply_now = error
182
183
    def done (self):
184
        "finalize this transaction - send output to the http channel"
185
186
        # ----------------------------------------
187
        # persistent connection management
188
        # ----------------------------------------
189
190
        #  --- BUCKLE UP! ----
191
192
        connection = string.lower (get_header (CONNECTION, self.header))
193
194
        close_it = 0
195
        wrap_in_chunking = 0
196
197
        if self.version == '1.0':
198
            if connection == 'keep-alive':
199
                if not self.has_key ('Content-Length'):
200
                    close_it = 1
201
                else:
202
                    self['Connection'] = 'Keep-Alive'
203
            else:
204
                close_it = 1
205
        elif self.version == '1.1':
206
            if connection == 'close':
207
                close_it = 1
208
            elif not self.has_key ('Content-Length'):
209
                if self.has_key ('Transfer-Encoding'):
210
                    if not self['Transfer-Encoding'] == 'chunked':
211
                        close_it = 1
212
                elif self.use_chunked:
213
                    self['Transfer-Encoding'] = 'chunked'
214
                    wrap_in_chunking = 1
215
                else:
216
                    close_it = 1
217
        elif self.version is None:
218
            # Although we don't *really* support http/0.9 (because we'd have to
219
            # use \r\n as a terminator, and it would just yuck up a lot of stuff)
220
            # it's very common for developers to not want to type a version number
221
            # when using telnet to debug a server.
222
            close_it = 1
223
224
        outgoing_header = producers.simple_producer (self.build_reply_header())
225
226
        if close_it:
227
            self['Connection'] = 'close'
228
229
        if wrap_in_chunking:
230
            outgoing_producer = producers.chunked_producer (
231
                producers.composite_producer (self.outgoing)
232
                )
233
            # prepend the header
234
            outgoing_producer = producers.composite_producer (
235
                fifo([outgoing_header, outgoing_producer])
236
                )
237
        else:
238
            # prepend the header
239
            self.outgoing.push_front (outgoing_header)
240
            outgoing_producer = producers.composite_producer (self.outgoing)
241
242
        # apply a few final transformations to the output
243
        self.channel.push_with_producer (
244
            # globbing gives us large packets
245
            producers.globbing_producer (
246
                # hooking lets us log the number of bytes sent
247
                producers.hooked_producer (
248
                    outgoing_producer,
249
                    self.log
250
                    )
251
                )
252
            )
253
254
        self.channel.current_request = None
255
256
        if close_it:
257
            self.channel.close_when_done()
258
259
    def log_date_string (self, when):
260
        return time.strftime (
261
            '%d/%b/%Y:%H:%M:%S ',
262
            time.gmtime(when)
263
            ) + tz_for_log
264
265
    def log (self, bytes):
266
        self.channel.server.logger.log (
267
            self.channel.addr[0],
268
            '%d - - [%s] "%s" %d %d\n' % (
269
                self.channel.addr[1],
270
                self.log_date_string (time.time()),
271
                self.request,
272
                self.reply_code,
273
                bytes
274
                )
275
            )
276
277
    responses = {
278
        100: "Continue",
279
        101: "Switching Protocols",
280
        200: "OK",
281
        201: "Created",
282
        202: "Accepted",
283
        203: "Non-Authoritative Information",
284
        204: "No Content",
285
        205: "Reset Content",
286
        206: "Partial Content",
287
        300: "Multiple Choices",
288
        301: "Moved Permanently",
289
        302: "Moved Temporarily",
290
        303: "See Other",
291
        304: "Not Modified",
292
        305: "Use Proxy",
293
        400: "Bad Request",
294
        401: "Unauthorized",
295
        402: "Payment Required",
296
        403: "Forbidden",
297
        404: "Not Found",
298
        405: "Method Not Allowed",
299
        406: "Not Acceptable",
300
        407: "Proxy Authentication Required",
301
        408: "Request Time-out",
302
        409: "Conflict",
303
        410: "Gone",
304
        411: "Length Required",
305
        412: "Precondition Failed",
306
        413: "Request Entity Too Large",
307
        414: "Request-URI Too Large",
308
        415: "Unsupported Media Type",
309
        500: "Internal Server Error",
310
        501: "Not Implemented",
311
        502: "Bad Gateway",
312
        503: "Service Unavailable",
313
        504: "Gateway Time-out",
314
        505: "HTTP Version not supported"
315
        }
316
317
    # Default error message
318
    DEFAULT_ERROR_MESSAGE = string.join (
319
        ['<head>',
320
         '<title>Error response</title>',
321
         '</head>',
322
         '<body>',
323
         '<h1>Error response</h1>',
324
         '<p>Error code %(code)d.',
325
         '<p>Message: %(message)s.',
326
         '</body>',
327
         ''
328
         ],
329
        '\r\n'
330
        )
331
332
333
# ===========================================================================
334
#                         HTTP Channel Object
335
# ===========================================================================
336
337
class http_channel (asynchat.async_chat):
338
339
    # use a larger default output buffer
340
    ac_out_buffer_size = 1<<16
341
342
    current_request = None
343
    channel_counter = counter()
344
345
    def __init__ (self, server, conn, addr):
346
        self.channel_number = http_channel.channel_counter.increment()
347
        self.request_counter = counter()
348
        asynchat.async_chat.__init__ (self, conn)
349
        self.server = server
350
        self.addr = addr
351
        self.set_terminator ('\r\n\r\n')
352
        self.in_buffer = ''
353
        self.creation_time = int (time.time())
354
        self.check_maintenance()
355
356
    def __repr__ (self):
357
        ar = asynchat.async_chat.__repr__(self)[1:-1]
358
        return '<%s channel#: %s requests:%s>' % (
359
            ar,
360
            self.channel_number,
361
            self.request_counter
362
            )
363
364
    # Channel Counter, Maintenance Interval...
365
    maintenance_interval = 500
366
367
    def check_maintenance (self):
368
        if not self.channel_number % self.maintenance_interval:
369
            self.maintenance()
370
371
    def maintenance (self):
372
        self.kill_zombies()
373
374
    # 30-minute zombie timeout.  status_handler also knows how to kill zombies.
375
    zombie_timeout = 30 * 60
376
377
    def kill_zombies (self):
378
        now = int (time.time())
379
        for channel in asyncore.socket_map.values():
380
            if channel.__class__ == self.__class__:
381
                if (now - channel.creation_time) > channel.zombie_timeout:
382
                    channel.close()
383
384
    # --------------------------------------------------
385
    # send/recv overrides, good place for instrumentation.
386
    # --------------------------------------------------
387
388
    # this information needs to get into the request object,
389
    # so that it may log correctly.
390
    def send (self, data):
391
        result = asynchat.async_chat.send (self, data)
392
        self.server.bytes_out.increment (len(data))
393
        return result
394
395
    def recv (self, buffer_size):
396
        try:
397
            result = asynchat.async_chat.recv (self, buffer_size)
398
            self.server.bytes_in.increment (len(result))
399
            return result
400
        except MemoryError:
401
            # --- Save a Trip to Your Service Provider ---
402
            # It's possible for a process to eat up all the memory of
403
            # the machine, and put it in an extremely wedged state,
404
            # where medusa keeps running and can't be shut down.  This
405
            # is where MemoryError tends to get thrown, though of
406
            # course it could get thrown elsewhere.
407
            sys.exit ("Out of Memory!")
408
409
    def handle_error (self):
410
        t, v = sys.exc_info()[:2]
411
        if t is SystemExit:
412
            raise t, v
413
        else:
414
            asynchat.async_chat.handle_error (self)
415
416
    def log (self, *args):
417
        pass
418
419
    # --------------------------------------------------
420
    # async_chat methods
421
    # --------------------------------------------------
422
423
    def collect_incoming_data (self, data):
424
        if self.current_request:
425
            # we are receiving data (probably POST data) for a request
426
            self.current_request.collect_incoming_data (data)
427
        else:
428
            # we are receiving header (request) data
429
            self.in_buffer = self.in_buffer + data
430
431
    def found_terminator (self):
432
        if self.current_request:
433
            self.current_request.found_terminator()
434
        else:
435
            header = self.in_buffer
436
            self.in_buffer = ''
437
            lines = string.split (header, '\r\n')
438
439
            # --------------------------------------------------
440
            # crack the request header
441
            # --------------------------------------------------
442
443
            while lines and not lines[0]:
444
                # as per the suggestion of http-1.1 section 4.1, (and
445
                # Eric Parker <[email protected]>), ignore a leading
446
                # blank lines (buggy browsers tack it onto the end of
447
                # POST requests)
448
                lines = lines[1:]
449
450
            if not lines:
451
                self.close_when_done()
452
                return
453
454
            request = lines[0]
455
456
            # unquote path if necessary (thanks to Skip Montaro for pointing
457
            # out that we must unquote in piecemeal fashion).
458
            if '%' in request:
459
                request = unquote (request)
460
461
            command, uri, version = crack_request (request)
462
            header = join_headers (lines[1:])
463
464
            r = http_request (self, request, command, uri, version, header)
465
            self.request_counter.increment()
466
            self.server.total_requests.increment()
467
468
            if command is None:
469
                self.log_info ('Bad HTTP request: %s' % repr(request), 'error')
470
                r.error (400)
471
                return
472
473
            # --------------------------------------------------
474
            # handler selection and dispatch
475
            # --------------------------------------------------
476
            for h in self.server.handlers:
477
                if h.match (r):
478
                    try:
479
                        self.current_request = r
480
                        # This isn't used anywhere.
481
                        # r.handler = h # CYCLE
482
                        h.handle_request (r)
483
                    except:
484
                        self.server.exceptions.increment()
485
                        (file, fun, line), t, v, tbinfo = asyncore.compact_traceback()
486
                        self.log_info(
487
                                'Server Error: %s, %s: file: %s line: %s' % (t,v,file,line),
488
                                'error')
489
                        try:
490
                            r.error (500)
491
                        except:
492
                            pass
493
                    return
494
495
            # no handlers, so complain
496
            r.error (404)
497
498
    def writable (self):
499
        # this is just the normal async_chat 'writable', here for comparison
500
        return self.ac_out_buffer or len(self.producer_fifo)
501
502
    def writable_for_proxy (self):
503
        # this version of writable supports the idea of a 'stalled' producer
504
        # [i.e., it's not ready to produce any output yet] This is needed by
505
        # the proxy, which will be waiting for the magic combination of
506
        # 1) hostname resolved
507
        # 2) connection made
508
        # 3) data available.
509
        if self.ac_out_buffer:
510
            return 1
511
        elif len(self.producer_fifo):
512
            p = self.producer_fifo.first()
513
            if hasattr (p, 'stalled'):
514
                return not p.stalled()
515
            else:
516
                return 1
517
518
# ===========================================================================
519
#                         HTTP Server Object
520
# ===========================================================================
521
522
class http_server (asyncore.dispatcher):
523
524
    SERVER_IDENT = 'HTTP Server (%s)' % VERSION_STRING
525
526
    channel_class = http_channel
527
528
    def __init__ (self, ip, port, resolver=None, logger_object=None):
529
        self.ip = ip
530
        self.port = port
531
        asyncore.dispatcher.__init__ (self)
532
        self.create_socket (socket.AF_INET, socket.SOCK_STREAM)
533
534
        self.handlers = []
535
536
        if not logger_object:
537
            logger_object = logger.file_logger (sys.stdout)
538
539
        self.set_reuse_addr()
540
        self.bind ((ip, port))
541
542
        # lower this to 5 if your OS complains
543
        self.listen (1024)
544
545
        host, port = self.socket.getsockname()
546
        if not ip:
547
            self.log_info('Computing default hostname', 'warning')
548
            ip = socket.gethostbyname (socket.gethostname())
549
        try:
550
            self.server_name = socket.gethostbyaddr (ip)[0]
551
        except socket.error:
552
            self.log_info('Cannot do reverse lookup', 'warning')
553
            self.server_name = ip       # use the IP address as the "hostname"
554
555
        self.server_port = port
556
        self.total_clients = counter()
557
        self.total_requests = counter()
558
        self.exceptions = counter()
559
        self.bytes_out = counter()
560
        self.bytes_in  = counter()
561
562
        if not logger_object:
563
            logger_object = logger.file_logger (sys.stdout)
564
565
        if resolver:
566
            self.logger = logger.resolving_logger (resolver, logger_object)
567
        else:
568
            self.logger = logger.unresolving_logger (logger_object)
569
570
        self.log_info (
571
            'Medusa (Version: %s) started at %s'
572
            '\n\tHostname: %s'
573
            '\n\tPort:%d'
574
            '\n' % (
575
                VERSION_STRING,
576
                time.ctime(time.time()),
577
                self.server_name,
578
                port,
579
                )
580
            )
581
582
    def writable (self):
583
        return 0
584
585
    def handle_read (self):
586
        pass
587
588
    def readable (self):
589
        return self.accepting
590
591
    def handle_connect (self):
592
        pass
593
594
    def handle_accept (self):
595
        self.total_clients.increment()
596
        try:
597
            conn, addr = self.accept()
598
        except socket.error:
599
            # linux: on rare occasions we get a bogus socket back from
600
            # accept.  socketmodule.c:makesockaddr complains that the
601
            # address family is unknown.  We don't want the whole server
602
            # to shut down because of this.
603
            self.log_info ('warning: server accept() threw an exception', 'warning')
604
            return
605
        except TypeError:
606
            # unpack non-sequence.  this can happen when a read event
607
            # fires on a listening socket, but when we call accept()
608
            # we get EWOULDBLOCK, so dispatcher.accept() returns None.
609
            # Seen on FreeBSD3.
610
            self.log_info ('warning: server accept() threw EWOULDBLOCK', 'warning')
611
            return
612
613
        self.channel_class (self, conn, addr)
614
615
    def install_handler (self, handler, back=0):
616
        if back:
617
            self.handlers.append (handler)
618
        else:
619
            self.handlers.insert (0, handler)
620
621
    def remove_handler (self, handler):
622
        self.handlers.remove (handler)
623
624
    def status (self):
625
        def nice_bytes (n):
626
            return string.join (status_handler.english_bytes (n))
627
628
        handler_stats = filter (None, map (maybe_status, self.handlers))
629
630
        if self.total_clients:
631
            ratio = self.total_requests.as_long() / float(self.total_clients.as_long())
632
        else:
633
            ratio = 0.0
634
635
        return producers.composite_producer (
636
            fifo ([producers.lines_producer (
637
                ['<h2>%s</h2>'                            % self.SERVER_IDENT,
638
                '<br>Listening on: <b>Host:</b> %s'        % self.server_name,
639
                '<b>Port:</b> %d'                        % self.port,
640
                 '<p><ul>'
641
                 '<li>Total <b>Clients:</b> %s'            % self.total_clients,
642
                 '<b>Requests:</b> %s'                    % self.total_requests,
643
                 '<b>Requests/Client:</b> %.1f'            % (ratio),
644
                 '<li>Total <b>Bytes In:</b> %s'    % (nice_bytes (self.bytes_in.as_long())),
645
                 '<b>Bytes Out:</b> %s'                % (nice_bytes (self.bytes_out.as_long())),
646
                 '<li>Total <b>Exceptions:</b> %s'        % self.exceptions,
647
                 '</ul><p>'
648
                 '<b>Extension List</b><ul>',
649
                 ])] + handler_stats + [producers.simple_producer('</ul>')]
650
                  )
651
            )
652
653
def maybe_status (thing):
654
    if hasattr (thing, 'status'):
655
        return thing.status()
656
    else:
657
        return None
658
659
CONNECTION = re.compile ('Connection: (.*)', re.IGNORECASE)
660
661
# merge multi-line headers
662
# [486dx2: ~500/sec]
663
def join_headers (headers):
664
    r = []
665
    for i in range(len(headers)):
666
        if headers[i][0] in ' \t':
667
            r[-1] = r[-1] + headers[i][1:]
668
        else:
669
            r.append (headers[i])
670
    return r
671
672
def get_header (head_reg, lines, group=1):
673
    for line in lines:
674
        m = head_reg.match (line)
675
        if m and m.end() == len(line):
676
            return m.group (group)
677
    return ''
678
679
def get_header_match (head_reg, lines):
680
    for line in lines:
681
        m = head_reg.match (line)
682
        if m and m.end() == len(line):
683
            return m
684
    return ''
685
686
REQUEST = re.compile ('([^ ]+) ([^ ]+)(( HTTP/([0-9.]+))$|$)')
687
688
def crack_request (r):
689
    m = REQUEST.match (r)
690
    if m.end() == len(r):
691
        if m.group(3):
692
            version = m.group(5)
693
        else:
694
            version = None
695
        return string.lower (m.group(1)), m.group(2), version
696
    else:
697
        return None, None, None
698
699
class fifo:
700
    def __init__ (self, list=None):
701
        if not list:
702
            self.list = []
703
        else:
704
            self.list = list
705
706
    def __len__ (self):
707
        return len(self.list)
708
709
    def first (self):
710
        return self.list[0]
711
712
    def push_front (self, object):
713
        self.list.insert (0, object)
714
715
    def push (self, data):
716
        self.list.append (data)
717
718
    def pop (self):
719
        if self.list:
720
            result = self.list[0]
721
            del self.list[0]
722
            return (1, result)
723
        else:
724
            return (0, None)
725
726
def compute_timezone_for_log ():
727
    if time.daylight:
728
        tz = time.altzone
729
    else:
730
        tz = time.timezone
731
    if tz > 0:
732
        neg = 1
733
    else:
734
        neg = 0
735
        tz = -tz
736
    h, rem = divmod (tz, 3600)
737
    m, rem = divmod (rem, 60)
738
    if neg:
739
        return '-%02d%02d' % (h, m)
740
    else:
741
        return '+%02d%02d' % (h, m)
742
743
# if you run this program over a TZ change boundary, this will be invalid.
744
tz_for_log = compute_timezone_for_log()
745
746
if __name__ == '__main__':
747
    import sys
748
    if len(sys.argv) < 2:
749
        print 'usage: %s <root> <port>' % (sys.argv[0])
750
    else:
751
        import monitor
752
        import filesys
753
        import default_handler
754
        import status_handler
755
        import ftp_server
756
        import chat_server
757
        import resolver
758
        import logger
759
        rs = resolver.caching_resolver ('127.0.0.1')
760
        lg = logger.file_logger (sys.stdout)
761
        ms = monitor.secure_monitor_server ('fnord', '127.0.0.1', 9999)
762
        fs = filesys.os_filesystem (sys.argv[1])
763
        dh = default_handler.default_handler (fs)
764
        hs = http_server ('', string.atoi (sys.argv[2]), rs, lg)
765
        hs.install_handler (dh)
766
        ftp = ftp_server.ftp_server (
767
            ftp_server.dummy_authorizer(sys.argv[1]),
768
            port=8021,
769
            resolver=rs,
770
            logger_object=lg
771
            )
772
        cs = chat_server.chat_server ('', 7777)
773
        sh = status_handler.status_extension([hs,ms,ftp,cs,rs])
774
        hs.install_handler (sh)
775
        if ('-p' in sys.argv):
776
            def profile_loop ():
777
                try:
778
                    asyncore.loop()
779
                except KeyboardInterrupt:
780
                    pass
781
            import profile
782
            profile.run ('profile_loop()', 'profile.out')
783
        else:
784
            asyncore.loop()
785