wsgi/request.py
changeset 0 b97547f5f1fa
child 1802 d628defebc17
equal deleted inserted replaced
-1:000000000000 0:b97547f5f1fa
       
     1 """WSGI request adapter for cubicweb
       
     2 
       
     3 NOTE: each docstring tagged with ``COME FROM DJANGO`` means that
       
     4 the code has been taken (or adapted) from Djanco source code :
       
     5   http://www.djangoproject.com/
       
     6 
       
     7 :organization: Logilab
       
     8 :copyright: 2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
       
     9 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
       
    10 """
       
    11 
       
    12 __docformat__ = "restructuredtext en"
       
    13 
       
    14 from StringIO import StringIO
       
    15 from urllib import quote
       
    16 
       
    17 from logilab.common.decorators import cached
       
    18 
       
    19 from cubicweb.web.request import CubicWebRequestBase
       
    20 from cubicweb.wsgi import (pformat, qs2dict, safe_copyfileobj, parse_file_upload,
       
    21                         normalize_header)
       
    22 
       
    23 
       
    24 
       
    25 class CubicWebWsgiRequest(CubicWebRequestBase):
       
    26     """most of this code COMES FROM DJANO
       
    27     """
       
    28     
       
    29     def __init__(self, environ, vreg, base_url=None):
       
    30         self.environ = environ
       
    31         self.path = environ['PATH_INFO']
       
    32         self.method = environ['REQUEST_METHOD'].upper()
       
    33         self._headers = dict([(normalize_header(k[5:]), v) for k, v in self.environ.items()
       
    34                               if k.startswith('HTTP_')])
       
    35         https = environ.get("HTTPS") in ('yes', 'on', '1')
       
    36         self._base_url = base_url or self.application_uri()
       
    37         post, files = self.get_posted_data()
       
    38         super(CubicWebWsgiRequest, self).__init__(vreg, https, post)
       
    39         if files is not None:
       
    40             for fdef in files.itervalues():
       
    41                 fdef[0] = unicode(fdef[0], self.encoding)
       
    42             self.form.update(files)
       
    43         # prepare output headers
       
    44         self.headers_out = {}
       
    45         
       
    46     def __repr__(self):
       
    47         # Since this is called as part of error handling, we need to be very
       
    48         # robust against potentially malformed input.
       
    49         form = pformat(self.form)
       
    50         meta = pformat(self.environ)
       
    51         return '<CubicWebWsgiRequest\FORM:%s,\nMETA:%s>' % \
       
    52             (form, meta)
       
    53 
       
    54     ## cubicweb request interface ################################################
       
    55     
       
    56     def base_url(self):
       
    57         return self._base_url
       
    58 
       
    59     def http_method(self):
       
    60         """returns 'POST', 'GET', 'HEAD', etc."""
       
    61         return self.method
       
    62     
       
    63     def relative_path(self, includeparams=True):
       
    64         """return the normalized path of the request (ie at least relative
       
    65         to the application's root, but some other normalization may be needed
       
    66         so that the returned path may be used to compare to generated urls
       
    67 
       
    68         :param includeparams:
       
    69            boolean indicating if GET form parameters should be kept in the path
       
    70         """
       
    71         path = self.environ['PATH_INFO']
       
    72         path = path[1:] # remove leading '/'
       
    73         if includeparams:
       
    74             qs = self.environ.get('QUERY_STRING')
       
    75             if qs:
       
    76                 return '%s?%s' % (path, qs)
       
    77         
       
    78         return path
       
    79 
       
    80     def get_header(self, header, default=None):
       
    81         """return the value associated with the given input HTTP header,
       
    82         raise KeyError if the header is not set
       
    83         """
       
    84         return self._headers.get(normalize_header(header), default)
       
    85     
       
    86     def set_header(self, header, value, raw=True):
       
    87         """set an output HTTP header"""
       
    88         assert raw, "don't know anything about non-raw headers for wsgi requests"
       
    89         self.headers_out[header] = value
       
    90 
       
    91     def add_header(self, header, value):
       
    92         """add an output HTTP header"""
       
    93         self.headers_out[header] = value
       
    94     
       
    95     def remove_header(self, header):
       
    96         """remove an output HTTP header"""
       
    97         self.headers_out.pop(header, None)
       
    98 
       
    99     def header_if_modified_since(self):
       
   100         """If the HTTP header If-modified-since is set, return the equivalent
       
   101         mx date time value (GMT), else return None
       
   102         """
       
   103         return None
       
   104         
       
   105     ## wsgi request helpers ###################################################
       
   106     
       
   107     def application_uri(self):
       
   108         """Return the application's base URI (no PATH_INFO or QUERY_STRING)
       
   109 
       
   110         see python2.5's wsgiref.util.application_uri code
       
   111         """
       
   112         environ = self.environ
       
   113         url = environ['wsgi.url_scheme'] + '://'
       
   114         if environ.get('HTTP_HOST'):
       
   115             url += environ['HTTP_HOST']
       
   116         else:
       
   117             url += environ['SERVER_NAME']
       
   118             if environ['wsgi.url_scheme'] == 'https':
       
   119                 if environ['SERVER_PORT'] != '443':
       
   120                     url += ':' + environ['SERVER_PORT']
       
   121             else:
       
   122                 if environ['SERVER_PORT'] != '80':
       
   123                     url += ':' + environ['SERVER_PORT']
       
   124         url += quote(environ.get('SCRIPT_NAME') or '/')
       
   125         return url
       
   126         
       
   127     def get_full_path(self):
       
   128         return '%s%s' % (self.path, self.environ.get('QUERY_STRING', '') and ('?' + self.environ.get('QUERY_STRING', '')) or '')
       
   129 
       
   130     def is_secure(self):
       
   131         return 'wsgi.url_scheme' in self.environ \
       
   132             and self.environ['wsgi.url_scheme'] == 'https'
       
   133 
       
   134     def get_posted_data(self):
       
   135         files = None
       
   136         if self.method == 'POST':
       
   137             if self.environ.get('CONTENT_TYPE', '').startswith('multipart'):
       
   138                 header_dict = dict((normalize_header(k[5:]), v)
       
   139                                    for k, v in self.environ.items()
       
   140                                    if k.startswith('HTTP_'))
       
   141                 header_dict['Content-Type'] = self.environ.get('CONTENT_TYPE', '')
       
   142                 post, files = parse_file_upload(header_dict, self.raw_post_data)
       
   143             else:
       
   144                 post = qs2dict(self.raw_post_data)
       
   145         else:
       
   146             # The WSGI spec says 'QUERY_STRING' may be absent.
       
   147             post = qs2dict(self.environ.get('QUERY_STRING', ''))
       
   148         return post, files
       
   149 
       
   150     @property
       
   151     @cached
       
   152     def raw_post_data(self):
       
   153         buf = StringIO()
       
   154         try:
       
   155             # CONTENT_LENGTH might be absent if POST doesn't have content at all (lighttpd)
       
   156             content_length = int(self.environ.get('CONTENT_LENGTH', 0))
       
   157         except ValueError: # if CONTENT_LENGTH was empty string or not an integer
       
   158             content_length = 0
       
   159         if content_length > 0:
       
   160             safe_copyfileobj(self.environ['wsgi.input'], buf,
       
   161                     size=content_length)
       
   162         postdata = buf.getvalue()
       
   163         buf.close()
       
   164         return postdata
       
   165 
       
   166     def _validate_cache(self):
       
   167         """raise a `DirectResponse` exception if a cached page along the way
       
   168         exists and is still usable
       
   169         """
       
   170         # XXX
       
   171 #         if self.get_header('Cache-Control') in ('max-age=0', 'no-cache'):
       
   172 #             # Expires header seems to be required by IE7
       
   173 #             self.add_header('Expires', 'Sat, 01 Jan 2000 00:00:00 GMT')
       
   174 #             return
       
   175 #         try:
       
   176 #             http.checkPreconditions(self._twreq, _PreResponse(self))
       
   177 #         except http.HTTPError, ex:
       
   178 #             self.info('valid http cache, no actual rendering')
       
   179 #             raise DirectResponse(ex.response)
       
   180         # Expires header seems to be required by IE7
       
   181         self.add_header('Expires', 'Sat, 01 Jan 2000 00:00:00 GMT')