source: trunk/packages/sipb-xen-www/code/main.py @ 863

Last change on this file since 863 was 863, checked in by price, 16 years ago

configurize some web code

  • Property svn:executable set to *
File size: 26.2 KB
Line 
1#!/usr/bin/python
2"""Main CGI script for web interface"""
3
4import base64
5import cPickle
6import cgi
7import datetime
8import hmac
9import random
10import sha
11import simplejson
12import sys
13import time
14import urllib
15from StringIO import StringIO
16
17def revertStandardError():
18    """Move stderr to stdout, and return the contents of the old stderr."""
19    errio = sys.stderr
20    if not isinstance(errio, StringIO):
21        return ''
22    sys.stderr = sys.stdout
23    errio.seek(0)
24    return errio.read()
25
26def printError():
27    """Revert stderr to stdout, and print the contents of stderr"""
28    if isinstance(sys.stderr, StringIO):
29        print revertStandardError()
30
31if __name__ == '__main__':
32    import atexit
33    atexit.register(printError)
34
35import templates
36from Cheetah.Template import Template
37import validation
38import cache_acls
39from webcommon import InvalidInput, CodeError, State
40import controls
41from getafsgroups import getAfsGroupMembers
42import sipb_xen_database
43from invirt import database
44from sipb_xen_database import Machine, CDROM, ctx, connect, MachineAccess, Type, Autoinstall
45from invirt.config import structs as config
46
47def pathSplit(path):
48    if path.startswith('/'):
49        path = path[1:]
50    i = path.find('/')
51    if i == -1:
52        i = len(path)
53    return path[:i], path[i:]
54
55class Checkpoint:
56    def __init__(self):
57        self.start_time = time.time()
58        self.checkpoints = []
59
60    def checkpoint(self, s):
61        self.checkpoints.append((s, time.time()))
62
63    def __str__(self):
64        return ('Timing info:\n%s\n' %
65                '\n'.join(['%s: %s' % (d, t - self.start_time) for
66                           (d, t) in self.checkpoints]))
67
68checkpoint = Checkpoint()
69
70def jquote(string):
71    return "'" + string.replace('\\', '\\\\').replace("'", "\\'").replace('\n', '\\n') + "'"
72
73def helppopup(subj):
74    """Return HTML code for a (?) link to a specified help topic"""
75    return ('<span class="helplink"><a href="help?' +
76            cgi.escape(urllib.urlencode(dict(subject=subj, simple='true')))
77            +'" target="_blank" ' +
78            'onclick="return helppopup(' + cgi.escape(jquote(subj)) + ')">(?)</a></span>')
79
80def makeErrorPre(old, addition):
81    if addition is None:
82        return
83    if old:
84        return old[:-6]  + '\n----\n' + str(addition) + '</pre>'
85    else:
86        return '<p>STDERR:</p><pre>' + str(addition) + '</pre>'
87
88Template.sipb_xen_database = sipb_xen_database
89Template.helppopup = staticmethod(helppopup)
90Template.err = None
91
92class JsonDict:
93    """Class to store a dictionary that will be converted to JSON"""
94    def __init__(self, **kws):
95        self.data = kws
96        if 'err' in kws:
97            err = kws['err']
98            del kws['err']
99            self.addError(err)
100
101    def __str__(self):
102        return simplejson.dumps(self.data)
103
104    def addError(self, text):
105        """Add stderr text to be displayed on the website."""
106        self.data['err'] = \
107            makeErrorPre(self.data.get('err'), text)
108
109class Defaults:
110    """Class to store default values for fields."""
111    memory = 256
112    disk = 4.0
113    cdrom = ''
114    autoinstall = ''
115    name = ''
116    description = ''
117    type = 'linux-hvm'
118
119    def __init__(self, max_memory=None, max_disk=None, **kws):
120        if max_memory is not None:
121            self.memory = min(self.memory, max_memory)
122        if max_disk is not None:
123            self.max_disk = min(self.disk, max_disk)
124        for key in kws:
125            setattr(self, key, kws[key])
126
127
128
129DEFAULT_HEADERS = {'Content-Type': 'text/html'}
130
131def invalidInput(op, username, fields, err, emsg):
132    """Print an error page when an InvalidInput exception occurs"""
133    d = dict(op=op, user=username, err_field=err.err_field,
134             err_value=str(err.err_value), stderr=emsg,
135             errorMessage=str(err))
136    return templates.invalid(searchList=[d])
137
138def hasVnc(status):
139    """Does the machine with a given status list support VNC?"""
140    if status is None:
141        return False
142    for l in status:
143        if l[0] == 'device' and l[1][0] == 'vfb':
144            d = dict(l[1][1:])
145            return 'location' in d
146    return False
147
148def parseCreate(username, state, fields):
149    kws = dict([(kw, fields.getfirst(kw)) for kw in 'name description owner memory disksize vmtype cdrom autoinstall'.split()])
150    validate = validation.Validate(username, state, strict=True, **kws)
151    return dict(contact=username, name=validate.name, description=validate.description, memory=validate.memory,
152                disksize=validate.disksize, owner=validate.owner, machine_type=validate.vmtype,
153                cdrom=getattr(validate, 'cdrom', None),
154                autoinstall=getattr(validate, 'autoinstall', None))
155
156def create(username, state, path, fields):
157    """Handler for create requests."""
158    try:
159        parsed_fields = parseCreate(username, state, fields)
160        machine = controls.createVm(username, state, **parsed_fields)
161    except InvalidInput, err:
162        pass
163    else:
164        err = None
165    state.clear() #Changed global state
166    d = getListDict(username, state)
167    d['err'] = err
168    if err:
169        for field in fields.keys():
170            setattr(d['defaults'], field, fields.getfirst(field))
171    else:
172        d['new_machine'] = parsed_fields['name']
173    return templates.list(searchList=[d])
174
175
176def getListDict(username, state):
177    """Gets the list of local variables used by list.tmpl."""
178    checkpoint.checkpoint('Starting')
179    machines = state.machines
180    checkpoint.checkpoint('Got my machines')
181    on = {}
182    has_vnc = {}
183    xmlist = state.xmlist
184    checkpoint.checkpoint('Got uptimes')
185    can_clone = 'ice3' not in state.xmlist_raw
186    for m in machines:
187        if m not in xmlist:
188            has_vnc[m] = 'Off'
189            m.uptime = None
190        else:
191            m.uptime = xmlist[m]['uptime']
192            if xmlist[m]['console']:
193                has_vnc[m] = True
194            elif m.type.hvm:
195                has_vnc[m] = "WTF?"
196            else:
197                has_vnc[m] = "ParaVM"+helppopup("ParaVM Console")
198    max_memory = validation.maxMemory(username, state)
199    max_disk = validation.maxDisk(username)
200    checkpoint.checkpoint('Got max mem/disk')
201    defaults = Defaults(max_memory=max_memory,
202                        max_disk=max_disk,
203                        owner=username,
204                        cdrom='gutsy-i386')
205    checkpoint.checkpoint('Got defaults')
206    def sortkey(machine):
207        return (machine.owner != username, machine.owner, machine.name)
208    machines = sorted(machines, key=sortkey)
209    d = dict(user=username,
210             cant_add_vm=validation.cantAddVm(username, state),
211             max_memory=max_memory,
212             max_disk=max_disk,
213             defaults=defaults,
214             machines=machines,
215             has_vnc=has_vnc,
216             can_clone=can_clone)
217    return d
218
219def listVms(username, state, path, fields):
220    """Handler for list requests."""
221    checkpoint.checkpoint('Getting list dict')
222    d = getListDict(username, state)
223    checkpoint.checkpoint('Got list dict')
224    return templates.list(searchList=[d])
225
226def vnc(username, state, path, fields):
227    """VNC applet page.
228
229    Note that due to same-domain restrictions, the applet connects to
230    the webserver, which needs to forward those requests to the xen
231    server.  The Xen server runs another proxy that (1) authenticates
232    and (2) finds the correct port for the VM.
233
234    You might want iptables like:
235
236    -t nat -A PREROUTING -s ! 18.181.0.60 -i eth1 -p tcp -m tcp \
237      --dport 10003 -j DNAT --to-destination 18.181.0.60:10003
238    -t nat -A POSTROUTING -d 18.181.0.60 -o eth1 -p tcp -m tcp \
239      --dport 10003 -j SNAT --to-source 18.187.7.142
240    -A FORWARD -d 18.181.0.60 -i eth1 -o eth1 -p tcp -m tcp \
241      --dport 10003 -j ACCEPT
242
243    Remember to enable iptables!
244    echo 1 > /proc/sys/net/ipv4/ip_forward
245    """
246    machine = validation.Validate(username, state, machine_id=fields.getfirst('machine_id')).machine
247
248    TOKEN_KEY = "0M6W0U1IXexThi5idy8mnkqPKEq1LtEnlK/pZSn0cDrN"
249
250    data = {}
251    data["user"] = username
252    data["machine"] = machine.name
253    data["expires"] = time.time()+(5*60)
254    pickled_data = cPickle.dumps(data)
255    m = hmac.new(TOKEN_KEY, digestmod=sha)
256    m.update(pickled_data)
257    token = {'data': pickled_data, 'digest': m.digest()}
258    token = cPickle.dumps(token)
259    token = base64.urlsafe_b64encode(token)
260    host = controls.listHost(machine)
261    if host:
262        port = 10003 + [h.hostname for h in config.hosts].index(host)
263    else:
264        port = 5900 # dummy
265
266    status = controls.statusInfo(machine)
267    has_vnc = hasVnc(status)
268
269    d = dict(user=username,
270             on=status,
271             has_vnc=has_vnc,
272             machine=machine,
273             hostname=state.environ.get('SERVER_NAME', 'localhost'),
274             port=port,
275             authtoken=token)
276    return templates.vnc(searchList=[d])
277
278def getHostname(nic):
279    """Find the hostname associated with a NIC.
280
281    XXX this should be merged with the similar logic in DNS and DHCP.
282    """
283    if nic.hostname and '.' in nic.hostname:
284        return nic.hostname
285    elif nic.machine:
286        return nic.machine.name + '.' + config.dns.domains[0]
287    else:
288        return None
289
290
291def getNicInfo(data_dict, machine):
292    """Helper function for info, get data on nics for a machine.
293
294    Modifies data_dict to include the relevant data, and returns a list
295    of (key, name) pairs to display "name: data_dict[key]" to the user.
296    """
297    data_dict['num_nics'] = len(machine.nics)
298    nic_fields_template = [('nic%s_hostname', 'NIC %s Hostname'),
299                           ('nic%s_mac', 'NIC %s MAC Addr'),
300                           ('nic%s_ip', 'NIC %s IP'),
301                           ]
302    nic_fields = []
303    for i in range(len(machine.nics)):
304        nic_fields.extend([(x % i, y % i) for x, y in nic_fields_template])
305        if not i:
306            data_dict['nic%s_hostname' % i] = getHostname(machine.nics[i])
307        data_dict['nic%s_mac' % i] = machine.nics[i].mac_addr
308        data_dict['nic%s_ip' % i] = machine.nics[i].ip
309    if len(machine.nics) == 1:
310        nic_fields = [(x, y.replace('NIC 0 ', '')) for x, y in nic_fields]
311    return nic_fields
312
313def getDiskInfo(data_dict, machine):
314    """Helper function for info, get data on disks for a machine.
315
316    Modifies data_dict to include the relevant data, and returns a list
317    of (key, name) pairs to display "name: data_dict[key]" to the user.
318    """
319    data_dict['num_disks'] = len(machine.disks)
320    disk_fields_template = [('%s_size', '%s size')]
321    disk_fields = []
322    for disk in machine.disks:
323        name = disk.guest_device_name
324        disk_fields.extend([(x % name, y % name) for x, y in
325                            disk_fields_template])
326        data_dict['%s_size' % name] = "%0.1f GiB" % (disk.size / 1024.)
327    return disk_fields
328
329def command(username, state, path, fields):
330    """Handler for running commands like boot and delete on a VM."""
331    back = fields.getfirst('back')
332    try:
333        d = controls.commandResult(username, state, fields)
334        if d['command'] == 'Delete VM':
335            back = 'list'
336    except InvalidInput, err:
337        if not back:
338            raise
339        print >> sys.stderr, err
340        result = err
341    else:
342        result = 'Success!'
343        if not back:
344            return templates.command(searchList=[d])
345    if back == 'list':
346        state.clear() #Changed global state
347        d = getListDict(username, state)
348        d['result'] = result
349        return templates.list(searchList=[d])
350    elif back == 'info':
351        machine = validation.Validate(username, state, machine_id=fields.getfirst('machine_id')).machine
352        return ({'Status': '303 See Other',
353                 'Location': 'info?machine_id=%d' % machine.machine_id},
354                "You shouldn't see this message.")
355    else:
356        raise InvalidInput('back', back, 'Not a known back page.')
357
358def modifyDict(username, state, fields):
359    """Modify a machine as specified by CGI arguments.
360
361    Return a list of local variables for modify.tmpl.
362    """
363    olddisk = {}
364    transaction = ctx.current.create_transaction()
365    try:
366        kws = dict([(kw, fields.getfirst(kw)) for kw in 'machine_id owner admin contact name description memory vmtype disksize'.split()])
367        validate = validation.Validate(username, state, **kws)
368        machine = validate.machine
369        oldname = machine.name
370
371        if hasattr(validate, 'memory'):
372            machine.memory = validate.memory
373
374        if hasattr(validate, 'vmtype'):
375            machine.type = validate.vmtype
376
377        if hasattr(validate, 'disksize'):
378            disksize = validate.disksize
379            disk = machine.disks[0]
380            if disk.size != disksize:
381                olddisk[disk.guest_device_name] = disksize
382                disk.size = disksize
383                ctx.current.save(disk)
384
385        update_acl = False
386        if hasattr(validate, 'owner') and validate.owner != machine.owner:
387            machine.owner = validate.owner
388            update_acl = True
389        if hasattr(validate, 'name'):
390            machine.name = validate.name
391        if hasattr(validate, 'description'):
392            machine.description = validate.description
393        if hasattr(validate, 'admin') and validate.admin != machine.administrator:
394            machine.administrator = validate.admin
395            update_acl = True
396        if hasattr(validate, 'contact'):
397            machine.contact = validate.contact
398
399        ctx.current.save(machine)
400        if update_acl:
401            print >> sys.stderr, machine, machine.administrator
402            cache_acls.refreshMachine(machine)
403        transaction.commit()
404    except:
405        transaction.rollback()
406        raise
407    for diskname in olddisk:
408        controls.resizeDisk(oldname, diskname, str(olddisk[diskname]))
409    if hasattr(validate, 'name'):
410        controls.renameMachine(machine, oldname, validate.name)
411    return dict(user=username,
412                command="modify",
413                machine=machine)
414
415def modify(username, state, path, fields):
416    """Handler for modifying attributes of a machine."""
417    try:
418        modify_dict = modifyDict(username, state, fields)
419    except InvalidInput, err:
420        result = None
421        machine = validation.Validate(username, state, machine_id=fields.getfirst('machine_id')).machine
422    else:
423        machine = modify_dict['machine']
424        result = 'Success!'
425        err = None
426    info_dict = infoDict(username, state, machine)
427    info_dict['err'] = err
428    if err:
429        for field in fields.keys():
430            setattr(info_dict['defaults'], field, fields.getfirst(field))
431    info_dict['result'] = result
432    return templates.info(searchList=[info_dict])
433
434
435def helpHandler(username, state, path, fields):
436    """Handler for help messages."""
437    simple = fields.getfirst('simple')
438    subjects = fields.getlist('subject')
439
440    help_mapping = {'ParaVM Console': """
441ParaVM machines do not support local console access over VNC.  To
442access the serial console of these machines, you can SSH with Kerberos
443to console.%s, using the name of the machine as your
444username.""" % config.dns.domains[0],
445                    'HVM/ParaVM': """
446HVM machines use the virtualization features of the processor, while
447ParaVM machines use Xen's emulation of virtualization features.  You
448want an HVM virtualized machine.""",
449                    'CPU Weight': """
450Don't ask us!  We're as mystified as you are.""",
451                    'Owner': """
452The owner field is used to determine <a
453href="help?subject=Quotas">quotas</a>.  It must be the name of a
454locker that you are an AFS administrator of.  In particular, you or an
455AFS group you are a member of must have AFS rlidwka bits on the
456locker.  You can check who administers the LOCKER locker using the
457commands 'attach LOCKER; fs la /mit/LOCKER' on Athena.)  See also <a
458href="help?subject=Administrator">administrator</a>.""",
459                    'Administrator': """
460The administrator field determines who can access the console and
461power on and off the machine.  This can be either a user or a moira
462group.""",
463                    'Quotas': """
464Quotas are determined on a per-locker basis.  Each locker may have a
465maximum of 512 megabytes of active ram, 50 gigabytes of disk, and 4
466active machines.""",
467                    'Console': """
468<strong>Framebuffer:</strong> At a Linux boot prompt in your VM, try
469setting <tt>fb=false</tt> to disable the framebuffer.  If you don't,
470your machine will run just fine, but the applet's display of the
471console will suffer artifacts.
472"""
473                    }
474
475    if not subjects:
476        subjects = sorted(help_mapping.keys())
477
478    d = dict(user=username,
479             simple=simple,
480             subjects=subjects,
481             mapping=help_mapping)
482
483    return templates.help(searchList=[d])
484
485
486def badOperation(u, s, p, e):
487    """Function called when accessing an unknown URI."""
488    return ({'Status': '404 Not Found'}, 'Invalid operation.')
489
490def infoDict(username, state, machine):
491    """Get the variables used by info.tmpl."""
492    status = controls.statusInfo(machine)
493    checkpoint.checkpoint('Getting status info')
494    has_vnc = hasVnc(status)
495    if status is None:
496        main_status = dict(name=machine.name,
497                           memory=str(machine.memory))
498        uptime = None
499        cputime = None
500    else:
501        main_status = dict(status[1:])
502        main_status['host'] = controls.listHost(machine)
503        start_time = float(main_status.get('start_time', 0))
504        uptime = datetime.timedelta(seconds=int(time.time()-start_time))
505        cpu_time_float = float(main_status.get('cpu_time', 0))
506        cputime = datetime.timedelta(seconds=int(cpu_time_float))
507    checkpoint.checkpoint('Status')
508    display_fields = """name uptime memory state cpu_weight on_reboot
509     on_poweroff on_crash on_xend_start on_xend_stop bootloader""".split()
510    display_fields = [('name', 'Name'),
511                      ('description', 'Description'),
512                      ('owner', 'Owner'),
513                      ('administrator', 'Administrator'),
514                      ('contact', 'Contact'),
515                      ('type', 'Type'),
516                      'NIC_INFO',
517                      ('uptime', 'uptime'),
518                      ('cputime', 'CPU usage'),
519                      ('host', 'Hosted on'),
520                      ('memory', 'RAM'),
521                      'DISK_INFO',
522                      ('state', 'state (xen format)'),
523                      ('cpu_weight', 'CPU weight'+helppopup('CPU Weight')),
524                      ('on_reboot', 'Action on VM reboot'),
525                      ('on_poweroff', 'Action on VM poweroff'),
526                      ('on_crash', 'Action on VM crash'),
527                      ('on_xend_start', 'Action on Xen start'),
528                      ('on_xend_stop', 'Action on Xen stop'),
529                      ('bootloader', 'Bootloader options'),
530                      ]
531    fields = []
532    machine_info = {}
533    machine_info['name'] = machine.name
534    machine_info['description'] = machine.description
535    machine_info['type'] = machine.type.hvm and 'HVM' or 'ParaVM'
536    machine_info['owner'] = machine.owner
537    machine_info['administrator'] = machine.administrator
538    machine_info['contact'] = machine.contact
539
540    nic_fields = getNicInfo(machine_info, machine)
541    nic_point = display_fields.index('NIC_INFO')
542    display_fields = (display_fields[:nic_point] + nic_fields +
543                      display_fields[nic_point+1:])
544
545    disk_fields = getDiskInfo(machine_info, machine)
546    disk_point = display_fields.index('DISK_INFO')
547    display_fields = (display_fields[:disk_point] + disk_fields +
548                      display_fields[disk_point+1:])
549
550    main_status['memory'] += ' MiB'
551    for field, disp in display_fields:
552        if field in ('uptime', 'cputime') and locals()[field] is not None:
553            fields.append((disp, locals()[field]))
554        elif field in machine_info:
555            fields.append((disp, machine_info[field]))
556        elif field in main_status:
557            fields.append((disp, main_status[field]))
558        else:
559            pass
560            #fields.append((disp, None))
561
562    checkpoint.checkpoint('Got fields')
563
564
565    max_mem = validation.maxMemory(machine.owner, state, machine, False)
566    checkpoint.checkpoint('Got mem')
567    max_disk = validation.maxDisk(machine.owner, machine)
568    defaults = Defaults()
569    for name in 'machine_id name description administrator owner memory contact'.split():
570        setattr(defaults, name, getattr(machine, name))
571    defaults.type = machine.type.type_id
572    defaults.disk = "%0.2f" % (machine.disks[0].size/1024.)
573    checkpoint.checkpoint('Got defaults')
574    d = dict(user=username,
575             on=status is not None,
576             machine=machine,
577             defaults=defaults,
578             has_vnc=has_vnc,
579             uptime=str(uptime),
580             ram=machine.memory,
581             max_mem=max_mem,
582             max_disk=max_disk,
583             owner_help=helppopup("Owner"),
584             fields = fields)
585    return d
586
587def info(username, state, path, fields):
588    """Handler for info on a single VM."""
589    machine = validation.Validate(username, state, machine_id=fields.getfirst('machine_id')).machine
590    d = infoDict(username, state, machine)
591    checkpoint.checkpoint('Got infodict')
592    return templates.info(searchList=[d])
593
594def unauthFront(_, _2, _3, fields):
595    """Information for unauth'd users."""
596    return templates.unauth(searchList=[{'simple' : True}])
597
598def overlord(username, state, path, fields):
599    if path == '':
600        return ({'Status': '303 See Other',
601                 'Location': 'overlord/'},
602                "You shouldn't see this message.")
603    if not username in getAfsGroupMembers('system:xvm', 'athena.mit.edu'):
604        raise InvalidInput('username', username, 'Not an overlord.')
605    newstate = State(username, overlord=True)
606    newstate.environ = state.environ
607    return handler(username, newstate, path, fields)
608
609def throwError(_, __, ___, ____):
610    """Throw an error, to test the error-tracing mechanisms."""
611    raise RuntimeError("test of the emergency broadcast system")
612
613mapping = dict(list=listVms,
614               vnc=vnc,
615               command=command,
616               modify=modify,
617               info=info,
618               create=create,
619               help=helpHandler,
620               unauth=unauthFront,
621               overlord=overlord,
622               errortest=throwError)
623
624def printHeaders(headers):
625    """Print a dictionary as HTTP headers."""
626    for key, value in headers.iteritems():
627        print '%s: %s' % (key, value)
628    print
629
630def send_error_mail(subject, body):
631    import subprocess
632
633    to = config.web.errormail
634    mail = """To: %s
635From: root@%s
636Subject: %s
637
638%s
639""" % (to, config.web.hostname, subject, body)
640    p = subprocess.Popen(['/usr/sbin/sendmail', to], stdin=subprocess.PIPE)
641    p.stdin.write(mail)
642    p.stdin.close()
643    p.wait()
644
645def show_error(op, username, fields, err, emsg, traceback):
646    """Print an error page when an exception occurs"""
647    d = dict(op=op, user=username, fields=fields,
648             errorMessage=str(err), stderr=emsg, traceback=traceback)
649    details = templates.error_raw(searchList=[d])
650    if username not in ('price', 'ecprice', 'andersk'): #add yourself at will
651        send_error_mail('xvm error on %s for %s: %s' % (op, username, err),
652                        details)
653    d['details'] = details
654    return templates.error(searchList=[d])
655
656def getUser(environ):
657    """Return the current user based on the SSL environment variables"""
658    return environ.get('REMOTE_USER', None)
659
660def handler(username, state, path, fields):
661    operation, path = pathSplit(path)
662    if not operation:
663        operation = 'list'
664    print 'Starting', operation
665    fun = mapping.get(operation, badOperation)
666    return fun(username, state, path, fields)
667
668class App:
669    def __init__(self, environ, start_response):
670        self.environ = environ
671        self.start = start_response
672
673        self.username = getUser(environ)
674        self.state = State(self.username)
675        self.state.environ = environ
676
677        random.seed() #sigh
678
679    def __iter__(self):
680        start_time = time.time()
681        sipb_xen_database.clear_cache()
682        sys.stderr = StringIO()
683        fields = cgi.FieldStorage(fp=self.environ['wsgi.input'], environ=self.environ)
684        operation = self.environ.get('PATH_INFO', '')
685        if not operation:
686            self.start("301 Moved Permanently", [('Location', './')])
687            return
688        if self.username is None:
689            operation = 'unauth'
690
691        try:
692            checkpoint.checkpoint('Before')
693            output = handler(self.username, self.state, operation, fields)
694            checkpoint.checkpoint('After')
695
696            headers = dict(DEFAULT_HEADERS)
697            if isinstance(output, tuple):
698                new_headers, output = output
699                headers.update(new_headers)
700            e = revertStandardError()
701            if e:
702                if hasattr(output, 'addError'):
703                    output.addError(e)
704                else:
705                    # This only happens on redirects, so it'd be a pain to get
706                    # the message to the user.  Maybe in the response is useful.
707                    output = output + '\n\nstderr:\n' + e
708            output_string =  str(output)
709            checkpoint.checkpoint('output as a string')
710        except Exception, err:
711            if not fields.has_key('js'):
712                if isinstance(err, InvalidInput):
713                    self.start('200 OK', [('Content-Type', 'text/html')])
714                    e = revertStandardError()
715                    yield str(invalidInput(operation, self.username, fields,
716                                           err, e))
717                    return
718            import traceback
719            self.start('500 Internal Server Error',
720                       [('Content-Type', 'text/html')])
721            e = revertStandardError()
722            s = show_error(operation, self.username, fields,
723                           err, e, traceback.format_exc())
724            yield str(s)
725            return
726        status = headers.setdefault('Status', '200 OK')
727        del headers['Status']
728        self.start(status, headers.items())
729        yield output_string
730        if fields.has_key('timedebug'):
731            yield '<pre>%s</pre>' % cgi.escape(str(checkpoint))
732
733def constructor():
734    connect()
735    return App
736
737def main():
738    from flup.server.fcgi_fork import WSGIServer
739    WSGIServer(constructor()).run()
740
741if __name__ == '__main__':
742    main()
Note: See TracBrowser for help on using the repository browser.