[Zope-Checkins] CVS: Zope/lib/python/ZServer - AccessLogger.py:1.2 DebugLogger.py:1.10 FCGIServer.py:1.22 FTPRequest.py:1.16 FTPResponse.py:1.13 FTPServer.py:1.26 HTTPResponse.py:1.43 HTTPServer.py:1.45 ICPServer.py:1.5 INSTALL.txt:1.8 PCGIServer.py:1.26 Producers.py:1.9 README.txt:1.12 WebDAVSrcHandler.py:1.10 ZService.py:1.16 __init__.py:1.29 component.xml:1.2 datatypes.py:1.2
Fred L. Drake, Jr.
fred@zope.com
Tue, 18 Mar 2003 16:15:47 -0500
Update of /cvs-repository/Zope/lib/python/ZServer
In directory cvs.zope.org:/tmp/cvs-serv23589/ZServer
Added Files:
AccessLogger.py DebugLogger.py FCGIServer.py FTPRequest.py
FTPResponse.py FTPServer.py HTTPResponse.py HTTPServer.py
ICPServer.py INSTALL.txt PCGIServer.py Producers.py README.txt
WebDAVSrcHandler.py ZService.py __init__.py component.xml
datatypes.py
Log Message:
Move ZServer into new location, including configuration support from the
new-install-branch.
=== Zope/lib/python/ZServer/AccessLogger.py 1.1 => 1.2 ===
--- /dev/null Tue Mar 18 16:15:46 2003
+++ Zope/lib/python/ZServer/AccessLogger.py Tue Mar 18 16:15:14 2003
@@ -0,0 +1,31 @@
+##############################################################################
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# 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.
+# 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
+#
+##############################################################################
+"""
+A logging module which handles ZServer access log messages.
+
+This depends on Vinay Sajip's PEP 282 logging module.
+"""
+import logging
+from zLOG.BaseLogger import BaseLogger
+
+class AccessLogger(BaseLogger):
+ logger = logging.getLogger('access')
+ def log(self, message):
+ if not self.logger.handlers: # dont log if we have no handlers
+ return
+ if message.endswith('\n'):
+ message = message[:-1]
+ self.logger.warn(message)
+
+access_logger = AccessLogger()
=== Zope/lib/python/ZServer/DebugLogger.py 1.9 => 1.10 ===
--- /dev/null Tue Mar 18 16:15:46 2003
+++ Zope/lib/python/ZServer/DebugLogger.py Tue Mar 18 16:15:14 2003
@@ -0,0 +1,57 @@
+##############################################################################
+#
+# Copyright (c) 2001 Zope Corporation and Contributors. 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.
+# 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
+#
+##############################################################################
+
+"""
+Logs debugging information about how ZServer is handling requests
+and responses. This log can be used to help locate troublesome requests.
+
+The format of a log message is:
+
+ <code> <request id> <time> <data>
+
+where:
+
+ <code> is B for begin, I for received input, A for received output,
+ E for sent output.
+
+ <request id> is a unique request id.
+
+ <time> is the local time in ISO 6801 format.
+
+ <data> is the HTTP method and the PATH INFO for B, the size of the
+ input for I, the HTTP status code and the size of the output for
+ A, or nothing for E.
+"""
+
+import time
+import logging
+
+from zLOG.BaseLogger import BaseLogger
+
+
+class DebugLogger(BaseLogger):
+
+ logger = logging.getLogger('trace')
+
+ def log(self, code, request_id, data=''):
+ if not self.logger.handlers:
+ return
+ # Omitting the second parameter requires Python 2.2 or newer.
+ t = time.strftime('%Y-%m-%dT%H:%M:%S')
+ message = '%s %s %s %s' % (code, request_id, t, data)
+ self.logger.warn(message)
+
+
+debug_logger = DebugLogger()
+log = debug_logger.log
+reopen = debug_logger.reopen
=== Zope/lib/python/ZServer/FCGIServer.py 1.21 => 1.22 === (669/769 lines abridged)
--- /dev/null Tue Mar 18 16:15:46 2003
+++ Zope/lib/python/ZServer/FCGIServer.py Tue Mar 18 16:15:14 2003
@@ -0,0 +1,766 @@
+##############################################################################
+#
+# Copyright (c) 2001 Zope Corporation and Contributors. 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.
+# 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
+#
+##############################################################################
+
+"""
+ZServer/Medusa FastCGI server, by Robin Dunn.
+
+Accepts connections from a FastCGI enabled webserver, receives request
+info using the FastCGi protocol, and then hands the request off to
+ZPublisher for processing. The response is then handed back to the
+webserver to send down to the browser.
+
+See http://www.fastcgi.com/fcgi-devkit-2.1/doc/fcgi-spec.html for the
+protocol specificaition.
+"""
+
+__version__ = "1.0"
+
+#----------------------------------------------------------------------
+
+import asynchat, asyncore
+from medusa import logger
+from medusa.counter import counter
+from medusa.http_server import compute_timezone_for_log
+
+from ZServer import CONNECTION_LIMIT, requestCloseOnExec
+
+from PubCore import handle
+from PubCore.ZEvent import Wakeup
+from ZPublisher.HTTPResponse import HTTPResponse
+from ZPublisher.HTTPRequest import HTTPRequest
+from Producers import ShutdownProducer, LoggingProducer, file_part_producer, file_close_producer
+
+import DebugLogger
+
+from cStringIO import StringIO
+from tempfile import TemporaryFile
+import socket, string, os, sys, time
[-=- -=- -=- 669 lines omitted -=- -=- -=-]
+ if t is not None:
+ self.stdout.write((file_close_producer(t), 0))
+ self._tempfile=None
+
+ self.channel.sendStreamTerminator(FCGI_STDOUT)
+ self.channel.sendEndRecord()
+ self.stdout.close()
+ self.stderr.close()
+
+ if not self.channel.closed:
+ self.channel.push_with_producer(LoggingProducer(self.channel,
+ self.stdout.length,
+ 'log_request'), 0)
+ if self._shutdownRequested():
+ self.channel.push(ShutdownProducer(), 0)
+ Wakeup(lambda: asyncore.close_all())
+ else:
+ self.channel.push(None,0)
+ Wakeup()
+ self.channel=None
+
+
+
+#----------------------------------------------------------------------
+
+class FCGIPipe:
+ """
+ This class acts like a file and is used to catch stdout/stderr
+ from ZPublisher and create FCGI records out of the data stream to
+ send back to the web server.
+ """
+ def __init__(self, channel, recType):
+ self.channel = channel
+ self.recType = recType
+ self.length = 0
+
+ def write(self, data):
+ if type(data)==type(''):
+ datalen = len(data)
+ else:
+ p, datalen = data
+ if data:
+ self.channel.sendDataRecord(data, self.recType)
+ self.length = self.length + datalen
+
+ def close(self):
+ self.channel = None
+
+
+#----------------------------------------------------------------------
=== Zope/lib/python/ZServer/FTPRequest.py 1.15 => 1.16 ===
--- /dev/null Tue Mar 18 16:15:46 2003
+++ Zope/lib/python/ZServer/FTPRequest.py Tue Mar 18 16:15:14 2003
@@ -0,0 +1,124 @@
+##############################################################################
+#
+# Copyright (c) 2001 Zope Corporation and Contributors. 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.
+# 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
+#
+##############################################################################
+"""
+FTP Request class for FTP server.
+
+The FTP Request does the dirty work of turning an FTP request into something
+that ZPublisher can understand.
+"""
+
+from ZPublisher.HTTPRequest import HTTPRequest
+
+from cStringIO import StringIO
+import os
+from base64 import encodestring
+import re
+
+class FTPRequest(HTTPRequest):
+
+ def __init__(self, path, command, channel, response, stdin=None,
+ environ=None,globbing=None,recursive=0):
+
+ # we need to store the globbing information to pass it
+ # to the ZPublisher and the manage_FTPlist function
+ # (ajung)
+ self.globbing = globbing
+ self.recursive= recursive
+
+ if stdin is None: stdin=StringIO()
+ if environ is None:
+ environ=self._get_env(path, command, channel, stdin)
+
+ self._orig_env=environ
+ HTTPRequest.__init__(self, stdin, environ, response, clean=1)
+
+ # support for cookies and cookie authentication
+ self.cookies=channel.cookies
+ if not self.cookies.has_key('__ac') and channel.userid != 'anonymous':
+ self.other['__ac_name']=channel.userid
+ self.other['__ac_password']=channel.password
+ for k,v in self.cookies.items():
+ if not self.other.has_key(k):
+ self.other[k]=v
+
+
+ def retry(self):
+ self.retry_count=self.retry_count+1
+ r=self.__class__(stdin=self.stdin,
+ environ=self._orig_env,
+ response=self.response.retry(),
+ channel=self, # For my cookies
+ )
+ return r
+
+ def _get_env(self, path, command, channel, stdin):
+ "Returns a CGI style environment"
+ env={}
+ env['SCRIPT_NAME']='/%s' % channel.module
+ env['REQUEST_METHOD']='GET' # XXX what should this be?
+ env['SERVER_SOFTWARE']=channel.server.SERVER_IDENT
+ if channel.userid != 'anonymous':
+ env['HTTP_AUTHORIZATION']='Basic %s' % re.sub('\012','',
+ encodestring('%s:%s' % (channel.userid, channel.password)))
+ env['SERVER_NAME']=channel.server.hostname
+ env['SERVER_PORT']=str(channel.server.port)
+ env['REMOTE_ADDR']=channel.client_addr[0]
+ env['GATEWAY_INTERFACE']='CGI/1.1' # that's stretching it ;-)
+
+ # FTP commands
+ #
+ if type(command)==type(()):
+ args=command[1:]
+ command=command[0]
+ if command in ('LST','CWD','PASS'):
+ env['PATH_INFO']=self._join_paths(channel.path,
+ path, 'manage_FTPlist')
+ elif command in ('MDTM','SIZE'):
+ env['PATH_INFO']=self._join_paths(channel.path,
+ path, 'manage_FTPstat')
+ elif command=='RETR':
+ env['PATH_INFO']=self._join_paths(channel.path,
+ path, 'manage_FTPget')
+ elif command in ('RMD','DELE'):
+ env['PATH_INFO']=self._join_paths(channel.path,
+ path, 'manage_delObjects')
+ env['QUERY_STRING']='ids=%s' % args[0]
+ elif command=='MKD':
+ env['PATH_INFO']=self._join_paths(channel.path,
+ path, 'manage_addFolder')
+ env['QUERY_STRING']='id=%s' % args[0]
+
+ elif command=='RNTO':
+ env['PATH_INFO']=self._join_paths(channel.path,
+ path, 'manage_renameObject')
+ env['QUERY_STRING']='id=%s&new_id=%s' % (args[0],args[1])
+
+ elif command=='STOR':
+ env['PATH_INFO']=self._join_paths(channel.path, path)
+ env['REQUEST_METHOD']='PUT'
+ env['CONTENT_LENGTH']=len(stdin.getvalue())
+ else:
+ env['PATH_INFO']=self._join_paths(channel.path, path, command)
+
+ # Fake in globbing information
+ env['GLOBBING'] = self.globbing
+ env['FTP_RECURSIVE'] = self.recursive
+
+ return env
+
+ def _join_paths(self,*args):
+ path=apply(os.path.join,args)
+ path=os.path.normpath(path)
+ if os.sep != '/':
+ path=path.replace(os.sep,'/')
+ return path
=== Zope/lib/python/ZServer/FTPResponse.py 1.12 => 1.13 ===
--- /dev/null Tue Mar 18 16:15:46 2003
+++ Zope/lib/python/ZServer/FTPResponse.py Tue Mar 18 16:15:14 2003
@@ -0,0 +1,95 @@
+##############################################################################
+#
+# Copyright (c) 2001 Zope Corporation and Contributors. 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.
+# 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
+#
+##############################################################################
+"""
+Response class for the FTP Server.
+"""
+
+from ZServer.HTTPResponse import ZServerHTTPResponse
+from PubCore.ZEvent import Wakeup
+from cStringIO import StringIO
+import marshal
+
+
+class FTPResponse(ZServerHTTPResponse):
+ """
+ Response to an FTP command
+ """
+
+ def __str__(self):
+# return ZServerHTTPResponse.__str__(self)
+ # ZServerHTTPResponse.__str__(self) return HTTP headers
+ # Why should be send them to the FTP client ??? (ajung)
+ return ''
+
+ def outputBody(self):
+ pass
+
+ def setCookie(self, name, value, **kw):
+ self.cookies[name]=value
+
+ def appendCookie(self, name, value):
+ self.cookies[name]=self.cookies[name] + value
+
+ def expireCookie(self, name, **kw):
+ if self.cookies.has_key(name):
+ del self.cookies[name]
+
+ def _cookie_list(self):
+ return []
+
+ def _marshalledBody(self):
+ return marshal.loads(self.body)
+
+ def setMessage(self, message):
+ self._message = message
+
+ def getMessage(self):
+ return getattr(self, '_message', '')
+
+class CallbackPipe:
+ """
+ Sends response object to a callback. Doesn't write anything.
+ The callback takes place in Medusa's thread, not the request thread.
+ """
+ def __init__(self, callback, args):
+ self._callback=callback
+ self._args=args
+ self._producers=[]
+
+ def close(self):
+ pass
+
+ def write(self, text, l=None):
+ if text:
+ self._producers.append(text)
+
+ def finish(self, response):
+ self._response=response
+ Wakeup(self.apply) # move callback to medusas thread
+
+ def apply(self):
+ result=apply(self._callback, self._args+(self._response,))
+
+ # break cycles
+ self._callback=None
+ self._response=None
+ self._args=None
+
+ return result
+
+def make_response(channel, callback, *args):
+ # XXX should this be the FTPResponse constructor instead?
+ r=FTPResponse(stdout=CallbackPipe(callback, args), stderr=StringIO())
+ r.setHeader('content-type','text/plain')
+ r.cookies=channel.cookies
+ return r
=== Zope/lib/python/ZServer/FTPServer.py 1.25 => 1.26 === (545/645 lines abridged)
--- /dev/null Tue Mar 18 16:15:46 2003
+++ Zope/lib/python/ZServer/FTPServer.py Tue Mar 18 16:15:14 2003
@@ -0,0 +1,642 @@
+##############################################################################
+#
+# Copyright (c) 2001 Zope Corporation and Contributors. 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.
+# 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
+#
+##############################################################################
+
+"""ZServer FTP Channel for use the medusa's ftp server.
+
+FTP Service for Zope.
+
+ This server allows FTP connections to Zope. In general FTP is used
+ to manage content. You can:
+
+ * Create and delete Folders, Documents, Files, and Images
+
+ * Edit the contents of Documents, Files, Images
+
+ In the future, FTP may be used to edit object properties.
+
+FTP Protocol
+
+ The FTP protocol for Zope gives Zope objects a way to make themselves
+ available to FTP services. See the 'lib/python/OFS/FTPInterface.py' for
+ more details.
+
+FTP Permissions
+
+ FTP access is controlled by one permission: 'FTP access' if bound to a
+ role, users of that role will be able to list directories, and cd to
+ them. Creating and deleting and changing objects are all governed by
+ existing Zope permissions.
+
+ Permissions are to a certain extent reflected in the permission bits
+ listed in FTP file listings.
+
+FTP Authorization
+
+ Zope supports both normal and anonymous logins. It can be difficult
+ to authorize Zope users since they are defined in distributed user
+ databases. Normally, all logins will be accepted and then the user must
[-=- -=- -=- 545 lines omitted -=- -=- -=-]
+class FTPServer(ftp_server):
+ """FTP server for Zope."""
+
+ ftp_channel_class = zope_ftp_channel
+ limiter=FTPLimiter(10,1)
+ shutup=0
+
+ def __init__(self,module,*args,**kw):
+ self.shutup=1
+ apply(ftp_server.__init__, (self, None) + args, kw)
+ self.shutup=0
+ self.module=module
+ self.log_info('FTP server started at %s\n'
+ '\tHostname: %s\n\tPort: %d' % (
+ time.ctime(time.time()),
+ self.hostname,
+ self.port
+ ))
+
+ def clean_shutdown_control(self,phase,time_in_this_phase):
+ if phase==2:
+ self.log_info('closing FTP to new connections')
+ self.close()
+
+ def log_info(self, message, type='info'):
+ if self.shutup: return
+ asyncore.dispatcher.log_info(self, message, type)
+
+ def create_socket(self, family, type):
+ asyncore.dispatcher.create_socket(self, family, type)
+ requestCloseOnExec(self.socket)
+
+ def handle_accept (self):
+ try:
+ conn, addr = self.accept()
+ except TypeError:
+ # unpack non-sequence as result of accept
+ # returning None (in case of EWOULDBLOCK)
+ return
+ self.total_sessions.increment()
+ self.log_info('Incoming connection from %s:%d' % (addr[0], addr[1]))
+ self.ftp_channel_class (self, conn, addr, self.module)
+
+ def readable(self):
+ return len(asyncore.socket_map) < CONNECTION_LIMIT
+
+ def listen(self, num):
+ # override asyncore limits for nt's listen queue size
+ self.accepting = 1
+ return self.socket.listen (num)
=== Zope/lib/python/ZServer/HTTPResponse.py 1.42 => 1.43 ===
--- /dev/null Tue Mar 18 16:15:46 2003
+++ Zope/lib/python/ZServer/HTTPResponse.py Tue Mar 18 16:15:14 2003
@@ -0,0 +1,310 @@
+##############################################################################
+#
+# Copyright (c) 2001 Zope Corporation and Contributors. 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.
+# 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
+#
+##############################################################################
+"""
+ZServer HTTPResponse
+
+The HTTPResponse class takes care of server headers, response munging
+and logging duties.
+
+"""
+import time, re, sys, tempfile
+from cStringIO import StringIO
+import thread
+from ZPublisher.HTTPResponse import HTTPResponse
+from medusa.http_date import build_http_date
+from PubCore.ZEvent import Wakeup
+from medusa.producers import hooked_producer
+from medusa import http_server
+import asyncore
+from Producers import ShutdownProducer, LoggingProducer, CallbackProducer, \
+ file_part_producer, file_close_producer
+from types import LongType
+import DebugLogger
+
+
+class ZServerHTTPResponse(HTTPResponse):
+ "Used to push data into a channel's producer fifo"
+
+ # Set this value to 1 if streaming output in
+ # HTTP/1.1 should use chunked encoding
+ http_chunk=1
+ http_chunk_size=1024
+
+ # defaults
+ _http_version='1.0'
+ _http_connection='close'
+ _server_version='Zope/2.0 ZServer/2.0'
+
+ # using streaming response
+ _streaming=0
+ # using chunking transfer-encoding
+ _chunking=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']
+
+ if not headers.has_key("Etag"):
+ self.setHeader('Etag','')
+
+ # 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)
+
+ _tempfile=None
+ _templock=None
+ _tempstart=0
+
+ def write(self,data):
+ """\
+ Return data as a stream
+
+ HTML data may be returned using a stream-oriented interface.
+ This allows the browser to display partial results while
+ computation of a response to proceed.
+
+ The published object should first set any output headers or
+ cookies on the response object.
+
+ Note that published objects must not generate any errors
+ after beginning stream-oriented output.
+
+ """
+ stdout=self.stdout
+
+ if not self._wrote:
+ l=self.headers.get('content-length', None)
+ if l is not None:
+ try:
+ if type(l) is type(''): l=int(l)
+ if l > 128000:
+ self._tempfile=tempfile.TemporaryFile()
+ self._templock=thread.allocate_lock()
+ except: pass
+
+ self._streaming=1
+ stdout.write(str(self))
+ self._wrote=1
+
+ if not data: return
+
+ if self._chunking:
+ data = '%x\r\n%s\r\n' % (len(data),data)
+
+ l=len(data)
+
+ t=self._tempfile
+ if t is None or l<200:
+ stdout.write(data)
+ else:
+ b=self._tempstart
+ e=b+l
+ self._templock.acquire()
+ try:
+ t.seek(b)
+ t.write(data)
+ finally:
+ self._templock.release()
+ self._tempstart=e
+ stdout.write(file_part_producer(t,self._templock,b,e), l)
+
+ _retried_response = None
+
+ def _finish(self):
+ if self._retried_response:
+ try:
+ self._retried_response._finish()
+ finally:
+ self._retried_response = None
+ return
+ stdout=self.stdout
+
+ t=self._tempfile
+ if t is not None:
+ stdout.write(file_close_producer(t), 0)
+ self._tempfile=None
+
+ stdout.finish(self)
+ stdout.close()
+
+ self.stdout=None # need to break cycle?
+ self._request=None
+
+ def retry(self):
+ """Return a request object to be used in a retry attempt
+ """
+ # This implementation is a bit lame, because it assumes that
+ # only stdout stderr were passed to the constructor. OTOH, I
+ # think that that's all that is ever passed.
+
+ response=self.__class__(stdout=self.stdout, stderr=self.stderr)
+ response.headers=self.headers
+ response._http_version=self._http_version
+ response._http_connection=self._http_connection
+ response._server_version=self._server_version
+ self._retried_response = response
+ return response
+
+
+class ChannelPipe:
+ """Experimental pipe from ZPublisher to a ZServer Channel.
+ Should only be used by one thread at a time. Note also that
+ the channel will be being handled by another thread, thus
+ restrict access to channel to the push method only."""
+
+ def __init__(self, request):
+ self._channel=request.channel
+ self._request=request
+ self._shutdown=0
+ self._close=0
+ self._bytes=0
+
+ def write(self, text, l=None):
+ if self._channel.closed:
+ return
+ if l is None: l=len(text)
+ self._bytes=self._bytes + l
+ self._channel.push(text,0)
+ Wakeup()
+
+ def close(self):
+ DebugLogger.log('A', id(self._request),
+ '%s %s' % (self._request.reply_code, self._bytes))
+ if not self._channel.closed:
+ self._channel.push(LoggingProducer(self._request, self._bytes), 0)
+ self._channel.push(CallbackProducer(self._channel.done), 0)
+ self._channel.push(CallbackProducer(
+ lambda t=('E', id(self._request)): apply(DebugLogger.log, t)), 0)
+ if self._shutdown:
+ self._channel.push(ShutdownProducer(), 0)
+ Wakeup()
+ else:
+ if self._close: self._channel.push(None, 0)
+ Wakeup()
+ else:
+ # channel closed too soon
+
+ self._request.log(self._bytes)
+ DebugLogger.log('E', id(self._request))
+
+ if self._shutdown:
+ Wakeup(lambda: asyncore.close_all())
+ else:
+ Wakeup()
+
+ self._channel=None #need to break cycles?
+ self._request=None
+
+ def flush(self): pass # yeah, whatever
+
+ def finish(self, response):
+ if response._shutdownRequested():
+ self._shutdown = 1
+ if response.headers.get('connection','') == 'close' or \
+ response.headers.get('Connection','') == 'close':
+ self._close=1
+ self._request.reply_code=response.status
+
+
+is_proxying_match = re.compile(r'[^ ]* [^ \\]*:').match
+proxying_connection_re = re.compile ('Proxy-Connection: (.*)', re.IGNORECASE)
+
+def make_response(request, headers):
+ "Simple http response factory"
+ # should this be integrated into the HTTPResponse constructor?
+
+ response=ZServerHTTPResponse(stdout=ChannelPipe(request), stderr=StringIO())
+ response._http_version=request.version
+ if request.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 = http_server.CONNECTION
+ response._http_connection = http_server.get_header(connection_re,
+ request.header).lower()
+ response._server_version=request.channel.server.SERVER_IDENT
+ return response
=== Zope/lib/python/ZServer/HTTPServer.py 1.44 => 1.45 ===
--- /dev/null Tue Mar 18 16:15:46 2003
+++ Zope/lib/python/ZServer/HTTPServer.py Tue Mar 18 16:15:14 2003
@@ -0,0 +1,408 @@
+##############################################################################
+#
+# Copyright (c) 2001 Zope Corporation and Contributors. 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.
+# 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
+#
+##############################################################################
+
+"""
+Medusa HTTP server for Zope
+
+changes from Medusa's http_server
+
+ Request Threads -- Requests are processed by threads from a thread
+ pool.
+
+ Output Handling -- Output is pushed directly into the producer
+ fifo by the request-handling thread. The HTTP server does not do
+ any post-processing such as chunking.
+
+ Pipelineable -- This is needed for protocols such as HTTP/1.1 in
+ which mutiple requests come in on the same channel, before
+ responses are sent back. When requests are pipelined, the client
+ doesn't wait for the response before sending another request. The
+ server must ensure that responses are sent back in the same order
+ as requests are received.
+
+"""
+import sys
+import re
+import os
+import posixpath
+import types
+import thread
+import time
+import socket
+from cStringIO import StringIO
+
+from PubCore import handle
+from HTTPResponse import make_response
+from ZPublisher.HTTPRequest import HTTPRequest
+
+from medusa.http_server import http_server,get_header, http_channel, VERSION_STRING
+import asyncore
+from medusa import counter, producers
+from medusa.test import max_sockets
+from medusa.default_handler import unquote
+from asyncore import compact_traceback, dispatcher
+
+from ZServer import CONNECTION_LIMIT, ZOPE_VERSION, ZSERVER_VERSION
+from ZServer import requestCloseOnExec
+from zLOG import LOG, register_subsystem, BLATHER, INFO, WARNING, ERROR
+import DebugLogger
+from medusa import logger
+
+register_subsystem('ZServer HTTPServer')
+
+CONTENT_LENGTH = re.compile('Content-Length: ([0-9]+)',re.I)
+CONNECTION = re.compile('Connection: (.*)', re.I)
+USER_AGENT = re.compile('User-Agent: (.*)', re.I)
+
+# maps request some headers to environment variables.
+# (those that don't start with 'HTTP_')
+header2env={'content-length' : 'CONTENT_LENGTH',
+ 'content-type' : 'CONTENT_TYPE',
+ 'connection' : 'CONNECTION_TYPE',
+ }
+
+
+class zhttp_collector:
+ def __init__(self, handler, request, size):
+ self.handler = handler
+ self.request = request
+ if size > 524288:
+ # write large upload data to a file
+ from tempfile import TemporaryFile
+ self.data = TemporaryFile('w+b')
+ else:
+ self.data = StringIO()
+ request.channel.set_terminator(size)
+ request.collector=self
+
+ # put and post collection methods
+ #
+ def collect_incoming_data (self, data):
+ self.data.write(data)
+
+ def found_terminator(self):
+ # reset collector
+ self.request.channel.set_terminator('\r\n\r\n')
+ self.request.collector=None
+ # finish request
+ self.data.seek(0)
+ r=self.request
+ d=self.data
+ del self.request
+ del self.data
+ self.handler.continue_request(d,r)
+
+class zhttp_handler:
+ "A medusa style handler for zhttp_server"
+
+ _force_connection_close = 0
+
+ def __init__ (self, module, uri_base=None, env=None):
+ """Creates a zope_handler
+
+ module -- string, the name of the module to publish
+ uri_base -- string, the base uri of the published module
+ defaults to '/<module name>' if not given.
+ env -- dictionary, environment variables to be overridden.
+ Replaces standard variables with supplied ones.
+ """
+
+ self.module_name=module
+ self.env_override=env or {}
+ self.hits = counter.counter()
+ # if uri_base is unspecified, assume it
+ # starts with the published module name
+ #
+ if uri_base is None:
+ uri_base='/%s' % module
+ elif uri_base == '':
+ uri_base='/'
+ else:
+ if uri_base[0] != '/':
+ uri_base='/'+uri_base
+ if uri_base[-1] == '/':
+ uri_base=uri_base[:-1]
+ self.uri_base=uri_base
+ uri_regex='%s.*' % self.uri_base
+ self.uri_regex = re.compile(uri_regex)
+
+ def match(self, request):
+ uri = request.uri
+ if self.uri_regex.match(uri):
+ return 1
+ else:
+ return 0
+
+ def handle_request(self,request):
+ self.hits.increment()
+
+ DebugLogger.log('B', id(request), '%s %s' % (request.command.upper(), request.uri))
+
+ size=get_header(CONTENT_LENGTH, request.header)
+ if size and size != '0':
+ size=int(size)
+ zhttp_collector(self, request, size)
+ else:
+ sin=StringIO()
+ self.continue_request(sin,request)
+
+ def get_environment(self, request,
+ # These are strictly performance hackery...
+ h2ehas=header2env.has_key,
+ h2eget=header2env.get,
+ workdir=os.getcwd(),
+ ospath=os.path,
+ ):
+
+ (path, params, query, fragment) = request.split_uri()
+
+ if params: path = path + params # undo medusa bug!
+
+ while path and path[0] == '/':
+ path = path[1:]
+ if '%' in path:
+ path = unquote(path)
+ if query:
+ # ZPublisher doesn't want the leading '?'
+ query = query[1:]
+
+ server=request.channel.server
+ env = {}
+ env['REQUEST_METHOD']=request.command.upper()
+ env['SERVER_PORT']=str(server.port)
+ env['SERVER_NAME']=server.server_name
+ env['SERVER_SOFTWARE']=server.SERVER_IDENT
+ env['SERVER_PROTOCOL']="HTTP/"+request.version
+ env['channel.creation_time']=request.channel.creation_time
+ if self.uri_base=='/':
+ env['SCRIPT_NAME']=''
+ env['PATH_INFO']='/' + path
+ else:
+ env['SCRIPT_NAME'] = self.uri_base
+ try:
+ path_info=path.split(self.uri_base[1:],1)[1]
+ except:
+ path_info=''
+ env['PATH_INFO']=path_info
+ env['PATH_TRANSLATED']=ospath.normpath(ospath.join(
+ workdir, env['PATH_INFO']))
+ if query:
+ env['QUERY_STRING'] = query
+ env['GATEWAY_INTERFACE']='CGI/1.1'
+ env['REMOTE_ADDR']=request.channel.addr[0]
+
+
+ # This is a really bad hack to support WebDAV
+ # clients accessing documents through GET
+ # on the HTTP port. We check if your WebDAV magic
+ # machinery is enabled and if the client is recognized
+ # as WebDAV client. If yes, we fake the environment
+ # to pretend the ZPublisher to have a WebDAV request.
+ # This sucks like hell but it works pretty fine ;-)
+
+ if env['REQUEST_METHOD']=='GET' and self._wdav_client_reg:
+ self._munge_webdav_source_port(request, env)
+
+
+ # If we're using a resolving logger, try to get the
+ # remote host from the resolver's cache.
+ if hasattr(server.logger, 'resolver'):
+ dns_cache=server.logger.resolver.cache
+ if dns_cache.has_key(env['REMOTE_ADDR']):
+ remote_host=dns_cache[env['REMOTE_ADDR']][2]
+ if remote_host is not None:
+ env['REMOTE_HOST']=remote_host
+
+ env_has=env.has_key
+ for header in request.header:
+ key,value=header.split(":",1)
+ key=key.lower()
+ value=value.strip()
+ if h2ehas(key) and value:
+ env[h2eget(key)]=value
+ else:
+ key='HTTP_%s' % ("_".join(key.split( "-"))).upper()
+ if value and not env_has(key):
+ env[key]=value
+ env.update(self.env_override)
+ return env
+
+ _wdav_client_reg = None
+
+ def _munge_webdav_source_port(self, request, env):
+ agent = get_header(USER_AGENT, request.header)
+ if self._wdav_client_reg(agent):
+ env['WEBDAV_SOURCE_PORT'] = 1
+ path_info = env['PATH_INFO']
+ path_info = posixpath.join(path_info, 'manage_FTPget')
+ path_info = posixpath.normpath(path_info)
+ env['PATH_INFO'] = path_info
+
+ def set_webdav_source_clients(self, regex):
+ self._wdav_client_reg = re.compile(regex).search
+
+ 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)
+ zresponse=make_response(request,env)
+ if self._force_connection_close:
+ zresponse._http_connection = 'close'
+ zrequest=HTTPRequest(sin, env, zresponse)
+ request.channel.current_request=None
+ request.channel.queue.append((self.module_name, zrequest, zresponse))
+ request.channel.work()
+
+ def status(self):
+ return producers.simple_producer("""
+ <li>Zope Handler
+ <ul>
+ <li><b>Published Module:</b> %s
+ <li><b>Hits:</b> %s
+ </ul>""" %(self.module_name, self.hits)
+ )
+
+
+
+class zhttp_channel(http_channel):
+ "http channel"
+
+ closed = 0
+ no_more_requests = 0
+ zombie_timeout=100*60 # 100 minutes
+ max_header_len = 8196
+
+ def __init__(self, server, conn, addr):
+ http_channel.__init__(self, server, conn, addr)
+ requestCloseOnExec(conn)
+ self.queue=[]
+ self.working=0
+
+ def push(self, producer, send=1):
+ # this is thread-safe when send is false
+ # note, that strings are not wrapped in
+ # producers by default
+ if self.closed:
+ return
+ self.producer_fifo.push(producer)
+ if send: self.initiate_send()
+
+ push_with_producer=push
+
+ def clean_shutdown_control(self,phase,time_in_this_phase):
+ if phase==3:
+ # This is the shutdown phase where we are trying to finish processing
+ # outstanding requests, and not accept any more
+ self.no_more_requests = 1
+ if self.working or self.writable():
+ # We are busy working on an old request. Try to stall shutdown
+ return 1
+ else:
+ # We are no longer busy. Close ourself and allow shutdown to proceed
+ self.close()
+ return 0
+
+ def work(self):
+ "try to handle a request"
+ if not self.working:
+ if self.queue and not self.no_more_requests:
+ self.working=1
+ try: module_name, request, response=self.queue.pop(0)
+ except: return
+ handle(module_name, request, response)
+
+ def close(self):
+ self.closed=1
+ while self.queue:
+ self.queue.pop()
+ if self.current_request is not None:
+ self.current_request.channel=None # break circ refs
+ self.current_request=None
+ while self.producer_fifo:
+ p=self.producer_fifo.first()
+ if p is not None and type(p) != types.StringType:
+ p.more() # free up resources held by producer
+ self.producer_fifo.pop()
+ dispatcher.close(self)
+
+ def done(self):
+ "Called when a publishing request is finished"
+ self.working=0
+ self.work()
+
+ def kill_zombies(self):
+ now = int (time.time())
+ for channel in asyncore.socket_map.values():
+ if channel.__class__ == self.__class__:
+ if (now - channel.creation_time) > channel.zombie_timeout:
+ channel.close()
+
+ def collect_incoming_data (self, data):
+ # Override medusa http_channel implementation to prevent DOS attacks
+ # that send never-ending HTTP headers.
+ if self.current_request:
+ # we are receiving data (probably POST data) for a request
+ self.current_request.collect_incoming_data (data)
+ else:
+ # we are receiving header (request) data
+ self.in_buffer = self.in_buffer + data
+ if len(self.in_buffer) > self.max_header_len:
+ raise ValueError('HTTP headers invalid (too long)')
+
+class zhttp_server(http_server):
+ "http server"
+
+ SERVER_IDENT='Zope/%s ZServer/%s' % (ZOPE_VERSION,ZSERVER_VERSION)
+
+ channel_class = zhttp_channel
+ shutup=0
+
+ def __init__ (self, ip, port, resolver=None, logger_object=None):
+ self.shutup=1
+ http_server.__init__(self, ip, port, resolver, logger_object)
+ self.shutup=0
+ self.log_info('HTTP server started at %s\n'
+ '\tHostname: %s\n\tPort: %d' % (
+ time.ctime(time.time()),
+ self.server_name,
+ self.server_port
+ ))
+
+ def clean_shutdown_control(self,phase,time_in_this_phase):
+ if phase==2:
+ self.log_info('closing HTTP to new connections')
+ self.close()
+
+ def log_info(self, message, type='info'):
+ if self.shutup: return
+ dispatcher.log_info(self, message, type)
+
+ def create_socket(self, family, type):
+ dispatcher.create_socket(self, family, type)
+ requestCloseOnExec(self.socket)
+
+ def readable(self):
+ return self.accepting and \
+ len(asyncore.socket_map) < CONNECTION_LIMIT
+
+ def listen(self, num):
+ # override asyncore limits for nt's listen queue size
+ self.accepting = 1
+ return self.socket.listen (num)
=== Zope/lib/python/ZServer/ICPServer.py 1.4 => 1.5 ===
--- /dev/null Tue Mar 18 16:15:46 2003
+++ Zope/lib/python/ZServer/ICPServer.py Tue Mar 18 16:15:14 2003
@@ -0,0 +1,128 @@
+##############################################################################
+#
+# Copyright (c) 2001 Zope Corporation and Contributors. 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.
+# 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
+#
+##############################################################################
+
+# Medusa ICP server
+#
+# Why would you want to use this?
+# see http://www.zope.org/Members/htrd/icp/intro
+
+import sys, string, os, socket, errno, struct
+
+import asyncore
+
+from medusa import counter
+
+
+ICP_OP_QUERY = 1
+ICP_OP_HIT = 2
+ICP_OP_MISS = 3
+ICP_OP_ERR = 4
+ICP_OP_MISS_NOFETCH = 21
+ICP_OP_DENIED = 22
+
+class BaseICPServer(asyncore.dispatcher):
+
+ REQUESTS_PER_LOOP = 4
+ _shutdown = 0
+
+ def __init__ (self,ip,port):
+ asyncore.dispatcher.__init__(self)
+ self.create_socket (socket.AF_INET, socket.SOCK_DGRAM)
+ self.set_reuse_addr()
+ self.bind((ip,port))
+ if ip=='':
+ addr = 'any'
+ else:
+ addr = ip
+ self.log_info('ICP server started\n\tAddress: %s\n\tPort: %s' % (addr,port) )
+
+ def clean_shutdown_control(self,phase,time_in_this_phase):
+ if phase==1:
+ # Stop responding to requests.
+ if not self._shutdown:
+ self._shutdown = 1
+ self.log_info('shutting down ICP')
+ if time_in_this_phase<2.0:
+ # We have not yet been deaf long enough for our front end proxies to notice.
+ # Do not allow shutdown to proceed yet
+ return 1
+ else:
+ # Shutdown can proceed. We dont need a socket any more
+ self.close()
+ return 0
+
+ def handle_read(self):
+ for i in range(self.REQUESTS_PER_LOOP):
+ try:
+ request, whence = self.socket.recvfrom(16384)
+ except socket.error,e:
+ if e[0]==errno.EWOULDBLOCK:
+ break
+ else:
+ raise
+ else:
+ if self.check_whence(whence):
+ reply = self.calc_reply(request)
+ if reply:
+ self.socket.sendto(reply,whence)
+
+ def readable(self):
+ return not self._shutdown
+
+ def writable(self):
+ return 0
+
+ def handle_write (self):
+ self.log_info ('unexpected write event', 'warning')
+
+ def handle_error (self): # don't close the socket on error
+ (file,fun,line), t, v, tbinfo = asyncore.compact_traceback()
+ self.log_info('Problem in ICP (%s:%s %s)' % (t, v, tbinfo),
+ 'error')
+
+ def check_whence(self,whence):
+ return 1
+
+ def calc_reply(self,request):
+ if len(request)>20:
+ opcode,version,length,number,options,opdata,junk = struct.unpack('!BBHIIII',request[:20])
+ if version==2:
+ if opcode==ICP_OP_QUERY:
+ if len(request)!=length:
+ out_opcode = ICP_OP_ERR
+ else:
+ url = request[24:]
+ if url[-1:]=='\x00':
+ url = url[:-1]
+ out_opcode = self.check_url(url)
+ return struct.pack('!BBHIIII',out_opcode,2,20,number,0,0,0)
+
+ def check_url(self,url):
+ # derived classes replace this with a more
+ # useful policy
+ return ICP_OP_MISS
+
+
+class ICPServer(BaseICPServer):
+ # Products that want to do special ICP handling should .append their hooks into
+ # this list. Each hook is called in turn with the URL as a parameter, and
+ # they must return an ICP_OP code from above or None. The first
+ # non-None return is used as the ICP response
+ hooks = []
+
+ def check_url(self,url):
+ for hook in self.hooks:
+ r = hook(url)
+ if r is not None:
+ return r
+ return ICP_OP_MISS
=== Zope/lib/python/ZServer/INSTALL.txt 1.7 => 1.8 ===
--- /dev/null Tue Mar 18 16:15:46 2003
+++ Zope/lib/python/ZServer/INSTALL.txt Tue Mar 18 16:15:14 2003
@@ -0,0 +1,123 @@
+ZServer Installation
+--------------------
+
+Requirements
+
+ ZServer comes with Zope 2. Though ZServer can be used with earlier
+ versions of Zope, this is not supported and not covered by this
+ document.
+
+Configuration
+
+ To run ZServer you simply execute the z2.py start script which is
+ located in your Zope directory. You can pass commandline arguments
+ to the start script in order to run Zope with different options. In
+ order to see a list of options use the '-h' help option.
+
+ Here's an example of use::
+
+ $ python2.1 z2.py -w 8888 -f "" -p "" -m "" &
+
+ This example starts Zope using a web server on port 8888. It does
+ not start and FTP server, or a PCGI server, or a Monitor server. It
+ also starts the server running in the backaground.
+
+Shell scripts and batch files
+
+ You may also wish to create a shell script (or batch file under
+ win32) to set environment variables (such as ZOPE_DEBUG_MODE and
+ PYTHONHOME) and run the start script.
+
+ Here's an example shell script for a binary Zope release::
+
+ ZOPE_DEBUG_MODE=1
+ export ZOPE_DEBUG_MODE
+ PYTHONHOME=/home/Zope
+ export PYTHONHOME
+ /home/Zope/bin/python /home/Zope/z2.py -w 9673 &
+
+ Note: If ZServer fails because it can't find some standard Python
+ libaries there's a good bet that you need to set the PYTHONHOME as
+ shown above.
+
+ Here's an example batch file::
+
+ set ZOPE_DEBUG_MODE=1
+ "\Program Files\Zope\bin\python" "\Program Files\Zope\z2.py -w
+ 8888 -f 8021"
+
+ Now you're ready to go.
+
+Starting ZServer
+
+ To start ZServer run the start script::
+
+ $ python2.1 z2.py
+
+ To stop the server type 'control-c'.
+
+ Note: If you've created a shell script or batch file to run ZServer
+ use that instead.
+
+ You should see some Medusa information come up on the screen as Zope
+ starts.
+
+ A log file will be written to the 'var' directory, named
+ 'Z2.log' by default.
+
+Using ZServer
+
+ Once you start ZServer is will publish Zope (or any Python module)
+ on HTTP and/or FTP. To access Zope via HTTP point your browser at
+ the server like so::
+
+ http://www.example.com:9673/
+
+ This assumes that you have chosen to put HTTP on port 9673 and that
+ you are publishing a module named whose URL prefix is set to ''.
+
+ Note: to publish Zope normally you publish the 'lib/python/Zope.py'
+ module.
+
+ To access Zope via FTP you need to FTP to it at the port you set FTP
+ to run on. For example::
+
+ ftp www.example.com 9221
+
+ This opens a FTP session to your machine on port 9221, ZServer's
+ default FTP port. When you are prompted to log in you should supply
+ a Zope username and password. (Probably you should use an account
+ with the 'Manager' role, unless you have configured Zope to allow
+ FTP access to the 'Anonymous' role.) You can also enter 'anonymous'
+ and any password for anonymous FTP access. Once you have logged in
+ you can start issuing normal FTP commands.
+
+ Right now ZServer supports most basic FTP commands.
+
+ Note: When you log in your working directory is set to '/'. If you
+ do not have FTP permissions in this directory, you will need to 'cd'
+ to a directory where you have permissions before you can do
+ anything. See above for more information about logging into FTP.
+
+Advanced Usage: zdaemon.py and the Zope NT service.
+
+ One problem you may notice with ZServer is that once the server is
+ shutdown, either through the web management interface, or by some
+ other means, it will not automatically be restarted.
+
+ On Unix you can use zdeamon.py to keep Zope running. Specifying
+ the '-Z' switch when starting Zope runs zdaemon.py. Zdeamon
+ will restart Zope when it Zope is restarted through the web, and in
+ case of an unexpected error.
+
+ On NT, use the Zope service for the same functionality. See ZServer.py
+ for more information on running ZServer as a service.
+
+Where to go from here
+
+ Check out the README.txt file. It contains information on what
+ ZServer can do, how it works and and what you can do if you run into
+ problems.
+
+ And don't forget to have fun!
+
=== Zope/lib/python/ZServer/PCGIServer.py 1.25 => 1.26 ===
--- /dev/null Tue Mar 18 16:15:46 2003
+++ Zope/lib/python/ZServer/PCGIServer.py Tue Mar 18 16:15:14 2003
@@ -0,0 +1,399 @@
+##############################################################################
+#
+# Copyright (c) 2001 Zope Corporation and Contributors. 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.
+# 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
+#
+##############################################################################
+
+"""
+Medusa PCGI server.
+
+This server functions as the PCGI publisher--it accepts the request
+from the PCGI wrapper CGI program, services the request, and sends
+back the response.
+
+It should work with both inet and unix domain sockets.
+
+Why would you want to use it? Using PCGI to connect to ZServer from
+another webserver is similar to using the web server as a proxy,
+with the difference, that the web server gets to control the
+environment and headers completely.
+
+Note that ZServer can operate multiple PCGI servers.
+"""
+
+from medusa import logger
+import asynchat, asyncore
+from medusa.counter import counter
+from medusa.http_server import compute_timezone_for_log
+from asyncore import compact_traceback
+
+import ZServer
+from ZServer import CONNECTION_LIMIT, requestCloseOnExec
+
+from PubCore import handle
+from PubCore.ZEvent import Wakeup
+from ZPublisher.HTTPResponse import HTTPResponse
+from ZPublisher.HTTPRequest import HTTPRequest
+from Producers import ShutdownProducer, LoggingProducer, CallbackProducer
+import DebugLogger
+
+from cStringIO import StringIO
+from tempfile import TemporaryFile
+import socket, string, os, sys, time
+from types import StringType, TupleType
+
+tz_for_log=compute_timezone_for_log()
+
+class PCGIChannel(asynchat.async_chat):
+ """Processes a PCGI request by collecting the env and stdin and
+ then passing them to ZPublisher. The result is wrapped in a
+ producer and sent back."""
+
+ closed=0
+
+ def __init__(self,server,sock,addr):
+ self.server = server
+ self.addr = addr
+ asynchat.async_chat.__init__ (self, sock)
+ requestCloseOnExec(sock)
+ self.env={}
+ self.data=StringIO()
+ self.set_terminator(10)
+ self.size=None
+ self.done=None
+
+ def found_terminator(self):
+ if self.size is None:
+ # read the next size header
+ # and prepare to read env or stdin
+ self.data.seek(0)
+ self.size=string.atoi(self.data.read())
+ self.set_terminator(self.size)
+ if self.size==0:
+
+ DebugLogger.log('I', id(self), 0)
+
+ self.set_terminator('\r\n')
+ self.data=StringIO()
+ self.send_response()
+ elif self.size > 1048576:
+ self.data=TemporaryFile('w+b')
+ else:
+ self.data=StringIO()
+ elif not self.env:
+ # read env
+ self.size=None
+ self.data.seek(0)
+ buff=self.data.read()
+ for line in string.split(buff,'\000'):
+ try:
+ k,v = string.split(line,'=',1)
+ self.env[k] = v
+ except:
+ pass
+ # Hack around broken IIS PATH_INFO
+ # maybe, this should go in ZPublisher...
+ if self.env.has_key('SERVER_SOFTWARE') and \
+ string.find(self.env['SERVER_SOFTWARE'],
+ 'Microsoft-IIS') != -1:
+ script = filter(None,string.split(
+ string.strip(self.env['SCRIPT_NAME']),'/'))
+ path = filter(None,string.split(
+ string.strip(self.env['PATH_INFO']),'/'))
+ self.env['PATH_INFO'] = '/' + string.join(path[len(script):],'/')
+ self.data=StringIO()
+
+ DebugLogger.log('B', id(self),
+ '%s %s' % (self.env['REQUEST_METHOD'],
+ self.env.get('PATH_INFO' ,'/')))
+
+ # now read the next size header
+ self.set_terminator(10)
+ else:
+
+ DebugLogger.log('I', id(self), self.terminator)
+
+ # we're done, we've got both env and stdin
+ self.set_terminator('\r\n')
+ self.data.seek(0)
+ self.send_response()
+
+ def send_response(self):
+ # create an output pipe by passing request to ZPublisher,
+ # and requesting a callback of self.log with the module
+ # name and PATH_INFO as an argument.
+ self.done=1
+ response=PCGIResponse(stdout=PCGIPipe(self), stderr=StringIO())
+ request=HTTPRequest(self.data, self.env, response)
+ handle(self.server.module, request, response)
+
+ def collect_incoming_data(self, data):
+ self.data.write(data)
+
+ def readable(self):
+ if not self.done:
+ return 1
+
+ def log_request(self, bytes):
+ if self.env.has_key('HTTP_USER_AGENT'):
+ user_agent=self.env['HTTP_USER_AGENT']
+ else:
+ user_agent=''
+ if self.env.has_key('HTTP_REFERER'):
+ referer=self.env['HTTP_REFERER']
+ else:
+ referer=''
+
+ if self.env.has_key('PATH_INFO'):
+ path=self.env['PATH_INFO']
+ else:
+ path='%s/' % self.server.module
+ if self.env.has_key('REQUEST_METHOD'):
+ method=self.env['REQUEST_METHOD']
+ else:
+ method="GET"
+ addr=self.addr
+ if addr and type(addr) is TupleType:
+ self.server.logger.log (
+ addr[0],
+ '%d - - [%s] "%s %s" %d %d "%s" "%s"' % (
+ addr[1],
+ time.strftime (
+ '%d/%b/%Y:%H:%M:%S ',
+ time.localtime(time.time())
+ ) + tz_for_log,
+ method, path, self.reply_code, bytes,
+ referer, user_agent
+ )
+ )
+ else:
+ self.server.logger.log (
+ '127.0.0.1',
+ ' - - [%s] "%s %s" %d %d "%s" "%s"' % (
+ time.strftime (
+ '%d/%b/%Y:%H:%M:%S ',
+ time.gmtime(time.time())
+ ) + tz_for_log,
+ method, path, self.reply_code, bytes,
+ referer, user_agent
+ )
+ )
+
+ def push(self, producer, send=1):
+ # this is thread-safe when send is false
+ # note, that strings are not wrapped in
+ # producers by default
+ self.producer_fifo.push(producer)
+ if send: self.initiate_send()
+
+ def __repr__(self):
+ return "<PCGIChannel at %x>" % id(self)
+
+ def close(self):
+ self.closed=1
+ while self.producer_fifo:
+ p=self.producer_fifo.first()
+ if p is not None and type(p) != StringType:
+ p.more() # free up resources held by producer
+ self.producer_fifo.pop()
+ asyncore.dispatcher.close(self)
+
+
+class PCGIServer(asyncore.dispatcher):
+ """Accepts PCGI requests and hands them off to the PCGIChannel for
+ handling.
+
+ PCGIServer can be configured with either a PCGI info file or by
+ directly specifying the module, pid_file, and either port (for
+ inet sockets) or socket_file (for unix domain sockets.)
+
+ For inet sockets, the ip argument specifies the address from which
+ the server will accept connections, '' indicates all addresses. If
+ you only want to accept connections from the localhost, set ip to
+ '127.0.0.1'."""
+
+ channel_class=PCGIChannel
+
+ def __init__ (self,
+ module='Main',
+ ip='127.0.0.1',
+ port=None,
+ socket_file=None,
+ pid_file=None,
+ pcgi_file=None,
+ resolver=None,
+ logger_object=None):
+
+ self.ip = ip
+ asyncore.dispatcher.__init__(self)
+ self.count=counter()
+ if not logger_object:
+ logger_object = logger.file_logger (sys.stdout)
+ if resolver:
+ self.logger = logger.resolving_logger (resolver, logger_object)
+ else:
+ self.logger = logger.unresolving_logger (logger_object)
+
+ # get configuration
+ self.module=module
+ self.port=port
+ self.pid_file=pid_file
+ self.socket_file=socket_file
+ if pcgi_file is not None:
+ self.read_info(pcgi_file)
+
+ # write pid file
+ try:
+ f = open(self.pid_file, 'w')
+ f.write(str(os.getpid()))
+ f.close()
+ except IOError:
+ self.log_info("Cannot write PID file.", 'error')
+
+ # setup sockets
+ if self.port:
+ self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
+ self.set_reuse_addr()
+ self.bind((self.ip, self.port))
+ self.log_info(
+ 'PCGI Server started at %s\n'
+ '\tInet socket port: %s' % (time.ctime(time.time()), self.port)
+ )
+ else:
+ try:
+ os.unlink(self.socket_file)
+ except os.error:
+ pass
+ self.create_socket(socket.AF_UNIX, socket.SOCK_STREAM)
+ self.set_reuse_addr()
+ self.bind(self.socket_file)
+ try:
+ os.chmod(self.socket_file,0777)
+ except os.error:
+ pass
+ self.log_info(
+ 'PCGI Server started at %s\n'
+ '\tUnix socket: %s' % (time.ctime(time.time()), self.socket_file)
+ )
+ self.listen(256)
+
+ def create_socket(self, family, type):
+ asyncore.dispatcher.create_socket(self, family, type)
+ requestCloseOnExec(self.socket)
+
+ def read_info(self,info_file):
+ "read configuration information from a PCGI info file"
+ lines=open(info_file).readlines()
+ directives={}
+ try:
+ for line in lines:
+ line=string.strip(line)
+ if not len(line) or line[0]=='#':
+ continue
+ k,v=string.split(line,'=',1)
+ directives[string.strip(k)]=string.strip(v)
+ except:
+ raise 'ParseError', 'Error parsing PCGI info file'
+
+ self.pid_file=directives.get('PCGI_PID_FILE',None)
+ self.socket_file=directives.get('PCGI_SOCKET_FILE',None)
+ if directives.has_key('PCGI_PORT'):
+ self.port=string.atoi(directives['PCGI_PORT'])
+ if directives.has_key('PCGI_MODULE'):
+ self.module=directives['PCGI_MODULE']
+ elif directives.has_key('PCGI_MODULE_PATH'):
+ path=directives['PCGI_MODULE_PATH']
+ path,module=os.path.split(path)
+ module,ext=os.path.splitext(module)
+ self.module=module
+
+ def handle_accept (self):
+ self.count.increment()
+ try:
+ conn, addr = self.accept()
+ except socket.error:
+ self.log_info('Server accept() threw an exception', 'warning')
+ return
+ self.channel_class(self, conn, addr)
+
+ def readable(self):
+ return len(asyncore.socket_map) < CONNECTION_LIMIT
+
+ def writable (self):
+ return 0
+
+ def listen(self, num):
+ # override asyncore limits for nt's listen queue size
+ self.accepting = 1
+ return self.socket.listen (num)
+
+
+class PCGIResponse(HTTPResponse):
+
+ def write(self, data):
+ if not self._wrote:
+ self.stdout.write(str(self))
+ self._wrote=1
+ self.stdout.write(data)
+
+ def _finish(self):
+ self.stdout.finish(self)
+ self.stdout.close()
+
+ self.stdout=None
+ self._request=None
+
+
+class PCGIPipe:
+ """
+ Formats a HTTP response in PCGI format
+
+ 10 digits indicating len of STDOUT
+ STDOUT
+ 10 digits indicating len of STDERR
+ STDERR
+
+ Note that this implementation never sends STDERR
+ """
+ def __init__(self, channel):
+ self._channel=channel
+ self._data=StringIO()
+ self._shutdown=0
+
+ def write(self,text):
+ self._data.write(text)
+
+ def close(self):
+ if not self._channel.closed:
+ data=self._data.getvalue()
+ l=len(data)
+ DebugLogger.log('A', id(self._channel),
+ '%s %s' % (self._channel.reply_code, l))
+ self._channel.push('%010d%s%010d' % (l, data, 0), 0)
+ self._channel.push(LoggingProducer(self._channel, l, 'log_request'), 0)
+ self._channel.push(CallbackProducer(
+ lambda t=('E', id(self._channel)): apply(DebugLogger.log,t)), 0)
+
+ if self._shutdown:
+ try: r=self._shutdown[0]
+ except: r=0
+ ZServer.exit_code=r
+ self._channel.push(ShutdownProducer(), 0)
+ Wakeup(lambda: asyncore.close_all())
+ else:
+ self._channel.push(None, 0)
+ Wakeup()
+ self._data=None
+ self._channel=None
+
+ def finish(self, response):
+ if response._shutdownRequested():
+ self._shutdown = 1
+ self._channel.reply_code=response.status
=== Zope/lib/python/ZServer/Producers.py 1.8 => 1.9 ===
--- /dev/null Tue Mar 18 16:15:46 2003
+++ Zope/lib/python/ZServer/Producers.py Tue Mar 18 16:15:14 2003
@@ -0,0 +1,104 @@
+##############################################################################
+#
+# Copyright (c) 2001 Zope Corporation and Contributors. 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.
+# 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
+#
+##############################################################################
+"""
+ZServer pipe utils. These producers basically function as callbacks.
+"""
+
+import asyncore
+import sys
+
+class ShutdownProducer:
+ "shuts down medusa"
+
+ def more(self):
+ asyncore.close_all()
+
+
+class LoggingProducer:
+ "logs request"
+
+ def __init__(self, logger, bytes, method='log'):
+ self.logger=logger
+ self.bytes=bytes
+ self.method=method
+
+ def more(self):
+ getattr(self.logger, self.method)(self.bytes)
+ self.logger=None
+ return ''
+
+
+class CallbackProducer:
+ "Performs a callback in the channel's thread"
+
+ def __init__(self, callback):
+ self.callback=callback
+
+ def more(self):
+ self.callback()
+ self.callback=None
+ return ''
+
+
+class file_part_producer:
+ "producer wrapper for part of a file[-like] objects"
+
+ # match http_channel's outgoing buffer size
+ out_buffer_size = 1<<16
+
+ def __init__(self, file, lock, start, end):
+ self.file=file
+ self.lock=lock
+ self.start=start
+ self.end=end
+
+ def more(self):
+ end=self.end
+ if not end: return ''
+ start=self.start
+ if start >= end: return ''
+
+ file=self.file
+ size=end-start
+ bsize=self.out_buffer_size
+ if size > bsize: size=bsize
+
+ self.lock.acquire()
+ try:
+ file.seek(start)
+ data = file.read(size)
+ finally:
+ self.lock.release()
+
+ if data:
+ start=start+len(data)
+ if start < end:
+ self.start=start
+ return data
+
+ self.end=0
+ del self.file
+
+ return data
+
+
+class file_close_producer:
+ def __init__(self, file):
+ self.file=file
+
+ def more(self):
+ file=self.file
+ if file is not None:
+ file.close()
+ self.file=None
+ return ''
=== Zope/lib/python/ZServer/README.txt 1.11 => 1.12 ===
--- /dev/null Tue Mar 18 16:15:46 2003
+++ Zope/lib/python/ZServer/README.txt Tue Mar 18 16:15:14 2003
@@ -0,0 +1,236 @@
+ZServer README
+--------------
+
+What is ZServer?
+
+ ZServer is an integration of the Zope application server and the
+ Medusa information server. See the ZServer architecture document for
+ more information::
+
+ http://www.zope.org/Documentation/Reference/ZServer
+
+ ZServer gives you HTTP, FTP, WebDAV, PCGI, and remote interactive
+ Python access. In later releases it will probably offer more
+ protocols such as FastCGI, etc.
+
+What is Medusa?
+
+ Medusa is a Python server framework with uses a single threaded
+ asynchronous sockets approach. For more information see::
+
+ http://www.nightmare.com/medusa
+
+ There's also an interesting Medusa tutorial at::
+
+ http://www.nightmare.com:8080/nm/apps/medusa/docs/programming.html
+
+ZServer HTTP support
+
+ ZServer offers HTTP 1.1 publishing for Zope. It does not support
+ publishing files from the file system. You can specify the HTTP port
+ using the -w command line argument for the z2.py start script. You
+ can also specify CGI environment variables on the command line using
+ z2.py
+
+ZServer FTP support
+
+ What you can do with FTP
+
+ FTP access to Zope allows you to FTP to the Zope object hierarchy
+ in order to perform managerial tasks. You can:
+
+ * Navigate the object hierarchy with 'cd'
+
+ * Replace the content of Documents, Images, and Files
+
+ * Create Documents, Images, Files, Folders
+
+ * Delete objects and Folders.
+
+ So basically you can do more than is possible with HTTP PUT. Also,
+ unlike PUT, FTP gives you access to Document content. So when you
+ download a Document you are getting its content, not what it looks
+ like when it is rendered.
+
+ Using FTP
+
+ To FTP into Zope, ZServer must be configured to serve FTP. By
+ default ZServer serves FTP on port 9221. So to connect to Zope you
+ would issue a command like so::
+
+ $ ftp localhost 9221
+
+ When logging in to FTP, you have some choices. You can connect
+ anonymously by using a username of 'anonymous' and any password.
+ Or you can login as a Zope user. Since Zope users are defined at
+ different locations in the object hierarchy, authentication can be
+ problematic. There are two solutions:
+
+ * login and then cd to the directory where you are defined.
+
+ * login with a special name that indicates where you are
+ defined.
+
+ The format of the special name is '<username>@<path>'. For
+ example::
+
+ joe@Marketing/Projects
+
+ FTP permissions
+
+ FTP support is provided for Folders, Documents, Images, and Files.
+ You can control access to FTP via the new 'FTP access' permission.
+ This permission controls the ability to 'cd' to a Folder and to
+ download objects. Uploading and deleting and creating objects are
+ controlled by existing permissions.
+
+ FTP limits
+
+ You can set limits for the number of simultaneous FTP connections.
+ You can separately configure the number of anonymous and
+ authenticated connections. Right now this setting is set in
+ 'ZServerFTP.py'. In the future, it may be more easy to configure.
+
+ Properties and FTP: The next step
+
+ The next phase of FTP support will allow you to edit properties of
+ all Zope objects. Probably properties will be exposed via special
+ files which will contain an XML representation of the object's
+ properties. You could then download the file, edit the XML and
+ upload it to change the object's properties.
+
+ We do not currently have a target date for FTP property support.
+
+ How does FTP work?
+
+ The ZServer's FTP channel object translates FTP requests into
+ ZPublisher requests. The FTP channel then analyses the response
+ and formulates an appropriate FTP response. The FTP channel
+ stores some state such as the current working directory and the
+ username and password.
+
+ On the Zope side of things, the 'lib/python/OFS/FTPInterface.py'
+ module defines the Zope FTP interface, for listing sub-items,
+ stating, and getting content. The interface is implemented in
+ 'SimpleItem', and in other Zope classes. Programmers will not
+ need to implement the entire interface if they inherit from
+ 'SimpleItem'. All the other FTP functions are handled by
+ existing methods like 'manage_delObjects', and 'PUT', etc.
+
+ZServer PCGI support
+
+ ZServer will service PCGI requests with both inet and unix domain
+ sockets. This means you can use ZServer instead of
+ 'pcgi_publisher.py' as your long running PCGI server process. In the
+ future, PCGI may be able to activate ZServer.
+
+ Using PCGI instead of HTTP allows you to forward requests from
+ another web server to ZServer. The CGI environment and HTTP headers
+ are controlled by the web server, so you don't need to worry about
+ managing the ZServer environment. However, this configuration will
+ impose a larger overhead than simply using the web server as an HTTP
+ proxy for ZServer.
+
+ To use PCGI, configure your PCGI info files to communicate with
+ ZServer by setting the PCGI_PORT, PCGI_SOCKET_FILE, and PCGI_NAME.
+ The other PCGI settings are currently ignored by ZServer.
+
+ ZServer's PCGI support will work with mod_pcgi.
+
+ZServer monitor server
+
+ ZServer now includes the Medusa monitor server. This basically gives
+ you a remote, secure Python prompt. You can interactively access Zope.
+ This is a very powerful, but dangerous tool. Be careful.
+
+ To use the monitor server specify a monitor port number using the -m
+ option with the z2.py start script. The default port is 9999.
+
+ To connect to the monitor server use the 'ZServer/medusa/monitor_client.py'
+ or 'ZServer/medusa/monitor_client_win32.py' script. For example::
+
+ $ python2.1 ZServer/medusa/monitor_client.py localhost 9999
+
+ You will then be asked to enter a password. This is the Zope super manager
+ password which is stored in the 'access' file.
+
+ Then you will be greeted with a Python prompt. To access Zope import
+ the Zope module::
+
+ >>> import Zope
+
+ The Zope top level Zope object is available via the 'Zope.app' function::
+
+ >>> a=Zope.app()
+
+ From this object you can reach all other Zope objects as subobjects.
+
+ Remember if you make changes to Zope objects and want those changes to be
+ saved you need to commmit the transaction::
+
+ >>> get_transaction().commit()
+
+ZServer WebDAV support
+
+ WebDAV is a new protocol for managing web resources. WebDAV operates
+ over HTTP. Since WebDAV uses HTTP, ZServer doesn't really have to do
+ anything special, except stay out of Zope's way when handling WebDAV
+ requests.
+
+ The only major WebDAV client at this time is Internet Explorer 5. It
+ works with Zope.
+
+Differences between ZopeHTTPServer and ZServer
+
+ ZopeHTTPServer is old and no longer being actively maintained.
+
+ Both ZopeHTTPServer and ZServer are Python HTTP servers.
+ ZopeHTTPServer is built on the standard Python SimpleHTTPServer
+ framework. ZServer is built on Medusa.
+
+ ZopeHTTPServer is very limited. It can only publish one module at a
+ time. It can only publish via HTTP. It has no support for thread
+ pools.
+
+ ZServer on the other hand is more complex and supports publishing
+ multiple modules, thread pools, and it uses a new threaded
+ architecture for accessing ZPublisher.
+
+Running ZServer as nobody
+
+ Normally ZServer will run with the userid of the user who starts
+ it. However, if ZServer is started by root, it will attempt to
+ become nobody or any userid you specify with the -u argument to the
+ z2.py start script.
+
+ ZServer is similar to ZopeHTTPServer in these respects.
+
+ If you run Zope with different userids you must be aware of
+ permission issues. Zope must be able to read and write to the 'var'
+ directory. If you change the userid Zope is running under you will
+ probably need to change the permissions on the 'var' directory
+ and the files in it in order for Zope to run under a different
+ userid.
+
+Support
+
+ Questions and comments should go to 'support@digicool.com'.
+
+ You can report bugs and check on the status of bugs using the Zope
+ bug collector::
+
+ http://www.zope.org/Resources/Collector/
+
+License
+
+ ZServer is covered by the ZPL despite the fact that it comes with
+ much of the Medusa source code. The portions of Medusa that come
+ with ZServer are licensed under the ZPL.
+
+Outstanding issues
+
+ The FTP interface for Zope objects may be changed.
+
+ HTTP 1.1 support is ZServer is incomplete, though it should work for
+ most HTTP 1.1 clients.
+
=== Zope/lib/python/ZServer/WebDAVSrcHandler.py 1.9 => 1.10 ===
--- /dev/null Tue Mar 18 16:15:46 2003
+++ Zope/lib/python/ZServer/WebDAVSrcHandler.py Tue Mar 18 16:15:14 2003
@@ -0,0 +1,59 @@
+##############################################################################
+#
+# Copyright (c) 2001 Zope Corporation and Contributors. 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.
+# 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.
+#
+##############################################################################
+
+"""HTTP handler which forces GET requests to return the document source.
+
+Works around current WebDAV clients' failure to implement the
+'source-link' feature of the specification. Uses manage_FTPget().
+"""
+
+import os
+import posixpath
+
+from ZServer.HTTPServer import zhttp_handler
+
+__version__ = "1.0"
+
+
+class WebDAVSrcHandler(zhttp_handler):
+
+ def get_environment(self, request):
+ """Munge the request to ensure that we call manage_FTPGet."""
+ env = zhttp_handler.get_environment(self, request)
+
+ # Set a flag to indicate this request came through the WebDAV source
+ # port server.
+ env['WEBDAV_SOURCE_PORT'] = 1
+
+ if env['REQUEST_METHOD'] == 'GET':
+ path_info = env['PATH_INFO']
+ if os.sep != '/':
+ path_info = path_info.replace(os.sep, '/')
+ path_info = posixpath.join(path_info, 'manage_FTPget')
+ path_info = posixpath.normpath(path_info)
+ env['PATH_INFO'] = path_info
+
+ # Workaround for lousy WebDAV implementation of M$ Office 2K.
+ # Requests for "index_html" are *sometimes* send as "index_html."
+ # We check the user-agent and remove a trailing dot for PATH_INFO
+ # and PATH_TRANSLATED
+
+ if env.get("HTTP_USER_AGENT", "").find(
+ "Microsoft Data Access Internet Publishing Provider") > -1:
+ if env["PATH_INFO"][-1] == '.':
+ env["PATH_INFO"] = env["PATH_INFO"][:-1]
+
+ if env["PATH_TRANSLATED"][-1] == '.':
+ env["PATH_TRANSLATED"] = env["PATH_TRANSLATED"][:-1]
+
+ return env
=== Zope/lib/python/ZServer/ZService.py 1.15 => 1.16 ===
--- /dev/null Tue Mar 18 16:15:46 2003
+++ Zope/lib/python/ZServer/ZService.py Tue Mar 18 16:15:14 2003
@@ -0,0 +1,241 @@
+##############################################################################
+#
+# Copyright (c) 2001 Zope Corporation and Contributors. 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.
+# 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
+#
+##############################################################################
+"""
+ZServer as a NT service.
+
+The serice starts up and monitors a ZServer process.
+
+Features:
+
+ * When you start the service it starts ZServer
+ * When you stop the serivice it stops ZServer
+ * It monitors ZServer and restarts it if it exits abnormally
+ * If ZServer is shutdown from the web, the service stops.
+ * If ZServer cannot be restarted, the service stops.
+
+Usage:
+
+ Installation
+
+ The ZServer service should be installed by the Zope Windows
+ installer. You can manually install, uninstall the service from
+ the commandline.
+
+ ZService.py [options] install|update|remove|start [...]
+ |stop|restart [...]|debug [...]
+
+ Options for 'install' and 'update' commands only:
+
+ --username domain\username : The Username the service is to run
+ under
+
+ --password password : The password for the username
+
+ --startup [manual|auto|disabled] : How the service starts,
+ default = manual
+
+ Commands
+
+ install : Installs the service
+
+ update : Updates the service, use this when you change
+ ZServer.py
+
+ remove : Removes the service
+
+ start : Starts the service, this can also be done from the
+ services control panel
+
+ stop : Stops the service, this can also be done from the
+ services control panel
+
+ restart : Restarts the service
+
+ debug : Runs the service in debug mode
+
+ You can view the usage options by running ZServer.py without any
+ arguments.
+
+ Note: you may have to register the Python service program first,
+
+ win32\pythonservice.exe /register
+
+ Starting Zope
+
+ Start Zope by clicking the 'start' button in the services control
+ panel. You can set Zope to automatically start at boot time by
+ choosing 'Auto' startup by clicking the 'statup' button.
+
+ Stopping Zope
+
+ Stop Zope by clicking the 'stop' button in the services control
+ panel. You can also stop Zope through the web by going to the
+ Zope control panel and by clicking 'Shutdown'.
+
+ Event logging
+
+ Zope events are logged to the NT application event log. Use the
+ event viewer to keep track of Zope events.
+
+ Registry Settings
+
+ You can change how the service starts ZServer by editing a registry
+ key.
+
+ HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\
+ <Service Name>\Parameters\start
+
+ The value of this key is the command which the service uses to
+ start ZServer. For example:
+
+ "C:\Program Files\Zope\bin\python.exe"
+ "C:\Program Files\Zope\z2.py" -w 8888
+
+
+TODO:
+
+ * Integrate it into the Windows installer.
+ * Add ZLOG logging in addition to event log logging.
+ * Make it easier to run multiple Zope services with one Zope install
+
+This script does for NT the same sort of thing zdaemon.py does for UNIX.
+Requires Python win32api extensions.
+"""
+import sys, os, time, imp
+
+# Some fancy path footwork is required here because we
+# may be run from python.exe or lib/win32/PythonService.exe
+
+home=os.path.split(os.path.split(sys.executable)[0])[0]
+if sys.executable[-10:]!='python.exe':
+ home=os.path.split(home)[0]
+ home=os.path.split(home)[0]
+sys.path.append(os.path.join(home, 'bin'))
+sys.path.append(os.path.join(home, 'ZServer'))
+sys.path.append(os.path.join(home, 'bin', 'lib', 'win32'))
+sys.path.append(os.path.join(home, 'bin', 'lib', 'win32', 'lib'))
+
+
+# pythoncom and pywintypes are special, and require these hacks when
+# we dont have a standard Python installation around.
+
+import win32api
+def magic_import(modulename, filename):
+ # by Mark Hammond
+ try:
+ # See if it does import first!
+ return __import__(modulename)
+ except ImportError:
+ pass
+ # win32 can find the DLL name.
+ h = win32api.LoadLibrary(filename)
+ found = win32api.GetModuleFileName(h)
+ # Python can load the module
+ mod = imp.load_module(modulename, None, found, ('.dll', 'rb', imp.C_EXTENSION))
+ # inject it into the global module list.
+ sys.modules[modulename] = mod
+ # And finally inject it into the namespace.
+ globals()[modulename] = mod
+ win32api.FreeLibrary(h)
+
+magic_import('pywintypes','pywintypes21.dll')
+
+import win32serviceutil, win32service, win32event, win32process
+try: import servicemanager
+except: pass
+
+
+
+class ZServerService(win32serviceutil.ServiceFramework):
+
+ # Some trickery to determine the service name. The WISE
+ # installer will write an svcname.txt to the ZServer dir
+ # that we can use to figure out our service name.
+
+ path=os.path.join(home, 'ZServer', 'svcname.txt')
+ file=open(path, 'r')
+ _svc_name_=file.readline().strip()
+ file.close()
+
+ _svc_display_name_ = "Zope (%s)" % _svc_name_
+
+ restart_min_time=5 # if ZServer restarts before this many
+ # seconds then we have a problem, and
+ # need to stop the service.
+
+ def __init__(self, args):
+ win32serviceutil.ServiceFramework.__init__(self, args)
+ self.hWaitStop = win32event.CreateEvent(None, 0, 0, None)
+
+ def SvcDoRun(self):
+ self.start_zserver()
+ while 1:
+ rc=win32event.WaitForMultipleObjects(
+ (self.hWaitStop, self.hZServer), 0, win32event.INFINITE)
+ if rc - win32event.WAIT_OBJECT_0 == 0:
+ break
+ else:
+ self.restart_zserver()
+ self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING, 5000)
+
+ def SvcStop(self):
+ servicemanager.LogInfoMsg('Stopping Zope.')
+ try:
+ self.stop_zserver()
+ except:
+ pass
+ self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
+ win32event.SetEvent(self.hWaitStop)
+
+ def start_zserver(self):
+ sc=self.get_start_command()
+ result=win32process.CreateProcess(None, self.get_start_command(),
+ None, None, 0, 0, None, None, win32process.STARTUPINFO())
+ self.hZServer=result[0]
+ self.last_start_time=time.time()
+ servicemanager.LogInfoMsg('Starting Zope.')
+
+ def stop_zserver(self):
+ win32process.TerminateProcess(self.hZServer,0)
+
+ def restart_zserver(self):
+ if time.time() - self.last_start_time < self.restart_min_time:
+ servicemanager.LogErrorMsg('Zope died and could not be restarted.')
+ self.SvcStop()
+ code=win32process.GetExitCodeProcess(self.hZServer)
+ if code == 0:
+ # Exited with a normal status code,
+ # assume that shutdown is intentional.
+ self.SvcStop()
+ else:
+ servicemanager.LogWarningMsg('Restarting Zope.')
+ self.start_zserver()
+
+ def get_start_command(self):
+ return win32serviceutil.GetServiceCustomOption(self,'start')
+
+
+def set_start_command(value):
+ "sets the ZServer start command if the start command is not already set"
+ current=win32serviceutil.GetServiceCustomOption(ZServerService,
+ 'start', None)
+ if current is None:
+ win32serviceutil.SetServiceCustomOption(ZServerService,'start',value)
+
+
+if __name__=='__main__':
+ win32serviceutil.HandleCommandLine(ZServerService)
+ if 'install' in sys.argv:
+ command='"%s" "%s" -S' % (sys.executable, os.path.join(home,'z2.py'))
+ set_start_command(command)
+ print "Setting ZServer start command to:", command
=== Zope/lib/python/ZServer/__init__.py 1.28 => 1.29 ===
--- /dev/null Tue Mar 18 16:15:46 2003
+++ Zope/lib/python/ZServer/__init__.py Tue Mar 18 16:15:14 2003
@@ -0,0 +1,78 @@
+##############################################################################
+#
+# Copyright (c) 2001 Zope Corporation and Contributors. 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.
+# 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.
+#
+##############################################################################
+
+import sys
+
+from medusa.test import max_sockets
+CONNECTION_LIMIT=max_sockets.max_select_sockets()
+
+ZSERVER_VERSION='1.1'
+try:
+ import App.version_txt
+ ZOPE_VERSION=App.version_txt.version_txt()
+except:
+ ZOPE_VERSION='experimental'
+
+exit_code = 0
+
+# Try to poke zLOG default logging into asyncore
+# XXX We should probably should do a better job of this,
+# however that would mean that ZServer required zLOG.
+# (Is that really a bad thing?)
+try:
+ from zLOG import LOG, register_subsystem, BLATHER, INFO, WARNING, ERROR
+except ImportError:
+ pass
+else:
+ register_subsystem('ZServer')
+ severity={'info':INFO, 'warning':WARNING, 'error': ERROR}
+
+ def log_info(self, message, type='info'):
+ if message[:14]=='adding channel' or \
+ message[:15]=='closing channel' or \
+ message == 'Computing default hostname':
+ LOG('ZServer', BLATHER, message)
+ else:
+ LOG('ZServer', severity[type], message)
+
+ import asyncore
+ asyncore.dispatcher.log_info=log_info
+
+# A routine to try to arrange for request sockets to be closed
+# on exec. This makes it easier for folks who spawn long running
+# processes from Zope code. Thanks to Dieter Maurer for this.
+try:
+ import fcntl
+
+ def requestCloseOnExec(sock):
+ try:
+ fcntl.fcntl(sock.fileno(), fcntl.F_SETFD, fcntl.FD_CLOEXEC)
+ except: # XXX What was this supposed to catch?
+ pass
+
+except (ImportError, AttributeError):
+
+ def requestCloseOnExec(sock):
+ pass
+
+import asyncore
+from medusa import resolver, logger
+from HTTPServer import zhttp_server, zhttp_handler
+from PCGIServer import PCGIServer
+from FCGIServer import FCGIServer
+from FTPServer import FTPServer
+from PubCore import setNumberOfThreads
+from medusa.monitor import secure_monitor_server
+
+# override the service name in logger.syslog_logger
+logger.syslog_logger.svc_name='ZServer'
=== Zope/lib/python/ZServer/component.xml 1.1 => 1.2 ===
--- /dev/null Tue Mar 18 16:15:46 2003
+++ Zope/lib/python/ZServer/component.xml Tue Mar 18 16:15:14 2003
@@ -0,0 +1,61 @@
+<component prefix="ZServer.datatypes">
+
+ <abstracttype name="server">
+ <description>
+ The "server" type is used to describe a single type of server
+ instance. The value for a server section is an object with the
+ ServerFactory interface.
+ </description>
+ </abstracttype>
+
+ <sectiontype name="http-server"
+ datatype=".HTTPServerFactory"
+ implements="server">
+ <key name="address" datatype="inet-address"/>
+ <key name="force-connection-close" datatype="boolean" default="off"/>
+ <key name="webdav-source-clients">
+ <description>
+ Regular expression used to identify clients who should
+ receive WebDAV source responses to GET requests.
+ </description>
+ </key>
+ </sectiontype>
+
+ <sectiontype name="webdav-source-server"
+ datatype=".WebDAVSourceServerFactory"
+ implements="server">
+ <key name="address" datatype="inet-address"/>
+ <key name="force-connection-close" datatype="boolean" default="off"/>
+ </sectiontype>
+
+ <sectiontype name="persistent-cgi"
+ datatype=".PCGIServerFactory"
+ implements="server">
+ <key name="path" datatype="existing-file"/>
+ </sectiontype>
+
+ <sectiontype name="fast-cgi"
+ datatype=".FCGIServerFactory"
+ implements="server">
+ <key name="address" datatype="socket-address"/>
+ </sectiontype>
+
+ <sectiontype name="ftp-server"
+ datatype=".FTPServerFactory"
+ implements="server">
+ <key name="address" datatype="inet-address"/>
+ </sectiontype>
+
+ <sectiontype name="monitor-server"
+ datatype=".MonitorServerFactory"
+ implements="server">
+ <key name="address" datatype="inet-address"/>
+ </sectiontype>
+
+ <sectiontype name="icp-server"
+ datatype=".ICPServerFactory"
+ implements="server">
+ <key name="address" datatype="inet-address"/>
+ </sectiontype>
+
+</component>
=== Zope/lib/python/ZServer/datatypes.py 1.1 => 1.2 ===
--- /dev/null Tue Mar 18 16:15:47 2003
+++ Zope/lib/python/ZServer/datatypes.py Tue Mar 18 16:15:14 2003
@@ -0,0 +1,167 @@
+##############################################################################
+#
+# Copyright (c) 2003 Zope Corporation and Contributors.
+# 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.
+# 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.
+#
+##############################################################################
+"""ZConfig datatype support for ZServer.
+
+Each server type is represented by a ServerFactory instance.
+"""
+
+class ServerFactory:
+ def __init__(self, address=None):
+ if address is None:
+ self.host = None
+ self.port = None
+ else:
+ self.host, self.port = address
+
+ def prepare(self, defaulthost=None, dnsresolver=None,
+ module=None, env=None, portbase=None):
+ if defaulthost is not None:
+ self._set_default_host(defaulthost)
+ self.dnsresolver = dnsresolver
+ self.module = module
+ self.cgienv = env
+ if portbase and self.port is not None:
+ self.port += portbase
+
+ def _set_default_host(self, host):
+ if not self.host:
+ self.host = host
+
+ def servertype(self):
+ s = self.__class__.__name__
+ if s.endswith("Factory"):
+ s = s[:-7]
+ return s
+
+ def create(self):
+ raise NotImplementedError(
+ "Concrete ServerFactory classes must implement create().")
+
+
+class HTTPServerFactory(ServerFactory):
+ def __init__(self, section):
+ ServerFactory.__init__(self, section.address)
+ self.force_connection_close = section.force_connection_close
+ # webdav-source-server sections won't have webdav_source_clients:
+ webdav_clients = getattr(section, "webdav_source_clients", None)
+ self.webdav_source_clients = webdav_clients
+
+ def create(self):
+ from ZServer import HTTPServer
+ from ZServer.AccessLogger import access_logger
+ handler = self.createHandler()
+ handler._force_connection_close = self.force_connection_close
+ if self.webdav_source_clients:
+ handler.set_webdav_source_clients(self.webdav_source_clients)
+ server = HTTPServer.zhttp_server(ip=self.host, port=self.port,
+ resolver=self.dnsresolver,
+ logger_object=access_logger)
+ server.install_handler(handler)
+ return server
+
+ def createHandler(self):
+ from ZServer import HTTPServer
+ return HTTPServer.zhttp_handler(self.module, '', self.cgienv)
+
+
+class WebDAVSourceServerFactory(HTTPServerFactory):
+ def createHandler(self):
+ from ZServer.WebDAVSrcHandler import WebDAVSrcHandler
+ return WebDAVSrcHandler(self.module, '', self.cgienv)
+
+
+class FTPServerFactory(ServerFactory):
+ def __init__(self, section):
+ ServerFactory.__init__(self, section.address)
+
+ def create(self):
+ from ZServer.AccessLogger import access_logger
+ from ZServer.FTPServer import FTPServer
+ return FTPServer(ip=self.host, port=self.port,
+ module=self.module, resolver=self.dnsresolver,
+ logger_object=access_logger)
+
+
+class PCGIServerFactory(ServerFactory):
+ def __init__(self, section):
+ ServerFactory.__init__(self)
+ self.path = section.path
+
+ def create(self):
+ from ZServer.AccessLogger import access_logger
+ from ZServer.PCGIServer import PCGIServer
+ return PCGIServer(ip=self.host, port=self.port,
+ module=self.module, resolver=self.dnsresolver,
+ pcgi_file=self.path,
+ logger_object=access_logger)
+
+
+class FCGIServerFactory(ServerFactory):
+ def __init__(self, section):
+ import socket
+ if section.address.family == socket.AF_INET:
+ address = section.address.address
+ path = None
+ else:
+ address = None
+ path = section.address.address
+ ServerFactory.__init__(self, address)
+ self.path = path
+
+ def _set_default_host(self, host):
+ if self.path is None:
+ ServerFactory._set_default_host(self, host)
+
+ def create(self):
+ from ZServer.AccessLogger import access_logger
+ from ZServer.FCGIServer import FCGIServer
+ return FCGIServer(ip=self.host, port=self.port,
+ socket_file=self.path,
+ module=self.module, resolver=self.dnsresolver,
+ logger_object=access_logger)
+
+
+class MonitorServerFactory(ServerFactory):
+ def __init__(self, section):
+ ServerFactory.__init__(self, section.address)
+
+ def create(self):
+ from ZServer.medusa.monitor import secure_monitor_server
+ return secure_monitor_server(hostname=self.host, port=self.port,
+ password=self.getPassword())
+
+ def getPassword(self):
+ # XXX This is really out of place; there should be a better
+ # way. For now, at least we can make it a separate method.
+
+ import ZODB # :-( required to import user
+ from AccessControl.User import emergency_user
+ if hasattr(emergency_user, '__null_user__'):
+ pw = None
+ else:
+ pw = emergency_user._getPassword()
+ if pw is None:
+ import zLOG
+ zLOG.LOG("Zope", zLOG.WARNING, 'Monitor server not started'
+ ' because no emergency user exists.')
+ return pw
+
+
+class ICPServerFactory(ServerFactory):
+ def __init__(self, section):
+ ServerFactory.__init__(self, section.address)
+
+ def create(self):
+ from ZServer.ICPServer import ICPServer
+ return ICPServer(self.host, self.port)