import os import sys import socket from BaseHTTPServer import HTTPServer import win32service import win32serviceutil import win32event import win32file from roundup_server import RoundupRequestHandler class SvcShutdown(Exception): pass class RoundupService(win32serviceutil.ServiceFramework, HTTPServer): '''A Roundup standalone server for Win32 by Ewout Prangsma. Modified by Giles Brown. ''' _svc_name_ = "Roundup Bug Tracker" _svc_display_name_ = "Roundup Bug Tracker" # Default options. # Sub-set of roundup_server.py options that make sense for NT service. options = { 'hostname' : '', 'port' : 8080, 'logfile' : None, 'log_ipaddress' : 1, } def __init__(self, args): options = dict(RoundupService.options) # Read options from registry for name, val in options.items(): options[name] = win32serviceutil.GetServiceCustomOption( RoundupService, name, val) RoundupRequestHandler.LOG_IPADDRESS = options['log_ipaddress'] numtrackers = win32serviceutil.GetServiceCustomOption( RoundupService, 'numtrackers', 0) RoundupRequestHandler.TRACKER_HOMES.clear() for i in range(numtrackers): trackername = win32serviceutil.GetServiceCustomOption( RoundupService, 'trackername%d' % i, '') trackerhome = win32serviceutil.GetServiceCustomOption( RoundupService, 'trackerhome%d' % i, '') RoundupRequestHandler.TRACKER_HOMES[trackername] = trackerhome win32serviceutil.ServiceFramework.__init__(self, args) HTTPServer.__init__(self, (options['hostname'], options['port']), RoundupRequestHandler) # redirect stdout/stderr to our logfile if options['logfile']: # appending, unbuffered sys.stdout = sys.stderr = open(options['logfile'], 'a', 0) else: # Re-direct standard output so PythonWin Trace Collection can read import win32traceutil # Create the necessary NT Event synchronization objects... # hevSvcStop is signaled when the SCM sends us a notification # to shutdown the service. self.hevSvcStop = win32event.CreateEvent(None, 0, 0, None) # hevConn is signaled when we have a new incomming connection. self.hevConn = win32event.CreateEvent(None, 0, 0, None) # Hang onto this module for other people to use for logging # purposes. import servicemanager self.servicemanager = servicemanager def SvcStop(self): # Before we do anything, tell the SCM we are starting the # stop process. self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING) win32event.SetEvent(self.hevSvcStop) def SvcDoRun(self): try: self.serve_forever() except SvcShutdown: pass def get_request(self): '''Return (blocking until) next HTTP request.''' # Call WSAEventSelect to enable self.socket to be waited on. win32file.WSAEventSelect(self.socket, self.hevConn, win32file.FD_ACCEPT) # Loop until either: # - we accept a socket connection, or # - we raise an exception because we have been asked to stop while 1: try: rv = self.socket.accept() break except socket.error, why: if why[0] != win32file.WSAEWOULDBLOCK: raise # Use WaitForMultipleObjects instead of select() because # on NT select() is only good for sockets, and not general # NT synchronization objects. rc = win32event.WaitForMultipleObjects((self.hevSvcStop, self.hevConn), 0, win32event.INFINITE) if rc == win32event.WAIT_OBJECT_0: # self.hevSvcStop was signaled, this means: # Stop the service! # So we throw the shutdown exception, which gets # caught by self.SvcDoRun raise SvcShutdown # Otherwise, rc == WAIT_OBJECT_0 + 1 which means # self.hevConn was signaled, which means when we call # self.socket.accept(), we'll have our incoming connection # socket! # Loop back to the top, and let that accept do its thing... # yay! we have a connection # However... the new socket is non-blocking, we need to # set it back into blocking mode. (The socket that accept() # returns has the same properties as the listening sockets, # this includes any properties set by WSAAsyncSelect, or # WSAEventSelect, and whether its a blocking socket or not.) # # So if you yank the following line, the setblocking() call # will be useless. The socket will still be in non-blocking # mode. win32file.WSAEventSelect(rv[0], self.hevConn, 0) rv[0].setblocking(1) return rv if __name__ == '__main__': # # Ugly hack to override win32serviceutil usage() origusage = win32serviceutil.usage def usage(): # Catch call to sys.exit(1) so we can tack our message on the end try: origusage() except SystemExit, e: pass print " -n hostname : sets the host name" print " -p port : sets the port to listen on" print " -l path : sets path of log file" print " -N : log names in access log not IP addresses (much slower)" print " -t = : add track to service" print print "Cannot change tracker options using 'update'." print "Instead use 'remove' then 'install'." sys.exit(1) win32serviceutil.usage = usage def customOptionHandler(optlist): options = dict(RoundupService.options) trackers = [] # Override default values for (opt, arg) in optlist: if opt == '-n': options['hostname'] = arg elif opt == '-p': options['port'] = int(arg) elif opt == '-l': options['logfile'] = os.path.abspath(arg) elif opt == '-N': options['log_ipaddress'] = 0 elif opt == '-t': trackers.append(arg.split('=')) # Save settings for name, val in options.iteritems(): win32serviceutil.SetServiceCustomOption(RoundupService, name, val) # XXX: would use 'enumerate' in python 2.3 onwards for index, (name, home) in zip(range(len(trackers)), trackers): win32serviceutil.SetServiceCustomOption(RoundupService, 'trackername%d' % index, name) win32serviceutil.SetServiceCustomOption(RoundupService, 'trackerhome%d' % index, home) win32serviceutil.SetServiceCustomOption(RoundupService, 'numtrackers', len(trackers)) win32serviceutil.HandleCommandLine(RoundupService, customInstallOptions='t:n:p:l:N', customOptionHandler=customOptionHandler) # Uncomment this for debugging first part of 'RoundupService.__init__' #else: # import win32traceutil