urllib3 / urllib3

urllib3 is a user-friendly HTTP client library for Python
https://urllib3.readthedocs.io
MIT License
3.77k stars 1.15k forks source link

SSLEOFError in Python 3.10 not in Python 3.9 #2733

Open adamtheturtle opened 2 years ago

adamtheturtle commented 2 years ago

Apologies if this is not the right place for this - I was torn between here and StackOverflow.

Summary: Code raises an exception on Python 3.10.6 and not Python 3.9.13. I would like to fully understand why and hopefully avoid the exception.

Simple reproducible case:

import urllib3 # This is urllib3==1.26.12

http = urllib3.PoolManager()
response = http.request(
    "POST",
    'https://cloudreco.vuforia.com/v1/query',
    body="a" * 100000000,  # A large body to hit the error.
)

print(response.data)

In Python 3.9, I get no exception. I see:

b'<html>\r\n<head><title>413 Request Entity Too Large</title></head>\r\n<body>\r\n<center><h1>413 Request Entity Too Large</h1></center>\r\n<hr><center>nginx</center>\r\n</body>\r\n</html>\r\n'

This is the response that I want.

In Python 3.10, I get the following traceback:

Traceback (most recent call last):
  File "/Users/adam/Documents/repositories/personal/vws-python-mock/minimal.py", line 5, in <module>
    response = http.request(
  File "/Users/adam/.virtualenvs/vws-python-mock/lib/python3.10/site-packages/urllib3/request.py", line 78, in request
    return self.request_encode_body(
  File "/Users/adam/.virtualenvs/vws-python-mock/lib/python3.10/site-packages/urllib3/request.py", line 170, in request_encode_body
    return self.urlopen(method, url, **extra_kw)
  File "/Users/adam/.virtualenvs/vws-python-mock/lib/python3.10/site-packages/urllib3/poolmanager.py", line 376, in urlopen
    response = conn.urlopen(method, u.request_uri, **kw)
  File "/Users/adam/.virtualenvs/vws-python-mock/lib/python3.10/site-packages/urllib3/connectionpool.py", line 815, in urlopen
    return self.urlopen(
  File "/Users/adam/.virtualenvs/vws-python-mock/lib/python3.10/site-packages/urllib3/connectionpool.py", line 815, in urlopen
    return self.urlopen(
  File "/Users/adam/.virtualenvs/vws-python-mock/lib/python3.10/site-packages/urllib3/connectionpool.py", line 815, in urlopen
    return self.urlopen(
  File "/Users/adam/.virtualenvs/vws-python-mock/lib/python3.10/site-packages/urllib3/connectionpool.py", line 787, in urlopen
    retries = retries.increment(
  File "/Users/adam/.virtualenvs/vws-python-mock/lib/python3.10/site-packages/urllib3/util/retry.py", line 592, in increment
    raise MaxRetryError(_pool, url, error or ResponseError(cause))
urllib3.exceptions.MaxRetryError: HTTPSConnectionPool(host='cloudreco.vuforia.com', port=443): Max retries exceeded with url: /v1/query (Caused by SSLError(SSLEOFError(8, 'EOF occurred in violation of protocol (_ssl.c:2396)')))

What I have tried so far:

What I tried:

Likely irrelevant code ```python # I forget where I got this list from. cipher_list = [ "!MD5", "!aNULL", "!eNULL", "AES128-GCM-SHA256", "AES128-GCM-SHA256", "AES128-SHA", "AES128-SHA256", "AES256-GCM-SHA384", "AES256-SHA", "AES256-SHA256", "DES-CBC3-SHA", "DH+3DES", "DH+AES", "DH+AES256", "DH+AESGCM", "DH+HIGH", "DHE-RSA-AES128-SHA", "DHE-RSA-AES256-SHA", "ECDH+3DES", "ECDH+AES128", "ECDH+AES256", "ECDH+AESGCM", "ECDH+HIGH", "ECDHE-ECDSA-AES128-SHA", "ECDHE-ECDSA-AES256-SHA", "ECDHE-RSA-AES128-SHA", "RSA+3DES", "RSA+AES", "RSA+AESGCM", "RSA+HIGH", "TLS_AES_128_GCM_SHA256", "TLS_AES_256_GCM_SHA384", "TLS_CHACHA20_POLY1305_SHA256", "TLS_DHE_RSA_WITH_AES_128_CBC_SHA", "TLS_DHE_RSA_WITH_AES_128_CBC_SHA256", "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", "TLS_DHE_RSA_WITH_AES_256_CBC_SHA", "TLS_DHE_RSA_WITH_AES_256_CBC_SHA256", "TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", "TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256", "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256", "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384", "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256", "TLS_ECDHE_RSA_AES_256_CBC_SHA1", "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384", "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256", "TLS_EMPTY_RENEGOTIATION_INFO_SCSV", "TLS_RSA_WITH_AES_128_CBC_SHA", "TLS_RSA_WITH_AES_128_CBC_SHA256", "TLS_RSA_WITH_AES_128_GCM_SHA256", "TLS_RSA_WITH_AES_256_CBC_SHA", "TLS_RSA_WITH_AES_256_CBC_SHA256", "TLS_RSA_WITH_AES_256_GCM_SHA384", ] ciphers = ":".join(cipher_list) urllib3.util.ssl_.DEFAULT_CIPHERS = ciphers ```

Context

I am building a mock for the 'https://cloudreco.vuforia.com/v1/query' service. I want the mock to behave the same when a body is too large as the real service does, so that the users of the mock can handle errors correctly. In this case, a requests user for example may need to try ... except SSLError: ... with the real service, and I want them to hit this same error when using the mock. If I can fully understand what the difference is in the (handling of the) request from Python 3.9 versus Python 3.10, I hope that this will help me to make the mock more accurate, or at least document the exact scenarios in which it is not accurate.

adamtheturtle commented 2 years ago

~I'm closing this as I reproduced the error with urllib (and not urllib3) and so this isn't the place to dig.~

adamtheturtle commented 2 years ago

I'm re-opening this as I have found what I think may be a behaviour change between Python versions that should be handled differently by urllib3.

See this code with urllib:

from urllib.request import Request, urlopen

# On Python 3.9 we get a "broken pipe", but on 3.10 we get an EOF occurred error.
body = b"a" * int(2 ** 21 + 1)

req = Request('https://cloudreco.vuforia.com/v1/query', data=body)
response = urlopen(req)

The broken pipe error is swallowed on purpose:

https://github.com/urllib3/urllib3/blob/a80c248c34a77e106551bf997e726457b03f42b5/src/urllib3/connectionpool.py#L446-L450

but the SSLEOFError is not.

The PR which introduced the broken pipe error skipping: #1524

This is getting a little deep for me (RFCs) and such. However, I put an except SSLEOFError: pass in connectionpool.py and ran my original sample code, and I saw the response in 3.10 just like in 3.9. That makes me think that potentially urllib3 should make this change, to match the behaviour with the broken pipes.

XJTLUmedia commented 1 year ago

I met the same problem as you when I trying to connect yahoo finance. When I was using old version of urllib3 it seems work fine, but now I got this error urllib3.exceptions.MaxRetryError: HTTPSConnectionPool(host='finance.yahoo.com', port=443): Max retries exceeded with url: /quote/EQH (Caused by SSLError(SSLEOFError(8, 'EOF occurred in violation of protocol (_ssl.c:997)'))) hope they can look into it

LoveEatCandy commented 3 months ago

I met the same problem, but after add param chunked=True for urlopen, it fixed. You can try this param.