summaryrefslogtreecommitdiff
path: root/docs/reference.txt
blob: b12ec40f7973bd5d8f3577a6981127ffa99e5edf (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
WebOb Reference
+++++++++++++++

.. contents::

.. comment:

    >>> from doctest import ELLIPSIS

Introduction
============

This document covers all the details of the Request and Response
objects.  It is written to be testable with `doctest
<http://python.org/doc/current/lib/module-doctest.html>`_ -- this
effects the flavor of the documentation, perhaps to its detriment.
But it also means you can feel confident that the documentation is
correct.

This is a somewhat different approach to reference documentation
compared to the extracted documentation for the `request
<class-webob.Request.html>`_ and `response
<class-webob.Response.html>`_.

Request
=======

The primary object in WebOb is ``webob.Request``, a wrapper around a
`WSGI environment <http://www.python.org/dev/peps/pep-0333/>`_.

The basic way you create a request object is simple enough:

.. code-block:: python

   >>> from webob import Request
   >>> environ = {'wsgi.url_scheme': 'http', ...}  #doctest: +SKIP
   >>> req = Request(environ)                      #doctest: +SKIP

(Note that the WSGI environment is a dictionary with a dozen required
keys, so it's a bit lengthly to show a complete example of what it
would look like -- usually your WSGI server will create it.)

The request object *wraps* the environment; it has very little
internal state of its own.  Instead attributes you access read and
write to the environment dictionary.

You don't have to understand the details of WSGI to use this library;
this library handles those details for you.  You also don't have to
use this exclusively of other libraries.  If those other libraries
also keep their state in the environment, multiple wrappers can
coexist.  Examples of libraries that can coexist include
`paste.wsgiwrappers.Request
<http://pythonpaste.org/class-paste.wsgiwrappers.WSGIRequest.html>`_
(used by Pylons) and `yaro.Request
<http://lukearno.com/projects/yaro/>`_.

The WSGI environment has a number of required variables.  To make it
easier to test and play around with, the ``Request`` class has a
constructor that will fill in a minimal environment:

.. code-block:: python

   >>> req = Request.blank('/article?id=1')
   >>> from pprint import pprint
   >>> pprint(req.environ)
   {'HTTP_HOST': 'localhost:80',
    'PATH_INFO': '/article',
    'QUERY_STRING': 'id=1',
    'REQUEST_METHOD': 'GET',
    'SCRIPT_NAME': '',
    'SERVER_NAME': 'localhost',
    'SERVER_PORT': '80',
    'SERVER_PROTOCOL': 'HTTP/1.0',
    'wsgi.errors': <open file '<stderr>', mode 'w' at ...>,
    'wsgi.input': <...IO... object at ...>,
    'wsgi.multiprocess': False,
    'wsgi.multithread': False,
    'wsgi.run_once': False,
    'wsgi.url_scheme': 'http',
    'wsgi.version': (1, 0)}

Request Body
------------

``req.body`` is a file-like object that gives the body of the request
(e.g., a POST form, the body of a PUT, etc).  It's kind of boring to
start, but you can set it to a string and that will be turned into a
file-like object.  You can read the entire body with
``req.body``.

.. code-block:: python

    >>> hasattr(req.body_file, 'read')
    True
    >>> req.body
    ''
    >>> req.body = 'test'
    >>> hasattr(req.body_file, 'read')
    True
    >>> req.body
    'test'

Method & URL
------------

All the normal parts of a request are also accessible through the
request object:

.. code-block:: python

    >>> req.method
    'GET'
    >>> req.scheme
    'http'
    >>> req.script_name  # The base of the URL
    ''
    >>> req.script_name = '/blog' # make it more interesting
    >>> req.path_info    # The yet-to-be-consumed part of the URL
    '/article'
    >>> req.content_type # Content-Type of the request body
    ''
    >>> print req.remote_user  # The authenticated user (there is none set)
    None
    >>> print req.remote_addr  # The remote IP
    None
    >>> req.host
    'localhost:80'
    >>> req.host_url
    'http://localhost'
    >>> req.application_url
    'http://localhost/blog'
    >>> req.path_url
    'http://localhost/blog/article'
    >>> req.url
    'http://localhost/blog/article?id=1'
    >>> req.path
    '/blog/article'
    >>> req.path_qs
    '/blog/article?id=1'
    >>> req.query_string
    'id=1'

You can make new URLs:

.. code-block:: python

    >>> req.relative_url('archive')
    'http://localhost/blog/archive'

For parsing the URLs, it is often useful to deal with just the next
path segment on PATH_INFO:

.. code-block:: python

    >>> req.path_info_peek() # Doesn't change request
    'article'
    >>> req.path_info_pop()  # Does change request!
    'article'
    >>> req.script_name
    '/blog/article'
    >>> req.path_info
    ''

Headers
-------

All request headers are available through a dictionary-like object
``req.headers``.  Keys are case-insensitive.

.. code-block:: python

    >>> req.headers['Content-Type'] = 'application/x-www-urlencoded'
    >>> sorted(req.headers.items())
    [('Content-Length', '4'), ('Content-Type', 'application/x-www-urlencoded'), ('Host', 'localhost:80')]
    >>> req.environ['CONTENT_TYPE']
    'application/x-www-urlencoded'

Query & POST variables
----------------------

Requests can have variables in one of two locations: the query string
(``?id=1``), or in the body of the request (generally a POST form).
Note that even POST requests can have a query string, so both kinds of
variables can exist at the same time.  Also, a variable can show up
more than once, as in ``?check=a&check=b``.

For these variables WebOb uses a `MultiDict
<class-webob.multidict.MultiDict.html>`_, which is basically a
dictionary wrapper on a list of key/value pairs.  It looks like a
single-valued dictionary, but you can access all the values of a key
with ``.getall(key)`` (which always returns a list, possibly an empty
list).  You also get all key/value pairs when using ``.items()`` and
all values with ``.values()``.

Some examples:

.. code-block:: python

    >>> req = Request.blank('/test?check=a&check=b&name=Bob')
    >>> req.str_GET
    GET([('check', 'a'), ('check', 'b'), ('name', 'Bob')])
    >>> req.str_GET['check']
    'b'
    >>> req.str_GET.getall('check')
    ['a', 'b']
    >>> req.str_GET.items()
    [('check', 'a'), ('check', 'b'), ('name', 'Bob')]

We'll have to create a request body and change the method to get
POST.  Until we do that, the variables are boring:

.. code-block:: python

    >>> req.str_POST
    <NoVars: Not a form request>
    >>> req.str_POST.items()  # NoVars can be read like a dict, but not written
    []
    >>> req.method = 'POST'
    >>> req.body = 'name=Joe&email=joe@example.com'
    >>> req.str_POST
    MultiDict([('name', 'Joe'), ('email', 'joe@example.com')])
    >>> req.str_POST['name']
    'Joe'

Often you won't care where the variables come from.  (Even if you care
about the method, the location of the variables might not be
important.)  There is a dictionary called ``req.params`` that
contains variables from both sources:

