Commit d1c27c75 authored by Vincent Pelletier's avatar Vincent Pelletier

Initial checkin of modified ClockServer. See README to know what comes from...

Initial checkin of modified ClockServer. See README to know what comes from Zope trunk, what has been altered and what is localy created.


git-svn-id: https://svn.erp5.org/repos/public/erp5/trunk@22385 20353a03-c40f-0410-a6d1-a30d3c3de9de
parent 82740ad1
##############################################################################
#
# Copyright (c) 2008 Nexedi SARL and Contributors. All Rights Reserved.
# Vincent Pelletier <vincent@nexedi.com>
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# garantees and support are strongly adviced to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
##############################################################################
from OriginalClockServer import ClockServer as OriginalClockServer
from OriginalClockServer import DummyChannel
from ZServer.medusa.http_server import http_request
from ZServer.HTTPResponse import make_response
from ZPublisher.HTTPRequest import HTTPRequest
import StringIO
import thread
wait_for_close_lock = thread.allocate_lock()
class ClockServer(OriginalClockServer):
running = True
def readable(self):
"""
Avoid starting a new tic if shutdown started.
"""
if self.running:
OriginalClockServer.readable(self)
return False
def clean_shutdown_control(self, _shutdown_phase, time_in_this_phase):
"""
Inform invoked method that a shutdown is in progress.
Here we:
- Prevent regular tics from being sent. This does not prevent
already-issued tics from running.
- Issue a special tic, ran asynchronously from regular tics and
asynchronously from this thread.
- Wait for that special tic to return, so that we know all clean
shutdown handlers have completely run.
- Return control to caller.
To wait for shutdown handler to return, it has been chosen to use a
semaphore scheme. It has the following drawbacks:
- It is intrusive: we need to hook foreign classes, since it's not
the way things happen with regular zope data exchange.
- We can't get what the shutdown handler returned (http return code,
page content, ...) so we will never take Lifetime's veto. So shutdown
handler must block until shutdown is complete, which is not how
clean_shutdown_control is supposed to work. Note though that it is a
design weakness in clean_shutdown_control, since some shutdown
handlers might not have finshed their job at the time process gets
closed.
"""
self.running = False
separator = '?' in self.method and '&' or '?'
# XXX: should use a float for time representation
# TODO: allow user to specify a separate shutdown method instead of
# reusing regular one.
method = '%s%sshutdown:int=1&phase:int=%i&time_in_phase:int=%i' % \
(self.method, separator, _shutdown_phase, time_in_this_phase)
stdin = StringIO.StringIO()
request_string = 'GET %s HTTP/1.0' % (method, )
request = http_request(DummyChannel(self), request_string, 'GET', method,
'1.0', self.headers)
environment = self.get_env(request)
response = make_response(request, environment)
# Hook response._finish to get a notification when request is over.
def _finish():
response.__class__._finish(response)
wait_for_close_lock.release()
response._finish = _finish
# (end of hook)
zope_request = HTTPRequest(stdin, environment, response)
wait_for_close_lock.acquire()
self.zhandler('Zope2', zope_request, response)
self.log_info('ClockServer: Waiting for shutdown handler.')
wait_for_close_lock.acquire()
self.log_info('ClockServer: Going on.')
wait_for_close_lock.release()
return 0 # TODO: detect an error to allow taking the veto.
##############################################################################
#
# Copyright (c) 2005 Chris McDonough. 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
#
##############################################################################
""" Zope clock server. Generate a faux HTTP request on a regular basis
by coopting the asyncore API. """
import posixpath
import os
import socket
import time
import StringIO
import asyncore
from ZServer.medusa.http_server import http_request
from ZServer.medusa.default_handler import unquote
from ZServer.PubCore import handle
from ZServer.HTTPResponse import make_response
from ZPublisher.HTTPRequest import HTTPRequest
def timeslice(period, when=None, t=time.time):
if when is None:
when = t()
return when - (when % period)
class LogHelper:
def __init__(self, logger):
self.logger = logger
def log(self, ip, msg, **kw):
self.logger.log(ip + ' ' + msg)
class DummyChannel:
# we need this minimal do-almost-nothing channel class to appease medusa
addr = ['127.0.0.1']
closed = 1
def __init__(self, server):
self.server = server
def push_with_producer(self):
pass
def close_when_done(self):
pass
class ClockServer(asyncore.dispatcher):
# prototype request environment
_ENV = dict(REQUEST_METHOD = 'GET',
SERVER_PORT = 'Clock',
SERVER_NAME = 'Zope Clock Server',
SERVER_SOFTWARE = 'Zope',
SERVER_PROTOCOL = 'HTTP/1.0',
SCRIPT_NAME = '',
GATEWAY_INTERFACE='CGI/1.1',
REMOTE_ADDR = '0')
# required by ZServer
SERVER_IDENT = 'Zope Clock'
def __init__ (self, method, period=60, user=None, password=None,
host=None, logger=None, handler=None):
self.period = period
self.method = method
self.last_slice = timeslice(period)
h = self.headers = []
h.append('User-Agent: Zope Clock Server Client')
h.append('Accept: text/html,text/plain')
if not host:
host = socket.gethostname()
h.append('Host: %s' % host)
auth = False
if user and password:
encoded = ('%s:%s' % (user, password)).encode('base64')
h.append('Authorization: Basic %s' % encoded)
auth = True
asyncore.dispatcher.__init__(self)
self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
self.logger = LogHelper(logger)
self.log_info('Clock server for "%s" started (user: %s, period: %s)'
% (method, auth and user or 'Anonymous', self.period))
if handler is None:
# for unit testing
handler = handle
self.zhandler = handler
def get_requests_and_response(self):
out = StringIO.StringIO()
s_req = '%s %s HTTP/%s' % ('GET', self.method, '1.0')
req = http_request(DummyChannel(self), s_req, 'GET', self.method,
'1.0', self.headers)
env = self.get_env(req)
resp = make_response(req, env)
zreq = HTTPRequest(out, env, resp)
return req, zreq, resp
def get_env(self, req):
env = self._ENV.copy()
(path, params, query, fragment) = req.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:]
env['PATH_INFO']= '/' + path
env['PATH_TRANSLATED']= posixpath.normpath(
posixpath.join(os.getcwd(), env['PATH_INFO']))
if query:
env['QUERY_STRING'] = query
env['channel.creation_time']=time.time()
for header in req.header:
key,value = header.split(":",1)
key = key.upper()
value = value.strip()
key = 'HTTP_%s' % ("_".join(key.split( "-")))
if value:
env[key]=value
return env
def readable(self):
# generate a request at most once every self.period seconds
slice = timeslice(self.period)
if slice != self.last_slice:
# no need for threadsafety here, as we're only ever in one thread
self.last_slice = slice
req, zreq, resp = self.get_requests_and_response()
self.zhandler('Zope2', zreq, resp)
return False
def handle_read(self):
return True
def handle_write (self):
self.log_info('unexpected write event', 'warning')
return True
def writable(self):
return False
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 Clock (%s:%s %s)' % (t, v, tbinfo),
'error')
This is an unnoficial Zope 2.8 backport of official Zope's ClockServer.
Status of initial checkin compared to official version:
__init__.py
Locally created.
ClockServer.py
Locally created.
component.xml
Fix component prefix.
datatypes.py
Import ServerFactory (originaly locally defined).
Fix ClockServer class import.
OriginalClockServer.py
Unchanged original ClockServer.py.
README
Locally created.
To enable it, add (and adapt) the following to your zope.conf:
%import Products.ClockServer
<clock-server>
# starts a clock which calls /foo/bar every 30 seconds
method /foo/bar
period 30
user admin
password 123
</clock-server>
ERP5 users: You are strongly encouraged to kee TimerService (but to stop using
timerserver) and use the following configuration:
method /Control_Panel/timer_service/process_timer?interval:int=5
period 5
Note: Because ClockServer uses asyncore's "readable" method polling,
configured frequency is only a maximum value. Minimum freqency depends on
asyncore configuration (one wakeup every 30s on my machine). If there is
activity on Zope's sockets, frequency will increase.
This ClockServer is extended (see ClockServer.py) to propagate shutdown
notification to configured method, by pasing it extra parameters.
This allows method to put shutdown sequence on hold (but not interrupt it).
Also, note that it must not be abused: it's both bad to make user wait, and
there are some timeouts killing Zope if it takes too long to stop.
<component prefix="Products.ClockServer.datatypes">
<sectiontype name="clock-server"
datatype=".ClockServerFactory"
implements="ZServer.server">
<key name="method" datatype="string">
<description>
The traversal path (from the Zope root) to an
executable Zope method (Python Script, external method, product
method, etc). The method must take no arguments. Ex: "/site/methodname"
</description>
</key>
<key name="period" datatype="integer" default="60">
<description>
The number of seconds between each clock "tick" (and
thus each call to the above "method"). The lowest number
providable here is typically 30 (this is the asyncore mainloop
"timeout" value). The default is 60. Ex: "30"
</description>
</key>
<key name="user" datatype="string">
<description>
A zope username. Ex: "admin"
</description>
</key>
<key name="password" datatype="string">
<description>
The password for the zope username provided above. Careful: this
is obviously not encrypted in the config file. Ex: "123"
</description>
</key>
<key name="host" datatype="string">
<description>
The hostname passed in via the "Host:" header in the
faux request. Could be useful if you have virtual host rules
set up inside Zope itself. Ex: "www.example.com"
</description>
</key>
</sectiontype>
</component>
##############################################################################
#
# Copyright (c) 2003 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.
#
##############################################################################
from ZServer.datatypes import ServerFactory
class ClockServerFactory(ServerFactory):
def __init__(self, section):
ServerFactory.__init__(self)
self.method = section.method
self.period = section.period
self.user = section.user
self.password = section.password
self.hostheader = section.host
self.host = None # appease configuration machinery
def create(self):
from Products.ClockServer.ClockServer import ClockServer
from ZServer.AccessLogger import access_logger
return ClockServer(self.method, self.period, self.user,
self.password, self.hostheader, access_logger)
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