[z3-checkins] r10647 - in z3/modzope/trunk/src/modzope: . apache mod_python

philikon at codespeak.net philikon at codespeak.net
Fri Apr 15 00:46:15 MEST 2005


Author: philikon
Date: Fri Apr 15 00:46:15 2005
New Revision: 10647

Added:
   z3/modzope/trunk/src/modzope/TODO
   z3/modzope/trunk/src/modzope/schema.xml
   z3/modzope/trunk/src/modzope/wsgi.py   (contents, props changed)
   z3/modzope/trunk/src/modzope/wsgizope.py   (contents, props changed)
Removed:
   z3/modzope/trunk/src/modzope/apache/
   z3/modzope/trunk/src/modzope/mod_python/
Modified:
   z3/modzope/trunk/src/modzope/app.py
Log:
Total revamp of modzope using WSGI (PEP333). Now that Zope 3 trunk has
a supposedly working implementation of an WSGI application, this seemed
possible.

This uses Phillip Eby's wsgiref reference implementation and some example
code from the web-sig mailinglist regarding using mod_python as a WSGI
server.
The now remaining code is
  a) startup code (app.py, schema.xml)
  b) glue between mod_python's WSGI handler and Zope's WSGI app (wsgizope.py)

If it would only work...


Added: z3/modzope/trunk/src/modzope/TODO
==============================================================================
--- (empty file)
+++ z3/modzope/trunk/src/modzope/TODO	Fri Apr 15 00:46:15 2005
@@ -0,0 +1,8 @@
+TODO
+----
+
+- somehow, the file names specified in modzope.conf are not
+  interpreted as relative to the location of modzope.conf but relative
+  to / !
+
+- nothing works, the system hangs when issuing a request

Modified: z3/modzope/trunk/src/modzope/app.py
==============================================================================
--- z3/modzope/trunk/src/modzope/app.py	(original)
+++ z3/modzope/trunk/src/modzope/app.py	Fri Apr 15 00:46:15 2005
@@ -1,82 +1,55 @@
 ##############################################################################
 #
-# Copyright (c) 2004 Philipp "philiKON" von Weitershausen
+# Copyright (c) 2005 Philipp "philiKON" von Weitershausen
 # All Rights Reserved.
 #
 # This software is subject to the provisions of the Zope Public License,
-# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
+# 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.
 #
 ##############################################################################
