【问题标题】:aiohttp: Getting a server's response when the request status code is not 2XXaiohttp:当请求状态码不是 2XX 时获取服务器的响应
【发布时间】:2022-08-15 19:55:06
【问题描述】:

我正在使用 aiohttp 进行异步 http 请求,当请求返回 4XX 错误时,我无法弄清楚如何从服务器获取响应。

    async def login(self, username: str, password: str) -> None:
        ...
        async with aiohttp.ClientSession(headers=self._headers) as session:
            async with session.post(route, data=data, headers=self._headers) as resp:
                if resp.ok:
                    response = await resp.json()
                    self._headers[\'Authorization\'] = \'Bearer \' + response[\'access_token\']
                else:
                    response = await resp.json()
                    raise InvalidGrant(response)

如果响应返回 2XX 代码,则使用 resp.json() 可以正常工作,但是当它返回 4XX 错误(在本例中为 400)时,它会引发 aiohttp.client_exceptions.ClientConnectionError 并且不会让我得到服务器的响应已发送(我需要,因为服务器返回某种比Bad Request 更具描述性的错误消息)。如果请求不成功,是否无法通过 aiohttp 获得响应?

  • 你真的抓住了错误来处理它吗?有关于那件事的信息。
  • @MisterMiyagi 捕捉错误不会帮助我从服务器获得响应,因为它发生在我尝试resp.json() 时,我不确定我还能用什么来获得响应
  • 据我了解文档,ClientConnectionError 表示联系错误,即网络层的问题,而不是顶部的 HTTP 层。在这种情况下,不会有 HTTP 错误代码或服务器响应。您的情况究竟是什么时候引发的错误?
  • @MisterMiyagi 当resp.ok 不正确时,它专门发生在response = await resp.json() 行上。删除该行时,不会引发异常。
  • 您是否有任何理由专门寻找resp.json() 而不仅仅是resp.text()?后者也失败了吗?

标签: python aiohttp


【解决方案1】:

出现此问题的原因是 response.ok 的副作用。在旧版本的 aiohttp(3.7 及以下)中,response.ok 调用了response.raise_for_status(),这会关闭 TCP 会话并导致无法再读取服务器的响应。

要解决此问题,您只需将response = await resp.json() 移动到response.ok 行上方,这样您就可以预先保存响应。例如:

    async def login(self, username: str, password: str) -> None:
        ...
        async with aiohttp.ClientSession(headers=self._headers) as session:
            async with session.post(route, data=data, headers=self._headers) as resp:
                response = await resp.json()
                if resp.ok:
                    self._headers['Authorization'] = 'Bearer ' + response['access_token']
                else:
                    raise InvalidGrant(response)

此问题已在 aiohttp 3.8 中得到修复:https://github.com/aio-libs/aiohttp/pull/5404

【讨论】:

    【解决方案2】:

    不要在任何地方使用 raise_for_status=True 作为关键字参数。取而代之的是在您检索响应、其状态代码、消息等后手动调用 response.raise_for_status()

    async def fetch_feed_items(
        feed: Feed, request_headers: CIMultiDict, session: aiohttp.ClientSession
    ) -> Tuple[Feed, CIMultiDictProxy, int, str]:
        """Load data from url.
    
        Args:
            feed (Feed): object which contains url, etag, last_modified and feed_id
            request_headers (CIMultiDict): headers sent with the request
            session (aiohttp.ClientSession): an aiohttp.ClientSession instance
    
        Returns:
            Tuple[Feed, CIMultiDictProxy, int, str]: Returns information about the source
        """
        response_headers = {}
        text = ""
        status = None
        try:
            async with session.get(feed.url, headers=request_headers, ssl=False) as response:
                text = await response.text()
                response_headers = response.headers
                status = response.status
                message = response.reason
                # Dont use raise_for_status=True option as it doesn't capture the status code and message
                response.raise_for_status()
        except aiohttp.ClientError as e:
            print(feed.url, "aiohttp client error", e)
        except aiohttp.http.HttpProcessingError as e:
            print(feed.url, "aiohttp processing error", e)
        except asyncio.TimeoutError as e:
            print(feed.url, "asyncio timeout error", e)
        except Exception as e:
            print(feed.url, "generic error", e)
        finally:
            return feed, response_headers, status, message, text
    

    在上面的sn-p中,当出现4xx或5xx错误时,先将response headers,status,message值存入一个变量,然后response.raise_for_status触发异常

    【讨论】:

      猜你喜欢
      • 2022-11-29
      • 2015-05-28
      • 2017-04-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-01-06
      • 1970-01-01
      相关资源
      最近更新 更多