-
-
Notifications
You must be signed in to change notification settings - Fork 2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Allow reading already retrieved response body after connection is closed #8451
Comments
+1, as a temporary alternative, I use |
And I found this. It works well. import asyncio
from aiohttp import ClientResponse, ClientSession
class WrappedResponse(ClientResponse):
async def read(self) -> bytes:
if self._body is None:
await super().read()
return self._body
async def main():
async with ClientSession(response_class=WrappedResponse) as session:
async with session.get("https://httpbin.org/get") as resp:
await resp.read()
body = await resp.read()
print(len(body))
if __name__ == '__main__':
asyncio.run(main()) |
@DanielNoord Can you provide a reproducer of this? I'm not clear how you are unable to read it a second time. I'm under the impression that you should be able to if you don't leave the response context (or call resp.close()). |
I don't have access to my work-laptop so can't give the full reproducer, but I can give the exact flow of what happens. I use a an adapter pattern that manages my
Thus, after Of course, it would be better for AWS to set their headers correctly, but I have failed to convince them to do so 😓 |
Can you provide the traceback for this exception when possible? I'm a little confused where that exception is coming from that could fail read() while also managing to set _body (it looks like that'd only be possible if the exception comes from the writer, which shouldn't be related to AWS's headers). But, also, maybe the source of that exception shouldn't be setting _released. Note that you can just use an explicit encoding in .text(), so if you know the encoding that should still work for you. |
It doesn't even matter what type of exception it is though, right? I'd like to give you a reproducer but don't know how I can easily set up a test server that sends the incorrect responses and then show it as a reproducer. Do you have a test in the test suite that I could modify (easily) so I can create a reproducer that helps you understand the issue? |
No, a quick test allows me to call read() twice...
test_client_functional.py is probably a good place. |
Sorry took a bit but I have written my use case into the format that async def test_encoding_header_is_incorrect(aiohttp_client: AiohttpClient) -> None:
async def status_handler(
status: int, body: bytes, response: ClientResponse
) -> None:
if status == 429:
# Handle backoff and retries
pass
elif status == 401:
# Handle authentication error
pass
elif b"Some known error" in body:
# Handle known error
pass
await response.raise_for_status()
async def send_incorrect_heading(request: web.Request) -> web.Response:
return web.Response(
status=400,
headers={"Content-Type": "text/html; charset=utf-8"},
body="Ø".encode("latin1"),
)
app = web.Application()
app.router.add_route("GET", "/headers", send_incorrect_heading)
client = await aiohttp_client(app)
async with client.get("/headers") as resp:
try:
await status_handler(resp.status, await resp.read(), resp)
except ClientResponseError:
# I can still read _body as it was already set, but _body is private
body = resp._body
assert body == "Ø".encode("latin1")
try:
# This will crash as read() checks whether the resposne was released
await resp.read()
except ClientConnectionError:
# This also won't work as text() checks the encoding which is set incorrectly by AWS
await resp.text() The Currently I can do so by reading Ideally in the first |
OK, so it's the raise_for_status() method, which actually calls |
I don't really understand your response, sorry. Could you clarify a bit? What I'm asking is whether it is possible to add/change a public API that gives access to If it should be impossible to access |
Just discussed this. What we're going to do is make .text()/.json() match the .read() behaviour, so they shouldn't be used outside the context (probably v4 only to reduce breakage). Then we'll change .raise_for_status() so it doesn't release() if we're already in a context, in which case it can just rely on |
Awesome! Thanks for the clear response and plan forward. Looking forward to the changes. Let me know if you need any further input or help with testing. |
Is your feature request related to a problem?
The changes in #3365 have been discussed in other issues before and this is a related issue.
With
ClientReponse.text
I'm able to get the body of a response even if I did raise the exception. Assuming Iread()
it before.aiohttp/aiohttp/client_reqrep.py
Lines 1083 to 1091 in f662958
If
_body
is already set it will just be decoded.However, I'm talking with a server that sets its headers incorrectly and therefore can't rely on
get_encoding()
. CallingClientReponse.read
actually breaks my code:aiohttp/aiohttp/client_reqrep.py
Lines 1040 to 1058 in f662958
Even if
._body
is already set I won't be able to read it again as there is now a check againstself._released
.Describe the solution you'd like
Would it be possible to expose a method that retrieves the body of a
ClientReponse
if it is already set without checking if the response is released yet?Describe alternatives you've considered
Not really.
Related component
Client
Additional context
No response
Code of Conduct
The text was updated successfully, but these errors were encountered: