【问题标题】:Requests http lib: include X-Request-ID请求 http lib:包括 X-Request-ID
【发布时间】:2019-12-05 10:46:31
【问题描述】:

我在 Python 中使用requests HTTP 客户端库。

有时 HTTP 请求失败,我收到状态为 500 的 HTTP 响应。

这可以在 CI 或生产中,我看到类似这样的内容:

AssertionError: 200 != 500 : <Response [500]>

这没有多大帮助。

如果我能在上面的消息中看到X-Request-ID,那就太好了。在我的环境中,每个 HTTP 响应都存在这种情况。

这意味着请求库的 HTTP 响应对象应该添加它。

我希望 repr() 看起来像 &lt;Response [500] XejfkmxcPfhM3dqhY2HJgQAAAAM&gt;

由于这不是我的代码,而是 requests 库的代码,我不确定如何实现它。

如何在requests响应对象的repr()中包含X-Request-ID

【问题讨论】:

  • 两个选项:1. 分叉并创建您自己的副本 2. Monkeypatch requests.Response 使用您自己的 Response 副本覆盖 __repr__
  • 您是否尝试使用 SENTRY & RAVEN(生产)之类的东西 - 看看它捕获了什么?还是用断点重新创建 TEST ENV 中的错误?
  • @OhadtheLad AFAIK sentry 是一项商业服务。如果可能的话,我更喜欢开源解决方案。到目前为止我还没有尝试过哨兵。

标签: python python-requests


【解决方案1】:

我宁愿创建自己的“响应流”以防万一,而不是使用任何类型的monkey patching。我已经检查了请求库的来源,我们很高兴去那里!我们可以使用requests Event Hooks system 实现所需的功能。

有一个工作示例,我们可以对响应做任何事情。唯一的变化是我们必须使用我们自己的Session 对象实例来使用该功能。但!如果我们不想覆盖任何源代码,我们可以做一个 linemonkey-patch,覆盖默认 api 调用的默认 Session 类,它可以像魅力一样在任何地方工作。

我的解决方案在这里

import requests


class ResponseVerbose(requests.Response):
    extra_header_repr = 'X-Request-Guid'

    def __repr__(self):
        return '<Response [{}] {}: {}>'.format(
            self.status_code,
            self.extra_header_repr,
            self.headers.get(self.extra_header_repr, 'None')
        )


class Session(requests.Session):
    def __init__(self):
        super().__init__()

        self.hooks['response'] = self.build_response

    @staticmethod
    def build_response(resp, *args, **kwargs):
        """
        Let's rebuild the source response into required verbose response object using all fields from the original

        FYI: requests.adapters.HTTPAdapter.build_response
        """
        response = ResponseVerbose()
        response.status_code = resp.status_code
        response.headers = resp.headers
        response.encoding = resp.encoding
        response.raw = resp.raw
        response.reason = response.raw.reason
        response.url = resp.url
        response.cookies = resp.cookies.copy()
        response.request = resp.request
        response.connection = resp.connection

        return response


def main():
    url = 'https://stackoverflow.com/'

    sess = Session()
    print('response using our own session object: {}'.format(sess.get(url)))

    import requests.api
    requests.api.sessions.Session = Session
    print('response using monkey patched global Session class: {}'.format(requests.get(url)))


if __name__ == '__main__':
    main()

输出

# python test123.py
response using our own session object: <Response [200] X-Request-Guid: 0c446bb5-7c96-495d-a831-061f5e3c2afe>
response using monkey patched global Session class: <Response [200] X-Request-Guid: 1db5aea7-8bc9-496a-addc-1231e8543a89>

一个使用Response.__getstate__()函数的更短示例

更多信息https://github.com/psf/requests/blob/master/requests/models.py#L654

正如我从源代码中看到的,您不应该对非常大的内容响应执行此操作,因为它会获取整个 resp.content 以便能够将响应状态转换为状态字典。因此,仅当您知道响应中没有千兆字节时才使用它:)

函数看起来更简单。

import requests


class ResponseVerbose(requests.Response):
    extra_header_repr = 'X-Request-Guid'

    def __repr__(self):
        return '<Response [{}] {}: {}>'.format(
            self.status_code,
            self.extra_header_repr,
            self.headers.get(self.extra_header_repr, 'None')
        )


