How to serve HTTP and HTTPS on the same port with Hyper?

Hello,

I would like to know how you serve HTTP and HTTPS on the same port? (using Hyper 1.0.1)
[nginx for example does it]
-->
for example, we have the server listening on 0.0.0.0:8084
and I have two paths I want to serve

  • /test/hello
  • /test/hello_tls

if the request is "/test/hello" , I must not use the tls acceptor, and if the request is "/test/hello_tls" I must use the TLS acceptor.

/!\ same principle for selection by SNI (host header) instead of URL

the problem is that I have to read the TcpStream to know what the client's request is, but if I do that, I can't reuse the stream because it's already been consumed.

how can I read the request and know which path to choose before passing it to "server::conn" ?

Is hyper limited on this point because it's very high-level and I have to use a lower-level library like tokio::net instead?
But how do Hyper-based frameworks work then? (axum,warp,..)

if I use low-level coding, I have to reimplement everything (http1,http2,tls,..) and that's a lot of work. I think there's a way to do it on hyper simply, but I can't find it.

Here is a simplified piece of code where the problem needs to be solved :

let acceptor = TlsAcceptor::from(Arc::new(default_ssl_config));
let listener = TcpListener::bind(addr).await.unwrap();

    loop {
        match listener.accept().await {
            Ok((stream, _)) => {
                //--------------- [conditional zone (T_T) ] ------------------
                // TLS stream
                let stream = match acceptor.accept(stream).await {
                    Ok(s) => s,
                    Err(e) => {
                        log::error!("TLS error : {}", e);
                        continue;
                    }
                };
                //--------------- [end conditional zone] ------------------
                let io = TokioIo::new(stream);


                tokio::task::spawn(async move {
                    let conn = hyper_util::server::conn::auto::Builder::new(TokioExecutor::new());
                    if let Err(err) = conn
                        .serve_connection(
                            io,
                            service_fn(|req| {
                                response_handler(req)
                            }),
                        )
                        .await
                    {
                        log::error!("Failed to serve connection: {}", err);
                    }
                });
            }
            Err(e) => log::error!("{}", e),
        }
    }


i found this crate for axum : axum-server-dual-protocol, it must contain some good ideas. :thinking:

Thank you in advance for your advice.

You can call TcpStream::peek with a single-byte buffer. If the byte is 0x16, treat it as a HTTPS request, and otherwise treat it as a HTTP request.

6 Likes

Thanks for this suggestion, it's just what I needed to read the stream without emptying it.

The remaining challenge is to decode ClientHello (~ Handshake Protocol ) and determine which certificate to use.
I'll try to code something usable and share it later if anyone needs it.