Skip to content
This repository was archived by the owner on Apr 14, 2022. It is now read-only.

Send request through trio backend #3

Merged
merged 5 commits into from
Jan 2, 2018

Conversation

pquentin
Copy link
Member

@pquentin pquentin commented Jan 2, 2018

I gave a try at the mechanical parts of njsmith/urllib3#1, ie. passing the backend from the pools to the connection, adding async/await keywords, and fixing minor issue in the trio backend.

Running python3 test_trio.py prints:

Response(status_code=200, headers=[(b'connection', b'keep-alive'), (b'server', b'meinheld/0.6.1'), (b'date', b'Tue, 02 Jan 2018 02:12:45 GMT'), (b'content-type', b'text/plain'), (b'content-length', b'30'), (b'access-control-allow-origin', b'*'), (b'access-control-allow-credentials', b'true'), (b'x-powered-by', b'Flask'), (b'x-processed-time', b'0.00075101852417'), (b'via', b'1.1 vegur')], http_version=b'1.1', reason=b'OK')
200
b''

So we see the headers of the h11 response in the trio backend but I don't know how to expose that in the urllib3 Response object, ditto for the body which is just the empty byte string but should not be. I did not even try running the test suite, but can try to work on it if you want to.

Helps to avoid bugs that we are not used to diagnose since flake8 avoids
them in the first place, such as missing `self`.
Test with `python3 trio_test.py`.
Copy link
Member

@njsmith njsmith left a comment

Choose a reason for hiding this comment

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

This is awesome, thank you! I made a few comments, but they ended up being more notes for future work rather than any kind of critique of what you did here, and this is definitely a step forward. So, up to you: do you want to keep working on this, maybe getting read() working, or do you want to merge this as is and then future work can be a new PR?

@@ -225,11 +225,12 @@ def consume_bytes(data):
elif isinstance(event, h11.Response):
# We have our response! Save it and get out of here.
h11_response = event
print(event)
Copy link
Member

Choose a reason for hiding this comment

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

I guess this should be removed at some point :-)

def forceful_close(self):
self._stream.forceful_close()
async def forceful_close(self):
await trio.aclose_forcefully(self._stream)
Copy link
Member

Choose a reason for hiding this comment

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

Ugh, forceful close raises such tricky questions for API design.

I started to write a long thing here about why close should maybe be synchronous after all. Trio's abstract stream interface makes it async because it's, y'know, abstract, and some streams need it to be async. But for the particular concrete streams that urllib3 uses, it's always synchronous. So maybe, I was thinking, it should be synchronous here too, and we'd make the trio backend jump through the necessary hoops to make that work.

Then l looked at twisted and asyncio and realized that they also make closing a socket an async operation for weird API reasons, so fine, let's just make it async :-). It's an issue we might want to revisit in the future though as this develops. (I'm also inclined to say that if we're going to make closing async, we should rename it aclose, which is the convention that trio uses; I stole it from the interpreter's async generator API. But that's also something we can worry about later.)

Copy link
Member Author

Choose a reason for hiding this comment

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

Thanks for the explanation. So forceful_close should become aclose_forcefully?

Copy link
Member

Choose a reason for hiding this comment

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

Oh, I guess it could. That doesn't matter too much though, because the backend API is internal anyway. The more important question is whether we want to rename Response.close, PoolManager.close, etc.

Copy link
Member Author

Choose a reason for hiding this comment

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

Okay, I'll prepare another PR for this change.

Copy link
Member Author

Choose a reason for hiding this comment

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

Just opened #4.

# XX kluge for now
state_machine._cstate.process_error(state_machine.our_role)

return _response_from_h11(h11_response)
return _response_from_h11(h11_response, request_bytes_iterable)
Copy link
Member

Choose a reason for hiding this comment

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

Ah, here's the problem with read :-). (Or at least, the first problem :-).)

request_bytes_iterable is the body of our request, i.e. what we're uploading. The second argument to _response_from_h11 is the body of the response, i.e. what we're downloading. The way the code is written, SyncHTTP1Connection actually acts as the response body (it implements the async iterator interface, see its __anext__ method), so the second argument here should just be self.

Copy link
Member Author

Choose a reason for hiding this comment

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

This comment was really useful.

@@ -306,7 +306,8 @@ def _error_catcher(self):

with self._error_catcher():
if amt is None:
data += b''.join(self.stream(decode_content))
async for chunk in self.stream(decode_content):
data += chunk
Copy link
Member

Choose a reason for hiding this comment

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

This is accidentally quadratic. Better to instead do something like:

chunks = []
async for chunk in self.stream(decode_content):
    chunks.append(chunk)
data = b''.join(chunks)

Copy link
Member Author

Choose a reason for hiding this comment

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

Of course, sorry. Fixed.

This is possible because it no longer depends on self.
@njsmith njsmith merged commit 42a61ef into python-trio:bleach-spike Jan 2, 2018
@kennethreitz
Copy link

Having trouble with the example shown here — i assume it doesn't work anymore :)

@pquentin
Copy link
Member Author

@kennethreitz Right, the test scripts are now in demo/ in the bleach-spike branch: https://github.com/njsmith/urllib3/tree/bleach-spike/demo. Do they work for you?

@kennethreitz
Copy link

got it

@kennethreitz
Copy link

backend= was tripping me up :)

@kennethreitz
Copy link

this is working well! I'm impressed :)

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants