lukeplant_me_uk/bibleverses/web/cgi-bin/lib/bibleverses/webutils.py
author Luke Plant <L.Plant.98@cantab.net>
Sun May 18 22:49:46 2008 +0100 (2 years ago)
changeset 242 3f021c1c17c1
parent 199645ed570763e
permissions -rwxr-xr-x
Fixed typo in comment.
        1 # Very simple web framework for a CGI app.
        2 # The only things implemented are the things needed for this app
        3 
        4 from cgi import parse_qsl
        5 import os
        6 import sys
        7 import re
        8 from Cookie import BaseCookie
        9 
       10 # Simple version of 'sorted' for Python 2.3 compat
       11 def sorted(l):
       12     newlist = list(l)
       13     newlist.sort()
       14     return newlist
       15 
       16 def _decode_utf8_namevals(s):
       17     """Decodes x-www-form-urlencoded data, interpreting as 
       18     UTF-8 bytestrings, and returning as a dictionary"""
       19     # Don't care about multiple values at the moment
       20     return dict([(k.decode('UTF-8'), v.decode('UTF-8'))
       21                  for k, v in parse_qsl(s)])
       22 
       23 class HttpRequest(object):
       24     def __init__(self, environ, inputstream=None):
       25         self.environ = environ
       26         self.GET = _decode_utf8_namevals(environ['QUERY_STRING'])
       27         self.COOKIES = BaseCookie(environ.get('HTTP_COOKIE', ''))
       28         self.path = environ['PATH_INFO']
       29         self.method = environ['REQUEST_METHOD']
       30         if self.method == 'POST' and inputstream is not None:
       31             self.POST = _decode_utf8_namevals(''.join(inputstream.readlines()))
       32         else:
       33             self.POST = {}
       34 
       35     def get_cookie(self, name, default):
       36         morsel = self.COOKIES.get(name)
       37         if morsel is None:
       38             return default
       39         else:
       40             return morsel.value
       41 
       42 def force_utf8(s):
       43     """Converts unicode object to UTF-8, returns bytestring as is
       44     (without checking it is valid UTF-8)"""
       45     if isinstance(s, str):
       46         return s
       47     else:
       48         return s.encode("UTF-8")
       49 
       50 def force_ascii(s):
       51     if isinstance(s, str):
       52         return s
       53     else:
       54         return s.encode("us-ascii")
       55 
       56 class HttpResponse(object):
       57     def __init__(self, content=u"", content_type="text/html",
       58                  status=200):
       59         self.content = content
       60         self.headers = {}
       61         self.content_type = content_type
       62         self.status = status
       63 
       64     def _get_content_type(self):
       65         return self._content_type
       66     
       67     def _set_content_type(self, val):
       68         self._content_type = val
       69         self.headers['Content-Type'] = "%s; charset=UTF-8" % val
       70 
       71     content_type = property(_get_content_type, _set_content_type)
       72 
       73     def _set_status(self, val):
       74         self.headers['Status'] = str(val)
       75 
       76     def _get_status(self):
       77         return int(self.headers['Status'])
       78 
       79     status = property(_get_status, _set_status)
       80 
       81     def __str__(self):
       82         return '\n'.join(["%s: %s" % (force_ascii(k), force_ascii(v))
       83                          for k, v in sorted(self.headers.iteritems())]) + \
       84                "\n\n" + force_utf8(self.content)
       85 
       86 class Http404(HttpResponse):
       87     def __init__(self, *args, **kwargs):
       88         kwargs['status'] = 404
       89         kwargs['content'] = "Not found"
       90         super(Http404, self).__init__(*args, **kwargs)
       91 
       92 def dispatch(urls):
       93     """Accepts a list of (regex, callable) pairs and calls the
       94     callable whose regex matches the GET request.  Named groups in the
       95     regex will be used as keyword arguments to the callable."""
       96 
       97     request = HttpRequest(os.environ, sys.stdin)
       98 
       99     found = False
      100     resp = None
      101     for regex, acallable in urls:
      102         m = re.match(regex, request.path)
      103         if m is not None:
      104             resp = acallable(request, **m.groupdict())
      105             found = True
      106             break
      107 
      108     if not found:
      109         resp = Http404()
      110 
      111     sys.stdout.write(str(resp))