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

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

Move the help popup code into the templates where it belongs

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