Make serve generic over the HTTP implementations#3484
Conversation
|
That's a big diff (even excluding |
|
This is looking very interesting. Should we move forward with this? |
|
I'd be happy to iterate on this, if you have any feedback, I can update the code or just start cleaning up the code/docs (I remember writing some, but it would probably deserve a bit more). |
|
As before, I'd really like to see the new public API extracted into a comment so we can build consensus on that separately from implementation details :) |
|
There are two new traits that users can implement: /// Types that can handle connections accepted by a [`Listener`].
pub trait ConnectionBuilder<Io, S>: Clone {
/// Take an accepted connection from the [`Listener`] (for example a `TcpStream`) and handle
/// requests on it using the provided service (usually a [`Router`](crate::Router)).
fn build_connection(&mut self, io: Io, service: S) -> impl Connection;
/// Signal to all ongoing connections that the server is shutting down.
fn graceful_shutdown(&mut self);
}
/// A connection returned by [`ConnectionBuilder`].
///
/// This type must be driven by calling [`Connection::poll_connection`].
///
/// Note that each [`Connection`] may handle multiple requests.
pub trait Connection: Send {
/// Poll the connection.
fn poll_connection(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Result<(), Box<dyn StdError + Send + Sync>>>;
}all of these methods are expected to be called by There is a new public struct
I know we historically didn't want to have There's a new The idea being people who do not care about this can keep using Technically, |
|
I'm guessing it's |
|
I didn't think of composability but yeah, I think we can do that. Just to make sure, you mean serve(listener, router).await;and this serve(listener, router).with_connection_builder(conn).await;would work? In any case, the |
e1acf1f to
7d73a9e
Compare
7d73a9e to
f1b4dee
Compare
|
I've redone it as Just note that serve(listener, router).with_connection_builder(conn).with_graceful_shtudown().await;but not serve(listener, router).with_graceful_shutdown().with_connection_builder(conn).await;We could add Another alternative would be to add a different Or we can just keep it as is but it just feels a little weird to not be consistent with how the two functions are handled (one changing just a genering on Another open question still is what to do with the public reexports of hyper/hyper-util types. We can have the base implementation without the option to tweak it in axum and have another with better control in |
Motivation
Currently, only serving with hyper with predefined configuration is supported out of the box. This runs into issues where people want to configure hyper settings (#3305, #2980, #2939). It's fairly complicated to use anything but the default hyper setup, though it is of course possible even now as shown in several low-level examples.
I see this as the natural extension of #2941. We can allow
serveto not only be generic over the way connections are accepted, but also how they're handled before aRequestis created for our router to handle (and how to handle the returnedResponse).Solution
This adds a
ConnectionBuildertrait that receives anIoandServiceand connects them to an underlying logic that handles HTTP, e.g. a hyper connection.The current implementation adds
hyper-utilas a public dependency which might not be desirable, in which case we may want to not expose the constructor withBuilder, and have a 3rd party crate likeaxum-servercreate something like this with exposed internals. That would go a bit against the issues listed in motivation, but even with that, I think it would be nicer to expose hyper-specific settings this way as well as letting others to build customConnectionBuilders without having to copy the wholeserveimplementation from here.I've also added two examples. One is TLS usage with otherwise the default hyper implementation and the other is for showcasing using something else than hyper, in this case Rama.