Repository: seanmonstar/reqwest (language: rust)

Issue:
Error when using a proxy with rustls for a specific url
I get errors for a request to a specifc https url through a proxy using rustls.
Requests to `https://httpbing.org/headers` are working with rustls.
Requests with openssl are working fine for all urls. 
I'm using a local `tinyproxy -d` instance but it fails with random free http proxies too.

[Full example](https://gist.github.com/nickelc/808a00b92c5f83bc6330ae7746215178) with requests using openssl and rustls for https://httpbin.org/headers and https://api.mod.io/v1/games

#### Simple Example
```rust
use reqwest::{Client, Proxy, Result};

fn main() -> Result<()> {
    env_logger::init();
    let url = "https://api.mod.io/v1/games";
    let proxy = Proxy::all("http://127.0.0.1:8888")?;
    let client = Client::builder()
        .use_default_tls()
        .proxy(proxy).build()?;

    client.get(url).send().map(|_| ())
}
```

#### Error log
```
// Hyper error kind: Parse(Version)

[2019-02-17T20:22:08Z DEBUG rustls::client::hs] Server DNS name is DNSName("api.mod.io")
[2019-02-17T20:22:08Z DEBUG rustls::verify] Unvalidated OCSP response: [...]
[2019-02-17T20:22:09Z DEBUG rustls::client::hs] Session saved
[2019-02-17T20:22:09Z DEBUG hyper::proto::h1::io] flushed 107 bytes
[2019-02-17T20:22:09Z DEBUG hyper::proto::h1::io] read 40 bytes
[2019-02-17T20:22:09Z DEBUG hyper::proto::h1::conn] parse error (invalid HTTP version specified) with 40 bytes
[2019-02-17T20:22:09Z DEBUG hyper::proto::h1::dispatch] read_head error: invalid HTTP version specified
[2019-02-17T20:22:09Z DEBUG rustls::session] Sending warning alert CloseNotify


// Hyper error kind: Close

[2019-02-17T20:58:36Z DEBUG rustls::client::hs] Server DNS name is DNSName("api.mod.io")
[2019-02-17T20:58:36Z DEBUG rustls::verify] Unvalidated OCSP response: [...]
[2019-02-17T20:58:36Z DEBUG rustls::client::hs] Session saved
[2019-02-17T20:58:36Z DEBUG hyper::proto::h1::io] read 40 bytes
[2019-02-17T20:58:36Z DEBUG hyper::proto::h1::conn] received 40 bytes on an idle connection
[2019-02-17T20:58:36Z DEBUG hyper::client] client connection error: an IO error occurred: unexpected bytes before writing message
[2019-02-17T20:58:36Z DEBUG tokio_reactor] dropping I/O source: 1
```


Discussion / hints:
Hm, do you happen to know what those 40 bytes are that are sent by the proxy?
No, what's the best way to get them?
You could try using something like Wireshark.
I patched hyper to print `Buffered::read_buf`

```
[2019-02-21T14:06:36Z TRACE hyper::proto::h1::conn] read_keep_alive check: ok, nothing to read yet
[2019-02-21T14:06:36Z TRACE hyper::proto::h1::role] Client::encode method=GET, body=None
[2019-02-21T14:06:36Z TRACE hyper::proto::h1::io] detected no usage of vectored write, flattening
[2019-02-21T14:06:36Z DEBUG hyper::proto::h1::io] flushed 107 bytes
[2019-02-21T14:06:36Z TRACE hyper::proto::h1::conn] flushed({role=client}): State { reading: Init, writing: KeepAlive, keep_alive: Busy }
[2019-02-21T14:06:36Z TRACE tokio_reactor] event Readable | Writable Token(0)
[2019-02-21T14:06:36Z TRACE tokio_reactor] loop process - 1 events, 0.000s
[2019-02-21T14:06:36Z TRACE hyper::proto::h1::conn] Conn::read_head
[2019-02-21T14:06:36Z DEBUG hyper::proto::h1::io] read 40 bytes
[2019-02-21T14:06:36Z DEBUG hyper::proto::h1::io] b"\0\0\x12\x04\0\0\0\0\0\0\x03\0\0\0\x80\0\x04\0\x01\0\0\0\x05\0\xff\xff\xff\0\0\x04\x08\0\0\0\0\0\x7f\xff\0\0"
[2019-02-21T14:06:36Z TRACE hyper::proto::h1::role] Response.parse([Header; 100], [u8; 40])
[2019-02-21T14:06:36Z TRACE hyper::proto::h1::conn] State::close_read()
[2019-02-21T14:06:36Z DEBUG hyper::proto::h1::conn] parse error (invalid HTTP version specified) with 40 bytes
[2019-02-21T14:06:36Z DEBUG hyper::proto::h1::dispatch] read_head error: invalid HTTP version specified
[2019-02-21T14:06:36Z TRACE hyper::proto::h1::conn] State::close()
```
```
[2019-02-21T14:09:05Z TRACE hyper::client::conn] client handshake HTTP/1
[2019-02-21T14:09:05Z TRACE hyper::client] handshake complete, spawning background dispatcher task
[2019-02-21T14:09:05Z TRACE tokio_reactor] event Readable Token(4194303)
[2019-02-21T14:09:05Z TRACE tokio_reactor] loop process - 1 events, 0.000s
[2019-02-21T14:09:05Z DEBUG hyper::proto::h1::io] read 40 bytes
[2019-02-21T14:09:05Z DEBUG hyper::proto::h1::io] b"\0\0\x12\x04\0\0\0\0\0\0\x03\0\0\0\x80\0\x04\0\x01\0\0\0\x05\0\xff\xff\xff\0\0\x04\x08\0\0\0\0\0\x7f\xff\0\0"
[2019-02-21T14:09:05Z DEBUG hyper::proto::h1::conn] received 40 bytes on an idle connection
[2019-02-21T14:09:05Z TRACE want] signal: Want
[2019-02-21T14:09:05Z TRACE want] signal found waiting giver, notifying
[2019-02-21T14:09:05Z DEBUG hyper::client] client connection error: an IO error occurred: unexpected bytes before writing message
```
it's always the same bytes.
I have no idea what those bytes are for, or why you only see them in rustls.

cc @quininer do you have any idea?
I was looking for other sites that trigger the error.
```
[2019-02-21T23:52:42Z DEBUG rustls::client::hs] Server DNS name is DNSName("www.wolfssl.com")
[2019-02-21T23:52:42Z DEBUG rustls::verify] Unvalidated OCSP response: [...]
[2019-02-21T23:52:42Z DEBUG rustls::client::hs] Session saved
[2019-02-21T23:52:42Z DEBUG hyper::proto::h1::io] flushed 135 bytes
[2019-02-21T23:52:42Z DEBUG rustls::session] Sending warning alert CloseNotify
[2019-02-21T23:52:42Z DEBUG hyper::proto::h1::io] read 0 bytes
[2019-02-21T23:52:42Z DEBUG hyper::proto::h1::io] b""
[2019-02-21T23:52:42Z DEBUG hyper::proto::h1::conn] parse error (parsed HTTP message from remote is incomplete) with 0 bytes
[2019-02-21T23:52:42Z DEBUG hyper::proto::h1::dispatch] read_head error: parsed HTTP message from remote is incomplete
```
I think this is because the server uses `h2` but the client did not. `default-tls` backend does not support alpn, so it is not affected.

Fix patch:
diff --git a/src/connect.rs b/src/connect.rs
index e6ce46834..c707fb012 100644
--- a/src/connect.rs
+++ b/src/connect.rs
@@ -91,11 +91,20 @@ impl Connector {
         let mut http = http_connector()?;
         http.set_local_address(local_addr.into());
         http.enforce_http(false);
-        let http = ::hyper_rustls::HttpsConnector::from((http, tls.clone()));
+
+        let inner = if proxies.is_empty() {
+            let tls = Arc::new(tls);
+            let http = ::hyper_rustls::HttpsConnector::from((http, tls.clone()));
+            Inner::RustlsTls(http, tls)
+        } else {
+            let mut tls_proxy = tls.clone();
+            tls_proxy.alpn_protocols.clear();
+            let http = ::hyper_rustls::HttpsConnector::from((http, tls_proxy));
+            Inner::RustlsTls(http, Arc::new(tls))
+        };
 
         Ok(Connector {
-            inner: Inner::RustlsTls(http, Arc::new(tls)),
-            proxies,
+            inner, proxies,
             timeout: None,
         })
     }
@@ -180,7 +189,6 @@ impl Connect for Connector {
                 match &self.inner {
                     #[cfg(feature = "default-tls")]
                     Inner::DefaultTls(http, tls) => if dst.scheme() == "https" {
-                        #[cfg(feature = "default-tls")]
                         use self::native_tls_async::TlsConnectorExt;
 
                         let host = dst.host().to_owned();
@@ -198,9 +206,8 @@ impl Connect for Connector {
                     },
                     #[cfg(feature = "rustls-tls")]
                     Inner::RustlsTls(http, tls) => if dst.scheme() == "https" {
-                        #[cfg(feature = "rustls-tls")]
+                        use rustls::Session;
                         use tokio_rustls::TlsConnector as RustlsConnector;
-                        #[cfg(feature = "rustls-tls")]
                         use tokio_rustls::webpki::DNSNameRef;
 
                         let host = dst.host().to_owned();
@@ -217,7 +224,14 @@ impl Connect for Connector {
                                     RustlsConnector::from(tls).connect(dnsname.as_ref(), tunneled)
                                         .map_err(|e| io::Error::new(io::ErrorKind::Other, e))
                                 })
-                                .map(|io| (Box::new(io) as Conn, connected.proxy(true)))
+                                .map(|io| {
+                                    let connected = if io.get_ref().1.get_alpn_protocol() == Some(b"h2") {
+                                        connected.negotiated_h2()
+                                    } else {
+                                        connected
+                                    };
+                                    (Box::new(io) as Conn, connected.proxy(true))
+                                })
                         }));
                     },
                     #[cfg(not(feature = "tls"))]


Test patch: