You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

请求超时异常未被正确捕获的原因及解决方法咨询

请求超时异常未被正确捕获的原因及解决方法咨询

嘿,我来帮你拆解这个问题并给出解决方案:

为什么部分超时没被正确捕获?

你遇到的两种超时属于不同场景下的异常,requests对它们的包装方式不一样:

  • 被requests.exceptions.Timeout捕获的情况,是请求层面的超时——已经成功和服务器建立TCP连接,但服务器在你设定的timeout时间内没有返回响应,这是requests直接抛出的上层超时异常。
  • 而被RequestException else分支捕获的('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}"

逻辑说明:

  1. 新增了requests.exceptions.ConnectionError的捕获分支,专门处理连接阶段的异常,先判断是否由超时导致,避免和其他请求异常混在一起。
  2. 用contains_timeout辅助函数遍历整个异常链(包括__cause__、__context__),同时检查嵌套在args里的异常结构,确保不会漏掉任何隐藏的TimeoutError。
  3. 所有超时场景都会统一抛出TimeoutError,保持异常类型的一致性,方便上层调用方处理。

备注:内容来源于stack exchange,提问作者KansaiRobot

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.13 19:17:56