.. code-block:: python

    >>> req.str_params
    NestedMultiDict([('check', 'a'), ('check', 'b'), ('name', 'Bob'), ('name', 'Joe'), ('email', 'joe@example.com')])
    >>> req.str_params['name']
    'Bob'
    >>> req.str_params.getall('name')
    ['Bob', 'Joe']
    >>> for name, value in req.str_params.items():
    ...     print '%s: %r' % (name, value)
    check: 'a'
    check: 'b'
    name: 'Bob'
    name: 'Joe'
    email: 'joe@example.com'

The ``POST`` and ``GET`` nomenclature is historical -- ``req.GET`` can
be used for non-GET requests to access query parameters, and
``req.POST`` can also be used for PUT requests with the appropriate
Content-Type.

    >>> req = Request.blank('/test?check=a&check=b&name=Bob')
    >>> req.method = 'PUT'
    >>> req.body = body = 'var1=value1&var2=value2&rep=1&rep=2'
    >>> req.environ['CONTENT_LENGTH'] = str(len(req.body))
    >>> req.environ['CONTENT_TYPE'] = 'application/x-www-form-urlencoded'
    >>> req.str_GET
    GET([('check', 'a'), ('check', 'b'), ('name', 'Bob')])
    >>> req.str_POST
    MultiDict([('var1', 'value1'), ('var2', 'value2'), ('rep', '1'), ('rep', '2')])

