<p dir="auto">Not sure whether this is actually a <code class="notranslate">requests</code> bug, but trying here first as it's here it manifests.</p>
<p dir="auto">I'm using a http proxy for https traffic, and it works fine for normal GET, POST and PUT requests using <code class="notranslate">requests</code>. However, if enabled chunked uploads, it fails with <code class="notranslate">requests.exceptions.ConnectionError: [SSL: WRONG_VERSION_NUMBER] wrong version number (_ssl.c:1131)</code></p>
<p dir="auto">From googling it appears to be trying to access the http proxy using https. I'm fully aware of the normal "problem" where people confuse the protocols and use "https://" for their http proxy, but as can be seen in the example below this is not the problem in this case.</p>
<p dir="auto">I've been digging into <code class="notranslate">adapters.py</code> and the <code class="notranslate">low_conn</code> connection it uses <em>appears</em> to have the correct proxy set (with "http" as protocol), but this is outside my expertise.</p>
<h2 dir="auto">Expected Result</h2>
<p dir="auto">Request to go through proxy like for non-chunked uploads without errors.</p>
<h2 dir="auto">Actual Result</h2>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="Traceback (most recent call last):
  File &quot;/usr/local/lib/python3.8/dist-packages/requests/adapters.py&quot;, line 521, in send
    low_conn.endheaders()
  File &quot;/usr/lib/python3.8/http/client.py&quot;, line 1251, in endheaders
    self._send_output(message_body, encode_chunked=encode_chunked)
  File &quot;/usr/lib/python3.8/http/client.py&quot;, line 1011, in _send_output
    self.send(msg)
  File &quot;/usr/lib/python3.8/http/client.py&quot;, line 951, in send
    self.connect()
  File &quot;/usr/local/lib/python3.8/dist-packages/urllib3/connection.py&quot;, line 414, in connect
    self.sock = ssl_wrap_socket(
  File &quot;/usr/local/lib/python3.8/dist-packages/urllib3/util/ssl_.py&quot;, line 449, in ssl_wrap_socket
    ssl_sock = _ssl_wrap_socket_impl(
  File &quot;/usr/local/lib/python3.8/dist-packages/urllib3/util/ssl_.py&quot;, line 493, in _ssl_wrap_socket_impl
    return ssl_context.wrap_socket(sock, server_hostname=server_hostname)
  File &quot;/usr/lib/python3.8/ssl.py&quot;, line 500, in wrap_socket
    return self.sslsocket_class._create(
  File &quot;/usr/lib/python3.8/ssl.py&quot;, line 1040, in _create
    self.do_handshake()
  File &quot;/usr/lib/python3.8/ssl.py&quot;, line 1309, in do_handshake
    self._sslobj.do_handshake()
ssl.SSLError: [SSL: WRONG_VERSION_NUMBER] wrong version number (_ssl.c:1131)"><pre class="notranslate"><code class="notranslate">Traceback (most recent call last):
  File "/usr/local/lib/python3.8/dist-packages/requests/adapters.py", line 521, in send
    low_conn.endheaders()
  File "/usr/lib/python3.8/http/client.py", line 1251, in endheaders
    self._send_output(message_body, encode_chunked=encode_chunked)
  File "/usr/lib/python3.8/http/client.py", line 1011, in _send_output
    self.send(msg)
  File "/usr/lib/python3.8/http/client.py", line 951, in send
    self.connect()
  File "/usr/local/lib/python3.8/dist-packages/urllib3/connection.py", line 414, in connect
    self.sock = ssl_wrap_socket(
  File "/usr/local/lib/python3.8/dist-packages/urllib3/util/ssl_.py", line 449, in ssl_wrap_socket
    ssl_sock = _ssl_wrap_socket_impl(
  File "/usr/local/lib/python3.8/dist-packages/urllib3/util/ssl_.py", line 493, in _ssl_wrap_socket_impl
    return ssl_context.wrap_socket(sock, server_hostname=server_hostname)
  File "/usr/lib/python3.8/ssl.py", line 500, in wrap_socket
    return self.sslsocket_class._create(
  File "/usr/lib/python3.8/ssl.py", line 1040, in _create
    self.do_handshake()
  File "/usr/lib/python3.8/ssl.py", line 1309, in do_handshake
    self._sslobj.do_handshake()
ssl.SSLError: [SSL: WRONG_VERSION_NUMBER] wrong version number (_ssl.c:1131)
</code></pre></div>
<h2 dir="auto">Reproduction Steps</h2>
<p dir="auto">Run script below with and without proxy. Without proxy it works fine, with http proxy it gives above error.</p>
<div class="highlight highlight-source-python notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="import requests

with open('test', 'w') as f:
    f.write('test')

with open('test', 'rb') as f:
    resp = requests.put('https://www.google.com', data=f)
    assert resp.status_code == 405

def x():
    yield &quot;test&quot;.encode('utf-8')

resp = requests.put('https://www.google.com', data=x())
assert resp.status_code == 405"><pre class="notranslate"><span class="pl-k">import</span> <span class="pl-s1">requests</span>

<span class="pl-k">with</span> <span class="pl-en">open</span>(<span class="pl-s">'test'</span>, <span class="pl-s">'w'</span>) <span class="pl-k">as</span> <span class="pl-s1">f</span>:
    <span class="pl-s1">f</span>.<span class="pl-en">write</span>(<span class="pl-s">'test'</span>)

<span class="pl-k">with</span> <span class="pl-en">open</span>(<span class="pl-s">'test'</span>, <span class="pl-s">'rb'</span>) <span class="pl-k">as</span> <span class="pl-s1">f</span>:
    <span class="pl-s1">resp</span> <span class="pl-c1">=</span> <span class="pl-s1">requests</span>.<span class="pl-en">put</span>(<span class="pl-s">'https://www.google.com'</span>, <span class="pl-s1">data</span><span class="pl-c1">=</span><span class="pl-s1">f</span>)
    <span class="pl-k">assert</span> <span class="pl-s1">resp</span>.<span class="pl-s1">status_code</span> <span class="pl-c1">==</span> <span class="pl-c1">405</span>

<span class="pl-k">def</span> <span class="pl-en">x</span>():
    <span class="pl-k">yield</span> <span class="pl-s">"test"</span>.<span class="pl-en">encode</span>(<span class="pl-s">'utf-8'</span>)

<span class="pl-s1">resp</span> <span class="pl-c1">=</span> <span class="pl-s1">requests</span>.<span class="pl-en">put</span>(<span class="pl-s">'https://www.google.com'</span>, <span class="pl-s1">data</span><span class="pl-c1">=</span><span class="pl-en">x</span>())
<span class="pl-k">assert</span> <span class="pl-s1">resp</span>.<span class="pl-s1">status_code</span> <span class="pl-c1">==</span> <span class="pl-c1">405</span></pre></div>
<h2 dir="auto">System Information</h2>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="$ python -m requests.help"><pre class="notranslate"><code class="notranslate">$ python -m requests.help
</code></pre></div>
<div class="highlight highlight-source-json notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="{
  &quot;chardet&quot;: {
    &quot;version&quot;: null
  },
  &quot;charset_normalizer&quot;: {
    &quot;version&quot;: &quot;3.0.1&quot;
  },
  &quot;cryptography&quot;: {
    &quot;version&quot;: &quot;&quot;
  },
  &quot;idna&quot;: {
    &quot;version&quot;: &quot;3.4&quot;
  },
  &quot;implementation&quot;: {
    &quot;name&quot;: &quot;CPython&quot;,
    &quot;version&quot;: &quot;3.8.10&quot;
  },
  &quot;platform&quot;: {
    &quot;release&quot;: &quot;4.18.0-425.10.1.el8_7.x86_64&quot;,
    &quot;system&quot;: &quot;Linux&quot;
  },
  &quot;pyOpenSSL&quot;: {
    &quot;openssl_version&quot;: &quot;&quot;,
    &quot;version&quot;: null
  },
  &quot;requests&quot;: {
    &quot;version&quot;: &quot;2.28.2&quot;
  },
  &quot;system_ssl&quot;: {
    &quot;version&quot;: &quot;1010106f&quot;
  },
  &quot;urllib3&quot;: {
    &quot;version&quot;: &quot;1.26.14&quot;
  },
  &quot;using_charset_normalizer&quot;: true,
  &quot;using_pyopenssl&quot;: false
}"><pre class="notranslate">{
  <span class="pl-ent">"chardet"</span>: {
    <span class="pl-ent">"version"</span>: <span class="pl-c1">null</span>
  },
  <span class="pl-ent">"charset_normalizer"</span>: {
    <span class="pl-ent">"version"</span>: <span class="pl-s"><span class="pl-pds">"</span>3.0.1<span class="pl-pds">"</span></span>
  },
  <span class="pl-ent">"cryptography"</span>: {
    <span class="pl-ent">"version"</span>: <span class="pl-s"><span class="pl-pds">"</span><span class="pl-pds">"</span></span>
  },
  <span class="pl-ent">"idna"</span>: {
    <span class="pl-ent">"version"</span>: <span class="pl-s"><span class="pl-pds">"</span>3.4<span class="pl-pds">"</span></span>
  },
  <span class="pl-ent">"implementation"</span>: {
    <span class="pl-ent">"name"</span>: <span class="pl-s"><span class="pl-pds">"</span>CPython<span class="pl-pds">"</span></span>,
    <span class="pl-ent">"version"</span>: <span class="pl-s"><span class="pl-pds">"</span>3.8.10<span class="pl-pds">"</span></span>
  },
  <span class="pl-ent">"platform"</span>: {
    <span class="pl-ent">"release"</span>: <span class="pl-s"><span class="pl-pds">"</span>4.18.0-425.10.1.el8_7.x86_64<span class="pl-pds">"</span></span>,
    <span class="pl-ent">"system"</span>: <span class="pl-s"><span class="pl-pds">"</span>Linux<span class="pl-pds">"</span></span>
  },
  <span class="pl-ent">"pyOpenSSL"</span>: {
    <span class="pl-ent">"openssl_version"</span>: <span class="pl-s"><span class="pl-pds">"</span><span class="pl-pds">"</span></span>,
    <span class="pl-ent">"version"</span>: <span class="pl-c1">null</span>
  },
  <span class="pl-ent">"requests"</span>: {
    <span class="pl-ent">"version"</span>: <span class="pl-s"><span class="pl-pds">"</span>2.28.2<span class="pl-pds">"</span></span>
  },
  <span class="pl-ent">"system_ssl"</span>: {
    <span class="pl-ent">"version"</span>: <span class="pl-s"><span class="pl-pds">"</span>1010106f<span class="pl-pds">"</span></span>
  },
  <span class="pl-ent">"urllib3"</span>: {
    <span class="pl-ent">"version"</span>: <span class="pl-s"><span class="pl-pds">"</span>1.26.14<span class="pl-pds">"</span></span>
  },
  <span class="pl-ent">"using_charset_normalizer"</span>: <span class="pl-c1">true</span>,
  <span class="pl-ent">"using_pyopenssl"</span>: <span class="pl-c1">false</span>
}</pre></div>
<p dir="auto">I am using the chunked request body feature (<code class="notranslate">data=generator</code>). When defining a HTTP proxy and sending a request to a HTTPS URL, <code class="notranslate">requests</code> is trying to perform a TLS handshake with the (HTTP) proxy server, instead of sending a CONNECT request and performing the handshake with the target server through the tunnel. This behaviour prevents the request from completing successfully.</p>
<h2 dir="auto">Expected Result</h2>
<p dir="auto"><code class="notranslate">requests</code> should send a CONNECT request to the proxy and negotiate TLS with the target server through the tunnel.</p>
<h2 dir="auto">Actual Result</h2>
<p dir="auto"><code class="notranslate">requests</code> tries to negotiate TLS directly with the HTTP proxy server, and fails to do so.</p>
<h2 dir="auto">Reproduction Steps</h2>
<p dir="auto">Run an HTTP proxy on <code class="notranslate">127.0.0.1:8080</code> and execute the following code:</p>
<div class="highlight highlight-source-python notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="  import requests
  
  def body_generator():
    yield &quot;test&quot;
    return
  
  requests.post(&quot;https://www.google.com/&quot;,
    proxies={&quot;https&quot;: &quot;http://127.0.0.1:8080/&quot;},
    data=body_generator(),
  )"><pre class="notranslate">  <span class="pl-k">import</span> <span class="pl-s1">requests</span>
  
  <span class="pl-k">def</span> <span class="pl-en">body_generator</span>():
    <span class="pl-k">yield</span> <span class="pl-s">"test"</span>
    <span class="pl-k">return</span>
  
  <span class="pl-s1">requests</span>.<span class="pl-en">post</span>(<span class="pl-s">"https://www.google.com/"</span>,
    <span class="pl-s1">proxies</span><span class="pl-c1">=</span>{<span class="pl-s">"https"</span>: <span class="pl-s">"http://127.0.0.1:8080/"</span>},
    <span class="pl-s1">data</span><span class="pl-c1">=</span><span class="pl-en">body_generator</span>(),
  )</pre></div>
<h2 dir="auto">More info</h2>
<p dir="auto">The chunked encoding generator feature is implemented in <code class="notranslate">requests/adapters.py</code>:</p>
<div class="highlight highlight-source-python notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="try:
    if not chunked:
        resp = conn.urlopen(
            ...
        )

    # Send the request.
    else:
        if hasattr(conn, &quot;proxy_pool&quot;):
            conn = conn.proxy_pool

        low_conn = conn._get_conn(timeout=DEFAULT_POOL_TIMEOUT)

        try:
            ... Send request with chunked body ..."><pre class="notranslate"><span class="pl-k">try</span>:
    <span class="pl-k">if</span> <span class="pl-c1">not</span> <span class="pl-s1">chunked</span>:
        <span class="pl-s1">resp</span> <span class="pl-c1">=</span> <span class="pl-s1">conn</span>.<span class="pl-en">urlopen</span>(
            ...
        )

    <span class="pl-c"># Send the request.</span>
    <span class="pl-k">else</span>:
        <span class="pl-k">if</span> <span class="pl-en">hasattr</span>(<span class="pl-s1">conn</span>, <span class="pl-s">"proxy_pool"</span>):
            <span class="pl-s1">conn</span> <span class="pl-c1">=</span> <span class="pl-s1">conn</span>.<span class="pl-s1">proxy_pool</span>

        <span class="pl-s1">low_conn</span> <span class="pl-c1">=</span> <span class="pl-s1">conn</span>.<span class="pl-en">_get_conn</span>(<span class="pl-s1">timeout</span><span class="pl-c1">=</span><span class="pl-v">DEFAULT_POOL_TIMEOUT</span>)

        <span class="pl-k">try</span>:
            ... <span class="pl-v">Send</span> <span class="pl-s1">request</span> <span class="pl-k">with</span> <span class="pl-s1">chunked</span> <span class="pl-s1">body</span> ...</pre></div>
<p dir="auto">In the case of chunked encoding, instead of using <code class="notranslate">conn.urlopen</code> to send the request, it uses <code class="notranslate">conn._get_conn</code> and sends the request manually.<br>
It seems like, for some reason, <code class="notranslate">conn._get_conn</code> is not handling the proxy correctly.</p>