请求超时异常未被正确捕获的原因及解决方法咨询
请求超时异常未被正确捕获的原因及解决方法咨询
嘿,我来帮你拆解这个问题并给出解决方案:
为什么部分超时没被正确捕获?
你遇到的两种超时属于不同场景下的异常,requests对它们的包装方式不一样:
- 被
requests.exceptions.Timeout捕获的情况,是请求层面的超时——已经成功和服务器建立TCP连接,但服务器在你设定的timeout时间内没有返回响应,这是requests直接抛出的上层超时异常。 - 而被
RequestExceptionelse分支捕获的('Connection aborted.', TimeoutError('timed out')),属于连接阶段的底层超时:在尝试和服务器建立连接的过程中就超时了,此时requests会把底层socket抛出的TimeoutError包装成requests.exceptions.ConnectionError(它是RequestException的子类),而非requests.exceptions.Timeout。
你的当前代码在RequestException分支里只检查了e.__cause__是否为TimeoutError,但这种连接中止场景下,底层的TimeoutError是嵌套在e.args里的,并没有被挂到__cause__属性上,所以判断逻辑没命中,最终走到了else分支。
怎么修正这个问题?
你可以优化异常检查逻辑,更全面地遍历异常链和嵌套结构,确保不会漏掉任何超时相关的异常:
def somefunction(): #.... try: response = requests.post(api_url, json=payload, timeout=timeout) response.raise_for_status() result = response.json() try: structured_output = json.loads(result["message"]["content"]) formatted_output = json.dumps(structured_output, indent=2) logger.info(json.dumps(structured_output, indent=2)) return formatted_output except json.JSONDecodeError: logger.warning("Error parsing response:", result) return f"Error parsing response: {result}" except requests.exceptions.Timeout: logger.error(f"Request to {api_url} timed out after {timeout} seconds") raise TimeoutError(f"Request to {api_url} timed out after {timeout} seconds") except TimeoutError: logger.error(f"Low-level timeout while connecting to {api_url}") raise TimeoutError(f"Low-level timeout while connecting to {api_url}") # 单独捕获连接错误,先处理连接阶段的超时 except requests.exceptions.ConnectionError as e: # 定义辅助函数检查异常链中是否存在TimeoutError def contains_timeout(exc): while exc: if isinstance(exc, TimeoutError): return True exc = exc.__cause__ or exc.__context__ # 检查嵌套在args里的异常 for arg in e.args: if isinstance(arg, Exception) and contains_timeout(arg): return True if isinstance(arg, tuple): for sub_arg in arg: if isinstance(sub_arg, Exception) and contains_timeout(sub_arg): return True return False if contains_timeout(e): logger.error(f"Connection to {api_url} timed out: {e}") raise TimeoutError(f"Connection to {api_url} timed out: {e}") # 非超时类连接错误,按原有逻辑处理 logger.error(f"Connection error: {e}") return f"Connection error: {e}" except requests.exceptions.RequestException as e: # 检查剩余请求异常中是否包含超时 def contains_timeout(exc): while exc: if isinstance(exc, TimeoutError): return True exc = exc.__cause__ or exc.__context__ return False if contains_timeout(e): logger.error(f"Wrapped TimeoutError detected: {e}") raise TimeoutError(f"Request failed due to a timeout: {e}") if hasattr(e, "response") and e.response is not None: logger.error( f"Request failed: {e}, Status Code: {e.response.status_code}, Response: {e.response.text}" ) return f"Request failed: {e}, Status Code: {e.response.status_code}, Response: {e.response.text}" else: logger.error(f"Request failed: {e}") return f"Request failed: {e}"
逻辑说明:
- 新增了
requests.exceptions.ConnectionError的捕获分支,专门处理连接阶段的异常,先判断是否由超时导致,避免和其他请求异常混在一起。 - 用
contains_timeout辅助函数遍历整个异常链(包括__cause__、__context__),同时检查嵌套在args里的异常结构,确保不会漏掉任何隐藏的TimeoutError。 - 所有超时场景都会统一抛出
TimeoutError,保持异常类型的一致性,方便上层调用方处理。
备注:内容来源于stack exchange,提问作者KansaiRobot
相关产品推荐
相关产品推荐

