From ff8bad4a7a4549a576003caed30b4297f8bc616b Mon Sep 17 00:00:00 2001 From: Sean McArthur Date: Wed, 6 Apr 2022 08:54:19 -0700 Subject: [PATCH] docs(lib): propose 1.0 roadmap --- docs/ROADMAP.md | 382 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 382 insertions(+) create mode 100644 docs/ROADMAP.md diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md new file mode 100644 index 0000000000..adf460f98a --- /dev/null +++ b/docs/ROADMAP.md @@ -0,0 +1,382 @@ +# hyper 1.0 Roadmap + +## Goal + +Align current hyper to the [hyper VISION][VISION]. + +The VISION outlines a decision-making framework, use-cases, and general shape +of hyper. This roadmap describes the currently known problems with hyper, and +then shows what changes are needed to make hyper 1.0 look more like what is in +the VISION. + +## Known Issues + + +> **Note**: These known issues are as of hyper v0.14.x. After v1.0 is released, +ideally these issues will have been solved. Keeping this history may be helpful +to Future Us, though. + +### Higher-level Client and Server problems + +Both the higher-level `Client` and `Server` types have stability concerns. + +For the `hyper::Server`: + +- The `Accept` trait is complex, and too easy to get wrong. If used with TLS, a +slow TLS handshake can affect all other new connections waiting for it to +finish. - The `MakeService<&IO>` is confusing. The bounds are an assault on +the eyes. - The `MakeService` API doesn't allow to easily annotate the HTTP +connection with `tracing`. - Graceful shutdown doesn't give enough control. + + +It's more common for people to simply use `hyper::server::conn` at this point, +than to bother with the `hyper::Server`. + +While the `hyper::Client` is much easier to use, problems still exist: + +- The whole `Connect` design isn't stable. + - ALPN and proxies can provide surprising extra configuration of connections. + - Some `Connect` implementations may wish to view the path, in addition to the scheme, host, and port. + - Wants `runtime` feature +- The Pool could be made more general or composable. At the same time, more customization is + desired, and it's not clear +how to expose it yet. + + +### Runtime woes + +hyper has been able to support different runtimes, but it has sometimes awkward +default support for Tokio. + +- The `runtime` cargo-feature isn't additive +- Built-in Tokio support can be confusing +- Executors and Timers + - The `runtime` feature currently enables a few options that require a timer, such as timeouts and + keepalive intervals. It implicitly relies on Tokio's timer context. This can be quite confusing. +- IO traits + - Should we publicly depend on Tokio's traits? + - `futures-io`? + - Definitely nope. + - Not stable. (0.3?) + - No uninitialized memory. + - Eventual `std` traits? + - They've been in design for years. + - We cannot base our schedule on them. + - When they are stable, we can: + - Provide a bridge in `hyper-util`. + - Consider a 2.0 of hyper. + - Define our own traits, provide util wrappers? + +### Forwards-compatibility + +There's a concern about forwards-compatibility. We want to be able to add +support for new HTTP features without needing a new major version. While most +of `http` and `hyper` are prepared for that, there's two potential problems. + +- New frames on an HTTP stream (body) + - Receiving a new frame type would require a new trait method + - There's no way to implement a "receive unknown frame" that hyper doesn't know about. + - Sending an unknown frame type would be even harder. + - Besides being able to pass an "unknown" type through the trait, the user would need to be + able to describe how that frame is encoded in HTTP/2/3. +- New HTTP versions + - HTTP/3 will require a new transport abstraction. It's not as simple as just using some + `impl AsyncRead + AsyncWrite`. While HTTP/2 bundled the concept of stream creation internally, + and thus could be managed wholy on top of a read-write transport, HTTP/3 is different. Stream + creation is shifted to the QUIC protocol, and HTTP/3 needs to be able to use that directly. + - This means the existing `Connection` types for both client and server will not be able to + accept a QUIC transport so we can add HTTP/3 support. + +### Errors + +It's not easy to match for specific errors. + +The `Error::source()` can leak an internal dependency. For example, a +`hyper::Error` may wrap an `h2::Error`. Users can downcast the source at +runtime, and hyper internally changing the version of its `h2` dependency can +cause runtime breakage for users. + +Formatting errors is in conflict with the current expected norm. The +`fmt::Display` implementation for `hyper::Error` currently prints its own +message, and then prints the message of any wrapped source error. The Errors +Working Group currently recommends that errors only print their own message +(link?). This conflict means that error "reporters", which crawl a source chain +and print each error, has a lot of duplicated information. + +``` +error fetching website: error trying to connect: tcp connect error: Connection refused (os error 61) +tcp connect error: Connection refused (os error 61) +Connection refused (os error 61) +``` + +While there is a good reason for why hyper's `Error` types do this, at the very +least, it _is_ unfortunate. + +### You call hyper, or hyper calls you? + +> Note: this problem space, of who calls whom, will be explored more deeply in +> a future article. + +At times, it's been wondered whether hyper should call user code, or if user +code should call hyper. For instance, should a `Service` be called with a +request when the connection receives one, or should the user always poll for +the next request. + +There's a similar question around sending a message body. Should hyper ask the +body for more data to write, or should the user call a `write` method directly? + +These both get at a root topic about [write +observability](https://github.com/hyperium/hyper/issues/2181). How do you know +when a response, or when body data, has been written successfully? This is +desirable for metrics, or for triggering other side-effects. + +The `Service` trait also has some other frequently mentioned issues. Does +`poll_ready` pull its complexity weight for servers? What about returning +errors, what does that mean? Ideally users would turn all errors into +appropriate `http::Response`s. But in HTTP/2 and beyond, stream errors are +different from HTTP Server Error responses. Could the `Service::Error` type do +more to encourage best practices? + +## Design + +The goal is to get hyper closer to the [VISION][], using that to determine the +best way to solve the known issues above. The main thrust of the proposed +changes are to make hyper more **Flexible** and stable. + +In order to keep hyper **Understandable**, however, the proposed changes *must* +be accompanied by providing utilities that solve the common usage patterns, +documentation explaining how to use the more flexible pieces, and guides on how +to reach for the `hyper-util`ity belt. + +The majority of the changes are smaller and can be contained to the *Public +API* section, since they usually only apply to a single module or type. But the +biggest changes are explained in detail here. + +### Split per HTTP version + +The existing `Connection` types, both for the client and server, abstract over +HTTP version by requiring a generic `AsyncRead + AsyncWrite` transport type. +But as we figure out HTTP/3, that needs to change. So to prepare now, the +`Connection` types will be split up. + +For example, there will now be `hyper::server::conn::http1::Connection` and +`hyper::server::conn::http2::Connection` types. + +These specific types will still have a very similar looking API that, as the +VISION describes, provides **Correct** connection management as it pertains to +HTTP. + +There will be still be a type to wrap the different versions. It will no longer +be generic over the transport type, to prepare for being able to wrap HTTP/3 +connections. Exactly how it will wrap, either by using internal trait objects, +or an `enum Either` style, or using a `trait Connection` that each type +implements, is something to be determined. It's likely that this "auto" type +will start in `hyper-util`. + +### Focus on the `Connection` level + +As mentioned in the *Known Issues*, the higher-level `Client` and `Server` have +stability and complexity problems. Therefore, for hyper 1.0, the main API will +focus on the "lower-level" connection types. The `Client` and `Server` helpers +will be moved to `hyper-util`. + +## Public API + +### body + +The `Body` struct is removed. Its internal "variants" are [separated into +distinct types](https://github.com/hyperium/hyper/issues/2345), and can start +in either `hyper-utils` or `http-body-utils`. + +The exported trait `HttpBody` is renamed to `Body`. + +A single `Body` implementation in `hyper` is the one provided by receiving +client responses and server requests. It has the name `Streaming`. + +> **Unresolved**: Other names can be considered during implementation. Another +> option is to not publicly name the implementation, but return `Response`s. + +The `Body` trait will be experimented on to see about making it possible to +return more frame types beyonds just data and trailers. + +> **Unresolved**: What exactly this looks like will only be known after +> experimentation. + +### client + +The high-level `hyper::Client` will be removed, along with the +`hyper::client::connect` module. They will be explored more in `hyper-util`. + +As described in *Design*, the `client::conn` module will gain `http1` and +`http2` sub-modules, providing per-version `SendRequest`, `Connection`, and +`Builder` structs. An `auto` version can be explored in `hyper-util`. + +### error + +The `hyper::Error` struct remains in place. + +All errors returned from `Error::source()` are made opaque. They are wrapped an +internal `Opaque` newtype that still allows printing, but prevents downcasting +to the internal dependency. + +A new `hyper::error::Code` struct is defined. It is an opaque struct, with +associated constants defining various code variants. + +> Alternative: define a non-exhaustive enum. It's not clear that this is +> definitely better, though. Keeping it an opaque struct means we can add +> secondary parts to the code in the future, or add bit flags, or similar +> extensions. + +The purpose of `Code` is to provide an abstraction over the kind of error that +is encountered. The `Code` could be some behavior noticed inside hyper, such as +an incomplete HTTP message. Or it can be "translated" from the underlying +protocol, if it defines protocol level errors. For example, an +`h2::Reason::CANCEL`. + +### rt + +The `Executor` trait stays in here. + +Define a new trait `Timer`, which describes a way for users to provide a source +of sleeping/timeout futures. Similar to `Executor`, a new generic is added to +connection builders to provide a `Timer`. + +### server + +The higher-level `hyper::Server` struct, its related `Builder`, and the +`Accept` trait are all removed. + +The `AddrStream` struct will be completely removed, as it provides no value but +causes binary bloat. + +Similar to `client`, and as describe in the *Design*, the `conn` modules will +be expanded to support `http1` and `http2` submodules. An `auto` version can be +explored in `hyper-util`. + +### service + +A vendored and simplified `Service` trait will be explored. + +The error type for `Service`s used for a server will explore having the return +type changed from any error to one that can become a `hyper::error::Code`. + +> **Unresolved**: Both of the above points are not set in stone. We will +> explore and decide if they are the best outcome during development. + +The `MakeService` pieces will be removed. + +### Cargo Features + +Remove the `stream` feature. The `Stream` trait is not stable, and we cannot +depend on an unstable API. + +Remove the `tcp` and `runtime` features. The automatic executor and timer parts +are handled by providing implementations of `Executor` and `Timer`. The +`connect` and `Accept` parts are also moving to `hyper-util`. + +### Public Dependencies + +- `http` - `http-body`? - `bytes` - `tower-service`? + +Cannot be public while "unstable": + +- `tracing` + +## `hyper-util` + + +### body + +A channel implementation of `Body` that has an API to know when the data has +been successfully written is provided in `hyper_util::body::channel`. + +### client + +A `Pool` struct that implements `Service` is provided. It fills a similar role +as the previous `hyper::Client`. + +> **Note**: The `Pool` might be something that goes into the `tower` crate +> instead. Or it might stay here as a slightly more specialized racing-connect +> pool. We'll find out as we go. + +A `connect` submodule that mostly mirrors the existing `hyper::client::connect` +module is moved here. Connectors can be used as a source to provide `Service`s +used by the `Pool`. + +### rt + +We can provide Tokio-backed implementations of `Executor` and `Timer`. + +### server + +A `GracefulShutdown` helper is provided, to allow for similar style of graceful +shutdown as the previous `hyper::Server` did, but with better control. + +# Appendix + +## Unresolved Questions + +There are some parts of the proposal which are not fully resolved. They are +mentioned in Design and API sections above, but also collected here for easy +finding. While they all have _plans_, they are more exploratory parts of the +API, and thus they have a higher possibility of changing as we implement them. + +The goal is to have these questions resolved and removed from the document by +the time there is a [Release Candidate][timeline]. + +### Should there be `hyper::io` traits? + +### Should returned body types be `impl Body`? + +### How could the `Body` trait prepare for unknown frames? + +We will experiment with this, and keep track of those experiments in a +dedicated issue. It might be possible to use something like this: + +```rust pub trait Body { type Data; fn poll_frame(..) -> +Result>>; } + +pub struct Frame(Kind); + +enum Kind { Data(T), Trailers(HeaderMap), Unknown(Box) } +``` + +### Should there be a simplified `hyper::Service` trait, or should hyper depend on `tower-service`? + +## FAQ + +### Why did you pick _that_ name? Why not this other better name? + +Naming is hard. We certainly should solve it, but discussion for particular +names for structs and traits should be scoped to the specific issues. This +document is to define the shape of the library API. + +### Should I publicly depend on `hyper-util`? + +The `hyper-util` crate will not reach 1.0 when `hyper` does. Some types and +traits are being moved to `hyper-util`. As with any pre-1.0 crate, you _can_ +publicly depend on it, but it is explicitly less stable. + +In most cases, it's recommended to not publicly expose your dependency on +`hyper-util`. If you depend on a trait, such as used by the moved higher-level +`Client` or `Server`, it may be better for your users to define your own +abstraction, and then make an internal adapter. + +### Isn't this making hyper harder? + +We are making hyper more **flexible**. As noted in the [VISION][], most use +cases of hyper require it to be flexible. That _can_ mean that the exposed API +is lower level, and that it feels more complicated. It should still be +**understandable**. + +But the hyper 1.0 effort is more than just the single `hyper` crate. Many +useful helpers will be migrated to a `hyper-util` crate, and likely improved in +the process. The [timeline][] also points out that we will have a significant +documentation push. While the flexible pieces will be in hyper to compose how +they need, we will also write guides for the [hyper.rs][] showing people how to +accomplish the most common tasks. + +[timeline]: https://seanmonstar.com/post/676912131372875776/hyper-10-timeline +[VISION]: https://github.com/hyperium/hyper/pull/2772 +[hyper.rs]: https://hyper.rs