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