Back to index

moin  1.9.0~rc2
singleserver.py
Go to the documentation of this file.
00001 # Copyright (c) 2005 Allan Saddi <allan@saddi.com>
00002 # All rights reserved.
00003 #
00004 # Redistribution and use in source and binary forms, with or without
00005 # modification, are permitted provided that the following conditions
00006 # are met:
00007 # 1. Redistributions of source code must retain the above copyright
00008 #    notice, this list of conditions and the following disclaimer.
00009 # 2. Redistributions in binary form must reproduce the above copyright
00010 #    notice, this list of conditions and the following disclaimer in the
00011 #    documentation and/or other materials provided with the distribution.
00012 #
00013 # THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
00014 # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
00015 # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
00016 # ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
00017 # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
00018 # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
00019 # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
00020 # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
00021 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
00022 # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
00023 # SUCH DAMAGE.
00024 #
00025 # $Id$
00026 
00027 __author__ = 'Allan Saddi <allan@saddi.com>'
00028 __version__ = '$Revision$'
00029 
00030 import sys
00031 import socket
00032 import select
00033 import signal
00034 import errno
00035 
00036 try:
00037     import fcntl
00038 except ImportError:
00039     def setCloseOnExec(sock):
00040         pass
00041 else:
00042     def setCloseOnExec(sock):
00043         fcntl.fcntl(sock.fileno(), fcntl.F_SETFD, fcntl.FD_CLOEXEC)
00044 
00045 __all__ = ['SingleServer']
00046 
00047 class SingleServer(object):
00048     def __init__(self, jobClass=None, jobArgs=(), **kw):
00049         self._jobClass = jobClass
00050         self._jobArgs = jobArgs
00051 
00052     def run(self, sock, timeout=1.0):
00053         """
00054         The main loop. Pass a socket that is ready to accept() client
00055         connections. Return value will be True or False indiciating whether
00056         or not the loop was exited due to SIGHUP.
00057         """
00058         # Set up signal handlers.
00059         self._keepGoing = True
00060         self._hupReceived = False
00061 
00062         # Might need to revisit this?
00063         if not sys.platform.startswith('win'):
00064             self._installSignalHandlers()
00065 
00066         # Set close-on-exec
00067         setCloseOnExec(sock)
00068         
00069         # Main loop.
00070         while self._keepGoing:
00071             try:
00072                 r, w, e = select.select([sock], [], [], timeout)
00073             except select.error, e:
00074                 if e[0] == errno.EINTR:
00075                     continue
00076                 raise
00077 
00078             if r:
00079                 try:
00080                     clientSock, addr = sock.accept()
00081                 except socket.error, e:
00082                     if e[0] in (errno.EINTR, errno.EAGAIN):
00083                         continue
00084                     raise
00085 
00086                 setCloseOnExec(clientSock)
00087                 
00088                 if not self._isClientAllowed(addr):
00089                     clientSock.close()
00090                     continue
00091 
00092                 # Hand off to Connection.
00093                 conn = self._jobClass(clientSock, addr, *self._jobArgs)
00094                 conn.run()
00095 
00096             self._mainloopPeriodic()
00097 
00098         # Restore signal handlers.
00099         self._restoreSignalHandlers()
00100 
00101         # Return bool based on whether or not SIGHUP was received.
00102         return self._hupReceived
00103 
00104     def _mainloopPeriodic(self):
00105         """
00106         Called with just about each iteration of the main loop. Meant to
00107         be overridden.
00108         """
00109         pass
00110 
00111     def _exit(self, reload=False):
00112         """
00113         Protected convenience method for subclasses to force an exit. Not
00114         really thread-safe, which is why it isn't public.
00115         """
00116         if self._keepGoing:
00117             self._keepGoing = False
00118             self._hupReceived = reload
00119 
00120     def _isClientAllowed(self, addr):
00121         """Override to provide access control."""
00122         return True
00123 
00124     # Signal handlers
00125 
00126     def _hupHandler(self, signum, frame):
00127         self._hupReceived = True
00128         self._keepGoing = False
00129 
00130     def _intHandler(self, signum, frame):
00131         self._keepGoing = False
00132 
00133     def _installSignalHandlers(self):
00134         supportedSignals = [signal.SIGINT, signal.SIGTERM]
00135         if hasattr(signal, 'SIGHUP'):
00136             supportedSignals.append(signal.SIGHUP)
00137 
00138         self._oldSIGs = [(x,signal.getsignal(x)) for x in supportedSignals]
00139 
00140         for sig in supportedSignals:
00141             if hasattr(signal, 'SIGHUP') and sig == signal.SIGHUP:
00142                 signal.signal(sig, self._hupHandler)
00143             else:
00144                 signal.signal(sig, self._intHandler)
00145 
00146     def _restoreSignalHandlers(self):
00147         for signum,handler in self._oldSIGs:
00148             signal.signal(signum, handler)
00149 
00150 if __name__ == '__main__':
00151     class TestJob(object):
00152         def __init__(self, sock, addr):
00153             self._sock = sock
00154             self._addr = addr
00155         def run(self):
00156             print "Client connection opened from %s:%d" % self._addr
00157             self._sock.send('Hello World!\n')
00158             self._sock.setblocking(1)
00159             self._sock.recv(1)
00160             self._sock.close()
00161             print "Client connection closed from %s:%d" % self._addr
00162     sock = socket.socket()
00163     sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
00164     sock.bind(('', 8080))
00165     sock.listen(socket.SOMAXCONN)
00166     SingleServer(jobClass=TestJob).run(sock)