使用基本HTTP服务器设置计数器

0 投票
1 回答
1494 浏览
提问于 2025-04-17 15:30

目前我已经搭建了一个基本的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

撰写回答