lib/http.py
changeset 46 185504387370
parent 45 e94ab812c0c8
child 47 3d59c9eeffaa
equal deleted inserted replaced
45:e94ab812c0c8 46:185504387370
     1 """
       
     2     WSGI HTTP utility code
       
     3 """
       
     4 
       
     5 # for utility functions
       
     6 import cgi
       
     7 
       
     8 # for header handling
       
     9 import wsgiref.headers
       
    10 
       
    11 # for path handling
       
    12 import os.path
       
    13 
       
    14 class Request (object) :
       
    15     """
       
    16         HTTP Request with associated metadata
       
    17     """
       
    18 
       
    19     def __init__ (self, env) :
       
    20         """
       
    21             Parse env data
       
    22         """
       
    23 
       
    24         # store env
       
    25         self.env = env
       
    26 
       
    27         # get the querystring
       
    28         self.arg_str = env.get('QUERY_STRING', '')
       
    29 
       
    30         # parse query args
       
    31         self.arg_dict = cgi.parse_qs(self.arg_str, True)
       
    32  
       
    33     @property
       
    34     def site_host (self) :
       
    35         """
       
    36             Returns the site's hostname (DNS name)
       
    37         """
       
    38         
       
    39         return self.env['HTTP_HOST']
       
    40   
       
    41     @property
       
    42     def site_root (self) :
       
    43         """
       
    44             Returns the URL path to the requested script's directory with no trailing slash, i.e.
       
    45 
       
    46             /               -> 
       
    47             /foo.cgi        -> 
       
    48             /foo/bar.cgi    -> /foo
       
    49         """
       
    50 
       
    51         return os.path.dirname(self.env['SCRIPT_NAME']).rstrip('/')
       
    52     
       
    53     @property
       
    54     def page_prefix (self) :
       
    55         """
       
    56             Returns the URL path root for page URLs, based on REQUEST_URI with PATH_INFO removed
       
    57 
       
    58             /                   -> 
       
    59             /foo.cgi            -> /foo.cgi
       
    60             /foo.cgi/index      -> /foo.cgi
       
    61             /foo.cgi/quux/bar   -> /foo.cgi
       
    62             /quux/foo.cgi/bar   -> /quux/foo.cgi
       
    63             /bar                -> 
       
    64         """
       
    65         
       
    66         # XXX: request uri path without the query string
       
    67         request_path = self.env.get('REQUEST_URI', '').split('?', 1)[0].rstrip('/')
       
    68 
       
    69         # path info
       
    70         page_name = self.get_page_name()
       
    71 
       
    72         # special-case for empty page_name
       
    73         if not page_name :
       
    74             return request_path
       
    75         
       
    76         # sanity-check
       
    77         assert request_path.endswith(page_name)
       
    78         
       
    79         # trim
       
    80         return request_path[:-len(page_name)].rstrip('/')
       
    81     
       
    82     def get_page_name (self) :
       
    83         """
       
    84             Returns the requested page path with no leading slash, i.e.
       
    85 
       
    86             /foo.cgi        -> 
       
    87             /foo.cgi/       -> 
       
    88             /foo.cgi/bar    -> bar
       
    89             /foo.cgi/quux/  -> quux/
       
    90         """
       
    91         
       
    92         # the raw PATH_INFO
       
    93         path_info = self.env.get('PATH_INFO')
       
    94         
       
    95         # avoid nasty '.' paths
       
    96         if path_info :
       
    97             return os.path.normpath(path_info).lstrip('/')
       
    98 
       
    99         else :
       
   100             return ''
       
   101     
       
   102     def get_args (self) :
       
   103         """
       
   104             Iterate over all available (key, value) pairs from the query string
       
   105         """
       
   106 
       
   107         return cgi.parse_qsl(self.arg_str)
       
   108 
       
   109 class Response (object) :
       
   110     """
       
   111         HTTP Response with headers and data
       
   112     """
       
   113 
       
   114     def __init__ (self, data, content_type='text/html', status='200 OK', charset='UTF-8') :
       
   115         """
       
   116             Create the response. The Content-type header is built from the given values. The given \a data must be
       
   117             either a str (which is sent plain), an unicode object (which is encoded with the relevant charset), or
       
   118             None, whereupon an empty response body is sent. The content_type argument can also be forced to None to
       
   119             not send a Content-type header (e.g. for redirects)
       
   120         """
       
   121 
       
   122         # store info
       
   123         self.status = status
       
   124         self.data = data
       
   125         self.charset = charset
       
   126 
       
   127         # headers
       
   128         self.headers = wsgiref.headers.Headers([])
       
   129         
       
   130         # add Content-type header?
       
   131         if content_type :
       
   132             self.add_header('Content-type', content_type, charset=charset)
       
   133 
       
   134     def add_header (self, name, value, **params) :
       
   135         """
       
   136             Add response header with the given name/value, plus option params
       
   137 
       
   138             XXX: uses the wsgiref.headers code, not sure how that behaves re multiple headers with the same name, etc
       
   139         """
       
   140         
       
   141         self.headers.add_header(name, value, **params)
       
   142     
       
   143     def get_status (self) :
       
   144         """
       
   145             Returns response status string (XXX Foo)
       
   146         """
       
   147 
       
   148         return self.status
       
   149     
       
   150     def get_headers (self) :
       
   151         """
       
   152             Returns the list of header (name, value) pairs
       
   153         """
       
   154 
       
   155         return self.headers.items()
       
   156 
       
   157     def get_data (self) :
       
   158         """
       
   159             Returns the response data - as an encoded string
       
   160         """
       
   161 
       
   162         if self.data :
       
   163             return self.data.encode(self.charset)
       
   164 
       
   165         else :
       
   166             return ''
       
   167 
       
   168 class ErrorResponse (Response) :
       
   169     """
       
   170         A response with an error code / message
       
   171     """
       
   172 
       
   173     def __init__ (self, status, message, details=None) :
       
   174         """
       
   175             Build a plain error message response with the given status/message
       
   176 
       
   177             @param status HTTP status code
       
   178             @param message short message to describe errors
       
   179             @param details optional details, plaintext
       
   180         """
       
   181 
       
   182         data = """\
       
   183 <html><head><title>%(title)s</title></head><body>
       
   184 <h1>%(title)s</h1>
       
   185 <p>%(message)s</p>
       
   186 %(details)s
       
   187 </body></html>
       
   188 """ % dict(
       
   189             title       = status, 
       
   190             message     = message,
       
   191             details     = '<pre>%s</pre>' % details if details else ''
       
   192         )
       
   193             
       
   194         super(ErrorResponse, self).__init__(data, status=status)
       
   195 
       
   196 class ResponseError (Exception) :
       
   197     """
       
   198         An exception that results in a specfic 4xx ErrorResponse message to the client
       
   199     """
       
   200 
       
   201     def __init__ (self, message, status='400 Bad Request', details=None) :
       
   202         self.status = status
       
   203         self.message = message
       
   204         self.details = details
       
   205 
       
   206         super(ResponseError, self).__init__(message)
       
   207 
       
   208     def get_response (self) :
       
   209         return ErrorResponse(self.status, self.message, self.details)
       
   210 
       
   211 class Redirect (Response) :
       
   212     """
       
   213         Redirect response
       
   214     """
       
   215 
       
   216     def __init__ (self, url) :
       
   217         """
       
   218             Redirect to given *absolute* URL
       
   219         """
       
   220         
       
   221         # no content-type or data
       
   222         super(Redirect, self).__init__(None, content_type=None, status='302 Found')
       
   223 
       
   224         # add Location: header
       
   225         self.add_header("Location", url)
       
   226 
       
   227