Skip to content
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

Handle h11.Connection.next_event() RemoteProtocolException #524

Merged
merged 1 commit into from
Nov 22, 2019

Conversation

toppk
Copy link
Contributor

@toppk toppk commented Nov 12, 2019

here is a proposed fix. I wasn't too clear on the test suite (as usual :), but I was able to make a case that triggers the failure mode and test that the fix works as expected.

Addresses github issue #96

Fixes #96

Copy link
Member

@florimondmanca florimondmanca left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this! Overall and after reading through the digging in #96, this looks sensible to me.

Some notes:

>>> import httpx
>>>httpx.get("http://localhost:8000/premature_close")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Users/florimond/Developer/python-projects/httpx/httpx/api.py", line 125, in get
    return request(
  File "/Users/florimond/Developer/python-projects/httpx/httpx/api.py", line 84, in request
    return client.request(
  File "/Users/florimond/Developer/python-projects/httpx/httpx/client.py", line 845, in request
    return self.send(
  File "/Users/florimond/Developer/python-projects/httpx/httpx/client.py", line 908, in send
    response.read()
  File "/Users/florimond/Developer/python-projects/httpx/httpx/models.py", line 1097, in read
    self._content = b"".join([part for part in self.stream()])
  File "/Users/florimond/Developer/python-projects/httpx/httpx/models.py", line 1097, in <listcomp>
    self._content = b"".join([part for part in self.stream()])
  File "/Users/florimond/Developer/python-projects/httpx/httpx/models.py", line 1108, in stream
    for chunk in self.raw():
  File "/Users/florimond/Developer/python-projects/httpx/httpx/models.py", line 1136, in raw
    for part in self._raw_stream:
  File "/Users/florimond/Developer/python-projects/httpx/httpx/concurrency/base.py", line 159, in iterate
    yield self.run(async_iterator.__anext__)
  File "/Users/florimond/Developer/python-projects/httpx/httpx/concurrency/asyncio.py", line 260, in run
    return self.loop.run_until_complete(coroutine(*args, **kwargs))
  File "/Users/florimond/.pyenv/versions/3.8.0/lib/python3.8/asyncio/base_events.py", line 608, in run_until_complete
    return future.result()
  File "/Users/florimond/Developer/python-projects/httpx/httpx/dispatch/http11.py", line 152, in _receive_response_data
    event = await self._receive_event(timeout)
  File "/Users/florimond/Developer/python-projects/httpx/httpx/dispatch/http11.py", line 164, in _receive_event
    event = self.h11_state.next_event()
  File "/Users/florimond/Developer/python-projects/httpx/venv/lib/python3.8/site-packages/h11/_connection.py", line 420, in next_event
    event = self._extract_next_receive_event()
  File "/Users/florimond/Developer/python-projects/httpx/venv/lib/python3.8/site-packages/h11/_connection.py", line 369, in _extract_next_receive_event
    event = self._reader.read_eof()
  File "/Users/florimond/Developer/python-projects/httpx/venv/lib/python3.8/site-packages/h11/_readers.py", line 169, in read_eof
    raise RemoteProtocolError(
h11._util.RemoteProtocolError: peer closed connection without sending complete message body (incomplete chunked read)

This error would be caught and handled by this PR though (it raises a ConnectionClosed error), so I think we're on a good track anyway. I haven't followed the whole story though so I'll let others make a more informed call. :)

tests/conftest.py Outdated Show resolved Hide resolved
tests/conftest.py Outdated Show resolved Hide resolved
@toppk
Copy link
Contributor Author

toppk commented Nov 13, 2019

Thanks for this! Overall and after reading through the digging in #96, this looks sensible to me.

Some notes:

* I'm not able to reproduce the results I got in [#96 (comment)](https://github.com/encode/httpx/issues/96#issuecomment-544193625). Could it be that somehow the issue is not surfacing on 3.8 / Uvicorn 0.10+?

I saw your comment, and I didn't understand why this would cause an exception at all. I assumed you were doing something else, like killing the server at the right moment, as mentioned in earlier comments by others.

* A `premature_close`-type endpoint does yield the exact same `RemoteProtocolError` than the one listed in #96:
>>> import httpx
>>>httpx.get("http://localhost:8000/premature_close")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Users/florimond/Developer/python-projects/httpx/httpx/api.py", line 125, in get
    return request(
  File "/Users/florimond/Developer/python-projects/httpx/httpx/api.py", line 84, in request
    return client.request(
  File "/Users/florimond/Developer/python-projects/httpx/httpx/client.py", line 845, in request
    return self.send(
  File "/Users/florimond/Developer/python-projects/httpx/httpx/client.py", line 908, in send
    response.read()
  File "/Users/florimond/Developer/python-projects/httpx/httpx/models.py", line 1097, in read
    self._content = b"".join([part for part in self.stream()])
  File "/Users/florimond/Developer/python-projects/httpx/httpx/models.py", line 1097, in <listcomp>
    self._content = b"".join([part for part in self.stream()])
  File "/Users/florimond/Developer/python-projects/httpx/httpx/models.py", line 1108, in stream
    for chunk in self.raw():
  File "/Users/florimond/Developer/python-projects/httpx/httpx/models.py", line 1136, in raw
    for part in self._raw_stream:
  File "/Users/florimond/Developer/python-projects/httpx/httpx/concurrency/base.py", line 159, in iterate
    yield self.run(async_iterator.__anext__)
  File "/Users/florimond/Developer/python-projects/httpx/httpx/concurrency/asyncio.py", line 260, in run
    return self.loop.run_until_complete(coroutine(*args, **kwargs))
  File "/Users/florimond/.pyenv/versions/3.8.0/lib/python3.8/asyncio/base_events.py", line 608, in run_until_complete
    return future.result()
  File "/Users/florimond/Developer/python-projects/httpx/httpx/dispatch/http11.py", line 152, in _receive_response_data
    event = await self._receive_event(timeout)
  File "/Users/florimond/Developer/python-projects/httpx/httpx/dispatch/http11.py", line 164, in _receive_event
    event = self.h11_state.next_event()
  File "/Users/florimond/Developer/python-projects/httpx/venv/lib/python3.8/site-packages/h11/_connection.py", line 420, in next_event
    event = self._extract_next_receive_event()
  File "/Users/florimond/Developer/python-projects/httpx/venv/lib/python3.8/site-packages/h11/_connection.py", line 369, in _extract_next_receive_event
    event = self._reader.read_eof()
  File "/Users/florimond/Developer/python-projects/httpx/venv/lib/python3.8/site-packages/h11/_readers.py", line 169, in read_eof
    raise RemoteProtocolError(
h11._util.RemoteProtocolError: peer closed connection without sending complete message body (incomplete chunked read)

This error would be caught and handled by this PR though (it raises a ConnectionClosed error), so I think we're on a good track anyway. I haven't followed the whole story though so I'll let others make a more informed call. :)

raise (new) ConnectionClosed exception if this occurs when
socket is closed, otherwise reuse ProtocolError exception.

Add test case for ConnectionClosed behavior.

Resolves encode#96
@toppk
Copy link
Contributor Author

toppk commented Nov 13, 2019

address some issues with original pr.

  • renamed the test case
  • learned about url.copy_with(path="")
  • removed the sleep from the "bad" app.

Other then not really understanding how @florimondmanca test case ever triggered, I'm confident this PR addresses an uncaught exception issue.

Copy link
Member

@florimondmanca florimondmanca left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good to me, though a second pair of eyes would be neat as I didn't follow the original issue that much.

@toppk
Copy link
Contributor Author

toppk commented Nov 20, 2019

pinging @tomchristie on this PR. it would be nice to get this trivial patch merged before it becomes stale.

@florimondmanca
Copy link
Member

So, I just tried this out locally and it works fab. Also handles the case when a server is shutdown while we're receiving the response. So, merging! Thanks @toppk

@florimondmanca florimondmanca merged commit f06ca87 into encode:master Nov 22, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
2 participants