summaryrefslogtreecommitdiffstats
path: root/google_appengine/lib/webob/docs/reference.txt
diff options
context:
space:
mode:
Diffstat (limited to 'google_appengine/lib/webob/docs/reference.txt')
-rw-r--r--google_appengine/lib/webob/docs/reference.txt1001
1 files changed, 1001 insertions, 0 deletions
diff --git a/google_appengine/lib/webob/docs/reference.txt b/google_appengine/lib/webob/docs/reference.txt
new file mode 100644
index 0000000..9cebd85
--- /dev/null
+++ b/google_appengine/lib/webob/docs/reference.txt
@@ -0,0 +1,1001 @@
+WebOb Reference
++++++++++++++++
+
+.. contents::
+
+.. comment:
+
+ >>> from dtopt 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::
+
+ >>> from webob import Request
+ >>> environ = {}
+ >>> req = Request(environ)
+
+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::
+
+ >>> 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': <cStringIO.StringI 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::
+
+ >>> req.body_file
+ <cStringIO.StringI object at ...>
+ >>> req.body
+ ''
+ >>> req.body = 'test'
+ >>> req.body_file
+ <cStringIO.StringI object at ...>
+ >>> req.body
+ 'test'
+
+Method & URL
+------------
+
+All the normal parts of a request are also accessible through the
+request object:
+
+.. code-block::
+
+ >>> 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::
+
+ >>> 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::
+
+ >>> 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::
+
+ >>> req.headers['content-type'] = 'application/x-www-urlencoded'
+ >>> req.headers
+ {'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::
+
+ >>> req = Request.blank('/test?check=a&check=b&name=Bob')
+ >>> req.GET
+ MultiDict([('check', 'a'), ('check', 'b'), ('name', 'Bob')])
+ >>> req.GET['check']
+ 'b'
+ >>> req.GET.getall('check')
+ ['a', 'b']
+ >>> req.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::
+
+ >>> req.POST
+ <NoVars: Not a POST request>
+ >>> req.POST.items() # NoVars can be read like a dict, but not written
+ []
+ >>> req.method = 'POST'
+ >>> req.body = 'name=Joe&email=joe@example.com'
+ >>> req.POST
+ MultiDict([('name', 'Joe'), ('email', 'joe@example.com')])
+ >>> req.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::
+
+ >>> req.params
+ NestedMultiDict([('check', 'a'), ('check', 'b'), ('name', 'Bob'), ('name', 'Joe'), ('email', 'joe@example.com')])
+ >>> req.params['name']
+ 'Bob'
+ >>> req.params.getall('name')
+ ['Bob', 'Joe']
+ >>> for name, value in req.params.items():
+ ... print '%s: %r' % (name, value)
+ check: 'a'
+ check: 'b'
+ name: 'Bob'
+ name: 'Joe'
+ email: 'joe@example.com'
+
+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::
+
+ >>> req.charset = 'utf8'
+ >>> req.GET
+ UnicodeMultiDict([(u'check', u'a'), (u'check', u'b'), (u'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::
+
+ >>> req.headers['Cookie'] = 'test=value'
+ >>> req.cookies
+ UnicodeMultiDict([(u'test', u'value')])
+ >>> req.charset = None
+ >>> req.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::
+
+ >>> '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::
+
+ >>> 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::
+
+ >>> 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::
+
+ >>> 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::
+
+ >>> 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::
+
+ >>> 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::
+
+ >>> 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::
+
+ >>> 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::
+
+ >>> 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::
+
+ >>> 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, 99)>
+ >>> cr = req.range.content_range(length=1000)
+ >>> cr.start, cr.stop, cr.length
+ (0, 99, 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::
+
+ >>> 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::
+
+ >>> 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::
+
+ >>> res = req.get_response(wsgi_app)
+ >>> res
+ <Response ... 200 OK>
+ >>> res.status
+ '200 OK'
+ >>> res.headers
+ HeaderDict([('Content-type', 'text/plain')])
+ >>> res.body
+ 'Hi!'
+
+You can learn more about this response object in the Response_ section.
+
+Thread-local Request Wrappers
+-----------------------------
+
+You can also give the ``Request`` object a function to get the
+environment. This can be used to make a single global request object
+dynamic. An example:
+
+.. code-block::
+
+ >>> import threading
+ >>> import webob
+ >>> environments = threading.local()
+ >>> def get_environ():
+ ... return environments.environ
+ >>> def set_thread_environ(environ):
+ ... environments.environ = environ
+ >>> request = webob.Request(environ_getter=get_environ)
+ >>> set_thread_environ({'SCRIPT_NAME': '/test'})
+ >>> request.script_name
+ '/test'
+
+Ad-Hoc Attributes
+-----------------
+
+You can assign attributes to your request objects. They will all go
+in ``environ['webob.adhoc_attrs']`` (a dictionary).
+
+.. code-block::
+
+ >>> 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::
+
+ >>> from webob import Response
+ >>> res = Response()
+ >>> res.status
+ '200 OK'
+ >>> res.headerlist
+ [('Content-Length', '0')]
+ >>> res.body
+ ''
+
+You can set any of these attributes, e.g.:
+
+.. code-block::
+
+ >>> 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::
+
+ >>> res.headers
+ HeaderDict([('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::
+
+ >>> res = Response(content_type='text/plain')
+ >>> 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::
+
+ >>> 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::
+
+ >>> # 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
+
+ >>> # 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, 499, 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::
+
+ >>> 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-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::
+
+ >>> 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::
+
+ >>> 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::
+
+ >>> res.set_cookie('key', 'value', max_age=360, path='/',
+ ... domain='example.org', secure=True)
+ >>> res.headers['Set-Cookie']
+ 'key=value; Domain=example.org; 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=; 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::
+
+ >>> 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::
+
+ >>> req = Request.blank('/')
+ >>> status, headers, app_iter = req.call_application(res)
+
+A possible pattern for your application might be:
+
+.. code-block::
+
+ >>> 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
+ Content-Length: 394
+ <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: <cStringIO.StringI 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::
+
+ >>> from webob.exc import *
+ >>> exc = HTTPTemporaryRedirect(location='foo')
+ >>> req = Request.blank('/path/to/something')
+ >>> print str(req.get_response(exc)).strip()
+ 307 Temporary Redirect
+ content-type: text/html
+ location: http://localhost/path/to/foo
+ Content-Length: 126
+ <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::
+
+ >>> req.accept += 'text/html'
+ >>> print str(req.get_response(exc)).strip()
+ 307 Temporary Redirect
+ content-type: text/html
+ location: http://localhost/path/to/foo
+ Content-Length: 270
+ <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::
+
+ >>> 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>
+ >>> del req.if_none_match
+ >>> req.range = (1, 5)
+ >>> result = req.get_response(res)
+ >>> result.headers['content-range']
+ 'bytes 1-6/10'
+ >>> result.body
+ '1234'