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

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

Show the cherrypy request object on the helloworld page

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