Unicode Variables
~~~~~~~~~~~~~~~~~

Submissions are non-unicode (``str``) strings, unless some character
set is indicated.  A client can indicate the character set with
``Content-Type: application/x-www-form-urlencoded; charset=utf8``, but
very few clients actually do this (sometimes XMLHttpRequest requests
will do this, as JSON is always UTF8 even when a page is served with a
different character set).  You can force a charset, which will effect
all the variables:

.. code-block:: python

    >>> req.charset = 'utf8'
    >>> req.GET
    UnicodeMultiDict([('check', u'a'), ('check', u'b'), ('name', u'Bob')])

If you always want ``str`` values, you can use ``req.str_GET``
and ``str_POST``.

Cookies
-------

Cookies are presented in a simple dictionary.  Like other variables,
they will be decoded into Unicode strings if you set the charset.

.. code-block:: python

    >>> req.headers['Cookie'] = 'test=value'
    >>> req.cookies
    UnicodeMultiDict([('test', u'value')])
    >>> req.charset = None
    >>> req.str_cookies
    {'test': 'value'}

Modifying the request
---------------------

The headers are all modifiable, as are other environmental variables
(like ``req.remote_user``, which maps to
``request.environ['REMOTE_USER']``).

If you want to copy the request you can use ``req.copy()``; this
copies the ``environ`` dictionary, and the request body from
``environ['wsgi.input']``.

The method ``req.remove_conditional_headers(remove_encoding=True)``
can be used to remove headers that might result in a ``304 Not
Modified`` response.  If you are writing some intermediary it can be
useful to avoid these headers.  Also if ``remove_encoding`` is true
(the default) then any ``Accept-Encoding`` header will be removed,
which can result in gzipped responses.

Header Getters
--------------

In addition to ``req.headers``, there are attributes for most of the
request headers defined by the HTTP 1.1 specification.  These
attributes often return parsed forms of the headers.

Accept-* headers
~~~~~~~~~~~~~~~~

There are several request headers that tell the server what the client
accepts.  These are ``accept`` (the Content-Type that is accepted),
``accept_charset`` (the charset accepted), ``accept_encoding``
(the Content-Encoding, like gzip, that is accepted), and
``accept_language`` (generally the preferred language of the client).

The objects returned support containment to test for acceptability.
E.g.:

.. code-block:: python

    >>> 'text/html' in req.accept
    True

Because no header means anything is potentially acceptable, this is
returning True.  We can set it to see more interesting behavior (the
example means that ``text/html`` is okay, but
``application/xhtml+xml`` is preferred):

.. code-block:: python

    >>> req.accept = 'text/html;q=0.5, application/xhtml+xml;q=1'
    >>> req.accept
    <MIMEAccept at ... Accept: text/html;q=0.5, application/xhtml+xml>
    >>> 'text/html' in req.accept
    True

There's three methods for different strategies of finding a match.
First, when you trust the server's preference over the client (a good
idea for Accept):

.. code-block:: python

    >>> req.accept.first_match(['text/html', 'application/xhtml+xml'])
    'text/html'

Because ``text/html`` is at least *somewhat* acceptible, it is
returned, even if the client says it prefers
``application/xhtml+xml``.  If we trust the client more:

.. code-block:: python

    >>> req.accept.best_match(['text/html', 'application/xhtml+xml'])
    'application/xhtml+xml'

If we just want to know everything the client prefers, in the order it
is preferred:

.. code-block:: python

    >>> req.accept.best_matches()
    ['application/xhtml+xml', 'text/html']

For languages you'll often have a "fallback" language.  E.g., if there's
nothing better then use ``en-US`` (and if ``en-US`` is okay, ignore
any less preferrable languages):

