Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Http::new(core: &'a Core) #1075

Closed
scottlamb opened this issue Feb 24, 2017 · 31 comments
Closed

Http::new(core: &'a Core) #1075

scottlamb opened this issue Feb 24, 2017 · 31 comments

Comments

@scottlamb
Copy link

scottlamb commented Feb 24, 2017

Currently hyper::server::Server owns its own tokio::reactor::Core. I propose it borrow one instead.

This would allow better sharing the reactor. It'd mean that a Server could share the Core with things that it can't now, such as another hyper server (http + https). Also, it'd mean that the reactor could be available to be used prior to bringing up the hyper server.

For example, I'd like to use it with tokio_signal, which (copying from the example) is intended to be used like this:

let mut core = Core::new().unwrap();
let ctrlc = tokio_signal::ctrl_c(&core.handle());
let stream = core.run(ctrlc).unwrap();

I'd like to install the signal handler before being able to call server.handle() (this would allow me to do things such as flushing async logs prior to dying if a signal is received earlier in the startup process), and I'd like to advance the core myself to turn the future into a stream before starting to serve requests via server.run_until (just seems cleaner in terms of error handling).

Another possible benefit is that it'd make it easier to pass a Remote to the service. The new_service argument to Server::bind now has to be provided before hyper creates the Core. So to pass it the Remote, you have to stuff it later into a std::sync::Mutex that you passed into the lambda or in a lazy_static. That's inconvenient. I think with this way you could simply do something like this:

let core = Core::new();
let remote = core.remote();
let server = hyper::server::Http::new(&core)
    .bind(&addr, move || Ok(MyService(remote.clone())))
    .unwrap();

(Although if services are always intended to be created on a reactor thread, it might be even better for tokio's NewService::new_service to take a Handle, to avoid having to deal with Remote::handle() returning None. That change would make this benefit moot.)

It looks like I can use hyper with a borrowed Core today by avoiding Http::bind in favor of Http::bind_connection. But in doing so, I have to roll my own logic for not only accepting connections but also graceful shutdown, duplicating a lot of hyper/src/server/mod.rs. I'd rather not do that.

@seanmonstar
Copy link
Member

The expect evolution is that you could use TcpServer in tokio-proto, and hyper can remove some of it's TCP-specific handling. Half of the work for TcpServer has been merged, the part adding graceful shutdown is still a PR: tokio-rs/tokio-proto#135

I'd probably want to defer those kinds of improvements to the TcpServer itself, since it's not an issue unique to hyper.

cc @alexcrichton

@alexcrichton
Copy link
Contributor

Note that this is also why Hyper's server gives access to its handle and so does tokio-proto's proposed implementation

@radix
Copy link

radix commented Mar 2, 2017

Funny thing that I came to the Hyper issue tracker to search for issues about sharing the Core, to see it at the very top of the list :)

I'm struggling even with using Server::handle, as @alexcrichton suggested -- I think for similar reasons that @scottlamb was talking about, with wanting to set things up before starting the HTTP server. For example, my HTTP server needs to have access to some other reactor-using data, but I can't get the reactor until I start my HTTP server.

I'm still new to both Tokio and Rust, so I don't understand the discussion about TcpServer. It seems that TcpServerInstance also wants to own the Core, which seems similarly problematic. In other asynchronous frameworks I've used, the loop is always something you instantiate separately and pass to things that need it, to facilitate running several different things on the same event loop, so it's surprising to find this inverted structure in the Tokio ecosystem. Am I just misunderstanding this design?

FWIW, the design I'm struggling with is setting up HTTP handlers that will communicate with an "Actor" (I'm trying the unreleased Kabuki library) in order to serialize a certain subset of my operations. The Actor needs to run on the reactor, and so does the Hyper server, but I need to be able to give the actor to my HTTP-handler closure, so I can't set things up in the right order.

@alexcrichton
Copy link
Contributor

@radix keep in mind that these APIs are "intro" apis, they're "easy mode". You can always configure the server yourself more manually by calling the raw methods on tokio-proto that work with connected sockets instead of spinning up a TCP listener for you.

In that sense everything you've mentioned is supported, it just depends on how much logic you'll have to vendor locally one way or another.

You bring up some good points, though, and it may be useful for the server (in tokio-proto and perhaps in Hyper as well) to have a mode of operation where it's constructed from a handle rather than constructing the core itself.

@mwanner
Copy link

mwanner commented Mar 14, 2017

Coming from a Python Twisted background, I'm also pretty surprised about the protocol implementations creating or owning their event loops. I had a pretty hard time figuring out how the "easy mode" was supposed to work and actually fail to see anything easy, there.

I hacked together something that does work for me, here:
mwanner@ef7c13d
Seeing this discussion, I don't quite this a PR is appropriate, though.

From what I understand, changing hyper to use the TcpServer from tokio_proto should solve this issue. I'd appreciate that.

@seanmonstar
Copy link
Member

I had a pretty hard time figuring out how the "easy mode" was supposed to work and actually fail to see anything easy, there.

That's useful feedback, thank you. Were you specifically looking for an 'easy' method to give a Core to? Or you find even the usage of the easy method in the hello world example to hard to figure out?

From what I understand, changing hyper to use the TcpServer from tokio_proto should solve this issue. I'd appreciate that.

So, it currently does, ish. The Http type in hyper implements all the right things to be usable with the TcpServer in tokio-proto, but only tokio-proto master. tokio-proto received some improvements to make integration easier, but those haven't yet been released in a new version to crates.io.

@mwanner
Copy link

mwanner commented Mar 14, 2017

Thanks for your quick response.

Were you specifically looking for an 'easy' method to give a Core to?

..any method at all. AFAICT the server part insists on creating its own Core. Or am I missing something? That's what I tried to fix with my patch (talking about and patching against master).

Or you find even the usage of the easy method in the hello world example to hard to figure out?

I'm missing the event loop from examples/server.rs. How would I run two HTTP Server instances on the same event loop?

@seanmonstar
Copy link
Member

AFAICT the server part insists on creating its own Core. Or am I missing something?

The type hyper::server::Server is basically a specialized version of tokio-proto's TcpServer at the moment. It's created from Http::bind, and is meant for the 'easy' case, which is "start using this thread to handle HTTP connections".

I'm missing the event loop from examples/server.rs. How would I run two HTTP Server instances on the same event loop?

It's true that we're currently light on docs. I expect one "guide" in #805 to discuss the advanced uses of the Http type, such as creating a Core outside of hyper, listening for connections from wherever you want, and just using the Http type to drive the HTTP state on the provided connection.

To run multiple servers, you would setup some listeners (probably TcpListener), pipe their incoming() connections into the Http instance, and then have the Core just run for as long as you want.

Example-ish:

fn spawn_server(http: Http, listener: TcpListener, handle: Handle, factory: YourNewServiceType) {
    handle.spawn(listener.incoming().for_each(move |(socket, addr)| {
        http.bind_connection(&handle, socket, addr, factory.new_service()?);
        Ok(())
    }))
}

fn main() {
    let core = make_core();
    let listeners = make_listeners();
    for listener in listeners {
        spawn_server(Http::new(), listener, core.handle().clone(), || Ok(HelloWorld));
    }
    core.run(future::empty::<(), ()>()).unwrap();
}

@mwanner
Copy link

mwanner commented Mar 14, 2017

I see, I definitely missed this comment here:

/// This server is intended as a convenience for ...

I guess the name Server fooled me, but I see how it can be useful for "easy" cases.

Together with your example, I managed to put together something pretty minimal that compiles. I even filed a PR, but please feel free to reject, though.

@seanmonstar
Copy link
Member

The main pain point here is when you want to use hyper's server, but you need the Core beforehand, or wish to control the Core yourself.

Instead of being given a reference to a Core, it seems a solution could be to allow an Http to "spawn" a server-like task into a Handle. This could end up looking like:

let mut core = Core::new()?;
Http::new().spawn(addr, new_service, core.handle());

// other Core/Handle things whatever
core.run(empty)?;

An alternative API to do the same thing could be:

let mut core = Core::new()?;
let server_future = Http::new().spawn(addr, new_service);
core.handle().spawn(server_future);

This would ease having a server and a client on the same core:

let mut core = Core::new()?;
let handle = core.handle();

let client = Client::new(&handle);
handle.spawn(Http::new().spawn(addr, move || {
    Ok(Proxy { client: client.clone() })
});
core.run(empty)?;

This doesn't give the ability to run the Core using hyper's run_until logic, but the run_until seems generic enough that it should probably go in tokio-proto.

@nielsle
Copy link

nielsle commented Apr 26, 2017

The alternative API looks nice. My ideal solution would require changes to tokio-proto as well. I wonder if the following could be massaged to pass the borrow checker.

core.handle()
     .spawn2(|handle| Http::spawn_new(addr, new_service(handle))

@alexcrichton
Copy link
Contributor

@seanmonstar to be clear though, Server supports a custom Core today already, right? That was the intended purpose of the bind_connection method. So in that sense, just to confirm, you're thinking that this issue is targeted at "make using a custom Core easier", right?

If that's all true then what you proposed seems reasonable. The downside is that it may be difficult to shut down such a server. I'd probably recommend Http::spawn as a method which spawns directly into the reactor vs giving you a future that you then spawn. (although there may be downsides with that)

@seanmonstar
Copy link
Member

seanmonstar commented Apr 26, 2017

@alexcrichton Server doesn't support a custom Core, it creates its own. Http can be used with an Handle, though.

Playing with this, it does seem like it doesn't help a whole lot.

Before:

let mut core = Core::new()?;
let handle = core.handle();
let addr = "127.0.0.1:3000".parse().unwrap();
let http = Http::new();
let listener = TcpListener::bind(&addr, &handle)?;
handle.spawn(listening.incoming().for_each(move |(sock, addr)| {
    http.bind_connection(&handle, sock, addr, Echo);
    Ok(())
}).map_err(|_| ()));
core.run(empty)?;

After:

let mut core = Core::new()?;
let handle = core.handle();
let addr = "127.0.0.1:3000".parse().unwrap();
let http = Http::new().spawn(&handle, &addr, || Ok(Echo))?;
handle.spawn(http);
core.run(empty)?;

The benefit of it being a Future is that you can wrap it in another, allowing you to adjust how to shutdown the http future task.

@alexcrichton
Copy link
Contributor

Ah right yes sorry the distinction between Server and Http is important here.

It's true tha working with a future is a bit nicer for shutdown, but probably still not what you want? Unless you're tracking all instances of the service yourself it'd still be difficult to have a graceful shutdown semantics maybe?

@tomyan
Copy link

tomyan commented May 20, 2017

Just adding my voice to those who found it surprising that protocol handlers would either create the core themselves, or initiate the event loop and block - seems like creating and running the event loop should be separate concerns (as in twisted, node, etc) from the protocol handlers, so that they can be composed together freely within the same event loop.

IMO it would still be easy if there were three steps: create the core, set up the protocol handler (without blocking) & run the event loop (where the first and third steps are not specific to hyper or any other protocol handler), but would not lead to a jarring experience when you want to deal with multiple different types of events within the same event loop - in my case before I found this issue I started trying to create a wrapper around Server's run method that would return a future and not block (using a thread to run the server in) - not the sort of thing I would expect to need to do with a event/futures based library.

@tomyan
Copy link

tomyan commented May 23, 2017

@seanmonstar, @alexcrichton: I've been trying to take the example from @seanmonstar's comment above (titled "Before") and make it work in my code, but haven't had much luck. I have the following function for running a server with a handle to an existing core:

pub fn run(handle: &Handle) {
    let addr = "127.0.0.1:1337".parse().unwrap();
    let http = Http::new();
    let listener = TcpListener::bind(&addr, &handle).unwrap();
    handle.spawn(listener.incoming().for_each(move |(socket, peer)| {
        println!("got connection from {}", peer);
        http.bind_connection(&handle, socket, peer, || Ok(RequestHandler));
        Ok(())
    }));
}

(RequestHandler is a struct that implements hyper::server::Service, and works when I use hyper's run method)

When I run this I get the following errors:

error[E0277]: the trait bound `[closure@src/http_server.rs:53:53: 53:74]: hyper::client::Service` is not satisfied
  --> src/http_server.rs:53:14
   |
53 |         http.bind_connection(&handle, socket, peer, || Ok(RequestHandler));
   |              ^^^^^^^^^^^^^^^ the trait `hyper::client::Service` is not implemented for `[closure@src/http_server.rs:53:53: 53:74]`

error[E0271]: type mismatch resolving `<futures::stream::ForEach<tokio_core::net::Incoming, [closure@src/http_server.rs:51:47: 55:6 http:_, handle:_], std::result::Result<(), std::io::Error>> as futures::Future>::Error == ()`
  --> src/http_server.rs:51:12
   |
51 |     handle.spawn(listener.incoming().for_each(move |(socket, peer)| {
   |            ^^^^^ expected struct `std::io::Error`, found ()
   |
   = note: expected type `std::io::Error`
              found type `()`

error: aborting due to 2 previous errors

I have been trying various things without much luck and was wondering if you'd be able to advise where I'm going wrong?

Thanks

Tom

@seanmonstar
Copy link
Member

@tomyan ah, with no compiler checking my code blocks in github comments, I forgot a piece. The listener.incoming().for_each() returns a Future<(), io::Error>, since accepting a socket can cause an IO error. You'd want to stick some sort of .map_err(|_| ()) on the end of it, since the thing passed to handle.spawn must be Future<(), ()>.

@tomyan
Copy link

tomyan commented May 24, 2017

Thanks @seanmonstar, that makes perfect sense (I probably should have been able to work it out). Do you have any idea about the other error, which I'm still getting:

error[E0277]: the trait bound `[closure@src/http_server.rs:55:53: 55:74]: hyper::client::Service` is not satisfied
  --> src/http_server.rs:55:14
   |
55 |         http.bind_connection(&handle, socket, peer, || Ok(RequestHandler));
   |              ^^^^^^^^^^^^^^^ the trait `hyper::client::Service` is not implemented for `[closure@src/http_server.rs:55:53: 55:74]`

I don't think RequestHandler has changed from what I was successfully running with the server.run(...) method. It seems strange that it would be expecting the hyper::client::Service trait, rather than hyper::server::Service? Anyway, here's the definition of RequestHandler:

#[derive(Clone, Copy)]
struct RequestHandler;

impl Service for RequestHandler {
    type Request = Request;
    type Response = Response;
    type Error = hyper::Error;
    type Future = FutureResult<Response, hyper::Error>;

    fn call(&self, request: Request) -> Self::Future {
        match (request.method(), request.path()) {
            (&Get, "/") => self.slash(),
            _           => self.not_found()
        }
    }
}

impl RequestHandler {
    fn slash(&self) -> <RequestHandler as Service>::Future {
        ok(Response::new().with_body("Hello, World!\n"))
    }

    fn not_found(&self) -> <RequestHandler as Service>::Future {
        ok(
            Response::new()
                .with_status(StatusCode::NotFound)
                .with_body("404 Not Found\n")
        )
    }
}

Thanks

Tom

@alexcrichton
Copy link
Contributor

You'll want to change || Ok(RequestHandler) to just RequestHandler I believe

@tomyan
Copy link

tomyan commented May 24, 2017

Of course, this is handling one connection! Thanks for the pointer :-)

@bwo
Copy link

bwo commented Jun 24, 2017

FWIW I also found this far from an "easy mode", because the first thing I wanted to do with Hyper was create a server which, in handling connections, would itself make http requests (surely not a particularly advanced requirement). The examples using Client all require creating a Core yourself and getting the handle from it; the examples using Server just implement call for the Service trait … which, as far as I can tell, doesn't give you a way to get your hands on a handle.

@tomyan
Copy link

tomyan commented Jun 24, 2017

@bwo well put. I'm hoping this gets revisited once tokio-rs/tokio-core#212 (or similar) gets incorporated - I would expect libraries to use the default (per thread) loop, with an option to override. @seanmonstar, @alexcrichton what are your thoughts on this?

@alexcrichton
Copy link
Contributor

I'm personally still too wary to bake a default event loop into tokio-core itself, but anything we can do to make that more amenable externally I'd be find thinking about!

@radix
Copy link

radix commented Jun 26, 2017

The Twisted project developers have long regretted having a default event loop. It makes many things difficult. I recommend against it.

@olehdevua
Copy link

totally agree with @bwo, because "easy mode" which is promoted in examples creates impression that it's "right/standard way" to create a web server, but web server created in this way is useless in most cases, since as @bwo mentioned it has no ability to talk to other web-services or DBs.

@brendanzab
Copy link
Contributor

I also found this when trying to create a reverse proxy. I posted my problem in #1263. I'm guessing this issue is the more general one though, so perhaps we should close that one.

@izolyomi
Copy link

izolyomi commented Jul 26, 2017

I've also just encountered this issue or at least something very similar. Being new to Rust and Hyper, I'm trying to create a few liner where some costful operation is served with async Http.

My approach was to implement my Service::call() to spawn a task to the reactor and use and_then() on its resulted future to build the HTTP response serializing the result. But I've found no way to access the message loop handle anywhere in call().

Do we have a final conclusion about the best way how the message loop handle should be retrieved in such cases?

EDIT: I've found this issue and example. I recommend to include something like this in the official examples.

@mehcode
Copy link

mehcode commented Sep 16, 2017

If we could solve this it would remove a lot of code inside Shio that's currently present to work around this.


As a short-term fix for those interested, Shio can be used as a lightweight wrapper around Hyper to run an HTTP server where the service has direct access to the underlying handle.

fn main() {
  Shio::new(|context| {
    // reference to the actual reactor Handle so you can easily 
    // use postgres-tokio, hyper client, etc.
    context.handle() 
  }).run("127.0.0.1:7878");
}

@seanmonstar
Copy link
Member

I've put this off for a while claiming that it will be solved in tokio-proto, but I'm now leaning towards may as well fix it in hyper, if a generic way appears in the future, no harm done. The proposal is in #1322.

@alu
Copy link

alu commented Dec 4, 2017

Hi.

I create http Proxy Like http server with hyper server and client.
The server need keep-alive connection to origin server so it need shared hyper client.

I found this example and tried implementing it with reference.

My codes is here.

I think I can more simplfy if #1322 be implimanted, but my codes are better (maybe not best) for now?

@seanmonstar
Copy link
Member

The API in master (soon to be 0.12) has been updated such that a Server is now just a future, needing to be run on some executor. Along with the change to tokio separating the reactor from the executor, this is effectively done.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests