`BaseHTTPRequestHandler` hides `TimeoutError` from `handle_error()`

未关闭
#99,777 5 条评论 0 个 reaction 已指派 0 人 在 GitHub 查看

还没有人认领这个 Issue。

评估

难度
3/5
预计耗时
1-2 天
新手友好度
35/100
Issue 类型
缺陷
描述清晰度
基本清楚
活跃度
停滞
技术栈
python

调研方向

从 BaseHTTPRequestHandler 和 issue 中描述的请求处理路径开始,然后复现提供的测试用例,以观察哪些 TimeoutError 异常会到达 handle_error()。当处理程序引发的超时通过 handle_error() 报告,且现有的请求套接字超时行为不受影响时,即表示完成。

由索引模型根据 Issue 内容生成。

描述

stdlib type-bug

Bug report

If anything in your method handlers (e.g. do_GET()) ends up throwing a TimeoutError, then that unhandled exception is never forwarded to handle_error(), and hence you do not get a chance to do whatever reporting you have set up there.

The cause of this is an undocumented timeout handling that was added in bpo-6267. It blindly assumes that any TimeoutError must come from the request socket and that it was requested and hence expected. That is not the case for any mildly complex request handler that might communicate with other services.

The timeline for this issue is:

?.? to 2.6: No exceptions are hidden from handle_error()
2.7 to 3.2: recv() and send() timeouts are hidden. Other timeouts, e.g. connect(), are not.
3.3 to 3.9: Same situation, but by pure luck since socket.timeout was not converted to TimeoutError yet
3.10: All timeouts are hidden, not just for recv() and send()

Your environment

  • CPython versions tested on: 2.7.18, 3.2.3, 3.5.10, 3.10.7
  • Operating system and architecture: Debian 7 x86_64 and Fedora 35 x86_64

Test case

This test case should give the output:

Got error!
Got error!

But instead gives:

127.0.0.1 - - [25/Nov/2022 13:48:49] Request timed out: TimeoutError(110, 'Connection timed out')
127.0.0.1 - - [25/Nov/2022 13:48:50] Request timed out: TimeoutError('timed out')

For 3.9 or older you get a mix.

Code: (change simulate if you want to have the actual socket code throw the exceptions)

#!/usr/bin/python3

import os
import errno
import socket
import threading
from time import sleep
try:
	from http.server import HTTPServer, BaseHTTPRequestHandler
except ImportError:
	from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler

simulate = True

class ConnectRequestHandler(BaseHTTPRequestHandler):
	def do_GET(self):
		if simulate:
			raise socket.error(errno.ETIMEDOUT, "Timeout")
		else:
			socket.create_connection(("1.2.3.4", 9000))

class RecvRequestHandler(BaseHTTPRequestHandler):
	def do_GET(self):
		if simulate:
			raise socket.timeout(errno.ETIMEDOUT, "Timeout")
		else:
			(sock, dummy) = socket.socketpair()
			sock.settimeout(1)
			while sock.recv(1024):
				pass

class Server(HTTPServer):
	def handle_error(self, request, client_address):
		print("Got error!")

def run_connect_server():
	s = Server(("127.0.0.1", 8998),
	           ConnectRequestHandler)
	s.handle_request()
	
def run_recv_server():
	s = Server(("127.0.0.1", 8999),
	           RecvRequestHandler)
	s.handle_request()

threading.Thread(target=run_connect_server).start()
threading.Thread(target=run_recv_server).start()
sleep(0.5)

s = socket.create_connection(("127.0.0.1", 8998))
s.send(b'GET / HTTP/1.0\r\n\r\n')
while s.recv(1024):
	pass
s.close()

s = socket.create_connection(("127.0.0.1", 8999))
s.send(b'GET / HTTP/1.0\r\n\r\n')
while s.recv(1024):
	pass
s.close()

sleep(0.5)
Linked PRs
  • gh-99806
主要语言
Python
星标
77.2k
派生
36k
平均合并
1 天 9 小时
30 天内合并 PR
558

贡献指南

打开贡献指南

从这里开始

  1. 先读完整个 Issue,再读项目的贡献指南。
  2. 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
  3. Fork 仓库,在一个分支上完成修改。
  4. 提交 Pull Request,并在描述里引用这个 Issue 编号。

python/cpython 的其他 Issue

查看 python/cpython 的全部 Issue

相似的 Issue

更多 Python Issue

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。