.. code-block:: python

    >>> req.accept_language = 'es, pt-BR'
    >>> req.accept_language.best_matches('en-US')
    ['es', 'pt-BR', 'en-US']
    >>> req.accept_language.best_matches('es')
    ['es']

Conditional Requests
~~~~~~~~~~~~~~~~~~~~

There a number of ways to make a conditional request.  A conditional
request is made when the client has a document, but it is not sure if
the document is up to date.  If it is not, it wants a new version.  If
the document is up to date then it doesn't want to waste the
bandwidth, and expects a ``304 Not Modified`` response.

ETags are generally the best technique for these kinds of requests;
this is an opaque string that indicates the identity of the object.
For instance, it's common to use the mtime (last modified) of the file,
plus the number of bytes, and maybe a hash of the filename (if there's
a possibility that the same URL could point to two different
server-side filenames based on other variables).  To test if a 304
response is appropriate, you can use:

.. code-block:: python

    >>> server_token = 'opaque-token'
    >>> server_token in req.if_none_match # You shouldn't return 304
    False
    >>> req.if_none_match = server_token
    >>> req.if_none_match
    <ETag opaque-token>
    >>> server_token in req.if_none_match # You *should* return 304
    True

For date-based comparisons If-Modified-Since is used:

.. code-block:: python

    >>> from webob import UTC
    >>> from datetime import datetime
    >>> req.if_modified_since = datetime(2006, 1, 1, 12, 0, tzinfo=UTC)
    >>> req.headers['If-Modified-Since']
    'Sun, 01 Jan 2006 12:00:00 GMT'
    >>> server_modified = datetime(2005, 1, 1, 12, 0, tzinfo=UTC)
    >>> req.if_modified_since and req.if_modified_since >= server_modified
    True

For range requests there are two important headers, If-Range (which is
form of conditional request) and Range (which requests a range).  If
the If-Range header fails to match then the full response (not a
range) should be returned:

.. code-block:: python

    >>> req.if_range
    <Empty If-Range>
    >>> req.if_range.match(etag='some-etag', last_modified=datetime(2005, 1, 1, 12, 0))
    True
    >>> req.if_range = 'opaque-etag'
    >>> req.if_range.match(etag='other-etag')
    False
    >>> req.if_range.match(etag='opaque-etag')
    True

You can also pass in a response object with:

.. code-block:: python

    >>> from webob import Response
    >>> res = Response(etag='opaque-etag')
    >>> req.if_range.match_response(res)
    True

To get the range information:

    >>> req.range = 'bytes=0-100'
    >>> req.range
    <Range ranges=(0, 101)>
    >>> cr = req.range.content_range(length=1000)
    >>> cr.start, cr.stop, cr.length
    (0, 101, 1000)

Note that the range headers use *inclusive* ranges (the last byte
indexed is included), where Python always uses a range where the last
index is excluded from the range.  The ``.stop`` index is in the
Python form.

Another kind of conditional request is a request (typically PUT) that
includes If-Match or If-Unmodified-Since.  In this case you are saying
"here is an update to a resource, but don't apply it if someone else
has done something since I last got the resource".  If-Match means "do
this if the current ETag matches the ETag I'm giving".
If-Unmodified-Since means "do this if the resource has remained
unchanged".

.. code-block:: python

    >>> server_token in req.if_match # No If-Match means everything is ok
    True
    >>> req.if_match = server_token
    >>> server_token in req.if_match # Still OK
    True
    >>> req.if_match = 'other-token'
    >>> # Not OK, should return 412 Precondition Failed:
    >>> server_token in req.if_match
    False

For more on this kind of conditional request, see `Detecting the Lost
Update Problem Using Unreserved Checkout
<http://www.w3.org/1999/04/Editing/>`_.

Calling WSGI Applications
-------------------------

The request object can be used to make handy subrequests or test
requests against WSGI applications.  If you want to make subrequests,
you should copy the request (with ``req.copy()``) before sending it to
multiple applications, since applications might modify the request
when they are run.

