############################################################################## # # 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')