1   
  2   
  3   
  4   
  5   
  6   
  7   
  8   
  9   
 10   
 11   
 12   
 13   
 14   
 15   
 16   
 17   
 18   
 19   
 20  r"""Support utilities for building a WSGI application. 
 21   
 22  """ 
 23  __docformat__ = "restructuredtext en" 
 24   
 25  import cgi 
 26  import re 
 27  import StringIO 
 28  import overlaydict 
 29  import pathinfo 
 30   
 31  import reason_phrases 
 32   
 34      """Convert text into unicode, if it's not already unicode. 
 35   
 36      """ 
 37      if isinstance(text, str): 
 38          return text.decode('utf-8') 
 39      return text 
  40   
 42      """Return True iff the method string is one of the known HTTP methods. 
 43   
 44      """ 
 45      return method in ( 
 46                        'OPTIONS', 
 47                        'GET', 
 48                        'HEAD', 
 49                        'POST', 
 50                        'PUT', 
 51                        'DELETE', 
 52                        'TRACE', 
 53                        'CONNECT', 
 54      ) 
  55   
 57      """Request object, used to represent a request via WSGI. 
 58   
 59      """ 
 61          self.path = to_uni(environ.get('PATH_INFO', u'/')) 
 62          if not self.path: 
 63              self.path = u'/' 
 64          self.path_components = self.path.split(u'/')[1:] 
 65   
 66           
 67          self.method = environ['REQUEST_METHOD'].upper() 
 68   
 69          self.GET = cgi.parse_qs(environ.get('QUERY_STRING', '')) 
 70          self.POST = {} 
 71   
 72          if self.method == 'POST': 
 73              try: 
 74                  content_length = int(environ.get('CONTENT_LENGTH', 0)) 
 75              except (ValueError, TypeError): 
 76                  content_length = 0 
 77              if content_length > 0: 
 78                  fd = environ['wsgi.input'] 
 79                  buf = StringIO.StringIO() 
 80                  while content_length > 0: 
 81                      chunk = fd.read(min(content_length, 65536)) 
 82                      if not chunk: 
 83                          break 
 84                      buf.write(chunk) 
 85                      content_length -= len(chunk) 
 86                  self.raw_post_data = buf.getvalue() 
 87                  buf.close() 
 88              else: 
 89                  self.raw_post_data = '' 
 90              self.POST = cgi.parse_qs(self.raw_post_data) 
 91   
 92          self.params = overlaydict.OverlayDict(self.POST, self.GET) 
  93   
 95          """Set the WSGIWAPI properties found on the handler. 
 96   
 97          This is used to warn when the properties on the handler do not match 
 98          those used by the decorator - this is usually due to a second decorator 
 99          dropping the properties. 
100   
101          """ 
102          self._handler_props = handler_props 
 103   
109   
111          return u"Request(%s, \"%s\", %r)" % ( 
112                                               self.method, 
113                                               self.path, 
114                                               self.GET 
115                                              ) 
  116   
118      """Object satisfying the WSGI protocol for making a response. 
119   
120      This object should be passed the start_reponse parameter (as supplied by 
121      the WSGI protocol), and the Response object for the response.  The status 
122      code, headers, and response body will be read from the Response object. 
123   
124      """ 
125 -    def __init__(self, start_response, response): 
 128   
133   
135          return len(self.response.body) 
  136   
138      """Response object, used to return stuff via WSGI protocol. 
139   
140      The Response object is a container fr the details of the response.  It 
141      contains three significant members: 
142   
143       - status: The status code (as a string, with code and reason phrase) for 
144         the reponse. 
145       - headers: The headers to return about the request. 
146       - body: The body of the page to return. 
147   
148      """ 
149 -    def __init__(self, body=u'', status=200, content_type=u'text/plain'): 
 150          """Create a new Response object. 
151   
152          The body defaults to being empty, the status defaults to "200 OK", and 
153          the content_type defaults to 'text/plain'. 
154   
155           - body: The value to store in the body member.  Defaults to ''. 
156           - status: The status to set for the response.  May be specified as a 
157             number, or as a string (optionally, with a reason phrase).  Defaults 
158             to 200. 
159           - content_type: The content type to set for the response (as specified 
160             for the set_content_type() method).  Defaults to 'text/plain'. 
161   
162          """ 
163          self.body = body 
164          self.status = status 
165          self.headers = [] 
166          self.set_content_type(content_type) 
 167   
