1616#
1717""" HTTP Server that serves roundup.
1818
19- $Id: roundup_server.py,v 1.35 2003-12-04 02:43:07 richard Exp $
19+ $Id: roundup_server.py,v 1.36 2003-12-06 02:46:34 richard Exp $
2020"""
2121
2222# python version check
4444}
4545
4646ROUNDUP_USER = None
47+ ROUNDUP_GROUP = None
48+ ROUNDUP_LOG_IP = 1
49+ HOSTNAME = ''
50+ PORT = 8080
51+ PIDFILE = None
52+ LOGFILE = None
4753
4854
4955#
@@ -196,7 +202,7 @@ def inner_run_cgi(self):
196202 c = tracker .Client (tracker , self , env )
197203 c .main ()
198204
199- LOG_IPADDRESS = 1
205+ LOG_IPADDRESS = ROUNDUP_LOG_IP
200206 def address_string (self ):
201207 if self .LOG_IPADDRESS :
202208 return self .client_address [0 ]
@@ -208,23 +214,128 @@ def error():
208214 exc_type , exc_value = sys .exc_info ()[:2 ]
209215 return _ ('Error: %s: %s' % (exc_type , exc_value ))
210216
217+ try :
218+ import win32serviceutil
219+ except :
220+ RoundupService = None
221+ else :
222+ # allow the win32
223+ import win32service
224+ import win32event
225+ from win32event import *
226+ from win32file import *
227+
228+ SvcShutdown = "ServiceShutdown"
229+
230+ class RoundupService (win32serviceutil .ServiceFramework ,
231+ BaseHTTPServer .HTTPServer ):
232+ ''' A Roundup standalone server for Win32 by Ewout Prangsma
233+ '''
234+ _svc_name_ = "Roundup Bug Tracker"
235+ _svc_display_name_ = "Roundup Bug Tracker"
236+ address = (HOSTNAME , PORT )
237+ def __init__ (self , args ):
238+ # redirect stdout/stderr to our logfile
239+ if LOGFILE :
240+ # appending, unbuffered
241+ sys .stdout = sys .stderr = open (LOGFILE , 'a' , 0 )
242+ win32serviceutil .ServiceFramework .__init__ (self , args )
243+ BaseHTTPServer .HTTPServer .__init__ (self , self .address ,
244+ RoundupRequestHandler )
245+
246+ # Create the necessary NT Event synchronization objects...
247+ # hevSvcStop is signaled when the SCM sends us a notification
248+ # to shutdown the service.
249+ self .hevSvcStop = win32event .CreateEvent (None , 0 , 0 , None )
250+
251+ # hevConn is signaled when we have a new incomming connection.
252+ self .hevConn = win32event .CreateEvent (None , 0 , 0 , None )
253+
254+ # Hang onto this module for other people to use for logging
255+ # purposes.
256+ import servicemanager
257+ self .servicemanager = servicemanager
258+
259+ def SvcStop (self ):
260+ # Before we do anything, tell the SCM we are starting the
261+ # stop process.
262+ self .ReportServiceStatus (win32service .SERVICE_STOP_PENDING )
263+ win32event .SetEvent (self .hevSvcStop )
264+
265+ def SvcDoRun (self ):
266+ try :
267+ self .serve_forever ()
268+ except SvcShutdown :
269+ pass
270+
271+ def get_request (self ):
272+ # Call WSAEventSelect to enable self.socket to be waited on.
273+ WSAEventSelect (self .socket , self .hevConn , FD_ACCEPT )
274+ while 1 :
275+ try :
276+ rv = self .socket .accept ()
277+ except socket .error , why :
278+ if why [0 ] != WSAEWOULDBLOCK :
279+ raise
280+ # Use WaitForMultipleObjects instead of select() because
281+ # on NT select() is only good for sockets, and not general
282+ # NT synchronization objects.
283+ rc = WaitForMultipleObjects ((self .hevSvcStop , self .hevConn ),
284+ 0 , INFINITE )
285+ if rc == WAIT_OBJECT_0 :
286+ # self.hevSvcStop was signaled, this means:
287+ # Stop the service!
288+ # So we throw the shutdown exception, which gets
289+ # caught by self.SvcDoRun
290+ raise SvcShutdown
291+ # Otherwise, rc == WAIT_OBJECT_0 + 1 which means
292+ # self.hevConn was signaled, which means when we call
293+ # self.socket.accept(), we'll have our incoming connection
294+ # socket!
295+ # Loop back to the top, and let that accept do its thing...
296+ else :
297+ # yay! we have a connection
298+ # However... the new socket is non-blocking, we need to
299+ # set it back into blocking mode. (The socket that accept()
300+ # returns has the same properties as the listening sockets,
301+ # this includes any properties set by WSAAsyncSelect, or
302+ # WSAEventSelect, and whether its a blocking socket or not.)
303+ #
304+ # So if you yank the following line, the setblocking() call
305+ # will be useless. The socket will still be in non-blocking
306+ # mode.
307+ WSAEventSelect (rv [0 ], self .hevConn , 0 )
308+ rv [0 ].setblocking (1 )
309+ break
310+ return rv
311+
211312def usage (message = '' ):
313+ if RoundupService :
314+ win = ''' -c: Windows Service options. If you want to run the server as a Windows
315+ Service, you must configure the rest of the options by changing the
316+ constants of this program. You will at least configure one tracker
317+ in the TRACKER_HOMES variable. This option is mutually exclusive
318+ from the rest. Typing "roundup-server -c help" shows Windows
319+ Services specifics.'''
320+ else :
321+ win = ''
322+ port = PORT
212323 print _ ('''%(message)s
213-
214324Usage:
215325roundup-server [options] [name=tracker home]*
216326
217327options:
218328 -n: sets the host name
219- -p: sets the port to listen on
329+ -p: sets the port to listen on (default: %(port)s)
220330 -u: sets the uid to this user after listening on the port
221331 -g: sets the gid to this group after listening on the port
222332 -l: sets a filename to log to (instead of stdout)
223- -d: sets a filename to write server PID to. This option causes the server
224- to run in the background. Note: on Windows the PID argument is needed,
225- but ignored. The -l option *must* be specified if this option is.
333+ -d: run the server in the background and on UN*X write the server's PID
334+ to the nominated file. The -l option *must* be specified if this
335+ option is.
226336 -N: log client machine names in access log instead of IP addresses (much
227337 slower)
338+ %(win)s
228339
229340name=tracker home:
230341 Sets the tracker home(s) to use. The name is how the tracker is
@@ -273,21 +384,29 @@ def daemonize(pidfile):
273384 os .dup2 (devnull , 1 )
274385 os .dup2 (devnull , 2 )
275386
276- def run (port = 8080 , success_message = None ):
387+ def run (port = PORT , success_message = None ):
277388 ''' Script entry point - handle args and figure out what to to.
278389 '''
279390 # time out after a minute if we can
280391 import socket
281392 if hasattr (socket , 'setdefaulttimeout' ):
282393 socket .setdefaulttimeout (60 )
283394
284- hostname = ''
285- pidfile = None
286- logfile = None
395+ hostname = HOSTNAME
396+ pidfile = PIDFILE
397+ logfile = LOGFILE
398+ user = ROUNDUP_USER
399+ group = ROUNDUP_GROUP
400+ svc_args = None
401+
287402 try :
288403 # handle the command-line args
404+ options = 'n:p:u:d:l:hN'
405+ if RoundupService :
406+ options += 'c'
407+
289408 try :
290- optlist , args = getopt .getopt (sys .argv [1 :], 'n:p:u:d:l:hN' )
409+ optlist , args = getopt .getopt (sys .argv [1 :], options )
291410 except getopt .GetoptError , e :
292411 usage (str (e ))
293412
@@ -302,6 +421,10 @@ def run(port=8080, success_message=None):
302421 elif opt == '-l' : logfile = os .path .abspath (arg )
303422 elif opt == '-h' : usage ()
304423 elif opt == '-N' : RoundupRequestHandler .LOG_IPADDRESS = 0
424+ elif opt == '-c' : svc_args = [opt ] + args ; args = None
425+
426+ if svc_args is not None and len (optlist ) > 1 :
427+ raise ValueError , _ ("windows service option must be the only one" )
305428
306429 if pidfile and not logfile :
307430 raise ValueError , _ ("logfile *must* be specified if pidfile is" )
@@ -355,11 +478,11 @@ def run(port=8080, success_message=None):
355478 if args :
356479 d = {}
357480 for arg in args :
358- try :
481+ try :
359482 name , home = arg .split ('=' )
360483 except ValueError :
361484 raise ValueError , _ ("Instances must be name=home" )
362- d [name ] = home
485+ d [name ] = os . path . abspath ( home )
363486 RoundupRequestHandler .TRACKER_HOMES = d
364487 except SystemExit :
365488 raise
@@ -380,6 +503,10 @@ def run(port=8080, success_message=None):
380503 else :
381504 daemonize (pidfile )
382505
506+ if svc_args is not None :
507+ # don't do any other stuff
508+ return win32serviceutil .HandleCommandLine (RoundupService , argv = svc_args )
509+
383510 # redirect stdout/stderr to our logfile
384511 if logfile :
385512 # appending, unbuffered
0 commit comments