如何让BaseHTTPRequestHandler的分块流式PUT请求不丢字节?
问题分析与解决:分块PUT请求的同步阻塞问题
问题背景
以下是一个Python程序的MCVE示例,功能如下:
- 启动时通过首个命令行参数设置全局变量
FIXME; - 在单独线程启动HTTP服务器:
- 通过对
self.rfile.fileno()调用select监听分块PUT请求; - 打印接收的数据;
- 通过对
- 主线程运行服务器客户端:
- 若
FIXME为True,休眠100毫秒; - 用
requests模块发送含单行数据的分块PUT请求。
- 若
正常执行情况(FIXME=1)
$ ./test.py 1 SERVER SELECT CLIENT write SERVER RECV 13 b'3\r\n123\r\n0\r\n\r\n' b'3\r\n123\r\n0\r\n\r\n' 127.0.0.1 - - [27/Nov/2023 11:14:01] "PUT / HTTP/1.1" 200 -
异常情况(FIXME=0)
程序阻塞且字节丢失(MCVE约10次尝试复现,实际程序必现):
$ ./1.py 0 CLIENT write SERVER SELECT
疑问点
- 如何移除额外休眠?缺少什么同步机制?原因是什么?
- 推测客户端在服务器进入
select前就传输/关闭了字节,但不解为何select无法处理套接字剩余字节。尝试设置set_blocking(inputfd, True)并调用os.read(inputfd,1)也无法读取,怀疑字节已被BaseHTTPRequestHandler读取,如何访问这些字节?
原因分析与解决方法
核心原因
问题出在**BaseHTTPRequestHandler的默认行为**和线程同步缺失:
- 当客户端在服务器线程进入
select前就发送完数据时,BaseHTTPRequestHandler在调用自定义处理逻辑前,已经自动读取并解析了请求头部和分块数据(HTTP分块编码的解析是handler内部的默认流程)。 - 此时再对
self.rfile.fileno()执行select,套接字已无可读数据,导致select永久阻塞。 FIXME=1时的休眠只是让服务器线程先进入select等待,客户端后发送数据,刚好避开了handler提前读取的情况,属于临时的“碰运气”方案,并非真正解决问题。
解决步骤
1. 移除休眠,添加线程同步
用**事件(Event)**实现服务器与客户端的同步,确保客户端在服务器准备就绪后再发送请求:
import threading # 定义同步事件 server_ready = threading.Event() class CustomHandler(BaseHTTPRequestHandler): def do_PUT(self): # 通知客户端:服务器已准备好接收数据 server_ready.set() # 后续处理逻辑... # 主线程流程 server_thread = threading.Thread(target=server.serve_forever) server_thread.start() # 等待服务器准备就绪 server_ready.wait() # 发送分块PUT请求 requests.put(url, data=chunked_data)
2. 正确获取分块数据:直接使用self.rfile
BaseHTTPRequestHandler已经完成了分块编码的解析,无需手动操作底层文件描述符。直接通过self.rfile读取即可:
def do_PUT(self): # 读取所有分块数据 data = self.rfile.read() print(f"SERVER RECV {len(data)} {data}") self.send_response(200) self.end_headers()
如果需要流式读取,可循环调用self.rfile.read(size),直到返回空字节串(表示分块结束)。
3. 为何直接操作fileno读取不到数据?
self.rfile是带缓冲的IO对象,BaseHTTPRequestHandler解析请求时,已将数据读取到内部缓冲区中,底层套接字的文件描述符自然没有剩余数据。必须直接操作self.rfile,而非绕过它读取fileno。
内容的提问来源于stack exchange,提问作者KamilCuk
相关产品推荐
相关产品推荐

