source: package_branches/invirt-web/cherrypy-rebased/code/main.py @ 2674

Last change on this file since 2674 was 2674, checked in by broder, 14 years ago

Use global imports for Mako templates

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