There's two forms of the subrequest.  The more primitive form is
this:

.. code-block:: python

    >>> req = Request.blank('/')
    >>> def wsgi_app(environ, start_response):
    ...     start_response('200 OK', [('Content-type', 'text/plain')])
    ...     return ['Hi!']
    >>> req.call_application(wsgi_app)
    ('200 OK', [('Content-type', 'text/plain')], ['Hi!'])

Note it returns ``(status_string, header_list, app_iter)``.  If
``app_iter.close()`` exists, it is your responsibility to call it.

A handier response can be had with:

.. code-block:: python

    >>> res = req.get_response(wsgi_app)
    >>> res
    <Response ... 200 OK>
    >>> res.status
    '200 OK'
    >>> res.headers
    ResponseHeaders([('Content-type', 'text/plain')])
    >>> res.body
    'Hi!'

You can learn more about this response object in the Response_ section.

Ad-Hoc Attributes
-----------------

You can assign attributes to your request objects.  They will all go
in ``environ['webob.adhoc_attrs']`` (a dictionary).

.. code-block:: python

    >>> req = Request.blank('/')
    >>> req.some_attr = 'blah blah blah'
    >>> new_req = Request(req.environ)
    >>> new_req.some_attr
    'blah blah blah'
    >>> req.environ['webob.adhoc_attrs']
    {'some_attr': 'blah blah blah'}

Response
========

The ``webob.Response`` object contains everything necessary to make a
WSGI response.  Instances of it are in fact WSGI applications, but it
can also represent the result of calling a WSGI application (as noted
in `Calling WSGI Applications`_).  It can also be a way of
accumulating a response in your WSGI application.

A WSGI response is made up of a status (like ``200 OK``), a list of
headers, and a body (or iterator that will produce a body).

Core Attributes
---------------

The core attributes are unsurprising:

.. code-block:: python

    >>> from webob import Response
    >>> res = Response()
    >>> res.status
    '200 OK'
    >>> res.headerlist
    [('Content-Type', 'text/html; charset=UTF-8'), ('Content-Length', '0')]
    >>> res.body
    ''

You can set any of these attributes, e.g.:

.. code-block:: python

    >>> res.status = 404
    >>> res.status
    '404 Not Found'
    >>> res.status_int
    404
    >>> res.headerlist = [('Content-type', 'text/html')]
    >>> res.body = 'test'
    >>> print res
    404 Not Found
    Content-type: text/html
    Content-Length: 4
    <BLANKLINE>
    test
    >>> res.body = u"test"
    Traceback (most recent call last):
        ...
    TypeError: You cannot set Response.body to a unicode object (use Response.unicode_body)
    >>> res.unicode_body = u"test"
    Traceback (most recent call last):
        ...
    AttributeError: You cannot access Response.unicode_body unless charset is set
    >>> res.charset = 'utf8'
    >>> res.unicode_body = u"test"
    >>> res.body
    'test'

You can set any attribute with the constructor, like
``Response(charset='utf8')``

Headers
-------

In addition to ``res.headerlist``, there is dictionary-like view on
the list in ``res.headers``:

.. code-block:: python

    >>> res.headers
    ResponseHeaders([('Content-Type', 'text/html; charset=utf8'), ('Content-Length', '4')])

This is case-insensitive.  It can support multiple values for a key,
though only if you use ``res.headers.add(key, value)`` or read them
with ``res.headers.getall(key)``.

Body & app_iter
---------------

The ``res.body`` attribute represents the entire body of the request
as a single string (not unicode, though you can set it to unicode if
you have a charset defined).  There is also a ``res.app_iter``
attribute that reprsents the body as an iterator.  WSGI applications
return these ``app_iter`` iterators instead of strings, and sometimes
it can be problematic to load the entire iterator at once (for
instance, if it returns the contents of a very large file).  Generally
it is not a problem, and often the iterator is something simple like a
one-item list containing a string with the entire body.

If you set the body then Content-Length will also be set, and an
``res.app_iter`` will be created for you.  If you set ``res.app_iter``
then Content-Length will be cleared, but it won't be set for you.

