如何在请求(或urllib3.response)中获取完整的服务器响应消息?

How to get full server response message in Requests (or urllib3.response)?

这几天我遇到了GoogleSheetAPI的问题,我能从Requests(通过requests_oauthlib扩展)得到的所有响应消息都是headers & 400 Bad Request。请求版本为 2.19.1。这发生在几个端点 (v4) 上,例如 spreadsheets.values.appendspreadsheets.values.batchUpdatespreadsheets.values.update

我已经浪费了几个小时来确保我已经形成了正确的数据结构,但无济于事,直到那时我才意识到我没有从 API 服务器获得完整的响应消息。

r = request.Request('https://httpbin.org') 返回的 requests.Response object 似乎来自 requests.HTTPAdapter object, which in turn is sort of a wrapper for urllib3.response.HTTPResponse. So I tried to use r.reasonr.raw._original_responser.raw._bodyr.raw.msg 除了 object 参考和 None(或只是空字符串)之外什么也没给我。

长话短说,我放弃了一段时间,当我回到它时,我意识到我应该尝试使用我一直忽略的 Google 的 API 资源管理器,并且结果 响应中的一条错误消息,400 错误不是由于任何错误的 payload/data 结构形式造成的。

{   "error": {
    "code": 400,
    "message": "Invalid data[0]: Requested writing within range [mysheet!A3:AH3], but tried writing to row [4]",
    "status": "INVALID_ARGUMENT"   } 
}

我的问题是:

我在调试来自请求的连接响应方面是否有误?实际上有一种方法可以在请求中从服务器获取完整的原始消息,例如 curl 中的完整调试模式?或者这条消息实际上仅在 API 请求来自 Google API 资源管理器时才发送?

我找到了a closed issue in requests repo from last year titled as "Suggestion: Improve the raise for status to include response body if there is one" issued by westover,他在里面写道:

Looking for more details on the HTTPError exception. The API I am working with sends back a 400 but also has a text/json body in it. It would be nice if the raise_for_status() function included the response text if there was any.

他还提供了一个小的变通功能,这成为讨论转移到 a pull request discussion 的原因的基础:

import requests
from requests.exceptions import HTTPError
def expanded_raise_for_status(res):
    """
    Take a "requests" response object and expand the raise_for_status method to return more helpful errors
    @param res:
    @return: None
    """
    try:
        res.raise_for_status()
    except HTTPError as e:
        if res.text:
            raise HTTPError('{} Error Message: {}'.format(str(e.message), res.text))
        else:
           raise e
    return

我将此应用到我现有的 class 并且本质上是因为 also pointed out, it extends/hijacks Response.raise_for_status() method that calls HTTPError(RequestsException) class whenever raise_for_status() 被用户用来提供来自服务器响应的错误消息,通常采用 assert response.status_code == 200, raise_for_status() 的形式。这是通过将 Response.text(或 content / json 方法)添加到异常消息和 return 响应主体而不是当前代码 return <self.status_code, self.reason, self.url> 转换为 400 Bad Requests <url>.

def raise_for_status(self):
    """Raises stored :class:`HTTPError`, if one occurred."""

    http_error_msg = ''
    if isinstance(self.reason, bytes):
        # We attempt to decode utf-8 first because some servers
        # choose to localize their reason strings. If the string
        # isn't utf-8, we fall back to iso-8859-1 for all other
        # encodings. (See PR #3538)
        try:
            reason = self.reason.decode('utf-8')
        except UnicodeDecodeError:
            reason = self.reason.decode('iso-8859-1')
    else:
        reason = self.reason

    if 400 <= self.status_code < 500:
        http_error_msg = u'%s Client Error: %s for url: %s' % (self.status_code, reason, self.url)

    elif 500 <= self.status_code < 600:
        http_error_msg = u'%s Server Error: %s for url: %s' % (self.status_code, reason, self.url)

    if http_error_msg:
        raise HTTPError(http_error_msg, response=self)

(在 this reply 中他进一步阐述了他认为它值得一个特性的原因,但是 pull request 被拒绝了,我认为维护者也有很好的观点),这可能会导致其他异常考虑到该模块的流行程度及其使用的广泛原因,如果不使用,可能会出现更多问题 thoughtfully/sparingly。

您要查找的错误消息位于响应正文中。这可以通过 response.text(或 response.json(),因为特定的正文是 JSON)访问。如果,正如您的自我回答所暗示的那样,您想从 raise_for_status() 引发的异常中获取正文,您可以通过异常的 response 属性:exception.response.textexception.response.json().