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 WSGIWebAPI 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):
277
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