TimerServer.py 6.46 KB
Newer Older
Kevin Deldycke's avatar
Kevin Deldycke committed
1 2
# -*- coding: UTF-8 -*-
# -*- Mode: Python; py-indent-offset: 4 -*-
3
# Authors: Nik Kim <fafhrd@legco.biz>
Kevin Deldycke's avatar
Kevin Deldycke committed
4 5 6 7 8
__version__ = 'TimerServer for Zope 0.1'

import traceback

import thread
9
import re
10
import sys, os, errno, time, socket
11
from functools import partial
Kevin Deldycke's avatar
Kevin Deldycke committed
12 13 14 15 16 17
from StringIO import StringIO
from zLOG import LOG, INFO

from ZPublisher.BaseRequest import BaseRequest
from ZPublisher.BaseResponse import BaseResponse
from ZPublisher.HTTPRequest import HTTPRequest
18
from ZPublisher.HTTPResponse import HTTPResponse
19
import ZPublisher.HTTPRequest
Kevin Deldycke's avatar
Kevin Deldycke committed
20 21

class TimerServer:
Kevin Deldycke's avatar
Kevin Deldycke committed
22
    def __init__(self, module, interval=600):
Kevin Deldycke's avatar
Kevin Deldycke committed
23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
        self.module = module

        self.interval = interval

        sync = thread.allocate_lock()

        self._a = sync.acquire
        self._r = sync.release

        self._a()
        thread.start_new_thread(self.run, ())
        self._r()

        LOG('ZServer', INFO,
            'Timer server started at %s\n'
            '\tInterval: %s seconds.\n'%(time.ctime(time.time()), interval))

    def run(self):
41 42 43 44 45 46 47 48 49 50 51
        try:
            zopewsgi = sys.modules['Products.ERP5.bin.zopewsgi']
        except KeyError:
            # wait until the zhttp_server exist in socket_map
            # because TimerService has to be started after the Zope HTTPServer
            from asyncore import socket_map
            ip = port = ''
            while 1:
                time.sleep(5)
                for k, v in socket_map.items():
                    if hasattr(v, 'addr'):
52 53 54
                        # see Zope/lib/python/App/ApplicationManager.py: def getServers(self)
                        type = str(getattr(v, '__class__', 'unknown'))
                        if type == 'ZServer.HTTPServer.zhttp_server':
55
                            ip, port = v.addr
56
                            break
57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80
                if port:
                    break
            from ZServer.PubCore import handle
        else:
            while 1:
                time.sleep(5)
                try:
                    server = zopewsgi.server
                    break
                except AttributeError:
                    pass

            ip, port = server.addr
            start_response = lambda *_: None

            class handle(object):
                def __init__(self, module_name, request, response):
                    self.service = partial(zopewsgi.publish_module,
                        request.environ,
                        start_response,
                        _module_name=module_name,
                        _request=request,
                        _response=response)
                    server.add_task(self)
81

82 83
        if ip == '0.0.0.0':
          ip = socket.gethostbyname(socket.gethostname())
84 85

        # To be very sure, try to connect to the HTTPServer
86 87 88
        # and only start after we are able to connect and got a response
        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        s.settimeout(None)
89 90
        while 1:
            try:
91
                s.connect((ip, port))
92 93
            except socket.error:
                time.sleep(5)
94
                continue
95 96
            s.send('GET / HTTP/1.1\r\n\r\n')
            s.recv(4096) # blocks until a response is received
97
            break
98
        s.close()
99

Kevin Deldycke's avatar
Kevin Deldycke committed
100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126
        module = self.module
        interval = self.interval

        # minutes = time.gmtime(time.time()[4], seconds = time.gmtime(time.time()[5]
        # max interval is therefore 59*60 + 59 = 208919 seconds

        wait = ((time.gmtime(time.time())[4] * 60) + time.gmtime(time.time())[5]) % interval
        sleep = interval - wait

        if sleep > 0:
            time.sleep(sleep)

        LOG('ZServer', INFO, 'Timerserver ready, starting timer services.')

        while 1:
            time.sleep(interval)
            # send message to zope
            try:
                out = StringIO()
                err = StringIO()
                response = TimerResponse(out, err)
                handle(module, TimerRequest(response, interval), response)
            except:
                pass


class TimerResponse(BaseResponse):
127 128 129

    after_list = ()

Kevin Deldycke's avatar
Kevin Deldycke committed
130 131 132 133 134
    def _finish(self):
        pass

    def unauthorized(self):
        pass
135

136 137 138
    def _unauthorized(self):
        pass

139 140 141
    def finalize(self):
        return None, None

142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160
    # This is taken from ZPublisher.HTTPResponse
    # I don't think it's safe to make TimerResponse a subclass of HTTPResponse,
    # so I inline here the method . This is required it you want unicode page
    # templates to be usable by timer service.
    # This is used by an iHotFix patch on PageTemplate.StringIO method
    def _encode_unicode(self, body,
                        charset_re=re.compile(r'(?:application|text)/[-+0-9a-z]+\s*;\s*' +
                                              r'charset=([-_0-9a-z]+' +
                                              r')(?:(?:\s*;)|\Z)',
                                              re.IGNORECASE)):
        # Encode the Unicode data as requested
        if self.headers.has_key('content-type'):
            match = charset_re.match(self.headers['content-type'])
            if match:
                encoding = match.group(1)
                return body.encode(encoding)
        # Use the default character encoding
        return body.encode(ZPublisher.HTTPResponse.default_encoding,'replace')

Kevin Deldycke's avatar
Kevin Deldycke committed
161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181

class TimerRequest(HTTPRequest):

    retry_max_count = 0

    def __init__(self, response, interval):
        stdin=StringIO()
        environ=self._get_env(stdin)
        HTTPRequest.__init__(self, stdin, environ, response, clean=1)

        self.other['interval'] = interval

    def _get_env(self, stdin):
        "Returns a CGI style environment"
        env={}
        env['REQUEST_METHOD']='GET'
        env['SERVER_SOFTWARE']= 'TimerServer for Zope'
        env['SERVER_NAME'] = ''
        env['SERVER_PORT'] = ''
        env['REMOTE_ADDR'] = ''
        env['GATEWAY_INTERFACE'] = 'CGI/1.1'
182
        env['SERVER_PROTOCOL'] = 'HTTP/1.0'
Kevin Deldycke's avatar
Kevin Deldycke committed
183 184 185

        env['PATH_INFO']= '/Control_Panel/timer_service/process_timer'
        return env
186 187 188 189 190 191 192 193 194 195 196 197 198 199

    def clone(self):
        # This method is a dumb copy of Zope-2.8's one that makes timerserver
        # works in Zope-2.12 too.
        #
        # Return a clone of the current request object
        # that may be used to perform object traversal.
        environ = self.environ.copy()
        environ['REQUEST_METHOD'] = 'GET'
        if self._auth:
            environ['HTTP_AUTHORIZATION'] = self._auth
        clone = HTTPRequest(None, environ, HTTPResponse(), clean=1)
        clone['PARENTS'] = [self['PARENTS'][-1]]
        return clone