【发布时间】:2021-09-13 17:02:29
【问题描述】:
问题:带有 ssl 的 ThreadingTCPServer 会冻结某些请求,尽管它应该是多线程的。
解释:
我正在尝试创建一个 https 服务器来处理单独线程上的每个请求,因此即使用户请求需要很长时间,服务器也不应该挂起。
这是我的带有打印语句的代码的简单版本,它最初似乎可以工作:
from http.server import BaseHTTPRequestHandler, HTTPServer
from socketserver import ThreadingTCPServer
import ssl
import threading
import time
class ChildHandler(BaseHTTPRequestHandler):
def __init__(self, *args, **kwargs):
print('ID:', threading.get_ident(), '1 - BaseHTTPRequestHandler INIT CALLED')
super().__init__(*args, **kwargs)
def do_GET(self):
print('ID:', threading.get_ident(), '2 - do_GET... working...')
time.sleep(5)
print('ID:', threading.get_ident(), '3 - do_get... done...')
self.send_response(200)
self.end_headers()
def log_message(self, *args): pass
if __name__ == '__main__':
server = ThreadingTCPServer(('', 1443), ChildHandler)
server.socket = ssl.wrap_socket(server.socket, certfile='./fullchain1.pem', keyfile='./privkey1.pem',
server_side=True, ssl_version=ssl.PROTOCOL_TLSv1_2)
server.serve_forever()
如果我打开 2 个指向服务器的 chrome 选项卡并同时连接,我们可以清楚地看到它同时为两个用户提供服务。输出如下:
ID: 49092 1 - BaseHTTPRequestHandler INIT CALLED
ID: 49092 2 - do_GET... working...
ID: 46236 1 - BaseHTTPRequestHandler INIT CALLED
ID: 46236 2 - do_GET... working...
ID: 49092 3 - do_get... done...
ID: 46236 3 - do_get... done...
但是,如果我让这台服务器打开几个小时或过夜,它会突然挂起。经过几天的测试,我终于能够重现这个问题,虽然我不知道如何解决它。以下是我可以在另一台计算机上运行的恶意脚本,它会完全挂起/冻结/破坏我的服务器。
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('MY PUBLIC IP', 1443))
之后我的服务器完全挂起。它实际上所做的就是打开一个套接字,然后将其留在那里......而且我的服务器的控制台中绝对没有输出(因此 BaseHTTPRequestHandler 甚至没有被初始化)。 这怎么可能,客户端不应该仅仅通过连接到我的服务器然后什么都不发送来完全挂起我的线程服务器!
进一步调试:
为了进一步分析,我创建了ThreadingTCPServer 的以下子类,它将在初始化请求处理程序的步骤之前打印:
class AnalyzeVer(ThreadingTCPServer):
def get_request(self):
print('ID:', threading.get_ident(), '-2 - GET REQUEST STARTED')
result = super().get_request()
print('ID:', threading.get_ident(), '-1 - GET REQUEST ENDED')
return result
def verify_request(self, request, client_address):
print('ID:', threading.get_ident(), '0 - VERIFY REQUEST')
return super().verify_request(request, client_address)
当我运行我的恶意脚本时,我的服务器显然完全挂起,并且在我的服务器中得到以下输出:
ID: 30880 -2 - GET REQUEST STARTED
所以它肯定会在 get_request 中冻结。
另外:删除 ssl 证书似乎可以解决这个问题,但我需要这些:/
另外:将套接字超时设置为某个值(例如 10 秒)将部分解决此问题,但它也会使服务器挂起,直到套接字超时(每次运行恶意脚本时冻结 10 秒):/
另外:将套接字超时设置为小于 3 分钟是不可能的,因为我需要传输可能需要很长时间的文件,因此每次运行恶意脚本时将服务器挂起 >3 分钟真的很糟糕:/
另外:恶意脚本需要在 python 终端上运行,并且不能关闭终端。如果恶意脚本终端关闭,则服务器恢复正常(必须与套接字打开而没有数据有关)
编辑:如上所示,挂起发生在名为get_request 的服务器函数中。我在文件socketserver.py line 397的python源代码中发现了以下内容
我假设 selector.select() 在调用这个函数之前已经返回了套接字是可读的,所以在 get_request() 中应该没有阻塞的风险。
所以,我假设写这篇文章的人没有考虑导致get_request() 挂起的 ssl 特定情况?
【问题讨论】:
标签: python python-3.x sockets ssl server