singleserver.py 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  1. # Copyright (c) 2005 Allan Saddi <allan@saddi.com>
  2. # All rights reserved.
  3. #
  4. # Redistribution and use in source and binary forms, with or without
  5. # modification, are permitted provided that the following conditions
  6. # are met:
  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.
  12. #
  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
  23. # SUCH DAMAGE.
  24. #
  25. # $Id$
  26. __author__ = 'Allan Saddi <allan@saddi.com>'
  27. __version__ = '$Revision$'
  28. import sys
  29. import socket
  30. import select
  31. import signal
  32. import errno
  33. try:
  34. import fcntl
  35. except ImportError:
  36. def setCloseOnExec(sock):
  37. pass
  38. else:
  39. def setCloseOnExec(sock):
  40. fcntl.fcntl(sock.fileno(), fcntl.F_SETFD, fcntl.FD_CLOEXEC)
  41. __all__ = ['SingleServer']
  42. class SingleServer(object):
  43. def __init__(self, jobClass=None, jobArgs=(), **kw):
  44. self._jobClass = jobClass
  45. self._jobArgs = jobArgs
  46. def run(self, sock, timeout=1.0):
  47. """
  48. The main loop. Pass a socket that is ready to accept() client
  49. connections. Return value will be True or False indiciating whether
  50. or not the loop was exited due to SIGHUP.
  51. """
  52. # Set up signal handlers.
  53. self._keepGoing = True
  54. self._hupReceived = False
  55. # Might need to revisit this?
  56. if not sys.platform.startswith('win'):
  57. self._installSignalHandlers()
  58. # Set close-on-exec
  59. setCloseOnExec(sock)
  60. # Main loop.
  61. while self._keepGoing:
  62. try:
  63. r, w, e = select.select([sock], [], [], timeout)
  64. except select.error, e:
  65. if e[0] == errno.EINTR:
  66. continue
  67. raise
  68. if r:
  69. try:
  70. clientSock, addr = sock.accept()
  71. except socket.error, e:
  72. if e[0] in (errno.EINTR, errno.EAGAIN):
  73. continue
  74. raise
  75. setCloseOnExec(clientSock)
  76. if not self._isClientAllowed(addr):
  77. clientSock.close()
  78. continue
  79. # Hand off to Connection.
  80. conn = self._jobClass(clientSock, addr, *self._jobArgs)
  81. conn.run()
  82. self._mainloopPeriodic()
  83. # Restore signal handlers.
  84. self._restoreSignalHandlers()
  85. # Return bool based on whether or not SIGHUP was received.
  86. return self._hupReceived
  87. def _mainloopPeriodic(self):
  88. """
  89. Called with just about each iteration of the main loop. Meant to
  90. be overridden.
  91. """
  92. pass
  93. def _exit(self, reload=False):
  94. """
  95. Protected convenience method for subclasses to force an exit. Not
  96. really thread-safe, which is why it isn't public.
  97. """
  98. if self._keepGoing:
  99. self._keepGoing = False
  100. self._hupReceived = reload
  101. def _isClientAllowed(self, addr):
  102. """Override to provide access control."""
  103. return True
  104. # Signal handlers
  105. def _hupHandler(self, signum, frame):
  106. self._hupReceived = True
  107. self._keepGoing = False
  108. def _intHandler(self, signum, frame):
  109. self._keepGoing = False
  110. def _installSignalHandlers(self):
  111. supportedSignals = [signal.SIGINT, signal.SIGTERM]
  112. if hasattr(signal, 'SIGHUP'):
  113. supportedSignals.append(signal.SIGHUP)
  114. self._oldSIGs = [(x,signal.getsignal(x)) for x in supportedSignals]
  115. for sig in supportedSignals:
  116. if hasattr(signal, 'SIGHUP') and sig == signal.SIGHUP:
  117. signal.signal(sig, self._hupHandler)
  118. else:
  119. signal.signal(sig, self._intHandler)
  120. def _restoreSignalHandlers(self):
  121. for signum,handler in self._oldSIGs:
  122. signal.signal(signum, handler)
  123. if __name__ == '__main__':
  124. class TestJob(object):
  125. def __init__(self, sock, addr):
  126. self._sock = sock
  127. self._addr = addr
  128. def run(self):
  129. print "Client connection opened from %s:%d" % self._addr
  130. self._sock.send('Hello World!\n')
  131. self._sock.setblocking(1)
  132. self._sock.recv(1)
  133. self._sock.close()
  134. print "Client connection closed from %s:%d" % self._addr
  135. sock = socket.socket()
  136. sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
  137. sock.bind(('', 8080))
  138. sock.listen(socket.SOMAXCONN)
  139. SingleServer(jobClass=TestJob).run(sock)