Skip to content

Supporting two-phase Listeners #3253

Open
@SabrinaJewson

Description

@SabrinaJewson
  • I have looked for existing issues (including closed) about this

Feature Request

Motivation

Some kinds of listener, like TLS listeners, will want to perform work after the connection has been accepted, but before HTTP requests can actually be served. This logic can be moved into the listener itself via complex multiplexing logic – for example, the TlsListener crate uses FuturesUnordered to do this – however I think that ideally it would be performed on the same task that serves the connection.

Proposal

Modify the Listener trait as follows:

pub trait Listener: Send + 'static {
    type Io: AsyncRead + AsyncWrite + Unpin + Send + 'static;
    type Addr: Send;
    fn accept(&mut self) -> impl Future<Output = impl Future<Output = Option<(Self::Io, Self::Addr)>> + Send + 'static> + Send;
    fn local_addr(&self) -> io::Result<Self::Addr>;
}

The outer future of accept will resolve when a connection has been accepted, while the inner future runs on the spawned task. Option<(Self::Io, Self::Addr)> is used to account for errors: if None is returned, then the task is aborted.

Alternatives

  • Use a type Error: Into<Box<dyn Error + Send + Sync>>; and Result<(Self::Io, Self::Addr), Self::Error> instead of Option. This might be more convenient for the user, as they would have to handle the errors less themselves.
    • A significant disadvantage of this is it makes composability much more difficult, as now errors have to compose. So I’m leaning toward Option.
  • Say that users should define their own “lazy TLS stream” type to handle this case. It would be an enum over the handshake future and the underlying TLS stream, and its implementation of AsyncRead/AsyncWrite would first drive the handshake to completion before performing I/O (or report EOF should the handshake fail). I can’t particularly think of practical reasons to avoid this other than “it feels weird”.
  • Say that users who want TLS in this way should not use axum::serve. That function is, however, a very convenient abstraction, taking care of graceful shutdown and constructing the server::conn::auto::Builder with the right configuration.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions