|
1 WebOb Reference |
|
2 +++++++++++++++ |
|
3 |
|
4 .. contents:: |
|
5 |
|
6 .. comment: |
|
7 |
|
8 >>> from dtopt import ELLIPSIS |
|
9 |
|
10 Introduction |
|
11 ============ |
|
12 |
|
13 This document covers all the details of the Request and Response |
|
14 objects. It is written to be testable with `doctest |
|
15 <http://python.org/doc/current/lib/module-doctest.html>`_ -- this |
|
16 effects the flavor of the documentation, perhaps to its detriment. |
|
17 But it also means you can feel confident that the documentation is |
|
18 correct. |
|
19 |
|
20 This is a somewhat different approach to reference documentation |
|
21 compared to the extracted documentation for the `request |
|
22 <class-webob.Request.html>`_ and `response |
|
23 <class-webob.Response.html>`_. |
|
24 |
|
25 Request |
|
26 ======= |
|
27 |
|
28 The primary object in WebOb is ``webob.Request``, a wrapper around a |
|
29 `WSGI environment <http://www.python.org/dev/peps/pep-0333/>`_. |
|
30 |
|
31 The basic way you create a request object is simple enough: |
|
32 |
|
33 .. code-block:: |
|
34 |
|
35 >>> from webob import Request |
|
36 >>> environ = {} |
|
37 >>> req = Request(environ) |
|
38 |
|
39 The request object *wraps* the environment; it has very little |
|
40 internal state of its own. Instead attributes you access read and |
|
41 write to the environment dictionary. |
|
42 |
|
43 You don't have to understand the details of WSGI to use this library; |
|
44 this library handles those details for you. You also don't have to |
|
45 use this exclusively of other libraries. If those other libraries |
|
46 also keep their state in the environment, multiple wrappers can |
|
47 coexist. Examples of libraries that can coexist include |
|
48 `paste.wsgiwrappers.Request |
|
49 <http://pythonpaste.org/class-paste.wsgiwrappers.WSGIRequest.html>`_ |
|
50 (used by Pylons) and `yaro.Request |
|
51 <http://lukearno.com/projects/yaro/>`_. |
|
52 |
|
53 The WSGI environment has a number of required variables. To make it |
|
54 easier to test and play around with, the ``Request`` class has a |
|
55 constructor that will fill in a minimal environment: |
|
56 |
|
57 .. code-block:: |
|
58 |
|
59 >>> req = Request.blank('/article?id=1') |
|
60 >>> from pprint import pprint |
|
61 >>> pprint(req.environ) |
|
62 {'HTTP_HOST': 'localhost:80', |
|
63 'PATH_INFO': '/article', |
|
64 'QUERY_STRING': 'id=1', |
|
65 'REQUEST_METHOD': 'GET', |
|
66 'SCRIPT_NAME': '', |
|
67 'SERVER_NAME': 'localhost', |
|
68 'SERVER_PORT': '80', |
|
69 'SERVER_PROTOCOL': 'HTTP/1.0', |
|
70 'wsgi.errors': <open file '<stderr>', mode 'w' at ...>, |
|
71 'wsgi.input': <cStringIO.StringI object at ...>, |
|
72 'wsgi.multiprocess': False, |
|
73 'wsgi.multithread': False, |
|
74 'wsgi.run_once': False, |
|
75 'wsgi.url_scheme': 'http', |
|
76 'wsgi.version': (1, 0)} |
|
77 |
|
78 Request Body |
|
79 ------------ |
|
80 |
|
81 ``req.body`` is a file-like object that gives the body of the request |
|
82 (e.g., a POST form, the body of a PUT, etc). It's kind of boring to |
|
83 start, but you can set it to a string and that will be turned into a |
|
84 file-like object. You can read the entire body with |
|
85 ``req.body``. |
|
86 |
|
87 .. code-block:: |
|
88 |
|
89 >>> req.body_file |
|
90 <cStringIO.StringI object at ...> |
|
91 >>> req.body |
|
92 '' |
|
93 >>> req.body = 'test' |
|
94 >>> req.body_file |
|
95 <cStringIO.StringI object at ...> |
|
96 >>> req.body |
|
97 'test' |
|
98 |
|
99 Method & URL |
|
100 ------------ |
|
101 |
|
102 All the normal parts of a request are also accessible through the |
|
103 request object: |
|
104 |
|
105 .. code-block:: |
|
106 |
|
107 >>> req.method |
|
108 'GET' |
|
109 >>> req.scheme |
|
110 'http' |
|
111 >>> req.script_name # The base of the URL |
|
112 '' |
|
113 >>> req.script_name = '/blog' # make it more interesting |
|
114 >>> req.path_info # The yet-to-be-consumed part of the URL |
|
115 '/article' |
|
116 >>> req.content_type # Content-Type of the request body |
|
117 '' |
|
118 >>> print req.remote_user # The authenticated user (there is none set) |
|
119 None |
|
120 >>> print req.remote_addr # The remote IP |
|
121 None |
|
122 >>> req.host |
|
123 'localhost:80' |
|
124 >>> req.host_url |
|
125 'http://localhost' |
|
126 >>> req.application_url |
|
127 'http://localhost/blog' |
|
128 >>> req.path_url |
|
129 'http://localhost/blog/article' |
|
130 >>> req.url |
|
131 'http://localhost/blog/article?id=1' |
|
132 >>> req.path |
|
133 '/blog/article' |
|
134 >>> req.path_qs |
|
135 '/blog/article?id=1' |
|
136 >>> req.query_string |
|
137 'id=1' |
|
138 |
|
139 You can make new URLs: |
|
140 |
|
141 .. code-block:: |
|
142 |
|
143 >>> req.relative_url('archive') |
|
144 'http://localhost/blog/archive' |
|
145 |
|
146 For parsing the URLs, it is often useful to deal with just the next |
|
147 path segment on PATH_INFO: |
|
148 |
|
149 .. code-block:: |
|
150 |
|
151 >>> req.path_info_peek() # Doesn't change request |
|
152 'article' |
|
153 >>> req.path_info_pop() # Does change request! |
|
154 'article' |
|
155 >>> req.script_name |
|
156 '/blog/article' |
|
157 >>> req.path_info |
|
158 '' |
|
159 |
|
160 Headers |
|
161 ------- |
|
162 |
|
163 All request headers are available through a dictionary-like object |
|
164 ``req.headers``. Keys are case-insensitive. |
|
165 |
|
166 .. code-block:: |
|
167 |
|
168 >>> req.headers['content-type'] = 'application/x-www-urlencoded' |
|
169 >>> req.headers |
|
170 {'Content-Length': '4', 'Content-Type': 'application/x-www-urlencoded', 'Host': 'localhost:80'} |
|
171 >>> req.environ['CONTENT_TYPE'] |
|
172 'application/x-www-urlencoded' |
|
173 |
|
174 Query & POST variables |
|
175 ---------------------- |
|
176 |
|
177 Requests can have variables in one of two locations: the query string |
|
178 (``?id=1``), or in the body of the request (generally a POST form). |
|
179 Note that even POST requests can have a query string, so both kinds of |
|
180 variables can exist at the same time. Also, a variable can show up |
|
181 more than once, as in ``?check=a&check=b``. |
|
182 |
|
183 For these variables WebOb uses a `MultiDict |
|
184 <class-webob.multidict.MultiDict.html>`_, which is basically a |
|
185 dictionary wrapper on a list of key/value pairs. It looks like a |
|
186 single-valued dictionary, but you can access all the values of a key |
|
187 with ``.getall(key)`` (which always returns a list, possibly an empty |
|
188 list). You also get all key/value pairs when using ``.items()`` and |
|
189 all values with ``.values()``. |
|
190 |
|
191 Some examples: |
|
192 |
|
193 .. code-block:: |
|
194 |
|
195 >>> req = Request.blank('/test?check=a&check=b&name=Bob') |
|
196 >>> req.GET |
|
197 MultiDict([('check', 'a'), ('check', 'b'), ('name', 'Bob')]) |
|
198 >>> req.GET['check'] |
|
199 'b' |
|
200 >>> req.GET.getall('check') |
|
201 ['a', 'b'] |
|
202 >>> req.GET.items() |
|
203 [('check', 'a'), ('check', 'b'), ('name', 'Bob')] |
|
204 |
|
205 We'll have to create a request body and change the method to get |
|
206 POST. Until we do that, the variables are boring: |
|
207 |
|
208 .. code-block:: |
|
209 |
|
210 >>> req.POST |
|
211 <NoVars: Not a POST request> |
|
212 >>> req.POST.items() # NoVars can be read like a dict, but not written |
|
213 [] |
|
214 >>> req.method = 'POST' |
|
215 >>> req.body = 'name=Joe&email=joe@example.com' |
|
216 >>> req.POST |
|
217 MultiDict([('name', 'Joe'), ('email', 'joe@example.com')]) |
|
218 >>> req.POST['name'] |
|
219 'Joe' |
|
220 |
|
221 Often you won't care where the variables come from. (Even if you care |
|
222 about the method, the location of the variables might not be |
|
223 important.) There is a dictionary called ``req.params`` that |
|
224 contains variables from both sources: |
|
225 |
|
226 .. code-block:: |
|
227 |
|
228 >>> req.params |
|
229 NestedMultiDict([('check', 'a'), ('check', 'b'), ('name', 'Bob'), ('name', 'Joe'), ('email', 'joe@example.com')]) |
|
230 >>> req.params['name'] |
|
231 'Bob' |
|
232 >>> req.params.getall('name') |
|
233 ['Bob', 'Joe'] |
|
234 >>> for name, value in req.params.items(): |
|
235 ... print '%s: %r' % (name, value) |
|
236 check: 'a' |
|
237 check: 'b' |
|
238 name: 'Bob' |
|
239 name: 'Joe' |
|
240 email: 'joe@example.com' |
|
241 |
|
242 Unicode Variables |
|
243 ~~~~~~~~~~~~~~~~~ |
|
244 |
|
245 Submissions are non-unicode (``str``) strings, unless some character |
|
246 set is indicated. A client can indicate the character set with |
|
247 ``Content-Type: application/x-www-form-urlencoded; charset=utf8``, but |
|
248 very few clients actually do this (sometimes XMLHttpRequest requests |
|
249 will do this, as JSON is always UTF8 even when a page is served with a |
|
250 different character set). You can force a charset, which will effect |
|
251 all the variables: |
|
252 |
|
253 .. code-block:: |
|
254 |
|
255 >>> req.charset = 'utf8' |
|
256 >>> req.GET |
|
257 UnicodeMultiDict([(u'check', u'a'), (u'check', u'b'), (u'name', u'Bob')]) |
|
258 |
|
259 If you always want ``str`` values, you can use ``req.str_GET`` |
|
260 and ``str_POST``. |
|
261 |
|
262 Cookies |
|
263 ------- |
|
264 |
|
265 Cookies are presented in a simple dictionary. Like other variables, |
|
266 they will be decoded into Unicode strings if you set the charset. |
|
267 |
|
268 .. code-block:: |
|
269 |
|
270 >>> req.headers['Cookie'] = 'test=value' |
|
271 >>> req.cookies |
|
272 UnicodeMultiDict([(u'test', u'value')]) |
|
273 >>> req.charset = None |
|
274 >>> req.cookies |
|
275 {'test': 'value'} |
|
276 |
|
277 Modifying the request |
|
278 --------------------- |
|
279 |
|
280 The headers are all modifiable, as are other environmental variables |
|
281 (like ``req.remote_user``, which maps to |
|
282 ``request.environ['REMOTE_USER']``). |
|
283 |
|
284 If you want to copy the request you can use ``req.copy()``; this |
|
285 copies the ``environ`` dictionary, and the request body from |
|
286 ``environ['wsgi.input']``. |
|
287 |
|
288 The method ``req.remove_conditional_headers(remove_encoding=True)`` |
|
289 can be used to remove headers that might result in a ``304 Not |
|
290 Modified`` response. If you are writing some intermediary it can be |
|
291 useful to avoid these headers. Also if ``remove_encoding`` is true |
|
292 (the default) then any ``Accept-Encoding`` header will be removed, |
|
293 which can result in gzipped responses. |
|
294 |
|
295 Header Getters |
|
296 -------------- |
|
297 |
|
298 In addition to ``req.headers``, there are attributes for most of the |
|
299 request headers defined by the HTTP 1.1 specification. These |
|
300 attributes often return parsed forms of the headers. |
|
301 |
|
302 Accept-* headers |
|
303 ~~~~~~~~~~~~~~~~ |
|
304 |
|
305 There are several request headers that tell the server what the client |
|
306 accepts. These are ``accept`` (the Content-Type that is accepted), |
|
307 ``accept_charset`` (the charset accepted), ``accept_encoding`` |
|
308 (the Content-Encoding, like gzip, that is accepted), and |
|
309 ``accept_language`` (generally the preferred language of the client). |
|
310 |
|
311 The objects returned support containment to test for acceptability. |
|
312 E.g.: |
|
313 |
|
314 .. code-block:: |
|
315 |
|
316 >>> 'text/html' in req.accept |
|
317 True |
|
318 |
|
319 Because no header means anything is potentially acceptable, this is |
|
320 returning True. We can set it to see more interesting behavior (the |
|
321 example means that ``text/html`` is okay, but |
|
322 ``application/xhtml+xml`` is preferred): |
|
323 |
|
324 .. code-block:: |
|
325 |
|
326 >>> req.accept = 'text/html;q=0.5, application/xhtml+xml;q=1' |
|
327 >>> req.accept |
|
328 <MIMEAccept at ... Accept: text/html;q=0.5, application/xhtml+xml> |
|
329 >>> 'text/html' in req.accept |
|
330 True |
|
331 |
|
332 There's three methods for different strategies of finding a match. |
|
333 First, when you trust the server's preference over the client (a good |
|
334 idea for Accept): |
|
335 |
|
336 .. code-block:: |
|
337 |
|
338 >>> req.accept.first_match(['text/html', 'application/xhtml+xml']) |
|
339 'text/html' |
|
340 |
|
341 Because ``text/html`` is at least *somewhat* acceptible, it is |
|
342 returned, even if the client says it prefers |
|
343 ``application/xhtml+xml``. If we trust the client more: |
|
344 |
|
345 .. code-block:: |
|
346 |
|
347 >>> req.accept.best_match(['text/html', 'application/xhtml+xml']) |
|
348 'application/xhtml+xml' |
|
349 |
|
350 If we just want to know everything the client prefers, in the order it |
|
351 is preferred: |
|
352 |
|
353 .. code-block:: |
|
354 |
|
355 >>> req.accept.best_matches() |
|
356 ['application/xhtml+xml', 'text/html'] |
|
357 |
|
358 For languages you'll often have a "fallback" language. E.g., if there's |
|
359 nothing better then use ``en-US`` (and if ``en-US`` is okay, ignore |
|
360 any less preferrable languages): |
|
361 |
|
362 .. code-block:: |
|
363 |
|
364 >>> req.accept_language = 'es, pt-BR' |
|
365 >>> req.accept_language.best_matches('en-US') |
|
366 ['es', 'pt-BR', 'en-US'] |
|
367 >>> req.accept_language.best_matches('es') |
|
368 ['es'] |
|
369 |
|
370 Conditional Requests |
|
371 ~~~~~~~~~~~~~~~~~~~~ |
|
372 |
|
373 There a number of ways to make a conditional request. A conditional |
|
374 request is made when the client has a document, but it is not sure if |
|
375 the document is up to date. If it is not, it wants a new version. If |
|
376 the document is up to date then it doesn't want to waste the |
|
377 bandwidth, and expects a ``304 Not Modified`` response. |
|
378 |
|
379 ETags are generally the best technique for these kinds of requests; |
|
380 this is an opaque string that indicates the identity of the object. |
|
381 For instance, it's common to use the mtime (last modified) of the file, |
|
382 plus the number of bytes, and maybe a hash of the filename (if there's |
|
383 a possibility that the same URL could point to two different |
|
384 server-side filenames based on other variables). To test if a 304 |
|
385 response is appropriate, you can use: |
|
386 |
|
387 .. code-block:: |
|
388 |
|
389 >>> server_token = 'opaque-token' |
|
390 >>> server_token in req.if_none_match # You shouldn't return 304 |
|
391 False |
|
392 >>> req.if_none_match = server_token |
|
393 >>> req.if_none_match |
|
394 <ETag opaque-token> |
|
395 >>> server_token in req.if_none_match # You *should* return 304 |
|
396 True |
|
397 |
|
398 For date-based comparisons If-Modified-Since is used: |
|
399 |
|
400 .. code-block:: |
|
401 |
|
402 >>> from webob import UTC |
|
403 >>> from datetime import datetime |
|
404 >>> req.if_modified_since = datetime(2006, 1, 1, 12, 0, tzinfo=UTC) |
|
405 >>> req.headers['If-Modified-Since'] |
|
406 'Sun, 01 Jan 2006 12:00:00 GMT' |
|
407 >>> server_modified = datetime(2005, 1, 1, 12, 0, tzinfo=UTC) |
|
408 >>> req.if_modified_since and req.if_modified_since >= server_modified |
|
409 True |
|
410 |
|
411 For range requests there are two important headers, If-Range (which is |
|
412 form of conditional request) and Range (which requests a range). If |
|
413 the If-Range header fails to match then the full response (not a |
|
414 range) should be returned: |
|
415 |
|
416 .. code-block:: |
|
417 |
|
418 >>> req.if_range |
|
419 <Empty If-Range> |
|
420 >>> req.if_range.match(etag='some-etag', last_modified=datetime(2005, 1, 1, 12, 0)) |
|
421 True |
|
422 >>> req.if_range = 'opaque-etag' |
|
423 >>> req.if_range.match(etag='other-etag') |
|
424 False |
|
425 >>> req.if_range.match(etag='opaque-etag') |
|
426 True |
|
427 |
|
428 You can also pass in a response object with: |
|
429 |
|
430 .. code-block:: |
|
431 |
|
432 >>> from webob import Response |
|
433 >>> res = Response(etag='opaque-etag') |
|
434 >>> req.if_range.match_response(res) |
|
435 True |
|
436 |
|
437 To get the range information: |
|
438 |
|
439 >>> req.range = 'bytes=0-100' |
|
440 >>> req.range |
|
441 <Range ranges=(0, 99)> |
|
442 >>> cr = req.range.content_range(length=1000) |
|
443 >>> cr.start, cr.stop, cr.length |
|
444 (0, 99, 1000) |
|
445 |
|
446 Note that the range headers use *inclusive* ranges (the last byte |
|
447 indexed is included), where Python always uses a range where the last |
|
448 index is excluded from the range. The ``.stop`` index is in the |
|
449 Python form. |
|
450 |
|
451 Another kind of conditional request is a request (typically PUT) that |
|
452 includes If-Match or If-Unmodified-Since. In this case you are saying |
|
453 "here is an update to a resource, but don't apply it if someone else |
|
454 has done something since I last got the resource". If-Match means "do |
|
455 this if the current ETag matches the ETag I'm giving". |
|
456 If-Unmodified-Since means "do this if the resource has remained |
|
457 unchanged". |
|
458 |
|
459 .. code-block:: |
|
460 |
|
461 >>> server_token in req.if_match # No If-Match means everything is ok |
|
462 True |
|
463 >>> req.if_match = server_token |
|
464 >>> server_token in req.if_match # Still OK |
|
465 True |
|
466 >>> req.if_match = 'other-token' |
|
467 >>> # Not OK, should return 412 Precondition Failed: |
|
468 >>> server_token in req.if_match |
|
469 False |
|
470 |
|
471 For more on this kind of conditional request, see `Detecting the Lost |
|
472 Update Problem Using Unreserved Checkout |
|
473 <http://www.w3.org/1999/04/Editing/>`_. |
|
474 |
|
475 Calling WSGI Applications |
|
476 ------------------------- |
|
477 |
|
478 The request object can be used to make handy subrequests or test |
|
479 requests against WSGI applications. If you want to make subrequests, |
|
480 you should copy the request (with ``req.copy()``) before sending it to |
|
481 multiple applications, since applications might modify the request |
|
482 when they are run. |
|
483 |
|
484 There's two forms of the subrequest. The more primitive form is |
|
485 this: |
|
486 |
|
487 .. code-block:: |
|
488 |
|
489 >>> req = Request.blank('/') |
|
490 >>> def wsgi_app(environ, start_response): |
|
491 ... start_response('200 OK', [('Content-type', 'text/plain')]) |
|
492 ... return ['Hi!'] |
|
493 >>> req.call_application(wsgi_app) |
|
494 ('200 OK', [('Content-type', 'text/plain')], ['Hi!']) |
|
495 |
|
496 Note it returns ``(status_string, header_list, app_iter)``. If |
|
497 ``app_iter.close()`` exists, it is your responsibility to call it. |
|
498 |
|
499 A handier response can be had with: |
|
500 |
|
501 .. code-block:: |
|
502 |
|
503 >>> res = req.get_response(wsgi_app) |
|
504 >>> res |
|
505 <Response ... 200 OK> |
|
506 >>> res.status |
|
507 '200 OK' |
|
508 >>> res.headers |
|
509 HeaderDict([('Content-type', 'text/plain')]) |
|
510 >>> res.body |
|
511 'Hi!' |
|
512 |
|
513 You can learn more about this response object in the Response_ section. |
|
514 |
|
515 Thread-local Request Wrappers |
|
516 ----------------------------- |
|
517 |
|
518 You can also give the ``Request`` object a function to get the |
|
519 environment. This can be used to make a single global request object |
|
520 dynamic. An example: |
|
521 |
|
522 .. code-block:: |
|
523 |
|
524 >>> import threading |
|
525 >>> import webob |
|
526 >>> environments = threading.local() |
|
527 >>> def get_environ(): |
|
528 ... return environments.environ |
|
529 >>> def set_thread_environ(environ): |
|
530 ... environments.environ = environ |
|
531 >>> request = webob.Request(environ_getter=get_environ) |
|
532 >>> set_thread_environ({'SCRIPT_NAME': '/test'}) |
|
533 >>> request.script_name |
|
534 '/test' |
|
535 |
|
536 Ad-Hoc Attributes |
|
537 ----------------- |
|
538 |
|
539 You can assign attributes to your request objects. They will all go |
|
540 in ``environ['webob.adhoc_attrs']`` (a dictionary). |
|
541 |
|
542 .. code-block:: |
|
543 |
|
544 >>> req = Request.blank('/') |
|
545 >>> req.some_attr = 'blah blah blah' |
|
546 >>> new_req = Request(req.environ) |
|
547 >>> new_req.some_attr |
|
548 'blah blah blah' |
|
549 >>> req.environ['webob.adhoc_attrs'] |
|
550 {'some_attr': 'blah blah blah'} |
|
551 |
|
552 Response |
|
553 ======== |
|
554 |
|
555 The ``webob.Response`` object contains everything necessary to make a |
|
556 WSGI response. Instances of it are in fact WSGI applications, but it |
|
557 can also represent the result of calling a WSGI application (as noted |
|
558 in `Calling WSGI Applications`_). It can also be a way of |
|
559 accumulating a response in your WSGI application. |
|
560 |
|
561 A WSGI response is made up of a status (like ``200 OK``), a list of |
|
562 headers, and a body (or iterator that will produce a body). |
|
563 |
|
564 Core Attributes |
|
565 --------------- |
|
566 |
|
567 The core attributes are unsurprising: |
|
568 |
|
569 .. code-block:: |
|
570 |
|
571 >>> from webob import Response |
|
572 >>> res = Response() |
|
573 >>> res.status |
|
574 '200 OK' |
|
575 >>> res.headerlist |
|
576 [('Content-Length', '0')] |
|
577 >>> res.body |
|
578 '' |
|
579 |
|
580 You can set any of these attributes, e.g.: |
|
581 |
|
582 .. code-block:: |
|
583 |
|
584 >>> res.status = 404 |
|
585 >>> res.status |
|
586 '404 Not Found' |
|
587 >>> res.status_int |
|
588 404 |
|
589 >>> res.headerlist = [('Content-type', 'text/html')] |
|
590 >>> res.body = 'test' |
|
591 >>> print res |
|
592 404 Not Found |
|
593 Content-type: text/html |
|
594 Content-Length: 4 |
|
595 <BLANKLINE> |
|
596 test |
|
597 >>> res.body = u"test" |
|
598 Traceback (most recent call last): |
|
599 ... |
|
600 TypeError: You cannot set Response.body to a unicode object (use Response.unicode_body) |
|
601 >>> res.unicode_body = u"test" |
|
602 Traceback (most recent call last): |
|
603 ... |
|
604 AttributeError: You cannot access Response.unicode_body unless charset is set |
|
605 >>> res.charset = 'utf8' |
|
606 >>> res.unicode_body = u"test" |
|
607 >>> res.body |
|
608 'test' |
|
609 |
|
610 You can set any attribute with the constructor, like |
|
611 ``Response(charset='utf8')`` |
|
612 |
|
613 Headers |
|
614 ------- |
|
615 |
|
616 In addition to ``res.headerlist``, there is dictionary-like view on |
|
617 the list in ``res.headers``: |
|
618 |
|
619 .. code-block:: |
|
620 |
|
621 >>> res.headers |
|
622 HeaderDict([('content-type', 'text/html; charset=utf8'), ('Content-Length', '4')]) |
|
623 |
|
624 This is case-insensitive. It can support multiple values for a key, |
|
625 though only if you use ``res.headers.add(key, value)`` or read them |
|
626 with ``res.headers.getall(key)``. |
|
627 |
|
628 Body & app_iter |
|
629 --------------- |
|
630 |
|
631 The ``res.body`` attribute represents the entire body of the request |
|
632 as a single string (not unicode, though you can set it to unicode if |
|
633 you have a charset defined). There is also a ``res.app_iter`` |
|
634 attribute that reprsents the body as an iterator. WSGI applications |
|
635 return these ``app_iter`` iterators instead of strings, and sometimes |
|
636 it can be problematic to load the entire iterator at once (for |
|
637 instance, if it returns the contents of a very large file). Generally |
|
638 it is not a problem, and often the iterator is something simple like a |
|
639 one-item list containing a string with the entire body. |
|
640 |
|
641 If you set the body then Content-Length will also be set, and an |
|
642 ``res.app_iter`` will be created for you. If you set ``res.app_iter`` |
|
643 then Content-Length will be cleared, but it won't be set for you. |
|
644 |
|
645 There is also a file-like object you can access, which will update the |
|
646 app_iter in-place (turning the app_iter into a list if necessary): |
|
647 |
|
648 .. code-block:: |
|
649 |
|
650 >>> res = Response(content_type='text/plain') |
|
651 >>> f = res.body_file |
|
652 >>> f.write('hey') |
|
653 >>> f.write(u'test') |
|
654 Traceback (most recent call last): |
|
655 . . . |
|
656 TypeError: You can only write unicode to Response.body_file if charset has been set |
|
657 >>> f.encoding |
|
658 >>> res.charset = 'utf8' |
|
659 >>> f.encoding |
|
660 'utf8' |
|
661 >>> f.write(u'test') |
|
662 >>> res.app_iter |
|
663 ['hey', 'test'] |
|
664 >>> res.body |
|
665 'heytest' |
|
666 |
|
667 Header Getters |
|
668 -------------- |
|
669 |
|
670 Like Request, HTTP response headers are also available as individual |
|
671 properties. These represent parsed forms of the headers. |
|
672 |
|
673 Content-Type is a special case, as the type and the charset are |
|
674 handled through two separate properties: |
|
675 |
|
676 .. code-block:: |
|
677 |
|
678 >>> res = Response() |
|
679 >>> res.content_type = 'text/html' |
|
680 >>> res.charset = 'utf8' |
|
681 >>> res.content_type |
|
682 'text/html' |
|
683 >>> res.headers['content-type'] |
|
684 'text/html; charset=utf8' |
|
685 >>> res.content_type = 'application/atom+xml' |
|
686 >>> res.content_type_params |
|
687 {'charset': 'utf8'} |
|
688 >>> res.content_type_params = {'type': 'entry', 'charset': 'utf8'} |
|
689 >>> res.headers['content-type'] |
|
690 'application/atom+xml; charset=utf8; type=entry' |
|
691 |
|
692 Other headers: |
|
693 |
|
694 .. code-block:: |
|
695 |
|
696 >>> # Used with a redirect: |
|
697 >>> res.location = 'http://localhost/foo' |
|
698 |
|
699 >>> # Indicates that the server accepts Range requests: |
|
700 >>> res.accept_ranges = 'bytes' |
|
701 |
|
702 >>> # Used by caching proxies to tell the client how old the |
|
703 >>> # response is: |
|
704 >>> res.age = 120 |
|
705 |
|
706 >>> # Show what methods the client can do; typically used in |
|
707 >>> # a 405 Method Not Allowed response: |
|
708 >>> res.allow = ['GET', 'PUT'] |
|
709 |
|
710 >>> # Set the cache-control header: |
|
711 >>> res.cache_control.max_age = 360 |
|
712 >>> res.cache_control.no_transform = True |
|
713 |
|
714 >>> # Used if you had gzipped the body: |
|
715 >>> res.content_encoding = 'gzip' |
|
716 |
|
717 >>> # What language(s) are in the content: |
|
718 >>> res.content_language = ['en'] |
|
719 |
|
720 >>> # Seldom used header that tells the client where the content |
|
721 >>> # is from: |
|
722 >>> res.content_location = 'http://localhost/foo' |
|
723 |
|
724 >>> # Seldom used header that gives a hash of the body: |
|
725 >>> res.content_md5 = 'big-hash' |
|
726 |
|
727 >>> # Means we are serving bytes 0-500 inclusive, out of 1000 bytes total: |
|
728 >>> # you can also use the range setter shown earlier |
|
729 >>> res.content_range = (0, 499, 1000) |
|
730 |
|
731 >>> # The length of the content; set automatically if you set |
|
732 >>> # res.body: |
|
733 >>> res.content_length = 4 |
|
734 |
|
735 >>> # Used to indicate the current date as the server understands |
|
736 >>> # it: |
|
737 >>> res.date = datetime.now() |
|
738 |
|
739 >>> # The etag: |
|
740 >>> res.etag = 'opaque-token' |
|
741 >>> # You can generate it from the body too: |
|
742 >>> res.md5_etag() |
|
743 >>> res.etag |
|
744 '1B2M2Y8AsgTpgAmY7PhCfg' |
|
745 |
|
746 >>> # When this page should expire from a cache (Cache-Control |
|
747 >>> # often works better): |
|
748 >>> import time |
|
749 >>> res.expires = time.time() + 60*60 # 1 hour |
|
750 |
|
751 >>> # When this was last modified, of course: |
|
752 >>> res.last_modified = datetime(2007, 1, 1, 12, 0, tzinfo=UTC) |
|
753 |
|
754 >>> # Used with 503 Service Unavailable to hint the client when to |
|
755 >>> # try again: |
|
756 >>> res.retry_after = 160 |
|
757 |
|
758 >>> # Indicate the server software: |
|
759 >>> res.server = 'WebOb/1.0' |
|
760 |
|
761 >>> # Give a list of headers that the cache should vary on: |
|
762 >>> res.vary = ['Cookie'] |
|
763 |
|
764 Note in each case you can general set the header to a string to avoid |
|
765 any parsing, and set it to None to remove the header (or do something |
|
766 like ``del res.vary``). |
|
767 |
|
768 In the case of date-related headers you can set the value to a |
|
769 ``datetime`` instance (ideally with a UTC timezone), a time tuple, an |
|
770 integer timestamp, or a properly-formatted string. |
|
771 |
|
772 After setting all these headers, here's the result: |
|
773 |
|
774 .. code-block:: |
|
775 |
|
776 >>> for name, value in res.headerlist: |
|
777 ... print '%s: %s' % (name, value) |
|
778 content-type: application/atom+xml; charset=utf8; type=entry |
|
779 location: http://localhost/foo |
|
780 Accept-Ranges: bytes |
|
781 Age: 120 |
|
782 Allow: GET, PUT |
|
783 Cache-Control: max-age=360, no-transform |
|
784 Content-Encoding: gzip |
|
785 Content-Language: en |
|
786 Content-Location: http://localhost/foo |
|
787 Content-MD5: big-hash |
|
788 Content-Range: bytes 0-500/1000 |
|
789 Content-Length: 4 |
|
790 Date: ... GMT |
|
791 ETag: ... |
|
792 Expires: ... GMT |
|
793 Last-Modified: Mon, 01 Jan 2007 12:00:00 GMT |
|
794 Retry-After: 160 |
|
795 Server: WebOb/1.0 |
|
796 Vary: Cookie |
|
797 |
|
798 You can also set Cache-Control related attributes with |
|
799 ``req.cache_expires(seconds, **attrs)``, like: |
|
800 |
|
801 .. code-block:: |
|
802 |
|
803 >>> res = Response() |
|
804 >>> res.cache_expires(10) |
|
805 >>> res.headers['Cache-Control'] |
|
806 'max-age=10' |
|
807 >>> res.cache_expires(0) |
|
808 >>> res.headers['Cache-Control'] |
|
809 'max-age=0, must-revalidate, no-cache, no-store' |
|
810 >>> res.headers['Expires'] |
|
811 '... GMT' |
|
812 |
|
813 You can also use the `timedelta |
|
814 <http://python.org/doc/current/lib/datetime-timedelta.html>`_ |
|
815 constants defined, e.g.: |
|
816 |
|
817 .. code-block:: |
|
818 |
|
819 >>> from webob import * |
|
820 >>> res = Response() |
|
821 >>> res.cache_expires(2*day+4*hour) |
|
822 >>> res.headers['Cache-Control'] |
|
823 'max-age=187200' |
|
824 |
|
825 Cookies |
|
826 ------- |
|
827 |
|
828 Cookies (and the Set-Cookie header) are handled with a couple |
|
829 methods. Most importantly: |
|
830 |
|
831 .. code-block:: |
|
832 |
|
833 >>> res.set_cookie('key', 'value', max_age=360, path='/', |
|
834 ... domain='example.org', secure=True) |
|
835 >>> res.headers['Set-Cookie'] |
|
836 'key=value; Domain=example.org; Max-Age=360; Path=/; secure;' |
|
837 >>> # To delete a cookie previously set in the client: |
|
838 >>> res.delete_cookie('bad_cookie') |
|
839 >>> res.headers['Set-Cookie'] |
|
840 'bad_cookie=; Max-Age=0; Path=/;' |
|
841 |
|
842 The only other real method of note (note that this does *not* delete |
|
843 the cookie from clients, only from the response object): |
|
844 |
|
845 .. code-block:: |
|
846 |
|
847 >>> res.unset_cookie('key') |
|
848 >>> res.unset_cookie('bad_cookie') |
|
849 >>> print res.headers.get('Set-Cookie') |
|
850 None |
|
851 |
|
852 Binding a Request |
|
853 ----------------- |
|
854 |
|
855 You can bind a request (or request WSGI environ) to the response |
|
856 object. This is available through ``res.request`` or |
|
857 ``res.environ``. This is currently only used in setting |
|
858 ``res.location``, to make the location absolute if necessary. |
|
859 |
|
860 Response as a WSGI application |
|
861 ------------------------------ |
|
862 |
|
863 A response is a WSGI application, in that you can do: |
|
864 |
|
865 .. code-block:: |
|
866 |
|
867 >>> req = Request.blank('/') |
|
868 >>> status, headers, app_iter = req.call_application(res) |
|
869 |
|
870 A possible pattern for your application might be: |
|
871 |
|
872 .. code-block:: |
|
873 |
|
874 >>> def my_app(environ, start_response): |
|
875 ... req = Request(environ) |
|
876 ... res = Response() |
|
877 ... res.content_type = 'text/plain' |
|
878 ... parts = [] |
|
879 ... for name, value in sorted(req.environ.items()): |
|
880 ... parts.append('%s: %r' % (name, value)) |
|
881 ... res.body = '\n'.join(parts) |
|
882 ... return res(environ, start_response) |
|
883 >>> req = Request.blank('/') |
|
884 >>> res = req.get_response(my_app) |
|
885 >>> print res |
|
886 200 OK |
|
887 content-type: text/plain |
|
888 Content-Length: 394 |
|
889 <BLANKLINE> |
|
890 HTTP_HOST: 'localhost:80' |
|
891 PATH_INFO: '/' |
|
892 QUERY_STRING: '' |
|
893 REQUEST_METHOD: 'GET' |
|
894 SCRIPT_NAME: '' |
|
895 SERVER_NAME: 'localhost' |
|
896 SERVER_PORT: '80' |
|
897 SERVER_PROTOCOL: 'HTTP/1.0' |
|
898 wsgi.errors: <open file '<stderr>', mode 'w' at ...> |
|
899 wsgi.input: <cStringIO.StringI object at ...> |
|
900 wsgi.multiprocess: False |
|
901 wsgi.multithread: False |
|
902 wsgi.run_once: False |
|
903 wsgi.url_scheme: 'http' |
|
904 wsgi.version: (1, 0) |
|
905 |
|
906 Exceptions |
|
907 ========== |
|
908 |
|
909 In addition to Request and Response objects, there are a set of Python |
|
910 exceptions for different HTTP responses (3xx, 4xx, 5xx codes). |
|
911 |
|
912 These provide a simple way to provide these non-200 response. A very |
|
913 simple body is provided. |
|
914 |
|
915 .. code-block:: |
|
916 |
|
917 >>> from webob.exc import * |
|
918 >>> exc = HTTPTemporaryRedirect(location='foo') |
|
919 >>> req = Request.blank('/path/to/something') |
|
920 >>> print str(req.get_response(exc)).strip() |
|
921 307 Temporary Redirect |
|
922 content-type: text/html |
|
923 location: http://localhost/path/to/foo |
|
924 Content-Length: 126 |
|
925 <BLANKLINE> |
|
926 307 Temporary Redirect |
|
927 <BLANKLINE> |
|
928 The resource has been moved to http://localhost/path/to/foo; you should be redirected automatically. |
|
929 |
|
930 Note that only if there's an ``Accept: text/html`` header in the |
|
931 request will an HTML response be given: |
|
932 |
|
933 .. code-block:: |
|
934 |
|
935 >>> req.accept += 'text/html' |
|
936 >>> print str(req.get_response(exc)).strip() |
|
937 307 Temporary Redirect |
|
938 content-type: text/html |
|
939 location: http://localhost/path/to/foo |
|
940 Content-Length: 270 |
|
941 <BLANKLINE> |
|
942 <html> |
|
943 <head> |
|
944 <title>307 Temporary Redirect</title> |
|
945 </head> |
|
946 <body> |
|
947 <h1>307 Temporary Redirect</h1> |
|
948 The resource has been moved to <a href="http://localhost/path/to/foo">http://localhost/path/to/foo</a>; |
|
949 you should be redirected automatically. |
|
950 <BLANKLINE> |
|
951 <BLANKLINE> |
|
952 </body> |
|
953 </html> |
|
954 |
|
955 |
|
956 This is taken from `paste.httpexceptions |
|
957 <http://pythonpaste.org/module-paste.httpexceptions.html>`_, and if |
|
958 you have Paste installed then these exceptions will be subclasses of |
|
959 the Paste exceptions. |
|
960 |
|
961 Note that on Python 2.4 and before, new-style classes could not be |
|
962 used as exceptions. ``Response`` objects must be new-style classes, |
|
963 so this causes a bit of a conflict. The base class |
|
964 ``webob.exc.HTTPException`` *is* an exception, so you can catch that, |
|
965 and it *is* a WSGI application. But you may not be able to use |
|
966 Response methods. You can always get ``obj.exception`` to get an |
|
967 exception that you can raise, and ``obj.wsgi_response`` to get the |
|
968 ``Response`` object that you can use. |
|
969 |
|
970 Conditional WSGI Application |
|
971 ---------------------------- |
|
972 |
|
973 The Response object can handle your conditional responses for you, |
|
974 checking If-None-Match, If-Modified-Since, and Range/If-Range. |
|
975 |
|
976 To enable this you must create the response like |
|
977 ``Response(conditional_request=True)``, or make a subclass like: |
|
978 |
|
979 .. code-block:: |
|
980 |
|
981 >>> class AppResponse(Response): |
|
982 ... default_content_type = 'text/html' |
|
983 ... default_conditional_response = True |
|
984 >>> res = AppResponse(body='0123456789', |
|
985 ... last_modified=datetime(2005, 1, 1, 12, 0, tzinfo=UTC)) |
|
986 >>> req = Request.blank('/') |
|
987 >>> req.if_modified_since = datetime(2006, 1, 1, 12, 0, tzinfo=UTC) |
|
988 >>> req.get_response(res) |
|
989 <Response ... 304 Not Modified> |
|
990 >>> del req.if_modified_since |
|
991 >>> res.etag = 'opaque-tag' |
|
992 >>> req.if_none_match = 'opaque-tag' |
|
993 >>> req.get_response(res) |
|
994 <Response ... 304 Not Modified> |
|
995 >>> del req.if_none_match |
|
996 >>> req.range = (1, 5) |
|
997 >>> result = req.get_response(res) |
|
998 >>> result.headers['content-range'] |
|
999 'bytes 1-6/10' |
|
1000 >>> result.body |
|
1001 '1234' |