【问题标题】:Why is python requests not terminating and why are these seperate logs printed?为什么 python 请求没有终止,为什么打印这些单独的日志?
【发布时间】:2019-08-27 07:57:02
【问题描述】:

我正在运行一项作业,该作业发出许多从 API 检索数据的请求。为了发出请求,我使用了 requests 模块并对此代码进行了迭代:

logger.debug("Some log message")
response = requests.get(
    url=self._url,
    headers=self.headers,
    auth=self.auth,
)
logger.debug("Some other log message")

这通常会产生以下日志:

[...] Some log message
[2019-08-27 03:00:57,201 - DEBUG - connectionpool.py:393] https://my.url.com:port "GET /some/important/endpoint?$skiptoken='12345' HTTP/1.1" 401 0
[2019-08-27 03:00:57,601 - DEBUG - connectionpool.py:393] https://my.url.com:port "GET /some/important/endpoint?$skiptoken='12345' HTTP/1.1" 200 951999
[...] Some other log message

然而,在极少数情况下,作业永远不会终止,并且在日志中显示:

[...] Some log message
[2019-08-27 03:00:57,201 - DEBUG - connectionpool.py:393] https://my.url.com:port "GET /some/important/endpoint?$skiptoken='12345' HTTP/1.1" 401 0

它从不打印剩余的日志消息并且从不返回。我无法重现该问题。我提出的请求从未手动返回,但它给了我想要的响应。

问题:

  1. 为什么urllib3 总是在打印状态代码为 200 的日志之前打印状态代码为 401 的日志?是否总是出现这种情况,还是由身份验证或 API 服务器的问题引起的?

  2. 在第二个日志被截断的极少数情况下,我的假设是否正确,即应用程序卡在发出永远不会返回的请求?或者:

    a) requests.get 是否会引发异常,从而导致其他日志语句永远不会被打印,然后“神奇地”在我的代码中的某个地方被捕获?

    b) 有没有我没有意识到的其他可能性?


