Need help to understanding hyper server with tokio core

I was reading a tutorial from hyper that uses tokio as core.

I couldn't understand why in this example they use two tokio core and below code is obscure.

let addr = "127.0.0.1:1337".parse().unwrap();
let mut core = tokio_core::reactor::Core::new().unwrap();
let server_handle = core.handle();
let client_handle = core.handle();

let serve = Http::new().serve_addr_handle(&addr, &server_handle, move || Ok(ResponseExamples(client_handle.clone()))).unwrap();

println!("Listening on http://{} with 1 thread.", serve.incoming_ref().local_addr());
let h2 = server_handle.clone();

What does this code do?

server_handle.spawn(serve.for_each(move |conn| {
h2.spawn(conn.map(|_| ()).map_err(|err| println!("serve error: {:?}", err)));
Ok(())
}).map_err(|_| ()));

why core.run is run with empty future ?

core.run(futures::future::empty::<(), ()>()).unwrap();

Note that they don't use 2 tokio Core instances, but rather 2 different Handles to it; each Handle here refers to the same Core, and the Core is the actual event loop (aka reactor). Handles are typically needed by components that want to interact with the event loop, such as spawning futures onto it. They're cloneable, and allow each component to have their own value (so don't need to capture references to them).

serve is a Stream where the items it yields are (essentially) TcpStreams (i.e. connections to the peer). serve.for_each(...) returns a Future that, for each accepted connection, spawns a handler of that connection - this is the h2.spawn(conn.map(...)... stuff. By spawn()'ing each accepted connection, these connections make progress independently inside the event loop (i.e. their processing/event notifications are independent of each other). ResponseExample is where the actual request handling logic lives, and it looks like it sets up two route handlers: one for a POST to /web_api and another for a GET of test.html. The conn.map(|_| ()).map_err(|err| println!(...)) just ignores the result of that handler, and also logs an error if it errors while handling the request.

server_handle.spawn(...) then submits this outer serve.for_each(...) future to the event loop, which will cause the event loop to drive it.

The Core in tokio 0.1 needs a root future to run - once that future completes, the loop finishes. When there's no concrete root future, but instead a bunch of background tasks (i.e. futures that are spawn()'ed), you can keep the loop running forever by giving it an empty future as the root - it never completes, and so the loop keeps going. This is mostly useful for examples, and not exactly how you'd write real code.

Why are you looking at how to use hyper with explicit tokio interaction? Most typical usage, I think, just lets hyper handle the tokio interaction internally.

2 Likes

Thank you for the complete answer :hibiscus::hibiscus: .

Why are you looking at how to use hyper with explicit tokio interaction? Most typical usage, I think, just lets hyper handle the tokio interaction internally.

I have two reasons for doing this.
first of all, hyper doesn't support SSL internally and I want to use OpenSSL with tokio.
Second, second, sometimes I need accept Non-HTTP contents, I couldn't find a right way for capturing this content with hyper.

Did you try hyper-tls for the SSL connections hyper_tls - Rust?

1 Like