-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
53f15e5
commit c9f21d1
Showing
1 changed file
with
380 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,380 @@ | ||
# 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<impl | ||
Body>`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<Option<Frame<Self::Data>>>; } | ||
pub struct Frame<T>(Kind<T>); | ||
enum Kind<T> { Data(T), Trailers(HeaderMap), Unknown(Box<dyn FrameThingy>) } | ||
``` | ||
|
||
### 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 |