Description
It would be nice to have hyper working without internally using tokio-proto. It should be possible to do this in a backwards-compatible way at first, such that the server still provides a ServerProto
implementation that makes use of the internal stuff, and that can be deprecated in the future.
Motivation
- The tokio-proto crate is being de-emphasized.
- We can do some things better, specifically for HTTP
- We can better detect if a body has a length if we control the
Stream
- We improve handling of
Expect: 100-continue
(Change 100-continue behavior to send when Body has been polled #838) - We can probably improve performance a little
- We can better detect if a body has a length if we control the
- Errors that are encountered reading, parsing, and writing are kind of just lost.
- They are given back to tokio-proto, but it's pretty tricky for a user to get those errors.
- It doesn't allow hyper to currently spawn a server that can handle both HTTP/1 and HTTP/2
API
Http::bind_connection(socket, service) -> Connection
Compared to the current bind_connection
:
- The
socket
isT: AsyncRead + AsyncWrite
. - The
service
is currently aS: Service
. - The
addr
is not needed anymore. Anyone wanting to know the address can store it on theirService
. - The
handle
is not provided, since it's only purpose was to spawn a task on it. With the desire to separate tokio from being a task executor, we can plan ahead.
The returned type is a Connection<T, S>
, which implements Future
. Instead of taking a Handle
, a user can instead receive this future and spawn it on any executor they desire.
Of course, since bind_connection
already exist, this would need a new name. It could be bind_connection2
, with the previous deprecated, with the intent to finish the replacement in 0.12.
Or maybe bind_connection
itself isn't the best of names. The action of this method is to apply the Http
protocol to a connection, aided by the Service
to respond to requests.
Connection
This is returned by Http::bind_connection
, and implements Future
.
Item = ()
Error = hyper::Error
If there are never any errors when processing the connection, the future yields Ok(Async::Ready)
when Http
says it should close.
If there are errors encountered that would require tearing down the connection, the future returns the Err
. Importantly, this allows users to very easily know if there was an error reading or writing the HTTP protocol.
Some unresolved questions:
- This could perhaps have the
Item
be the socket (T
) instead, in case there is desire to be able to do something with the socket after HTTP is "done" (perhaps due to a protocol upgrade). - When HTTP/2 is introduced, it is possible to have stream errors which aren't fatal to the connection, but do abort a stream. It wouldn't be feasible to make those stream errors be returned from the
Connection
future, as it should keep working.- Perhaps
Connection
shouldn't be aFuture
directly, but more like aStream
... ofErr
s... - The
Connection
could alternatively grow aon_stream_error<F>(callback: F)
whereF: Fn(hyper::Error)
or something...
- Perhaps
Server
The current Server
type is supposed to be an "easy mode" for starting up a TCP listener and accepting plain text HTTP requests. There is a related proposal (#1322) to make Server
accept any kind of listener, and be a Future
so users can execute it with their own executor.
Perhaps that proposal should be updated to support this new Connection
future:
impl Stream for Server
instead ofFuture
.- The
Server
could be aStream
of already boundConnection
s. - This would allow someone to use the
Server
to automatically callHttp::bind_connection
with a service, and manage graceful shutdown, while still being able to watch for errors that happen in that connection.
- The
We could add some convenience methods to Server
, like run_ignore_errors() -> impl Future
, so a user could easily just "run" the server without worrying about it being a Stream
of Connection
s.
Usage
Bare bones bind_connection
let http = Http::new();
// we've received a socket and made a service somewhere else
let conn = http.bind_connection(socket, service);
// whatever your executor is
executor.spawn(conn.map_err(move |err| {
println!("connection [{}] error: {}", addr, err);
}));
Fuller example with a tokio Core
and hyper Server
let mut core = Core::new()?;
let handle = core.handle();
let tcp = TcpListener::bind(addr, &handle).map(|listener| {
listener.incoming()
});
let tls = tcp.and_then(|tcp_streams| {
tcp_streams.and_then(|(socket, _addr)| {
TlsAcceptorThing::handshake(socket)
})
});
let server = tls.map(|tls_streams| {
let http = Http::new();
http.serve(tls_streams, || Ok(Echo))
});
let connections = server.and_then(move |srv| {
// srv is `Server`, so it is a `Stream<Item=Connection>`
srv.for_each(move |conn| {
// each connection should be its own task, so spawn it on the executor
handle.spawn(conn.map_err(|err| {
println!("http error: {}", err);
}));
Ok(())
})
});
core.run(connections)?;
Meta unresolved questions
- Perhaps the naming of types could be improved as well? (Of course, only finish renaming in 0.12)
- Currently:
Http
is more like aServerBuilder
orConnectionBuilder
...- If the
Http
were named a builder,Http
could replaceConnection
? Such thatServer
is aStream<Item=Http>
?
- Many people are used to reaching for a
Server
type, but hyper currently asks you to grab theHttp
to configure aServer
.
- Currently: