121 lines
4.1 KiB
Python
121 lines
4.1 KiB
Python
"""模型 HTTP 传输执行作用域长连接复用与生命周期机械验证。"""
|
|
|
|
import asyncio
|
|
import threading
|
|
from http.server import BaseHTTPRequestHandler, HTTPServer
|
|
from socketserver import ThreadingMixIn
|
|
|
|
import pytest
|
|
|
|
from muse.基础设施.模型.HTTP传输 import HTTP传输, 作用域HTTP客户端
|
|
|
|
|
|
class _可复用HTTP服务(ThreadingMixIn, HTTPServer):
|
|
daemon_threads = True
|
|
|
|
def __init__(self, server_address, handler_cls):
|
|
super().__init__(server_address, handler_cls)
|
|
self.tcp_accept_count = 0
|
|
self.request_count = 0
|
|
self.lock = threading.Lock()
|
|
|
|
def get_request(self):
|
|
sock, addr = super().get_request()
|
|
with self.lock:
|
|
self.tcp_accept_count += 1
|
|
return sock, addr
|
|
|
|
|
|
class _SSE处理器(BaseHTTPRequestHandler):
|
|
protocol_version = "HTTP/1.1"
|
|
|
|
def do_POST(self):
|
|
length = int(self.headers.get("content-length", 0))
|
|
_ = self.rfile.read(length)
|
|
with self.server.lock:
|
|
self.server.request_count += 1
|
|
|
|
body = b'data: {"type": "response.output_text.delta", "delta": "ok"}\n\ndata: [DONE]\n\n'
|
|
self.send_response(200)
|
|
self.send_header("Content-Type", "text/event-stream")
|
|
self.send_header("Content-Length", str(len(body)))
|
|
self.end_headers()
|
|
self.wfile.write(body)
|
|
self.wfile.flush()
|
|
|
|
def log_message(self, format, *args):
|
|
pass
|
|
|
|
|
|
@pytest.fixture
|
|
def 本地HTTP服务():
|
|
server = _可复用HTTP服务(("127.0.0.1", 0), _SSE处理器)
|
|
thread = threading.Thread(target=server.serve_forever)
|
|
thread.daemon = True
|
|
thread.start()
|
|
port = server.server_port
|
|
try:
|
|
yield f"http://127.0.0.1:{port}", server
|
|
finally:
|
|
server.shutdown()
|
|
server.server_close()
|
|
|
|
|
|
@pytest.mark.网络
|
|
@pytest.mark.case_id("TC-HTTP-KEEP-ALIVE-REUSE")
|
|
def test_作用域HTTP客户端复用单条TCP连接(本地HTTP服务, tmp_path):
|
|
base_url, server = 本地HTTP服务
|
|
key_file = tmp_path / "mock-key.txt"
|
|
key_file.write_text("test-key")
|
|
|
|
async def _运行三次请求():
|
|
async with 作用域HTTP客户端() as 客户端:
|
|
传输 = HTTP传输(base_url, "受控存储", str(key_file), "responses", 客户端=客户端)
|
|
for _ in range(3):
|
|
事件 = []
|
|
async for 项 in 传输.流({"input": "hi"}, 总期限秒=5.0):
|
|
事件.append(项)
|
|
assert len(事件) == 1
|
|
assert 事件[0]["delta"] == "ok"
|
|
assert not 客户端.is_closed
|
|
assert 客户端.is_closed
|
|
|
|
asyncio.run(_运行三次请求())
|
|
|
|
# 3 次 HTTP 请求在同一作用域内共享同一个客户端,只建立 1 次真实 TCP 连接
|
|
assert server.request_count == 3
|
|
assert server.tcp_accept_count == 1
|
|
|
|
|
|
@pytest.mark.网络
|
|
@pytest.mark.case_id("TC-HTTP-SHORT-CONNECTION-CONTRAST")
|
|
def test_未指定作用域时每次请求独立建连(本地HTTP服务, tmp_path):
|
|
base_url, server = 本地HTTP服务
|
|
key_file = tmp_path / "mock-key.txt"
|
|
key_file.write_text("test-key")
|
|
|
|
async def _运行三次独立请求():
|
|
# 无外部客户端,每次请求使用临时客户端
|
|
传输 = HTTP传输(base_url, "受控存储", str(key_file), "responses")
|
|
for _ in range(3):
|
|
事件 = []
|
|
async for 项 in 传输.流({"input": "hi"}, 总期限秒=5.0):
|
|
事件.append(项)
|
|
assert len(事件) == 1
|
|
|
|
asyncio.run(_运行三次独立请求())
|
|
|
|
assert server.request_count == 3
|
|
assert server.tcp_accept_count == 3
|
|
|
|
|
|
@pytest.mark.case_id("TC-HTTP-HEADER-NO-CONNECTION-CLOSE")
|
|
def test_请求头移除强制关闭并保留恒等编码(tmp_path):
|
|
key_file = tmp_path / "mock-key.txt"
|
|
key_file.write_text("test-key")
|
|
传输 = HTTP传输("http://127.0.0.1:8000", "受控存储", str(key_file), "responses")
|
|
准备 = 传输.准备({"input": "test"}, 总期限秒=10.0)
|
|
头字典 = dict(准备.头)
|
|
assert "Connection" not in 头字典 or 头字典["Connection"] != "close"
|
|
assert 头字典.get("Accept-Encoding") == "identity"
|