附加信息:

  • Python 2.7.13(我们已经在升级到 Python3,但这需要在完成之前解决

  • 请求 2.21.0

  • urllib3 1.24.3

  • auth 是通过requests.auth.HTTPDigestAuth(username, password)

  • 我的代码没有 try/except 块,这就是我在问题 2.a 中“神奇地”写的原因。这是因为我们更希望这项工作“大声”失败。

  • 我正在迭代生成 url 的生成器以发出多个请求

  • 作业由 Jenkins 2.95 按计划进行

  • 当一切顺利运行时,它会在大约 5 分钟内发出大约 300 个请求

  • 我正在运行两个 python 脚本,它们都运行相同的代码,但在一个作业中针对不同的端点但并行


更新

Q1的答案:

这似乎是 HTTP Digest Auth 的预期行为。 请参阅此github issueWikipedia

【问题讨论】:

    标签: python python-2.7 python-requests urllib3


    【解决方案1】:

    要回答您的问题, 1. 似乎是您的 API 的问题。确保您可以运行 curl 命令并查看?

    curl -i https://my.url.com:port/some/important/endpoint?$skiptoken='12345'
    
    1. 它永远不会终止,可能是因为 API 没有响应。添加 timeout 来避免这种阻塞。

      响应 = requests.get( url=self._url, headers=self.headers, auth=self.auth, 超时=60 )

    希望这对您的问题有所帮助。

    【讨论】:

    • 您好,感谢您的回答和建议。我试图通过手动调用端点来重现它。当我这样做时,它成功了。自从我们遇到问题后,该作业也成功运行了几次。
    • 感谢超时信息。我会在请求中添加一个超时,但是我很想了解这个问题,也许能够重现它。
    【解决方案2】:

    正如 Vithulan 已经回答的那样,在进行网络调用时,您应该始终设置一个超时值 - 除非您不关心您的进程会永远卡住......

    现在 wrt/ 错误处理等:

    a) requests.get 是否会引发异常,从而导致 其他日志语句永远不会被打印,然后是“神奇地”得到 卡在我的代码中的某个地方?

    调用堆栈中的其他一些 try/except 块确实有可能吞下异常,但只有你能说出来。请注意,如果是这种情况,您有一些行为非常恶劣的代码 - try/except 应该 1/ 只针对它应该处理的确切异常,2/ 在 try 块中使用尽可能少的代码以避免捕获来自代码另一部分的类似错误和 3/ 永远不会使异常保持沉默(IOW 它应该至少记录异常和回溯)。

    请注意,您最好只使用一个停用的记录器 FWIW ;-)

    话虽如此,在您确定没有此类问题之前,您仍然可以通过在函数中记录请求异常来获取更多调试信息:

    logger.debug("Some log message")
    try:
        response = requests.get(
            url=self._url,
            headers=self.headers,
            auth=self.auth,
            timeout=SOME_TIMEOUT_VALUE   
      )
    except Exception as e:
        # this will log the full traceback too
        logger.exception("oops, call to %s failed : %s", self._url, e)
        # make sure we don't swallow the exception
        raise
    
    logger.debug("Some other log message")
    

    现在生活中的一个事实是,HTTP 请求可能由于很多原因而失败,以至于您实际上应该预计它会失败,因此您可能需要一些重试机制。此外,对requests.get 的调用没有引发这一事实并不意味着调用失败 - 您仍然需要检查响应代码(或使用response.raise_for_status())。

    编辑:

    正如我的问题中提到的,我的代码没有 try/except 块,因为如果出现任何问题,我们希望整个作业终止。

    try/except 块不会阻止您终止作业 - 只需重新引发异常(最终在 X 次重试后),或引发新异常,或调用 sys.exit()(实际上通过引发异常)-,它可以让您获得有用的调试信息等,参见我的示例代码。

    如果记录器出现问题,则只会在极少数情况下发生。我无法想象运行相同代码但有时记录器被激活有时不被激活的场景。

    我说的是调用堆栈中的另一个记录器。但这只是为了完整性,我真的认为您只是有一个由于没有超时而永远不会返回的请求。

    你知道我为什么会注意到我在问题 1 中谈到的问题吗?

    不,这实际上是我会立即调查的事情,因为 AFAICT,对于相同的请求,您应该只有 401 或只有 200。

    根据the RFC

    10.4.2 401 未经授权

    请求需要用户身份验证。响应必须包含 WWW-Authenticate 标头 字段(第 14.47 节)包含适用于 请求的资源。客户端可以用合适的方式重复请求 授权头字段(第 14.8 节)。

    如果请求已包含授权凭据,则 401 响应指示 对这些证书的授权已被拒绝。如果 401 响应包含与先前响应相同的挑战,并且 用户代理已经至少尝试过一次身份验证,然后 应该向用户呈现在 响应,因为该实体可能包括相关的诊断 信息。

    所以除非requests 对auth 标头做了一些奇怪的事情(据我记得这不是事实,但是...),您应该只记录一个响应。

    编辑 2:

    我想说的是,如果抛出异常但我的代码没有明确捕获,它应该终止作业(在我运行的一些测试中就是这种情况)

    如果异常到达调用堆栈的顶部而没有得到处理,运行时确实会终止进程 - 但您必须确保调用堆栈上的任何处理程序都不会启动并吞下异常。 单独测试函数不会出现此问题,因此您必须检查完整调用堆栈。

    这就是说:

    它没有终止的事实向我表明,没有抛出异常。

    这确实是最有可能的,但只有您才能确定确实如此(我们不知道完整的代码、记录器配置等)。

    【讨论】:

    • 感谢布鲁诺的回答!正如我的问题中提到的,我的代码没有 try/except 块,因为如果出现任何问题,我们希望整个作业终止。如果记录器出现问题,则只会在极少数情况下发生。我无法想象运行相同代码但有时记录器被激活有时不被激活的场景。
    • 你知道为什么我会注意到我在问题 1 中谈到的问题吗?
    • 感谢编辑!关于 401 和 200 问题,我将使用psf/requests 创建一个问题。关于异常处理,在我的评论中我想说,如果抛出异常但我的代码没有明确捕获,它应该终止工作(在我运行的一些测试中就是这种情况)。它没有终止的事实向我表明,没有抛出异常。我基本上只是在寻找确认这很可能是 API/服务器问题,而不是我的代码中的问题(由于您没有看到我的代码,因此您无法明确确认)。
    • "关于 401 和 200 问题,我将使用 psf/requests 创建一个问题" => 您肯定想在之前调查该问题(在调试器中跟踪代码执行可能是个好主意)。
    猜你喜欢
    • 2021-12-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-12-18
    • 2023-03-25
    • 1970-01-01
    相关资源
    最近更新 更多