Skip to content
This repository was archived by the owner on Jan 13, 2021. It is now read-only.

Add connection/read timeout for requests adapter #342

Merged
merged 9 commits into from
Jul 27, 2017

Conversation

hyxbiao
Copy link
Contributor

@hyxbiao hyxbiao commented Jul 24, 2017

[+] Add connection/read timeout for requests adapter, we can use like this:

import requests
from hyper.contrib import HTTP20Adapter
s = requests.Session()
s.mount('https://http2bin.org', HTTP20Adapter())
r = s.get('https://http2bin.org/get', timeout=30)
print(r.status_code)

Copy link
Member

@Lukasa Lukasa 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! I've left a note inline. It'd also be really useful to actually add tests for the timeout functionality that we're implicitly also adding to our lower-level classes.

hyper/contrib.py Outdated
@@ -77,7 +77,8 @@ def get_connection(self, host, port, scheme, cert=None, verify=True,
secure=secure,
ssl_context=ssl_context,
proxy_host=proxy_netloc,
proxy_headers=proxy_headers)
proxy_headers=proxy_headers,
**kwargs)
Copy link
Member

Choose a reason for hiding this comment

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

We shouldn't be sending arbitrary kwargs from Requests to hyper: there's no reason to assume that those kwargs will match in any sensible way. We should aim to be explicit here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

OK, I had change to explicit timeout and add more timeout tests

Copy link
Contributor

@KostyaEsmukov KostyaEsmukov 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! I was going to work on timeouts this week but you have started earlier :)

I have one more note on this: the _create_tunnel function of http11 module should respect the timeout too.

PS: Related issue: #187

