thirdparty/google_appengine/lib/webob/docs/file-example.txt
changeset 109 620f9b141567
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/thirdparty/google_appengine/lib/webob/docs/file-example.txt	Tue Aug 26 21:49:54 2008 +0000
@@ -0,0 +1,217 @@
+WebOb File-Serving Example
+==========================
+
+This document shows how you can make a static-file-serving application
+using WebOb.  We'll quickly build this up from minimal functionality
+to a high-quality file serving application.
+
+.. comment:
+
+   >>> import webob, os
+   >>> base_dir = os.path.dirname(os.path.dirname(webob.__file__))
+   >>> doc_dir = os.path.join(base_dir, 'docs')
+   >>> from dtopt import ELLIPSIS
+
+First we'll setup a really simple shim around our application, which
+we can use as we improve our application:
+
+.. code-block::
+
+   >>> from webob import Request, Response
+   >>> import os
+   >>> class FileApp(object):
+   ...     def __init__(self, filename):
+   ...         self.filename = filename
+   ...     def __call__(self, environ, start_response):
+   ...         res = make_response(self.filename)
+   ...         return res(environ, start_response)
+   >>> import mimetypes
+   >>> def get_mimetype(filename):
+   ...     type, encoding = mimetypes.guess_type(filename)
+   ...     # We'll ignore encoding, even though we shouldn't really
+   ...     return type or 'application/octet-stream'
+
+Now we can make different definitions of ``make_response``.  The
+simplest version:
+
+.. code-block::
+
+   >>> def make_response(filename):
+   ...     res = Response(content_type=get_mimetype(filename))
+   ...     res.body = open(filename, 'rb').read()
+   ...     return res
+
+Let's give it a go.  We'll test it out with a file ``test-file.txt``
+in the WebOb doc directory:
+
+.. code-block::
+
+   >>> fn = os.path.join(doc_dir, 'test-file.txt')
+   >>> open(fn).read()
+   'This is a test.  Hello test people!\n'
+   >>> app = FileApp(fn)
+   >>> req = Request.blank('/')
+   >>> print req.get_response(app)
+   200 OK
+   content-type: text/plain
+   Content-Length: 36
+   <BLANKLINE>
+   This is a test.  Hello test people!
+   <BLANKLINE>
+
+Well, that worked.  But it's not a very fancy object.  First, it reads
+everything into memory, and that's bad.  We'll create an iterator instead:
+
+.. code-block::
+
+   >>> class FileIterable(object):
+   ...     def __init__(self, filename):
+   ...         self.filename = filename
+   ...     def __iter__(self):
+   ...         return FileIterator(self.filename)
+   >>> class FileIterator(object):
+   ...     chunk_size = 4096
+   ...     def __init__(self, filename):
+   ...         self.filename = filename
+   ...         self.fileobj = open(self.filename, 'rb')
+   ...     def __iter__(self):
+   ...         return self
+   ...     def next(self):
+   ...         chunk = self.fileobj.read(self.chunk_size)
+   ...         if not chunk:
+   ...             raise StopIteration
+   ...         return chunk
+   >>> def make_response(filename):
+   ...     res = Response(content_type=get_mimetype(filename))
+   ...     res.app_iter = FileIterable(filename)
+   ...     res.content_length = os.path.getsize(filename)
+   ...     return res
+
+And testing:
+
+.. code-block::
+
+   >>> req = Request.blank('/')
+   >>> print req.get_response(app)
+   200 OK
+   content-type: text/plain
+   Content-Length: 36
+   <BLANKLINE>
+   This is a test.  Hello test people!
+   <BLANKLINE>
+
+Well, that doesn't *look* different, but lets *imagine* that it's
+different because we know we changed some code.  Now to add some basic
+metadata to the response:
+
+.. code-block::
+
+   >>> def make_response(filename):
+   ...     res = Response(content_type=get_mimetype(filename),
+   ...                    conditional_response=True)
+   ...     res.app_iter = FileIterable(filename)
+   ...     res.content_length = os.path.getsize(filename)
+   ...     res.last_modified = os.path.getmtime(filename)
+   ...     res.etag = '%s-%s-%s' % (os.path.getmtime(filename),
+   ...                              os.path.getsize(filename), hash(filename))
+   ...     return res
+
+Now, with ``conditional_response`` on, and with ``last_modified`` and
+``etag`` set, we can do conditional requests:
+
+.. code-block::
+
+   >>> req = Request.blank('/')
+   >>> res = req.get_response(app)
+   >>> print res
+   200 OK
+   content-type: text/plain
+   Content-Length: 36
+   Last-Modified: ... GMT
+   ETag: ...-...
+   <BLANKLINE>
+   This is a test.  Hello test people!
+   <BLANKLINE>
+   >>> req2 = Request.blank('/')
+   >>> req2.if_none_match = res.etag
+   >>> req2.get_response(app)
+   <Response ... 304 Not Modified>
+   >>> req3 = Request.blank('/')
+   >>> req3.if_modified_since = res.last_modified
+   >>> req3.get_response(app)
+   <Response ... 304 Not Modified>
+   
+We can even do Range requests, but it will currently involve iterating
+through the file unnecessarily.  When there's a range request (and you
+set ``conditional_response=True``) the application will satisfy that
+request.  But with an arbitrary iterator the only way to do that is to
+run through the beginning of the iterator until you get to the chunk
+that the client asked for.  We can do better because we can use
+``fileobj.seek(pos)`` to move around the file much more efficiently.
+
+So we'll add an extra method, ``app_iter_range``, that ``Response``
+looks for:
+
+.. code-block::
+
+   >>> class FileIterable(object):
+   ...     def __init__(self, filename, start=None, stop=None):
+   ...         self.filename = filename
+   ...         self.start = start
+   ...         self.stop = stop
+   ...     def __iter__(self):
+   ...         return FileIterator(self.filename, self.start, self.stop)
+   ...     def app_iter_range(self, start, stop):
+   ...         return self.__class__(self.filename, start, stop)
+   >>> class FileIterator(object):
+   ...     chunk_size = 4096
+   ...     def __init__(self, filename, start, stop):
+   ...         self.filename = filename
+   ...         self.fileobj = open(self.filename, 'rb')
+   ...         if start:
+   ...             self.fileobj.seek(start)
+   ...         if stop is not None:
+   ...             self.length = stop - start
+   ...         else:
+   ...             self.length = None
+   ...     def __iter__(self):
+   ...         return self
+   ...     def next(self):
+   ...         if self.length is not None and self.length <= 0:
+   ...             raise StopIteration
+   ...         chunk = self.fileobj.read(self.chunk_size)
+   ...         if not chunk:
+   ...             raise StopIteration
+   ...         if self.length is not None:
+   ...             self.length -= len(chunk)
+   ...             if self.length < 0:
+   ...                 # Chop off the extra:
+   ...                 chunk = chunk[:self.length]
+   ...         return chunk
+
+Now we'll test it out:
+
+.. code-block::
+
+   >>> req = Request.blank('/')
+   >>> res = req.get_response(app)
+   >>> req2 = Request.blank('/')
+   >>> # Re-fetch the first 5 bytes:
+   >>> req2.range = (0, 5)
+   >>> res2 = req2.get_response(app)
+   >>> res2
+   <Response ... 206 Partial Content>
+   >>> # Let's check it's our custom class:
+   >>> res2.app_iter
+   <FileIterable object at ...>
+   >>> res2.body
+   'This '
+   >>> # Now, conditional range support:
+   >>> req3 = Request.blank('/')
+   >>> req3.if_range = res.etag
+   >>> req3.range = (0, 5)
+   >>> req3.get_response(app)
+   <Response ... 206 Partial Content>
+   >>> req3.if_range = 'invalid-etag'
+   >>> req3.get_response(app)
+   <Response ... 200 OK>