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.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.