-"""Modzope application.
+"""Application initialization
 
 $Id: app.py 218 2004-05-24 04:48:56Z philipp $
 """
-from zope.publisher.interfaces.browser import IBrowserRequest
-from zope.publisher.publish import publish as _publish
-
+import sys
+import os.path
 import zope.app.appsetup
-import zope.app.debug
-import zope.app.event
-from zope.app.publication.browser import BrowserPublication
-
-class Application(object):
-    """Modzope application
-
-    The application takes care of ZCML configuration, database(s) and
-    publishing a request.
-    """
-
-    def __init__(self, db_file, config_file):
-        zope.app.appsetup.config(config_file)
-        # already publishes IDatabaseOpenedEvent
-        self.db = zope.app.appsetup.database(db_file)
-        zope.app.event.publish(None, zope.app.appsetup.ProcessStarting())
-
-    # XXX use IHTTPRequest, HTTPPublication as default?!?
-    def _request(self, context, request_type=IBrowserRequest,
-                 publication=BrowserPublication):
-        pub = publication(self.db)
-        request = request_type(context)
-        request.setPublication(pub)
-        return request
-
-    def publish(self, context, *args, **kw):
-        #XXX take a few arguments more in order to decide which
-        # request/publication to use
-        request = self._request(context)
-
-        # _publish executes request.close() at which point the
-        # response is gone so, let's save it here.
-        response = request.response
-        _publish(request)
-        return response
-
-    def run(self, context, *args, **kw):
-        self.publish(context, *args, **kw)
-
-class DebugApplication(Application, zope.app.debug.Debugger):
-    """Application object useful for debugging"""
-
-_application = None
-
-def getApplication(context):
-    global _application
-    if _application is not None:
-        return _application
+from zope.event import notify
+from zope.app.server.main import ZopeOptions
+from zope.app.wsgi import WSGIPublisherApplication
+
+class ModzopeOptions(ZopeOptions):
+
+    # XXX
+    configfile = "/Users/philipp/dev/Zope3/modzope-demosite/modzope.conf"
+
+def load_options(args=[]):
+    options = ModzopeOptions()
+    options.schemadir = os.path.dirname(os.path.abspath(__file__))
+    options.realize(args)
+    options = options.configroot
+
+    if options.path:
+        sys.path[:0] = [os.path.abspath(p) for p in options.path]
+    return options
+
+def setup(options):
+    sys.setcheckinterval(options.check_interval)
+    zope.app.appsetup.config(options.site_definition)
+    db = options.database.open()
+    notify(zope.app.appsetup.interfaces.DatabaseOpened(db))
+    notify(zope.app.appsetup.interfaces.ProcessStarting())
+    return db
 
+def setupApplication(config=None):
     # waargh
-    import sys
     sys.argv = []
+    # XXX get load_option args from apache conf?
+    db = setup(load_options())
+    return WSGIPublisherApplication(db)
 
-    import os.path
-    config = context.get_options()
-    sitepath = config.get('sitepath', '')
-    db = config.get('dbfile', os.path.join(sitepath, 'Data.fs'))
-    config_file = config.get('siteconfig', os.path.join(sitepath, 'site.zcml'))
-
-    _application = Application(db, config_file)
-    return _application
+application = setupApplication()

Added: z3/modzope/trunk/src/modzope/schema.xml
==============================================================================
--- (empty file)
+++ z3/modzope/trunk/src/modzope/schema.xml	Fri Apr 15 00:46:15 2005
@@ -0,0 +1,62 @@
+<schema>
+  <description>
+    Zope 3 configuration schema.
+
+    This schema describes the configuration options available to a
+    site administrator via the zope.conf configuration file.
+  </description>
+
+  <!-- database and storage types -->
+  <import package="ZODB" />
+
+  <!-- logging configuration -->
+  <import package="ZConfig.components.logger" />
+  <import package="zope.app.server" file="accesslog.xml" />
+
+  <sectiontype name="server" datatype="zope.app.server.server.ServerFactory">
+    <key name="type" required="yes" />
+    <key name="address" datatype="inet-address" />
+    <key name="verbose" datatype="boolean" />
+  </sectiontype>
+
+  <section type="ZODB.database" name="*" required="yes"
+           attribute="database">
+    <description>
+      The main application database that should be used.
+    </description>
+  </section>
+
+  <key name="site-definition" default="site.zcml">
+    <description>
+      The name of the top-level ZCML file that defines the component
+      configuration used for this site.
+    </description>
+  </key>
+
+  <key name="interrupt-check-interval" datatype="integer" default="120"
+       attribute="check_interval">
+    <description>
+      Value passed to Python's sys.setcheckinterval() function.
+
+      This integer value determines how often the interpreter checks
+      for periodic things such as thread switches and signal handlers.
+      Setting it to a larger value may increase performance for
+      programs using threads.  Setting it to a value &lt;= 0 checks every
+      virtual instruction, maximizing responsiveness as well as
+      overhead.
+    </description>
+  </key>
+
+  <multikey name="path" datatype="string">
+    <description>
+      This specifies additional paths directories which are inserted into
+      the beginning of Python's module search path.  The set of directories
+      specified is inserted into the beginning of the module search path in
+      the order which they are specified here.  Note that the processing of
+      this directive may happen too late under some circumstances; it is
+      recommended that you use the PYTHONPATH environment variable if
+      using this directive doesn't work for you.
+    </description>
+    <metadefault>$softwarehome/src</metadefault>
+  </multikey>
+</schema>

Added: z3/modzope/trunk/src/modzope/wsgi.py
==============================================================================
--- (empty file)
+++ z3/modzope/trunk/src/modzope/wsgi.py	Fri Apr 15 00:46:15 2005
@@ -0,0 +1,112 @@
+##############################################################################
+#
+# Copyright (c) 2005 Philipp "philiKON" von Weitershausen
+# 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 glue for mod_python
+
+This aims to implement the WSGI spec v1.0, a.k.a. PEP 333
+(see http://python.org/peps/pep-0333.html)
+
+Largely based on the example by Robert Brewer, posted at
+http://mail.python.org/pipermail/web-sig/2004-October/000980.html
+
+$Id$
+"""
+import sys
+from wsgiref.handlers import BaseCGIHandler
+from mod_python import apache
+
+class ModPythonInputStream(object):
+    
+    def __init__(self, request):
+        self.request = request
+
+    def read(self, size=-1):
+        return self.request.read(size)
+    
+    def readline(self):
+        return self.request.readline()
+    
+    def readlines(self, hint=-1):
+        return self.request.readlines(hint)
+    
+    def __iter__(self):
+        return iter(self.request.readlines())
+
+class ModPythonErrorStream(object):
+    """Error stream
+
+    This enables logging to a custom error log file as defined with a
+    ErrorLog directive.  Errors are printed to sys.stderr end up in
+    the global apache error log file."""
+
+    def __init__(self, request):
+        self.request = request
+        config = request.get_config()
+        self.debug = bool(config.get("PythonDebug", False))
+
+    def flush(self):
+        pass
+
+    def write(self, msg):
+        self.request.log_error(msg)
+        if self.debug:
+            self.request.write(msg)
+
+    def writelines(self, seq):
+	self.write('\r\n'.join(seq))
+
+class ModPythonHandler(BaseCGIHandler):
+    
+    def __init__(self, request):
+        options = request.get_options()
+
+        try:
+            mpm_query = apache.mpm_query
+        except AttributeError:
+            # Threading and forking
+            threaded = options.get('multithread', '')
+            forked = options.get('multiprocess', '')
+            if not (threaded and forked):
+                raise ValueError("You must provide 'multithread' and "
+                                 "'multiprocess' PythonOptions when "
+                                 "running mod_python < 3.1")
+            threaded = threaded.lower() in ('on', 't', 'true', '1')
+            forked = forked.lower() in ('on', 't', 'true', '1')
+        else:
+            threaded = mpm_query(apache.AP_MPMQ_IS_THREADED)
+            forked = mpm_query(apache.AP_MPMQ_IS_FORKED)
+
+        apache_env = apache.build_cgi_env(request)
+
+        BaseCGIHandler.__init__(
+	    self, stdin=ModPythonInputStream(req), stdout=None,
+	    stderr=ModPythonErrorStream(req), environ=env,
+	    multiprocess=forked, multithread=threaded)
+        self.request = request
+        self._write = request.write
+
+    def _flush(self):
+        pass
+
+    #XXX what about status?
+    def send_headers(self):
+        self.cleanup_headers()
+        self.headers_sent = True
+        self.request.status = int(self.status[:3])
+        for name, value in self.headers.items():
+	    if name.lower() == 'content-length':
+		self.request.set_content_length(int(value))
+	    elif name.lower() == 'content-type':
+		self.request.content_type = value
+	    else:
+		self.request.headers_out[name] = value

Added: z3/modzope/trunk/src/modzope/wsgizope.py
==============================================================================
--- (empty file)
+++ z3/modzope/trunk/src/modzope/wsgizope.py	Fri Apr 15 00:46:15 2005
@@ -0,0 +1,25 @@
+##############################################################################
+#
+# Copyright (c) 2005 Philipp "philiKON" von Weitershausen
+# 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.
+#
+##############################################################################
+"""Handler that connects mod_python to Zope's WSGI application
+
+$Id$
+"""
+from mod_python import apache
+from modzope.wsgi import ModPythonHandler
+from modzope.app import application
+
+def handler(request):
+    handler = ModPythonHandler(request)
+    handler.run(application)
+    return apache.OK


More information about the z3-checkins mailing list