There is also a file-like object you can access, which will update the
app_iter in-place (turning the app_iter into a list if necessary):

.. code-block:: python

    >>> res = Response(content_type='text/plain', charset=None)
    >>> f = res.body_file
    >>> f.write('hey')
    >>> f.write(u'test')
    Traceback (most recent call last):
      . . .
    TypeError: You can only write unicode to Response.body_file if charset has been set
    >>> f.encoding
    >>> res.charset = 'utf8'
    >>> f.encoding
    'utf8'
    >>> f.write(u'test')
    >>> res.app_iter
    ['hey', 'test']
    >>> res.body
    'heytest'

Header Getters
--------------

Like Request, HTTP response headers are also available as individual
properties.  These represent parsed forms of the headers.

Content-Type is a special case, as the type and the charset are
handled through two separate properties:

.. code-block:: python

    >>> res = Response()
    >>> res.content_type = 'text/html'
    >>> res.charset = 'utf8'
    >>> res.content_type
    'text/html'
    >>> res.headers['content-type']
    'text/html; charset=utf8'
    >>> res.content_type = 'application/atom+xml'
    >>> res.content_type_params
    {'charset': 'utf8'}
    >>> res.content_type_params = {'type': 'entry', 'charset': 'utf8'}
    >>> res.headers['content-type']
    'application/atom+xml; charset=utf8; type=entry'

Other headers:

.. code-block:: python

    >>> # Used with a redirect:
    >>> res.location = 'http://localhost/foo'

    >>> # Indicates that the server accepts Range requests:
    >>> res.accept_ranges = 'bytes'

    >>> # Used by caching proxies to tell the client how old the
    >>> # response is:
    >>> res.age = 120

    >>> # Show what methods the client can do; typically used in
    >>> # a 405 Method Not Allowed response:
    >>> res.allow = ['GET', 'PUT']

    >>> # Set the cache-control header:
    >>> res.cache_control.max_age = 360
    >>> res.cache_control.no_transform = True

    >>> # Tell the browser to treat the response as an attachment:
    >>> res.content_disposition = 'attachment; filename=foo.xml'

    >>> # Used if you had gzipped the body:
    >>> res.content_encoding = 'gzip'

    >>> # What language(s) are in the content:
    >>> res.content_language = ['en']

    >>> # Seldom used header that tells the client where the content
    >>> # is from:
    >>> res.content_location = 'http://localhost/foo'

    >>> # Seldom used header that gives a hash of the body:
    >>> res.content_md5 = 'big-hash'

    >>> # Means we are serving bytes 0-500 inclusive, out of 1000 bytes total:
    >>> # you can also use the range setter shown earlier
    >>> res.content_range = (0, 501, 1000)

    >>> # The length of the content; set automatically if you set
    >>> # res.body:
    >>> res.content_length = 4

    >>> # Used to indicate the current date as the server understands
    >>> # it:
    >>> res.date = datetime.now()

    >>> # The etag:
    >>> res.etag = 'opaque-token'
    >>> # You can generate it from the body too:
    >>> res.md5_etag()
    >>> res.etag
    '1B2M2Y8AsgTpgAmY7PhCfg'

    >>> # When this page should expire from a cache (Cache-Control
    >>> # often works better):
    >>> import time
    >>> res.expires = time.time() + 60*60 # 1 hour

    >>> # When this was last modified, of course:
    >>> res.last_modified = datetime(2007, 1, 1, 12, 0, tzinfo=UTC)

    >>> # Used with 503 Service Unavailable to hint the client when to
    >>> # try again:
    >>> res.retry_after = 160

    >>> # Indicate the server software:
    >>> res.server = 'WebOb/1.0'

    >>> # Give a list of headers that the cache should vary on:
    >>> res.vary = ['Cookie']

Note in each case you can general set the header to a string to avoid
any parsing, and set it to None to remove the header (or do something
like ``del res.vary``).

In the case of date-related headers you can set the value to a
``datetime`` instance (ideally with a UTC timezone), a time tuple, an
integer timestamp, or a properly-formatted string.

