1
# Copyright (c) 2005 Allan Saddi <allan@saddi.com>
4
# Redistribution and use in source and binary forms, with or without
5
# modification, are permitted provided that the following conditions
7
# 1. Redistributions of source code must retain the above copyright
8
# notice, this list of conditions and the following disclaimer.
9
# 2. Redistributions in binary form must reproduce the above copyright
10
# notice, this list of conditions and the following disclaimer in the
11
# documentation and/or other materials provided with the distribution.
13
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
14
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
16
# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
17
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
18
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
19
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
20
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
21
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
22
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27
__author__ = 'Allan Saddi <allan@saddi.com>'
28
__version__ = '$Revision$'
39
def setCloseOnExec(sock):
42
def setCloseOnExec(sock):
43
fcntl.fcntl(sock.fileno(), fcntl.F_SETFD, fcntl.FD_CLOEXEC)
45
__all__ = ['SingleServer']
47
class SingleServer(object):
48
def __init__(self, jobClass=None, jobArgs=(), **kw):
49
self._jobClass = jobClass
50
self._jobArgs = jobArgs
52
def run(self, sock, timeout=1.0):
54
The main loop. Pass a socket that is ready to accept() client
55
connections. Return value will be True or False indiciating whether
56
or not the loop was exited due to SIGHUP.
58
# Set up signal handlers.
59
self._keepGoing = True
60
self._hupReceived = False
62
# Might need to revisit this?
63
if not sys.platform.startswith('win'):
64
self._installSignalHandlers()
70
while self._keepGoing:
72
r, w, e = select.select([sock], [], [], timeout)
73
except select.error, e:
74
if e[0] == errno.EINTR:
80
clientSock, addr = sock.accept()
81
except socket.error, e:
82
if e[0] in (errno.EINTR, errno.EAGAIN):
86
setCloseOnExec(clientSock)
88
if not self._isClientAllowed(addr):
92
# Hand off to Connection.
93
conn = self._jobClass(clientSock, addr, *self._jobArgs)
96
self._mainloopPeriodic()
98
# Restore signal handlers.
99
self._restoreSignalHandlers()
101
# Return bool based on whether or not SIGHUP was received.
102
return self._hupReceived
104
def _mainloopPeriodic(self):
106
Called with just about each iteration of the main loop. Meant to
111
def _exit(self, reload=False):
113
Protected convenience method for subclasses to force an exit. Not
114
really thread-safe, which is why it isn't public.
117
self._keepGoing = False
118
self._hupReceived = reload
120
def _isClientAllowed(self, addr):
121
"""Override to provide access control."""
126
def _hupHandler(self, signum, frame):
127
self._hupReceived = True
128
self._keepGoing = False
130
def _intHandler(self, signum, frame):
131
self._keepGoing = False
133
def _installSignalHandlers(self):
134
supportedSignals = [signal.SIGINT, signal.SIGTERM]
135
if hasattr(signal, 'SIGHUP'):
136
supportedSignals.append(signal.SIGHUP)
138
self._oldSIGs = [(x,signal.getsignal(x)) for x in supportedSignals]
140
for sig in supportedSignals:
141
if hasattr(signal, 'SIGHUP') and sig == signal.SIGHUP:
142
signal.signal(sig, self._hupHandler)
144
signal.signal(sig, self._intHandler)
146
def _restoreSignalHandlers(self):
147
for signum,handler in self._oldSIGs:
148
signal.signal(signum, handler)
150
if __name__ == '__main__':
151
class TestJob(object):
152
def __init__(self, sock, addr):
156
print "Client connection opened from %s:%d" % self._addr
157
self._sock.send('Hello World!\n')
158
self._sock.setblocking(1)
161
print "Client connection closed from %s:%d" % self._addr
162
sock = socket.socket()
163
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
164
sock.bind(('', 8080))
165
sock.listen(socket.SOMAXCONN)
166
SingleServer(jobClass=TestJob).run(sock)