Closed Revanee closed 8 months ago
I'm not sure why this is yielding NotConnected
, but it sounds pretty similar to https://github.com/hyperium/hyper/issues/3427 and connected issues (https://github.com/rustls/rustls/issues/1635, https://github.com/denoland/deno/issues/13058). Those were mainly for HTTP/2, but I wouldn't surprised if this same issue is happening for HTTP/1.1. I don't see tokio-rustls building NotConnected
specifically, so maybe that's coming out of hyper?
I think the underlying error comes from the tokio::net::TcpStream
, which then gets propagated by the tokio_rustls::server::TlsStream
and causes the hyper
server return the wrapped error. A similar issue seems to have been addressed in h2, but the NotConnected
error happens with HTTP/2
as well. I'm not sure if the issue should be addressed in rustls
, tokio_rustls
, or hyper
. Since this doesn't happen with tokio_native_tls
, maybe it should be addressed here?
Okay, so I think this happens because tokio-rustls tries to write outgoing data to the TcpStream
before it actually shuts it down:
https://github.com/rustls/tokio-rustls/blob/main/src/common/mod.rs#L335
tokio-native-tls doesn't do this:
https://github.com/tokio-rs/tls/blob/master/tokio-native-tls/src/lib.rs#L218 https://github.com/sfackler/rust-native-tls/blob/master/src/imp/openssl.rs#L455
Looks like this behavior was first added this behavior in 7949f4377afa19105b9ae04331b5aa54a780faef about 5 years ago. @quininer do you remember why this is necessary?
Relevant context from tokio's AsyncWrite
docs:
This shutdown method is required by implementers of the
AsyncWrite
trait. Wrappers typically just want to proxy this call through to the wrapped type, and base types will typically implement shutdown logic here or just returnOk(().into())
. Note that if you’re wrapping an underlyingAsyncWrite
a call to shutdown implies that transitively the entire stream has been shut down. After your wrapper’s shutdown logic has been executed you should shut down the underlying stream.Invocation of a shutdown implies an invocation of flush. Once this method returns
Ready
it implies that a flush successfully happened before the shutdown happened. That is, callers don’t need to call flush before calling shutdown. They can rely that by calling shutdown any pending buffered data will be written out.
NotConnected
errors from write_io()
in poll_shutdown()
.
When serving a
TlsStream<TcpStream>
using ahyper
server, the stream'spoll_shutdown
method sometimes returns aio::ErrorKind::NotConnected
. This happens when calling the server using curl withHTTP/1.1
, but not withHTTP/1.0
. Also, it happens when calling the server usingopenssl s_client
and pressingctrl+c
instead ofctrl+d
.It seems that when a client doesn't explicitly close the connection, but simply hangs up, the stream ends with an error.
This only happens with
tokio_rustls
and not withtokio_native_tls
.Here is a somewhat minimal reproduction of the error:
To reproduce using curl:
curl -k -v https://localhost:8080 --http1.0
gives no error.curl -k -v https://localhost:8080 --http1.1
gives aNotConnected
error.To reproduce using
openssl s_client
:openssl s_client -connect localhost:8080
thenCtrl+D
gives no error.openssl s_client -connect localhost:8080
thenCtrl+C
gives aNotConnected
error.