After setting all these headers, here's the result:

.. code-block:: python

    >>> for name, value in res.headerlist:
    ...     print '%s: %s' % (name, value)
    Content-Type: application/atom+xml; charset=utf8; type=entry
    Location: http://localhost/foo
    Accept-Ranges: bytes
    Age: 120
    Allow: GET, PUT
    Cache-Control: max-age=360, no-transform
    Content-Disposition: attachment; filename=foo.xml
    Content-Encoding: gzip
    Content-Language: en
    Content-Location: http://localhost/foo
    Content-MD5: big-hash
    Content-Range: bytes 0-500/1000
    Content-Length: 4
    Date: ... GMT
    ETag: ...
    Expires: ... GMT
    Last-Modified: Mon, 01 Jan 2007 12:00:00 GMT
    Retry-After: 160
    Server: WebOb/1.0
    Vary: Cookie

You can also set Cache-Control related attributes with
``req.cache_expires(seconds, **attrs)``, like:

.. code-block:: python

    >>> res = Response()
    >>> res.cache_expires(10)
    >>> res.headers['Cache-Control']
    'max-age=10'
    >>> res.cache_expires(0)
    >>> res.headers['Cache-Control']
    'max-age=0, must-revalidate, no-cache, no-store'
    >>> res.headers['Expires']
    '... GMT'

You can also use the `timedelta
<http://python.org/doc/current/lib/datetime-timedelta.html>`_
constants defined, e.g.:

.. code-block:: python

    >>> from webob import *
    >>> res = Response()
    >>> res.cache_expires(2*day+4*hour)
    >>> res.headers['Cache-Control']
    'max-age=187200'

Cookies
-------

Cookies (and the Set-Cookie header) are handled with a couple
methods.  Most importantly:

.. code-block:: python

    >>> res.set_cookie('key', 'value', max_age=360, path='/',
    ...                domain='example.org', secure=True)
    >>> res.headers['Set-Cookie']
    'key=value; Domain=example.org; expires="... GMT"; Max-Age=360; Path=/; secure'
    >>> # To delete a cookie previously set in the client:
    >>> res.delete_cookie('bad_cookie')
    >>> res.headers['Set-Cookie']
    'bad_cookie=; expires="... GMT"; Max-Age=0; Path=/'

The only other real method of note (note that this does *not* delete
the cookie from clients, only from the response object):

.. code-block:: python

    >>> res.unset_cookie('key')
    >>> res.unset_cookie('bad_cookie')
    >>> print res.headers.get('Set-Cookie')
    None

Binding a Request
-----------------

You can bind a request (or request WSGI environ) to the response
object.  This is available through ``res.request`` or
``res.environ``.  This is currently only used in setting
``res.location``, to make the location absolute if necessary.

Response as a WSGI application
------------------------------

A response is a WSGI application, in that you can do:

.. code-block:: python

    >>> req = Request.blank('/')
    >>> status, headers, app_iter = req.call_application(res)

A possible pattern for your application might be:

.. code-block:: python

    >>> def my_app(environ, start_response):
    ...     req = Request(environ)
    ...     res = Response()
    ...     res.content_type = 'text/plain'
    ...     parts = []
    ...     for name, value in sorted(req.environ.items()):
    ...         parts.append('%s: %r' % (name, value))
    ...     res.body = '\n'.join(parts)
    ...     return res(environ, start_response)
    >>> req = Request.blank('/')
    >>> res = req.get_response(my_app)
    >>> print res
    200 OK
    Content-Type: text/plain; charset=UTF-8
    Content-Length: ...
    <BLANKLINE>
    HTTP_HOST: 'localhost:80'
    PATH_INFO: '/'
    QUERY_STRING: ''
    REQUEST_METHOD: 'GET'
    SCRIPT_NAME: ''
    SERVER_NAME: 'localhost'
    SERVER_PORT: '80'
    SERVER_PROTOCOL: 'HTTP/1.0'
    wsgi.errors: <open file '<stderr>', mode 'w' at ...>
    wsgi.input: <...IO... object at ...>
    wsgi.multiprocess: False
    wsgi.multithread: False
    wsgi.run_once: False
    wsgi.url_scheme: 'http'
    wsgi.version: (1, 0)

