hyper Vision

Purpose

This is an overview of what the shape of hyper looks like, but also somewhat zoomed out, so that the vision can survive while the exact minute details might shift and change over time.

Charter

hyper is a protective and efficient HTTP library for all.

Tenets

Tenets are guiding principles. They guide how decisions are made for the whole project. Ideally, we do all of them all the time. In some cases, though, we may be forced to decide between slightly penalizing one goal or another. In that case, we tend to support those goals that come earlier in the list over those that come later (but every case is different).

  1. Open
  2. Correct
  3. Fast
  4. HTTP/*
  5. Flexible
  6. Understandable

There’s a lot more detail about each in tenets.

Use Cases

Who are the users of hyper? How would they use hyper?

Low-Level Client Library (curl, reqwest, aws-sdk)

These client libraries care that hyper is Flexible, since they are expressing their own opinion on how a more-featured HTTP client should act. This includes opinions on connection establishment, management, pooling, HTTP version options, and even runtimes.

curl’s main reason for using hyper is that it is Safe.

Web Server Frameworks (deno, axum)

These are using hyper’s server feature to expose a different, higher-level API to users. Besides the obvious requirements, these require that hyper is Fast. Servers are costly, handling more requests faster is important to them.

That hyper is Flexible is also important, in that it needs to be flexible enough for them to build a server framework, and allow them to express their own opinions about API to their users.

Services and Proxies (linkerd, cloudflare, fastly)

These are using hyper directly, likely both the client and server, in order to build efficient and powerful services, applications, and tools for their end users. They care greatly that hyper is Correct, since web traffic can stretch the limits of what is valid HTTP, and exercise less-common parts of the specifications.

They also require hyper to be Fast, for similar reasons that the web server frameworks do.

New Rust Web Developers

These are developers who are either new to Rust, or new to web servers, and have reached for hyper to start with.

It’s likely that these users don’t have strong opinions about how an HTTP server or client should work, just that it should handle all the things they normally assume it would. For these users, it would be best to quickly help them compare their own expectations with hyper’s capabilities, and may suggest reaching for higher-level, easier libraries instead.

Those that stick around after that recommendation are users that wish both to learn at a lower level, and to pick and choose what batteries they plug in to hyper as they move along. While they do care about the other tenets, that hyper is Understandable is of extra importance to them.

The Library

So with all that context in mind, what does hyper, the library, actually look like? This doesn’t highlight what is and isn’t present. What currently needs to change to reach this vision is left to individual version roadmaps.

Layers

In all cases, a user brings their own runtime and IO to work with hyper. The IO is provided to hyper, and hyper acts on top of it. hyper returns Futures that the user then decides how to poll, likely involving their runtime options.

architecture diagram

Protocol Codecs

hyper has dedicated codecs for the major HTTP versions. Each is internally designed to be Correct and Fast when it comes to encoding and decoding.

The individual codecs may be implemented as sub-crates, with a less-stable promise, to support the Flexible needs of some users who wish to build their own connection management, or customize encoding and decoding beyond what is officially supported.

Connection State Management

A Correct implementation includes more than just enforcing certain characters when encoding and decoding. Order of frames, and flags in certain frames can affect the state of the connection. Some examples of things enforced at this layer:

  • If a message has a content-length, enforce only that many bytes are read or written.
  • Reading a Response before a Request is even written implies a mismatched reply that should be interpreted as an error.
  • The presence of some headers, such as Connection: close, or the absence of others, such as content-length and transfer-encoding, can mean that the connection should terminate after the current message.
  • HTTP/2 and HTTP/3 may send connection-level frames that don’t pertain to any specific transaction, and must be read and handled regardless of if a user is currently checking for a message.

HTTP Role and Version Abstraction

This is the public API layer. Methods exposed are around sending and receiving http::Requests and http::Responses, not around framing specifics of the different versions. These are built around a client or server Connection interface.

By exposing this layer publicly, we take care of the Correct tenet, by not forcing the user to send the specific frames themselves. The API should be designed in a way that a user cannot easily (if at all) create an incorrect HTTP connection.

Motivated by the Flexible tenet, there are version-specific options that can be configured at this level, and version-specific functionality can usually be handled via http::Extensions.

Not quite stable, but utile (useful)

Beyond what is directly in the hyper crate, there are useful (utile) parts that may not meet hyper’s stability promise. Developing, experimenting, and exposing those parts is the purpose of the hyper-util crate. That crate does not have the same stability level as hyper. However, the goal is that things that other libraries might want to expose as a public dependency do not live in hyper-util forever, but rather stabilize and get promoted into hyper.

Exactly what gets put into hyper-util presently is kept in the roadmap documents.

Stability Promise

What even is hyper’s stability promise? Does it mean we are “done”? No. Will we ever make breaking changes again? Probably. We’ll still follow the semantic versioning.

Prior to 1.0, hyper has already only done breaking changes once a year. So 1 year isn’t much of a promise. We’ll have significant more use and understanding after a few years, and that could prompt some redesign.

As of this writing, we’ll promise that major versions of hyper are stable for 3 years. New features will come out in minor versions frequently. If it is determined necessary to make breaking changes to the API, we’ll save them for after the 3 years.

hyper also establishes a Minimum Supported Rust Version (MSRV). hyper will support Rust versions at least 6 months old. If a new Rust version is released with a feature hyper wishes to use, we won’t do so until at least 6 months afterwards. hyper will only ever require a new Rust version as a minor release (1.x), not as a patch (1.x.y).

Security

The security of hyper is a large part of what makes hyper protective. We make hyper secure via the combined efforts of being Correct, focusing on HTTP/*, and making it all Understandable.

Memory Safety

Being Correct requires that hyper be memory-safe. Using the Rust language gets us most of the way there. But there is the ability to write unsafe Rust. Does being Correct mean that we can never write unsafe code anywhere? Even if it helps make hyper Fast? We can, carefully.

How do we balance the two, so that hyper is secure?

hyper prefers not to have large modules of intertwined unsafe code. hyper does allow small unsafe blocks, no more than a few lines, where it’s easier to verify that the unsafe code was written Correctly.

Meticulous Testing

hyper’s test suite grows and grows. There’s a lot that needs to be right. Parsers, encoders, state machines. When easily isolated, those pieces have internal unit tests. But hyper also keeps a large list of growing integration tests that make sure all the parts are Correct.

Making writing new tests easy is a high priority. Investing in the testing infrastructure is a proven way to make sure hyper stays Correct and secure.

Constant Fuzzing

One thing is to know specific cases to test for. But we can’t know all the inputs or states that might cause a bug. That’s why hyper has rounds of fuzzing built into its CI. It’s also why hyper signs up for and uses resources to provide constant, around-the-clock fuzzing, always looking for something that hyper should be hardened against.

Security Process

hyper has an outlined SECURITY process, so we can safely report and fix issues.

Non-goals

After writing this up, it is easier to articulate what sorts of things many might associate with an HTTP library, but which are explicitly not for hyper. These are all things that are definitely out of scope.

  • TLS: We learned early that bundling TLS directly in hyper has problems. People also have very strong opinions about which TLS implementation to use. The design of hyper allows users to bring their own TLS.
  • Routing
  • Cookies
  • Not-HTTP: WebSockets, or other protocols that are built next to HTTP. It should be possible to use hyper to upgrade, but the actual next-protocol should be handled by a different library.