Commit a14c654c authored by Lennart Regebro's avatar Lennart Regebro
parents 4630a5bc 59ef1c77
...@@ -48,6 +48,27 @@ Zope Changes ...@@ -48,6 +48,27 @@ Zope Changes
- Using FastCGI is offically deprecated. - Using FastCGI is offically deprecated.
Features added Features added
- Experimental WSGI and Twisted support for http.
Zope now has a WSGI interface for integration with other
web-servers than ZServer. Most notably Twisted is supported.
The WSGI application is ZPublisher.WSGIPublisher.publish_module
You can make ZServer use the twisted interface with the
"use-wsgi on" keyword in the http-server section in zope.conf.
You can run Twisted by installing Twisted (2.1 recommended) and
replacing the http-server section with a server section in
zope.conf. It is not possible to run a Twisted server together with
a ZServer at the same time.
<server>
address 8080
type Zope2-HTTP
</server>
WSGI: http://www.python.org/dev/peps/pep-0333/
Twisted: http://twistedmatrix.com/
- The traversal has been refactored to take heed of Zope3s - The traversal has been refactored to take heed of Zope3s
IPublishTraverse adapter interfaces. The ZCML directives IPublishTraverse adapter interfaces. The ZCML directives
......
...@@ -31,6 +31,11 @@ def shutdown(exit_code,fast = 0): ...@@ -31,6 +31,11 @@ def shutdown(exit_code,fast = 0):
import ZServer import ZServer
ZServer.exit_code = exit_code ZServer.exit_code = exit_code
_shutdown_phase = 1 _shutdown_phase = 1
try:
from twisted.internet import reactor
reactor.callLater(0.1, reactor.stop)
except ImportError:
pass
if fast: if fast:
# Someone wants us to shutdown fast. This is hooked into SIGTERM - so # Someone wants us to shutdown fast. This is hooked into SIGTERM - so
# possibly the system is going down and we can expect a SIGKILL within # possibly the system is going down and we can expect a SIGKILL within
......
...@@ -223,7 +223,8 @@ class TestPythonScriptErrors(PythonScriptTestBase): ...@@ -223,7 +223,8 @@ class TestPythonScriptErrors(PythonScriptTestBase):
def testBadImports(self): def testBadImports(self):
self.assertPSRaises(ImportError, body="from string import *") self.assertPSRaises(ImportError, body="from string import *")
self.assertPSRaises(ImportError, body="import mmap") self.assertPSRaises(ImportError, body="from datetime import datetime")
#self.assertPSRaises(ImportError, body="import mmap")
def testAttributeAssignment(self): def testAttributeAssignment(self):
# It's illegal to assign to attributes of anything that # It's illegal to assign to attributes of anything that
......
...@@ -122,7 +122,6 @@ def publish(request, module_name, after_list, debug=0, ...@@ -122,7 +122,6 @@ def publish(request, module_name, after_list, debug=0,
return response return response
except: except:
# DM: provide nicer error message for FTP # DM: provide nicer error message for FTP
sm = None sm = None
if response is not None: if response is not None:
......
##############################################################################
#
# Copyright (c) 2002 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
#
##############################################################################
__doc__="""Python Object Publisher -- Publish Python objects on web servers
$Id: Publish.py 67721 2006-04-28 14:57:35Z regebro $"""
import sys, os, re, time
import transaction
from Response import Response
from Request import Request
from maybe_lock import allocate_lock
from mapply import mapply
from zExceptions import Redirect
from cStringIO import StringIO
from ZServer.medusa.http_date import build_http_date
class WSGIResponse(Response):
"""A response object for WSGI
This Response object knows nothing about ZServer, but tries to be
compatible with the ZServerHTTPResponse.
Most significantly, streaming is not (yet) supported."""
_streaming = 0
def __str__(self,
html_search=re.compile('<html>',re.I).search,
):
if self._wrote:
if self._chunking:
return '0\r\n\r\n'
else:
return ''
headers=self.headers
body=self.body
# set 204 (no content) status if 200 and response is empty
# and not streaming
if not headers.has_key('content-type') and \
not headers.has_key('content-length') and \
not self._streaming and \
self.status == 200:
self.setStatus('nocontent')
# add content length if not streaming
if not headers.has_key('content-length') and \
not self._streaming:
self.setHeader('content-length',len(body))
content_length= headers.get('content-length', None)
if content_length>0 :
self.setHeader('content-length', content_length)
headersl=[]
append=headersl.append
status=headers.get('status', '200 OK')
# status header must come first.
append("HTTP/%s %s" % (self._http_version or '1.0' , status))
if headers.has_key('status'):
del headers['status']
# add zserver headers
append('Server: %s' % self._server_version)
append('Date: %s' % build_http_date(time.time()))
if self._http_version=='1.0':
if self._http_connection=='keep-alive' and \
self.headers.has_key('content-length'):
self.setHeader('Connection','Keep-Alive')
else:
self.setHeader('Connection','close')
# Close the connection if we have been asked to.
# Use chunking if streaming output.
if self._http_version=='1.1':
if self._http_connection=='close':
self.setHeader('Connection','close')
elif not self.headers.has_key('content-length'):
if self.http_chunk and self._streaming:
self.setHeader('Transfer-Encoding','chunked')
self._chunking=1
else:
self.setHeader('Connection','close')
for key, val in headers.items():
if key.lower()==key:
# only change non-literal header names
key="%s%s" % (key[:1].upper(), key[1:])
start=0
l=key.find('-',start)
while l >= start:
key="%s-%s%s" % (key[:l],key[l+1:l+2].upper(),key[l+2:])
start=l+1
l=key.find('-',start)
append("%s: %s" % (key, val))
if self.cookies:
headersl=headersl+self._cookie_list()
headersl[len(headersl):]=[self.accumulated_headers, body]
return "\r\n".join(headersl)
class Retry(Exception):
"""Raise this to retry a request
"""
def __init__(self, t=None, v=None, tb=None):
self._args=t, v, tb
def reraise(self):
t, v, tb = self._args
if t is None: t=Retry
if tb is None: raise t, v
try: raise t, v, tb
finally: tb=None
def call_object(object, args, request):
result=apply(object,args) # Type s<cr> to step into published object.
return result
def missing_name(name, request):
if name=='self': return request['PARENTS'][0]
request.response.badRequestError(name)
def dont_publish_class(klass, request):
request.response.forbiddenError("class %s" % klass.__name__)
_default_debug_mode = False
_default_realm = None
def set_default_debug_mode(debug_mode):
global _default_debug_mode
_default_debug_mode = debug_mode
def set_default_authentication_realm(realm):
global _default_realm
_default_realm = realm
def publish(request, module_name, after_list, debug=0,
# Optimize:
call_object=call_object,
missing_name=missing_name,
dont_publish_class=dont_publish_class,
mapply=mapply,
):
(bobo_before, bobo_after, object, realm, debug_mode, err_hook,
validated_hook, transactions_manager)= get_module_info(module_name)
parents=None
response=None
try:
request.processInputs()
request_get=request.get
response=request.response
# First check for "cancel" redirect:
if request_get('SUBMIT','').strip().lower()=='cancel':
cancel=request_get('CANCEL_ACTION','')
if cancel:
raise Redirect, cancel
after_list[0]=bobo_after
if debug_mode:
response.debug_mode=debug_mode
if realm and not request.get('REMOTE_USER',None):
response.realm=realm
if bobo_before is not None:
bobo_before()
# Get the path list.
# According to RFC1738 a trailing space in the path is valid.
path=request_get('PATH_INFO')
request['PARENTS']=parents=[object]
if transactions_manager:
transactions_manager.begin()
object=request.traverse(path, validated_hook=validated_hook)
if transactions_manager:
transactions_manager.recordMetaData(object, request)
result=mapply(object, request.args, request,
call_object,1,
missing_name,
dont_publish_class,
request, bind=1)
if result is not response:
response.setBody(result)
if transactions_manager:
transactions_manager.commit()
return response
except:
# DM: provide nicer error message for FTP
sm = None
if response is not None:
sm = getattr(response, "setMessage", None)
if sm is not None:
from asyncore import compact_traceback
cl,val= sys.exc_info()[:2]
sm('%s: %s %s' % (
getattr(cl,'__name__',cl), val,
debug_mode and compact_traceback()[-1] or ''))
if err_hook is not None:
if parents:
parents=parents[0]
try:
try:
return err_hook(parents, request,
sys.exc_info()[0],
sys.exc_info()[1],
sys.exc_info()[2],
)
except Retry:
if not request.supports_retry():
return err_hook(parents, request,
sys.exc_info()[0],
sys.exc_info()[1],
sys.exc_info()[2],
)
finally:
if transactions_manager:
transactions_manager.abort()
# Only reachable if Retry is raised and request supports retry.
newrequest=request.retry()
request.close() # Free resources held by the request.
try:
return publish(newrequest, module_name, after_list, debug)
finally:
newrequest.close()
else:
if transactions_manager:
transactions_manager.abort()
raise
def publish_module_standard(environ, start_response):
must_die=0
status=200
after_list=[None]
stdout = StringIO()
stderr = StringIO()
response = WSGIResponse(stdout=stdout, stderr=stderr)
response._http_version = environ['SERVER_PROTOCOL'].split('/')[1]
response._http_connection = environ.get('CONNECTION_TYPE', 'close')
response._server_version = environ['SERVER_SOFTWARE']
request = Request(environ['wsgi.input'], environ, response)
# Let's support post-mortem debugging
handle_errors = environ.get('wsgi.handleErrors', True)
try:
response = publish(request, 'Zope2', after_list=[None],
debug=handle_errors)
except SystemExit, v:
must_die=sys.exc_info()
request.response.exception(must_die)
except ImportError, v:
if isinstance(v, tuple) and len(v)==3: must_die=v
elif hasattr(sys, 'exc_info'): must_die=sys.exc_info()
else: must_die = SystemExit, v, sys.exc_info()[2]
request.response.exception(1, v)
except:
request.response.exception()
status=response.getStatus()
if response:
# Start the WSGI server response
status = response.getHeader('status')
# ZServerHTTPResponse calculates all headers and things when you
# call it's __str__, so we need to get it, and then munge out
# the headers from it. It's a bit backwards, and we might optimize
# this by not using ZServerHTTPResponse at all, and making the
# HTTPResponses more WSGI friendly. But this works.
result = str(response)
headers, body = result.split('\r\n\r\n',1)
headers = [tuple(n.split(': ',1)) for n in headers.split('\r\n')[1:]]
start_response(status, headers)
# If somebody used response.write, that data will be in the
# stdout StringIO, so we put that before the body.
# XXX This still needs verification that it really works.
result=(stdout.getvalue(), body)
request.close()
stdout.close()
if after_list[0] is not None: after_list[0]()
if must_die:
# Try to turn exception value into an exit code.
try:
if hasattr(must_die[1], 'code'):
code = must_die[1].code
else: code = int(must_die[1])
except:
code = must_die[1] and 1 or 0
if hasattr(request.response, '_requestShutdown'):
request.response._requestShutdown(code)
try: raise must_die[0], must_die[1], must_die[2]
finally: must_die=None
# Return the result body iterable.
return result
_l=allocate_lock()
def get_module_info(module_name, modules={},
acquire=_l.acquire,
release=_l.release,
):
if modules.has_key(module_name): return modules[module_name]
if module_name[-4:]=='.cgi': module_name=module_name[:-4]
acquire()
tb=None
g = globals()
try:
try:
module=__import__(module_name, g, g, ('__doc__',))
# Let the app specify a realm
if hasattr(module,'__bobo_realm__'):
realm=module.__bobo_realm__
elif _default_realm is not None:
realm=_default_realm
else:
realm=module_name
# Check for debug mode
debug_mode=None
if hasattr(module,'__bobo_debug_mode__'):
debug_mode=not not module.__bobo_debug_mode__
else:
debug_mode = _default_debug_mode
bobo_before = getattr(module, "__bobo_before__", None)
bobo_after = getattr(module, "__bobo_after__", None)
if hasattr(module,'bobo_application'):
object=module.bobo_application
elif hasattr(module,'web_objects'):
object=module.web_objects
else: object=module
error_hook=getattr(module,'zpublisher_exception_hook', None)
validated_hook=getattr(module,'zpublisher_validated_hook', None)
transactions_manager=getattr(
module,'zpublisher_transactions_manager', None)
if not transactions_manager:
# Create a default transactions manager for use
# by software that uses ZPublisher and ZODB but
# not the rest of Zope.
transactions_manager = DefaultTransactionsManager()
info= (bobo_before, bobo_after, object, realm, debug_mode,
error_hook, validated_hook, transactions_manager)
modules[module_name]=modules[module_name+'.cgi']=info
return info
except:
t,v,tb=sys.exc_info()
v=str(v)
raise ImportError, (t, v), tb
finally:
tb=None
release()
class DefaultTransactionsManager:
def begin(self):
transaction.begin()
def commit(self):
transaction.commit()
def abort(self):
transaction.abort()
def recordMetaData(self, object, request):
# Is this code needed?
request_get = request.get
T= transaction.get()
T.note(request_get('PATH_INFO'))
auth_user=request_get('AUTHENTICATED_USER',None)
if auth_user is not None:
T.setUser(auth_user, request_get('AUTHENTICATION_PATH'))
# profiling support
_pfile = None # profiling filename
_plock=allocate_lock() # profiling lock
_pfunc=publish_module_standard
_pstat=None
def install_profiling(filename):
global _pfile
_pfile = filename
def pm(environ, start_response):
try:
r=_pfunc(environ, start_response)
except: r=None
sys._pr_=r
def publish_module_profiled(environ, start_response):
import profile, pstats
global _pstat
_plock.acquire()
try:
if request is not None:
path_info=request.get('PATH_INFO')
else: path_info=environ.get('PATH_INFO')
if path_info[-14:]=='manage_profile':
return _pfunc(environ, start_response)
pobj=profile.Profile()
pobj.runcall(pm, menviron, start_response)
result=sys._pr_
pobj.create_stats()
if _pstat is None:
_pstat=sys._ps_=pstats.Stats(pobj)
else: _pstat.add(pobj)
finally:
_plock.release()
if result is None:
try:
error=sys.exc_info()
file=open(_pfile, 'w')
file.write(
"See the url "
"http://www.python.org/doc/current/lib/module-profile.html"
"\n for information on interpreting profiler statistics.\n\n"
)
sys.stdout=file
_pstat.strip_dirs().sort_stats('cumulative').print_stats(250)
_pstat.strip_dirs().sort_stats('time').print_stats(250)
file.flush()
file.close()
except: pass
raise error[0], error[1], error[2]
return result
def publish_module(environ, start_response):
""" publish a Python module, with or without profiling enabled """
if _pfile: # profiling is enabled
return publish_module_profiled(environ, start_response)
else:
return publish_module_standard(environ, start_response)
...@@ -311,6 +311,16 @@ class ChannelPipe: ...@@ -311,6 +311,16 @@ class ChannelPipe:
self._close=1 self._close=1
self._request.reply_code=response.status self._request.reply_code=response.status
def start_response(self, status, headers, exc_info=None):
# Used for WSGI
self._request.reply_code = int(status.split(' ')[0])
status = 'HTTP/%s %s\r\n' % (self._request.version, status)
self.write(status)
headers = '\r\n'.join([': '.join(x) for x in headers])
self.write(headers)
self.write('\r\n\r\n')
return self.write
is_proxying_match = re.compile(r'[^ ]* [^ \\]*:').match is_proxying_match = re.compile(r'[^ ]* [^ \\]*:').match
proxying_connection_re = re.compile ('Proxy-Connection: (.*)', re.IGNORECASE) proxying_connection_re = re.compile ('Proxy-Connection: (.*)', re.IGNORECASE)
......
...@@ -279,6 +279,48 @@ class zhttp_handler: ...@@ -279,6 +279,48 @@ class zhttp_handler:
</ul>""" %(self.module_name, self.hits) </ul>""" %(self.module_name, self.hits)
) )
from HTTPResponse import ChannelPipe
class zwsgi_handler(zhttp_handler):
def continue_request(self, sin, request):
"continue handling request now that we have the stdin"
s=get_header(CONTENT_LENGTH, request.header)
if s:
s=int(s)
else:
s=0
DebugLogger.log('I', id(request), s)
env=self.get_environment(request)
version = request.version
if version=='1.0' and is_proxying_match(request.request):
# a request that was made as if this zope was an http 1.0 proxy.
# that means we have to use some slightly different http
# headers to manage persistent connections.
connection_re = proxying_connection_re
else:
# a normal http request
connection_re = CONNECTION
env['http_connection'] = get_header(connection_re,
request.header).lower()
env['server_version']=request.channel.server.SERVER_IDENT
env['wsgi.output'] = ChannelPipe(request)
env['wsgi.input'] = sin
env['wsgi.errors'] = sys.stderr
env['wsgi.version'] = (1,0)
env['wsgi.multithread'] = True
env['wsgi.multiprocess'] = True
env['wsgi.run_once'] = True
env['wsgi.url_scheme'] = env['SERVER_PROTOCOL'].split('/')[0]
request.channel.current_request=None
request.channel.queue.append(('Zope2WSGI', env,
env['wsgi.output'].start_response))
request.channel.work()
class zhttp_channel(http_channel): class zhttp_channel(http_channel):
......
...@@ -14,13 +14,25 @@ ...@@ -14,13 +14,25 @@
class ZServerPublisher: class ZServerPublisher:
def __init__(self, accept): def __init__(self, accept):
from ZPublisher import publish_module from ZPublisher import publish_module
from ZPublisher.WSGIPublisher import publish_module as publish_wsgi
while 1: while 1:
try: name, a, b=accept()
name, request, response=accept() if name == "Zope2":
publish_module( try:
name, publish_module(
request=request, name,
response=response) request=a,
finally: response=b)
response._finish() finally:
request=response=None b._finish()
a=b=None
elif name == "Zope2WSGI":
try:
res = publish_wsgi(a, b)
for r in res:
a['wsgi.output'].write(r)
finally:
# TODO: Support keeping connections open.
a['wsgi.output']._close = 1
a['wsgi.output'].close()
...@@ -19,6 +19,7 @@ ...@@ -19,6 +19,7 @@
receive WebDAV source responses to GET requests. receive WebDAV source responses to GET requests.
</description> </description>
</key> </key>
<key name="use-wsgi" datatype="boolean" default="off" />
</sectiontype> </sectiontype>
<sectiontype name="webdav-source-server" <sectiontype name="webdav-source-server"
...@@ -26,6 +27,7 @@ ...@@ -26,6 +27,7 @@
implements="ZServer.server"> implements="ZServer.server">
<key name="address" datatype="inet-binding-address"/> <key name="address" datatype="inet-binding-address"/>
<key name="force-connection-close" datatype="boolean" default="off"/> <key name="force-connection-close" datatype="boolean" default="off"/>
<key name="use-wsgi" datatype="boolean" default="off" />
</sectiontype> </sectiontype>
<sectiontype name="persistent-cgi" <sectiontype name="persistent-cgi"
......
...@@ -71,6 +71,7 @@ class HTTPServerFactory(ServerFactory): ...@@ -71,6 +71,7 @@ class HTTPServerFactory(ServerFactory):
# webdav-source-server sections won't have webdav_source_clients: # webdav-source-server sections won't have webdav_source_clients:
webdav_clients = getattr(section, "webdav_source_clients", None) webdav_clients = getattr(section, "webdav_source_clients", None)
self.webdav_source_clients = webdav_clients self.webdav_source_clients = webdav_clients
self.use_wsgi = section.use_wsgi
def create(self): def create(self):
from ZServer.AccessLogger import access_logger from ZServer.AccessLogger import access_logger
...@@ -86,7 +87,10 @@ class HTTPServerFactory(ServerFactory): ...@@ -86,7 +87,10 @@ class HTTPServerFactory(ServerFactory):
def createHandler(self): def createHandler(self):
from ZServer import HTTPServer from ZServer import HTTPServer
return HTTPServer.zhttp_handler(self.module, '', self.cgienv) if self.use_wsgi:
return HTTPServer.zwsgi_handler(self.module, '', self.cgienv)
else:
return HTTPServer.zhttp_handler(self.module, '', self.cgienv)
class WebDAVSourceServerFactory(HTTPServerFactory): class WebDAVSourceServerFactory(HTTPServerFactory):
......
...@@ -20,12 +20,16 @@ import sys ...@@ -20,12 +20,16 @@ import sys
import socket import socket
from re import compile from re import compile
from socket import gethostbyaddr from socket import gethostbyaddr
try:
import twisted.internet.reactor
_use_twisted = True
except ImportError:
_use_twisted = True
import ZConfig import ZConfig
from ZConfig.components.logger import loghandler from ZConfig.components.logger import loghandler
logger = logging.getLogger("Zope") logger = logging.getLogger("Zope")
started = False started = False
...@@ -96,7 +100,10 @@ class ZopeStarter: ...@@ -96,7 +100,10 @@ class ZopeStarter:
self.makePidFile() self.makePidFile()
self.setupInterpreter() self.setupInterpreter()
self.startZope() self.startZope()
self.registerSignals() from App.config import getConfiguration
config = getConfiguration()
if not config.twisted_servers:
self.registerSignals()
# emit a "ready" message in order to prevent the kinds of emails # emit a "ready" message in order to prevent the kinds of emails
# to the Zope maillist in which people claim that Zope has "frozen" # to the Zope maillist in which people claim that Zope has "frozen"
# after it has emitted ZServer messages. # after it has emitted ZServer messages.
...@@ -106,10 +113,24 @@ class ZopeStarter: ...@@ -106,10 +113,24 @@ class ZopeStarter:
def run(self): def run(self):
# the mainloop. # the mainloop.
try: try:
from App.config import getConfiguration
config = getConfiguration()
import ZServer import ZServer
import Lifetime if config.twisted_servers and config.servers:
Lifetime.loop() raise ZConfig.ConfigurationError(
sys.exit(ZServer.exit_code) "You can't run both ZServer servers and twisted servers.")
if config.twisted_servers:
if not _use_twisted:
raise ZConfig.ConfigurationError(
"You do not have twisted installed.")
twisted.internet.reactor.run()
# Storing the exit code in the ZServer even for twisted,
# but hey, it works...
sys.exit(ZServer.exit_code)
else:
import Lifetime
Lifetime.loop()
sys.exit(ZServer.exit_code)
finally: finally:
self.shutdown() self.shutdown()
......
...@@ -339,3 +339,11 @@ def zopeClassFactory(jar, module, name, ...@@ -339,3 +339,11 @@ def zopeClassFactory(jar, module, name,
# Zope class factory." This no longer works with the implementation of # Zope class factory." This no longer works with the implementation of
# mounted databases, so we just use the zopeClassFactory as the default # mounted databases, so we just use the zopeClassFactory as the default
try:
from zope.app.twisted.server import ServerFactory
class TwistedServerFactory(ServerFactory):
pass
except ImportError:
class TwistedServerFactory:
def __init__(self, section):
raise ImportError("You do not have twisted installed.")
import os import os
import sys import sys
import time
import logging
from re import compile from re import compile
from socket import gethostbyaddr from socket import gethostbyaddr
try:
import twisted.internet
from twisted.application.service import MultiService
import zope.app.appsetup.interfaces
import zope.app.twisted.main
import twisted.web2.wsgi
import twisted.web2.server
import twisted.web2.log
try:
from twisted.web2.http import HTTPFactory
except ImportError:
from twisted.web2.channel.http import HTTPFactory
from zope.component import provideUtility
from zope.app.twisted.server import ServerType, SSLServerType
from zope.app.twisted.interfaces import IServerType
from ZPublisher.WSGIPublisher import publish_module
_use_twisted = True
except ImportError:
_use_twisted = False
# top-level key handlers # top-level key handlers
...@@ -133,7 +159,7 @@ def catalog_getObject_raises(value): ...@@ -133,7 +159,7 @@ def catalog_getObject_raises(value):
"'catalog-getObject-raises' option will be removed in Zope 2.10:\n", "'catalog-getObject-raises' option will be removed in Zope 2.10:\n",
DeprecationWarning) DeprecationWarning)
from Products.ZCatalog import CatalogBrains from Products.ZCatalog import CatalogBrains
CatalogBrains.GETOBJECT_RAISES = bool(value) CatalogBrains.GETOBJECT_RAISES = bool(value)
return value return value
...@@ -143,7 +169,8 @@ def catalog_getObject_raises(value): ...@@ -143,7 +169,8 @@ def catalog_getObject_raises(value):
def root_handler(config): def root_handler(config):
""" Mutate the configuration with defaults and perform """ Mutate the configuration with defaults and perform
fixups of values that require knowledge about configuration fixups of values that require knowledge about configuration
values outside of their context. """ values outside of their context.
"""
# Set environment variables # Set environment variables
for k,v in config.environment.items(): for k,v in config.environment.items():
...@@ -165,7 +192,7 @@ def root_handler(config): ...@@ -165,7 +192,7 @@ def root_handler(config):
instanceprod = os.path.join(config.instancehome, 'Products') instanceprod = os.path.join(config.instancehome, 'Products')
if instanceprod not in config.products: if instanceprod not in config.products:
config.products.append(instanceprod) config.products.append(instanceprod)
import Products import Products
L = [] L = []
for d in config.products + Products.__path__: for d in config.products + Products.__path__:
...@@ -190,6 +217,23 @@ def root_handler(config): ...@@ -190,6 +217,23 @@ def root_handler(config):
config.cgi_environment, config.cgi_environment,
config.port_base) config.port_base)
if not config.twisted_servers:
config.twisted_servers = []
else:
# Set number of threads (reuse zserver_threads variable)
twisted.internet.reactor.suggestThreadPoolSize(config.zserver_threads)
# Create a root service
rootService = MultiService()
for server in config.twisted_servers:
service = server.create(None)
service.setServiceParent(rootService)
rootService.startService()
twisted.internet.reactor.addSystemEventTrigger(
'before', 'shutdown', rootService.stopService)
# set up trusted proxies # set up trusted proxies
if config.trusted_proxies: if config.trusted_proxies:
import ZPublisher.HTTPRequest import ZPublisher.HTTPRequest
...@@ -217,3 +261,15 @@ def _name2Ips(host, isIp_=compile(r'(\d+\.){3}').match): ...@@ -217,3 +261,15 @@ def _name2Ips(host, isIp_=compile(r'(\d+\.){3}').match):
if isIp_(host): return [host] if isIp_(host): return [host]
return gethostbyaddr(host)[2] return gethostbyaddr(host)[2]
# Twisted support:
def createHTTPFactory(ignored):
resource = twisted.web2.wsgi.WSGIResource(publish_module)
resource = twisted.web2.log.LogWrapperResource(resource)
return HTTPFactory(twisted.web2.server.Site(resource))
if _use_twisted:
http = ServerType(createHTTPFactory, 8080)
provideUtility(http, IServerType, 'Zope2-HTTP')
...@@ -11,6 +11,12 @@ ...@@ -11,6 +11,12 @@
<import package="tempstorage"/> <import package="tempstorage"/>
<import package="Zope2.Startup" file="warnfilter.xml"/> <import package="Zope2.Startup" file="warnfilter.xml"/>
<sectiontype name="server" datatype="Zope2.Startup.datatypes.TwistedServerFactory">
<key name="type" required="yes" />
<key name="address" datatype="inet-address" />
<key name="backlog" datatype="integer" default="50" />
</sectiontype>
<sectiontype name="logger" datatype=".LoggerFactory"> <sectiontype name="logger" datatype=".LoggerFactory">
<description> <description>
This "logger" type only applies to access and request ("trace") This "logger" type only applies to access and request ("trace")
...@@ -805,7 +811,9 @@ ...@@ -805,7 +811,9 @@
<metadefault>on</metadefault> <metadefault>on</metadefault>
</key> </key>
<multisection type="server" name="*" attribute="twisted_servers" />
<multisection type="ZServer.server" name="*" attribute="servers"/> <multisection type="ZServer.server" name="*" attribute="servers"/>
<key name="port-base" datatype="integer" default="0"> <key name="port-base" datatype="integer" default="0">
<description> <description>
Base port number that gets added to the specific port numbers Base port number that gets added to the specific port numbers
......
...@@ -904,6 +904,8 @@ instancehome $INSTANCE ...@@ -904,6 +904,8 @@ instancehome $INSTANCE
# valid keys are "address" and "force-connection-close" # valid keys are "address" and "force-connection-close"
address 8080 address 8080
# force-connection-close on # force-connection-close on
# You can also use the WSGI interface between ZServer and ZPublisher:
# use-wsgi on
</http-server> </http-server>
# Examples: # Examples:
...@@ -947,6 +949,13 @@ instancehome $INSTANCE ...@@ -947,6 +949,13 @@ instancehome $INSTANCE
# user admin # user admin
# password 123 # password 123
# </clock-server> # </clock-server>
#
# <server>
# # This uses Twisted as the web-server. You must install Twisted
# # separately. You can't run Twisted and ZServer at same time.
# address 8080
# type Zope2-HTTP
# </server>
# Database (zodb_db) section # Database (zodb_db) section
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment