使用基本HTTP服务器设置计数器
目前我已经搭建了一个基本的HTTP服务器,使用的是BaseHTTPRequestHandler
,并且我在用它的do_GET
方法。我想要一个函数check
,如果在5秒内没有收到请求,就能被调用。
我在考虑使用多进程和时间模块来实现这个功能,但我对它的可靠性有点担心。有没有什么好的建议或者最佳实践可以参考呢?
谢谢。
[编辑]
Marjin的解决方案真的很不错,但我遇到了以下错误信息 :-
Traceback (most recent call last):
File "test.py", line 89, in <module>
main()
File "test.py", line 83, in main
server.serve_forever()
File "/System/Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6/SocketServer.py", line 224, in serve_forever
r, w, e = select.select([self], [], [], poll_interval)
select.error: (4, 'Interrupted system call')
[编辑 2]
我在Python 2.7上试过,但错误依然存在。
[编辑 3]
Traceback (most recent call last):
File "test.py", line 90, in <module>
main()
File "test.py", line 84, in main
server.serve_forever()
File "/usr/local/lib/python2.7/SocketServer.py", line 225, in serve_forever
r, w, e = select.select([self], [], [], poll_interval)
select.error: (4, 'Interrupted system call')
1 个回答
1
对于一个简单的服务器,比如基于 BaseHTTPRequestHandler
的服务器,你可以使用信号处理器:
import time
import signal
import sys
last_request = sys.maxint # arbitrary high value to *not* trigger until there has been 1 requests at least
def itimer_handler(signum, frame):
print 'itimer heartbeat'
if time.time() - last_request > 300: # 5 minutes have passed at least with no request
# do stuff now to log, kill, restart, etc.
print 'Timeout, no requests for 5 minutes!'
signal.signal(signal.SIGALRM, itimer_handler)
signal.setitimer(signal.ITIMER_REAL, 30, 30) # check for a timeout every 30 seconds
# ...
def do_GET(..):
global last_request
last_request = time.time() # reset the timer again
调用 signal.setitimer()
会让操作系统定期向我们的程序发送 SIGALRM
信号。这种方式的精确度不是特别高;这里的 setitimer()
设置的是每30秒触发一次。每当有请求进来时,程序会重置一个全局的时间戳,而 itimer_handler
每30秒被调用一次,它会检查自上次设置时间戳以来是否已经过去了5分钟。
SIGALRM
信号会打断正在处理的请求,所以在处理这个信号的函数里,你需要快速完成任务。当这个函数执行完毕后,正常的Python代码流程会继续,就像线程一样。
需要注意的是,这个功能至少需要Python 2.7.4才能使用;具体情况可以查看 问题 7978,而2.7.4版本还没有发布。你可以下载即将包含在Python 2.7.4中的 SocketServer.py
文件,或者你也可以应用以下的补丁,以添加在该版本中引入的 errorno.EINTR
处理:
'''Backport of 2.7.4 EINTR handling'''
import errno
import select
import SocketServer
def _eintr_retry(func, *args):
"""restart a system call interrupted by EINTR"""
while True:
try:
return func(*args)
except (OSError, select.error) as e:
if e.args[0] != errno.EINTR:
raise
def serve_forever(self, poll_interval=0.5):
"""Handle one request at a time until shutdown.
Polls for shutdown every poll_interval seconds. Ignores
self.timeout. If you need to do periodic tasks, do them in
another thread.
"""
self._BaseServer__is_shut_down.clear()
try:
while not self._BaseServer__shutdown_request:
# XXX: Consider using another file descriptor or
# connecting to the socket to wake this up instead of
# polling. Polling reduces our responsiveness to a
# shutdown request and wastes cpu at all other times.
r, w, e = _eintr_retry(select.select, [self], [], [],
poll_interval)
if self in r:
self._handle_request_noblock()
finally:
self._BaseServer__shutdown_request = False
self._BaseServer__is_shut_down.set()
def handle_request(self):
"""Handle one request, possibly blocking.
Respects self.timeout.
"""
# Support people who used socket.settimeout() to escape
# handle_request before self.timeout was available.
timeout = self.socket.gettimeout()
if timeout is None:
timeout = self.timeout
elif self.timeout is not None:
timeout = min(timeout, self.timeout)
fd_sets = _eintr_retry(select.select, [self], [], [], timeout)
if not fd_sets[0]:
self.handle_timeout()
return
self._handle_request_noblock()
# patch in updated methods
SocketServer.BaseServer.serve_forever = serve_forever
SocketServer.BaseServer.handle_request = handle_request