[Zope3-checkins] SVN: Zope3/trunk/src/zope/app/wsgi/ I have
improved the tests to demonstrate clearly how to use the WSGI
Stephan Richter
srichter at cosmos.phy.tufts.edu
Tue Apr 12 11:14:52 EDT 2005
Log message for revision 29949:
I have improved the tests to demonstrate clearly how to use the WSGI
PublisherApp class. I also have created some interfaces for better
documentation. Next I will try to create a ZServer-based WSGI server.
Changed:
U Zope3/trunk/src/zope/app/wsgi/README.txt
U Zope3/trunk/src/zope/app/wsgi/__init__.py
A Zope3/trunk/src/zope/app/wsgi/interfaces.py
U Zope3/trunk/src/zope/app/wsgi/tests.py
-=-
Modified: Zope3/trunk/src/zope/app/wsgi/README.txt
===================================================================
--- Zope3/trunk/src/zope/app/wsgi/README.txt 2005-04-12 13:58:33 UTC (rev 29948)
+++ Zope3/trunk/src/zope/app/wsgi/README.txt 2005-04-12 15:14:51 UTC (rev 29949)
@@ -2,42 +2,206 @@
Zope WSGI Application
=====================
-About
------
-This package contains a WSGI application for Zope.
+This package contains an interpretation of the WSGI specification (PEP-0333)
+for the Zope application server by providing a WSGI application object. First,
+we create a stream that will contain the response:
-WSGI is the Python Web Server Gateway Interface, an
-upcoming PEP to standardize the interface between web servers
-and python applications to promote portability.
+ >>> import StringIO
+ >>> data = StringIO.StringIO('')
-For more information, refer to the WSGI specification: http://www.python.org/peps/pep-0333.html
+Usually this stream is created by the HTTP server and refers to the output
+stream sent to the server client.
-Usage
------
-To use Zope as a WSGI application, the following steps must be taken
+Now that we have our output stream, we need to initialize the WSGI-compliant
+Zope application that is called from the server. To do that, we first have to
+create and open a ZODB connection:
-* configure and setup Zope
+ >>> from ZODB.MappingStorage import MappingStorage
+ >>> from ZODB.DB import DB
+
+ >>> storage = MappingStorage('test.db')
+ >>> db = DB(storage, cache_size=4000)
-* create an instance of ``zope.app.wsgi.PublisherApp`` must be created
- with a refernce to an open database
+We can now initialize the application:
-* This instance must be set as the WSGI servers application object
+ >>> from zope.app import wsgi
+ >>> app = wsgi.PublisherApp(db)
+The callable ``app`` object accepts two positional arguments, the environment
+and the function that initializes the response and returns a function with
+which the output data can be written.
-Example::
+Even though this is commonly done by the server, our first task is to create
+an appropriate environment for the request.
+ >>> environ = {
+ ... 'PATH_INFO': '/',
+ ... 'wsgi.input': StringIO.StringIO('')}
+
+Next we create a WSGI-compliant ``start_response()`` method that accepts the
+status of the response to the HTTP request and the headers that are part of
+the response stream. The headers are expected to be a list of
+2-tuples. However, for the purpose of this demonstration we simply ignore all
+the arguments and push a simple message to the stream. The
+``start_response()`` funtion must also return a ``write()`` callable that
+accepts further data.
+
+ >>> def start_response(status, headers):
+ ... data.write('status and headers.\n\n')
+ ... return data.write
+ ...
+
+Now we can send the fabricated HTTP request to the application for processing:
+
+ >>> app(environ, start_response)
+ ''
+
+We expect the output of this call to be always an empty string, since all the
+output is written to the output stream directly. Looking at the output
+
+ >>> print data.getvalue()
+ status and headers.
+ <BLANKLINE>
+ <html><head><title>Unauthorized</title></head>
+ <body><h2>Unauthorized</h2>
+ A server error occurred.
+ </body></html>
+ <BLANKLINE>
+
+we can see that application really crashed and did not know what to do. This
+is okay, since we have not setup anything. Getting a request successfully
+processed would require us to bring up a lot of Zope 3's system, which would
+be just a little bit too much for this demonstration.
+
+Now that we have seen the manual way of initializing and using the publisher
+application, here is the way it is done using all of Zope 3's setup machinery::
+
from zope.app.server.main import setup, load_options
from zope.app.wsgi import PublisherApp
+ # Read all configuration files and bring up the component architecture
args = ["-C/path/to/zope.conf"]
db = setup(load_options(args))
- my_app = PublisherApp(db)
+ # Initialize the WSGI-compliant publisher application with the database
+ wsgiApplication = PublisherApp(db)
- wsgi_server.set_app(my_app)
+ # Here is an example on how the application could be registered with a
+ # WSGI-compliant server. Note that the ``setApplication()`` method is not
+ # part of the PEP 333 specification.
+ wsgiServer.setApplication(wsgiApplication)
-This assumes, that Zope is available on the ``PYTHONPATH``.
-Note that you may have to edit ``zope.conf`` to provide
-an absolute path for ``site.zcml``.
+The code above assumes, that Zope is available on the ``PYTHONPATH``. Note
+that you may have to edit ``zope.conf`` to provide an absolute path for
+``site.zcml``. Unfortunately we do not have enough information about the
+directory structure to make this code a doctest.
+In summary, to use Zope as a WSGI application, the following steps must be
+taken:
+* configure and setup Zope
+
+* an instance of ``zope.app.wsgi.PublisherApp`` must be created with a
+ refernce to the opened database
+
+* this application instance must be somehow communicated to the WSGI server,
+ i.e. by calling a method on the server that sets the application.
+
+
+The ``IWSGIOutput`` Component
+-----------------------------
+
+Under the hood the WSGI support uses a component that implements
+``IWSGIOutput`` that manages the response headers and provides the output
+stream by implementing the ``write()`` method. In the following text the
+functionality of this class is introduced:
+
+First, we reset our output stream:
+
+ >>> data.__init__('')
+
+Then we initialize an instance of the WSGI output object:
+
+ >>> output = wsgi.WSGIOutput(start_response)
+
+You can set the response status
+
+ >>> output.setResponseStatus("200", "OK")
+ >>> output._statusString
+ '200 OK'
+
+or set arbitrary headers as a mapping:
+
+ >>> output.setResponseHeaders({'a':'b', 'c':'d'})
+
+The headers must be returned as a list of tuples:
+
+ >>> output.getHeaders()
+ [('a', 'b'), ('c', 'd')]
+
+Calling ``setResponseHeaders()`` again adds new values:
+
+ >>> output.setResponseHeaders({'x':'y', 'c':'d'})
+ >>> h = output.getHeaders()
+ >>> h.sort()
+ >>> h
+ [('a', 'b'), ('c', 'd'), ('x', 'y')]
+
+Headers that can potentially repeat are added using
+``appendResponseHeaders()``:
+
+ >>> output.appendResponseHeaders(['foo: bar'])
+ >>> h = output.getHeaders()
+ >>> h.sort()
+ >>> h
+ [('a', 'b'), ('c', 'd'), ('foo', ' bar'), ('x', 'y')]
+ >>> output.appendResponseHeaders(['foo: bar'])
+ >>> h = output.getHeaders()
+ >>> h.sort()
+ >>> h
+ [('a', 'b'), ('c', 'd'), ('foo', ' bar'), ('foo', ' bar'), ('x', 'y')]
+
+Headers containing a colon should also work
+
+ >>> output.appendResponseHeaders(['my: brain:hurts'])
+ >>> h = output.getHeaders()
+ >>> h.sort()
+ >>> h
+ [('a', 'b'), ('c', 'd'), ('foo', ' bar'), ('foo', ' bar'),
+ ('my', ' brain:hurts'), ('x', 'y')]
+
+The headers should not be written to the output
+
+ >>> output.wroteResponseHeader()
+ False
+ >>> data.getvalue()
+ ''
+
+Let's now write something to the output stream:
+
+ >>> output.write('Now for something')
+
+The headers should be sent and the data written to the stream:
+
+ >>> output.wroteResponseHeader()
+ True
+ >>> data.getvalue()
+ 'status and headers.\n\nNow for something'
+
+Calling write again the headers should not be sent again
+
+ >>> output.write(' completly different!')
+ >>> data.getvalue()
+ 'status and headers.\n\nNow for something completly different!'
+
+
+About WSGI
+----------
+
+WSGI is the Python Web Server Gateway Interface, an upcoming PEP to
+standardize the interface between web servers and python applications to
+promote portability.
+
+For more information, refer to the WSGI specification:
+http://www.python.org/peps/pep-0333.html
+
Modified: Zope3/trunk/src/zope/app/wsgi/__init__.py
===================================================================
--- Zope3/trunk/src/zope/app/wsgi/__init__.py 2005-04-12 13:58:33 UTC (rev 29948)
+++ Zope3/trunk/src/zope/app/wsgi/__init__.py 2005-04-12 15:14:51 UTC (rev 29949)
@@ -20,89 +20,14 @@
from zope.publisher.interfaces.http import IHeaderOutput
from zope.app.publication.httpfactory import HTTPPublicationRequestFactory
+from zope.app.wsgi import interfaces
-class WsgiOutput(object):
- """This class handles the output generated by
- the publisher. It is used to collect the headers
- and as an outstream to output the response body.
-
- When write is first called by the response, it initiates
- the reponse by invoking the WSGI start_response callable.
-
- Create a mock implementation of the wsgi write callable
- >>> from StringIO import StringIO
- >>> data = StringIO('')
- >>> def start_response(status, headers):
- ... data.write('status and headers.')
- ... return data.write
- ...
-
- create an instance
- >>> output = WsgiOutput(start_response)
-
- Set the response status
- >>> output.setResponseStatus("200", "OK")
- >>> output._statusString
- '200 OK'
-
- Set the headers as a mapping
- >>> output.setResponseHeaders({'a':'b', 'c':'d'})
-
- They must be returned as a list of tuples
- >>> output.getHeaders()
- [('a', 'b'), ('c', 'd')]
-
- calling setResponseHeaders again adds new values
- >>> output.setResponseHeaders({'x':'y', 'c':'d'})
- >>> h = output.getHeaders()
- >>> h.sort()
- >>> h
- [('a', 'b'), ('c', 'd'), ('x', 'y')]
-
- Headers that can potentially repeat are added using
- appendResponseHeaders
- >>> output.appendResponseHeaders(['foo: bar'])
- >>> h = output.getHeaders()
- >>> h.sort()
- >>> h
- [('a', 'b'), ('c', 'd'), ('foo', ' bar'), ('x', 'y')]
- >>> output.appendResponseHeaders(['foo: bar'])
- >>> h = output.getHeaders()
- >>> h.sort()
- >>> h
- [('a', 'b'), ('c', 'd'), ('foo', ' bar'), ('foo', ' bar'), ('x', 'y')]
-
- Headers containing a colon should also work
- >>> output.appendResponseHeaders(['my: brain:hurts'])
- >>> h = output.getHeaders()
- >>> h.sort()
- >>> h
- [('a', 'b'), ('c', 'd'), ('foo', ' bar'), \
-('foo', ' bar'), ('my', ' brain:hurts'), ('x', 'y')]
-
- The headers should not be written to the output
- >>> output.wroteResponseHeader()
- False
- >>> data.getvalue()
- ''
-
- now write something
- >>> output.write('Now for something')
-
- The headers should be sent and the data written to the stream
- >>> output.wroteResponseHeader()
- True
- >>> data.getvalue()
- 'status and headers.Now for something'
-
- calling write again the headers should not be sent again
- >>> output.write(' completly different!')
- >>> data.getvalue()
- 'status and headers.Now for something completly different!'
+class WSGIOutput(object):
+ """This class handles the output generated by the publisher. It is used to
+ collect the headers and as an outstream to output the response body.
"""
+ implements(interfaces.IWSGIOutput)
- implements(IHeaderOutput)
-
def __init__(self, start_response):
self._headers = {}
self._accumulatedHeaders = []
@@ -112,55 +37,41 @@
self.wsgi_write = None
self.start_response = start_response
- def setResponseStatus(self,status, reason):
- """Sets the status code and the accompanying message.
- """
- self._statusString = str(status)+' '+reason
+ def setResponseStatus(self, status, reason):
+ """See zope.publisher.interfaces.http.IHeaderOutput"""
+ self._statusString = str(status) + ' ' + reason
def setResponseHeaders(self, mapping):
- """Sets headers. The headers must be Correctly-Cased.
- """
+ """See zope.publisher.interfaces.http.IHeaderOutput"""
self._headers.update(mapping)
def appendResponseHeaders(self, lst):
- """Sets headers that can potentially repeat.
-
- Takes a list of strings.
- """
+ """See zope.publisher.interfaces.http.IHeaderOutput"""
self._accumulatedHeaders.extend(lst)
def wroteResponseHeader(self):
- """Returns a flag indicating whether the response
-
- header has already been sent.
- """
+ """See zope.publisher.interfaces.http.IHeaderOutput"""
return self._headersSent
def setAuthUserName(self, name):
- """Sets the name of the authenticated user so the name can be logged.
- """
+ """See zope.publisher.interfaces.http.IHeaderOutput"""
pass
def getHeaders(self):
- """return the response headers as a list of tuples according
- to the WSGI spec
- """
+ """See zope.app.wsgi.interfaces.IWSGIOutput"""
response_headers = self._headers.items()
- accum = [ tuple(line.split(':',1)) for line in self._accumulatedHeaders]
+ accum = [tuple(line.split(':', 1))
+ for line in self._accumulatedHeaders]
response_headers.extend(accum)
return response_headers
-
def write(self, data):
- """write the response.
- If the reponse has not begun, call the wsgi servers
- 'start_reponse' callable to begin the response
- """
+ """See zope.app.wsgi.interfaces.IWSGIOutput"""
if not self._headersSent:
self.wsgi_write = self.start_response(self._statusString,
- self.getHeaders())
+ self.getHeaders())
self._headersSent = True
self.wsgi_write(data)
@@ -169,34 +80,33 @@
class PublisherApp(object):
"""A WSGI application implemenation for the zope publisher
- Instances of this class can be used as a WSGI application
- object.
+ Instances of this class can be used as a WSGI application object.
The class relies on a properly initialized request factory.
-
"""
+ implements(interfaces.IWSGIApplication)
-
def __init__(self, db=None, factory=HTTPPublicationRequestFactory):
- self.request_factory = None
+ self.requestFactory = None
if db is not None:
- self.request_factory = factory(db)
+ self.requestFactory = factory(db)
- def __call__(self, env, start_response):
- """makes instances a WSGI callable application object
- """
- wsgi_output = WsgiOutput(start_response)
+ def __call__(self, environ, start_response):
+ """See zope.app.wsgi.interfaces.IWSGIApplication"""
+ # wsgiOutput has two purposes: (1) it is the response headers output
+ # manager and (2) it functions as the output stream for the publisher.
+ wsgiOutput = WSGIOutput(start_response)
- request = self.request_factory( \
- env['wsgi.input'], wsgi_output, env)
+ request = self.requestFactory(
+ environ['wsgi.input'], wsgiOutput, environ)
- request.response.setHeaderOutput(wsgi_output)
+ request.response.setHeaderOutput(wsgiOutput)
publish(request)
request.close()
- # since the response is written using the WSGI write callable
- # return an empty iterable (see
+ # since the response is written using the WSGI ``write()`` callable
+ # return an empty iterable (see PEP 333).
return ""
Added: Zope3/trunk/src/zope/app/wsgi/interfaces.py
===================================================================
--- Zope3/trunk/src/zope/app/wsgi/interfaces.py 2005-04-12 13:58:33 UTC (rev 29948)
+++ Zope3/trunk/src/zope/app/wsgi/interfaces.py 2005-04-12 15:14:51 UTC (rev 29949)
@@ -0,0 +1,74 @@
+##############################################################################
+#
+# Copyright (c) 2005 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""WSGI-specific and compatible interfaces
+
+See PEP-0333 for details.
+
+$Id$
+"""
+__docformat__ = "reStructuredText"
+
+from zope.interface import Interface
+from zope.publisher.interfaces.http import IHeaderOutput
+
+
+class IWSGIOutput(IHeaderOutput):
+ """This class handles the output generated by the publisher. It is used to
+ collect the headers and as an outstream to output the response body.
+ """
+
+ def getHeaders():
+ """Return the response headers.
+
+ The headers will be returned as a list of tuples according to the WSGI
+ specification.
+ """
+
+ def write(data):
+ """Write the response to the server.
+
+ If the reponse has not begun, call the WSGI server's
+ ``start_response()`` callable to begin the response.
+ """
+
+class IWSGIApplication(Interface):
+ """A WSGI application."""
+
+ def __call__(environ, start_response):
+ """Called by a WSGI server.
+
+ The ``environ`` parameter is a dictionary object, containing CGI-style
+ environment variables. This object must be a builtin Python dictionary
+ (not a subclass, UserDict or other dictionary emulation), and the
+ application is allowed to modify the dictionary in any way it
+ desires. The dictionary must also include certain WSGI-required
+ variables (described in a later section), and may also include
+ server-specific extension variables, named according to a convention
+ that will be described below.
+
+ The ``start_response`` parameter is a callable accepting two required
+ positional arguments, and one optional argument. For the sake of
+ illustration, we have named these arguments ``status``,
+ ``response_headers``, and ``exc_info``, but they are not required to
+ have these names, and the application must invoke the
+ ``start_response`` callable using positional arguments
+ (e.g. ``start_response(status, response_headers)``).
+ """
+
+
+class IWSGIServer(Interface):
+ """A WSGI server."""
+
+ def set_application(app):
+ """Tells the server about the application to use."""
Property changes on: Zope3/trunk/src/zope/app/wsgi/interfaces.py
___________________________________________________________________
Name: svn:eol-style
+ native
Modified: Zope3/trunk/src/zope/app/wsgi/tests.py
===================================================================
--- Zope3/trunk/src/zope/app/wsgi/tests.py 2005-04-12 13:58:33 UTC (rev 29948)
+++ Zope3/trunk/src/zope/app/wsgi/tests.py 2005-04-12 15:14:51 UTC (rev 29949)
@@ -16,11 +16,21 @@
$Id$
"""
import unittest
-from zope.testing.doctestunit import DocTestSuite
+from zope.testing import doctest
+from zope.app.testing import setup, ztapi, placelesssetup
+def setUp(test):
+ placelesssetup.setUp(test)
+
+def tearDown(test):
+ placelesssetup.tearDown(test)
+
+
def test_suite():
return unittest.TestSuite((
- DocTestSuite('zope.app.wsgi'),
+ doctest.DocFileSuite('README.txt',
+ setUp=setUp, tearDown=tearDown,
+ optionflags=doctest.NORMALIZE_WHITESPACE),
))
if __name__ == '__main__':
More information about the Zope3-Checkins
mailing list