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

Last change on this file since 2410 was 2410, checked in by quentin, 15 years ago

Get username from cherrypy request object

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