Exceptions
==========

In addition to Request and Response objects, there are a set of Python
exceptions for different HTTP responses (3xx, 4xx, 5xx codes).

These provide a simple way to provide these non-200 response.  A very
simple body is provided.

.. code-block:: python

    >>> from webob.exc import *
    >>> exc = HTTPTemporaryRedirect(location='foo')
    >>> req = Request.blank('/path/to/something')
    >>> print str(req.get_response(exc)).strip()
    307 Temporary Redirect
    Location: http://localhost/path/to/foo
    Content-Length: 126
    Content-Type: text/plain; charset=UTF-8
    <BLANKLINE>
    307 Temporary Redirect
    <BLANKLINE>
    The resource has been moved to http://localhost/path/to/foo; you should be redirected automatically.

Note that only if there's an ``Accept: text/html`` header in the
request will an HTML response be given:

.. code-block:: python

    >>> req.accept += 'text/html'
    >>> print str(req.get_response(exc)).strip()
    307 Temporary Redirect
    Location: http://localhost/path/to/foo
    Content-Length: 270
    Content-Type: text/html; charset=UTF-8
    <BLANKLINE>
    <html>
     <head>
      <title>307 Temporary Redirect</title>
     </head>
     <body>
      <h1>307 Temporary Redirect</h1>
      The resource has been moved to <a href="http://localhost/path/to/foo">http://localhost/path/to/foo</a>;
    you should be redirected automatically.
    <BLANKLINE>
    <BLANKLINE>
     </body>
    </html>


This is taken from `paste.httpexceptions
<http://pythonpaste.org/module-paste.httpexceptions.html>`_, and if
you have Paste installed then these exceptions will be subclasses of
the Paste exceptions.

Note that on Python 2.4 and before, new-style classes could not be
used as exceptions.  ``Response`` objects must be new-style classes,
so this causes a bit of a conflict.  The base class
``webob.exc.HTTPException`` *is* an exception, so you can catch that,
and it *is* a WSGI application.  But you may not be able to use
Response methods.  You can always get ``obj.exception`` to get an
exception that you can raise, and ``obj.wsgi_response`` to get the
``Response`` object that you can use.

Conditional WSGI Application
----------------------------

The Response object can handle your conditional responses for you,
checking If-None-Match, If-Modified-Since, and Range/If-Range.

To enable this you must create the response like
``Response(conditional_request=True)``, or make a subclass like:

.. code-block:: python

    >>> class AppResponse(Response):
    ...     default_content_type = 'text/html'
    ...     default_conditional_response = True
    >>> res = AppResponse(body='0123456789',
    ...                   last_modified=datetime(2005, 1, 1, 12, 0, tzinfo=UTC))
    >>> req = Request.blank('/')
    >>> req.if_modified_since = datetime(2006, 1, 1, 12, 0, tzinfo=UTC)
    >>> req.get_response(res)
    <Response ... 304 Not Modified>
    >>> del req.if_modified_since
    >>> res.etag = 'opaque-tag'
    >>> req.if_none_match = 'opaque-tag'
    >>> req.get_response(res)
    <Response ... 304 Not Modified>

    >>> req.if_none_match = '*'
    >>> 'x' in req.if_none_match
    True
    >>> req.if_none_match = req.if_none_match
    >>> 'x' in req.if_none_match
    True
    >>> req.if_none_match = None
    >>> 'x' in req.if_none_match
    False
    >>> req.if_match = None
    >>> 'x' in req.if_match
    True
    >>> req.if_match = req.if_match
    >>> 'x' in req.if_match
    True
    >>> req.headers.get('If-Match')
    '*'

    >>> del req.if_none_match

    >>> req.range = (1, 5)
    >>> result = req.get_response(res)
    >>> result.headers['content-range']
    'bytes 1-4/10'
    >>> result.body
    '1234'