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

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

Implement help handler

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