169          """Get the list of headers, encoded suitably for HTTP. 
170   
171          This converts the unicode headers to byte strings, suitable for passing 
172          through HTTP. 
173   
174          """ 
175          result = [] 
176          for header, value in self.headers: 
177              if not isinstance(header, str): 
178                  header = header.encode('iso-8859-1') 
179              if not isinstance(value, str): 
180                   
181                   
182                   
183                  value = value.encode('iso-8859-1') 
184              result.append((header, value)) 
185          return result 
 186   
187      VALID_STATUS_RE = re.compile(r'[12345][0-9][0-9]') 
189          if isinstance(status, basestring): 
190              if len(status) == 3: 
191                  pass  
192              elif len(status) <= 4: 
193                  raise ValueError(u"Supplied status (%r) is not valid" % status) 
194              elif status[3] == ' ': 
195                  if not Response.VALID_STATUS_RE.match(status[:3]): 
196                      raise ValueError(u"Supplied status (%r) is not valid" % 
197                                       status) 
198                  if isinstance(status, unicode): 
199                      try: 
200                          self._status = status.encode('us-ascii') 
201                      except UnicodeError, e: 
202                          e.reason += ", HTTP status line must be " \ 
203                                      "encodable as US-ASCII" 
204                          raise 
205                  else: 
206                      self._status = status 
207                  return 
208   
209          try: 
210              statusint = int(status) 
211          except ValueError: 
212              raise ValueError(u"Supplied status (%r) is not a valid " 
213                               "status code" % status) 
214   
215          if statusint < 100 or statusint >= 600: 
216              raise ValueError(u"Supplied status (%r) is not in valid range" % 
217                               status) 
218   
219          try: 
220              self._status = reason_phrases.phrase_dict[statusint] 
221          except KeyError: 
222              raise ValueError(u"Supplied status (%r) is not known" % 
223                               status) 
 224   
227      status = property(_get_status, _set_status, doc= 
228          """The status line to return. 
229   
230          This may be set to either a string or a number.  If a string, it may 
231          either contain only the status code, or may contain a reason phrase 
232          following the status code (separated by a space). 
233           
234          If there is no reason phrase, or the status code is a number, an 
235          appropriate reason phrase will be used, as long as the status code is 
236          one of the standard HTTP 1.1 codes.  For non-standard codes, the reason 
237          phrase must be supplied. 
238   
239          If `status` is a unicode string, it must contain only characters which 
240          can be encoded in the US-ASCII character set.  Any other characters 
241          will cause an exception to be thrown. 
242   
243          """) 
244   
246          """Set the value of a header which should only occur once. 
247   
248          If any values for the header already exist in the list of headers, they 
249          are first removed.  The comparison of header names is performed case 
250          insensitively. 
251   
252          """ 
253          self.headers = filter(lambda x: x[0].lower() != header.lower(), self.headers) 
254          self.headers.append((header, value)) 
 255   
256 -    def set_content_type(self, content_type): 
 257          """Set the content type to return. 
258   
259          """ 
260          self.set_unique_header(u'Content-Type', content_type) 
 261   
263          return "Response(%s, %s, %s)" % ( 
264              self.status, 
265              self.headers, 
266              self.body 
267          ) 
  268   
270 -    def __init__(self, status=500, message=None): 
 279      """Raise this exception if a requested resource is not found. 
280   
281      """ 
 284   
286      """Raise this exception if a method which is not allowed was used. 
287   
288      """ 
289 -    def __init__(self, request_method, allowed_methods): 
 290          if method_known(request_method): 
291              HTTPError.__init__(self, 405) 
292               
293              allow = list(allowed_methods) 
294              allow.sort() 
295              self.set_unique_header(u'Allow', u', '.join(allow)) 
296          else: 
297              raise HTTPError(501, "Request method %s is not implemented" % 
298                              request_method) 
  299   
301      """Raise this exception if a server error occurs. 
302   
303      """ 
 306   
307   
308