@@ -150,6 +150,15 @@ def __init__(self, host, port=None, secure=None, ssl_context=None,
#: the standard hyper parsing interface.
self.parser = Parser()

# timeout
timeout = kwargs.get('timeout')
Copy link
Contributor

Choose a reason for hiding this comment

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

timeout should be optional. I think it should be None - that means no timeout.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, timeout is optional and default None, is equivalent to socket.setblocking(1)

@hyxbiao
Copy link
Contributor Author

hyxbiao commented Jul 25, 2017

timeout is optional, the default value is None, means that it will block connection/read, it is the same as request/adapters.py:
def send(self, request, stream=False, timeout=None, verify=True, cert=None, proxies=None):

if isinstance(timeout, tuple):
    try:
        connect, read = timeout
        timeout = TimeoutSauce(connect=connect, read=read)
    except ValueError as e:
        # this may raise a string formatting error.
        err = ("Invalid timeout {0}. Pass a (connect, read) "
               "timeout tuple, or a single float to set "
               "both timeouts to the same value".format(timeout))
        raise ValueError(err)
else:
    timeout = TimeoutSauce(connect=timeout, read=timeout)

it also can set timeout to socket._GLOBAL_DEFAULT_TIMEOUT

hyper/contrib.py Outdated
@@ -92,13 +93,15 @@ def send(self, request, stream=False, cert=None, verify=True, proxies=None,
proxy = prepend_scheme_if_needed(proxy, 'http')

parsed = urlparse(request.url)
timeout = kwargs.get('timeout')
Copy link
Member

Choose a reason for hiding this comment

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

Let's just add timeout to our list of kwargs.

conn = self.get_connection()
try:
conn.connect()
assert False
Copy link
Member

Choose a reason for hiding this comment

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

This should be pytest.fail, and generally should go in an else block.


# Wait for request
req_event.wait(5)
# Now, send the headers for the response. This response has no body
Copy link
Member

Choose a reason for hiding this comment

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

This comment is misleading.

time.sleep(1)

# Now, send the headers for the response. This response has no body
f = build_headers_frame(
Copy link
Member

Choose a reason for hiding this comment

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

We never actually need to send this, and doing so could cause exceptions, so let's not send any data here.


try:
conn.get_response()
assert False
Copy link
Member

Choose a reason for hiding this comment

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

Same note about pytest.fail and else blocks.


send_event.wait(5)

assert data[0].startswith(b'PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n')
Copy link
Member

Choose a reason for hiding this comment

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

How can this possibly pass? If the connection fails, we're never going to see this data.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

default timeout None will block connect, so it will connect sucess and go on

Copy link
Member

Choose a reason for hiding this comment

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

Ah, sorry, I see that. Thanks!


def socket_handler(listener):
time.sleep(1)
sock = listener.accept()[0]
Copy link
Member

Choose a reason for hiding this comment

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

No point calling accept: it'll usually fail, and throw exceptions, which we don't want.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

if connection timeout smaller than 1s, it will throw timeout exception, in this test connection timeout is set to 0.5, so it will throw exception as expect

Copy link
Member

Choose a reason for hiding this comment

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

Right, but this exception is not caught, meaning it'll be logged and generally treated badly. We shouldn't do anything in the background thread that we know will fail, and accept will fail here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

got it, I just updated

@@ -101,7 +101,7 @@ class HTTP11Connection(object):

def __init__(self, host, port=None, secure=None, ssl_context=None,
proxy_host=None, proxy_port=None, proxy_headers=None,
**kwargs):
timeout=None, **kwargs):
Copy link
Member

Choose a reason for hiding this comment

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

We should probably add this to the common HTTPConnection object as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ok,I just added


assert c._connect_timeout == 5
assert c._read_timeout == 60

Copy link
Member

Choose a reason for hiding this comment

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

We need these tests for HTTP/2 as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ok,I add this in test/test_hyper.py


def socket_handler(listener):
time.sleep(1)
sock = listener.accept()[0]
Copy link
Member

Choose a reason for hiding this comment

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

Right, but this exception is not caught, meaning it'll be logged and generally treated badly. We shouldn't do anything in the background thread that we know will fail, and accept will fail here.

# assert 'timed out' in e.message
pass
else:
assert False
Copy link
Member

Choose a reason for hiding this comment

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

pytest.fail, please.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

sorry, i'm not familiar with pytest before, i replace it with pytest.raises or pytest.fail

Copy link
Member

Choose a reason for hiding this comment

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

pytest.raises is the best thing to do if you don't plan to do anything in the except block.

except (SocketTimeout, ssl.SSLError):
# Py2 raises this as a BaseSSLError,
# Py3 raises it as socket timeout.
# assert 'timed out' in e.message
Copy link
Member

Choose a reason for hiding this comment

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

Probably we don't need the assert statement in the comment?

# assert 'timed out' in e.message
pass
else:
assert False
Copy link
Member

Choose a reason for hiding this comment

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

pytest.fail, please.

except (SocketTimeout, ssl.SSLError):
# Py2 raises this as a BaseSSLError,
# Py3 raises it as socket timeout.
assert False
Copy link
Member

Choose a reason for hiding this comment

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

pytest.fail, please.


send_event.wait(5)

assert data[0].startswith(b'PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n')
Copy link
Member

Choose a reason for hiding this comment

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

Ah, sorry, I see that. Thanks!

@@ -78,8 +79,10 @@ def __init__(self,
self._h1_kwargs.update(kwargs)
self._h2_kwargs.update(kwargs)

self._timeout = timeout
Copy link
Member

Choose a reason for hiding this comment

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

This should go into _h1_kwargs and _h2_kwargs: it'll also need associated tests.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ok, it's done

@hyxbiao
Copy link
Contributor Author

hyxbiao commented Jul 26, 2017

update information:
add timeout for _create_tunnel funciton, almost forget it
move timeout to _h1/2_kwargs in hyper/common/connection.py
add more timeout tests

Copy link
Member

@Lukasa Lukasa left a comment

Choose a reason for hiding this comment

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

Very nicely done, thank you!

@Lukasa Lukasa merged commit 75f25f7 into python-hyper:development Jul 27, 2017
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