[Zope3-checkins]
SVN: Zope3/branches/stephan_and_jim-response-refactor/
Got servers running and fixed some tests.
Stephan Richter
srichter at cosmos.phy.tufts.edu
Fri Sep 2 19:12:37 EDT 2005
Log message for revision 38273:
Got servers running and fixed some tests.
Changed:
U Zope3/branches/stephan_and_jim-response-refactor/src/zope/app/wsgi/__init__.py
U Zope3/branches/stephan_and_jim-response-refactor/src/zope/server/http/publisherhttpserver.py
D Zope3/branches/stephan_and_jim-response-refactor/src/zope/server/http/tests/test_publisherserver.py
A Zope3/branches/stephan_and_jim-response-refactor/src/zope/server/http/tests/test_wsgiserver.py
U Zope3/branches/stephan_and_jim-response-refactor/src/zope/server/http/wsgihttpserver.py
U Zope3/branches/stephan_and_jim-response-refactor/zope.conf.in
-=-
Modified: Zope3/branches/stephan_and_jim-response-refactor/src/zope/app/wsgi/__init__.py
===================================================================
--- Zope3/branches/stephan_and_jim-response-refactor/src/zope/app/wsgi/__init__.py 2005-09-02 23:08:40 UTC (rev 38272)
+++ Zope3/branches/stephan_and_jim-response-refactor/src/zope/app/wsgi/__init__.py 2005-09-02 23:12:37 UTC (rev 38273)
@@ -42,8 +42,12 @@
"""See zope.app.wsgi.interfaces.IWSGIApplication"""
request = self.requestFactory(environ['wsgi.input'], environ)
response = request.response
- publish(request)
+ # Let's support post-mortem debugging
+ handle_errors = environ.get('wsgi.handleErrors', True)
+
+ publish(request, handle_errors=handle_errors)
+
# Start the WSGI server response
start_response(response.getStatusString(), response.getHeaders())
Modified: Zope3/branches/stephan_and_jim-response-refactor/src/zope/server/http/publisherhttpserver.py
===================================================================
--- Zope3/branches/stephan_and_jim-response-refactor/src/zope/server/http/publisherhttpserver.py 2005-09-02 23:08:40 UTC (rev 38272)
+++ Zope3/branches/stephan_and_jim-response-refactor/src/zope/server/http/publisherhttpserver.py 2005-09-02 23:12:37 UTC (rev 38273)
@@ -15,70 +15,54 @@
$Id$
"""
-from zope.server.http.httpserver import HTTPServer
+import zope.deprecation
+from zope.server.http import wsgihttpserver
from zope.publisher.publish import publish
import zope.security.management
-class PublisherHTTPServer(HTTPServer):
- """Zope Publisher-specific HTTP Server"""
+class PublisherHTTPServer(wsgihttpserver.WSGIHTTPServer):
def __init__(self, request_factory, sub_protocol=None, *args, **kw):
- # This 'adjustment' to args[0] (the hostname for the HTTP server)
- # under Windows is to get Zope to accept connections from other machines
- # when the host name is omitted from the server address (in zope.conf).
- # The address comes in as 'localhost' from ZConfig by way of some
- # dubious special handling. See collector issue 383 for more info.
- import sys
- if sys.platform[:3] == "win" and args[0] == 'localhost':
- args = ('',) + args[1:]
+ def application(environ, start_response):
+ request = request_factory(environ['wsgi.input'], environ)
+ response = request.response
+ publish(request)
+ start_response(response.getStatusString(), response.getHeaders())
+ return response.result.body
+ return super(PublisherHTTPServer, self).__init__(
+ application, sub_protocol, *args, **kw)
- # The common HTTP
- self.request_factory = request_factory
- # An HTTP server is not limited to serving up HTML; it can be
- # used for other protocols, like XML-RPC, SOAP and so as well
- # Here we just allow the logger to output the sub-protocol type.
- if sub_protocol:
- self.SERVER_IDENT += ' (%s)' %str(sub_protocol)
+class PMDBHTTPServer(wsgihttpserver.WSGIHTTPServer):
- HTTPServer.__init__(self, *args, **kw)
+ def __init__(self, request_factory, sub_protocol=None, *args, **kw):
- def executeRequest(self, task):
- """Overrides HTTPServer.executeRequest()."""
- env = task.getCGIEnvironment()
- instream = task.request_data.getBodyStream()
-
- request = self.request_factory(instream, task, env)
- response = request.response
- response.setHeaderOutput(task)
- response.setHTTPTransaction(task)
- publish(request)
-
-
-class PMDBHTTPServer(PublisherHTTPServer):
- """Enter the post-mortem debugger when there's an error"""
-
- def executeRequest(self, task):
- """Overrides HTTPServer.executeRequest()."""
- env = task.getCGIEnvironment()
- instream = task.request_data.getBodyStream()
-
- request = self.request_factory(instream, task, env)
- response = request.response
- response.setHeaderOutput(task)
- try:
- publish(request, handle_errors=False)
- except:
- import sys, pdb
- print "%s:" % sys.exc_info()[0]
- print sys.exc_info()[1]
- zope.security.management.restoreInteraction()
+ def application(environ, start_response):
+ request = request_factory(environ['wsgi.input'], environ)
+ response = request.response
try:
- pdb.post_mortem(sys.exc_info()[2])
- raise
- finally:
- zope.security.management.endInteraction()
+ publish(request, handle_errors=False)
+ except:
+ import sys, pdb
+ print "%s:" % sys.exc_info()[0]
+ print sys.exc_info()[1]
+ zope.security.management.restoreInteraction()
+ try:
+ pdb.post_mortem(sys.exc_info()[2])
+ raise
+ finally:
+ zope.security.management.endInteraction()
+ start_response(response.getStatusString(), response.getHeaders())
+ return response.result.body
+ return super(PublisherHTTPServer, self).__init__(
+ application, sub_protocol, *args, **kw)
+
+zope.deprecation.deprecated(
+ ('PublisherHTTPServer', 'PMDBHTTPServer'),
+ 'This plain publisher support has been replaced in favor of the '
+ 'WSGI HTTP server '
+ 'The reference will be gone in X3.4.')
Deleted: Zope3/branches/stephan_and_jim-response-refactor/src/zope/server/http/tests/test_publisherserver.py
===================================================================
--- Zope3/branches/stephan_and_jim-response-refactor/src/zope/server/http/tests/test_publisherserver.py 2005-09-02 23:08:40 UTC (rev 38272)
+++ Zope3/branches/stephan_and_jim-response-refactor/src/zope/server/http/tests/test_publisherserver.py 2005-09-02 23:12:37 UTC (rev 38273)
@@ -1,194 +0,0 @@
-##############################################################################
-#
-# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
-#
-# This software is subject to the provisions of the Zope Public License,
-# Version 1.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.
-##############################################################################
-"""Test Puvlisher-based HTTP Server
-
-$Id$
-"""
-import unittest
-from asyncore import socket_map, poll
-from threading import Thread
-
-from zope.server.taskthreads import ThreadedTaskDispatcher
-from zope.server.http.publisherhttpserver import PublisherHTTPServer
-
-from zope.component.testing import PlacelessSetup
-import zope.component
-
-from zope.i18n.interfaces import IUserPreferredCharsets
-
-from zope.publisher.http import IHTTPRequest
-from zope.publisher.http import HTTPCharsets
-from zope.publisher.browser import BrowserRequest
-from zope.publisher.base import DefaultPublication
-from zope.publisher.interfaces import Redirect, Retry
-from zope.publisher.http import HTTPRequest
-
-from httplib import HTTPConnection
-
-from time import sleep
-
-td = ThreadedTaskDispatcher()
-
-LOCALHOST = '127.0.0.1'
-
-HTTPRequest.STAGGER_RETRIES = 0 # Don't pause.
-
-
-class Conflict(Exception):
- """
- Pseudo ZODB conflict error.
- """
-
-
-class PublicationWithConflict(DefaultPublication):
-
- def handleException(self, object, request, exc_info, retry_allowed=1):
- if exc_info[0] is Conflict and retry_allowed:
- # This simulates a ZODB retry.
- raise Retry(exc_info)
- else:
- DefaultPublication.handleException(self, object, request, exc_info,
- retry_allowed)
-
-class Accepted(Exception):
- pass
-
-class tested_object(object):
- """Docstring required by publisher."""
- tries = 0
-
- def __call__(self, REQUEST):
- return 'URL invoked: %s' % REQUEST.URL
-
- def redirect_method(self, REQUEST):
- "Generates a redirect using the redirect() method."
- REQUEST.response.redirect("http://somewhere.com/redirect")
-
- def redirect_exception(self):
- "Generates a redirect using an exception."
- raise Redirect("http://somewhere.com/exception")
-
- def conflict(self, REQUEST, wait_tries):
- """
- Returns 202 status only after (wait_tries) tries.
- """
- if self.tries >= int(wait_tries):
- raise Accepted
- else:
- self.tries += 1
- raise Conflict
-
-
-class Tests(PlacelessSetup, unittest.TestCase):
-
- def setUp(self):
- super(Tests, self).setUp()
- zope.component.provideAdapter(HTTPCharsets, [IHTTPRequest],
- IUserPreferredCharsets, '')
- obj = tested_object()
- obj.folder = tested_object()
- obj.folder.item = tested_object()
-
- obj._protected = tested_object()
-
- pub = PublicationWithConflict(obj)
-
- def request_factory(input_stream, output_steam, env):
- request = BrowserRequest(input_stream, output_steam, env)
- request.setPublication(pub)
- return request
-
- td.setThreadCount(4)
- # Bind to any port on localhost.
- self.server = PublisherHTTPServer(request_factory, 'Browser',
- LOCALHOST, 0, task_dispatcher=td)
- self.port = self.server.socket.getsockname()[1]
- self.run_loop = 1
- self.thread = Thread(target=self.loop)
- self.thread.start()
- sleep(0.1) # Give the thread some time to start.
-
- def tearDown(self):
- self.run_loop = 0
- self.thread.join()
- td.shutdown()
- self.server.close()
-
- def loop(self):
- while self.run_loop:
- poll(0.1, socket_map)
-
- def testResponse(self, path='/', status_expected=200,
- add_headers=None, request_body=''):
- h = HTTPConnection(LOCALHOST, self.port)
- h.putrequest('GET', path)
- h.putheader('Accept', 'text/plain')
- if add_headers:
- for k, v in add_headers.items():
- h.putheader(k, v)
- if request_body:
- h.putheader('Content-Length', str(int(len(request_body))))
- h.endheaders()
- if request_body:
- h.send(request_body)
- response = h.getresponse()
- length = int(response.getheader('Content-Length', '0'))
- if length:
- response_body = response.read(length)
- else:
- response_body = ''
-
- # Please do not disable the status code check. It must work.
- self.failUnlessEqual(int(response.status), status_expected)
-
- self.failUnlessEqual(length, len(response_body))
-
- if (status_expected == 200):
- if path == '/': path = ''
- expect_response = 'URL invoked: http://%s:%d%s' % (LOCALHOST,
- self.port, path)
- self.failUnlessEqual(response_body, expect_response)
-
- def testDeeperPath(self):
- self.testResponse(path='/folder/item')
-
- def testNotFound(self):
- self.testResponse(path='/foo/bar', status_expected=404)
-
- def testUnauthorized(self):
- self.testResponse(path='/_protected', status_expected=401)
-
- def testRedirectMethod(self):
- self.testResponse(path='/redirect_method', status_expected=303)
-
- def testRedirectException(self):
- self.testResponse(path='/redirect_exception', status_expected=303)
- self.testResponse(path='/folder/redirect_exception',
- status_expected=303)
-
- def testConflictRetry(self):
- # Expect the "Accepted" response since the retries will succeed.
- self.testResponse(path='/conflict?wait_tries=2', status_expected=202)
-
- def testFailedConflictRetry(self):
- # Expect a "Conflict" response since there will be too many
- # conflicts.
- self.testResponse(path='/conflict?wait_tries=10', status_expected=409)
-
-
-
-def test_suite():
- loader = unittest.TestLoader()
- return loader.loadTestsFromTestCase(Tests)
-
-if __name__=='__main__':
- unittest.TextTestRunner().run(test_suite())
Copied: Zope3/branches/stephan_and_jim-response-refactor/src/zope/server/http/tests/test_wsgiserver.py (from rev 38226, Zope3/branches/stephan_and_jim-response-refactor/src/zope/server/http/tests/test_publisherserver.py)
===================================================================
--- Zope3/branches/stephan_and_jim-response-refactor/src/zope/server/http/tests/test_publisherserver.py 2005-09-01 18:41:05 UTC (rev 38226)
+++ Zope3/branches/stephan_and_jim-response-refactor/src/zope/server/http/tests/test_wsgiserver.py 2005-09-02 23:12:37 UTC (rev 38273)
@@ -0,0 +1,197 @@
+##############################################################################
+#
+# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 1.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.
+##############################################################################
+"""Test Puvlisher-based HTTP Server
+
+$Id$
+"""
+import unittest
+from asyncore import socket_map, poll
+from threading import Thread
+from time import sleep
+from httplib import HTTPConnection
+
+from zope.server.taskthreads import ThreadedTaskDispatcher
+from zope.server.http.wsgihttpserver import WSGIHTTPServer
+
+from zope.component.testing import PlacelessSetup
+import zope.component
+
+from zope.i18n.interfaces import IUserPreferredCharsets
+
+from zope.publisher.publish import publish
+from zope.publisher.http import IHTTPRequest
+from zope.publisher.http import HTTPCharsets
+from zope.publisher.browser import BrowserRequest
+from zope.publisher.base import DefaultPublication
+from zope.publisher.interfaces import Redirect, Retry
+from zope.publisher.http import HTTPRequest
+
+td = ThreadedTaskDispatcher()
+
+LOCALHOST = '127.0.0.1'
+
+HTTPRequest.STAGGER_RETRIES = 0 # Don't pause.
+
+
+class Conflict(Exception):
+ """
+ Pseudo ZODB conflict error.
+ """
+
+
+class PublicationWithConflict(DefaultPublication):
+
+ def handleException(self, object, request, exc_info, retry_allowed=1):
+ if exc_info[0] is Conflict and retry_allowed:
+ # This simulates a ZODB retry.
+ raise Retry(exc_info)
+ else:
+ DefaultPublication.handleException(self, object, request, exc_info,
+ retry_allowed)
+
+class Accepted(Exception):
+ pass
+
+class tested_object(object):
+ """Docstring required by publisher."""
+ tries = 0
+
+ def __call__(self, REQUEST):
+ return 'URL invoked: %s' % REQUEST.URL
+
+ def redirect_method(self, REQUEST):
+ "Generates a redirect using the redirect() method."
+ REQUEST.response.redirect("http://somewhere.com/redirect")
+
+ def redirect_exception(self):
+ "Generates a redirect using an exception."
+ raise Redirect("http://somewhere.com/exception")
+
+ def conflict(self, REQUEST, wait_tries):
+ """
+ Returns 202 status only after (wait_tries) tries.
+ """
+ if self.tries >= int(wait_tries):
+ raise Accepted
+ else:
+ self.tries += 1
+ raise Conflict
+
+
+class Tests(PlacelessSetup, unittest.TestCase):
+
+ def setUp(self):
+ super(Tests, self).setUp()
+ zope.component.provideAdapter(HTTPCharsets, [IHTTPRequest],
+ IUserPreferredCharsets, '')
+ obj = tested_object()
+ obj.folder = tested_object()
+ obj.folder.item = tested_object()
+
+ obj._protected = tested_object()
+
+ pub = PublicationWithConflict(obj)
+
+ def application(environ, start_response):
+ request = BrowserRequest(environ['wsgi.input'], environ)
+ request.setPublication(pub)
+ response = request.response
+ publish(request)
+ start_response(response.getStatusString(), response.getHeaders())
+ return response.result.body
+
+ td.setThreadCount(4)
+ # Bind to any port on localhost.
+ self.server = WSGIHTTPServer(application, 'Browser',
+ LOCALHOST, 0, task_dispatcher=td)
+
+ self.port = self.server.socket.getsockname()[1]
+ self.run_loop = 1
+ self.thread = Thread(target=self.loop)
+ self.thread.start()
+ sleep(0.1) # Give the thread some time to start.
+
+ def tearDown(self):
+ self.run_loop = 0
+ self.thread.join()
+ td.shutdown()
+ self.server.close()
+
+ def loop(self):
+ while self.run_loop:
+ poll(0.1, socket_map)
+
+ def testResponse(self, path='/', status_expected=200,
+ add_headers=None, request_body=''):
+ h = HTTPConnection(LOCALHOST, self.port)
+ h.putrequest('GET', path)
+ h.putheader('Accept', 'text/plain')
+ if add_headers:
+ for k, v in add_headers.items():
+ h.putheader(k, v)
+ if request_body:
+ h.putheader('Content-Length', str(int(len(request_body))))
+ h.endheaders()
+ if request_body:
+ h.send(request_body)
+ response = h.getresponse()
+ length = int(response.getheader('Content-Length', '0'))
+ if length:
+ response_body = response.read(length)
+ else:
+ response_body = ''
+
+ # Please do not disable the status code check. It must work.
+ self.failUnlessEqual(int(response.status), status_expected)
+
+ self.failUnlessEqual(length, len(response_body))
+
+ if (status_expected == 200):
+ if path == '/': path = ''
+ expect_response = 'URL invoked: http://%s:%d%s' % (LOCALHOST,
+ self.port, path)
+ self.failUnlessEqual(response_body, expect_response)
+
+ def testDeeperPath(self):
+ self.testResponse(path='/folder/item')
+
+ def testNotFound(self):
+ self.testResponse(path='/foo/bar', status_expected=404)
+
+ def testUnauthorized(self):
+ self.testResponse(path='/_protected', status_expected=401)
+
+ def testRedirectMethod(self):
+ self.testResponse(path='/redirect_method', status_expected=303)
+
+ def testRedirectException(self):
+ self.testResponse(path='/redirect_exception', status_expected=303)
+ self.testResponse(path='/folder/redirect_exception',
+ status_expected=303)
+
+ def testConflictRetry(self):
+ # Expect the "Accepted" response since the retries will succeed.
+ self.testResponse(path='/conflict?wait_tries=2', status_expected=202)
+
+ def testFailedConflictRetry(self):
+ # Expect a "Conflict" response since there will be too many
+ # conflicts.
+ self.testResponse(path='/conflict?wait_tries=10', status_expected=409)
+
+
+
+def test_suite():
+ loader = unittest.TestLoader()
+ return loader.loadTestsFromTestCase(Tests)
+
+if __name__=='__main__':
+ unittest.TextTestRunner().run(test_suite())
Modified: Zope3/branches/stephan_and_jim-response-refactor/src/zope/server/http/wsgihttpserver.py
===================================================================
--- Zope3/branches/stephan_and_jim-response-refactor/src/zope/server/http/wsgihttpserver.py 2005-09-02 23:08:40 UTC (rev 38272)
+++ Zope3/branches/stephan_and_jim-response-refactor/src/zope/server/http/wsgihttpserver.py 2005-09-02 23:12:37 UTC (rev 38273)
@@ -19,8 +19,14 @@
import sys
from zope.server.http.httpserver import HTTPServer
from zope.publisher.publish import publish
+import zope.security.management
+def fakeWrite(body):
+ raise NotImplementedError(
+ "Zope 3's HTTP Server does not support the WSGI write() function.")
+
+
class WSGIHTTPServer(HTTPServer):
"""Zope Publisher-specific WSGI-compliant HTTP Server"""
@@ -49,7 +55,41 @@
task.appendResponseHeaders(['%s: %s' % i for i in headers])
# Return the write method used to write the response data.
- return task.write
+ return fakeWrite
# Call the application to handle the request and write a response
- self.application(env, start_response)
+ task.write(''.join(self.application(env, start_response)))
+
+
+class PMDBWSGIHTTPServer(WSGIHTTPServer):
+ """Enter the post-mortem debugger when there's an error"""
+
+ def executeRequest(self, task):
+ """Overrides HTTPServer.executeRequest()."""
+ env = task.getCGIEnvironment()
+ env['wsgi.input'] = task.request_data.getBodyStream()
+ env['wsgi.handleErrors'] = False
+
+ def start_response(status, headers):
+ # Prepare the headers for output
+ status, reason = re.match('([0-9]*) (.*)', status).groups()
+ task.setResponseStatus(status, reason)
+ task.appendResponseHeaders(['%s: %s' % i for i in headers])
+
+ # Return the write method used to write the response data.
+ return fakeWrite
+
+ # Call the application to handle the request and write a response
+ try:
+ task.write(''.join(self.application(env, start_response)))
+ except:
+ import sys, pdb
+ print "%s:" % sys.exc_info()[0]
+ print sys.exc_info()[1]
+ zope.security.management.restoreInteraction()
+ try:
+ pdb.post_mortem(sys.exc_info()[2])
+ raise
+ finally:
+ zope.security.management.endInteraction()
+
Modified: Zope3/branches/stephan_and_jim-response-refactor/zope.conf.in
===================================================================
--- Zope3/branches/stephan_and_jim-response-refactor/zope.conf.in 2005-09-02 23:08:40 UTC (rev 38272)
+++ Zope3/branches/stephan_and_jim-response-refactor/zope.conf.in 2005-09-02 23:12:37 UTC (rev 38273)
@@ -6,7 +6,7 @@
interrupt-check-interval 200
<server http>
- type HTTP
+ type WSGI-HTTP
address 8080
</server>
More information about the Zope3-Checkins
mailing list