class Session(requests.Session):
    def __init__(self):
        super().__init__()

        self.hooks['response'] = self.build_response

    @staticmethod
    def build_response(resp, *args, **kwargs):
        """
        Let's rebuild the source response into required verbose response object using all fields from the original

        FYI: requests.adapters.HTTPAdapter.build_response
        """

        response = ResponseVerbose()
        for k, v in resp.__getstate__().items():
            setattr(response, k, v)

        return response


def main():
    url = 'https://stackoverflow.com/'

    sess = Session()
    print('response using our own session object: {}'.format(sess.get(url)))

    import requests.api
    requests.api.sessions.Session = Session
    print('response using monkey patched global Session class: {}'.format(requests.get(url)))


if __name__ == '__main__':
    main()

该解决方案仅打印 StackOverflow 的额外响应标头X-Request-Guid。这个额外的标题我很容易配置,只是为了展示如何以正确的方式完成。

【讨论】:

  • 不错。不幸的是build_response() 仍然很长(例如复制cookie)。也许请求库的作者会允许拉取请求以使更改响应类更简单?
  • 为什么不呢,传递响应和请求类可能是一个很好的特性。但不是现在我相信 :) 作为一个快速的解决方案,它对我来说看起来很棒:)
  • @guettli 添加了一个更简单的解决方案,使用魔术方法将响应状态转储到状态字典。看起来更干净:)
  • 我仍然认为更改响应类应该更容易。也许请求库的作者会允许拉取请求以使更改响应类更简单?
  • 何乐而不为,你可以试着和作者谈谈:)
【解决方案2】:

一种方法是在运行时覆盖 requests.models.Response.__repr__ 方法(也称为 [Wikipedia]: Monkey patch) 正如@heemayl 评论的那样。请注意,这是这种方式的一种变体(我认为是最简单的)。

code00.py

#!/usr/bin/env python3

import sys
import requests


def __response_repr(self):
    repr_headers = (
        "X-Request-ID",
        "Content-Encoding",  # @TODO - cfati: For demo purposes only!!! DELETE (COMMENT) THIS LINE.
    )
    repr_parts = ["<Response [{0:d}]".format(self.status_code)]
    for repr_header in repr_headers:
        if repr_header in self.headers:
            repr_parts.append(" {0:}".format(self.headers[repr_header]))
    repr_parts.append(">")
    return "".join(repr_parts)


def main(*argv):
    if argv:
        print("Monkey patch {0:}...\n".format(requests.models.Response))
        requests.models.Response.__str__ = requests.models.Response.__repr__  # Keep the original __str__ behavior
        requests.models.Response.__repr__ = __response_repr

    url = "https://www.google.com"
    print("Connecting to: {0:s} ...\n".format(url))

    r = requests.get(url)
    print("str(Response): {0:s}".format(str(r)))
    print("repr(Response): {0:s}".format(repr(r)))


if __name__ == "__main__":
    print("Python {0:s} {1:d}bit on {2:s}\n".format(" ".join(item.strip() for item in sys.version.split("\n")), 64 if sys.maxsize > 0x100000000 else 32, sys.platform))
    main(*sys.argv[1:])
    print("\nDone.")

输出

[cfati@CFATI-5510-0:e:\Work\Dev\StackOverflow\q059193447]> "e:\Work\Dev\VEnvs\py_064_03.07.03_test0\Scripts\python.exe" code00.py
Python 3.7.3 (v3.7.3:ef4ec6ed12, Mar 25 2019, 22:22:05) [MSC v.1916 64 bit (AMD64)] 64bit on win32

Connecting to: https://www.google.com ...

str(Response): <Response [200]>
repr(Response): <Response [200]>

Done.

[cfati@CFATI-5510-0:e:\Work\Dev\StackOverflow\q059193447]> "e:\Work\Dev\VEnvs\py_064_03.07.03_test0\Scripts\python.exe" code00.py dummy_arg_to_trigger_monkey_patch
Python 3.7.3 (v3.7.3:ef4ec6ed12, Mar 25 2019, 22:22:05) [MSC v.1916 64 bit (AMD64)] 64bit on win32

Monkey patch <class 'requests.models.Response'>...

Connecting to: https://www.google.com ...

str(Response): <Response [200]>
repr(Response): <Response [200] gzip>

Done.

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-02-20
    • 2021-07-20
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多