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

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

more sipb_xen_database -> invirt.database in web

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